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.
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
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: