257 lines
7 KiB
C++
Executable file
257 lines
7 KiB
C++
Executable file
// Implement a 16 note polyphonic midi player :-)
|
|
//
|
|
// Music data is read from memory. The "Miditones" program is used to
|
|
// convert from a MIDI file to this compact format.
|
|
//
|
|
// This example code is in the public domain.
|
|
|
|
#include <Audio.h>
|
|
#include <Wire.h>
|
|
#include <SD.h>
|
|
#include <SPI.h>
|
|
#include <SerialFlash.h>
|
|
|
|
#include "PlaySynthMusic.h"
|
|
|
|
unsigned char *sp = score;
|
|
|
|
#define AMPLITUDE (0.2)
|
|
|
|
// Create 16 waveforms, one for each MIDI channel
|
|
AudioSynthWaveform sine0, sine1, sine2, sine3;
|
|
AudioSynthWaveform sine4, sine5, sine6, sine7;
|
|
AudioSynthWaveform sine8, sine9, sine10, sine11;
|
|
AudioSynthWaveform sine12, sine13, sine14, sine15;
|
|
AudioSynthWaveform *waves[16] = {
|
|
&sine0, &sine1, &sine2, &sine3,
|
|
&sine4, &sine5, &sine6, &sine7,
|
|
&sine8, &sine9, &sine10, &sine11,
|
|
&sine12, &sine13, &sine14, &sine15
|
|
};
|
|
|
|
// allocate a wave type to each channel.
|
|
// The types used and their order is purely arbitrary.
|
|
short wave_type[16] = {
|
|
WAVEFORM_SINE,
|
|
WAVEFORM_SQUARE,
|
|
WAVEFORM_SAWTOOTH,
|
|
WAVEFORM_TRIANGLE,
|
|
WAVEFORM_SINE,
|
|
WAVEFORM_SQUARE,
|
|
WAVEFORM_SAWTOOTH,
|
|
WAVEFORM_TRIANGLE,
|
|
WAVEFORM_SINE,
|
|
WAVEFORM_SQUARE,
|
|
WAVEFORM_SAWTOOTH,
|
|
WAVEFORM_TRIANGLE,
|
|
WAVEFORM_SINE,
|
|
WAVEFORM_SQUARE,
|
|
WAVEFORM_SAWTOOTH,
|
|
WAVEFORM_TRIANGLE
|
|
};
|
|
|
|
// Each waveform will be shaped by an envelope
|
|
AudioEffectEnvelope env0, env1, env2, env3;
|
|
AudioEffectEnvelope env4, env5, env6, env7;
|
|
AudioEffectEnvelope env8, env9, env10, env11;
|
|
AudioEffectEnvelope env12, env13, env14, env15;
|
|
AudioEffectEnvelope *envs[16] = {
|
|
&env0, &env1, &env2, &env3,
|
|
&env4, &env5, &env6, &env7,
|
|
&env8, &env9, &env10, &env11,
|
|
&env12, &env13, &env14, &env15
|
|
};
|
|
|
|
// Route each waveform through its own envelope effect
|
|
AudioConnection patchCord01(sine0, env0);
|
|
AudioConnection patchCord02(sine1, env1);
|
|
AudioConnection patchCord03(sine2, env2);
|
|
AudioConnection patchCord04(sine3, env3);
|
|
AudioConnection patchCord05(sine4, env4);
|
|
AudioConnection patchCord06(sine5, env5);
|
|
AudioConnection patchCord07(sine6, env6);
|
|
AudioConnection patchCord08(sine7, env7);
|
|
AudioConnection patchCord09(sine8, env8);
|
|
AudioConnection patchCord10(sine9, env9);
|
|
AudioConnection patchCord11(sine10, env10);
|
|
AudioConnection patchCord12(sine11, env11);
|
|
AudioConnection patchCord13(sine12, env12);
|
|
AudioConnection patchCord14(sine13, env13);
|
|
AudioConnection patchCord15(sine14, env14);
|
|
AudioConnection patchCord16(sine15, env15);
|
|
|
|
// Four mixers are needed to handle 16 channels of music
|
|
AudioMixer4 mixer1;
|
|
AudioMixer4 mixer2;
|
|
AudioMixer4 mixer3;
|
|
AudioMixer4 mixer4;
|
|
|
|
// Mix the 16 channels down to 4 audio streams
|
|
AudioConnection patchCord17(env0, 0, mixer1, 0);
|
|
AudioConnection patchCord18(env1, 0, mixer1, 1);
|
|
AudioConnection patchCord19(env2, 0, mixer1, 2);
|
|
AudioConnection patchCord20(env3, 0, mixer1, 3);
|
|
AudioConnection patchCord21(env4, 0, mixer2, 0);
|
|
AudioConnection patchCord22(env5, 0, mixer2, 1);
|
|
AudioConnection patchCord23(env6, 0, mixer2, 2);
|
|
AudioConnection patchCord24(env7, 0, mixer2, 3);
|
|
AudioConnection patchCord25(env8, 0, mixer3, 0);
|
|
AudioConnection patchCord26(env9, 0, mixer3, 1);
|
|
AudioConnection patchCord27(env10, 0, mixer3, 2);
|
|
AudioConnection patchCord28(env11, 0, mixer3, 3);
|
|
AudioConnection patchCord29(env12, 0, mixer4, 0);
|
|
AudioConnection patchCord30(env13, 0, mixer4, 1);
|
|
AudioConnection patchCord31(env14, 0, mixer4, 2);
|
|
AudioConnection patchCord32(env15, 0, mixer4, 3);
|
|
|
|
// Now create 2 mixers for the main output
|
|
AudioMixer4 mixerLeft;
|
|
AudioMixer4 mixerRight;
|
|
AudioOutputI2S audioOut;
|
|
|
|
// Mix all channels to both the outputs
|
|
AudioConnection patchCord33(mixer1, 0, mixerLeft, 0);
|
|
AudioConnection patchCord34(mixer2, 0, mixerLeft, 1);
|
|
AudioConnection patchCord35(mixer3, 0, mixerLeft, 2);
|
|
AudioConnection patchCord36(mixer4, 0, mixerLeft, 3);
|
|
AudioConnection patchCord37(mixer1, 0, mixerRight, 0);
|
|
AudioConnection patchCord38(mixer2, 0, mixerRight, 1);
|
|
AudioConnection patchCord39(mixer3, 0, mixerRight, 2);
|
|
AudioConnection patchCord40(mixer4, 0, mixerRight, 3);
|
|
AudioConnection patchCord41(mixerLeft, 0, audioOut, 0);
|
|
AudioConnection patchCord42(mixerRight, 0, audioOut, 1);
|
|
|
|
AudioControlSGTL5000 codec;
|
|
|
|
// Initial value of the volume control
|
|
int volume = 50;
|
|
|
|
void setup()
|
|
{
|
|
Serial.begin(115200);
|
|
//while (!Serial) ; // wait for Arduino Serial Monitor
|
|
delay(200);
|
|
|
|
// http://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html
|
|
Serial.print("Begin ");
|
|
Serial.println(__FILE__);
|
|
|
|
// Proc = 12 (13), Mem = 2 (8)
|
|
// Audio connections require memory to work.
|
|
// The memory usage code indicates that 10 is the maximum
|
|
// so give it 12 just to be sure.
|
|
AudioMemory(18);
|
|
|
|
codec.enable();
|
|
codec.volume(0.45);
|
|
|
|
// reduce the gain on some channels, so half of the channels
|
|
// are "positioned" to the left, half to the right, but all
|
|
// are heard at least partially on both ears
|
|
mixerLeft.gain(1, 0.36);
|
|
mixerLeft.gain(3, 0.36);
|
|
mixerRight.gain(0, 0.36);
|
|
mixerRight.gain(2, 0.36);
|
|
|
|
// set envelope parameters, for pleasing sound :-)
|
|
for (int i=0; i<16; i++) {
|
|
envs[i]->attack(9.2);
|
|
envs[i]->hold(2.1);
|
|
envs[i]->decay(31.4);
|
|
envs[i]->sustain(0.6);
|
|
envs[i]->release(84.5);
|
|
// uncomment these to hear without envelope effects
|
|
//envs[i]->attack(0.0);
|
|
//envs[i]->hold(0.0);
|
|
//envs[i]->decay(0.0);
|
|
//envs[i]->release(0.0);
|
|
}
|
|
|
|
Serial.println("setup done");
|
|
|
|
// Initialize processor and memory measurements
|
|
AudioProcessorUsageMaxReset();
|
|
AudioMemoryUsageMaxReset();
|
|
}
|
|
|
|
|
|
unsigned long last_time = millis();
|
|
void loop()
|
|
{
|
|
unsigned char c,opcode,chan;
|
|
unsigned long d_time;
|
|
|
|
// Change this to if(1) for measurement output every 5 seconds
|
|
if(1) {
|
|
if(millis() - last_time >= 5000) {
|
|
Serial.print("Proc = ");
|
|
Serial.print(AudioProcessorUsage());
|
|
Serial.print(" (");
|
|
Serial.print(AudioProcessorUsageMax());
|
|
Serial.print("), Mem = ");
|
|
Serial.print(AudioMemoryUsage());
|
|
Serial.print(" (");
|
|
Serial.print(AudioMemoryUsageMax());
|
|
Serial.println(")");
|
|
last_time = millis();
|
|
}
|
|
}
|
|
|
|
// Volume control
|
|
// uncomment if you have a volume pot soldered to your audio shield
|
|
/*
|
|
int n = analogRead(15);
|
|
if (n != volume) {
|
|
volume = n;
|
|
codec.volume((float)n / 1023);
|
|
}
|
|
*/
|
|
|
|
// read the next note from the table
|
|
c = *sp++;
|
|
opcode = c & 0xF0;
|
|
chan = c & 0x0F;
|
|
|
|
if(c < 0x80) {
|
|
// Delay
|
|
d_time = (c << 8) | *sp++;
|
|
delay(d_time);
|
|
return;
|
|
}
|
|
if(*sp == CMD_STOP) {
|
|
for (chan=0; chan<10; chan++) {
|
|
envs[chan]->noteOff();
|
|
waves[chan]->amplitude(0);
|
|
}
|
|
Serial.println("DONE");
|
|
while(1);
|
|
}
|
|
|
|
// It is a command
|
|
|
|
// Stop the note on 'chan'
|
|
if(opcode == CMD_STOPNOTE) {
|
|
envs[chan]->noteOff();
|
|
return;
|
|
}
|
|
|
|
// Play the note on 'chan'
|
|
if(opcode == CMD_PLAYNOTE) {
|
|
unsigned char note = *sp++;
|
|
unsigned char velocity = *sp++;
|
|
AudioNoInterrupts();
|
|
waves[chan]->begin(AMPLITUDE * velocity2amplitude[velocity-1],
|
|
tune_frequencies2_PGM[note],
|
|
wave_type[chan]);
|
|
envs[chan]->noteOn();
|
|
AudioInterrupts();
|
|
return;
|
|
}
|
|
|
|
// replay the tune
|
|
if(opcode == CMD_RESTART) {
|
|
sp = score;
|
|
return;
|
|
}
|
|
}
|