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.

652 lines
18KB

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