Hacking the SX-150. Building an Arduino-based sequencer - Part 3
This is the big one. Following up on Part 1 -- where we took control of the SX-150 pitch control, and Part 2 -- where we built on that to have basic sequencer note-on/note-off control, Part 3 is where it comes together with a hardware-and-Arduino based 4-step sequencer for the SX-150!
Follow up:
To jump right into it, here's the final schematic:
You can see that there are several different sections to the circuit:
Pitch Control: This is what we spent the last two parts working on. It uses an SPI-controlled digital potentiometer to set the pitch that will be played on the SX-150, and a transistor to switch the note on and off.
Step Enable: 4 momentary switches that tell the Arduino "Hey, I want to change the pitch of this note" and 4 LEDs that indicate which note is being changed.
Each input has it's own line to the Arduino, and I use a 5th to create a rudimentary "OR" gate and send it to an interrupt line. If any of the buttons is pressed, the interrupt goes high, and then the Arduino reads the 4 inputs to determine which one of them was pressed. Each note also has their output line to drive the LED.
Clearly this is where most of my Arduino inputs/outputs go and this limits me to 4 steps. I really wanted 8, but I will have to introduce shift registers to the equation to make that happen. (That'll be part 4).
Pitch Select: I use a rotary encoder, which gives me a nice 'clicky' feel and more precision than using a standard pot. It's a little tricky to program, but there's a decent article on it. It works, but I think I have a better way and that will be my next blog post. When the knob is turned, an interrupt is fired, both interrupts are read and compared, and -- if a step enable is active -- it changes the value of that note.
Speed Control: A simple pot that goes into an analog input, the Arduino speeds up the notes based on the analog value coming in.
When it all comes together on a breadboard, it looks pretty messy...this one is crying out for some better wiring, but this is just my first cut at it.
Behind the scenes, we've got the source code running on the arduino. A lot of my code builds on Part 1 & Part 2, so there's no sense repeating it all here. The complete source code is available, so check it for all the details, but here are some highlights.
Sequence playing: The core of the sequencer, obviously, is going to be playing the sequence...
int stepVals[] = {18, 18, 18, 18}; //possible values 0-35, start in the middle.
int activeButton = -1;
void loop()
{
// Each pass through the loop, play the full sequence of 4 steps
for (int i = 0; i < 4; i++)
{
// Convert the potentiometer hooked to the Speed input into a value between 50ms & 1s
int speed = map(analogRead(SEQUENCER_SPEED),0,1023, 5,100)*10;
// Turn on the given note for 90% of the full note period
NoteOn(stepVals[i]);
delay(speed*0.9);
// Turn the note off for 10% of the full note period
NoteOff();
delay(speed*0.1);
}
}
Changing the notes: There are two steps to changing the sequence. Note-enable -- which indicates what step to change -- and the actual changing of the note. There is an interrupt handler for each part:
// onButton() is an interrupt handler that responds when a note-enable button is pressed
void onButton()
{
// Determine which button was pressed
int selectedButton;
if (analogRead(STEP1_ENABLE) > 128) selectedButton = 1;
if (analogRead(STEP2_ENABLE) > 128) selectedButton = 2;
if (analogRead(STEP3_ENABLE) > 128) selectedButton = 3;
if (analogRead(STEP4_ENABLE) > 128) selectedButton = 4;
// if that button was already selected, turn it off, otherwise make the
// new button the current one
if (selectedButton == activeButton)
activeButton = -1;
else
activeButton = selectedButton;
// activate the LED associated with the selected step
digitalWrite(STEP1_ENABLEDISPLAY, (activeButton == 1));
digitalWrite(STEP2_ENABLEDISPLAY, (activeButton == 2));
digitalWrite(STEP3_ENABLEDISPLAY, (activeButton == 3));
digitalWrite(STEP4_ENABLEDISPLAY, (activeButton == 4));
}
// onTurn() is an interrupt handler that fires when the rotary encoder is turned
void onTurn()
{
// If no button selected, just discard
if (activeButton == -1)
return;
// Get the current value for this step, update it, and write it back
int currentVal = stepVals[activeButton-1];
int a = digitalRead(NOTE_SELECT_INTERUPT);
int b = digitalRead(NOTE_SELECT_B);
if (a == HIGH) // found a low-to-high on channel A
{
if (b == LOW) // check channel B to see which way encoder is turning
currentVal = constrain(currentVal-1,0,35); // CCW
else
currentVal = constrain(currentVal+1,0,35); // CW
}
else // found a high-to-low on channel A
{
if (b == LOW) // check channel B to see which way encoder is turning
currentVal = constrain(currentVal+1,0,35); // CW
else
currentVal = constrain(currentVal-1,0,35); // CCW
}
stepVals[activeButton-1] = currentVal;
}
And that about wraps it up. Once you have more precise control over the SX-150, it starts to feel less like a toy and more like a viable sound-making tool.
In my next experiment, I think I will look at using shift registers to expand from 4 steps to 8, and i'd like to have 4 banks of sequences I can pick between. Also, I'll eventually have to get this off the breadboard and solder this up and find a way to package this up into something useful. Stick around for more exciting adventures!
01/24/10 02:41:19 pm, 