Improved Randomization for the Party Button

After awhile in use, a problem came up with the Party Button: some song were being played much more often than others. One time, a song was played 4 times in a row, which was quite unsatisfying. Obviously, something was not quite right with the random number generator.

Turns out that analogRead(0) is not a very good source of entropy, as also others have noted. Using analogRead(0) often returns the same value, and using that as a seed for the pseudo-random number algorithm results in the same “random” sequence of songs being played.

In order to improve this, the new code below uses the time the button is being pressed in microseconds as the source of true random numbers. This worked out well. However, I still wanted it less probable that a song would be played twice in a row, since even with a perfectly random selection, the chance of this happening would be 1/n (n being the number of different songs). Thus, the code below “shuffles” the songs in the playlist, i.e., each song is played once, but in a random order. This drops the chance of 2 consecutive same songs to 1/n^2 (1/n for being at the end of the list and 1/n for the next song being the same as last). With my current 8 songs on the playlist, this happens about 1.5 % of the time, which is a probability I’m happy with.

#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"
#include <LowPower.h>
#include <EEPROM.h>


#define wakePin 2
#define ledPinPWM 5

int state_counter= 0;
int sleepState=0;
bool has_seed=false;
unsigned long ref_time=0;
unsigned long time_seed=0;
int number_of_songs=8;

SoftwareSerial mySoftwareSerial(4, 3); // RX, TX
DFRobotDFPlayerMini myDFPlayer;

void wakeUp(){ // here the interrupt is handled after wakeup 
}

// Shuffle the playlist: generate a random order for the songs and store it
// to EEPROM. The 0th address tells the address for the next song index.
void initializeSongs(){
  randomSeed(time_seed); // true random number generated from a button press
  Serial.print("init songs, new seed set: ");
  Serial.println(time_seed);
  
  int songs[number_of_songs];
  for(int i=0; i<number_of_songs; i++)
    songs[i]=i+1;
  for(int i=0; i < number_of_songs - 1; i++){
    int index = random(i,number_of_songs);
    int temp = songs[index];
    songs[index] = songs[i];
    songs[i] = temp;
    Serial.print(temp);  
  }
  Serial.print(songs[number_of_songs-1]);
  Serial.println("");

  EEPROM.write(0,1);
  for(int i=0; i < number_of_songs; i++)
    EEPROM.write(i+1,songs[i]); 
}

// Read the next song number from EEPROM. If it contains an invalid
// number, (re)initialize the playlist, i.e., shuffle.
int getNextSong(){
  Serial.print("getnextsong: ");
  int val=(int)EEPROM.read(0);
  Serial.print(val);
  if(val<1 || val > number_of_songs){
    initializeSongs();
    val=1;
  }
  int val2=(int)EEPROM.read(val);
  Serial.print(" ");
  Serial.print(val2);
  Serial.println("");
  if(val2<1 || val2>number_of_songs){
    initializeSongs();
    val2=getNextSong();
  }
  EEPROM.write(0,val+1);
  return val2;
}

void setup() {
  Serial.begin(57600);
  
  mySoftwareSerial.begin(9600);
  if (!myDFPlayer.begin(mySoftwareSerial))
    Serial.println(F("PLAYER Unable to begin"));  
 
  pinMode(wakePin, INPUT_PULLUP);
  pinMode(ledPinPWM, OUTPUT); 
}


void loop() {
  
  if(sleepState == 0){ // sleep.
    Serial.println("Going to sleep. Sleepstate -->0");
    attachInterrupt(0, wakeUp, LOW);  
    analogWrite(ledPinPWM,0);
                                        
    state_counter=0;
    LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);   //enter sleep mode
    detachInterrupt(0);                      //resumes from after wake-up 
    Serial.println("Woke Up! Sleepstate 0-->1");

    ref_time=micros(); // microseconds after program start
    sleepState=1;   
  }
  else if(sleepState==1){ // button is down
    int val = digitalRead(wakePin);
    if(val == HIGH){
      Serial.println("Button Up! Sleepstate 1-->2");

      // record the random number here in case we need it
      time_seed=micros()-ref_time; 
            
      int song=getNextSong();
      Serial.print("Song: ");
      Serial.println(song);    
      myDFPlayer.volume(27);
      myDFPlayer.play(song);
      sleepState=2;
    }
  }
  else if(sleepState==2){ //button is up again, song playing; flash led
    int blink_time=100*120; //2mins; each loop here is 10ms
    if(state_counter>blink_time)
      sleepState=0;  

    // Produce a triangle wave for PWM led flashing
    int period=400; //4000ms 
    int led_mod=state_counter % period;
    if(led_mod>(period/2)) led_mod=period-led_mod;
    int upper_limit=(period/2)*0.95;
    int lower_limit=(period/2)*0.05;
    led_mod=max(lower_limit,led_mod);
    led_mod=min(upper_limit,led_mod);
    int led_power=map(led_mod, lower_limit, upper_limit, 0, 255);
 
    analogWrite(ledPinPWM,led_power);        

    delay(10);
    state_counter+=1; 
  }
}

 

 

You May Also Like

Leave a Reply

Your email address will not be published.

Note: comments are moderated.