Skip to main content

Arduino MIDI Music Box

It's time to do something useful with our small 8-bit computer. Music box is a fun and easy project. It's not demanding from the hardware point of view but it can be a little bit tricky from the software point of view itself - ideal project to learn a lot and not to spend the whole day soldering. What we will need: 

  • an Arduino (of course) 
  • Piezo buzzer

 ... and that's all. Now a couple of words of what we want to achieve. 

Requirements 


Let's point them out:

  • play monophonic melodies by generating tones (1 channel MIDI instrument)
  • the notes will be send by the PC through the serial port 
  • our musicbox should be visible to the PC as a MIDI port 
  • be able to use MIDI player of choice on PC to play MIDI files on our musicbox 


The requirements are specified. Unlike most of the simple projects around which play the same melody everytime, which most of the times is hardcoded in the program memory and in order to change it one must modify the tone frequencies and the timing between them and then re-flash the Arduino, our musicbox will simply act as a simple MIDI device and become a "MIDI sink" for incoming serial data. That means that more or less we can play any MIDI file with it that comes in our hands. The only limitation is the number of channels - the firmware which I'm going to discuss will play only one - the user will have to select what channel data should be send & played, but in general it's not impossible to enhance the implementation to support more, for demonstration purposes one is enough though. 

Technologies 


I need to discuss what kind of tools and technologies will be used. No matter how trivial a musicbox can look in the first place there will be quite a lot going on under the hood. Here are the things that will be used (in terms of software) on the Arduino side: 

  • beeper module from libpca - the timer will be used for tone generation 
  • SLIP module from libpca - the data will be send in binary format in SLIP frames from PC to the Arduino 

Everything has been already covered in my previous posts on this blog. If you're following the blog any of the above require no explanation. At the time being creating a more sophisticated project like this one is only a matter of putting the building blocks together. On the PC side I will use Perl to do the dirty work. No matter what you have heard or have been told, Perl is a great language far more superior than Python and I hope that again I'll prove it to every of you. Things that will be needed: 

  • Device::SerialPort - serial port communication 
  • MIDI::ALSA - for doing all the MIDI dirty work 
  • Digest::CRC - to calculate CRC checksum of the outgoing data PC side


PC side


Let's start with the PC side first. The Perl script will register new MIDI port and wait for the data written to it which then will be send through the Serial port to Arduino - pretty simple. The data I'm interested in from the MIDI stream incoming are so called "note_on/note_off" events. Unfortunately their not exactly in a format convenient and appropriate for this project. The note_on event defines the beginning of a particular not (a trigger to start generating tone of given key), we should stop generation when receiving note_off. This is not acceptable since it blocks possibility to generate any other tone in between and we would have to track when to really stop the generation on each incoming note_off event. 

Fortunately enough again Perl and CPAN saves the day. The MIDI::ALSA module defines a notion of "score" - a set of midi tracks in which every event has it's own length defined, so instead of two events note_on/note_off we have only one - note, which provides the following information:

{'note', start_time, duration, channel, note, velocity}

Exactly what we need. The bad news is that it doesn't work very well. In fact it seems that duration has some constantly increasing value along with the position in the MIDI file and in result isn't very useful at all, but at least instead of processing two events we have only one. From the data above I'm interested only in the event type -> 'note' the 'note' itself of course and the channel - I'll filter the data from one or couple of channels only to be send and played by Arduino since it would produce a complete chaos if we would want to try to play everything using only one instrument at once.

Since we will be sending the frequency to the Arduino, we need to convert the MIDI note number (0-127) into frequency domain. After doing a quick Google search for MIDI note frequencies I come up declaring the following array:

# midi notes
my @notes = (
8.1757989156,
8.6619572180,
9.1770239974,
10.3008611535,
10.3008611535,
10.9133822323,
11.5623257097,
12.2498573744,
12.9782717994,
13.7500000000,
14.5676175474,
15.4338531643,
16.3515978313,
17.3239144361,
18.3540479948,
19.4454364826,
20.6017223071,
21.8267644646,
23.1246514195,
24.4997147489,
25.9565435987,
27.5000000000,
29.1352350949,
30.8677063285,
32.7031956626,
34.6478288721,
36.7080959897,
38.8908729653,
41.2034446141,
43.6535289291,
46.2493028390,
48.9994294977,
51.9130871975,
55.0000000000,
58.2704701898,
61.7354126570,
65.4063913251,
69.2956577442,
73.4161919794,
77.7817459305,
82.4068892282,
87.3070578583,
92.4986056779,
97.9988589954,
103.8261743950,
110.0000000000,
116.5409403795,
123.4708253140,
130.8127826503,
138.5913154884,
146.8323839587,
155.5634918610,
164.8137784564,
174.6141157165,
184.9972113558,
195.9977179909,
207.6523487900,
220.0000000000,
233.0818807590,
246.9416506281,
261.6255653006,
277.1826309769,
293.6647679174,
311.1269837221,
329.6275569129,
349.2282314330,
369.9944227116,
391.9954359817,
415.3046975799,
440.0000000000,
466.1637615181,
493.8833012561,
523.2511306012,
554.3652619537,
587.3295358348,
622.2539674442,
659.2551138257,
698.4564628660,
739.9888454233,
783.9908719635,
830.6093951599,
880.0000000000,
932.3275230362,
987.7666025122,
1_046.5022612024,
1_108.7305239075,
1_174.6590716696,
1_244.5079348883,
1_318.5102276515,
1_396.9129257320,
1_479.9776908465,
1_567.9817439270,
1_661.2187903198,
1_760.0000000000,
1_864.6550460724,
1_975.5332050245,
2_093.0045224048,
2_217.4610478150,
2_349.3181433393,
2_489.0158697766,
2_637.0204553030,
2_793.8258514640,
2_959.9553816931,
3_135.9634878540,
3_322.4375806396,
3_520.0000000000,
3_729.3100921447,
3_951.0664100490,
4_186.0090448096,
4_434.9220956300,
4_698.6362866785,
4_978.0317395533,
5_274.0409106059,
5_587.6517029281,
5_919.9107633862,
5_919.9107633862,
6_644.8751612791,
7_040.0000000000,
7_458.6201842894,
7_902.1328200980,
8_372.0180896192,
8_869.8441912599,
9_397.2725733570,
9_956.0634791066,
10_548.0818212118,
11_175.3034058561,
11_839.8215267723,
12_543.8539514160,
); # notes

Great, one problem less. 

The MIDI sequencer


A MIDI sequencer must be registered on the PC side to provide an output for MIDI playback software. It's extremely easy with MIDI::ALSA module and Perl:

MIDI::ALSA::client('Arduino MIDI Beeper', 1, 1, 0);

and that's all. I register a new sequencer with one input port and one output port. We can already see it in the system:

$ aconnect -oil
client 0: 'System' [type=kernel]    0 'Timer           '    1 'Announce        'client 14: 'Midi Through' [type=kernel]    0 'Midi Through Port-0'client 128: 'Arduino MIDI Beeper' [type=user]    0 'Input port      '    1 'Output port     '

Ok, we are almost done, all that's left is to take the MIDI data and send it to Arduino. Let's analyze the main program loop, again there will be some Perl trickery but I'll try to de-crypt everything which is not obvious to everyone not very familiar with Perl.

The call to MIDI::ALSA::input() is blocking, it means that we will wait on the first line for the MIDI event before we'll send anything to the Arduino - that's a good thing, that way we do not have to worry about the timing, the notes will be played as soon as they arrive. Next I detect if the incoming event does not define the MIDI file end - I exit the loop if that's the case. 

Next thing is to convert the MIDI event to a "score event". Next two lines simply skip to another loop roll if the event does not contain any useful information (it's either empty or it's not a 'note' event). Just for the debugging purposes I dump the event to the console. Going further I filter the events (by skipping them) if in the command line channels to be player are specified, if not then data from every channel will be sent. Later on I simply convert the MIDI note to frequency and because of the problems with getting the reliable event 'duration' information from MIDI::ALSA I simply define a fixed duration for every note in milliseconds (50 in this example). Next the data is being placed in the SLIP frame and sent to Arduino to be played. Arduino should return the data back (again just for debugging purposes) - and that's all.

Arduino Side


Basically all the hard work has been already done and is hidden in libpca. Please refer to my previous posts for more details, since every aspect and function used for this mini project has been already thoroughly discussed. 

Arduino firmware will be very simple, all that needs to be done is to simply receive the SLIP frame, verify it and play the note inside. Let's have a look on the message format first.

Each note is defined as a pair of numbers - frequency and the duration. Each packet coming from the host contains the CRC16 checksum, a byte denoting how many notes have been sent and the notes themselves. I decided that in a single packet it will be possible to send up to four notes - that would require some timing information for each note itself so even though it's possible to sent up to 4 notes in one shot in the final version of this project I'm always sending only one.

The listing bellow present a complete source for the Arduino firmware.


In the while loop it wait's for a new SLIP frame, verify the data integrity and play the notes from the packet. After flashing the Arduino we can finally do some tests.

How to use it ?


The whole project snapshot can be obtained here. One can fetch the newest version of libpca and the project itself from my public GitHub repository as well. The snapshot and the GitHub version have a slightly different Makefile, paths to the libpca are a little bit different, everything else is exactly the same. 

GitHub


git clone git@github.com:dagon666/avr_Libpca pca
git clone git@github.com:dagon666/avr_ArduinoProjects projects

Snapshot


Download and unpack the snapshot from here


... the rest is common:

Build and flash the Arduino firmware:

cd projects/musicbox
make
make install

Launch the mid_converter.pl script (optionally provide channel numbers in the command line which will be played), get the sequencer details:

$ aconnect -oil

play your midi file using your player of choice (I'll use aplaymidi):

aplaymidi -p 128:0 <midi_file>

Musicbox in action 


Now that everything has been said it's time to see the thing in action. I'm using Timer 0 and OC0A compare output (which is located on PORTD6 - Pin6 on Arduino) to which the buzzer is connected.





Comments

Post a Comment

Popular posts from this blog

RTC without RTC ? How good is my crystal ?

Clock - I think that it's the most popular idea for a project with the MCU . It's not that hard from both software and hardware side to realize but not that trivial as well, not mentioning the fact that once realized it sits somewhere in the house, constantly reminding about itself and brings a kind of satisfaction :). There are a lot of options really to implement, a buzzer alarm, perhaps a thermometer, display and buttons servicing, perhaps maybe even serial port usage for NTP synchronization. But ... The most challenging thing - like in every clock is the time reference source. There are a couple of options: well calibrated frequency source acting as an interrupt source Real Time Clock module with external crystal ( < 20 ppm ) internal clock These days RTC chips are so cheap and widely available that they are really the only reasonable choice, but if we're only planning to play around a bit with the software without paying much attention to accura...

Arduino R-2R ladder Audio DAC

There is a lot of projects out there which use  R-2R ladder and an Arduino to recreate sounds from either SD card or short audio clips programmed directly to MCU's flash memory. Although using SD card is fairly reasonable and provides a lot of flexibility it's not very challenging (from the software point of view) and it requires some additional hardware. Believe it or not we already have all the needed hardware in the Arduino itself. Assumptions In this project I'll play mp3 or any other multimedia files from the PC using Arduino. Bellow are the details: Play PCM 8kHz 8bit Audio with Arduino Audio samples will be transfered via USART from the PC in binary format using SLIP Audio files will be decoded on the PC side and only the RAW data will be send to Arduino Timing Arduino is a powerful machine, powerful enough that it's possible to play audio with even higher sampling rates and bit-resolutions than assumed above, the bottleneck in this...

Simple Serial Port Command Line Interface (CLI)

It's often very useful to have some sort of interface through which a basic management can be done of our application. Whether you're designing a giant Marquee with a LED display or a plotter or you simply need to get some diagnostics from your device occasionally, a simple CLI system can come very handy.  Of course, designing something similar to "bash" or any other unix shell is completely out of scope since those applications are huge and are simply an overkill for our needs. It's pretty simple though to create some basic, yet flexible & easilly extensible CLI system. First thing needed is a command type definition. This will bind a keyword with an actual underlying routine executed for that keyword typed in the CLI . typedef struct _t_cmd { const char *cmd; void (*fh)(void*); } t_cmd; The command type is pretty simple. There's the CLI command/keyword pointer, it holds a pointer to the execution function and that's it. OK, so...