Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 8 of 8

Thread: Teensytron (Audio Library Mellotron project)

  1. #1
    Member
    Join Date
    Nov 2012
    Location
    Portland
    Posts
    62

    Teensytron (Audio Library Mellotron project)

    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/Mellotro...ronSamples.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 by drjohn; 03-17-2014 at 08:10 PM. Reason: code was embarrasingly bad :/

  2. #2
    Member
    Join Date
    Nov 2012
    Location
    Portland
    Posts
    62

    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.

  3. #3
    Senior Member
    Join Date
    Nov 2012
    Location
    Boston, MA, USA
    Posts
    1,112
    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 by Nantonos; 03-14-2014 at 06:10 AM.

  4. #4
    Member
    Join Date
    Nov 2012
    Location
    Portland
    Posts
    62

    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.

  5. #5
    Great work! Tnx for sharing
    I suggest a short fadeout on NoteOff, that would make it more smooth.

  6. #6
    Senior Member pictographer's Avatar
    Join Date
    May 2013
    Location
    San Jose, CA
    Posts
    701
    As a prog rock fan, I'm delighted about this project and hope it goes far!

  7. #7
    Senior Member
    Join Date
    Nov 2012
    Location
    Boston, MA, USA
    Posts
    1,112
    Quote Originally Posted by drjohn View Post
    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.

  8. #8
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,609
    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

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •