A MIDI File player for Arduino, made in CoffeeScript (with node).
A subset of the MIDI File specification is implemented.
It supports:
- various tracks
- simultaneous notes in one track
- bpm changes
-
It ignores:
- effects (bend, vibrato...)
- track's information (instrument*, volume...)
- any other non-note events
* be careful with percussion tracks
-
The higher note that the buzzers can play is a B4: the exception is the first buzzer, which takes advantage of the Arduino's internal timer.
- up to 5 buzzers simultaneously, connected to pins [3..7]
- a 220Ω resistor for each connected buzzer
#node is required
npm install grunt-cli
npm install
#upload sketch/fast/fast.ino to your Arduino board
grunt --midi=examples/beethoven-virus.mid
grunt --midi=examples/mentirosa.mid
grunt --midi=examples/pokemon-intro.mid --firstIdle
grunt --midi=examples/pokemon-battle.mid
grunt --midi=examples/technical-difficulties.mid
The player is made with buzzers. A buzzer makes a clicking sound each time it is pulsed with current. A clicking sound is not funny, but what if we pulse it 440 times in a second? Bingo, we got an A note.
To produce a square wave with 440hz of frequency, the time that the pin has to be HIGH is 1136us. How do we know that?
The period is the amount of time it takes for the wave to repeat itself:
=> we need the buzzer's pin to be HIGH in the first half of the period, and LOW in the other half =>
timeHigh = period / 2 = (1 / frequency) / 2
The frequency of a note can be calculated (taking by reference an A in the 4th octave: A4 - 440hz):
440 * c^distance
//c: a constant, the twelfth root of 2
//distance: semitones between the note and the A4
Arduino boards can be controlled by any computer using the Firmata Protocol, particularly by nodejs thanks to the johnny-five programming framework: it sends to the board the instructions by the serial connection.
Node is slow. Not really, but it's slower than native C code running on the board. To reach high notes, pauses of very few microseconds are needed. Because of this, the wave-generating part is implemented in the sketch.
The js script tells to the sketch what note it has to play and in which speaker: this is made by a pseudo custom protocol. The serial port is used by Firmata to control the board, so extra info can't be appended.
=> The analogWrite
message was used on a specific port (3) for sending notes.
For example, to make the buzzer 6 to play an A4, the messages are:
analogWrite(3, 6); //selected buzzer
analogWrite(3, 1136); //timeHigh of the A4
For making the buzzer to stop:
analogWrite(3, 6);
analogWrite(3, 0);
The sketch code (while it's optimized to write ports with direct-io), can't handle high frequencies. That limitation impedes the buzzers [4..7] to play high notes. The only one that can play any notes is the buzzer 3: it uses the internal timer and the tone() function.
Many times, some MIDI Tracks can be unified: while one is playing notes, another is playing silences. The player makes a mix only with the notes that will be actually played. This depends on the playing modes.
Because the max-note-limitation, there're two modes of playing files:
- First Idle: (--firstIdle) It assigns the first idle buzzer to all notes. This produces more uniform sound when the user is sure that the notes of the MIDI are lower than the max note.
- High Channel: [default] The notes higher than the max note are assigned to the first buzzer. If two high notes have to sound together, one will be ignored.