Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
grokpot committed Sep 6, 2020
0 parents commit 08e3163
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 0 deletions.
28 changes: 28 additions & 0 deletions client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import mido

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner
import socket
import time

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

Always love this line, wish I could do it IRL


HOST = 'x.x.x.x'

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

Populate this, it'll be the device IP from client.py

PORT = 12345 # Needs to match host

def runit():

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

I put everything in a function so you can copy/paste the code without it running, and then just call runit() when you want it to run

with mido.open_output('IAC Driver Bus 1') as port:

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

This shouldn't be hardcoded. You'll need to run mido.get_output_names() to find the name of the output port you'll be using.
Important to remember that we're using an OUTPUT port because we're the input.

note = 50

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

Discussed in Blog. Value for note chosen is random

port.send(mido.Message('note_on', note=note, velocity=50))
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.connect((HOST, PORT))
while True:
s.sendall(b'ping')

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

ping the host (ESP32) for a value. 'ping' is just a random string

data_raw = s.recv(1024)
data = int(data_raw.decode())

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

we're receiving a string but cast to int

data_converted = int(data * 16000 / 511) - 8000

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

The MIDI pitch message can have a value from -8192..8191
Here we do a rough transformation from the 0-511 scale of the ADC to the -8192 - 8191 scale of MIDI pitch

# print("Converted value:", data_converted)
pitch = data_converted

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

could clean these lines up

port.send(mido.Message('pitchwheel', pitch=pitch))
time.sleep(.05)

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

Adjust this for the interval of how fast we ping the ESP32.
< 0.02 crashes the chip 😓

Remember that all of this is happening via WLAN, not USB...

except Exception:
pass
finally:
port.send(mido.Message('note_off', note=note))

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

Really important, otherwise you'll have a constant note playing.

Mido has two utility functions: reset() and panic()
I couldn't get either of them to work ☹️

55 changes: 55 additions & 0 deletions host.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Clean up files and ports that I probabaly forgot to close
gc.collect()

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

Yeah.. not so nice, but I found this useful because if your program errors out / exits early, then the WLAN ports will block and you’ll have to unplug/replug the ESP32.


import network
import machine
import socket
import time
from machine import ADC

WLAN_SSID = ''
WLAN_PW = ''

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

Populate these


# Connect to network

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

Network connection info is found in standard micropython docs

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('connecting to network...')
wlan.connect(WLAN_SSID, WLAN_PW)
while not wlan.isconnected():
pass
print(wlan.ifconfig())
device_ip = wlan.ifconfig()[0]
print('Device IP:', device_ip)

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

Good to print out so we can hardcode it into the client code


pin = machine.Pin(32, machine.Pin.IN)

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

I used Pin #32 but others are available.
This was super useful

adc = ADC(pin)
# Calibrate ADC
# set 11dB input attenuation (voltage range roughly 0.0v - 3.6v)
adc.atten(ADC.ATTN_11DB)
# set 9 bit return values (returned range 0-511)
adc.width(ADC.WIDTH_9BIT)

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

ADC (analog to digital conversion) needs to be calibrated since it's a 3.5v potentiometer.
ESP32 docs describe this


HOST = device_ip
PORT = 12345 # Needs to match client

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

Choose whatever port


try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

SOCK_STREAM indicates we're using TCP instead of UDP. That's so we can reuse the connection.

s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

Making the socket reusable is nice because it won't block if your program unexpectedly exits

s.bind((HOST, PORT))
# Wait for new connections
while True:
s.listen(1)

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

There are lots of resources on Sockets and Python... and the options are endless.
Here, we start listening for a connection (from a client - probably your computer)

conn, addr = s.accept()
print('Connected by', addr)
# Wait for query
while True:
data = conn.recv(1024)

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

1024 bytes is a pretty standard packet size. It could be adjusted though. I'm not being too careful here about how much I'm sending. If this was a professional piece I'd clean this up.

if not data:
break

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

äähm, I forgot why this is there 😅

conn.sendall(str(adc.read()).encode())

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

read the ADC value, convert it to a string and call .encode() so we're sending bytes (requirement of sockets' .sendall())
We could just convert the number to int/float, but number conversion to bytes in Python is a bit more tricky, so a string works 😋

except KeyboardInterrupt:
pass # suppress keyboard interrupt traceback

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

If we press CTRL+C, don't throw an error

finally:
conn.close()
s.close()

This comment has been minimized.

Copy link
@grokpot

grokpot Sep 6, 2020

Author Owner

Clean up those sockets.
By putting everything in a try/except/finally, we ensure this is called when our program exits.
Could be cleaner though

11 changes: 11 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Docs
* https://microbit-micropython.readthedocs.io/en/latest/devguide/repl.html

## REPL Prompt
* To enter: `screen /dev/cu.SLAB_USBtoUART 115200`
* To exit, press Ctrl-A + k
* Paste mode: Ctrl-E

# midi converter in mac
* https://mido.readthedocs.io/en/latest/

0 comments on commit 08e3163

Please sign in to comment.