#!/usr/bin/env python3 # # Sampler - play video samples interactively according to midi messages # # Copyright (C) 2016 Antonio Ospite # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import mido import gi gi.require_version('Gst', '1.0') from gi.repository import Gst Gst.init(None) from gi.repository import GObject GObject.threads_init() import vidi class Sampler(vidi.Player): def __init__(self, videofont_path): playbin = Gst.ElementFactory.make("playbin", "player") vidi.Player.__init__(self, playbin) self.videofont_path = videofont_path self.last_note = None self.uri = Gst.filename_to_uri(self.get_sample_path(self.last_note)) self.pipeline.set_property("uri", self.uri) self.pipeline.connect("about-to-finish", self.on_about_to_finish) def on_about_to_finish(self, element): element.set_property("uri", self.uri) def get_sample_path(self, note): if note is None: sample_name = "rest" else: sample_name = vidi.MidiNote(note).name return "%s/sample_%s.webm" % (self.videofont_path, sample_name) def midi_message_cb(self, msg): if vidi.is_note(msg): new_note = msg.note # The logic is as follows: # - Always play a new note on; # - only play silence if the note off is the same one that was # played last; # - otherwise do nothing. if vidi.is_note_on(msg): note = new_note elif vidi.is_note_off(msg) \ and new_note == self.last_note: note = None else: note = self.last_note if note != self.last_note: self.last_note = note sample_path = self.get_sample_path(note) if os.path.exists(sample_path): self.switch(sample_path) else: print("Warning: videofont is missing sample '%s'" % sample_path) def switch(self, sample_path): print("Next: %s" % sample_path) self.uri = Gst.filename_to_uri(sample_path) seek_event = Gst.Event.new_seek(1.0, Gst.Format.TIME, Gst.SeekFlags.FLUSH, Gst.SeekType.END, -1, Gst.SeekType.NONE, 0) self.pipeline.send_event(seek_event) class DeviceSampler(Sampler): def __init__(self, videofont_path, midi_source_name): Sampler.__init__(self, videofont_path) print(mido) midi_source = mido.open_input(midi_source_name) midi_source.callback = self.midi_message_cb class FileSampler(Sampler): def __init__(self, videofont_path, midi_file_name): Sampler.__init__(self, videofont_path) self.midi_file = mido.MidiFile(midi_file_name) overlapping_notes = vidi.check_overlapping_notes(self.midi_file) if overlapping_notes: print("Sorry, supporting only midi file with no overlapping notes on channel 0") return None def play(self): def next_midi_msg(midi_file_generator): try: msg = midi_file_generator.__next__() if vidi.is_note(msg) and msg.channel == 0: self.midi_message_cb(msg) return True except StopIteration: self.stop() return False GObject.timeout_add(1, next_midi_msg, self.midi_file.play()) Sampler.play(self)