# SPDX-FileCopyrightText: 2020 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import time
import board
import simpleio
import busio
import adafruit_lis3dh
import digitalio
from digitalio import DigitalInOut, Direction, Pull
from analogio import AnalogIn
import usb_midi
import adafruit_midi
from adafruit_midi.note_on          import NoteOn
from adafruit_midi.note_off         import NoteOff
from adafruit_midi.control_change   import ControlChange
from adafruit_midi.pitch_bend       import PitchBend

#  imports MIDI
midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)

#  setup for LIS3DH accelerometer
i2c = busio.I2C(board.SCL, board.SDA)
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)

lis3dh.range = adafruit_lis3dh.RANGE_2_G

#  setup for 3 potentiometers
pitchbend_pot = AnalogIn(board.A1)
mod_pot = AnalogIn(board.A2)
velocity_pot = AnalogIn(board.A3)

#  setup for two switches that will switch modes
mod_select = DigitalInOut(board.D52)
mod_select.direction = Direction.INPUT
mod_select.pull = Pull.UP

strum_select = DigitalInOut(board.D53)
strum_select.direction = Direction.INPUT
strum_select.pull = Pull.UP

#  setup for strummer switches
strumUP = DigitalInOut(board.D22)
strumUP.direction = Direction.INPUT
strumUP.pull = Pull.UP

strumDOWN = DigitalInOut(board.D23)
strumDOWN.direction = Direction.INPUT
strumDOWN.pull = Pull.UP

#  setup for cherry mx switches on neck
note_pins = [board.D14, board.D2, board.D3, board.D4, board.D5,
             board.D6, board.D7, board.D8, board.D9, board.D10, board.D11, board.D12]

note_buttons = []

for pin in note_pins:
    note_pin = digitalio.DigitalInOut(pin)
    note_pin.direction = digitalio.Direction.INPUT
    note_pin.pull = digitalio.Pull.UP
    note_buttons.append(note_pin)

#  setup for rotary switch
oct_sel_pins = [board.D24, board.D25, board.D26, board.D27, board.D28, board.D29,
                board.D30, board.D31]

octave_selector = []

for pin in oct_sel_pins:
    sel_pin = digitalio.DigitalInOut(pin)
    sel_pin.direction = digitalio.Direction.INPUT
    sel_pin.pull = digitalio.Pull.UP
    octave_selector.append(sel_pin)

#  cherry mx switch states
note_e_pressed = None
note_f_pressed = None
note_fsharp_pressed = None
note_g_pressed = None
note_gsharp_pressed = None
note_a_pressed = None
note_asharp_pressed = None
note_b_pressed = None
note_c_pressed = None
note_csharp_pressed = None
note_d_pressed = None
note_dsharp_pressed = None

#  state machines
strummed = None
pick = None
up_pick = None
down_pick = None

#  states for analog inputs
pitchbend_val2 = 0
mod_val2 = 0
velocity_val2 = 0
acc_pos_val2 = 0
acc_neg_val2 = 0

#  array for cherry mx switch states
note_states = [note_e_pressed, note_f_pressed, note_fsharp_pressed, note_g_pressed,
               note_gsharp_pressed, note_a_pressed, note_asharp_pressed, note_b_pressed,
               note_c_pressed, note_csharp_pressed, note_d_pressed, note_dsharp_pressed]

#  array of MIDI note numbers
note_numbers = [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
                41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
                61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
                81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100,
                101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
                117, 118, 119, 120, 121, 120, 123, 124, 125, 126, 127]

#  list of note name variables that are assigned to the note_numbers array
#  this allows you to use the note names rather than numbers when assigning
#  them to the cherry mx switches
(A0, Bb0, B0, C1, Db1, D1, Eb1, E1, F1, Gb1, G1, Ab1,
 A1, Bb1, B1, C2, Db2, D2, Eb2, E2, F2, Gb2, G2, Ab2,
 A2, Bb2, B2, C3, Db3, D3, Eb3, E3, F3, Gb3, G3, Ab3,
 A3, Bb3, B3, C4, Db4, D4, Eb4, E4, F4, Gb4, G4, Ab4,
 A4, Bb4, B4, C5, Db5, D5, Eb5, E5, F5, Gb5, G5, Ab5,
 A5, Bb5, B5, C6, Db6, D6, Eb6, E6, F6, Gb6, G6, Ab6,
 A6, Bb6, B6, C7, Db7, D7, Eb7, E7, F7, Gb7, G7, Ab7,
 A7, Bb7, B7, C8, Db8, D8, Eb8, E8, F8, Gb8, G8, Ab8,
 A8, Bb8, B8, C9, Db9, D9, Eb9, E9, F9, Gb9, G9) = note_numbers

#  arrays for note inputs that are tied to the rotary switch
octave_8_cc = [E8, F8, Gb8, G8, Ab8, A8, Bb8, B8, C9, Db9, D9, Eb9]
octave_7_cc = [E7, F7, Gb7, G7, Ab7, A7, Bb7, B7, C8, Db8, D8, Eb8]
octave_6_cc = [E6, F6, Gb6, G6, Ab6, A6, Bb6, B6, C7, Db7, D7, Eb7]
octave_5_cc = [E5, F5, Gb5, G5, Ab5, A5, Bb5, B5, C6, Db6, D6, Eb6]
octave_4_cc = [E4, F4, Gb4, G4, Ab4, A4, Bb4, B4, C5, Db5, D5, Eb5]
octave_3_cc = [E3, F3, Gb3, G3, Ab3, A3, Bb3, B3, C4, Db4, D4, Eb4]
octave_2_cc = [E2, F2, Gb2, G2, Ab2, A2, Bb2, B2, C3, Db3, D3, Eb3]
octave_1_cc = [E1, F1, Gb1, G1, Ab1, A1, Bb1, B1, C2, Db2, D2, Eb2]

octave_select = [octave_1_cc, octave_2_cc, octave_3_cc, octave_4_cc,
                 octave_5_cc, octave_6_cc, octave_7_cc, octave_8_cc]

#  function for reading analog inputs
def val(voltage):
    return voltage.value

#  beginning script REPL printout
print("MX MIDI Guitar")

print("Default output MIDI channel:", midi.out_channel + 1)

#  loop
while True:
    #  values for LIS3DH
    x, y, z = [value / adafruit_lis3dh.STANDARD_GRAVITY for value in lis3dh.acceleration]

    #  mapping analog values to MIDI value ranges
    #  PitchBend MIDI has a range of 0 to 16383
    #  all others used here are 0 to 127
    pitchbend_val1 = round(simpleio.map_range(val(pitchbend_pot), 0, 65535, 0, 16383))
    mod_val1 = round(simpleio.map_range(val(mod_pot), 0, 65535, 0, 127))
    velocity_val1 = round(simpleio.map_range(val(velocity_pot), 0, 65535, 0, 127))
    acc_pos_val1 = round(simpleio.map_range(x, 0, 0.650, 127, 0))
    acc_neg_val1 = round(simpleio.map_range(y, -0.925, 0, 127, 0))

    #  checks if modulation switch is engaged
    if not mod_select.value:
        #  if it is, then get modulation MIDI data from LIS3DH
        #  positive and negative values for LIS3DH depending on
        #  orientation of the guitar neck
        #  when the guitar is held "normally" aka horizontal
        #  then the modulation value is neutral aka 0

        #  compares previous LIS3DH value to current value
        if abs(acc_pos_val1 - acc_pos_val2) < 50:
            #  updates previous value to hold current value
            acc_pos_val2 = acc_pos_val1
            #  MIDI data has to be sent as an integer
            #  this converts the LIS3DH data into an int
            accelerator_pos = int(acc_pos_val2)
            #  int is stored as a CC message
            accWheel_pos = ControlChange(1, accelerator_pos)
            #  CC message is sent
            midi.send(accWheel_pos)
            #  delay to settle MIDI data
            time.sleep(0.001)

        #  same code but for negative values
        elif abs(acc_neg_val1 - acc_neg_val2) < 50:
            acc_neg_val2 = acc_neg_val1
            accelerator_neg = int(acc_neg_val2)
            accWheel_neg = ControlChange(1, accelerator_neg)
            midi.send(accWheel_neg)
            time.sleep(0.001)

    #  if it isn't then get modulation MIDI data from pot
    else:
        #  compares previous mod_pot value to current value
        if abs(mod_val1 - mod_val2) > 2:
            #  updates previous value to hold current value
            mod_val2 = mod_val1
            #  MIDI data has to be sent as an integer
            #  this converts the pot data into an int
            modulation = int(mod_val2)
            #  int is stored as a CC message
            modWheel = ControlChange(1, modulation)
            #  CC message is sent
            midi.send(modWheel)
            #  delay to settle MIDI data
            time.sleep(0.001)

    #  reads analog input to send MIDI data for Velocity
    #  compares previous velocity pot value to current value
    if abs(velocity_val1 - velocity_val2) > 2:
        #  updates previous value to hold current value
        velocity_val2 = velocity_val1
        #  MIDI data has to be sent as an integer
        #  this converts the pot data into an int
        #  velocity data is sent with NoteOn message
        #  NoteOn is sent in the loop
        velocity = int(velocity_val2)
        #  delay to settle MIDI data
        time.sleep(0.001)

    #  reads analog input to send MIDI data for PitchBend
    #  compares previous picthbend pot value to current value
    if abs(pitchbend_val1 - pitchbend_val2) > 75:
        #  updates previous value to hold current value
        pitchbend_val2 = pitchbend_val1
        #  MIDI data has to be sent as an integer
        #  this converts the pot data into an int
        #  int is stored as a PitchBend message
        a_pitch_bend = PitchBend(int(pitchbend_val2))
        #  PitchBend message is sent
        midi.send(a_pitch_bend)
        #  delay to settle MIDI data
        time.sleep(0.001)

    #  checks position of the rotary switch
    #  determines which notes are mapped to the cherry mx switches
    for s in octave_selector:
        if not s.value:
            o = octave_selector.index(s)
            octave = octave_select[o]

        #  checks if strum select switch is engaged
        if not strum_select.value:
            #  if it is, then:
            #  setup states for both strummer switches
            if strumUP.value and up_pick is None:
                up_pick = "strummed"
                pick = time.monotonic()
            if strumDOWN.value and down_pick is None:
                down_pick = "strummed"
                pick = time.monotonic()
            #  bug fix using time.monotonic(): if you hit the strummer, but don't hit a note
            #  the state of the strummer switch is reset
            if (not pick) or ((time.monotonic() - pick) > 0.5 and
                              (down_pick or up_pick == "strummed")):
                up_pick = None
                down_pick = None

            #  if either strummer switch is hit
            if (not strumUP.value and up_pick == "strummed") or (not strumDOWN.value
                                                                 and down_pick == "strummed"):
                #  indexes the cherry mx switch array
                for i in range(12):
                    buttons = note_buttons[i]
                    #  if any of the mx cherry switches are pressed
                    #  and they weren't previously pressed (checking note_states[i])
                    #  where i is the matching index from the note_buttons array
                    if not buttons.value and not note_states[i]:
                        #  send the NoteOn message that matches with the octave[i] array
                        #  along with the velocity value
                        midi.send(NoteOn(octave[i], velocity))
                        #  note number is printed to REPL
                        print(octave[i])
                        #  note state is updated
                        note_states[i] = True
                        #  updates strummer switch states
                        up_pick = None
                        down_pick = None
                        #  delay to settle MIDI data
                        time.sleep(0.001)

            #  the next if statement allows for you to strum notes multiple times without
            #  having to release the note

            #  if either strummer switch is hit
            if (not strumUP.value and up_pick == "strummed") or (not strumDOWN.value
                                                                 and down_pick == "strummed"):
                #  indexes the cherry mx switch array
                for i in range(12):
                    buttons = note_buttons[i]
                    #  if any of the cherry mx switches are pressed
                    #  and they *were* previously pressed (checking note_states[i])
                    #  where i is the matching index from the note_buttons array
                    if not buttons.value and note_states[i]:
                        #  send the NoteOn message that matches with the octave[i] array
                        #  along with the velocity value
                        midi.send(NoteOn(octave[i], velocity))
                        #  note number is printed to REPL
                        print(octave[i])
                        #  note state is updated
                        note_states[i] = True
                        #  updates strummer switch states
                        up_pick = None
                        down_pick = None
                        #  sends a NoteOff message to prevent notes from
                        #  staying on forever aka preventing glitches
                        midi.send(NoteOff(octave[i], velocity))
                        #  delay to settle MIDI data
                        time.sleep(0.001)

            #  the next for statement sends NoteOff when the cherry mx switches
            #  are released

            #  indexes the cherry mx switch array
            for i in range(12):
                buttons = note_buttons[i]
                #  if any of the cherry mx switches are released
                #  and they *were* previously pressed (checking note_states[i])
                #  where i is the matching index from the note_buttons array
                if buttons.value and note_states[i]:
                    #  send the NoteOff message that matches with the octave[i] array
                    #  along with the velocity value
                    midi.send(NoteOff(octave[i], velocity))
                    #  note state is updated
                    note_states[i] = False
                    #  updates strummer switch states
                    down_pick = None
                    up_pick = None
                    #  delay to settle MIDI data
                    time.sleep(0.001)

        #  if strum select switch is not engaged

        else:
            #  indexes the cherry mx switch array
            for i in range(12):
                buttons = note_buttons[i]
                #  if any of the mx cherry switches are pressed
                #  and they weren't previously pressed (checking note_states[i])
                #  where i is the matching index from the note_buttons array
                if not buttons.value and not note_states[i]:
                    #  send the NoteOn message that matches with the octave[i] array
                    #  along with the velocity value
                    midi.send(NoteOn(octave[i], velocity))
                    #  note number is printed to REPL
                    print(octave[i])
                    #  note state is updated
                    note_states[i] = True
                    #  delay to settle MIDI data
                    time.sleep(0.001)

                #  if any of the cherry mx switches are released
                #  and they *were* previously pressed (checking note_states[i])
                #  where i is the matching index from the note_buttons array
                if (buttons.value and note_states[i]):
                    #  send the NoteOff message that matches with the octave[i] array
                    #  along with the velocity value
                    midi.send(NoteOff(octave[i], velocity))
                    #  note state is updated
                    note_states[i] = False
                    #  delay to settle MIDI data
                    time.sleep(0.001)

    #  delay to settle MIDI data
    time.sleep(0.005)
 

Python Online Compiler

Write, Run & Share Python code online using OneCompiler's Python online compiler for free. It's one of the robust, feature-rich online compilers for python language, supporting both the versions which are Python 3 and Python 2.7. Getting started with the OneCompiler's Python editor is easy and fast. The editor shows sample boilerplate code when you choose language as Python or Python2 and start coding.

Taking inputs (stdin)

OneCompiler's python online editor supports stdin and users can give inputs to programs using the STDIN textbox under the I/O tab. Following is a sample python program which takes name as input and print your name with hello.

import sys
name = sys.stdin.readline()
print("Hello "+ name)

About Python

Python is a very popular general-purpose programming language which was created by Guido van Rossum, and released in 1991. It is very popular for web development and you can build almost anything like mobile apps, web apps, tools, data analytics, machine learning etc. It is designed to be simple and easy like english language. It's is highly productive and efficient making it a very popular language.

Tutorial & Syntax help

Loops

1. If-Else:

When ever you want to perform a set of operations based on a condition IF-ELSE is used.

if conditional-expression
    #code
elif conditional-expression
    #code
else:
    #code

Note:

Indentation is very important in Python, make sure the indentation is followed correctly

2. For:

For loop is used to iterate over arrays(list, tuple, set, dictionary) or strings.

Example:

mylist=("Iphone","Pixel","Samsung")
for i in mylist:
    print(i)

3. While:

While is also used to iterate a set of statements based on a condition. Usually while is preferred when number of iterations are not known in advance.

while condition  
    #code 

Collections

There are four types of collections in Python.

1. List:

List is a collection which is ordered and can be changed. Lists are specified in square brackets.

Example:

mylist=["iPhone","Pixel","Samsung"]
print(mylist)

2. Tuple:

Tuple is a collection which is ordered and can not be changed. Tuples are specified in round brackets.

Example:

myTuple=("iPhone","Pixel","Samsung")
print(myTuple)

Below throws an error if you assign another value to tuple again.

myTuple=("iPhone","Pixel","Samsung")
print(myTuple)
myTuple[1]="onePlus"
print(myTuple)

3. Set:

Set is a collection which is unordered and unindexed. Sets are specified in curly brackets.

Example:

myset = {"iPhone","Pixel","Samsung"}
print(myset)

4. Dictionary:

Dictionary is a collection of key value pairs which is unordered, can be changed, and indexed. They are written in curly brackets with key - value pairs.

Example:

mydict = {
    "brand" :"iPhone",
    "model": "iPhone 11"
}
print(mydict)

Supported Libraries

Following are the libraries supported by OneCompiler's Python compiler

NameDescription
NumPyNumPy python library helps users to work on arrays with ease
SciPySciPy is a scientific computation library which depends on NumPy for convenient and fast N-dimensional array manipulation
SKLearn/Scikit-learnScikit-learn or Scikit-learn is the most useful library for machine learning in Python
PandasPandas is the most efficient Python library for data manipulation and analysis
DOcplexDOcplex is IBM Decision Optimization CPLEX Modeling for Python, is a library composed of Mathematical Programming Modeling and Constraint Programming Modeling