The dream machine

      No Comments on The dream machine

My More Significant Other (MSO) sometimes has trouble sleeping. She’ll wake in the night and can’t drift off again.

So she bought a hypnotherapy recording and a headband/blindfold thing with built-in headphones. And she played the recording on her iPod Nano that some fantastically thoughtful husband once bought her.

There was a problem with that set-up, though. Getting the iPod to play involved flipping open its case, hitting a button to wake it up and checking that the right track is selected — and selecting it if not. For some bizarre reason, if she didn’t check carefully, she’d get ‘Christmas Wrapping’ by The Waitresses instead of the hypnotherapy session. All that effort, simple as it sounds, is enough to wake her up rather than putting her to sleep (and possibly make her think it’s December again). Worse, the brightness of the iPod’s screen in the darkened room was like someone flashing a torch in your eyes. Not conducive to rest.

Some time ago, I built a dawn clock based on an Arduino Uno. It was the precursor to my later (and hugely successful, if you ask me) Raspberry Pi-based device. I used an Adafruit audio shield on the Uno to play WAV tracks from an SD card. That got me thinking. What if I could build a device that had just one button to press and played only one track?

Behold, the Dream Machine.

An AVR-based microprocessor board was obviously the way to go. I’m a big fan of the Teensy, because it’s small but relatively powerful and can be programmed using the Arduino IDE (although I’m tiring of that, frankly). So I thought, ‘I wonder if the Teensy guys do some kind of audio board’. And yes, my wishes were granted. The audio adapter not only handles all the audio stuff, and attaches directly to the Teensy, but it also has a built-in micro-SD socket. Perfect.

I ordered two. I always order two of everything, because then there’s less time to wait and less postage to pay when you let the magic smoke out of the first one.

I should say right up-front that my Dream Machine barely begins to scratch the surface of what this audio board is capable of. More on that later.

Soldering the Teensy on to the audio board didn’t give me a complete project. I needed two other things — a volume control and a button to switch on the recording. I also had to work out a bit of logic.

The idea is that the MSO, having woken in the night, can grope for the Dream Machine, locate the button by touch and just press it. The recording then plays. Pressing the button again would stop the recording, but this is unlikely to happen often. In normal use, the MSO would fall asleep while the track is playing. That meant the device needed to know when the track had finished.

I decided to use a momentary button with a built-in red annular LED. The button sits relatively flush to the project box, and the pressy bit (technical term) is slightly recessed. That means Trish can tuck the box under her pillow with no danger of accidental button presses.

The LED comes on to confirm that the button had been pressed. It would go off if the button was pressed again to stop the recording, or automatically when the recording finishes playing. But the brightness of the LED had to be very, very dim. This meant putting lots of resistance in series with the LED circuit. How much resistance? That was hard to tell. What might look like the right level of dimness in my office could appear like a burning sun to the MSO in the middle of the night. Obviously, I needed a potentiometer.

I also decided to include a volume control. A momentary on-off-on rocker switch seemed to fit the bill. I ordered it online and didn’t pay enough attention to the size. I could just fit it into the project box. It’s also designed for mains voltages. (I haven’t yet got the hang of specifying stuff.) On the plus side, it’s so heavy-duty that it takes a determined press to actuate the switches. That makes it difficult to do accidentally.

The whole thing is powered via the Teensy’s micro-USB socket. It just so happens that we have a micro-USB power cable permanently installed on the bedhead, so that we can charge our Kindles while reading at night. What a leading-edge, technological couple we are. Both the micro-USB socket and the headphone socket sit at the same end of the project box, which is neat. (The same can’t be said of the holes I made to access them. Sorry, no pictures.)

Connecting up

So, where are we? I need to connect the button’s LED to ground and, via a potentiometer, to one of the Teensy’s pins, from which it draws its (very modest) power. The switch needs to be connected to two pins and to ground. And the main button also needs attaching to a pin and to ground. I’m using the Teensy’s built-in pull-up resistors for all the button/switch pins, so that high means off and low means on.

It made sense to me to connect the Teensy’s pins to a tiny daughterboard holding the pot and a common ground connection for all. As the main Teensy/audio board and the daughterboard would be attached to the base of the project box and the button and rocker switch to the top, all the wires from the latter two devices would attach to the daughterboard via header pins/connectors. That makes the top completely removable for maintenance.

I converted the audio track from M4A to WAV using … well, something I found on the interwebs … and copied the file to the micro-SD. I set the pot so that the LED is barely visible, but still offers confirmation that the device is playing. And I hacked out a little code, with the audio defaulting to something I thought would be appropriate. This is the code – I hope the comments in it are enough explanation:

#include <Bounce.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

AudioPlaySdWav           wavePlayer;  // All this stuff is provided by the Teensy
AudioOutputI2S           i2s1;        // audio library. It's very clever.
AudioConnection          patchCord1(wavePlayer, 0, i2s1, 0);  // I don't fully
AudioConnection          patchCord2(wavePlayer, 1, i2s1, 1);  // understand it yet.
AudioControlSGTL5000     audioController;                     // But it works.

#define VOL_STEP_UP 0.01     // increments for volume changes per switch press
#define VOL_STEP_DN -0.01
#define MAX_VOLUME 0.65

#define LED_PIN 3
#define BUTTON_PIN 2
#define VOL_UP_PIN 0
#define VOL_DN_PIN 1

#define BUTTON_PRESSED 0    // these just make the code more readable
#define BUTTON_RELEASED 1
#define YES 1

float volume = 0.4;                           // volume level
Bounce mainButton = Bounce(BUTTON_PIN, 5);    // Using the classic Bounce library
Bounce volUpButton = Bounce(VOL_UP_PIN, 5);   // to manage button/switch presses
Bounce volDnButton = Bounce(VOL_DN_PIN, 5);
bool startRequired = false;                   // has playing of audio been requested?
bool playAudio = false;
uint8_t buttonState;
uint8_t buttonChanged;

// ******************************************************************************************
// ***   FUNCTIONS                                                                        ***
// ******************************************************************************************

void changeVolume(float stepChange) {
  // make sure volume never goes above maximum. It's not nice
  volume = constrain(volume + stepChange, 0.0, MAX_VOLUME);
  audioController.volume(volume);
  delay(200); // don't allow changes to happen too rapidly if button still pressed
}

void flashLED (uint8_t repeats = 5) {
  for(int i=0; i<repeats; i++) { 
      digitalWrite(LED_PIN, HIGH);
      delay(100);
      digitalWrite(LED_PIN, LOW);
      delay(100); 
  }
}

// *** SETUP *** //
void setup() { 
  pinMode(LED_PIN, OUTPUT);          // drive the LED from a pin
  pinMode(BUTTON_PIN, INPUT_PULLUP); // button and switch pins as inputs with
  pinMode(VOL_UP_PIN, INPUT_PULLUP); // internal pull-ups
  pinMode(VOL_DN_PIN, INPUT_PULLUP); 
  delay(500);                        // allow pullups to get ready 
  // --- INITIAL BUTTON STATE --- 
  buttonChanged = mainButton.update(); // read the button once to update internal property 
  buttonChanged = 0; // then set to 0 anyway to stop audio from playing straight away 
  // --- AUDIO SETUP --- 
  AudioMemory(8); 
  audioController.enable(); 
  audioController.volume(volume); // set default volume
  SPI.setMOSI(7); 
  SPI.setSCK(14); 
  if (!(SD.begin(10))) { 
    while (1) {       // Unable to access the SD card 
      flashLED(250);  // flash forever in abject panic
    } 
  } 
  delay(1000); 
  flashLED(2); // confirm powered up & working
}

// *** MAIN LOOP *** //
void loop() { 
  if(wavePlayer.isPlaying() == true) { // already playing 
    startRequired = false;             // don't want to start again 
    if (!playAudio) {                  // but we've set PlayAudio state to false 
      wavePlayer.stop();               // so stop 
      digitalWrite(LED_PIN, LOW);      // & turn off LED 
      delay(1000);                     // allow time for state to change 
    } 
  } else {                             // Not currently playing 
    digitalWrite(LED_PIN, LOW);        // ensure light is off
    if ( startRequired ) {             // but a start has been requested
      startRequired = false;           // now we're here, this can be reset
      digitalWrite(LED_PIN, HIGH);     // light on to confirm
      wavePlayer.play("DRM.WAV");      // start WAV file playing
      delay(1000);                     // allow it to get started otherwise .isPlaying() 
                                       // won't have the right value the next time
                                       // through the loop
    } else {
      playAudio = false;    // no start requested, to ensure playAudio state is false
    }
  }
  buttonChanged = mainButton.update(); // refresh button state
  buttonState = mainButton.read();     // and gets its value
  if (buttonChanged == YES && buttonState == BUTTON_RELEASED) { // button has been pressed & released
    playAudio = !playAudio;            // toggle playAudio state
    if (playAudio && !wavePlayer.isPlaying()) {
      startRequired = true;            // we want audio to play but it isn't currently
    }
  }
  
  buttonChanged = volUpButton.update();
  if (buttonChanged == YES) {
    buttonState = volUpButton.read();
    while (buttonState == BUTTON_PRESSED) { // button has been pressed & released
      changeVolume(VOL_STEP_UP);
      buttonChanged = volUpButton.update();
      buttonState = volUpButton.read();
    }
  }

  buttonChanged = volDnButton.update();
  if (buttonChanged == YES) {
    buttonState = volDnButton.read();
    while (buttonState == BUTTON_PRESSED) { // button has been pressed & released
      changeVolume(VOL_STEP_DN);
      buttonChanged = volDnButton.update();
      buttonState = volDnButton.read();
    }
  }

}

Pretty, isn’t it? And it works. At least, I assume it works because if it didn’t I’d damn sure have heard about it by now. Sweet dreams…

Oh, and before I forget, I mentioned that the Teensy audio board is capable of doing a lot more. Don’t believe me? Check out this video made by its creator, Paul Stoffregen:

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *