Floppy Jukebox
From
Just like me you probably have listened to the music produced by floppy disks/scanners/and other hardware containing stepper motors. I remember the first time i heard this was on the Amiga a long time ago. I got inspired by this and i thought it would be a good learning experience to create something similar using the Arduino. Some 5,25" and 3,25" drives where gathering dust at our hacking space and in my attic i found an old SCSI case with power supply and room for 4 5,25" devices. I thought it would be nice to create a sort of Jukebox which could play songs. I ended up with a Floppy Jukebox playing midi songs stored on an micro-sd card. A remote from a broken cd-player is used to select and play the songs.
Later i added an RGB led strip for some visuals and for the Gogbot 2011 OSC_Databending project i added an Ethernet port to control the Jukebox with OSC Messages.
From 23 may - until 9 june 2013 you can see the floppy jukebox at the Twente Biënnale For this i created a Euro sign with leds and connected it to the jukebox:
Floppy Jukebox with disco lights:
First version:
Hardware
- Arduino Uno used as controller, later replaced by arduino ethernet for OSC_Databending
- 4 Floppy drives as instruments
- SD-card to store the songs
- IR sensor + remote to control playing
- A 4 x 5,25" device bay case with power supply
- RGB Led strip
- Optional: Ethernet shield for OSC
Software
- Arduino 0022 software
- SD-FAT library to access the SD card
- IR library for remote controller
- I made a python midi decoder File:Midi2mmi-V0.7.zip that decodes and converts midi files to 1 track 'MMI' songs. Those can be more easily read by the Arduino.
- The main code File:Floppy-Jukebox-V1.0.zip on the arduino reads the songs from the sd-card and uses a ringbuffer and interrupt driven code to play a maximum of 4 notes simultaneously on the 4 drives. At the same time activity on drive 1 to 3 is also controlling the RGB values of the led strip creating a disco like effect. If no song is playing a slow color cycling routine drives the led strip. Trough the later added OSC interface a song can be selected or (when not playing) the lighting color can be set.
Music List
All these songs are converted MIDI songs. At this moment we have the following songs converted for the Floppy Jukebox:
- Willam Tell Overture - Rossini
- Pacman theme
- Can Can - Jacques Offenbach
- The Fifth Symphony - Beethoven
- Mario Brothers theme
- Starwars theme
- Bolero - Ravel
- Prelude in c major - JS Bach
- Opening sequence form Also Sprach Zarathustra - Richard Strauss
- Für Elise - Beethoven
- Moonlight Soneta - Beethoven
- Tocata - JS Bach
- O Fortuna - Carmina Burana
- The Liberty Bell march - Hohn Philip Sousa
- Axel F - Harold Faltermeyer
- Three's Company theme song
- Dr. Who theme song
- Nyan Cat song
MIDI using multicast UDP
Multicast is handy because several synthesizers can join a multicast IP address and respond to MIDI events simultaneously. The default arduino ethernet library doesn't support multicast, however the W5100 chip on the ethernet shield does support one multicast address per socket.
See on Arduino UDP multicast what to change to enable the Arduino UDP Multicast.
Update 2016
Replaced firmware with this:
#include <TimerOne.h> #include "pitches.h" const uint32_t maxfreq = 10000; /* Hz */ const uint8_t numdrives = 5; const uint8_t pin_step[numdrives] = {10,11,12,13}; const uint8_t pin_dir[numdrives] = {6,7,3,5}; const uint8_t pin_enable[numdrives] = {A3,A2,A1,A0}; const uint8_t pin_rotate = A4; const uint16_t maxpos[numdrives] = {70,70,40,50}; /* Position and direction tracking */ uint16_t pos[numdrives] = {0}; bool dir[numdrives] = {false}; /* Pulse frequency */ uint16_t counter[numdrives] = {0}; uint16_t countto[numdrives] = {0}; bool pulseState[numdrives] = {false}; bool currState[numdrives] = {false}; void blinkLED(void) { for (uint8_t drive = 0; drive<numdrives; drive++) { if (countto[drive]>0) { if (counter[drive]>=countto[drive]) { counter[drive] = 0; pulseState[drive] = !pulseState[drive]; } else { counter[drive]++; } if (pulseState[drive]!=currState[drive]) { if (pulseState[drive]) { PORTB &= ~(1<<(pin_step[drive]-8)); pos[drive]++; if (pos[drive]>maxpos[drive]) { pos[drive]=0; dir[drive] = !dir[drive]; if (dir[drive]) { if (drive==0) { PORTD |= (1<<6); } else if (drive==1) { PORTD |= (1<<7); } else if (drive==2) { PORTD |= (1<<3); } else if (drive==3) { PORTD |= (1<<5); } } else { if (drive==0) { PORTD &= ~(1<<6); } else if (drive==1) { PORTD &= ~(1<<7); } else if (drive==2) { PORTD &= ~(1<<3); } else if (drive==3) { PORTD &= ~(1<<5); } } } } else { PORTB |= (1<<(pin_step[drive]-8)); } //digitalWrite(pin_step[drive], pulseState[drive]); currState[drive] = pulseState[drive]; } } else { PORTB |= (1<<(pin_step[drive]-8)); } } } void initDrives(void) { for (uint8_t drive = 0; drive<numdrives; drive++) { digitalWrite(pin_enable[drive],false); digitalWrite(pin_dir[drive], HIGH); for (uint32_t i=0; i<maxpos[drive]*2; i++) { digitalWrite(pin_step[drive], HIGH); delay(5); digitalWrite(pin_step[drive], LOW); delay(5); } digitalWrite(pin_dir[drive], LOW); for (uint32_t i=0; i<maxpos[drive]/2; i++) { digitalWrite(pin_step[drive], HIGH); delay(5); digitalWrite(pin_step[drive], LOW); delay(5); } pos[drive] = maxpos[drive]/2; digitalWrite(pin_enable[drive],true); delay(1000); } } void setup() { Serial.begin(31250); pinMode(pin_rotate, OUTPUT); for (uint8_t drive = 0; drive<numdrives; drive++) { pinMode(pin_step[drive], OUTPUT); pinMode(pin_dir[drive], OUTPUT); pinMode(pin_enable[drive], OUTPUT); digitalWrite(pin_enable[drive],true); } digitalWrite(pin_rotate,true); initDrives(); rotateSet(false); Timer1.initialize(50); Timer1.attachInterrupt(blinkLED); } void setFreq(uint8_t drive, uint16_t freq) { if (freq>0) { countto[drive] = (maxfreq)/(freq); digitalWrite(pin_enable[drive],false); } else { countto[drive] = 0; digitalWrite(pin_enable[drive],true); } } void rotateSet(bool state) { digitalWrite(pin_rotate,state); } #define NOTE_ON 0x90 #define NOTE_OFF 0x80 uint8_t midi_note[numdrives] = {0}; uint8_t midi_velocity[numdrives] = {0}; const uint16_t note_to_freq_lot[89] = { NOTE_B0, NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1, NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1, NOTE_C2, NOTE_CS2, NOTE_D2, NOTE_DS2, NOTE_E2, NOTE_F2, NOTE_FS2, NOTE_G2, NOTE_GS2, NOTE_A2, NOTE_AS2, NOTE_B2, NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3, NOTE_FS3, NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3, NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4, NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5, NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6, NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7, NOTE_C8, NOTE_CS8, NOTE_D8, NOTE_DS8}; uint8_t lmms_workaround[2] = {0,0}; //channel, note bool lmms_justset = false; void loop() { if (Serial.available() > 0) { byte b = Serial.read(); if (!lmms_justset) { lmms_workaround[0] = 0; lmms_workaround[1] = 0; } else { lmms_justset = false; } for (uint8_t i = 0; i<numdrives; i++) { if (b==NOTE_ON+i) { while (Serial.available()<2); midi_note[i] = Serial.read(); midi_velocity[i] = Serial.read(); if ((midi_note[i]<22)||(midi_note[i]>(22+89))) { midi_note[i] = 0; midi_velocity[i] = 0; setFreq(i, 0); } else { setFreq(i, note_to_freq_lot[midi_note[i]-22]); } lmms_workaround[0] = i; lmms_workaround[1] = midi_note[i]; lmms_justset = true; break; } else if (b==NOTE_OFF+i) { while (Serial.available()<2); uint8_t c = Serial.read(); Serial.read(); if (!((lmms_workaround[0]==i)&&(lmms_workaround[1]==c))) { if (midi_note[i]==c) { midi_note[i] = 0; midi_velocity[i] = 0; setFreq(i, 0); } } lmms_workaround[0] = 0; lmms_workaround[1] = 0; break; } } } }