Implement looping, PlaybackNext, and EndLoop
PlaybackNext replaces the current playback object with new media. Optionally, it waits until the end of the media. In this case, it only waits until the end of the current loop. EndLoop stops the playback at the end of the current loop. Note that PlaybackNext and EndLoop have no effect if there is no media currently playing.
This commit is contained in:
81
events.py
81
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)
|
||||
|
||||
Reference in New Issue
Block a user