Python synthesizer stuff
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

355 lines
9.3KB

  1. """
  2. Python audio synthesis framework.
  3. """
  4. from pymod import _ysynth_init, _ysynth_init, _ysynth_set_callback
  5. from math import cos, sin, pi, modf
  6. from itertools import izip
  7. __all__ = ['YSynth', 'Sin', 'Cos', 'Saw', 'RevSaw', 'Square', 'Pulse']
  8. class YSynth(object):
  9. """
  10. YSynth synthesis and audio context.
  11. """
  12. def __init__(self, samplerate=44100, channels=2):
  13. """
  14. Setup a simple synthesizer.
  15. samplerate: Samples/second
  16. channels: Number of output channels.
  17. e.g. 1 for mono, 2 for stereo
  18. """
  19. self.context = _ysynth_init(samplerate, channels)
  20. self.samples = 0
  21. self.volume = 0.75
  22. # XXX need to fetch 'acquired' values from ysynth context
  23. self.samplerate = samplerate
  24. self.channels = channels
  25. self.graph = None
  26. self.set_callback(self.default_callback)
  27. def deinit(self):
  28. """
  29. Shutdown synthesizer, afterwards this object becomes useless.
  30. """
  31. if self.context:
  32. _ysynth_shutdown(self.context)
  33. self.context = None
  34. def default_callback(self, *channels):
  35. """
  36. Default synthesis callback.
  37. """
  38. # Outputs silence if no graph available
  39. if not self.graph:
  40. return
  41. # Process audio graph
  42. buf_len = len(channels[0])
  43. for i in xrange(buf_len):
  44. next_sample = next(self.graph)
  45. for j in xrange(len(channels)):
  46. channels[j][i] = next_sample
  47. # Advance sampleclock
  48. self.samples += 1
  49. def set_graph(self, graph):
  50. graph.set_synth(self)
  51. self.graph = iter(graph)
  52. def get_graph(self):
  53. return self.graph
  54. def __del__(self):
  55. """
  56. Deinitialise synth before being removed from mem
  57. """
  58. print "Deinitialising"
  59. self.deinit()
  60. def set_callback(self, func):
  61. """
  62. Set audio output function.
  63. Without any callback the synthesizer will simply output silence.
  64. The callback should adhere the following signature:
  65. def callback(channels):
  66. channels shall contain a list of lists and each list
  67. contains a large block of 0.0 floats describing the next set of samples.
  68. Those floats should be set to the chunk of audio.
  69. """
  70. _ysynth_set_callback(self.context, func)
  71. def get_callback(self):
  72. return _ysynth_get_callback(self.context)
  73. class YConstant(object):
  74. """
  75. Unchanging signal output.
  76. """
  77. def __init__(self, const):
  78. self.const = float(const)
  79. def __iter__(self):
  80. return iter(self())
  81. def __call__(self):
  82. while True:
  83. yield self.const
  84. def set_synth(self, synth):
  85. pass
  86. class YAudioGraphNode(object):
  87. """
  88. Base audio graph node
  89. This base class provides YSynth's DSL behaviours such as
  90. adding, substracting and the sample iteration protocol.
  91. """
  92. def __init__(self, *inputs):
  93. self.inputs = []
  94. lens = 0
  95. for stream in inputs:
  96. if not isinstance(stream, YAudioGraphNode):
  97. stream = YConstant(stream)
  98. self.inputs.append(stream)
  99. self.inputs = tuple(self.inputs)
  100. #for stream in inputs:
  101. # if isinstance(stream, YAudioGraphNode):
  102. # pass
  103. # # Make sure all multi-channels share the same size
  104. # if len(stream) != 1:
  105. # if not lens:
  106. # lens = len
  107. # elif lens != len(stream):
  108. # # TODO: Expand error info to contain sizes
  109. # raise ValueError("Cannot combine different sized "
  110. # "multi-channel streams")
  111. #self.channels = lens
  112. def __iter__(self):
  113. """
  114. Initialise graph for synthesis, link to sampleclock.
  115. """
  116. # XXX This function is not graph cycle safe
  117. # FIXME: I need to unpack the channels from the input streams
  118. # multiplex any mono-streams if there are multi-channel streams
  119. # available, and then initialise every set of streams' component
  120. # generator function by calling self with the correct arguments.
  121. # Setup input streams
  122. for stream in self.inputs:
  123. stream.set_synth(self.synth)
  124. # Setup self
  125. # The self.samples variable is used for protecting against
  126. # multiple next() calls, next will only evaluate the next
  127. # set of input samples if the synth's sampleclock has changed
  128. self.samples = self.synth.samples - 1
  129. # XXX self.last_sample is currently not initialised on purpose
  130. # maybe this must be changed at a later time.
  131. # Connect the actual generator components
  132. sample_iter = iter(self(izip(*self.inputs)))
  133. # Build the sample protection function
  134. def sample_func():
  135. """
  136. Make sure multiple next() calls during the same
  137. clock cycle yield the same sample value.
  138. """
  139. while True:
  140. if self.samples != self.synth.samples:
  141. self.last_sample = next(sample_iter)
  142. self.samples = self.synth.samples
  143. yield self.last_sample
  144. return sample_func()
  145. def set_synth(self, synth):
  146. """
  147. Set this component's synthesizer.
  148. This is mainly useful for reading the synth's sampleclock. However every
  149. active component requires a valid synthesizer.
  150. """
  151. self.synth = synth
  152. def get_synth(self, synth):
  153. """
  154. Return this component's synthesizer.
  155. """
  156. return self.synth
  157. def __add__(self, other):
  158. return Adder(self, other)
  159. def __radd__(self, other):
  160. return Adder(other, self)
  161. def __sub__(self, other):
  162. return Subtractor(self, other)
  163. def __rsub__(self, other):
  164. return Subtractor(other, self)
  165. def __mul__(self, other):
  166. return Multiplier(self, other)
  167. def __rmul__(self, other):
  168. return Multiplier(other, self)
  169. def __div__(self, other):
  170. return Divisor(self, other)
  171. def __rdiv__(self, other):
  172. return Divisor(other, self)
  173. def __getitem__(self, delay):
  174. """
  175. Return a delayed version of the output signal.
  176. """
  177. return Delay(self, delay)
  178. def __next__(self):
  179. """
  180. Process next sample.
  181. """
  182. def __call__(self):
  183. raise NotImplementedError("You need to inherit this class")
  184. def process(self, *streams):
  185. raise NotImplementedError("You need to inherit this class")
  186. # Basic signal arithmetic
  187. class Adder(YAudioGraphNode):
  188. def __call__(self, l):
  189. for a, b in l:
  190. yield a + b
  191. class Subtractor(YAudioGraphNode):
  192. def __call__(self, l):
  193. for a, b in l:
  194. yield a - b
  195. class Multiplier(YAudioGraphNode):
  196. def __call__(self, l):
  197. for a, b in l:
  198. yield a * b
  199. class Divisor(YAudioGraphNode):
  200. def __call__(self, l):
  201. for a, b in l:
  202. yield a / b
  203. # Sample delay
  204. class Delay(YAudioGraphNode):
  205. def __call__(self, l):
  206. buf = [0.0] * 4096
  207. samples = 0
  208. for sig, delay in l:
  209. buf[samples] = sig
  210. yield buf[(samples - delay) % 4096]
  211. samples = (samples + 1) % 4096
  212. # Base oscillator class
  213. class YOscillator(YAudioGraphNode):
  214. def __call__(self, l):
  215. def cycle_gen():
  216. """
  217. This function generates the oscillation cycle
  218. upon which all basic decoupled oscillators base their output
  219. signal.
  220. A decoupled oscillator is an oscillator
  221. with its own cycle generator. These oscillators respond well to
  222. incoming frequency changes, but due to the limitations of floating
  223. point are at risk of drifting out of phase, coupled oscillators
  224. are always in phase with each other.
  225. The generated cycle ranges from 0.0 to 1.0 exclusive.
  226. """
  227. cycle = 0.0
  228. samples = 0
  229. for freq, in l:
  230. cycle = freq * samples / float(self.synth.samplerate)
  231. yield modf(cycle)[0]
  232. samples = (samples + 1) % self.synth.samplerate
  233. return self.oscillate(cycle_gen())
  234. def oscillate(self, l):
  235. raise NotImplementedError("Inherit this class")
  236. # Basic oscillators
  237. class Sin(YOscillator):
  238. """
  239. Sine wave oscillator
  240. """
  241. def oscillate(self, l):
  242. for pos in l:
  243. yield sin(pos * 2 * pi)
  244. class Cos(YOscillator):
  245. """
  246. Cosine wave oscillator
  247. """
  248. def oscillate(self, l):
  249. for pos in l:
  250. yield cos(pos * 2 * pi)
  251. class Saw(YOscillator):
  252. """
  253. Saw wave oscillator
  254. """
  255. def oscillate(self, l):
  256. for pos in l:
  257. yield pos * 2.0 - 1.0
  258. class RevSaw(YOscillator):
  259. """
  260. Reverse Saw wave oscillator
  261. """
  262. def oscillate(self, l):
  263. for pos in l:
  264. yield -(pos * 2.0 - 1.0)
  265. class Square(YOscillator):
  266. """
  267. Square wave oscillator
  268. """
  269. def oscillate(self, l):
  270. for pos in l:
  271. yield 1.0 if pos < 0.5 else -1.0
  272. class Pulse(YOscillator):
  273. """
  274. Pulse oscillator
  275. """
  276. def oscillate(self, l):
  277. prev_pos = 1.0
  278. for pos in l:
  279. yield 1.0 if pos < prev_pos else 0.0
  280. prev_pos = pos