Teensytron (Audio Library Mellotron project)

Status
Not open for further replies.

drjohn

Well-known member
I'm building a sample player, mostly for classic Mellotron samples ( http://en.wikipedia.org/wiki/Mellotron ). USB (and eventually serial) MIDI note on and note off messages play (and stop playing) samples on the Teensy 3.1 DAC.

I have been obsessively checking in on the Audio Library files, examples, and documentation--waiting for smarter folks' project code to arrive for my dissection. In the meantime, I have bashed out the included code, which compiles but likely needs some buffing owing to crusty coding skills and my never ever having worked with pointers or the audio library. Changing instruments (pointing to a different folder of samples on the SD card) and adding effects are probably simple code but can wait.

Playfromsketch and PlaywavfromSDCard have gotten me most of the way there. It is basically a polyphonic version of the latter with unsophisticated voice allocation: When you run out of voices (seven), the next (eighth) note does not sound.

The hardware is simple and derived from the above example sketches: SD card holds the sample files / internal DAC out through capacitor to cheap headphones or your amp of choice. The code is generic enough that it's suitable for kids toys, drum machines, homebrew pinball, etc.

Code:
#include <Audio.h>
#include <Wire.h>
#include <SD.h>
#include <SPI.h>

// Create the Audio components.  These should be created in the
// order data flows, inputs/sources -> processing -> outputs

AudioPlaySdWav     wav0;
AudioPlaySdWav     wav1;
AudioPlaySdWav     wav2;
AudioPlaySdWav     wav3;
AudioPlaySdWav     wav4;
AudioPlaySdWav     wav5;
AudioPlaySdWav     wav6;
AudioPlaySdWav     wav7;
AudioPlaySdWav *waves[7] = {
  &wav0,
  &wav1,
  &wav2,
  &wav3,
  &wav4,
  &wav5,
  &wav6,
};

// AudioPlayMemory    sound0;   // vestigial notification tone, perhaps useful

AudioMixer4        mix1;    // two 4-channel mixers are needed in
AudioMixer4        mix2;    // tandem to combine 7 audio sources

AudioOutputAnalog  dac;     // play to on-chip DAC

// Create Audio connections between the components
//
AudioConnection c1(wav0, 0, mix1, 0);
AudioConnection c2(wav1, 0, mix1, 1);
AudioConnection c3(wav2, 0, mix1, 2);
AudioConnection c4(wav3, 0, mix1, 3);
AudioConnection c5(mix1, 0, mix2, 0);   // output of mix1 into 1st input on mix2
AudioConnection c6(wav4, 0, mix2, 1);
AudioConnection c7(wav5, 0, mix2, 2);
AudioConnection c8(wav6, 0, mix2, 3);     // for compiled-in sound, comment out
//AudioConnection c8(sound0, 0, mix2, 3);  // and uncomment this line
AudioConnection c9(mix2, 0, dac, 0);

char* notefile[]={"G2.wav", "G#2.wav", "A2.wav","A#2.wav","B2.wav",
"C3.wav","C#3.wav","D3.wav","D#3.wav","E3.wav","F3.wav","F#3.wav","G3.wav", "G#3.wav", "A3.wav","A#3.wav","B3.wav",
"C4.wav","C#4.wav","D4.wav","D#4.wav","E4.wav","F4.wav","F#4.wav","G4.wav", "G#4.wav", "A4.wav","A#4.wav","B4.wav",
"C5.wav","C#5.wav","D5.wav","D#5.wav","E5.wav","F5.wav"};

// Array to hold which note is in which mixer channel
// (needed for voice allocation and note off)
byte notenum [7]={99,99,99,99,99,99,99};  

byte i;  //index for note on counter (channels)
byte t;  //index for note off counter (searches)
float ggain = 0.25;  //global gain

void setup() {

  AudioMemory(10);   // see the MemoryAndCpuUsage example
  analogWriteResolution(12);
  dac.analogReference(INTERNAL);   // internal 1.2V reference by default

  // reduce the gain on mixer channels, so more than 1
  // sound can play simultaneously without clipping
  mix1.gain(0, ggain);
  mix1.gain(1, ggain);
  mix1.gain(2, ggain);
  mix1.gain(3, ggain);
  mix2.gain(1, ggain);
  mix2.gain(2, ggain);
  mix2.gain(3, ggain);
  
  Serial.begin(115200);
  usbMIDI.setHandleNoteOff(OnNoteOff);
  usbMIDI.setHandleNoteOn(OnNoteOn);
  delay(2000);            // little wait to get the serial screen up
  SPI.setMOSI(7);
  SPI.setSCK(14);
  if (SD.begin(10)) {
    waves[5]->play("E3.wav");     // it lives!  Gratuitous beepage
    waves[6]->play("C3.wav");     // it lives!  Gratuitous beepage    
    Serial.print("SD is a go.");
  }  
}

void loop() { 
  usbMIDI.read();                      // Using callbacks; this is all we need for the magic
}

void OnNoteOn(byte channel, byte note, byte velocity) {

  if (note<43 && velocity > 0) {
    Serial.print(note, DEC);
    Serial.print("Below G2");          // sample set has limited range
    note = 0;                          // so we'll play lowest/highest note
  }                                    // if it's below/above range
  else if (note>77 && velocity > 0) {
    Serial.print(note, DEC);
    Serial.print("Above F5");
    note = 34;
  }
  else if (velocity > 0)   {           // checking to make sure it is not a note off    
    Serial.print("Note=");
    Serial.print(note, DEC);
    note = note - 43;
  }
  // Above code adjusts note number
  
  // Below code plays note 
  
     waves[i]->play(notefile[note]);   
     Serial.print(" Channel: ");
     Serial.println(i, DEC);          
     i++;
     if (i>6) {
     Serial.print(" <channel wrap> ");       
     i=0;}
     
  }  

void OnNoteOff(byte channel, byte note, byte velocity) {  
  for (t = 0; t < 7; t++)  {           // search for same note already playing
    if (notenum[t] = note) {             
      waves[t]->stop();        // note found and terminated 
      notenum[t] = 99;
      break;          
     }    
   }
}


More details:
The Mellotron samples are 44.1k and monophonic. They are sets of 35 files, each with note-specific names like C#5.wav.

Sample sets (*please* do be gentle on this guy's server!): http://leisureland.us/audio/MellotronSamples/MellotronSamples.htm

Polyphony: Two mixers worth is 7 notes, which seems under the fast SD card limit (~12% CPU each sample totals 84%). Effects, reverb in particular, would be IMO worth sacrificing a note.

Interestingly the Mellotron tape beds ended after 7-8 seconds, requiring a "spidering" technique for sustained chords lest your note just end as its tape head ran off the tape. A low level of arpeggiation is mandatory, in other words. It keeps you lively.

This is hopefully helpful but assuredly not quite optimal code. Any comments, typo fixes, pointer mangle fixes, and other refactorings are very welcome. I am also curious about how the internal DAC sounds. It'll be days to a week before I can test and tweak it myself, but will dutifully report back if noone beats me to it.

EDIT: code fixed, mostly
 
Last edited:
partial victory

One change was needed to get it to make a peep:

Code:
  if (SD.begin(10)) {
    waves[6]->play("A3.wav");     // there was no waves [7]
    waves[5]->play("C3.wav");    //  single sounds can be thin; chords sound epic
}

I have to say...the start-up notes sound great in headphones! There is a short non-melodic buzz before the samples play which I think may be noise from SD card access, though I thought that SD access was continuous and...the annoying noise isn't.

I can see that the program reaches the main loop but it does not seem to respond to USB MIDI. I am sending notes in through the MIOS control panel app for OSX, which shows lot of detail of what is going in and out and has a virtual keyboard.

Any suggestions on what is going wrong in the void loop or MIDI processing? I am not even getting debug messages, which is...strange.

Debugging will continue as time allows. I am psyched to have this so bare bones (just SD card adapter and a capacitor on the DAC out) and still sound amazing.
 
Your explanation says you are using USB MIDI. Your code starts off as if you are going to use DIN MIDI:

Code:
#include <MIDI.h>
MIDI.begin(MIDI_CHANNEL_OMNI);

then changes its mind and sets up some USB MIDI callbacks:

Code:
  usbMIDI.setHandleNoteOff(OnNoteOff);
  usbMIDI.setHandleNoteOn(OnNoteOn);

which never get any data because you never call usbMIDI.read() although you do call (DIN) MIDI read:

Code:
MIDI.read();

also, you are needlessly checking for running status (which USB MIDI disallows):
Code:
else if (velocity > 0)   {           // checking to make sure it is not a note off
 
Last edited:
Sounds like....victory.

A heartfelt thanks for the corrections, Nantonos! The residual skeletons from the "everything" version are now mostly exorcised. I have revised the code in the initial message to an actual working version. I had to go with simple rotating voice allocation that is less than optimal, but it'll do.

Polyphony seems limited by SD card quality, with the failure mode being squealing lockup, not stuttering. Hopefully this and the initial bzzzt sound will improve with rewiring, faster SD card, and getting things off a breadboard.

Sound? It sounds mighty! BUT: the abrupt cutoff on NoteOff leaves me yearning for reverb. I'm sure that'll arrive, though I'm tempted to do a "same note, played delayed and softer" version. Maybe with a little filtering? That would cut into the polyphony, though. Outboard stompbox for now.
 
Great work! Tnx for sharing
I suggest a short fadeout on NoteOff, that would make it more smooth.
 
BUT: the abrupt cutoff on NoteOff leaves me yearning for reverb. I'm sure that'll arrive, though I'm tempted to do a "same note, played delayed and softer" version. Maybe with a little filtering? That would cut into the polyphony, though. Outboard stompbox for now.

Effectively (in terms of an ADSR envelope) you have very short attack, no decay, high sustain and zero release. It would be better to do some sort of volume control through the mixer for the note that is being released. You could decrement some counter and set the volume to that, until it is zero, then stop playing that note and return it to the polyphony pool.
 
There is an AudioEffectFade in the library which might be of use in fading out the notes. I haven't tried it yet so I don't know if it would help.

Pete
 
Status
Not open for further replies.
Back
Top