First, the STEMMA_I2C port is setup for the I2C devices. Then the TSC2007 and ADXL343 are setup.

#  I2C setup for STEMMA port
i2c = board.STEMMA_I2C()

#  touchscreen setup for TSC2007
irq_dio = None
tsc = adafruit_tsc2007.TSC2007(i2c, irq=irq_dio)

#  accelerometer setup
accelerometer = adafruit_adxl34x.ADXL343(i2c)
accelerometer.enable_tap_detection()

A number of variables and states are declared. Their function is commented in the code.

Most importantly, the notes array holds the MIDI note numbers that are sent to the Pure Data script and played through the synth. If you want to play different notes, you'll want to edit that array.

#  MIDI notes - 2 octaves of Cmaj7 triad
notes = [48, 52, 55, 59, 60, 64, 67, 71]
#  reads touch input
point = tsc.touch
#  accelerometer x coordinate
acc_x = 0
#  accelerometer y coordinate
acc_y = 0
#  last accelerometer x coordinate
last_accX = 0
#  last accelerometer y coordinate
last_accY = 0
#  mapped value for touchscreen x coordinate
x_map = 0
#  mapped value for touchscreen y coordinate
y_map = 0
#  last mapped value for touchscreen x coordinate
last_x = 0
#  last mapped value for touchscreen y coordinate
last_y = 0
#  state for whether synth is running
run = 0
#  tap detection state
last_tap = False
#  new value detection state
new_val = False

Socket Connection

After the QT Py ESP32-S2 connects to your WiFi network, it connects to your computer with a socket connection. The socket needs to know your computer's IP address, which is stored in host in the secrets.py file. The port is defined as 12345.

Once a socket connection is established with the Pure Data script, the loop begins to run.

HOST = secrets["host"]
PORT = 12345
TIMEOUT = 5
INTERVAL = 5
MAXBUF = 256

#  connect to WIFI
print("Connecting to %s"%secrets["ssid"])
wifi.radio.connect(secrets["ssid"], secrets["password"])
print("Connected to %s!"%secrets["ssid"])

pool = socketpool.SocketPool(wifi.radio)

ipv4 = ipaddress.ip_address(pool.getaddrinfo(HOST, PORT)[0][4][0])

buf = bytearray(MAXBUF)

print("Create TCP Client Socket")
s = pool.socket(pool.AF_INET, pool.SOCK_STREAM)

print("Connecting")
s.connect((HOST, PORT))

The Loop

The loop begins by checking for tap detection from the accelerometer. If the ADXL343 detects a tap, it will either start or stop the Pure Data synth from playing.

#  tap detection
    #  if tap is detected and the synth is not running...
    if accelerometer.events["tap"] and not last_tap and not run:
        #  run is updated to 1
        run = 1
        #  last_tap is reset
        last_tap = True
        print("running")
        #  message is sent to Pd to start the synth
        #  all Pd messages need to end with a ";"
        size = s.send(str.encode(' '.join(["run", str(run), ";"])))
    #  if tap is detected and the synth is running...
    if accelerometer.events["tap"] and not last_tap and run:
        #  run is updated to 0
        run = 0
        #  last_tap is reset
        last_tap = True
        print("not running")
        #  message is sent to Pd to stop the synth
        #  all Pd messages need to end with a ";"
        size = s.send(str.encode(' '.join(["run", str(run), ";"])))
    #  tap detection debounce
    if not accelerometer.events["tap"] and last_tap:
        last_tap = False

Reading the Touch Screen

If a touch input is detected with the TSC2007, the x and y coordinates are mapped from their 12-bit value to a range of 0 to 8. This mapping matches with the sequencer array in the Pure Data script.

#  if the touchscreen is touched...
    if tsc.touched:
        #  point holds touch data
        point = tsc.touch
        #  x coordinate is remapped to 0 - 8
        x_map = simpleio.map_range(point["x"], 0, 4095, 0, 8)
        #  y coordinate is remapped to 0 - 8
        y_map = simpleio.map_range(point["y"], 0, 4095, 0, 8)

Reading the ADXL343

The x and y values from the ADXL343 are mapped to match values for the synth's filters in the Pure Data script. 

#  accelerometer x value is remapped for synth filter
    acc_x = simpleio.map_range(accelerometer.acceleration[0], -10, 10, 450, 1200)
    #  accelerometer y value is remapped for synth filter
    acc_y = simpleio.map_range(accelerometer.acceleration[1], -10, 10, 250, 750)

Comparing Values

The current and previous values from the TSC2007 and ADXL343 are compared using if statements. If the values do not match, then the previous value is updated and the state of new_val is set to True. This indicates that a new value has been received and should be sent to the Pure Data script.

#  if any of the values are different from the last value...
    if x_map != last_x:
        #  last value is updated
        last_x = x_map
        #  new value is detected
        new_val = True
    if y_map != last_y:
        last_y = y_map
        new_val = True
    if int(acc_x) != last_accX:
        last_accX = int(acc_x)
        new_val = True
    if int(acc_y) != last_accY:
        last_accY = int(acc_y)
        new_val = True

Sending Data Over the Socket

If a new value is received from one of the sensors, then a message is sent over the socket with the new readings. Each piece of data is preceded by a variable name that Pure Data will be listening for. 

A new note is sent to the sequencer in the Pure Data patch with the note variable. The y_map coordinate on the touch screen is used as an index marker in the notes array.

It's important to note that Pure Data uses a networking protocol called FUDI. As a result, each message needs to end with a semicolon (;). This allows the message to be terminated and sent to the Pure Data script.

#  if a new value is detected...
    if new_val:
        #  note index is updated to y coordinate on touch screen
        note = notes[int(y_map)]
        #  message with updated values is sent via socket to Pd
        #  all Pd messages need to end with a ";"
        size = s.send(str.encode(' '.join(["x", str(x_map), ";",
                                           "y", str(y_map), ";",
                                           "aX", str(acc_x), ";",
                                           "aY", str(acc_y), ";",
                                           "n", str(note), ";"])))
        #  new_val is reset
        new_val = False

This guide was first published on Apr 05, 2022. It was last updated on Sep 20, 2023.

This page (How the CircuitPython Code Works) was last updated on Apr 05, 2022.

Text editor powered by tinymce.