Skip to content
This repository was archived by the owner on Nov 23, 2023. It is now read-only.

Commit df10552

Browse files
MIDI WIP
1 parent 6bbeda8 commit df10552

3 files changed

Lines changed: 66 additions & 24 deletions

File tree

examples/iipyper/iipyper-tutorial.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from iipyper import OSC, MIDI, repeat, run
22

3+
# TODO: MIDI
4+
# TODO: loops are broken, why?
5+
36
def main(osc_host='127.0.0.1', osc_port=9999, loop_time=1, loop_msg='hello'):
47
# loop API:
58
# use the @repeat decorator to define functions which run every n seconds
@@ -11,25 +14,22 @@ def _():
1114
def _():
1215
print('looping every 1.5 sec')
1316

14-
# midi = MIDI()
17+
midi = MIDI()
1518
# # MIDI API:
1619
# # midi.note_on, midi.cc, etc
1720

18-
# # use as a decorator to make a midi handler
19-
# @midi.handle.note_on
20-
# def _(pitch, velocity, channel):
21-
# print(pitch, velocity, channel)
22-
23-
# # above is kind of an abuse of python syntax, could do this instead:
24-
# # (which would allow filtering on port, even note)
25-
# @midi.handle.note_on(pitch=range(1,128), channel=0, port='')
26-
# def _(pitch, velocity, channel):
27-
# print(pitch, velocity, channel)
21+
# # decorator to make a midi handler:
22+
# # here filtering for pitch > 0, channel = 0
23+
@midi.handle.note_on(pitches=range(1,128), channels=0)
24+
def _(pitch, velocity, channel):
25+
print(pitch, velocity, channel)
2826

29-
# use as a function to send MIDI
27+
# function to send MIDI:
28+
midi.note_on(pitch=60, velocity=100, channel=0)
29+
midi.cc(control=0, value=127, channel=1)
3030

3131
# @repeat(1)
32-
# def step():
32+
# def _():
3333
# midi.note_on(pitch=60, velocity=100, channel=0)
3434
# midi.cc(number=0, value=127, channel=1)
3535

iipyper/iipyper/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ async def _run_async():
3939
# for osc in OSC.instances:
4040
# osc.close_server()
4141

42-
def run(main):
43-
fire.Fire(main)
42+
def run(main=None):
43+
if main is not None:
44+
fire.Fire(main)
4445
asyncio.run(_run_async())

iipyper/iipyper/midi.py

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ def _get_filter(item):
1212
class MIDI:
1313
""""""
1414
def __init__(self, in_ports=None, out_ports=None, verbose=True):
15-
""""""
15+
"""
16+
Args:
17+
in_ports: list of input devices (uses all by default)
18+
out_ports: list of output devices (uses all by default)
19+
"""
1620
self.verbose = verbose
1721
# type -> list[Optional[set[port], Optional[set[channel]], function]
1822
self.handlers = defaultdict(list)
@@ -39,6 +43,11 @@ def __init__(self, in_ports=None, out_ports=None, verbose=True):
3943

4044
self.handle = MIDIHandlers(self)
4145

46+
# TODO: filtering needs to work for all message types...
47+
# maybe there should be just one decorator
48+
# which can accept any filter, including on type?
49+
# and which just takes a mido message?
50+
4251
def get_midi_callback(self):
4352
"""callback for mido MIDI handling"""
4453
# close over `self``
@@ -82,22 +91,54 @@ def decorator(f):
8291

8392
return decorator if f is None else decorator(f)
8493

85-
# TODO: MIDI send
86-
def note_on(self, pitch, velocity=64, channel=0):
87-
pass
94+
# send on all ports by default
95+
def _send(self, port, *a, **kw):
96+
ports = self.out_ports.values() if port is None else [self.out_ports[port]]
97+
for p in ports:
98+
p.send(mido.Message(*a, **kw))
99+
100+
# see https://mido.readthedocs.io/en/latest/message_types.html
101+
102+
def note_on(self, note, velocity=64, channel=0, port=None):
103+
self._send(port, 'note_on', channel=channel, note=note, velocity=velocity)
104+
105+
def note_off(self, note, velocity=64, channel=0, port=None):
106+
self._send(port, 'note_off', channel=channel, note=note, velocity=velocity)
107+
108+
def control_change(self, control, value, channel=0, port=None):
109+
self._send(port, 'control_change', channel=channel, control=control, value=value)
110+
111+
def cc(self, *a, **kw):
112+
self.control_change(*a, **kw)
113+
114+
def program_change(self, program, channel=0, port=None):
115+
self._send(port, 'program_change', channel=channel, program=program)
116+
117+
def polytouch(self, note, value=64, channel=0, port=None):
118+
self._send(port, 'polytouch', channel=channel, note=note, value=value)
119+
120+
def aftertouch(self, value=64, channel=0, port=None):
121+
self._send(port, 'aftertouch', channel=channel, value=value)
122+
123+
def pitchwheel(self, pitch=0, port=None):
124+
self._send(port, 'pitchwheel', pitch=pitch)
125+
126+
def pitchbend(self, *a, **kw):
127+
self.pitchwheel(*a, **kw)
128+
88129

89-
# this is effectively part of MIDI,
130+
# this is effectively part of MIDI class,
90131
# it is only a separate class for naming aesthetics
91132
class MIDIHandlers:
92133
"""specific MIDI handler decorators"""
93134
def __init__(self, midi):
94135
self.midi = midi
95136

96-
def note_on(self, ports=None, channels=None, pitches=None, velocities=None):
97-
return self.midi._decorator('note_on', ports, channels, pitches, velocities)
137+
def note_on(self, ports=None, channels=None, notes=None, velocities=None):
138+
return self.midi._decorator('note_on', ports, channels, notes, velocities)
98139

99-
def note_off(self, ports=None, channels=None, pitches=None, velocities=None):
100-
return self.midi._decorator('note_off', ports, channels, pitches, velocities)
140+
def note_off(self, ports=None, channels=None, notes=None, velocities=None):
141+
return self.midi._decorator('note_off', ports, channels, notes, velocities)
101142

102143
def control_change(self, ports=None, channels=None, controls=None, values=None):
103144
return self.midi._decorator('control_change', ports, channels, controls, values)

0 commit comments

Comments
 (0)