├── .gitattributes
├── .gitignore
├── .idea
├── .gitignore
├── Micropython-midi-library.iml
├── inspectionProfiles
│ └── profiles_settings.xml
├── libraries
│ └── MicroPython.xml
├── misc.xml
└── modules.xml
├── Fritzing schematics
├── midi_in.fzz
├── midi_out.fzz
└── midilib.fzz
├── LICENSE
├── README.md
├── examples
├── main_midi_to_cv.py
├── main_rx_test.py
├── main_sysex_tx.py
├── main_thru_test.py
└── main_tx_test.py
├── main.py
└── midi.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .nox/
42 | .coverage
43 | .coverage.*
44 | .cache
45 | nosetests.xml
46 | coverage.xml
47 | *.cover
48 | .hypothesis/
49 | .pytest_cache/
50 |
51 | # Translations
52 | *.mo
53 | *.pot
54 |
55 | # Django stuff:
56 | *.log
57 | local_settings.py
58 | db.sqlite3
59 |
60 | # Flask stuff:
61 | instance/
62 | .webassets-cache
63 |
64 | # Scrapy stuff:
65 | .scrapy
66 |
67 | # Sphinx documentation
68 | docs/_build/
69 |
70 | # PyBuilder
71 | target/
72 |
73 | # Jupyter Notebook
74 | .ipynb_checkpoints
75 |
76 | # IPython
77 | profile_default/
78 | ipython_config.py
79 |
80 | # pyenv
81 | .python-version
82 |
83 | # celery beat schedule file
84 | celerybeat-schedule
85 |
86 | # SageMath parsed files
87 | *.sage.py
88 |
89 | # Environments
90 | .env
91 | .venv
92 | env/
93 | venv/
94 | ENV/
95 | env.bak/
96 | venv.bak/
97 |
98 | # Spyder project settings
99 | .spyderproject
100 | .spyproject
101 |
102 | # Rope project settings
103 | .ropeproject
104 |
105 | # mkdocs documentation
106 | /site
107 |
108 | # mypy
109 | .mypy_cache/
110 | .dmypy.json
111 | dmypy.json
112 |
113 | # Pyre type checker
114 | .pyre/
115 | .idea/vcs.xml
116 | main.py
117 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/Micropython-midi-library.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/MicroPython.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Fritzing schematics/midi_in.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sensai7/Micropython-midi-library/cb36e8e2f0e2623a4f83edf98881f01489ad90f6/Fritzing schematics/midi_in.fzz
--------------------------------------------------------------------------------
/Fritzing schematics/midi_out.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sensai7/Micropython-midi-library/cb36e8e2f0e2623a4f83edf98881f01489ad90f6/Fritzing schematics/midi_out.fzz
--------------------------------------------------------------------------------
/Fritzing schematics/midilib.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sensai7/Micropython-midi-library/cb36e8e2f0e2623a4f83edf98881f01489ad90f6/Fritzing schematics/midilib.fzz
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 sensai7
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Micropython-midi-library
2 | A complete MIDI I/O library for Micropython devices
3 |
4 | To use, simply download the midi.py file and add it to your project. Availability thorugh pip is on the works.
5 |
6 | At the current state this library supports all types of MIDI messages via input or output.
7 |
8 | Some usage examples are provided in the /examples folder.
9 |
10 | ## Quick Reference
11 |
12 | ### Initializing a MIDI instance object
13 |
14 | ```python
15 | MIDI_TX = Pin(8) # Optocoupled MIDI input in general purpose pin 8 (UART1TX)
16 | MIDI_RX = Pin(9) # MIDI output in general purpose pin 9 (UART1RX)
17 | UART_1 = 1
18 | my_midi = midi.Midi(UART_1, tx=MIDI_TX, rx=MIDI_RX)
19 | ```
20 |
21 | ### Sending MIDI messages
22 |
23 | 
24 |
25 | ```python
26 | my_midi.send_note_on(midi.CHANNEL[1], midi.NOTE_CODE["C#4"], velocity=100)
27 | my_midi.send_note_off(midi.CHANNEL[1], midi.NOTE_CODE["C#4"])
28 | my_midi.send_control_change(midi.CHANNEL[1], midi.CONTROL_CHANGE_CODE["MODULATION_WHEEL"], value=80)
29 | # (...Other send methods)
30 | ```
31 |
32 | ### Receiving MIDI messages
33 |
34 | 
35 |
36 | ```python
37 | while True:
38 | if my_midi.any() > 0:
39 | my_midi.load_message(my_midi.read(1))
40 | if my_midi.last_sequence == midi.MIDI_SEQUENCE["NOTE_ON"]:
41 | channel = my_midi.channel
42 | note = my_midi.get_parameter("note_on", "note")
43 | velocity = my_midi.get_parameter("note_on", "velocity")
44 | # do something with these parameters
45 |
46 | if my_midi.last_sequence == midi.MIDI_SEQUENCE["NOTE_OFF"]:
47 | channel = my_midi.channel
48 | note = my_midi.get_parameter("note_off", "note")
49 | # do something with this parameter
50 |
51 | # (...Other receive blocks)
52 | ```
53 |
--------------------------------------------------------------------------------
/examples/main_midi_to_cv.py:
--------------------------------------------------------------------------------
1 | # generic MIDI to CV/GATE
2 | from machine import Pin, PWM
3 | import midi
4 |
5 | MIDI_TX = Pin(8) # Optocoupled MIDI input in general purpose pin 8 (UART1TX)
6 | MIDI_RX = Pin(9) # MIDI output in general purpose pin 9 (UART1RX)
7 | GATE = Pin(1) # Gate out in general purpose pin 8
8 | PWM_NOTE = PWM(Pin(20)) # Note CV PWM out in general purpose pin 20
9 | PWM_VELOCITY = PWM(Pin(21)) # Velocity CV PWM out in general purpose pin 21
10 |
11 | my_midi = midi.Midi(1, tx=MIDI_TX, rx=MIDI_RX)
12 | note_balance = 0
13 | PWM_NOTE.freq(10000)
14 | PWM_VELOCITY.freq(10000)
15 |
16 | while True:
17 | if my_midi.any() > 0:
18 | byte = my_midi.read(1)
19 | my_midi.load_message(byte)
20 | if my_midi.last_sequence == midi.MIDI_SEQUENCE["NOTE_ON"]:
21 | note = my_midi.last_rx_parameters["note_on"]["note"]
22 | velocity = my_midi.last_rx_parameters["note_on"]["velocity"]
23 | note_duty = (note - 36) * pow(2, 16) // 60 # Pitch CV 0V~3.3V (C2~C7)
24 | velocity_duty = (velocity + 1) * pow(2, 9) - 1 # Velocity CV 0V~3.3V
25 | PWM_NOTE.duty_u16(note_duty)
26 | PWM_VELOCITY.duty_u16(velocity_duty)
27 | note_balance += 1
28 | GATE.high()
29 |
30 | if my_midi.last_sequence == midi.MIDI_SEQUENCE["NOTE_OFF"]:
31 | note_balance -= 1
32 | if note_balance == 0:
33 | GATE.low()
34 |
--------------------------------------------------------------------------------
/examples/main_rx_test.py:
--------------------------------------------------------------------------------
1 | # generic midi send program
2 | from machine import Pin
3 | import midi
4 |
5 | MIDI_TX = Pin(8) # Optocoupled MIDI input in general purpose pin 8 (UART1TX)
6 | MIDI_RX = Pin(9) # MIDI output in general purpose pin 9 (UART1RX)
7 | led = Pin(25, Pin.OUT)
8 |
9 | my_midi = midi.Midi(1, tx=MIDI_TX, rx=MIDI_RX)
10 |
11 |
12 | while True:
13 | if my_midi.any() > 0:
14 | byte = my_midi.read(1)
15 | my_midi.load_message(byte)
16 |
17 | if my_midi.last_sequence == midi.MIDI_SEQUENCE["NOTE_ON"]:
18 | channel = my_midi.channel
19 | note = my_midi.get_parameter("note_on", "note")
20 | velocity = my_midi.get_parameter("note_on", "velocity")
21 | # do something with these parameters
22 | led.on()
23 |
24 | if my_midi.last_sequence == midi.MIDI_SEQUENCE["NOTE_OFF"]:
25 | channel = my_midi.channel
26 | note = my_midi.get_parameter("note_off", "note")
27 | # do something with this parameter
28 | led.off()
29 |
--------------------------------------------------------------------------------
/examples/main_sysex_tx.py:
--------------------------------------------------------------------------------
1 | # Send a SYSEX sequence
2 | from machine import Pin
3 | import midi
4 |
5 | MIDI_TX = Pin(8) # Optocoupled MIDI input in general purpose pin 8 (UART1TX)
6 | MIDI_RX = Pin(9) # MIDI output in general purpose pin 9 (UART1RX)
7 |
8 | my_midi = midi.Midi(1, tx=MIDI_TX, rx=MIDI_RX)
9 | midi_sysex = [0x20, 0x19, 0x13, 0x20, 0x05, 0x29, 0x26, 0x08, 0x20, 0x22, 0x17, 0x30, 0x02, 0x21, 0x22,
10 | 0x13, 0x12, 0x25, 0x28, 0x26, 0x14, 0x29, 0x25, 0x10, 0x06, 0x28, 0x02, 0x23, 0x19, 0x12,
11 | 0x20, 0x04, 0x17, 0x23, 0x26, 0x15, 0x16, 0x12, 0x08, 0x08, 0x17, 0x11, 0x05, 0x01, 0x21,
12 | 0x19, 0x24, 0x07, 0x26, 0x29, 0x02, 0x28, 0x01, 0x23, 0x30, 0x07, 0x10, 0x30, 0x05, 0x03,
13 | 0x00, 0x26, 0x03, 0x30, 0x24, 0x25, 0x12, 0x08, 0x01, 0x27, 0x26, 0x06, 0x29, 0x16, 0x17,
14 | 0x12, 0x27, 0x26, 0x12, 0x22, 0x23, 0x00, 0x13, 0x11, 0x25, 0x26, 0x28, 0x19, 0x2,
15 | 0x13, 0x10, 0x08, 0x20, 0x16, 0x28, 0x05, 0x30, 0x17, 0x02, 0x00]
16 |
17 | my_midi.send_sysex_start() # sends 0xF0 to start a system exclusive sequence
18 | my_midi.send_sysex(midi_sysex) # loops through the sysex commands list
19 | my_midi.send_sysex_stop() # sends 0xF7 to stop a system exclusive sequence
20 |
--------------------------------------------------------------------------------
/examples/main_thru_test.py:
--------------------------------------------------------------------------------
1 | # generic midi thru program
2 | from machine import Pin
3 | import midi
4 |
5 | MIDI_TX = Pin(8) # Optocoupled MIDI input in general purpose pin 8 (UART1TX)
6 | MIDI_RX = Pin(9) # MIDI output in general purpose pin 9 (UART1RX)
7 | led = Pin(25, Pin.OUT)
8 |
9 | my_midi = midi.Midi(1, tx=MIDI_TX, rx=MIDI_RX)
10 |
11 | while True:
12 | if my_midi.any() > 0:
13 | byte = my_midi.read(1)
14 | my_midi.load_message(byte)
15 | my_midi.write(my_midi.message)
16 |
--------------------------------------------------------------------------------
/examples/main_tx_test.py:
--------------------------------------------------------------------------------
1 | # generic midi send program
2 | from machine import Pin
3 | import midi
4 | import time
5 |
6 | MIDI_TX = Pin(8) # Optocoupled MIDI input in general purpose pin 8 (UART1TX)
7 | MIDI_RX = Pin(9) # MIDI output in general purpose pin 9 (UART1RX)
8 | led = Pin(25, Pin.OUT)
9 |
10 | my_midi = midi.Midi(1, tx=MIDI_TX, rx=MIDI_RX)
11 |
12 | led.on()
13 | my_midi.send_note_on(midi.CHANNEL[1], midi.NOTE_CODE["C#4"], velocity=100)
14 | led.off()
15 | time.sleep(1)
16 |
17 | led.on()
18 | my_midi.send_note_off(midi.CHANNEL[1], midi.NOTE_CODE["C#4"])
19 | led.off()
20 | time.sleep(1)
21 |
22 | led.on()
23 | my_midi.send_control_change(midi.CHANNEL[1], midi.CONTROL_CHANGE_CODE["MODULATION_WHEEL"], value=80)
24 | led.off()
25 | time.sleep(1)
26 |
27 | led.on()
28 | my_midi.send_channel_aftertouch(midi.CHANNEL[1], amount=23)
29 | led.off()
30 | time.sleep(1)
31 |
32 | led.on()
33 | my_midi.send_playback_start()
34 | led.off()
35 | time.sleep(1)
36 |
37 | led.on()
38 | my_midi.send_playback_continue()
39 | led.off()
40 | time.sleep(1)
41 |
42 | led.on()
43 | my_midi.send_playback_stop()
44 | led.off()
45 | time.sleep(1)
46 |
47 | led.on()
48 | my_midi.send_sysex_start()
49 | led.off()
50 | time.sleep(1)
51 |
52 | led.on()
53 | my_midi.send_sysex([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
54 | led.off()
55 | time.sleep(1)
56 |
57 | led.on()
58 | my_midi.send_sysex_stop()
59 | led.off()
60 | time.sleep(1)
61 |
62 | led.on()
63 | my_midi.send_poly_aftertouch(midi.CHANNEL[10], midi.NOTE_CODE['C6'], pressure=24)
64 | led.off()
65 | time.sleep(1)
66 |
67 | led.on()
68 | my_midi.send_program_change(midi.CHANNEL[3], program=21)
69 | led.off()
70 | time.sleep(1)
71 |
72 | led.on()
73 | my_midi.send_timing_clock()
74 | led.off()
75 | time.sleep(1)
76 |
77 | led.on()
78 | my_midi.send_tune_request()
79 | led.off()
80 | time.sleep(1)
81 |
82 | led.on()
83 | my_midi.send_song_select(10)
84 | led.off()
85 | time.sleep(1)
86 |
87 | led.on()
88 | my_midi.send_song_position_pointer(64)
89 | led.off()
90 | time.sleep(1)
91 |
92 | led.on()
93 | my_midi.send_time_code_qtr_frame(midi.MIDI_QTR_FRAME_CODE["30 fps"], hours=23, minutes=24, seconds=14, frames=1)
94 | led.off()
95 | time.sleep(1)
96 |
97 | led.on()
98 | my_midi.send_active_sensing()
99 | led.off()
100 | time.sleep(1)
101 |
102 | led.on()
103 | my_midi.send_reset()
104 | led.off()
105 | time.sleep(1)
106 |
107 |
108 | print("Execution finished.")
109 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | # Send a SYSEX sequence
2 | from machine import Pin
3 | import midi
4 |
5 | MIDI_TX = Pin(8) # Optocoupled MIDI input in general purpose pin 8 (UART1TX)
6 | MIDI_RX = Pin(9) # MIDI output in general purpose pin 9 (UART1RX)
7 |
8 | my_midi = midi.Midi(1, tx=MIDI_TX, rx=MIDI_RX)
9 | midi_sysex = [0x20, 0x19, 0x13, 0x20, 0x05, 0x29, 0x26, 0x08, 0x20, 0x22, 0x17, 0x30, 0x02, 0x21, 0x22,
10 | 0x13, 0x12, 0x25, 0x28, 0x26, 0x14, 0x29, 0x25, 0x10, 0x06, 0x28, 0x02, 0x23, 0x19, 0x12,
11 | 0x20, 0x04, 0x17, 0x23, 0x26, 0x15, 0x16, 0x12, 0x08, 0x08, 0x17, 0x11, 0x05, 0x01, 0x21,
12 | 0x19, 0x24, 0x07, 0x26, 0x29, 0x02, 0x28, 0x01, 0x23, 0x30, 0x07, 0x10, 0x30, 0x05, 0x03,
13 | 0x00, 0x26, 0x03, 0x30, 0x24, 0x25, 0x12, 0x08, 0x01, 0x27, 0x26, 0x06, 0x29, 0x16, 0x17,
14 | 0x12, 0x27, 0x26, 0x12, 0x22, 0x23, 0x00, 0x13, 0x11, 0x25, 0x26, 0x28, 0x19, 0x2,
15 | 0x13, 0x10, 0x08, 0x20, 0x16, 0x28, 0x05, 0x30, 0x17, 0x02, 0x00]
16 |
17 | my_midi.send_sysex_start() # sends 0xF0 to start a system exclusive sequence
18 | my_midi.send_sysex(midi_sysex) # loops through the sysex commands list
19 | my_midi.send_sysex_stop() # sends 0xF7 to stop a system exclusive sequence
20 |
--------------------------------------------------------------------------------
/midi.py:
--------------------------------------------------------------------------------
1 | from machine import UART
2 |
3 | # generic MIDI macros
4 | BAUD = 31250
5 | FREQUENCY = BAUD
6 |
7 | # MIDI message macros
8 | MESSAGE_NOTE_OFF = 0x80
9 | MESSAGE_NOTE_ON = 0x90
10 | MESSAGE_POLYPHONIC_AFTERTOUCH = 0XA0
11 | MESSAGE_CONTROL_CHANGE = 0xB0
12 | MESSAGE_PROGRAM_CHANGE = 0xC0
13 | MESSAGE_CHANNEL_AFTERTOUCH = 0xD0
14 | MESSAGE_PITCH_BEND = 0xE0
15 | MESSAGE_SYSTEM_EXCLUSIVE_START = 0xF0
16 | MESSAGE_MIDI_TIME_CODE_QTR_FRAME = 0xF1
17 | MESSAGE_SONG_POSITION_POINTER = 0xF2
18 | MESSAGE_SONG_SELECT = 0xF3
19 | # 0xF4 and 0xF5 reserved
20 | MESSAGE_TUNE_REQUEST = 0xF6
21 | MESSAGE_SYSTEM_EXCLUSIVE_STOP = 0xF7
22 | MESSAGE_TIMING_CLOCK = 0xF8
23 | # 0xF9 reserved
24 | MESSAGE_START = 0xFA
25 | MESSAGE_CONTINUE = 0xFB
26 | MESSAGE_STOP = 0xFC
27 | # 0xFD reserved
28 | MESSAGE_ACTIVE_SENSING = 0xFE
29 | MESSAGE_SYSTEM_RESET = 0xFF
30 |
31 | CHANNEL = {
32 | 1: 0,
33 | 2: 1,
34 | 3: 2,
35 | 4: 3,
36 | 5: 4,
37 | 6: 5,
38 | 7: 6,
39 | 8: 7,
40 | 9: 8,
41 | 10: 9,
42 | 11: 10,
43 | 12: 11,
44 | 13: 12,
45 | 14: 13,
46 | 15: 14,
47 | 16: 15,
48 | }
49 |
50 | CONTROL_CHANGE_CODE = {
51 | 0: "BANK_SELECT",
52 | 1: "MODULATION_WHEEL",
53 | 2: "BREATH_CONTROLLER",
54 | 3: "UNDEFINED",
55 | 4: "FOOT_CONTROLLER",
56 | 5: "PORTAMENTO_TIME",
57 | 6: "DATA_ENTRY",
58 | 7: "CHANNEL_VOLUME",
59 | 8: "BALANCE",
60 | 9: "UNDEFINED",
61 | 10: "PAN",
62 | 11: "EXPRESSION_CONTROLLER",
63 | 12: "EFFECT_CONTROL_1",
64 | 13: "EFFECT_CONTROL_2",
65 | 14: "UNDEFINED",
66 | 15: "UNDEFINED",
67 | 16: "GENERAL_PURPOSE_CONTROLLER_1",
68 | 17: "GENERAL_PURPOSE_CONTROLLER_2",
69 | 18: "GENERAL_PURPOSE_CONTROLLER_3",
70 | 19: "GENERAL_PURPOSE_CONTROLLER_4",
71 | 20: "UNDEFINED",
72 | 21: "UNDEFINED",
73 | 22: "UNDEFINED",
74 | 23: "UNDEFINED",
75 | 24: "UNDEFINED",
76 | 25: "UNDEFINED",
77 | 26: "UNDEFINED",
78 | 27: "UNDEFINED",
79 | 28: "UNDEFINED",
80 | 29: "UNDEFINED",
81 | 30: "UNDEFINED",
82 | 31: "UNDEFINED",
83 | 32: "CONTROL_0",
84 | 33: "CONTROL_1",
85 | 34: "CONTROL_2",
86 | 35: "CONTROL_3",
87 | 36: "CONTROL_4",
88 | 37: "CONTROL_5",
89 | 38: "CONTROL_6",
90 | 39: "CONTROL_7",
91 | 40: "CONTROL_8",
92 | 41: "CONTROL_9",
93 | 42: "CONTROL_10",
94 | 43: "CONTROL_11",
95 | 44: "CONTROL_12",
96 | 45: "CONTROL_13",
97 | 46: "CONTROL_14",
98 | 47: "CONTROL_15",
99 | 48: "CONTROL_16",
100 | 49: "CONTROL_17",
101 | 50: "CONTROL_18",
102 | 51: "CONTROL_19",
103 | 52: "CONTROL_20",
104 | 53: "CONTROL_21",
105 | 54: "CONTROL_22",
106 | 55: "CONTROL_23",
107 | 56: "CONTROL_24",
108 | 57: "CONTROL_25",
109 | 58: "CONTROL_26",
110 | 59: "CONTROL_27",
111 | 60: "CONTROL_28",
112 | 61: "CONTROL_29",
113 | 62: "CONTROL_30",
114 | 63: "CONTROL_31",
115 | 64: "DAMPER_PEDAL_ON_OFF",
116 | 65: "PORTAMENTO_ON_OFF",
117 | 66: "SOSTENUTO_ON_OFF",
118 | 67: "SOFT_PEDAL_ON_OFF",
119 | 68: "LEGATO_FOOTSWITCH",
120 | 69: "HOLD_2",
121 | 70: "SOUND_CONTROLLER_1",
122 | 71: "SOUND_CONTROLLER_2",
123 | 72: "SOUND_CONTROLLER_3",
124 | 73: "SOUND_CONTROLLER_4",
125 | 74: "SOUND_CONTROLLER_5",
126 | 75: "SOUND_CONTROLLER_6",
127 | 76: "SOUND_CONTROLLER_7",
128 | 77: "SOUND_CONTROLLER_8",
129 | 78: "SOUND_CONTROLLER_9",
130 | 79: "SOUND_CONTROLLER_10",
131 | 80: "GENERAL_PURPOSE_CONTROLLER_5",
132 | 81: "GENERAL_PURPOSE_CONTROLLER_6",
133 | 82: "GENERAL_PURPOSE_CONTROLLER_7",
134 | 83: "GENERAL_PURPOSE_CONTROLLER_8",
135 | 84: "PORTAMENTO_CONTROL",
136 | 85: "UNDEFINED",
137 | 86: "UNDEFINED",
138 | 87: "UNDEFINED",
139 | 88: "HIGH_RESOLUTION_VELOCITY_PREFIX",
140 | 89: "UNDEFINED",
141 | 90: "UNDEFINED",
142 | 91: "EFFECTS_1_DEPTH",
143 | 92: "EFFECTS_2_DEPTH",
144 | 93: "EFFECTS_3_DEPTH",
145 | 94: "EFFECTS_4_DEPTH",
146 | 95: "EFFECTS_5_DEPTH",
147 | 96: "DATA_INCREMENT",
148 | 97: "DATA_DECREMENT",
149 | 98: "NON_REGISTERED_PARAMETER_NUMBER_LSB",
150 | 99: "NON_REGISTERED_PARAMETER_NUMBER_MSB",
151 | 100: "REGISTERED_PARAMETER_NUMBER_LSB",
152 | 101: "REGISTERED_PARAMETER_NUMBER_MSB",
153 | 102: "UNDEFINED",
154 | 103: "UNDEFINED",
155 | 104: "UNDEFINED",
156 | 105: "UNDEFINED",
157 | 106: "UNDEFINED",
158 | 107: "UNDEFINED",
159 | 108: "UNDEFINED",
160 | 109: "UNDEFINED",
161 | 110: "UNDEFINED",
162 | 111: "UNDEFINED",
163 | 112: "UNDEFINED",
164 | 113: "UNDEFINED",
165 | 114: "UNDEFINED",
166 | 115: "UNDEFINED",
167 | 116: "UNDEFINED",
168 | 117: "UNDEFINED",
169 | 118: "UNDEFINED",
170 | 119: "UNDEFINED",
171 | 120: "ALL_SOUND_OFF",
172 | 121: "RESET_ALL_CONTROLLERS",
173 | 122: "LOCAL_CONTROL_ON_OFF",
174 | 123: "ALL_NOTES_OFF",
175 | 124: "OMNI_MODE_OFF",
176 | 125: "OMNI_MODE_ON",
177 | 126: "MONO_MODE_ON",
178 | 127: "POLY_MODE_ON",
179 | "BANK_SELECT": 0,
180 | "MODULATION_WHEEL": 1,
181 | "BREATH_CONTROLLER": 2,
182 | "FOOT_CONTROLLER": 4,
183 | "PORTAMENTO_TIME": 5,
184 | "DATA_ENTRY": 6,
185 | "CHANNEL_VOLUME": 7,
186 | "BALANCE": 8,
187 | "PAN": 10,
188 | "EXPRESSION_CONTROLLER": 11,
189 | "EFFECT_CONTROL_1": 12,
190 | "EFFECT_CONTROL_2": 13,
191 | "GENERAL_PURPOSE_CONTROLLER_1": 16,
192 | "GENERAL_PURPOSE_CONTROLLER_2": 17,
193 | "GENERAL_PURPOSE_CONTROLLER_3": 18,
194 | "GENERAL_PURPOSE_CONTROLLER_4": 19,
195 | "CONTROL_0": 32,
196 | "CONTROL_1": 33,
197 | "CONTROL_2": 34,
198 | "CONTROL_3": 35,
199 | "CONTROL_4": 36,
200 | "CONTROL_5": 37,
201 | "CONTROL_6": 38,
202 | "CONTROL_7": 39,
203 | "CONTROL_8": 40,
204 | "CONTROL_9": 41,
205 | "CONTROL_10": 42,
206 | "CONTROL_11": 43,
207 | "CONTROL_12": 44,
208 | "CONTROL_13": 45,
209 | "CONTROL_14": 46,
210 | "CONTROL_15": 47,
211 | "CONTROL_16": 48,
212 | "CONTROL_17": 49,
213 | "CONTROL_18": 50,
214 | "CONTROL_19": 51,
215 | "CONTROL_20": 52,
216 | "CONTROL_21": 53,
217 | "CONTROL_22": 54,
218 | "CONTROL_23": 55,
219 | "CONTROL_24": 56,
220 | "CONTROL_25": 57,
221 | "CONTROL_26": 58,
222 | "CONTROL_27": 59,
223 | "CONTROL_28": 60,
224 | "CONTROL_29": 61,
225 | "CONTROL_30": 62,
226 | "CONTROL_31": 63,
227 | "DAMPER_PEDAL_ON_OFF": 64,
228 | "PORTAMENTO_ON_OFF": 65,
229 | "SOSTENUTO_ON_OFF": 66,
230 | "SOFT_PEDAL_ON_OFF": 67,
231 | "LEGATO_FOOTSWITCH": 68,
232 | "HOLD_2": 69,
233 | "SOUND_CONTROLLER_1": 70,
234 | "SOUND_CONTROLLER_2": 71,
235 | "SOUND_CONTROLLER_3": 72,
236 | "SOUND_CONTROLLER_4": 73,
237 | "SOUND_CONTROLLER_5": 74,
238 | "SOUND_CONTROLLER_6": 75,
239 | "SOUND_CONTROLLER_7": 76,
240 | "SOUND_CONTROLLER_8": 77,
241 | "SOUND_CONTROLLER_9": 78,
242 | "SOUND_CONTROLLER_10": 79,
243 | "GENERAL_PURPOSE_CONTROLLER_5": 80,
244 | "GENERAL_PURPOSE_CONTROLLER_6": 81,
245 | "GENERAL_PURPOSE_CONTROLLER_7": 82,
246 | "GENERAL_PURPOSE_CONTROLLER_8": 83,
247 | "PORTAMENTO_CONTROL": 84,
248 | "HIGH_RESOLUTION_VELOCITY_PREFIX": 88,
249 | "EFFECTS_1_DEPTH": 91,
250 | "EFFECTS_2_DEPTH": 92,
251 | "EFFECTS_3_DEPTH": 93,
252 | "EFFECTS_4_DEPTH": 94,
253 | "EFFECTS_5_DEPTH": 95,
254 | "DATA_INCREMENT": 96,
255 | "DATA_DECREMENT": 97,
256 | "NON_REGISTERED_PARAMETER_NUMBER_LSB": 98,
257 | "NON_REGISTERED_PARAMETER_NUMBER_MSB": 99,
258 | "REGISTERED_PARAMETER_NUMBER_LSB": 100,
259 | "REGISTERED_PARAMETER_NUMBER_MSB": 101,
260 | "ALL_SOUND_OFF": 120,
261 | "RESET_ALL_CONTROLLERS": 121,
262 | "LOCAL_CONTROL_ON_OFF": 122,
263 | "ALL_NOTES_OFF": 123,
264 | "OMNI_MODE_OFF": 124,
265 | "OMNI_MODE_ON": 125,
266 | "MONO_MODE_ON": 126,
267 | "POLY_MODE_ON": 127,
268 | "UNDEFINED": [3, 9, 14, 15, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 85, 86, 87, 89, 90, 102,
269 | 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119]
270 | }
271 |
272 | NOTE_CODE = {
273 | 127: ["G9", 12543.85],
274 | 126: ["F#9", 11839.82],
275 | 125: ["F9", 11175.30],
276 | 124: ["E9", 10548.08],
277 | 123: ["D#9", 9956.06],
278 | 122: ["D9", 9397.27],
279 | 121: ["C#9", 8869.84],
280 | 120: ["C9", 8372.02],
281 | 119: ["B8", 7902.13],
282 | 118: ["A#8", 7458.62],
283 | 117: ["A8", 7040.00],
284 | 116: ["G#8", 6644.88],
285 | 115: ["G8", 6271.93],
286 | 114: ["F#8", 5919.91],
287 | 113: ["F8", 5587.65],
288 | 112: ["E8", 5274.04],
289 | 111: ["D#8", 4978.03],
290 | 110: ["D8", 4698.64],
291 | 109: ["C#8", 4434.92],
292 | 108: ["C8", 4186.01],
293 | 107: ["B7", 3951.07],
294 | 106: ["A#7", 3729.31],
295 | 105: ["A7", 3520.00],
296 | 104: ["G#7", 3322.44],
297 | 103: ["G7", 3135.96],
298 | 102: ["F#7", 2959.96],
299 | 101: ["F7", 2793.83],
300 | 100: ["E7", 2637.02],
301 | 99: ["D#7", 2489.02],
302 | 98: ["D7", 2349.32],
303 | 97: ["C#7", 2217.46],
304 | 96: ["C7", 2093.00],
305 | 95: ["B6", 1975.53],
306 | 94: ["A#6", 1864.66],
307 | 93: ["A6", 1760.00],
308 | 92: ["G#6", 1661.22],
309 | 91: ["G6", 1567.98],
310 | 90: ["F#6", 1479.98],
311 | 89: ["F6", 1396.91],
312 | 88: ["E6", 1318.51],
313 | 87: ["D#6", 1244.51],
314 | 86: ["D6", 1174.66],
315 | 85: ["C#6", 1108.73],
316 | 84: ["C6", 1046.50],
317 | 83: ["B5", 987.77],
318 | 82: ["A#5", 932.33],
319 | 81: ["A5", 880.00],
320 | 80: ["G#5", 830.61],
321 | 79: ["G5", 783.99],
322 | 78: ["F#5", 739.99],
323 | 77: ["F5", 698.46],
324 | 76: ["E5", 659.26],
325 | 75: ["D#5", 622.25],
326 | 74: ["D5", 587.33],
327 | 73: ["C#5", 554.37],
328 | 72: ["C5", 523.25],
329 | 71: ["B4", 493.88],
330 | 70: ["A#4", 466.16],
331 | 69: ["A4", 440.00],
332 | 68: ["G#4", 415.30],
333 | 67: ["G4", 392.00],
334 | 66: ["F#4", 369.99],
335 | 65: ["F4", 349.23],
336 | 64: ["E4", 329.63],
337 | 63: ["D#4", 311.13],
338 | 62: ["D4", 293.66],
339 | 61: ["C#4", 277.18],
340 | 60: ["C4", 261.63],
341 | 59: ["B3", 246.94],
342 | 58: ["A#3", 233.08],
343 | 57: ["A3", 220.00],
344 | 56: ["G#3", 207.65],
345 | 55: ["G3", 196.00],
346 | 54: ["F#3", 185.00],
347 | 53: ["F3", 174.61],
348 | 52: ["E3", 164.81],
349 | 51: ["D#3", 155.56],
350 | 50: ["D3", 146.83],
351 | 49: ["C#3", 138.59],
352 | 48: ["C3", 130.81],
353 | 47: ["B2", 123.47],
354 | 46: ["A#2", 116.54],
355 | 45: ["A2", 110.00],
356 | 44: ["G#2", 103.83],
357 | 43: ["G2", 98.00],
358 | 42: ["F#2", 92.50],
359 | 41: ["F2", 87.31],
360 | 40: ["E2", 82.41],
361 | 39: ["D#2", 77.78],
362 | 38: ["D2", 73.42],
363 | 37: ["C#2", 69.30],
364 | 36: ["C2", 65.41],
365 | 35: ["B1", 61.74],
366 | 34: ["A#1", 58.27],
367 | 33: ["A1", 55.00],
368 | 32: ["G#1", 51.91],
369 | 31: ["G1", 49.00],
370 | 30: ["F#1", 46.25],
371 | 29: ["F1", 43.65],
372 | 28: ["E1", 41.20],
373 | 27: ["D#1", 38.89],
374 | 26: ["D1", 36.71],
375 | 25: ["C#1", 34.65],
376 | 24: ["C1", 32.70],
377 | 23: ["B0", 30.87],
378 | 22: ["A#0", 29.14],
379 | 21: ["A0", 27.50],
380 | "G9": 127,
381 | "F#9": 126,
382 | "F9": 125,
383 | "E9": 124,
384 | "D#9": 123,
385 | "D9": 122,
386 | "C#9": 121,
387 | "C9": 120,
388 | "B8": 119,
389 | "A#8": 118,
390 | "A8": 117,
391 | "G#8": 116,
392 | "G8": 115,
393 | "F#8": 114,
394 | "F8": 113,
395 | "E8": 112,
396 | "D#8": 111,
397 | "D8": 110,
398 | "C#8": 109,
399 | "C8": 108,
400 | "B7": 107,
401 | "A#7": 106,
402 | "A7": 105,
403 | "G#7": 104,
404 | "G7": 103,
405 | "F#7": 102,
406 | "F7": 101,
407 | "E7": 100,
408 | "D#7": 99,
409 | "D7": 98,
410 | "C#7": 97,
411 | "C7": 96,
412 | "B6": 95,
413 | "A#6": 94,
414 | "A6": 93,
415 | "G#6": 92,
416 | "G6": 91,
417 | "F#6": 90,
418 | "F6": 89,
419 | "E6": 88,
420 | "D#6": 87,
421 | "D6": 86,
422 | "C#6": 85,
423 | "C6": 84,
424 | "B5": 83,
425 | "A#5": 82,
426 | "A5": 81,
427 | "G#5": 80,
428 | "G5": 79,
429 | "F#5": 78,
430 | "F5": 77,
431 | "E5": 76,
432 | "D#5": 75,
433 | "D5": 74,
434 | "C#5": 73,
435 | "C5": 72,
436 | "B4": 71,
437 | "A#4": 70,
438 | "A4": 69,
439 | "G#4": 68,
440 | "G4": 67,
441 | "F#4": 66,
442 | "F4": 65,
443 | "E4": 64,
444 | "D#4": 63,
445 | "D4": 62,
446 | "C#4": 61,
447 | "C4": 60,
448 | "B3": 59,
449 | "A#3": 58,
450 | "A3": 57,
451 | "G#3": 56,
452 | "G3": 55,
453 | "F#3": 54,
454 | "F3": 53,
455 | "E3": 52,
456 | "D#3": 51,
457 | "D3": 50,
458 | "C#3": 49,
459 | "C3": 48,
460 | "B2": 47,
461 | "A#2": 46,
462 | "A2": 45,
463 | "G#2": 44,
464 | "G2": 43,
465 | "F#2": 42,
466 | "F2": 41,
467 | "E2": 40,
468 | "D#2": 39,
469 | "D2": 38,
470 | "C#2": 37,
471 | "C2": 36,
472 | "B1": 35,
473 | "A#1": 34,
474 | "A1": 33,
475 | "G#1": 32,
476 | "G1": 31,
477 | "F#1": 30,
478 | "F1": 29,
479 | "E1": 28,
480 | "D#1": 27,
481 | "D1": 26,
482 | "C#1": 25,
483 | "C1": 24,
484 | "B0": 23,
485 | "A#0": 22,
486 | "A0": 21,
487 | }
488 |
489 | MIDI_QTR_FRAME_CODE = {
490 | 0: "24 fps",
491 | 1: "25 fps",
492 | 2: "29.97 fps",
493 | 3: "30 fps",
494 | "24 fps": 0,
495 | "25 fps": 1,
496 | "29.97 fps": 2,
497 | "30 fps": 3,
498 | }
499 |
500 | MIDI_SEQUENCE = {
501 | "NONE": None,
502 | "NOTE_OFF": 0x80,
503 | "NOTE_ON": 0x90,
504 | "POLY_AFTERTOUCH": 0xA0,
505 | "CONTROL_CHANGE": 0xB0,
506 | "PROGRAM_CHANGE": 0xC0,
507 | "CHANNEL_AFTERTOUCH": 0xD0,
508 | "PITCH_BEND": 0xE0,
509 | "SYSEX": 0xF0,
510 | "TIME_CODE_QTR_FRAME": 0xF1,
511 | "SONG_POSITION_POINTER": 0xF2,
512 | "SONG_SELECT": 0xF3,
513 | "TUNE_REQUEST": 0xF6,
514 | "SYSEX_END": 0xF7,
515 | "TIMING_CLOCK": 0xF8,
516 | "START": 0xFA,
517 | "CONTINUE": 0xFB,
518 | "STOP": 0xFC,
519 | "ACTIVE_SENSING": 0xFD,
520 | "RESET": 0xFF,
521 | }
522 |
523 |
524 | # Misc. methods ########################################################################################################
525 | def percentage_to_7_bit(percentage: float) -> int:
526 | return int(percentage / 100 * 127)
527 |
528 |
529 | # Midi class ###########################################################################################################
530 | # noinspection PyTypeChecker
531 | class Midi:
532 | def __init__(self, uart, tx, rx):
533 | self.last_sequence = MIDI_SEQUENCE["NONE"]
534 | self.message = 0
535 | self.channel = 0
536 | self.state = 0
537 | self.last_rx_parameters = {
538 | "note_off": {
539 | "note": None,
540 | },
541 | "note_on": {
542 | "note": None,
543 | "velocity": None,
544 | },
545 | "poly_aftertouch": {
546 | "note": None,
547 | "pressure": None,
548 | },
549 | "control_change": {
550 | "code": None,
551 | "value": None,
552 | },
553 | "program_change": {
554 | "program": None,
555 | },
556 | "channel_aftertouch": {
557 | "pressure": None,
558 | },
559 | "pitch_bend": {
560 | "bend_LSB": None,
561 | "bend_MSB": None,
562 | "bend": None,
563 | },
564 | "song_position": 0,
565 | "song_position_pointer": {
566 | "LSB": None,
567 | "MSB": None,
568 | "Position": None,
569 | },
570 | "song_select": {
571 | "song": None,
572 | },
573 | "time_code_qtr_frame": {
574 | "rate": None,
575 | "hours": None,
576 | "minutes": None,
577 | "seconds": None,
578 | "frames": None,
579 | },
580 | "sysex_vendor_id": "",
581 | "sysex": [],
582 | }
583 | print("Starting MIDI...")
584 | self.uart = UART(uart, BAUD, tx=tx, rx=rx)
585 | print("Success!\t->OUTPUT <-INPUT")
586 |
587 | # UART "inherited" methods. C-based classes don't work when inherited in python ###################################
588 | def write(self, value: int):
589 | self.uart.write(bytes([value]))
590 |
591 | def read(self, n_bytes: int):
592 | return self.uart.read(n_bytes)
593 |
594 | def any(self):
595 | return self.uart.any()
596 |
597 | # Other ############################################################################################################
598 | def get_parameter(self, message_type, parameter):
599 | return self.last_rx_parameters[message_type][parameter]
600 |
601 | # MIDI send methods ################################################################################################
602 | def send_note_off(self, channel: int, note: int):
603 | self.write(MESSAGE_NOTE_OFF | channel)
604 | self.write(note)
605 | self.write(0)
606 | print(f"-> NOTE OFF\t\t{note} ({NOTE_CODE[note][0]})")
607 |
608 | def send_note_on(self, channel: int, note: int, velocity: int):
609 | self.write(MESSAGE_NOTE_ON | channel)
610 | self.write(note)
611 | self.write(velocity)
612 | print(f"-> NOTE ON\t\t{note} ({NOTE_CODE[note][0]})\t\tVELOCITY {velocity}")
613 |
614 | def send_poly_aftertouch(self, channel: int, note: int, pressure: int):
615 | self.write(MESSAGE_POLYPHONIC_AFTERTOUCH | channel)
616 | self.write(note)
617 | self.write(pressure)
618 | print(f"-> POLY AT\t\t{note} ({NOTE_CODE[note][0]})\t\t\tPRESSURE {pressure}")
619 |
620 | def send_control_change(self, channel: int, cc: int, value: int):
621 | self.write(MESSAGE_CONTROL_CHANGE | channel)
622 | self.write(cc)
623 | self.write(value)
624 | print(f"-> CONTROL CHANGE\t{cc} ({CONTROL_CHANGE_CODE[cc]})\tVALUE {value}")
625 |
626 | def send_program_change(self, channel: int, program: int):
627 | self.write(MESSAGE_PROGRAM_CHANGE | channel)
628 | self.write(program)
629 | print(f"-> PROGRAM CHANGE\t{channel} TO {program}")
630 |
631 | def send_channel_aftertouch(self, channel: int, amount: int):
632 | self.write(MESSAGE_CHANNEL_AFTERTOUCH | channel)
633 | self.write(amount)
634 | print(f"-> CHANNEL AT\t\tCHANNEL {channel+1}\t\tAMOUNT {amount}")
635 |
636 | def send_pitch_bend(self, channel: int, note: int, amount: int):
637 | self.write(MESSAGE_PITCH_BEND | channel)
638 | self.write(note)
639 | self.write(amount)
640 | print(f"-> PITCH BEND\t{note} AMOUNT {amount}")
641 |
642 | def send_sysex_start(self):
643 | self.write(MESSAGE_SYSTEM_EXCLUSIVE_START)
644 | print(f"-> SYSEX START")
645 |
646 | def send_time_code_qtr_frame(self, rate: int, hours: int, minutes: int, seconds: int, frames: int):
647 | self.write(MESSAGE_MIDI_TIME_CODE_QTR_FRAME)
648 | self.write((rate << 5) | hours)
649 | self.write(minutes)
650 | self.write(seconds)
651 | self.write(frames)
652 | print(f"-> TIME CODE\t\t{hours}:{minutes}:{seconds}.{frames}")
653 |
654 | def send_song_position_pointer(self, beats_since_start: int):
655 | lsb = beats_since_start & 0x7F
656 | msb = (beats_since_start >> 7) & 0x7F
657 | self.write(MESSAGE_SONG_POSITION_POINTER)
658 | self.write(lsb)
659 | self.write(msb)
660 | print(f"-> SONG POSITION\tBEAT {beats_since_start}")
661 |
662 | def send_song_select(self, song: int):
663 | self.write(MESSAGE_SONG_SELECT)
664 | self.write(song)
665 | print(f"-> SONG SELECT\t\tSONG {song}")
666 |
667 | def send_tune_request(self):
668 | self.write(MESSAGE_TUNE_REQUEST)
669 | print(f"-> TUNE REQUEST")
670 |
671 | def send_sysex(self, sysex_list: list):
672 | for item in sysex_list:
673 | self.write(item)
674 |
675 | def send_sysex_stop(self):
676 | self.write(MESSAGE_SYSTEM_EXCLUSIVE_STOP)
677 | print(f"-> SYSEX STOP")
678 |
679 | def send_timing_clock(self):
680 | self.write(MESSAGE_TIMING_CLOCK)
681 | print(f"-> TIMING CLOCK")
682 |
683 | def send_playback_start(self):
684 | self.write(MESSAGE_START)
685 | print(f"-> PLAYBACK START")
686 |
687 | def send_playback_continue(self):
688 | self.write(MESSAGE_CONTINUE)
689 | print(f"-> PLAYBACK CONTINUE")
690 |
691 | def send_playback_stop(self):
692 | self.write(MESSAGE_STOP)
693 | print(f"-> PLAYBACK STOP")
694 |
695 | def send_active_sensing(self):
696 | self.write(MESSAGE_ACTIVE_SENSING)
697 | print(f"-> ACTIVE SENSING")
698 |
699 | def send_reset(self):
700 | self.write(MESSAGE_SYSTEM_RESET)
701 | print(f"-> SYSTEM RESET")
702 |
703 | # MIDI receive methods #############################################################################################
704 | def load_message(self, msg: int):
705 | self.message = msg
706 | self.analyze_message()
707 |
708 | def get_channel(self):
709 | return self.message & 0x0F
710 |
711 | def analyze_message(self):
712 | # ANYTHING GOES
713 | if self.state == 0:
714 | if self.message < 0xF0:
715 | self.state = self.message & 0xF0
716 | self.channel = self.get_channel()
717 | else:
718 | self.state = self.message
719 | # NOTE OFF
720 | elif self.state == MESSAGE_NOTE_OFF:
721 | self.last_rx_parameters["note_off"]["note"] = self.message
722 | self.state = 0x81
723 | elif self.state == 0x81:
724 | self.state = 0
725 | note_code = self.last_rx_parameters["note_off"]["note"]
726 | note_name = NOTE_CODE[self.last_rx_parameters["note_off"]["note"]][0]
727 | print(f"<- CH{self.channel+1} NOTE OFF\t\t{note_code} ({note_name})")
728 | self.last_sequence = MIDI_SEQUENCE["NOTE_OFF"]
729 | # NOTE ON
730 | elif self.state == MESSAGE_NOTE_ON:
731 | self.last_rx_parameters["note_on"]["note"] = self.message
732 | self.state = 0x91
733 | elif self.state == 0x91:
734 | self.last_rx_parameters["note_on"]["velocity"] = self.message
735 | self.state = 0
736 | note_code = self.last_rx_parameters["note_on"]["note"]
737 | note_name = NOTE_CODE[self.last_rx_parameters["note_on"]["note"]][0]
738 | velocity = self.last_rx_parameters["note_on"]['velocity']
739 | print(f"<- CH{self.channel+1} NOTE ON\t\t{note_code} ({note_name})\t\t\tVELOCITY {velocity}")
740 | self.last_sequence = MIDI_SEQUENCE["NOTE_ON"]
741 | # POLY AFTERTOUCH
742 | elif self.state == MESSAGE_POLYPHONIC_AFTERTOUCH:
743 | self.last_rx_parameters["poly_aftertouch"]["note"] = self.message
744 | self.state = 0xA1
745 | elif self.state == 0xA1:
746 | self.last_rx_parameters["poly_aftertouch"]["pressure"] = self.message
747 | self.state = 0
748 | note_code = self.last_rx_parameters["poly_aftertouch"]["note"]
749 | note_name = NOTE_CODE[self.last_rx_parameters["poly_aftertouch"]["note"]][0]
750 | pressure = self.last_rx_parameters["poly_aftertouch"]["pressure"]
751 | print(f"<- CH{self.channel+1} POLY AFTERTOUCH\t\t{note_code} ({note_name})\t\tPRESSURE {pressure}")
752 | self.last_sequence = MIDI_SEQUENCE["POLY_AFTERTOUCH"]
753 | # CONTROL CHANGE
754 | elif self.state == MESSAGE_CONTROL_CHANGE:
755 | self.last_rx_parameters["control_change"]["code"] = self.message
756 | self.state = 0xB1
757 | elif self.state == 0xB1:
758 | self.last_rx_parameters["control_change"]["value"] = self.message
759 | self.state = 0
760 | cc_name = CONTROL_CHANGE_CODE[self.last_rx_parameters["control_change"]["code"]]
761 | cc_code = self.last_rx_parameters["control_change"]["code"]
762 | value = self.last_rx_parameters["control_change"]["value"]
763 | print(f"<- CH{self.channel+1} CONTROL CHANGE\t{cc_code} ({cc_name})\tVALUE {value}")
764 | self.last_sequence = MIDI_SEQUENCE["CONTROL_CHANGE"]
765 | # PROGRAM CHANGE
766 | elif self.state == MESSAGE_PROGRAM_CHANGE:
767 | self.last_rx_parameters["program_change"]["program"] = self.message
768 | self.state = 0
769 | program = self.last_rx_parameters["program_change"]["program"]
770 | print(f"<- CH{self.channel + 1} PROGRAM CHANGE\t{program}")
771 | self.last_sequence = MIDI_SEQUENCE["PROGRAM_CHANGE"]
772 | # CHANNEL AFTERTOUCH
773 | elif self.state == MESSAGE_CHANNEL_AFTERTOUCH:
774 | self.last_rx_parameters["channel_aftertouch"]["pressure"] = self.message
775 | self.state = 0
776 | pressure = self.last_rx_parameters["channel_aftertouch"]["pressure"]
777 | print(f"<- CH{self.channel + 1} CHANNEL AFTERTOUCH\t\t\tPRESSURE {pressure}")
778 | self.last_sequence = MIDI_SEQUENCE["CHANNEL_AFTERTOUCH"]
779 | # PITCH BEND
780 | elif self.state == MESSAGE_PITCH_BEND:
781 | self.last_rx_parameters["pitch_bend"]["bend_LSB"] = self.message
782 | self.state = 0xE1
783 | elif self.state == 0xE1:
784 | self.last_rx_parameters["pitch_bend"]["bend_MSB"] = self.message
785 | bend_msb = self.last_rx_parameters["pitch_bend"]["bend_MSB"]
786 | bend_lsb = self.last_rx_parameters["pitch_bend"]["bend_LSB"]
787 | self.state = 0
788 | self.last_rx_parameters["pitch_bend"]["bend"] = (bend_msb << 8) | bend_lsb
789 | bend = self.last_rx_parameters["pitch_bend"]["bend"]
790 | print(f"<- CH{self.channel+1} PITCH BEND\t\tBEND {bend}")
791 | self.last_sequence = MIDI_SEQUENCE["PITCH_BEND"]
792 | # START
793 | elif self.state == MESSAGE_START:
794 | self.state = 0
795 | print(f"<- START")
796 | self.last_sequence = MIDI_SEQUENCE["START"]
797 | # STOP
798 | elif self.state == MESSAGE_STOP:
799 | self.state = 0
800 | print(f"<- STOP")
801 | self.last_sequence = MIDI_SEQUENCE["STOP"]
802 | # CONTINUE
803 | elif self.state == MESSAGE_CONTINUE:
804 | self.state = 0
805 | print(f"<- CONTINUE")
806 | self.last_sequence = MIDI_SEQUENCE["CONTINUE"]
807 | # TIMING CLOCK
808 | elif self.state == MESSAGE_TIMING_CLOCK:
809 | self.state = 0
810 | print(f"<- TIMING CLOCK")
811 | self.last_sequence = MIDI_SEQUENCE["TIMING_CLOCK"]
812 | # SYSEX START
813 | elif self.state == MESSAGE_START:
814 | self.state = 0xF4
815 | print(f"<- SYSEX START")
816 | self.last_sequence = MIDI_SEQUENCE["SYSEX"]
817 | self.last_rx_parameters["sysex_vendor_id"] = self.message
818 | # SYSEX SEQUENCE
819 | elif self.state == MESSAGE_SYSTEM_EXCLUSIVE_START:
820 | if self.message != MESSAGE_SYSTEM_EXCLUSIVE_STOP:
821 | self.last_rx_parameters["sysex"].append(self.message)
822 | print(f"<- SYSEX: {hex(self.message)}")
823 | else:
824 | self.state = 0
825 | print(f"<- SYSEX STOP")
826 | self.last_sequence = MIDI_SEQUENCE["SYSEX_END"]
827 | # TUNE REQUEST
828 | elif self.state == MESSAGE_TUNE_REQUEST:
829 | self.state = 0
830 | print(f"<- TUNE REQUEST")
831 | self.last_sequence = MIDI_SEQUENCE["TUNE_REQUEST"]
832 | # SONG SELECT
833 | elif self.state == MESSAGE_SONG_SELECT:
834 | self.last_rx_parameters["song_select"]["song"] = self.message
835 | self.state = 0
836 | song = self.last_rx_parameters["song_select"]["song"]
837 | print(f"<- SONG SELECT\t\t{song}")
838 | self.last_sequence = MIDI_SEQUENCE["SONG_SELECT"]
839 | # ACTIVE SENSING
840 | elif self.state == MESSAGE_ACTIVE_SENSING:
841 | self.state = 0
842 | print(f"<- ACTIVE SENSING")
843 | self.last_sequence = MIDI_SEQUENCE["ACTIVE_SENSING"]
844 | # SYSTEM RESET
845 | elif self.state == MESSAGE_SYSTEM_RESET:
846 | self.state = 0
847 | print(f"<- SYSTEM RESET")
848 | self.last_sequence = MIDI_SEQUENCE["SYSTEM_RESET"]
849 | # MIDI TIME CODE QTR FRAME
850 | elif self.state == 0xF1:
851 | self.state = 0xF10
852 | rate = (0b01100000 | self.message) >> 5
853 | self.last_rx_parameters["time_code_qtr_frame"]["rate"] = MIDI_QTR_FRAME_CODE[rate]
854 | hour = (0b00011111 | self.message)
855 | self.last_rx_parameters["time_code_qtr_frame"]["hour"] = hour
856 | elif self.state == 0xF10:
857 | self.state = 0xF11
858 | minute = self.message
859 | self.last_rx_parameters["time_code_qtr_frame"]["minute"] = minute
860 | elif self.state == 0xF11:
861 | self.state = 0xF12
862 | second = self.message
863 | self.last_rx_parameters["time_code_qtr_frame"]["second"] = second
864 | elif self.state == 0xF12:
865 | self.state = 0
866 | frame = self.message
867 | self.last_rx_parameters["time_code_qtr_frame"]["frame"] = frame
868 | rate = self.last_rx_parameters["time_code_qtr_frame"]["rate"]
869 | hour = self.last_rx_parameters["time_code_qtr_frame"]["hour"]
870 | second = self.last_rx_parameters["time_code_qtr_frame"]["second"]
871 | minute = self.last_rx_parameters["time_code_qtr_frame"]["minute"]
872 | print(f"<- TIME CODE QTR FRAME\t{rate}\t{hour}:{minute}:{second}.{frame}")
873 | self.last_sequence = MIDI_SEQUENCE["TIME_CODE_QTR_FRAME"]
874 | # SONG POSITION POINTER
875 | elif self.state == 0xF2:
876 | self.last_rx_parameters["song_position_pointer"]["LSB"] = self.message
877 | self.state = 0xF9
878 | elif self.state == 0xF9:
879 | self.state = 0
880 | self.last_rx_parameters["song_position_pointer"]["MSB"] = self.message
881 | msb = self.last_rx_parameters["song_position_pointer"]["MSB"]
882 | lsb = self.last_rx_parameters["song_position_pointer"]["LSB"]
883 | position = (msb << 8) | lsb
884 | self.last_rx_parameters["song_position_pointer"]["position"] = position
885 | print(f"<- SONG POSITION POINTER {position}")
886 | self.last_sequence = MIDI_SEQUENCE["SONG_POSITION_POINTER"]
887 | # OTHER (THIS SHOULD NEVER HAPPEN)
888 | else:
889 | self.state = 0
890 | print("<- NOT DEFINED")
891 |
892 |
--------------------------------------------------------------------------------