Gobetwino Music Server for iTunes

Building a music server the hard way having fun with Arduino.
Using Arduino, LCD screen and SC-card reader to start a selected album in iTunes on a pc.

First step is to control iTunes with Arduino. I tried to use Gobetwino but this doesn't always make the right selections. I ended up using AAC-Keys to translate serial data to key presses and Auto Hot Key (AHK) to control iTunes.
The most difficult part was to select the iTunes search field.

Second step is to make al list of albums. In iTunes export a playlist to Excel. In Excel remove all info except artist and album name. Save as csv-file. Rename to txt-file and copy to SD-card. This creates a list for Arduino to read like:

Giant Sand;Selections Circa 1990 - 2000
Joan Baez;Play Me Backwards
Room Eleven;Mmm... Gumbo?
Nina Simone;The Very Best Of Nina Simone
The Walkabouts;Nighttown
Calexico;The Black Light

Third step is to make Arduino scroll up and down this list. To make Arduino read this list all at once is explained in the SD-card examples.
The scolling function is based on line feeds. During setup al line feed positions are stored in an array. Now we know the starting points of all next lines (albums). The ";" is used to display the album title on the 2nd row.
Setup ends with displaying the first album on the LCD screen. I use a Nuelectronics LCD screen with buttons like this:

Use up and down buttons to display the next or previous album on the LCD screen (see next()). Pushing key 0 sends an AHK command to the pc, adds the title of the selected album and starts the first song in iTunes (see start()).

All works fine on my laptop for now:

Next is to set up a second hand pc as iTunes server with digital out (toslink or coax). This I will connect through a dac to my stereo. I'd like to convert an old AKAI digital tuner into the music server by exchanging the screen with the Arduino LCD screen. But this will take some time.

The code works well and certainly fast enough. Improvements and suggestions on how to use a NAS instead of a pc are welcome.

#include <SD.h>
#include <PString.h>
#include <LCD4Bit_mod.h> 

LCD4Bit_mod lcd = LCD4Bit_mod(2); 
int keyarray[5] = {30, 150, 360, 535, 760 }; //see LCD button shield
int NUM_KEYS = 5;
int keypad;
int key = -1;
int oldkey = -1;

const int chipSelect = 10;
File dataFile;
char x;
int myLines[24];  // array of 24 albums based on line feed positions
int n = 0;        // album counter
int pos = 0;      // album position variable
char album[60];   // "artist name;album name" variable
PString myPstring(album, sizeof(album)); // to built a string out of characters


void setup(){
  Serial.begin(9600);
  pinMode(10, OUTPUT);     
  SD.begin(chipSelect);
  dataFile = SD.open("SAVONDS2.TXT"); 
  if (dataFile) {   
    while (dataFile.available()) {
      x = dataFile.read();         //characters read one by one
      if (x == 10){                //line feed is 10
        myLines[n] = dataFile.position(); //array of line feed positions
        n++;                       
    } }
    dataFile.close();
    myLines[23] = 0;               //change last pos into first album position
    n = 23;
  } 
  lcd.init();
  next();                          //print first album to LCD
  delay(200);
}  

void loop(){
  keypad = analogRead(0);          //detect key press
  key = getkey(keypad);	 
  if (key != oldkey){		 
    delay(25);		        
    keypad = analogRead(0);   
    key = getkey(keypad);  
    if (key != oldkey){
      oldkey = key; 
      if (key >=0){buttons();}    // LCD shield with 5 buttons   
    }
  }
  delay(25);
}

void buttons(){               
  if (key == 0){start();}         // itunes starts displayed album
  if (key == 1){n=n-1; next();}   // display next album
  if (key == 2){n=n+1; next();}   // display previous album
  if (key == 3){Serial.println("\033,hold,alt.s");}  // AHK start/pause itunes
  if (key == 4){Serial.println("\033,hold,alt.s");}
  delay(100);
}

void next(){        //read artist and album name (n) from SD-card
  if(n<0){n=23;}    //scroll from first to last album
  if(n>23){n=0;}    //scroll from last to first album
  pos = myLines[n]; //position of line feed preceding ablum (n)
  myPstring = "";
  dataFile = SD.open("SAVONDS2.TXT"); 
  if (dataFile) {  
    lcd.clear(); 
    lcd.cursorTo(1, 0);           //artist name on first row
    dataFile.seek(pos);           //position to read
    int q = 0;   
    while (dataFile.available()) {
      x=dataFile.read();          //read next character 'x'
      q++;                        //character counter
      if (x == ';'){
        lcd.cursorTo(2, 0);       //album name on second row
        q=0;                      //reset character counter for next row
        myPstring.print(' ');}    //space between artist and album
      else{
        if (x != 10 && x != 13){
          if(q<17){lcd.print(x);} //send 'x' to LCD screen
          myPstring.print(x);    //add 'x' to album
        }
        else{
          dataFile.close();       //stop while loop
        }                         //myPstring contains artist and album name
      }
    }
  }
}

void start(){
  Serial.println("\033,hold,alt.z"); //AHK open itunes search field
  delay(500);                        //wait for blank search field
  Serial.println(album);             //copy album from myPstring
  delay(1500);                       //wait for itunes
  Serial.println("\033,hold,alt.s"); //itunes start album
}

int getkey(unsigned int input){
  int k;
  for (k=0; k<NUM_KEYS; k++){
    if (input < keyarray[k]){return k;}
  }
  if (k >= NUM_KEYS){k = -1; return k;}
}

AHK script:

; based on code snippets from the iTunes Anywhere topic
; thanks to daorc for activating the search control 
; thanks to brohan for ComObjCreate function

#SingleInstance, Force

itunes := ComObjCreate("iTunes.Application")

ControlSend, ahk_parent,{Tab}, iTunes ahk_class iTunes
ControlSend, ahk_parent,{Tab}, iTunes ahk_class iTunes
DetectHiddenWindows, On

!s::
itunes.PlayPause()
return

!z::
IfWinExist,ahk_class iTunes
   {
   itunes.Pause()
   winset,top
   winactivate
   controlclick,Edit1,iTunes ahk_class iTunes ;activate the "search" control
   controlfocus,Edit1,iTunes ahk_class iTunes ;activate the "search" control
   ControlSend, ahk_parent,{Esc}, iTunes ahk_class iTunes
   }
return

great project I will try your code :slight_smile:

Using Gobetwino it is possible to read a file with album names from the computer and start and control iTunes. So the SD-card is not needed anymore.

An old AKAI digital tuner is used as housing. Behind the dial is a 10-turns potentiometer. Turning the dial one of a hundred albums can be selected.

The arduino is placed in the old digital tuner. The buttons are wired. The tuning knob is used te scroll through the albums. Gobetwino is used to read the album names from a file on the pc. The button next to the knob is used to start playing the selected album. Gobetwino sends the artist and album name to the iTunes search box and starts the album. Pressing the button again, without changing an album, functions as pause/start for the current song. The button left of the screen shuts down iTunes.

The pc is an Asus Cube with an digital coax out for the highest sound quality.

Future enhancements:

  • remote control (IR)
  • playlists select function (small lists in favor of one big list)
  • relocate the on/off function of the pc to the tuner
  • move the pc system into the tuner housing
  • move the cd's to the attic

With some heavy tuning of the code the Music Server now functions well. I used a lot off sub/sub routines because Gobetwino seems to be responding quicker that way. On the PC-side Gobetwino can activate shutdown.exe now. The main power button (wired to Arduino) now shuts down the PC. I moved the PC to the cellar and used a long USB cable to connect to the music server. The music is send wirelessly to an Apple Airport Express.

#include <PString.h>
#include <LCD4Bit_mod.h> 

LCD4Bit_mod lcd = LCD4Bit_mod(2); 
int iAnVal = A2; int val; int oldval = 1050;
int pot; int oldpot = -1; 
int button1; 
int button2; int oldbutton2 = LOW; 
int buttonPin1 = 13; int buttonPin2 = 12; 
int state = 1;
int n;                                      // number of albums
int p;                                      // album position (2 tot n+1)
int y = 0;                                  // playlists no. 0 to 3
int steps = 80;                             // potmeter instances
char* myPlaylists[4]= {"SAVONDS","STEVIG","DIVERSEN","KLASSIEK"};
char x; char buffer[5]; char album[33];     // "artist name;album name" variable
PString myPstring(album, sizeof(album));    // put characters into a string 
boolean newalbum = false;
boolean play = false;

void setup(){
  Serial.begin(9600);
  pinMode(buttonPin1, INPUT);
  lcd.init(); lcd.cursorTo(1, 0);
  lcd.printIn("Start");
  starttunes();             
}  

void starttunes(){                            
  readline(1);
  while(!Serial.available()){lcd.printIn("Waiting ..."); delay(3000); readline(1);}
  number();            delay(100);
  tunes();             delay(5000);      
}

void number(){
  myPstring = "";
  while(Serial.available()){
    x = Serial.read();                   //read character from Gobetwino
    if(x!=10 && x!=13 && x!=48 && x!=59){
      myPstring.print(x);}}              //add character to string(album)
  delay(100);
  n = atoi(album);                       //string to int (first line states the number of albums)
  lcd.cursorTo(2, 0);
  lcd.printIn(itoa((n), buffer, 10));    //display number of albums on LCD
}

void tunes(){
  Serial.println("#S|TUNES|[]#");        //start iTunes (Gobetwino command)
  delay(200);
}

void loop(){                            
  button1 = digitalRead(buttonPin1);        
  button2 = digitalRead(buttonPin2);          
  if(button2 != oldbutton2){                  //select playlist
    if(button2 == HIGH){selectlist();}
    if(button2 == LOW){selectalbum();}
    oldbutton2 = button2; return;}
  if(button2 == LOW && button1 == HIGH){      //button1 pressed
    if(newalbum == true){start(); return;}    //start selected album
    else{
      Serial.println("#S|SENDK|[0& ]#");
      play = !play; 
      delay(1000); return;}} 
  val = analogRead(iAnVal);                   //read potentiometer
  if(val>oldval-5 && val<oldval+5){delay(100); return;}
  delay(10);
  val = analogRead(iAnVal);                 //read potentiometer again
  if(val>oldval-5 && val<oldval+5){delay(100); return;}
  oldval = val;
  pot = map(val, 0, 1023, 0, steps);          //map number of steps 
  if(pot != oldpot){
    if(button2 == HIGH){selectlist();}
    if(button2 == LOW){selectalbum();}
    oldpot = pot;}
  else{delay(100);}    
}

void selectlist(){
  if(y == 3){y = -1;}
  y = y+1;
  readline(1);
  lcd.printIn(myPlaylists[y]);
  number();
}

void selectalbum(){
  if(pot == steps || pot == 0 || oldpot == -1){
    if(pot == steps){p = n+1;}
    if(pot == 0){p = 2;}
    if(oldpot == -1){p = pot;}}
  else{
    if(pot>oldpot){                     //rotate right
      int i = pot-oldpot;
      if(i < 3){p = p+i;} 
      else{p = p+5*(pot-oldpot);}}      //skip some albums
    if(pot<oldpot){                     //rotate left
      int i = oldpot-pot;
      if(i < 3){p = p-i;} 
      else{p = p-5*(oldpot-pot);}}}     //skip some albums          
  while (p > n+1){p = p-n;}           
  if(p<2){p = n+1;}
  newalbum = true;                      //wait for button1 to start album
  displayAlbum();  
}

void displayAlbum(){                    //state 2; read album p from file
  readline(p); 
  if(!Serial.available()){lcd.printIn("Time out");return;}
  myPstring = "";
  int q = 0; while (Serial.available() && q < 60){
    x = Serial.read(); q++;             //read character from Gobetwino
    if(x == ';'){                       //artist;album seperator
      lcd.cursorTo(2, 0); q = 0;        //album name on second row
      myPstring.print(' ');}            //reset character counter for next row
    if(x != ';' && x !=10 && x != 13 && x != 48 && q < 17){ //discard line feed etc.
      lcd.print(x);                     //send 'x' to LCD screen
      myPstring.print(x);}}             //add 'x' album 
  delay(50);
}

void readline(int r){                    //Gobetwino command
  lcd.clear(); lcd.cursorTo(1, 0); 
  int q = 0; while (Serial.available() && q < 200){
    x = Serial.read(); q++;}             //empty serial buffer
  Serial.print("#S|");
  Serial.print(myPlaylists[y]);
  Serial.print("|[");
  Serial.print(itoa((r), buffer, 10));   //read myPlaylist[y] line p 
  Serial.println("]#");                  //from gobetwino-defined file
  q = 0; while(!Serial.available() && q < 200){delay(10); q++;} // wait for serial return
}

void start(){                            //start selected album
  if(play == true){
    Serial.println("#S|SENDK|[0& ]#"); 
    play = false; delay(50);}
  Serial.println("#S|SENDK|[0&^i]#"); delay(50);
  Serial.println("#S|SENDK|[0&^i]#"); delay(50);
  Serial.println("#S|SENDK|[0&^i]#"); delay(50);
  Serial.print("#S|SENDK|[0&");
  Serial.print(album);                   //copy album from myPstring 
  Serial.println("]#");                  //to iTunes
  delay(1000);                           //wait for itunes 1800
  Serial.println("#S|SENDK|[0&{TAB}]#"); delay(50);
  Serial.println("#S|SENDK|[0& ]#");
  play = true; newalbum = false; 
  delay(100); 
}

Impressive job.

I would love to have such a Hifi system, but with the audio output, and that would dig the music data onto a local ethernet samba server.

I already found some equivalent equipments, but all of them are based on TV for the man machine interface.

My little heart gets all warm and tender when i see GoBetwino beeing used for cool projects :slight_smile:

MikMo:
My little heart gets all warm and tender when i see GoBetwino beeing used for cool projects :slight_smile:

Thanks MikMo!
You have written the only program I could work with that can read files on a PC.

Could not have done it without you! :wink: