diff --git a/Midi-Expression-Pedal.py b/boot.py similarity index 50% rename from Midi-Expression-Pedal.py rename to boot.py index 0a87a2f..df179f0 100644 --- a/Midi-Expression-Pedal.py +++ b/boot.py @@ -1,3 +1,12 @@ +from typing import type_check_only +import machine +import midi + +# Devices +exp = machine.ADC(31) # Expression pedal device on pin 31 +serial = pyb.USB_VCP() +led = Pin(25, Pin.OUT) + # Expression pedal settings exp_min = 0 # Expression pedal minimum value exp_max = 65535 #Expression pedal maximum value @@ -13,5 +22,18 @@ def translate(exp_val): return (((exp_val - exp_min) * (cc_max - cc_min)) / (exp_max - exp_min)) + cc_min # Test the translate function: -for i in range(65535): - print (translate(i)) \ No newline at end of file +# for i in range(65535): +# print (translate(i)) + +usb_midi = midi.Controller(serial, channel=midi_channel) +exp_value_previous = 0 + +while True: + exp_value_current = exp.read_u16() + if exp_value_current != exp_value_previous: + led.value(1) # Turn led on + cc_val = translate(exp_value_current) + usb_midi.control_change(cc, cc_val) + led.value(0) # Turn led off + exp_value_previous = exp_value_current + print(cc_val) \ No newline at end of file diff --git a/midi.py b/midi.py new file mode 100644 index 0000000..a1d6461 --- /dev/null +++ b/midi.py @@ -0,0 +1,224 @@ +# Copyright (C) 2014 Craig Barnes +# +# 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 . + +""" +Micro Python Midi +This module implements channel commands according to the midi specification. +Each midi message consists of 3 bytes. +The first byte is the sum of the command and the midi channel (1-16 > 0-F). +the value of bytes 2 and 3 (data 1 and 2) are dependant on the command. +command data1 data2 Description +------- ----- ----- ----------- +0x80-0x8F Key # (0-127) Off Velocity (0-127) Note Off +0x90-0x90 Key # (0-127) On Velocity (0-127) Note On +0xA0-0xA0 Key # (0-127) Pressure (0-127) Poly Pressure +0xB0-0xB0 Control # (0-127) Control Value (0-127) Control +0xC0-0xC0 Program # (0-127) Not Used (send 0) Program Change +0xD0-0xD0 Pressure Value (0-127) Not Used (send 0) Channel Pressure +0xE0-0xE0 Range LSB (0-127) Range MSB (0-127) Pitch Bend +http://www.midi.org/techspecs/midimessages.php +""" + + +class MidiInteger: + """A midi message sends data as 7 bit values between 0 and 127.""" + def __init__(self, value): + if 0 <= value < 2 ** 7: + self.value = value + else: + raise ValueError( + 'Invalid midi data value: {}'.format(value), + 'A midi data value must be an integer between 0 and 127') + + def __repr__(self): + return ''.format(self.value) + + +class BigMidiInteger: + """Some messages use 14 bit values, these need to be spit down to + msb and lsb before being sent.""" + def __init__(self, value): + if 0 <= value <= 2 ** 14: + self.msb = value // 2 ** 7 + self.lsb = value % 2 ** 7 + else: + raise ValueError( + 'Invalid midi data value: {}'.format(value), + 'A midi datavalue must be an integer between0' + ' and {}'.format(2 ** 14)) + + def __repr__(self): + return ''.format(self.lsb, self.msb) + + +class Controller: + """A device that is designed to send midi messages to an external + instrument or sequencer, is commonly referred to as a midi controller. + http://en.wikipedia.org/wiki/MIDI_controller + An instance of the Controller class should be considered to be a midi + contoller in the same context. + More than one can be created, there are no constraints. but the + convention is to keep each controller on a separate midi port or channel. + Usage + ===== + The following example creates a controller on midi channel one using the + USB Virtual Com Port device. Then sends a note on message followed by a + note off message. + >>> import pyb + >>> my_controller = Controller(pyb.USB_VCP(), 1) + >>> my_controller.note_on(65) + >>> pyb.delay(100) + >>> my_controller.note_off(65) + In order to pass these midi signals on to your computers midi stack, you + will need to disconnect your console from the Micro Python board, + and run a serial to midi bridge utility such as ttymidi for Linux. + Others are available for Mac and Windows. + With your console still attached you would have seen the characters + representing the bytes. + The example above demonstrates the nature of midi note information. + When a keyboard key is pressed a note_on message is sent. When the + key is released a note off message is sent. + An optional velocity value can also be sent, this usually controls + the volume of the note played, but this is often used to control other + characteristics of the sound played. + """ + COMMANDS = ( + 0x80, # Note Off + 0x90, # Note On + 0xA0, # Poly Pressure + 0xB0, # Control Change + 0xC0, # Program Change + 0xD0, # Mono Pressure + 0xE0 # Pich Bend + ) + + def __init__(self, port, channel=1): + self.port = port + try: + assert 1 <= channel <= 16 + except: + raise ValueError('channel must be an integer between 1 & 16') + self.channel = channel + self.timeout = 100 + + def __repr__(self): + return ''.format( + port=self.port, + channel=self.channel) + + def send_message(self, command, data1, data2=0): + """Send a midi message to the serial device.""" + if command not in self.COMMANDS: + raise ValueError('Invalid Command: {}'.format(command)) + + command += self.channel - 1 + + self.port.send(command, timeout=self.timeout) + self.port.send(MidiInteger(data1).value, timeout=self.timeout) + self.port.send(MidiInteger(data2).value, timeout=self.timeout) + + def note_off(self, note, velocity=0): + """Send a 'Note Off' message""" + self.send_message(0x80, note, velocity) + + def note_on(self, note, velocity=127): + """Send a 'Note On' message""" + self.send_message(0x90, note, velocity) + + def pressure(self, value, note=None): + """If a note value is provided then send a polyphonic pressure + message, otherwise send a Channel (mono) pressure message.""" + if note: + self.send_message(0xA0, note, value) + else: + self.send_message(0xD0, value) + + def control_change(self, control, value): + """Send a control e.g. modulation or pedal message.""" + self.send_message(0xB0, control, value) + + def program_change(self, value, bank=None): + """Send a program change message, include bank if provided.""" + if bank: + bank = BigMidiInteger(bank) + self.control_change(32, bank.lsb) + self.control_change(0, bank.msb) + self.send_message(0xC0, value) + + def pitch_bend(self, value=0x2000): + """Send a pich bend message. + Pich bend is a 14 bit value, centreed at 0x2000""" + value = BigMidiInteger(value) + self.send_message(0xE0, value.lsb, value.msb) + + def modulation(self, value, fine=False): + """Send modulation control change.""" + if fine: + value = MidiInteger(value) + self.control_change(33, value.lsb) + self.control_change(1, value.msb) + else: + self.control_change(1, value) + + def volume(self, value, fine=False): + """Send volume control change.""" + if fine: + value = BigMidiInteger(value) + self.control_change(39, value.lsb) + self.control_change(7, value.msb) + else: + self.control_change(7, value) + + def all_sound_off(self): + """Switch all sounds off. """ + self.control_change(120, 0) + + def reset_all_controllers(self): + """Set all controllers to their default values.""" + self.control_change(121, 0) + + def local_control(self, value): + """Enable or disable local control.""" + if bool(value): + self.control_change(122, 127) + else: + self.control_change(122, 0) + + def all_notes_off(self): + """Send 'All Notes Off' message.""" + self.control_change(123, 0) + + def panic(self): + """Reset everything and stop making noise.""" + self.all_sound_off() + self.reset_all_controllers() + self.all_notes_off() + + +if __name__ == '__main__': + import pyb + serial = pyb.USB_VCP() + instrument1 = Controller(serial, channel=1) + accel = pyb.Accel() + switch = pyb.Switch() + while 1: + while not switch(): + pyb.delay(10) + note = accel.x() + velocity = accel.y() + instrument1.note_on(+(note + 65), +(velocity + 65)) + while switch(): + pyb.delay(50) + instrument1.note_off(note + 65) \ No newline at end of file