Skip to content

Commit

Permalink
Refactor MIDI event factory (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
fengalin committed May 22, 2017
1 parent d25c2cb commit a6d9d7b
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 91 deletions.
50 changes: 25 additions & 25 deletions gstation_edit/jstation_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from pyalsa import alsaseq

from .midi.port import *
from .midi.event_resp_factory import *
from .midi.event_factory import *
from .midi.cc_event import *
from .midi.prg_change_event import *

Expand Down Expand Up @@ -59,29 +59,34 @@ class JStationInterface:
def __init__(self, app_name, main_window):
self.factory = MidiEventFactory()

CCMidiEvent.register_callback(self.one_parameter_cc_callback)
PrgChangeEvent.register_callback(self.program_change_callback)
CCMidiEvent.register_event_type_builder()
CCMidiEvent.register(self.one_parameter_cc_callback)

BankDumpRequest.register_callback(None)
EndBankDumpResponse.register_callback(self.end_bank_dump_callback)
PrgChangeEvent.register_event_type_builder()
PrgChangeEvent.register(self.program_change_callback)

OneProgramResponse.register_callback(self.one_program_callback)
JStationSysExEvent.register_event_type_builder()

PRGIndicesRequest.register_callback(None)
PRGIndicesResponse.register_callback(self.program_indices_callback)
BankDumpRequest.register()
EndBankDumpResponse.register(self.end_bank_dump_callback)

ReceiveProgramUpdate.register_callback(self.program_update_response)
RequestProgramUpdate.register_callback(None)
OneProgramResponse.register(self.one_program_callback)

StartBankDumpResponse.register_callback(self.default_event_callback)
PRGIndicesRequest.register()
PRGIndicesResponse.register(self.program_indices_callback)

ToMessageResponse.register_callback(self.response_to_message_callback)
ReceiveProgramUpdate.register(self.program_update_response)
RequestProgramUpdate.register()

UtilitySettingsRequest.register_callback(None)
UtilitySettingsResponse.register_callback(self.utility_settings_callback)
StartBankDumpResponse.register(self.default_event_callback)

WhoAmIRequest.register_callback(self.who_am_i_callback_req)
WhoAmIResponse.register_callback(self.who_am_i_callback)
ToMessageResponse.register(self.response_to_message_callback)

UtilitySettingsRequest.register()
UtilitySettingsResponse.register(self.utility_settings_callback)

WhoAmIRequest.register(self.who_am_i_callback_req)
WhoAmIResponse.register(self.who_am_i_callback)


self.is_connected = False
Expand Down Expand Up @@ -235,19 +240,14 @@ def wait_for_events(self):
if 0 < len(event_list):
for seq_event in event_list:
if None != seq_event:
# print('==> Event from JStation: %s'%(seq_event))
event = self.factory.get_event_from_seq_event(seq_event)
# print('==> Received event: %s'%(seq_event))
event = self.factory.build_from_seq_event(seq_event)
if None != event:
# print('\t%s'%(event))
event.process()
else:
print('\tCould not build event')
if seq_event.type == alsaseq.SEQ_EVENT_SYSEX:
event = JStationSysExEvent(seq_event=seq_event)
print('\tproduct: %d/%d, channel:%d, '\
'procedure: x%02x'\
%(event.manufacturer_id, event.product_id,
event.channel, event.procedure_id))
# print('\tCould not build event')
pass
else:
print('seq event is null')
event_list = list()
Expand Down
65 changes: 40 additions & 25 deletions gstation_edit/messages/jstation_sysex_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.

from ..midi.sysex_event import *
from ..midi.event_factory import *
from ..midi.split_bytes import *

class JStationSysExEvent(SysExMidiEvent):
Expand All @@ -33,34 +34,44 @@ class JStationSysExEvent(SysExMidiEvent):
VERSION = -1 # TO BE DEFINED IN HEIRS


event_classes = dict()

@classmethod
def is_event(class_, seq_event):
result = False
if SysExMidiEvent.EVENT_TYPE == seq_event.type:
sys_ex_data = seq_event.get_data().get(SysExMidiEvent.SYSEX_DATA_KEY)
if None != sys_ex_data:
if class_.PROCEDURE_ID_POS < len(sys_ex_data):
if class_.PROCEDURE_ID == \
sys_ex_data[JStationSysExEvent.PROCEDURE_ID_POS]:
# matching procedure id
#print("%s: found matching procecdure id"%(class_))
result = True
else:
#print("%s: procecdure id msimatch: %d / %d"\
# %(class_,
# sys_ex_data[JStationSysExEvent.PROCEDURE_ID_POS],
# class_.PROCEDURE_ID))
pass
else:
#print("%s: sysex data too short to read procecdure id"%(class_))
pass
def register_event_type_builder(class_):
MidiEventFactory.register_event_type_builder(JStationSysExEvent)

@classmethod
def register(class_, callback=None):
JStationSysExEvent.event_classes[class_.PROCEDURE_ID] = class_
if callback != None:
MidiEvent.callbacks[class_.__name__] = callback

@classmethod
def build_from_seq_event(class_, seq_event):
result = None
# assert: seq_event.type == SysExMidiEvent.EVENT_TYPE
sys_ex_data = seq_event.get_data().get(SysExMidiEvent.SYSEX_DATA_KEY)
if None != sys_ex_data:
if JStationSysExEvent.PROCEDURE_ID_POS < len(sys_ex_data):
proc_id = sys_ex_data[JStationSysExEvent.PROCEDURE_ID_POS]

event_class = JStationSysExEvent.event_classes.get(proc_id)
if event_class != None:
result = JStationSysExEvent.\
event_classes[proc_id](seq_event=seq_event)

if result == None:
event = JStationSysExEvent(seq_event=seq_event)
print('\n!! Unknown sysex event received: '\
'product: %d/%d, channel:%d, procedure: x%02x'\
%(event.manufacturer_id, event.product_id,
event.channel, event.procedure_id))
else:
#print("%s: couldn't sysex data key: %d"%(class_))
pass
print('Sysex data too short to read procecdure id: %s'\
%(sys_ex_data))
else:
#print("%s: doesn't match event with type: %d"%(class_,
# seq_event.type))
pass
print('Couldn\'t find sysex data key in seq event: %s'%(seq_event))

return result


Expand All @@ -86,6 +97,10 @@ def __init__(self, channel=-1, seq_event=None):
if 3 < len_sysex_data:
if self.SYSEX_DATA_START == sysex_data[0] and \
self.SYSEX_DATA_END == sysex_data[len_sysex_data-1]:
# Note: if this first part is common to all sysex
# events, it should be moved to SysExMidiEvent along
# with the factory stuff above

# get the actual buffer and check sum
check_sum = sysex_data[len_sysex_data-2]
self.data_buffer = sysex_data[1: len_sysex_data-2]
Expand Down
15 changes: 9 additions & 6 deletions gstation_edit/midi/cc_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from pyalsa import alsaseq

from .event import *
from .event_factory import *

class CCMidiEvent(MidiEvent):
EVENT_TYPE = alsaseq.SEQ_EVENT_CONTROLLER
Expand All @@ -28,11 +29,12 @@ class CCMidiEvent(MidiEvent):
VALUE_KEY = 'control.value'

@classmethod
def is_event(class_, seq_event):
result = False
if class_.EVENT_TYPE == seq_event.type:
result = True
return result
def register_event_type_builder(class_):
MidiEventFactory.register_event_type_builder(CCMidiEvent)

@classmethod
def build_from_seq_event(class_, seq_event):
return CCMidiEvent(seq_event=seq_event)


def __init__(self, channel=-1, param=-1, value=-1, seq_event=None):
Expand Down Expand Up @@ -73,8 +75,9 @@ def fill_seq_event(self):
MidiEvent.fill_seq_event(self)
if 0 <= self.channel and 0 <= self.param and 0 <= self.value:
event_data = dict()
if self.param != -1:
event_data[self.PARAM_KEY] = self.param
event_data[self.CHANNEL_KEY] = self.channel
event_data[self.PARAM_KEY] = self.param
event_data[self.VALUE_KEY] = self.value
self.seq_event.set_data(event_data)
self.is_valid = True
Expand Down
9 changes: 3 additions & 6 deletions gstation_edit/midi/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,13 @@
from .event_resp_factory import *

class MidiEvent(object):
class __metaclass__(type):
def __init__(class_, name, bases, dict):
MidiEventFactory.register_midi_event(class_, bases)

# class memeber
callbacks = dict()

@classmethod
def register_callback(class_, callback):
MidiEvent.callbacks[class_.__name__] = callback
def register(class_, callback=None):
if callback != None:
MidiEvent.callbacks[class_.__name__] = callback

@classmethod
def is_event(class_, seq_event):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,33 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.

class MidiEventFactory:
event_classes = list()
event_type_builder_classes = dict()

def __init__(self):
pass

def register_midi_event(self_class, midi_event_class, bases):
# print('Registering: %s'%(midi_event_class))
self_class.event_classes.append(midi_event_class)
register_midi_event = classmethod(register_midi_event)
@classmethod
def register_event_type_builder(self_class, event_type_class):
# print('Registering MIDI event type builder: %s'%(event_type_class))
MidiEventFactory.event_type_builder_classes[
event_type_class.EVENT_TYPE.real
] = event_type_class

def get_event_from_seq_event(self, seq_event):

@classmethod
def build_from_seq_event(class_, seq_event):
result = None
if None != seq_event:
# print('Received event with type %d'%(seq_event.type))
for event_class in self.event_classes:
if event_class.is_event(seq_event):
# print('Event recognized as: %s'%(l_event_class.__name__))
result = event_class(seq_event=seq_event)
break
event_type_builder = MidiEventFactory.\
event_type_builder_classes.get(seq_event.type.real)
if event_type_builder != None:
result = event_type_builder.build_from_seq_event(seq_event)
if result != None:
# print('Event identified as: %s'%(result))
pass
else:
print('Couldn\'t identify event: %s'%(seq_event))
else:
print('Unknown event type for: %s'%(seq_event))
return result
8 changes: 8 additions & 0 deletions gstation_edit/midi/prg_change_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@
from pyalsa import alsaseq

from .cc_event import *
from .event_factory import *

class PrgChangeEvent(CCMidiEvent):
EVENT_TYPE = alsaseq.SEQ_EVENT_PGMCHANGE

@classmethod
def register_event_type_builder(class_):
MidiEventFactory.register_event_type_builder(PrgChangeEvent)

@classmethod
def build_from_seq_event(class_, seq_event):
return PrgChangeEvent(seq_event=seq_event)
16 changes: 5 additions & 11 deletions sniffer/jstation_sniffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ def connect_sniffer(self, midi_port_in, midi_port_out):
self.jstation_wait_for_events_thread.join()
self.jstation_wait_for_events_thread = None
self.is_disconnecting.clear()
print('terminated j-station connection event loop')
print('Terminated J-Station connection event loop')

print('\nLaunching sniffer event loop')
print('\nSniffing events...')
self.jstation_wait_for_events_thread = Thread(
target = self.sniff_events,
name = 'sniff events'
Expand All @@ -68,18 +68,12 @@ def sniff_events(self):
origin = 'J-Edit'
forward_event = True

event = self.factory.get_event_from_seq_event(seq_event)
event = self.factory.build_from_seq_event(seq_event)
if None != event:
print('\n** %s => %s'%(origin, event))
else:
print('\n** Could not build event from %s'\
%(seq_event))
if seq_event.type == alsaseq.SEQ_EVENT_SYSEX:
event = JStationSysExEvent(seq_event=seq_event)
print('\tproduct: %d/%d, channel:%d, '\
'procedure: x%02x'\
%(event.manufacturer_id, event.product_id,
event.channel, event.procedure_id))
# print('\n** Could not build event')
pass

if forward_event:
seq_event.dest = (jstation_cid, jstation_in_port)
Expand Down
4 changes: 2 additions & 2 deletions test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from test import event_resp_factory
event_resp_factory.test()
from test import event_factory
event_factory.test()

from test import split_bytes
split_bytes.test()
Expand Down
19 changes: 14 additions & 5 deletions test/event_resp_factory.py → test/event_factory.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
gstation-edit MidiEventResponseFactory test
gstation-edit MidiEventFactory test
"""
# this file is part of gstation-edit
# Copyright (C) F LAIGNEL 2009-2017 <fengalin@free.fr>
Expand All @@ -19,12 +19,21 @@

from pyalsa import alsaseq

from gstation_edit.midi.event_resp_factory import *
from gstation_edit.midi.event_factory import *
from gstation_edit.midi.cc_event import *

from gstation_edit.messages.jstation_sysex_event import *
from gstation_edit.messages.who_am_i_resp import *

def test():
print('\n==== MidiEventResponseFactory test')
print('\n==== MidiEventFactory test')

CCMidiEvent.register_event_type_builder()
CCMidiEvent.register()

JStationSysExEvent.register_event_type_builder()
WhoAmIResponse.register()

sysex_data = [
0xf0, 0, 0, 0x10, 0x7f, 0x54, 0x41,
0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 121, 0xf7
Expand All @@ -37,7 +46,7 @@ def test():
sysex_seq_event.set_data(sysex_evt_data);

factory = MidiEventFactory()
sysex_event = factory.get_event_from_seq_event(sysex_seq_event)
sysex_event = factory.build_from_seq_event(sysex_seq_event)
if None != sysex_event:
print(sysex_event.is_valid)
print(str(sysex_event))
Expand All @@ -51,7 +60,7 @@ def test():
cc_data['control.value'] = 20;
cc_seq_event.set_data(cc_data);

cc_event = factory.get_event_from_seq_event(cc_seq_event)
cc_event = factory.build_from_seq_event(cc_seq_event)
if None != cc_event:
print(cc_event.is_valid)
print(str(cc_event))
Expand Down

0 comments on commit a6d9d7b

Please sign in to comment.