Toy Keyboard Tone Piano – Part 2

Having now plugged an Arduino into my Toy Keyboard, it is time to start experimenting.  The first thing I really wanted to do, was build an Oskitone Scout, but not having a 3D printer or the funds to purchase one, I thought I’d have a go at getting the code to run on my toy keyboard.  Here is the result.

  • Consult Part 1 for details of adding an Arduino to the original toy keyboard.

Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

These are the key Arduino tutorials for the main concepts used in this project:

If you are new to Arduino, see the Getting Started pages.

The Oskitone Scout

Oskitone produce small keyboards and mini-synths in full, kit, or “build it yourself” forms, that have a very unique charm.  All of the designs for the cases, electronics and code have been published on GitHub, which makes them very DIY-friendly.

Some of my tone and “top octave generator” experiments were inspired by the Poly555.

This latest experiment is inspired by the Scout Synth, which is a 1.5 octave, 3D printed, Arduino-based, hackable “synth”.  In its default form it uses the Arduino tone() output, but has a really interesting portamento (gliding) effect as it is played.

The DIY Toy Keyboard Scout

I was really keen to see what would be involved in getting the Scout code running on my toy keyboard.  It turns out, not a lot!

The scout code can be found on GitHub here: https://github.com/oskitone/scout/tree/main/arduino/scout

It relies on the following two libraries:

  • Arduino KeyPad (the same one I’ve used myself) – this comes with the Arduino environment.
  • CircularBuffer (by AgileWare”).  This is available via the Arduino Library Manager.  Note there is a CircularBuffer implementation that comes with the Mozzi library but that is not compatible with this code (as I found out when it all failed to build!).

Basic Mods – Support for the keys

The following changes are required to the Scout code to make it work with my Toy Keyboard.

Notes.h

Change the NOTES_COUNT to 32 rather than the Scout’s 17.

const int NOTES_COUNT = 32;

KeyBuffer.Cpp

As the Scout uses the same KeyPad library as my previous code, it is simply a case of replacing the Scout’s key matrix with that of my own toy keyboard.  Note that I’ve only decoded the actual music keys here.  Also, the values representing keys need to be consecutive numbers starting from 1, not the MIDI note numbers that I used last time.  Recall that my keyboard has a weird numbering sequence!

const byte ROWS = 8;
const byte COLS = 4;
char key_indexes[ROWS][COLS] = {
/*        BP07  11  12  13 */
/* BP20 */ {32, 24,  9,  1},
/* BP21 */ {31, 23, 10,  2},
/* BP22 */ {30, 22, 11,  3},
/* BP23 */ {29, 21, 12,  4},
/* BP24 */ {28, 20, 13,  5},
/* BP25 */ {27, 19, 14,  6},
/* BP26 */ {26, 18, 15,  7},
/* BP27 */ {25, 17, 16,  8},
};
//                  BP20,21,22,23,24,25,26,27
byte rowPins[ROWS] = { 4, 5, 6, 7, 8,10,11,12 };
//                 BP07,11,12,13
byte colPins[COLS] = {1, 0, 2, 3};

Scout.ino

Finally there are a couple of changes required to the main Scout file as follows.

There is a change relating to the number of keys (it needs to know how many keys are on the keyboard below “concert A” in order to calculate the correct frequencies), and the pin for the tone/speaker output needs changing.

const int STARTING_NOTE_DISTANCE_FROM_MIDDLE_A = -16;
const int SPEAKER_PIN = 9;

The code uses digitalWrite to the LED_BUILTIN as an indicator, but you may recall that for my toy keyboard, the sense of the LEDs are reversed.  Consequently, I have to change

digitalWrite (LED_BUILTIN, HIGH)

to

digitalWrite (LED_BUILTIN, LOW)

and vice versa.

If I was being good, I’d probably turn these into ledOn() and ledOff() functions as I’ve done before to keep the specific logic away from the main code, but this will do for playing around.

Finally, as I’m using RX/TX to decode the keyboard, I’ve also disabled the Serial.print functionality.  Although as I’m not using all the buttons, I could move the appropriate “column” connections over to some different digital pins, but I’ve opted to keep the same pinout as before for now.

This involves commenting out the following:

In setup():
// Serial.begin(9600);

In loop():
  if (printToSerial) {
    //frequency.print();
  }

Although to be honest, as long as printToSerial is set to false, these could probably be left in with no side effects as the serial port won’t be used anyway.

And that is it.  Build and upload and my toy keyboard is not an extended, DIY Oskitone Scout!  But without the charm 🙂

Advanced Mods – Add in the Buttons

It is possible to include support for decoding the buttons too, but the updates are a little more involved.  To get to the point where the buttons can be decoded, the following is required.

KeyBuffer.Cpp

A more complex key map is required as it needs to decode the button presses too.  Taking the map from my previous experiment, this is one I’m now using.

const byte ROWS = 8;
const byte COLS = 10;
/*          BP00    BP02      BP03     BP04     BP05        BP06      BP07 11  12  13 */
/* BP20 */ {0,      B_ROCK,   B_VOLP,  0,       B_SLOWROCK, B_DEMOSEL, 32, 24,  9, 1},
/* BP21 */ {0,      B_MARCH,  B_VOLN,  B_STOP,  0,          B_GUITAR,  31, 23, 10, 2},
/* BP22 */ {0,      B_NEWAGE, 0,       B_PLAY,  B_DISCO,    B_TRUMPET, 30, 22, 11, 3},
/* BP23 */ {B_SW2,  B_SAMBA,  0,       B_REC,   B_BLUES,    0,         29, 21, 12, 4},
/* BP24 */ {B_DOG,  0,        B_TEMPP, B_DEMO,  B_VIOLIN,   0,         28, 20, 13, 5},
/* BP25 */ {B_BIRD, 0,        B_TEMPN, B_PIANO, B_BELL,     0,         27, 19, 14, 6},
/* BP26 */ {B_FROG, 0,        0,       B_ORGAN, B_MUSICBOX, 0,         26, 18, 15, 7},
/* BP27 */ {B_DUCK, 0,        0,       B_WALTZ, B_MANDOLIN, 0,         25, 17, 16, 8},

byte rowPins[ROWS] = { 4, 5, 6, 7, 8,10,11,12 };
byte colPins[COLS] = {A0,A1,A2,A3,A4,A5, 1, 0, 2, 3};

I could have used the numbers 33 to 52 directly for the buttons, but to make it easy to reference them elsewhere, I added a set of definitions into KeyBuffer.h.  What is important is that the buttons carry on immediately from the last music key.

KeyBuffer.h

#define B_START 33 // This has to follow on from NOTES_COUNT
#define B_TEMPP    (B_START)
#define B_TEMPN    (B_START+1)
#define B_REC      (B_START+2)
// and so on down to
#define B_MUSICBOX (B_START+29)

Scout.ino

The slightly more complex part has to happen in the main loop.  At present, as soon as something is detected in the key buffer (i.e. the call to buffer.isEmpty() returns false) the following code happens:

frequency.update(notes.get(buffer.getFirst()) / 4 * pow(2, octave));
tone(SPEAKER_PIN, frequency.get());
digitalWrite(LED_BUILTIN, HIGH);

This has to change so that this will only happen if one of the keys (i.e. the notes) is pressed, otherwise we’ll need to handle it as a button.  Consequently the above three lines in the else clause must be replaced by the following:

char nextkey = buffer.getFirst();
if (nextkey < NOTES_COUNT) {
   frequency.update(notes.get(nextkey) / 4 * pow(2, octave));
   tone(SPEAKER_PIN, frequency.get());
   digitalWrite(LED_BUILTIN, LOW);
} else {
   digitalWrite(LED_BUILTIN, LOW);
   switch (nextkey+1) {
      case B_MUSICBOX:
         break;
      // Add cases for any buttons to be handled
      default:
         // Ignore the rest
   }
   delay(200);
}

There are several things to note about this code:

  • I flash the LED on button presses too.  As mentioned previously the LED sense has to be reversed for my keyboard, hence the digitalWrite to LOW.
  • buffer.getFirst() returns the values stored in the keys_index[][] map less 1!  So the keys, which are numbered 1 to 32, get returned as 0 to 31.  This means that to check for a key, the test has to be < NOTES_COUNT, not <= NOTES_COUNT.
  • This also means that when it comes to checking against the defines I created in KeyBuffer.h for the buttons, the switch statement has to act on (nextkey+1) not simply nextkey.  Either that or every single case has to (B_XXX – 1) or I need two sets of defines, or adding 1 in the keys_index, etc – doing +1 in the switch seemed the simplest if not the most intuitive.
  • To complete the button handling, I’ve added a short delay to provide a degree of simple “debouncing”.  This doesn’t matter for keys, in fact you want the keyboard to be really responsive, but it causes problems with buttons if they are triggered several times.
  • Responding to buttons requires adding new case B_XXX: statements ad associated code.

Actual Button Functions

The above puts the framework in place to recognise button presses and distinguish them from keys.  I’ve implemented the following functions.

VOL+ and VOL- to change the Octave

case B_VOLP:
   octave++;
   if (octave > 5) octave = 5;
   break;
case B_VOLN:
   octave--;
   if (octave < 1) octave = 1;
   break;

SW2 to toggle “glide between pressed notes”

case B_SW2:
   glideOnFreshKeyPresses = !glideOnFreshKeyPresses;
   break;

STOP and PLAY to Disable and Enable Glide Completely

Ok, this was a little more complex. I wondered about using these to update the _glide variable, as setting this to 0.0 should disable the glide, but that didn’t really sit right with me.  In the end I added a new method to the Frequency class to enable or disable glide as follows.

In Frequency.Cpp

void Frequency::glide (bool onOrOff) {
   if (onOrOff) {
      _glideOff = false;
   } else {
      _glideOff = true;
   }
}

This requires a new member variable (bool _glideOff) and function definition (glide) in Frequency.h, which has to be initialised to false in Frequency::Frequency (yes, I know the logic here perhaps isn’t the clearest!).

Then in the Frequency::update function, the following line needs updating

 if ((_frequency == 0) || (_glide == 0)) {

to

 if ((_frequency == 0) || (_glide == 0) || (_glideOff)) {

Finally this means that the following code can now be added to Scout.ino

case B_STOP:
   frequency.glide(false);
   break;
case B_PLAY:
   frequency.glide(true);
   break;

Here are the results!

Closing Thoughts

This was surprisingly easy to do – I really wasn’t expecting that, but I was just so attracted to that characteristic Scout glide that I had to give it a go.  And I think it has ported over really well!

I do need to get some control of volume in there though.  The original had digital VOL+ and VOL- controls, but I’m wondering about adding an actual analog volume pot into the output.

Alternatively, I might use the slider on/off switch as a simple passive HIGH/LOW volume control.

And now I’m after more ideas for what to do with the buttons.

Kevin

IMG_5827

2 thoughts on “Toy Keyboard Tone Piano – Part 2

Leave a comment