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: