Skip to content

Commit ad6cecd

Browse files
committed
add MIDI synthesizer example
1 parent 9010efe commit ad6cecd

1 file changed

Lines changed: 288 additions & 0 deletions

File tree

examples/midi.md

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
---
2+
jupyter:
3+
jupytext:
4+
text_representation:
5+
extension: .md
6+
format_name: markdown
7+
format_version: '1.2'
8+
jupytext_version: 1.7.1
9+
kernelspec:
10+
display_name: Python 3
11+
language: python
12+
name: python3
13+
---
14+
15+
# Using MIDI synthesizer on Android
16+
MIDI synthesizer could be used with the [midistream](https://github.com/b3b/midistream) library.
17+
18+
```python
19+
%load_ext pythonhere
20+
%connect-there
21+
```
22+
23+
```python
24+
%%there
25+
from midistream import Synthesizer
26+
midi = Synthesizer()
27+
```
28+
29+
Get syntesizer configuration:
30+
31+
```python
32+
%%there
33+
from pprint import pprint
34+
pprint(midi.config)
35+
```
36+
37+
## Execute MIDI commands
38+
39+
```python
40+
%%there
41+
midi.write([0x90, 60, 127])
42+
```
43+
44+
* **0x90** - code for MIDI event **Note ON** on channel **0**
45+
* **60** - **C4** (middle C) note number
46+
* **127** - maximum velocity
47+
48+
MIDI commands reference:
49+
50+
* [https://en.wikipedia.org/wiki/MIDI#Messages](https://en.wikipedia.org/wiki/MIDI#Messages)
51+
52+
* [https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message](https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message)
53+
54+
55+
56+
Off middle C note:
57+
58+
```python
59+
%%there
60+
midi.write([0x80, 60, 127])
61+
```
62+
63+
## Helpers
64+
[midistream.helpers](https://midistream.readthedocs.io/en/latest/#module-midistream.helpers) module could be used to construct MIDI messages
65+
66+
```python
67+
%%there
68+
from midistream.helpers import (
69+
Control,
70+
Note,
71+
midi_control_change,
72+
midi_note_on,
73+
midi_note_off,
74+
midi_channels,
75+
midi_instruments,
76+
midi_program_change,
77+
note_name,
78+
)
79+
```
80+
81+
```python
82+
%%there
83+
program = 44
84+
print(f"Change program on channel 0 to: {midi_instruments[program]}")
85+
midi.write(midi_program_change(program))
86+
midi.write(midi_note_on(Note.Es4))
87+
```
88+
89+
```python
90+
%%there -d 1
91+
midi.write(midi_note_off(Note.Es4))
92+
```
93+
94+
## Channels
95+
MIDI channels allows to play different parts at the same time.
96+
97+
16 channels are available, channel **9** is a persussion channel.
98+
99+
```python
100+
%%there
101+
print(list(midi_channels()))
102+
```
103+
104+
Helpers functions accept an optional *channel* argument:
105+
106+
```python
107+
%%there
108+
midi.write(midi_program_change(20, channel=1))
109+
midi.write(midi_note_on(Note.A3, channel=1))
110+
111+
midi.write(midi_program_change(21, channel=2))
112+
midi.write(midi_note_on(Note.E4, channel=2))
113+
```
114+
115+
```python
116+
%%there -d 1
117+
midi.write(midi_note_off(Note.A3, channel=1))
118+
```
119+
120+
At this point, A3 note is off on channel **1**, but channel **2** is continue to sound.
121+
122+
```python
123+
%%there -d 1
124+
midi.write(midi_note_off(Note.E4, channel=2))
125+
```
126+
127+
## Control Change messages
128+
129+
```python
130+
%%there
131+
midi.write(
132+
midi_control_change(Control.volume, 110) +
133+
midi_control_change(Control.modulation, 90) +
134+
midi_program_change(62) +
135+
midi_note_on(Note.D3) + midi_note_on(Note.F3)
136+
)
137+
```
138+
139+
```python
140+
%%there -d 2
141+
midi.write(midi_control_change(Control.all_sound_off)) # Turns off sounds on a channel instantly
142+
midi.write(midi_control_change(Control.modulation, 0))
143+
```
144+
145+
## Master volume and Reverb effect
146+
are controlled with *Synthesizer* properties:
147+
148+
```python
149+
%%there
150+
from midistream import ReverbPreset
151+
midi.volume = 100 # Set maximum master volume
152+
midi.reverb = ReverbPreset.ROOM # Enable Room preset
153+
```
154+
155+
## Repeating a note with Kivy Clock
156+
Play note every second:
157+
158+
```python
159+
%%there
160+
from kivy.clock import Clock
161+
162+
beat_program = 33
163+
beat_note = Note.C3
164+
165+
def beat(_):
166+
midi.write(midi_program_change(beat_program, channel=1))
167+
midi.write(midi_note_on(beat_note, channel=1))
168+
169+
beat_event = Clock.schedule_interval(beat, .5)
170+
```
171+
172+
Change playing instrument and note:
173+
174+
```python
175+
%%there -d 2
176+
beat_note += 12
177+
beat_program = 115
178+
```
179+
180+
Stop the scheduled event:
181+
182+
```python
183+
%%there -d 2
184+
Clock.unschedule(beat_event)
185+
```
186+
187+
## Playing sequence with Kivy Animation
188+
189+
```python
190+
%%there kv
191+
BoxLayout:
192+
```
193+
194+
```python
195+
%%there
196+
from kivy.properties import NumericProperty
197+
from kivy.uix.label import Label
198+
199+
class NoteLabel(Label):
200+
"""Play and show note."""
201+
prev_note = 0
202+
note = NumericProperty(Note.E2)
203+
font_size = 150
204+
205+
def on_note(self, obj, note):
206+
note = int(note) # animation produce float values
207+
if note == self.prev_note: # do not repeat same note
208+
return
209+
self.text = f"{note} : {note_name(note):3}"
210+
self.prev_note = note
211+
midi.write(midi_note_on(note) + midi_note_on(note + 7))
212+
```
213+
214+
```python
215+
%%there
216+
from kivy.animation import Animation
217+
218+
root.clear_widgets()
219+
widget = NoteLabel()
220+
root.add_widget(widget)
221+
222+
midi.write(midi_program_change(36))
223+
224+
sequence = (
225+
Animation(note=widget.note + 6, duration=1.5, transition="in_elastic") +
226+
Animation(note=widget.note + 13, duration=1.5, transition="in_elastic") +
227+
Animation(note=widget.note, duration=4, transition="out_sine") +
228+
Animation(note=widget.note, duration=.8)
229+
)
230+
sequence.bind(on_complete=lambda *args: midi.write(midi_control_change(Control.all_sound_off)))
231+
sequence.start(widget)
232+
```
233+
234+
```python
235+
%there -d8 screenshot -w 200
236+
```
237+
238+
## Instruments list
239+
240+
```python
241+
%%there kv
242+
#:import midi_instruments midistream.helpers.midi_instruments
243+
244+
<InstrumentButton>:
245+
group: "instruments"
246+
text: "[b]{:3}[/b] : {}".format(self.instrument_code, midi_instruments[self.instrument_code])
247+
markup: True
248+
size_hint: 1, None
249+
text_size: self.size
250+
valign: "center"
251+
padding_x: 40
252+
253+
ScrollView:
254+
do_scroll_x: False
255+
size_hint: 1, 1
256+
BoxLayout:
257+
id: instruments
258+
orientation: "vertical"
259+
size_hint: 1, None
260+
height: self.minimum_size[1]
261+
```
262+
263+
```python
264+
%%there
265+
from kivy.properties import NumericProperty
266+
from kivy.uix.togglebutton import ToggleButton
267+
268+
269+
class InstrumentButton(ToggleButton):
270+
instrument_code = NumericProperty()
271+
notes = Note.A3, Note.D4, Note.A5
272+
273+
def on_state(self, widget, value):
274+
if value == "down":
275+
midi.write(midi_program_change(self.instrument_code))
276+
for note_code in self.notes:
277+
midi.write(midi_note_on(note_code))
278+
else:
279+
for note_code in self.notes:
280+
midi.write(midi_note_off(note_code))
281+
282+
for code in list(midi_instruments.keys()):
283+
root.ids.instruments.add_widget(InstrumentButton(instrument_code=code))
284+
```
285+
286+
```python
287+
%there screenshot -w 200
288+
```

0 commit comments

Comments
 (0)