Music Controller

Design Challenge: Make a device to control playback of pre-recorded music.

Parameters:

  • Make a device that sends asynchronous serial messages to another device which will play the music.
  • All serial messages should be terminated by a linefeed.
  • The playback device will echo your message back to you as an acknowledgement message when it executes your command.

The device supports the following features:

  • It is operable by a user who cannot see the device.
  • The user gets an acknowledgement when they activate any control.
  • Start or stop the playback of a track (start plays track from the beginning).
  • Pause or resume playback of a track (resume plays track from last position stopped)
  • Skip ahead one track in the playlist.
  • Skip back one track in the playlist.
  • Fast-forward the current track (double speed of the track).
  • Skip to a random track in the playlist.

Tools used:

  • Arduino IDE
  • Arduino MKR1000
  • Python server
  • P5.js
  • P5 Serial Control App
  • Buttons (momentary and )
  • Vibration Sensor
  • Frosted plastic container
 
Arduino and Physical Components
 

I started this assignment by wiring my components to the breadboard and Arduino. As shown below, I wired the 2 toggle buttons, 4 momentary buttons and the vibration sensor. 

Arduino Code:

This proved to be quite tricky for me initially. My goal was to create the code from scratch and not rely on references. I encountered errors with the switch state where the serial monitor was just writing the value continually. Adding a delay did not help. I eventually discovered that my syntax was wrong (odd that the IDE did not pick up on this when I verified the code.) The culprit was a semi colon.

 
 My code:
 

const int startStop = 11;
const int pausePlay = 10;
const int next = 9;
const int back = 8;
const int randomSong = 7;
const int fast = 6;

int vibeSensor = 5;

int lastStartStopState = LOW;
int lastPausePlayState = HIGH;
int lastNextState = LOW;
int lastBackState = LOW;
int lastRandomState = LOW;
int lastFastState = LOW;

void setup() {
Serial.begin(9600);

pinMode(startStop, INPUT_PULLUP);
pinMode(pausePlay, INPUT_PULLUP);
pinMode(next, INPUT_PULLUP);
pinMode(back, INPUT_PULLUP);
pinMode(randomSong, INPUT_PULLUP);
pinMode(fast, INPUT_PULLUP);
pinMode(vibeSensor, OUTPUT);
}

void loop() {
int buttonState1 = digitalRead(startStop);
int buttonState2 = digitalRead(pausePlay);
int buttonState3 = digitalRead(next);
int buttonState4 = digitalRead(back);
int buttonState5 = digitalRead(randomSong);
int buttonState6 = digitalRead(fast);


// 1. Start Stop Button
// If the button is on, send a message to serial to start playing the song
if (buttonState1 != lastStartStopState) {
if (buttonState1 == HIGH) {
Serial.println(“start”);
Serial.write(49);

analogWrite(vibeSensor, 255); // give feedback
analogWrite(vibeSensor, 0);
delay(100);

// If the button is off, send a message to serial to stop playing the song
} else {
Serial.println(“stop”);
Serial.write(49);
//
analogWrite(vibeSensor, 255); // give feedback
analogWrite(vibeSensor, 0);
delay(100);
}
lastStartStopState = buttonState1;
}

// 2. Pause Play Button
//If the button is on, send a message to serial to pause the song
if (buttonState2 != lastPausePlayState) {
if (buttonState2 == LOW) {

Serial.println(“pause”);
Serial.write(50);
delay(500);

analogWrite(vibeSensor, 255); // give feedback
delay(100);
analogWrite(vibeSensor, 0);

// If the button is off, send a message to serial to play the song
} else {
Serial.println(“play”);
Serial.write(50);

analogWrite(vibeSensor, 255); // give feedback
delay(100);
analogWrite(vibeSensor, 0);
}
}
lastPausePlayState = buttonState2;

//3. Next Button (skip to the next song)
// If the button is pressed, send a message to serial to skip forward to the next song

if (buttonState3 != lastNextState) {
if (buttonState3 == LOW) {

Serial.println(“next”);
Serial.write(51);
// delay(500);

analogWrite(vibeSensor, 255); // give feedback
delay(100);
analogWrite(vibeSensor, 0);
}
lastNextState = buttonState3;
}

// 4. Back Button (go back and play the previous song)
// If the button is on, send a message to serial to skip to the previous song

if (buttonState4 != lastBackState) {
if (buttonState4 == LOW) {

Serial.println(“back”);
Serial.write(52);
// delay(500);

analogWrite(vibeSensor, 255); // give feedback
delay(100);
analogWrite(vibeSensor, 0);
}
lastBackState = buttonState4;
}

// 5. Random Button (play a random song)
// If the button is on, send a message to serial to skip to a random song

if (buttonState5 != lastRandomState) {
if (buttonState5 == LOW) {

Serial.println(“random”);
Serial.write(53);
// delay(500);

analogWrite(vibeSensor, 255); // give feedback
delay(100);
analogWrite(vibeSensor, 0);
}
lastRandomState = buttonState5;
}


// 6. Fast Button (fast forward through the current song)
// If the button is on, send a message to serial to fast forward through the current song

if (buttonState6 != lastFastState) {
if (buttonState6 == LOW) {

Serial.println(“fast”);
Serial.write(54);
// delay(500);

analogWrite(vibeSensor, 255); // give feedback
delay(100);
analogWrite(vibeSensor, 0);
}
lastFastState = buttonState6;
}

}

 
 
 

 
 

P5.js and Serial Communication

I haven’t touched p5 since ICM. However, setting up the serial communication and updating the sample p5 code was the most straightforward part of this assignment. 

Using this serial input to P5 tutorial, I installed the P5.serialcontrol app and the p5.serialserver. With the arduino connected to the port, I used the serial control app to ensure the port was available. Then using the command line, opened a python server the tested my controller. 

P5 code: 

var serial;
var portName = ‘/dev/cu.usbmodem1411’;
var inData;

var song; // the sound file to be played

// the list of songs:
var songs = [‘FastWine.mp3’, ‘FullExtreme.mp3’, ‘Incredible.mp3’, ‘LeaveMeAlone.mp3’, ‘TurnUp.mp3’];

var songCount = songs.length; // number of songs in the music dir
var currentSong = 0; // current song number

function preload() { // load the first song on preload
song = loadSound(‘music/’ + songs[currentSong]);
}

function setup() {
createCanvas(400, 300);

serial = new p5.SerialPort(); // make a new instance of the serialport library
serial.on(‘connected’, serverConnected); // callback for connecting to the server
serial.on(‘open’, portOpen); // callback for the port opening
serial.on(‘data’, serialEvent); // callback for when new data arrives
serial.on(‘error’, serialError); // callback for errors
serial.on(‘close’, portClose); // callback for the port closing

serial.list(); // list the serial ports
serial.open(portName); // open a serial port
}

function serverConnected() {
println(“We are connected!”);
}

function portOpen() {
println(“Serial Port is open!”);
}

function serialEvent() {
inData = Number(serial.read());
controlSound(inData);
console.log(inData)
}

function serialError(err) {
println(‘Something went wrong with the serial port ‘ + err);
}

function portClose() {
println(‘The serial port is closed’);
}

function draw() {
background(30, 20, 180);
fill(255);
text(“sensor value: ” + inData, 30, 30);
// draw the song’s name and current time in seconds:
text(songs[currentSong], 20, 50);
text(song.currentTime().toFixed(3), 20, 100);
}

function controlSound(input) {
switch (input) {
case 49: // start/stop, press 1
if (song.isPlaying()) {
song.stop();
} else {
song.play();
}
break;
case 50: // play/pause, press 2
if (song.isPlaying()) {
song.pause();
} else {
song.play();
}
break;
case 51: // skip ahead, press 3
// make sure the song number is valid, and increment:
if (currentSong < songs.length - 1) {
currentSong++;
} else {
currentSong = 0;
}
// get new song:
getSong(currentSong);
break;
case 52: // skip back, press 4
// in the first second, just rewind the current track:
if (song.currentTime() > 1.0) {
song.jump(0);
// if more than a second has elapsed, then
// make sure the song number is valid, and decrement:
} else {
if (currentSong > 0) {
currentSong–;
} else {
currentSong = songs.length – 1;
}
// get new song:
getSong(currentSong);
}
break;
case 53: // fast forward, press 5
song.rate(2.0); // double the play speed
if (!song.isPlaying()) {
song.play();
}
break;
case 54: // random song, press 6
currentSong = Math.round(random(songCount)); // get a new song number
getSong(currentSong); // play it
break;
}
}

function getSong(songNumber) {
if (songNumber < songs.length) { // if the song number is in range
if (song.isPlaying()) {
song.stop();
}
// load a new song:
song = loadSound(‘music/’ + songs[currentSong], resumePlay);
return true;
} else { // if the song number was out of range, return false
return false;
}
}

function resumePlay() {
// if the song isn’t playing, play it
if (song.isPlaying()) {
song.stop();
} else {
song.play();
}
}

function keyReleased() {
controlSound(keyCode); // send the ASCII number of the key
}

 

Fabrication

I wanted the feel of a walkman so I purchased a small plastic container that could be held with one hand. I drilled holes for my 6 buttons and for the power cable. Then carefully installed my buttons and MKR1000. 

Experience new music

So, that was soca. In case you were wondering what I was playing. Take a listen to the tracks on my playlist, below.

Leave a Reply

Your email address will not be published.