Creating Patches for the Episodes
The Teensy GUI makes it extremely easy to create audio effects with all manner of crazy and creative signal chains. However the video and available literature aren't so clear on how to use them with a guitar, especially for beginners. Hopefully I can clear that up here.
(If any of the language here isn't clear, check out my Intro to Teensy article. If it's too clear, sorry I'm trying not to leave anyone behind!)
This guide is intended to be used with a particular circuit (the Episodes) using specific inputs on the Teensy for the pots and switches. That said you could modify the code included below if you want to use different Teensy inputs. The circuit uses a Teensy 4.0 and the Audio Board.
I've created a sketch template to use with the Teensy pedal, but you'll still probably want to read through this article to see how it all works. The template is (of course) missing any effect-specific code, so it won't do much on it's own.
If you use the template, just copy/paste the GUI generated code where specified (near the top of the template) and write any effect-specific functions in 'setup' (if you don't want to change them with knobs/switches etc) or in 'loop' (if you do). Then follow the advise below for controlling things with the pots and switches.
Make sure that you have the Arduino IDE configured for the right board by going to Tools -> Board -> Teensyduino -> Teensy 4.0
PJRC have created this handy video to walk you through using the GUI, but I'll go through the Episodes-specific method below as well.
Whether you're using the template or creating a sketch from scratch, firstly you'll need to create your signal chain in the GUI. Use "i2s" (the first object under "input") as your input and "i2s" (under "output") as your output. Be sure to also include the "sgtl5000_1" object (find this near the bottom under "control"). This last object doesn't connect to anything, it just controls the audio board.
The bare necessities, input, output and audio board control
The top nodes on the input and output objects control the left (mono) channel, and the bottom nodes control the right channel. You can hook up identical FX chains for each channel to make a stereo effect or run entirely different effects chains for each channel.
Click and drag from each output node (on the right of each object) to the input (on the left) of the object you want to connect to. A "patchcord" will appear connecting the objects. You can connect an output node to multiple input nodes, but not the other way around. Use a mixer object so send multiple patchcords to a single input.
When you're happy with your signal chain, click "Export" at the top, and copy/paste the GUI generated code where specified in the template sketch.
This info appears near the top of the template and is important for configuring the Teensy to run your program (but you don't really need to understand it!):
AudioMemory(40); // allocate memory for audio samples. the number represents sample blocks, each block provides 2.9ms of audio
sgtl5000_1.enable(); // this turns on the SGTL5000, which is the audio codec on the audio board
sgtl5000_1.volume(1); // This sets the output volume. 0 = silence, 1 = max volume. Yuo can use any decimal fraction in between
sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN); // selects the audio input, we always use Line In
analogReadResolution(12); // This configures the bit depth of your potentiometers. Here it is set to 12 bit, so readings from pots will be between 0 and 4095.
Variables
You'll probably want to use some variables to control each parameter in your effects chain. In this example I'm using the Bitcrusher object. I've declared two variables (one for bit depth and one for sample rate) like so:
byte bitdepth = 16;
int samplerate = 44100;
I've declared these variables outside of setup() and loop(), towards the top of the sketch, but under the GUI generated code. This means we can use the variables anywhere in the code. You have to declare them before you use them basically, and if you declare them inside setup() or loop(), you can only use them inside those curly brackets.
Functions
Every function that each effect can perform is listed on the right hand side of the GUI when you click on the relevant object. Some of them you will have to include to make the effect work, others are just ways to control a parameter. Take this example - beginPitchShift - from the granular object:
In your code this will look like:
granular1.beginPitchShift(grainsize);
"grainsize" is a variable you would have created before calling this function (although you could call it something else) eg:
int grainsize;
As you can see from the description, you have to initiate the granular object with the "begin()" function. Doing so looks like this:
granular1.begin(granularMemory, GRANULAR_MEMORY_SIZE);
This function should be called in "setup()" as it will only be used once.
"granularMemory" is an array (a series of variables). In this instance the array is a list of audio samples. "GRANULAR_MEMORY_SIZE" is like a variable, but it doesn't change value while our code runs. It's value has been set using "#define", a way of setting a value once that is easy to change when we edit the code.
#define GRANULAR_MEMORY_SIZE 12800
int16_t granularMemory[GRANULAR_MEMORY_SIZE];
Potentiometers
To check the position of a potentiometer, use the "analogRead" function inside of "loop()", eg:
bitrate = analogRead(A0);
The value given by the pot is now saved to "bitrate" and writing "bitrate" in your code is now equivalent to writing that value. Obviously you'll want to change the variable name ("bitrate") to match a variable you've declared in your sketch.
"A0" is the pot that we want to read. The __pedalname__uses pots A0, A1, A2 and A3.
This last line of code isn't very good, however, as we want to use this variable to control the bit depth and the "bits()" function only accepts variables from 1 to 16, while the 12 bit pot reading gives numbers from 0 to 4095.
To scale the range of potential pot-readings to a usable range, we can simply divide the number like this:
bitrate = analogRead(A0) / 256;
Our variable "bitrate" will now be any number between 0 and 15, depending on the pot position. All we need to do is add 1 and our pot reading will be in the perfect range to use with the "bits()" function:
bitrate = (analogRead(A0) / 256) + 1;
I recommend always using parentheses to make sure certain parts of each equation happen before others.
An important thing to remember is that integer types ('byte' ... ''int' ... 'short' ... 'long' ...) can't store decimal fractions and will round down to the nearest whole number, so 5 / 2 = 2. It's literally 1984. Using a calculator or Teensy's serial.print() function will clear up any uncertainties.
I haven't gone over all the various mathematical operators that C++ (and C) provide because others have already explained it better (see here).
You should make yourself familiar with using "if", "while" and "for" loops, as well as basic Boolean operators ('and', 'not' and 'or') . Give them a google, they're fairly straightforward and I promise that's the limit of learning you'll have to do! You can go a long way with just the "if" statement.
Footswitch
To incorporate the footswitch into your program, the template has this line of code at the very top:
#include <Bounce.h>
This means we can access the "Bounce" library and easily add code to debounce our footswitch. You'll also see this line beneath it:
Bounce footswitch = Bounce(0, 50);
This sets up digital pin 0 (the footswitch) with a debounce of 50 milliseconds. You can change the word "footswitch" to name the switch something else if you like.
I've added an internal pull-up resistor for the footswitch using this line of code in "setup()":
pinMode(0, INPUT_PULLUP);
The footswitch is now all set up and ready to use. You can assign it to a function (i.e. make it do something) using code like this inside the "loop()" curly brackets:
footswitch.update(); // this checks the status of the footswitch, whether or not it's been pressed
if(footswitch.fallingEdge()) { // if the footswitch has been pressed..
granular1.beginFreeze(grainsize);
This second part, begining with "if..." will only process the code inside the curly brackets if the footswitch has been pressed. In this instance I've used the freeze function from the granular object, so the pedal will freeze (repeat short samples of your guitar signal) when the footswitch is pressed. "grainsize" needs to be a variable here.
To make the footswitch do something when the footswitch is released, change the "if..." code to:
if(footswitch.risingEdge()) {
// your code here
}
It's also possible to check the state of a digital input (eg. the toggle or footswitch) without using the debouncing routine. This can be used for the toggle or when the footswitch is acting in momentary mode:
if(digitalRead(0, LOW)) {
// code here will execute if the footswitch is currently pressed
}
Swapping "LOW" for "HIGH" will execute code if the footswitch isn't pressed,
Toggle Switch
The toggle switch selects between 3 options using 2 digital inputs. Setting up the toggle goes the same as for the footswitch. See this section in the template under the GUI generated code:
Bounce D1 = Bounce(1, 50); // debounce the toggle
Bounce D2 = Bounce(2, 50);
...and this in 'setup':
pinMode(1, INPUT_PULLUP); // add internal pullup resistor
pinMode(2, INPUT_PULLUP);
I've written a function to determine the position of the toggle switch. Functions like this are written outside of setup and loop and need to be declared (ie. appear in your code) before you use them. I've also declared the booleans (a type that can be a 1 or a 0) we'll use just above the function. Notice the use of "!" (the "not" operator) which will be true if the condition is false.
// check the toggle position
bool right;
bool middle;
bool left;
void checkToggle () { // our function to check toggle position
D1.update(); D2.update(); // check digital inputs connected to toggle
if(digitalRead(1) && !digitalRead(2)) {right = 1; middle = 0; left = 0;} // toggle is right
if(digitalRead(1) && digitalRead(2)) {right = 0; middle = 1; left = 0;} // toggle is in the middle
if(!digitalRead(1) && digitalRead(2)) {right = 0; middle = 0; left = 1;} // toggle is left
}
Now, to use the toggle for something, for example when it's in the right position, write this in the 'loop':
checkToggle(); // this runs the function I just wrote
if(right) {
// code here will execute when the switch is right
}
You can change "right" to "left" or "middle" to make the switch do different things in different positions.
LED
The bottom LED on the __pedalname__ PCB is your typical bypass LED. The top LED can be turned on and off easily in your code. At the very top of the template you'll see:
#define LED 3
Now whenever you write "LED" in your code, the program will read it as "3", the pin that our LED is connected to.
This part in setup() configures pin 3 as an output:
pinMode(3, OUTPUT);
Change the LED in your code like so:
digitalWrite(LED, HIGH); // turns the LED on
digitalWrite(LED, LOW); // turns the LED off
So that's the basics for getting your digital effect up and running!
I'll try to answer any questions you may have, but remember that Arduino is a very beginner-friendly language and there are reams of info online as well as forums with people to help you. Chances are any issues you have will have been solved elsewhere.
It's always a good idea to check the example sketches in the IDE (File -> Examples -> Audio -> Effects). Hardly any of them use the "Line In" as an input, so you'll need to modify most of them to work with the Episodes.
Alternatively you can zap any of the pre-written patches straight into the pedal.
Comments