Python synthesizer stuff
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

411 行
11KB

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