Python synthesizer stuff
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

537 lines
15KB

  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 self.graph is None:
  42. channels.fill(0)
  43. return
  44. try:
  45. # Process audio graph
  46. buf_len = channels.shape[0]
  47. self.chunk_size = buf_len
  48. next_chunk = next(self.graph)
  49. # Do we need to mix from mono to stereo?
  50. if len(next_chunk.shape) == 1:
  51. next_chunk = np.dot(np.reshape(next_chunk, (-1, 1)), ((1, 1),))
  52. #print channels.shape, next_chunk.shape
  53. np.round(next_chunk * 32767.5, 0, out=channels)
  54. # Advance sampleclock
  55. self.samples += buf_len
  56. # When this happens set the graph back to
  57. # None, this suppresses futher errors, and let's
  58. # a first exception slip through, causing a backtrace
  59. # in ysynth.
  60. except StopIteration:
  61. self.graph = None
  62. def set_graph(self, graph):
  63. if isinstance(graph, tuple):
  64. graph = ChannelJoiner(*graph)
  65. graph.set_synth(self)
  66. self.graph = iter(graph)
  67. def get_graph(self):
  68. return self.graph
  69. def __del__(self):
  70. """
  71. Deinitialise synth before being removed from mem
  72. """
  73. print "Deinitialising"
  74. self.deinit()
  75. def set_callback(self, func):
  76. """
  77. Set audio output function.
  78. Without any callback the synthesizer will simply output silence.
  79. The callback should adhere the following signature:
  80. def callback(channels):
  81. channels shall contain a list of lists and each list
  82. contains a large block of 0.0 floats describing the next set of samples.
  83. Those floats should be set to the chunk of audio.
  84. """
  85. _ysynth_set_callback(self.context, func)
  86. def get_callback(self):
  87. return _ysynth_get_callback(self.context)
  88. class YConstant(object):
  89. """
  90. Unchanging signal output.
  91. """
  92. def __init__(self, const):
  93. self.const = float(const)
  94. def __iter__(self):
  95. return iter(self())
  96. def __call__(self):
  97. while True:
  98. x = np.empty((self.synth.chunk_size,))
  99. x.fill(self.const)
  100. yield x
  101. def set_synth(self, synth):
  102. self.synth = synth
  103. # XXX: Write a function that broadcasts
  104. # various channeled signals to a common
  105. # shape for operations that require this.
  106. def broadcast(stream):
  107. """
  108. This generator takes an iterator yielding a variable number of arrays and
  109. scalars and broadcasts any arrays and scalars that require this to the
  110. correct amount of channels. If an array argument could not be normalized,
  111. this function raises a ValueError with the appropriate information.
  112. """
  113. channels = 0
  114. channels_shape = None
  115. require_broadcast = False
  116. stream = iter(stream)
  117. # First, check for a single multichannel count, if not
  118. # we need to raise an exception.
  119. args = next(stream)
  120. for c in args:
  121. if len(c.shape) == 2 and c.shape[1] != 1:
  122. if not channels:
  123. channels = c.shape[1]
  124. channels_shape = c.shape
  125. else:
  126. if channels != c.shape[1]:
  127. raise ValueError("Cannot broadcast input signals of"
  128. " shape %r and %r together." % (channels_shape, c.shape))
  129. else:
  130. # There is no need for broadcasting if no differently sized
  131. # channels occur
  132. if channels:
  133. require_broadcast = True
  134. # Okay we need to broadcast every argument to 'channels' nr. of channels.
  135. if require_broadcast:
  136. while True:
  137. r = []
  138. for c in args:
  139. if len(c.shape) != 2:
  140. c = np.reshape(c, (-1, 1))
  141. if c.shape[1] != channels:
  142. r.append(np.dot(c, ([1] * channels,)))
  143. else:
  144. r.append(c)
  145. yield tuple(r)
  146. args = next(stream)
  147. # Or we don't need to broadcast in which case we simply pass
  148. # on our results
  149. while True:
  150. yield(args)
  151. args = next(stream)
  152. class YAudioGraphNode(object):
  153. """
  154. Base audio graph node
  155. This base class provides YSynth's DSL behaviours such as
  156. adding, substracting and the sample iteration protocol.
  157. """
  158. def __init__(self, *inputs):
  159. self.inputs = []
  160. lens = 0
  161. # Convert input arguments to audio streaming
  162. # components
  163. for stream in inputs:
  164. # Attempt to join multiple channels
  165. if isinstance(stream, tuple):
  166. if len(stream) == 1:
  167. stream = stream[1]
  168. else:
  169. stream = ChannelJoiner(*stream)
  170. # Convert constant value to audio stream
  171. if not isinstance(stream, YAudioGraphNode):
  172. stream = YConstant(stream)
  173. # Append to input signals
  174. self.inputs.append(stream)
  175. self.inputs = tuple(self.inputs)
  176. #for stream in inputs:
  177. # if isinstance(stream, YAudioGraphNode):
  178. # pass
  179. # # Make sure all multi-channels share the same size
  180. # if len(stream) != 1:
  181. # if not lens:
  182. # lens = len
  183. # elif lens != len(stream):
  184. # # TODO: Expand error info to contain sizes
  185. # raise ValueError("Cannot combine different sized "
  186. # "multi-channel streams")
  187. #self.channels = lens
  188. def __iter__(self):
  189. """
  190. Initialise graph for synthesis, link to sampleclock.
  191. """
  192. # XXX This function is not graph cycle safe
  193. # FIXME: I need to unpack the channels from the input streams
  194. # multiplex any mono-streams if there are multi-channel streams
  195. # available, and then initialise every set of streams' component
  196. # generator function by calling self with the correct arguments.
  197. # Setup input streams
  198. for stream in self.inputs:
  199. stream.set_synth(self.synth)
  200. # Setup self
  201. # The self.samples variable is used for protecting against
  202. # multiple next() calls, next will only evaluate the next
  203. # set of input samples if the synth's sampleclock has changed
  204. self.samples = self.synth.samples - 1
  205. # XXX self.last_sample is currently not initialised on purpose
  206. # maybe this must be changed at a later time.
  207. # Connect the actual generator components
  208. sample_iter = iter(self(broadcast(izip(*self.inputs))))
  209. # Build the sample protection function
  210. def sample_func():
  211. """
  212. Make sure multiple next() calls during the same
  213. clock cycle yield the same sample value.
  214. """
  215. while True:
  216. if self.samples != self.synth.samples:
  217. self.last_sample = next(sample_iter)
  218. self.samples = self.synth.samples
  219. yield self.last_sample
  220. return sample_func()
  221. def set_synth(self, synth):
  222. """
  223. Set this component's synthesizer.
  224. This is mainly useful for reading the synth's sampleclock. However every
  225. active component requires a valid synthesizer.
  226. """
  227. self.synth = synth
  228. def get_synth(self, synth):
  229. """
  230. Return this component's synthesizer.
  231. """
  232. return self.synth
  233. def __add__(self, other):
  234. return Adder(self, other)
  235. def __radd__(self, other):
  236. return Adder(other, self)
  237. def __sub__(self, other):
  238. return Subtractor(self, other)
  239. def __rsub__(self, other):
  240. return Subtractor(other, self)
  241. def __mul__(self, other):
  242. return Multiplier(self, other)
  243. def __rmul__(self, other):
  244. return Multiplier(other, self)
  245. def __div__(self, other):
  246. return Divisor(self, other)
  247. def __rdiv__(self, other):
  248. return Divisor(other, self)
  249. def __getitem__(self, key):
  250. """
  251. Return a delayed version of the output signal.
  252. """
  253. # Return delay
  254. if isinstance(key, int) or isinstance(key, float) or \
  255. isinstance(key, YAudioGraphNode):
  256. return Delay(self, key)
  257. # Split off channels
  258. elif isinstance(key, slice):
  259. if x.start is None:
  260. start = 0
  261. if x.stop is None:
  262. # XXX Channels here
  263. # FIXME: Incomplete code
  264. stop = self.hoi
  265. if x.step is None:
  266. step = 1
  267. rng = xrange(start, stop, step)
  268. else:
  269. rng = key
  270. # XXX FIXME Incomplete channel split code
  271. return # Return channel ranges
  272. def __next__(self):
  273. """
  274. Process next sample.
  275. """
  276. def __call__(self):
  277. raise NotImplementedError("You need to inherit this class")
  278. def process(self, *streams):
  279. raise NotImplementedError("You need to inherit this class")
  280. # Basic signal arithmetic
  281. class Adder(YAudioGraphNode):
  282. def __call__(self, l):
  283. for a, b in l:
  284. yield a + b
  285. class Subtractor(YAudioGraphNode):
  286. def __call__(self, l):
  287. for a, b in l:
  288. yield a - b
  289. class Multiplier(YAudioGraphNode):
  290. def __call__(self, l):
  291. for a, b in l:
  292. yield a * b
  293. class Divisor(YAudioGraphNode):
  294. def __call__(self, l):
  295. for a, b in l:
  296. yield a / b
  297. # Sample delay
  298. # Currently uses a fixed buffer of 4096 samples
  299. # should support dynamic buffer size in the future
  300. class Delay(YAudioGraphNode):
  301. def __call__(self, l):
  302. buf = None
  303. samples = 0
  304. for sig, delay in l:
  305. if buf is None:
  306. if len(sig.shape) == 2:
  307. buf = np.zeros((4096, sig.shape[1]))
  308. else:
  309. buf = np.zeros((4096,))
  310. # Update delay buffer
  311. if samples + self.synth.chunk_size > buf.shape[0]:
  312. s_left = buf.shape[0] - samples
  313. buf[samples:] = sig[:s_left]
  314. buf[:self.synth.chunk_size - s_left] = sig[s_left:]
  315. else:
  316. buf[samples:samples + self.synth.chunk_size] = sig
  317. # Construct delayed signal from delay input
  318. delayed_idx = np.array((np.arange(samples, samples +
  319. self.synth.chunk_size) - delay) % buf.shape[0], dtype=int)
  320. yield buf[delayed_idx]
  321. # Finally update sample pointer
  322. samples = (samples + self.synth.chunk_size) % buf.shape[0]
  323. # Tracker
  324. # XXX: Incomplete class
  325. class SimpleStaticTracker(YAudioGraphNode):
  326. """
  327. Think MOD file.
  328. """
  329. def __init__(self, track):
  330. self.track = track
  331. # Base oscillator class
  332. class YOscillator(YAudioGraphNode):
  333. def __call__(self, l):
  334. def cycle_gen():
  335. """
  336. This function generates the oscillation cycle
  337. upon which all basic decoupled oscillators base their output
  338. signal.
  339. A decoupled oscillator is an oscillator
  340. with its own cycle generator. These oscillators respond well to
  341. incoming frequency changes, but due to the limitations of floating
  342. point are at risk of drifting out of phase, coupled oscillators
  343. are always in phase with each other.
  344. The generated cycle ranges from 0.0 to 1.0 exclusive.
  345. """
  346. last_cycle = [0.0]
  347. for freq, in l:
  348. # The last_cycle will only be a list during the initial
  349. # iteration, perfect for a 'once' statement, we want
  350. # to force the first cycle value to 0.
  351. if isinstance(last_cycle, list):
  352. if isinstance(freq, np.ndarray):
  353. ifreq = freq[0]
  354. else:
  355. ifreq = freq
  356. last_cycle = [-(ifreq / float(self.synth.samplerate))]
  357. # Compute cycle using IIR filter and np.fmod
  358. # XXX: I won't work with multichannel input
  359. cycle, last_cycle = lfilter([1], [1, -1], freq /
  360. float(self.synth.samplerate) *
  361. np.ones((self.synth.chunk_size,) + freq.shape[1:]),
  362. zi=last_cycle, axis=0)
  363. yield np.fmod(cycle, 1.0)
  364. last_cycle = np.fmod(last_cycle, 1.0)
  365. return self.oscillate(cycle_gen())
  366. def oscillate(self, l):
  367. raise NotImplementedError("Inherit this class")
  368. # Channel modifiers
  369. class ChannelJoiner(YAudioGraphNode):
  370. """
  371. Join multiple input channels into a single
  372. output stream.
  373. """
  374. def __call__(self, l):
  375. for chunks in l:
  376. yield np.hstack(np.reshape(chunk, (-1, 1)) for chunk in chunks)
  377. # Noise signals
  378. # XXX: I am probably not White Noise, fix me
  379. # up at a later time.
  380. class WhiteNoise(YAudioGraphNode):
  381. def __call__(self, l):
  382. while True:
  383. yield np.random.rand(self.synth.chunk_size)
  384. # Basic oscillators
  385. class Sin(YOscillator):
  386. """
  387. Sine wave oscillator
  388. """
  389. def oscillate(self, l):
  390. for pos in l:
  391. yield np.sin(2 * pi * pos)
  392. class Cos(YOscillator):
  393. """
  394. Cosine wave oscillator
  395. """
  396. def oscillate(self, l):
  397. for pos in l:
  398. yield np.cos(2 * pi * pos)
  399. class Saw(YOscillator):
  400. """
  401. Saw wave oscillator
  402. """
  403. def oscillate(self, l):
  404. for pos in l:
  405. yield pos * 2.0 - 1.0
  406. class RevSaw(YOscillator):
  407. """
  408. Reverse Saw wave oscillator
  409. """
  410. def oscillate(self, l):
  411. for pos in l:
  412. yield -(pos * 2.0 - 1.0)
  413. class Square(YOscillator):
  414. """
  415. Square wave oscillator
  416. """
  417. def oscillate(self, l):
  418. for pos in l:
  419. #yield 1.0 if pos < 0.5 else -1.0
  420. pos = pos - 0.5
  421. yield -(np.abs(pos) / pos)
  422. class Pulse(YOscillator):
  423. """
  424. Pulse oscillator
  425. """
  426. def oscillate(self, l):
  427. zi = [1.0]
  428. for pos in l:
  429. pos, zi = lfilter([-1, 1], [1], pos, zi=zi)
  430. yield np.array(pos > 0, dtype=float)
  431. # Flanger effect
  432. # FIXME: Incomplete class
  433. class Flanger(YAudioGraphNode):
  434. """
  435. Perform virtual tape flange by mixing the
  436. input signal with a delayed version.
  437. """