midiwasp, kind of

Had a question from another Wasp owner about MIDI-ing up a Wasp so I chucked this thing together:

which seems like it works alright, but now I’m not so sure, due to my Wasp’s keyboard playing up fairly badly after being left alone for a few months. It seems like there’s a clocking problem somewhere in the keyboard note detection circuitry which causes it to semi-randomly flit between the note you pressed and a much lower one at a high speed… in some ways it’s quite an interesting effect, but not all the time.

To add to that, the timing from the x0xb0x via the Arduino seemed to get messed up as well, so I’m not convinced that this code is worth much but here goes anyway. Feel free to change it, smash it up, call it names.

/*

  __  __ ___ ___ _____      ___   ___ ___
 |  \/  |_ _|   \_ _\ \    / /_\ / __| _ \
 | |\/| || || |) | | \ \/\/ / _ \\__ \  _/
 |_|  |_|___|___/___| \_/\_/_/ \_\___/_|  
                                         

  - possibly cranky 5 pin DIN MIDI to Wasp LINK converter
  - uses PORTB (digital pins 8 to 13) for the note output
    and pin 7 for the gate trigger
  - tested on an Arduino Duemilanove,
  - should work on other 5V Arduinos - though the PORT
    number may change

  http://ua726.co.uk/2016/02/28/midiwasp-kind-of

  Libraries
  ===================
 
  You will need the...
 
  - FlexiTimer2 - http://playground.arduino.cc/Main/FlexiTimer2
  - MIDI library - http://arduinomidilib.fortyseveneffects.com/index.html

*/


// **************************************
// Set these up how you want them....

// MIDI base note is the lowest MIDI note that the Wasp will respond to
// 36 = C2
#define MIDI_BASE_NOTE 37

// MIDI input channel is the channel you want the Wasp to respond on
#define MIDI_INPUT_CHANNEL 1

//***************************************

#include <MIDI.h>
#include <FlexiTimer2.h>

#define TRIG_OUT 7
#define WASP_MAX_NOTE 35
#define NOTE_MEMORY_ARRAY_LENGTH 8
#define NO_NOTE -1

#define NP_LAST_NOTE 1
#define NP_HIGHEST_NOTE 2

const int8_t note_priority_mode = NP_LAST_NOTE;
volatile int8_t noteIsOnCount = 0;
volatile boolean triggerOutState = false;
int8_t noteMemory[NOTE_MEMORY_ARRAY_LENGTH] = {NO_NOTE, NO_NOTE, NO_NOTE, NO_NOTE, NO_NOTE, NO_NOTE, NO_NOTE, NO_NOTE};
const int8_t codeLookup [] = { 43, 42, 41, 40, 39, 38, 37, 36, 35, 34,
                      33, 32, 27, 26, 25, 24, 23, 22, 21, 20,
                      19, 18, 17, 16, 11, 10, 9,  8,  7,  6,
                      5, 4, 3, 2, 1, 0 };

MIDI_CREATE_DEFAULT_INSTANCE();

// -----------------------------------------------------------------------------

void handleNoteOn(byte channel, byte pitch, byte velocity)
{
  if(isMidiNoteInBounds(pitch)) {
    int8_t noteOn = pitch - MIDI_BASE_NOTE;
    noteMemory[noteIsOnCount] = noteOn;
    int8_t noteOnToPlay = getNoteToPlay();
    noteIsOnCount++;
    doNoteOn(noteOnToPlay);
  }
}

void handleNoteOff(byte channel, byte pitch, byte velocity)
{    
  if(isMidiNoteInBounds(pitch)) {
    int8_t noteOffToPlay = NO_NOTE;
    int8_t noteOff = pitch - MIDI_BASE_NOTE;
    removeNote(noteMemory, noteOff);
    noteIsOnCount--;
    noteOffToPlay = getNoteToPlay();
    if(noteIsOnCount > 0 && noteOffToPlay != NO_NOTE  ) {
        doNoteOn(noteOffToPlay);
    }
  }
}

int8_t getNoteToPlay()
{
  int8_t noteToPlay = NO_NOTE;
  if(note_priority_mode == NP_HIGHEST_NOTE)
  {
    noteToPlay = findHighestNote(noteMemory, NOTE_MEMORY_ARRAY_LENGTH);
  } else {
    noteToPlay = findLastNote(noteMemory, NOTE_MEMORY_ARRAY_LENGTH);
  }
 
  return noteToPlay;
}

boolean isMidiNoteInBounds(byte pitch)
{
  return pitch >= MIDI_BASE_NOTE && pitch <= MIDI_BASE_NOTE + WASP_MAX_NOTE;
}

int find(int8_t arr[], int8_t len, int8_t seek)
{
    for (int i = 0; i < len; ++i)
    {
        if (arr[i] == seek) return i;
    }
    return -1;
}
 
void removeElement(int8_t *array, int8_t index, int8_t array_length)
{
   int8_t i;
   for(i = index; i < array_length ; i++) {
     array[i] = array[i + 1];
   };
   array[array_length-1] = NO_NOTE;
}

void removeNote(int8_t *array, int8_t note)
{
  int8_t index = find(array, NOTE_MEMORY_ARRAY_LENGTH, note);
  if(index != -1) {
    removeElement(array,  index, NOTE_MEMORY_ARRAY_LENGTH);
  }
}

int8_t findHighestNote(int8_t *array, int8_t array_length)
{
  int8_t i = 0;
  int8_t highestNote = -1;
  while (i < array_length) {
    if(array[i] > highestNote ) {
      highestNote = array[i];
    }
    i++;
  }
  return highestNote;
}

int8_t findLastNote(int8_t *array, int8_t array_length)
{
  int8_t i = 0;
  int8_t lastNote = NO_NOTE;
  while (i < array_length) {
    if(array[i] != NO_NOTE ) {
      lastNote = array[i];
    }
    i++;
  }
  return lastNote;
}

// avoiding hanging notes with sequencers like the x0xb0x
void handleStop()
{
  doNoteOff();
  noteIsOnCount = 0;
}

void doNoteOn(uint8_t noteNumber)
{
  byte myNoteNumber = noteNumber;
  uint8_t note = codeLookup[noteNumber];
  PORTB = note;
  triggerOutState = HIGH;
  digitalWrite(TRIG_OUT, triggerOutState);
}

void doNoteOff()
{
  triggerOutState = LOW;
  digitalWrite(TRIG_OUT, LOW);
}

// 48hz - period of 20.833333333 ms
// need to use 10ms (as one-half of period)
void gateFlipTimerSetup()
{
  FlexiTimer2::set(10, 1.0/1000, gateFlip);
  FlexiTimer2::start();
}

void gateFlip()
{
  if(noteIsOnCount > 0) {
    triggerOutState = !triggerOutState;
    digitalWrite(TRIG_OUT, triggerOutState);
  }
}

void setup()
{
    // set PORT B to output mode
    DDRB = B11111111;
    PORTB = B00000000;
 
    // set up trigger out
    pinMode(TRIG_OUT, OUTPUT);
 
    gateFlipTimerSetup();
 
    MIDI.setHandleNoteOn(handleNoteOn);
    MIDI.setHandleNoteOff(handleNoteOff);
    MIDI.setHandleStop(handleStop);
    MIDI.begin(MIDI_INPUT_CHANNEL);
}

void loop()
{
    MIDI.read();  
}

I used a CNY13-4 as the optocoupler to connect MIDI to the Arduino this post just because that’s what I had to hand – for my final version I’d heed Olivier’s warning and use a 6N137. This is the relevant part from the MI Shruthi schematic:

Example MIDI to Arduino from MI Shruthi
The Wasp works off 5 volts so I’d recommend a 5v Arduino. It might work with a 3.3v Teensy 3.x, but you’d probably need a bunch of level shifting from 3.3v to 5v to get it work reliably. Also, out of laziness I’m using a PORTB command to write out to more than one pin at the same time, so you’d need to change this if you decided to use something other than an a Atmega 168/328-type Arduino.

For the connection from the Arduino to the Wasp you’ll need a 8 pin DIN plug. The connector that you’ll need to make, looking from the back from left to right, goes like this:

1 – trigger
2 – a (least significant bit)
3 – b
4 – c
5 – d
6 – e
7 – f (most significant bit)

…and don’t forget the ground at the bottom, that needs to connect to ground on your Arduino. The trigger pin will connect to pin 7 on the Arduino, with pins 2 – 7 being connected to Arduino digital inputs 8 – 13 respectively.

Looking at the Spider schematic, the note outputs should all be connected through 10K resistors, with the trigger connected through 47K. In my quick test I’ve gone direct, mostly because the keyboard is playing up and affecting the input.

The code started out being the most simple thing that would work, before I realised that I wanted to to do highest note priority, as with the original keyboard. Then I wanted it to do last note priority, so it got more complicated.

You will need to stop MIDI input when uploading the program to the Arduino because it uses the TX and RX pins as part of that process.

You could make it fancier, or try variations on the theme. It’d be good to do a USB MIDI > Wasp version for example – that should be easier with an Arduino Uno or Teensy. Or if you’ve modded your Wasp (or Jasper…) for filter CV input you could add a simple DAC to control the VCF from velocity or other MIDI controller.

I guess you could do a CV/gate > Wasp version as well if you were really keen to sequence it from your MC-4 or whatever. It’s just a matter of sampling the CV voltage when interrupted by a gate, and then quantising that to a note number.

It remembers the notes you’ve held down (up to a maximum of 8) so you could step through them on a clock (MIDI or analogue clock input) to make a simple arpeggiator.

Also also… if that Disklavier in the corner of the room just seems a bit unsatisfying, why not alter the code to make it into a MIDI output and control that grand piano from the Wasp’s capacitive touch keyboard? Endless fun, and so on.

Tagged under ,

9 comments

  1. 29th July 2016Ricky Grayson says:

    Hi. Could your trigger problem be due to timer2 only being able to output a frequency as low as 61hz ? You’re trigger signal will be actually running at 122hz

  2. 29th July 2016Ricky Grayson says:

    Try this:

    Inside setup()
    change:
    gateFlipTimerSetup();
    to:
    TCCR1A = 0;
    TCCR1B = 0;
    TCNT1 = 0;
    OCR1A = 40000;
    TCCR1B |= (1 << CS10) | (1 << WGM12);
    TIMSK1 |= (1 < 0) {
    triggerOutState = !triggerOutState;
    digitalWrite(TRIG_OUT, triggerOutState);
    }
    }

  3. 29th July 2016Ricky Grayson says:

    last message got scrambled?
    Inside setup() change: gateFlipTimerSetup();
    to:
    TCCR1A = 0;
    TCCR1B = 0;
    TCNT1 = 0;
    OCR1A = 40000;
    TCCR1B |= (1 << CS10) | (1 << WGM12);
    TIMSK1 |= (1 0) {
    triggerOutState = !triggerOutState;
    digitalWrite(TRIG_OUT, triggerOutState);
    }
    }

  4. 29th July 2016Ricky Grayson says:

    All was wrong, my bad frequency halfed not doubled

  5. 30th July 2016ua726 says:

    Ta for the comment and the code: must admit that I thought I checked the frequency of my trigger out when I was fiddling with my breadboarded attempt at a Spider-style sequencer, but that was ages ago. If I get a chance this weekend I’ll plug it all in and report back.

  6. 1st August 2016ua726 says:

    One knee on Nord Modular, holding a note down while I took a photo:

    wasp trigger on oscilloscope

    …looks about right to me with no changes to the code. Actually just playing it by hand from the Nord seemed fine. I’ll hook up a MIDI sequencer that isn’t the x0xb0x to see how that goes.

  7. 2nd August 2016Ricky Grayson says:

    Sorry meant to say your code was right, long day coding, I was helping a friend get his working, his notes are jumping around a lot similar to what you described. After hours of researching and testing, I somehow thought toggling a pin at a given frequency doubled the frequency on the pin not halved and that was the source of the error, which it wasn’t.

  8. 2nd August 2016ua726 says:

    Tried the MPC, but the Wasp is playing up again – some notes were ok, others were wobbling around.

    It doesn’t help that the Wasp keyboard decoder (and possibly further on) is playing up on mine. I really need to finish off the Jasper Wasp clone, and then use that as a basis to fix the Wasp.

  9. 24th August 2016Stephen Swales says:

    Works great on an original Wasp using an UNO R3. I even forgot to connect the digital ground and bizarrely it still worked. Optocoupler was a 6N138 on a Spark MIDI shield.

    Sorry to hear about your Wasp, had the same problem a few years ago, a lot of trial and error replacing ancient 4000 series chips.

Write a comment: