Given a short MIDI file, this command-line MIDI conversion utility writes the same music as a simple bytecode.
The Arduino player sketch uses this simple bytecode to control music playback. As the player updates the synthesizer's 64 voices, it also updates the colored circles. Each one represents a voice, and the size of each circle shows the voice's amplitude.
#include <SPI.h>
#include <GD.h>
#include "mont.h"
// visualize voice (v) at amplitude (a) on an 8x8 grid
void visualize(byte v, byte a)
{
int x = 64 + ((v & 7) * 34);
int y = 14 + ((v >> 3) * 34);
byte sprnum = (v << 2); // draw each voice using four sprites
GD.sprite(sprnum++, x + 16, y + 16, a, 0, 0);
GD.sprite(sprnum++, x + 0, y + 16, a, 0, 2);
GD.sprite(sprnum++, x + 16, y + 0, a, 0, 4);
GD.sprite(sprnum++, x + 0, y + 0, a, 0, 6);
}
void setup()
{
int i;
GD.begin();
GD.wr16(RAM_SPRPAL + 2 * 255, TRANSPARENT);
// draw 32 circles into 32 sprite images
for (i = 0; i < 32; i++) {
GD.wr16(RAM_SPRPAL + 2 * i, RGB(8 * i, 64, 255 - 8 * i));
int dst = RAM_SPRIMG + 256 * i;
GD.__wstart(dst);
byte x, y;
int r2 = min(i * i, 256);
for (y = 0; y < 16; y++) {
for (x = 0; x < 16; x++) {
byte pixel;
if ((x * x + y * y) <= r2)
pixel = i; // use color above
else
pixel = 0xff; // transparent
SPI.transfer(pixel);
}
}
GD.__end();
}
for (i = 0; i < 64; i++)
visualize(i, 0);
}
byte amp[64]; // current voice amplitude
byte target[64]; // target voice amplitude
// Set volume for voice v to a
void setvol(byte v, byte a)
{
GD.__wstart(VOICES + (v << 2) + 2);
SPI.transfer(a);
SPI.transfer(a);
GD.__end();
}
void adsr() // handle ADSR for 64 voices
{
byte v;
for (v = 0; v < 64; v++) {
int d = target[v] - amp[v]; // +ve means need to increase
if (d) {
if (d > 0)
amp[v] += 4; // attack
else
amp[v]--; // decay
setvol(v, amp[v]);
visualize(v, amp[v]);
}
}
}
void loop()
{
prog_uchar *pc;
long started = millis();
int ticks = 0;
for (pc = mont; pc < mont + sizeof(mont);) {
byte cmd = pgm_read_byte_near(pc++); // upper 2 bits are command code
if ((cmd & 0xc0) == 0) {
// Command 0x00: pause N*5 milliseconds
ticks += (cmd & 63);
while (millis() < (started + ticks * 5)) {
adsr();
delay(1);
}
} else {
byte v = (cmd & 63);
byte a;
if ((cmd & 0xc0) == 0x40) {
// Command 0x40: silence voice
target[v] = 0;
} else if ((cmd & 0xc0) == 0x80) {
// Command 0x80: set voice frequency and amplitude
byte flo = pgm_read_byte_near(pc++);
byte fhi = pgm_read_byte_near(pc++);
GD.__wstart(VOICES + 4 * v);
SPI.transfer(flo);
SPI.transfer(fhi);
GD.__end();
target[v] = pgm_read_byte_near(pc++);
}
}
}
}
The converted MIDI file is "MONT" by Morusque. The music data takes about 9K of flash on the Arduino.