diff --git a/events.py b/events.py index d476818..1463621 100644 --- a/events.py +++ b/events.py @@ -16,7 +16,10 @@ class Event: self._at.set_timings(timings) def set_audiencecontent(self, audiencecontent): - self.audiencecontent = audiencecontent + self._audiencecontent = audiencecontent + + def set_eventrunner(self, eventrunner): + self._eventrunner = eventrunner @property def scheduled(self): @@ -39,6 +42,9 @@ class Event: def stop(self): pass + def notify(self, *args, **kwargs): + pass + class Display(Event): def __init__(self, media, *args, **kwargs): super().__init__(*args, **kwargs) @@ -46,7 +52,7 @@ class Display(Event): class Playback(Event): - def __init__(self, media, device=None, channels=[1,2], tempo=None, beats=None, leadin=0, loop=False, *args, **kwargs): + def __init__(self, media, device=None, channels=[1,2], tempo=None, beats=None, leadin=0, loop=False, gain=0, *args, **kwargs): super().__init__(*args, **kwargs) self._media = media self._device = device @@ -57,6 +63,9 @@ class Playback(Event): self._beats = beats self._leadin = leadin self._loop = loop + self._gain = 10**(gain/10) # Convert from dB to linear gain + self._fadecurve = None + self._fadeframe = None def prepare(self): self._at._timings.register_tempo(self._tempo, self._beats, self._at) @@ -68,11 +77,13 @@ class Playback(Event): def start(self): print("Start playback") - self._data, fs = soundfile.read(self._media) + self._data, self._fs = soundfile.read(self._media) self._current_frame = 0 + self._fadecurve = None + self._fadeframe = None self._stream = sounddevice.OutputStream( - samplerate=fs, device=self._device, channels=self._channels, + samplerate=self._fs, device=self._device, channels=self._channels, callback=self.callback, finished_callback=self.finished_callback) self._stream.start() @@ -80,7 +91,12 @@ class Playback(Event): if status: print(status) chunksize = min(len(self._data) - self._current_frame, frames) - outdata[:chunksize, self._mapping] = self._data[self._current_frame:self._current_frame + chunksize] + 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 + if self._fadecurve is not None: + outdata[: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() @@ -88,7 +104,10 @@ class Playback(Event): def finished_callback(self): self._data = None + self._fadecurve = None + self._fadeframe = None print("Playback finished") + self._eventrunner.finished(self) def stop(self): print("Stop playback") @@ -96,6 +115,22 @@ class Playback(Event): self._stream.close(True) self._stream = None + def notify(self, message, fadetime=10, db=30, *args, **kwargs): + if message == 'fadeout': + self._fadecurve = np.logspace(0, -db/10, fadetime * self._fs) + self._fadeframe = 0 + +class FadeOut(Event): + def __init__(self, duration=10, db=30, *args, **kwargs): + super().__init__(*args, **kwargs) + self._duration = duration + self._db = db + + def start(self): + print("Fading out") + self._eventrunner.notify('fadeout', fadetime=self._duration, db=self._db) + self._eventrunner.finished(self) + class SetTempo(Event): def __init__(self, tempo=None, beats=None, *args, **kwargs): super().__init__(*args, **kwargs) @@ -106,7 +141,7 @@ class SetTempo(Event): self._at._timings.register_tempo(self._tempo, self._beats, self._at) def start(self): - pass + self._eventrunner.finished(self) def stop(self): pass @@ -120,7 +155,7 @@ class SetMark(Event): self._at._timings.register_mark(self._name, self._at) def start(self): - pass + self._eventrunner.finished(self) def stop(self): pass @@ -134,7 +169,7 @@ class ButtonMark(Event): self._at._timings.register_mark(self._name, None) def start(self): - pass + self._eventrunner.finished(self) def stop(self): pass @@ -146,10 +181,15 @@ class Lyrics(Event): self._duration = duration def start(self): - self.audiencecontent.new_content(display.DisplayLyrics(self._text)) + self._audiencecontent.new_content(display.DisplayLyrics(self._text)) print("Started displaying lyrics") print(self._text) def stop(self): - self.audiencecontent.new_content(display.DisplayContent()) + self._audiencecontent.new_content(display.DisplayContent()) print("Stopped displaying lyrics") + + def notify(self, message, *args, **kwargs): + if message == 'newdisplay': + self._eventrunner.finished(self) + diff --git a/gig.py b/gig.py index 13ac216..00a027f 100755 --- a/gig.py +++ b/gig.py @@ -24,6 +24,8 @@ class EventRunner: self._playing = None self._active = [] self._currentitem = None + for item in self._gigconfig['items']: + self._gigconfig['items'][item].set_eventrunner(self) def set_audiencecontent(self, audiencecontent): for item in self._gigconfig['items']: @@ -75,7 +77,8 @@ class EventRunner: self._playing = None self._selected = None for event in self._active: - event.stop() + if event is not None: + event.stop() self._active = [] if self._currentitem is not None: self._currentitem.reset() @@ -84,6 +87,17 @@ class EventRunner: event.start() self._active.append(event) + def notify(self, *args, **kwargs): + for event in self._active: + if event is not None: + event.notify(*args, **kwargs) + + def finished(self, event): + # We might get called back here from within a notify or other such construct + for i, e in enumerate(self._active): + if e is event: + self._active[i] = None + def pause(self): pass diff --git a/item.py b/item.py index 10a20d3..a7609a4 100644 --- a/item.py +++ b/item.py @@ -51,3 +51,7 @@ class Item: def set_audiencecontent(self, audiencecontent): for event in self._events: event.set_audiencecontent(audiencecontent) + + def set_eventrunner(self, eventrunner): + for event in self._events: + event.set_eventrunner(eventrunner)