Human Interface Subsystem

A project log for The Guitar Pedal Project: Multi-Effect Processor

A guitar multi-effect stompbox that allows musicians to create new and never-before-seen effect creations using their mobile phone.

ben-jacobsBen Jacobs 05/02/2020 at 14:290 Comments

Subsystem Description

The Human Interface Subsystem, assigned to team member Ben Jacobs, allows for input from various analog hardware controls. These inputs are routed to the PureData DSP engine through a Python script to change effect parameters 'on the fly'. Our hardware prototype has four hardware knobs, and one expression pedal input. This could be expanded by three inputs with the existing ADC hardware, for up to 8 total analog inputs.

The subsystem is comprised of two ADS1015 Analog Digital Converter boards, which communicate with the Raspberry Pi over the same i2c bus as the Audio Codec. As mentioned above, a Python script handles this communication, value averaging and thresholding, and the routing of the digital values to PureData. The potentiometers that are used for the four knobs in our hardware prototype are 30k Ohms linear taper, but depending on your preference, any sufficiently high value would work (10k - 100k). Remember that the lower the resistance value, the more wasted power there will be due to increased current.

Subsystem Bill of Materials

Subsystem Hardware Connections

Below is a block diagram showing the connections to the Raspberry Pi. Notice that these connections are made over the same i2c bus as the Audio Codec uses. Both devices on the bus will have different addresses. Also notice that there are three free channels on the second ADS 1015 device (channels 6 through 8) that could theoretically be used for more human interface device inputs with minor software modifications. Make note of the 100 Ohm current limiting resistor on the expression pedal VCC connection. This is to reduce current inrush when the pedal is plugged in.

Subsystem Software

To accompany the hardware for this subsystem, there are two main software pieces to complete the path from the hardware knobs to the PureData DSP engine. These come in the form of a Python script called KnobSend, and a PureData object called Knobs. These files will be available for download under the files section of this site.

The Python Script

The purpose of the Python script is to read the ADC devices over the i2c bus and do some averaging and threshold math on the returned values so that they end up ranging between 0 and 1, which makes them useful for modifying PureData patch (effect) parameters.

The script makes use of the following Adafruit libraries, which must be installed on the Raspberry Pi. The following links have useful information on completing those installs.

CircuitPython (busio)

ADS1015-specific library module for CircuitPython

The full code of the Python script can be found in the files section of this site, but a few key snippets will be examined here. The snippet below sets up the i2c connection for use in the rest of the script. Notice the i2c addresses are hard coded here in decimal (72 = 0x48, for example).

#Adafruit Library Code
i2c = busio.I2C(board.SCL, board.SDA)
import adafruit_ads1x15.ads1015 as ADS
from adafruit_ads1x15.analog_in import AnalogIn

#Define ADC object
ads1 = ADS.ADS1015(i2c, address=72) #The ADC with the knobs (0x48)
ads2 = ADS.ADS1015(i2c, address = 73) #The ADC with the expr. pedal (0x49) 

This snippet sets some parameters for the rest of the script, called avgs (averages) and threshold. The averages parameter sets how many readings should be taken for every final value that is outputted. The readings are averaged to reduce parasitic noise in the readings. The default is 10.

The threshold parameter sets the minimum delta (change) between two values (post-averaging) that is necessary for the script to consider a knob to have been turned. This further reduces noise, and prevents the system from getting bogged down due to constantly sending new values, even if they aren't substantially different from the last value. The default is a minimum delta of 0.01 out of 1.

#Define variables
avgs = 10 #How many averages should be performed?
#Set avgs to one to theoretically eliminate this feature (untested)

threshold = 0.01 #What is the threshold for "knob moved?".
# Set Threshold to zero to theoretically eliminate this feature (untested)

The code snippet below is a mask to set which ADC channels are read from by the script. By placing a '1' in a slot, the channel will be read. If you place a '0' in a slot, the channel will be ignored. This is where you could theoretically expand the number of knobs on your pedal, by adding '1's to the previously unused ADC channels.

#      ===ADC 1=== ===ADC 2====
#       0  1  2  3  0  1  2  3
mask = [1, 1, 1, 1, 1, 0, 0, 0] #Set mask for active channels on ADCs
# 1 -> Channel = ADC Reading, 0 -> Channel = 0 (disabled)
#Default Mask is 1 1 1 1 1 0 0 0 for 4 knobs and one expression pedal

 The script communicates with PureData using the following code snippet. The code 'packs' the ADC value into a string of the format 'kX 1.11;', where X is the zero indexed channel number ranging from 0 to 7, and 1.11 is the numeric value from that channel. Messages are terminated with a semicolon. This string is sent over a local UDP connection on port 13000 to PureData using the UNIX command 'pdsend'. We will see how PureData receives this message in the next section.

   #If knob moved more than threshold, send values to Pure Data with 'pdsend' 
   #Note format string %.2f. This gives two sigfigs of resolution.
   msg = "k" + str(i) + " " + str('%.2f'%(k_avg[i]/3.3)) + ";"
   os.system("echo '" + msg + "' | pdsend 13000 localhost udp") 

The PureData Objects

To receive the messages with ADC values from the Python script, we must create a specialized PureData object. In fact, we created two versions, which we called knobs.pd and knobs_smooth.pd. Both are available in the files section of this site.

A screenshot of knobs.pd, the "base" version of the receiver object, is shown below. The 'netreceive' object at the top interfaces over UDP port 1300 to the Python script. It pipes what it receives into the 'route' object, which sorts the incoming messages based on the header 'kX' that we saw above. The values from the body of the message then show up in the number boxes, and they are piped into outlets at the end. By placing knobs.pd into your own PureData effect as an abstraction, you can access these outputs to modify your effect parameters.

A screenshot of knobs_smooth.pd is shown below. This version differs from knobs.pd in that its outputs are "audio" signals rather than numbers, which is done through the line~ object. This fixes a known issue in PureData when you try to modify a gain control "on the fly" (eg while DSP is running) in which an unwanted "zipper" noise is heard on the output. The knobs.pd we see above can be used to modify any control that accepts a number, while knobs_smooth.pd may only be used with controls that accept an audio signal.
As an example, here is a PureData patch set up with both versions of the knobs object (in reality you cannot use both at the same time, unfortunately). In this setup, knobs.pd (in the right audio channel) would cause zipper noises, but knobs_smooth.pd (in the left audio channel) would not. However, notice that only knobs.pd can attach its outputs to non-audio inputs, such as the horizontal slider shown here.


Below is a short, informal demonstration video of the Human Interface Subsystem when it was a breadboard prototype. Notice that, with the adjustment of a potentiometer, a horizontal slider connected to a Knobs object (above) moves accordingly.