diff --git a/events.py b/events.py index 84bf2e2..7488faf 100644 --- a/events.py +++ b/events.py @@ -107,6 +107,9 @@ class Playback(Event): self._current_frame = 0 self._fadecurve = None self._fadeframe = None + self._nextdata = None + self._nextfs = None + self._delay = False self._stream = sounddevice.OutputStream( samplerate=self._fs, device=self._device, channels=self._channels, @@ -116,17 +119,44 @@ class Playback(Event): def callback(self, outdata, frames, time, status): if status: print(status) + if self._nextdata is not None and not self._delay: + self.callback_startnew() + offset = 0 + while frames > 0: + chunksize = self.callback_fillchunk(outdata, offset, frames) + offset += chunksize + frames -= chunksize + if frames > 0: + if self._nextdata is not None: + self.callback_startnew() + elif self._loop: + self._current_frame = 0 + else: + outdata[offset:, self._mapping] = 0 + raise sounddevice.CallbackStop() + self._current_frame += frames + frames = 0 + + def callback_fillchunk(self, outdata, offset, frames): chunksize = min(len(self._data) - self._current_frame, frames) if self._fadecurve is not None: chunksize = min(chunksize, len(self._fadecurve) - self._fadeframe) - outdata[:chunksize, self._mapping] = self._data[self._current_frame:self._current_frame + chunksize] * self._gain + outdata[offset:offset + chunksize, self._mapping] = self._data[self._current_frame:self._current_frame + chunksize] * self._gain if self._fadecurve is not None: - outdata[:chunksize, self._mapping] *= self._fadecurve[self._fadeframe:self._fadeframe + chunksize, np.newaxis] + outdata[offset:offset + chunksize, self._mapping] *= self._fadecurve[self._fadeframe:self._fadeframe + chunksize, np.newaxis] self._fadeframe += chunksize - if chunksize < frames: - outdata[chunksize:, self._mapping] = 0 - raise sounddevice.CallbackStop() self._current_frame += chunksize + return chunksize + + def callback_startnew(self): + self._data = self._nextdata + self._fs = self._nextfs + self._loop = self._nextloop + self._current_frame = 0 + self._nextdata = None + self._nextfs = None + self._fadecurve = None + self._fadeframe = None def finished_callback(self): self._data = None @@ -141,10 +171,21 @@ class Playback(Event): self._stream.close(True) self._stream = None - def notify(self, message, fadetime=10, db=30, *args, **kwargs): + def replace(self, media, delay=False, loop=False): + self._nextdata, self._nextfs = soundfile.read(media) + self._delay = delay + self._nextloop = loop + # We really should check that the media is the sampe shape as the + # existing playback. But for now, let's trust the user. *cough* + + def notify(self, message, fadetime=10, db=30, media=None, delay=False, *args, **kwargs): if message == 'fadeout': self._fadecurve = np.logspace(0, -db/10, fadetime * self._fs) self._fadeframe = 0 + elif message == 'replaceplayback': + self.replace(media, delay) + elif message == 'endloop': + self._loop = False class FadeOut(Event): def __init__(self, duration=10, db=30, *args, **kwargs): @@ -160,6 +201,34 @@ class FadeOut(Event): self._eventrunner.notify('fadeout', fadetime=self._duration, db=self._db) self._eventrunner.finished(self) +class EndLoop(Event): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def __repr__(self): + return '{}(at={})'.format(type(self).__name__, self._at) + + def start(self): + print("Ending loop") + self._eventrunner.notify('endloop') + self._eventrunner.finished(self) + + +class PlaybackNext(Event): + def __init__(self, media, delay=False, loop=False, *args, **kwargs): + super().__init__(*args, **kwargs) + self._media = media + self._delay = delay + self._loop = loop + + def __repr__(self): + return '{}(at={}, media={}, delay={}, loop={})'.format(type(self).__name__, self._at, self._media, self._delay, self._loop) + + def start(self): + print("Replacing playback") + self._eventrunner.notify('replaceplayback', media=self._media, delay=self._delay, loop=self._loop) + self._eventrunner.finished(self) + class SetTempo(Event): def __init__(self, tempo=None, beats=None, *args, **kwargs): super().__init__(*args, **kwargs)