While standing in line for the Jungle Cruise at Walt Disney World, there's an audio loop of a fictional radio station, the Jungle Cruise radio, hosted by Albert Awol. One day while standing in line, one of my friends and I came up with the idea to have a real radio which played the loop.
Over the past couple of months, I took an old 1930's Silvertone radio which was completely nonfunctional and re-fitted it with a raspberry pi and an arduino, making it fully functional as a fictional radio, able to play audio files in loops, shuffled stations, and even dynamically generated stations with DJ interludes, all with a persistance effect, to seem like these stations were genuine radio stations, playing whether you were tuned in or not.
The google photos album of the build is located here, but a brief summary of the technical details are listed here for ease of access.
The radio is powered by a 1st gen Raspberry Pi B+, with both a bluetooth (to allow use as a bluetooth speaker, done using instructions here) and a wifi dongle (used to connect wirelessly for development). It connects via USB/Serial to an Arduino Uno, which reads the status of both potentiometers (one for volume, one for tuning). The volume potentiometer is actually an original part from the radio, including a switch which allows turning on/off the radio when the dial is turned all the way to the left.
The raspberry pi has the Adafruit I2S 3W Stereo Speaker Bonnet as a DAC, since the onboard headphone jack isn't enough to power a full-size speaker. Using this, I attached the original speaker of the radio to the left audio channel and duplicated the right audio channel to that, down-mixing the stereo output of the dac to the original Mono of the radio.
The tuning mechanism on the original radio has a linear display, used to give an approximation of where you are tuned to. This is done via a string pulling that back and forth, then wrapped around a large tuning mechanism for the original electronics. To get this into a single 270° potentiometer I designed a 3D printed mount and wheel, which when connected via another string to the tuning wheel converts the position of the spectrum perfectly to the potentiometer.
On boot, the raspberry pi starts the script MagicRadio.py
in its directory. MagicRadio.py
sets up the program in the following order:
- Begins logging through
MRLogging.py
- Initializes the Serial connection to the raspberry pi with
SerialHandler.py
- Creates and runs the thread which parses and stores the input variables from the serial connection in
InputControl.py
- Initializes the playback loop through
PyGameHandler.py
- Creates the tuning spectrum from
FictionalTuner.py
and finally passes that back to thePyGameHandler.py
analogRead
in an arduino will put out a number ranging from 0 to 1023. As such, there are a possible 1024 positions on the tuning spectrum with a standard potentiometer. To create the effect of tuning a radio between different stations, the program builds a list 1024 elements long which stores both the station
the radio would be tuned to and the volume
of the station at each point.
A more in-depth explanation of the creation of the tuning spectrum is documented inside FictionalTuner.py
Stations are the objects which hold a list of tracks avaliable on each station, a list of their durations, and they also contain the logic used to determine which audio track should be playing. For some, like the base Station
object, it's simple, with one audio loop playing ad infinitum. The PickStations
and DynamicStations
are far more intensive in their logic, with DynamicStations being the most complicated of all.
Station | Behavior |
---|---|
Station |
Used as a fixed station, will only play one audio file in its directory and will be played in a loop. |
PickStation |
This is effectively a shuffled station. It picks at random the next track to be played, attempting not to play any of the last four tracks over again. |
DynamicStation |
This station type is used to create stations hosted by fictional DJs. Much like the radio stations in the Fallout games, it will play shuffled music with the same logic as a PickStation but will also play procedurally generated radio shows using rules and segments defined in a .dj file (formatted as json) inside the station folder. More information is included in the readme inside the example_stations/exampleDynamicStation folder |
Stations are generated at runtime according to their definitions in stations/stations.json see the readme inside the example_stations folder for more information.
The code for the arduino is located in a gist here.
The arduino uses smoothing to reduce the effect of electrical noise on both the volume and tuning potentiometers.
Communication between the raspberry pi and ardunio is done using a serial connection, printing each line in a format "tuning,volume,onoff"
See the InputControl.py
file for more information on parsing this.
This was written for python 2.7, which comes standard in raspbian. It only relies on two external modules to be installed.
Two folders must be created, stations
and logs
.
stations
will hold all stations you wish to play, defined in stations/stations.json
Please view the example files inside example_stations
, as well as the documentation for these files in their readme and the wiki.
MRGlobals.py
holds the path to your arduino's location. It currently holds mine. This probably will not be your arduino. Change serialPath
to match the serial path of your arduino.
MRGlobals.py
also holds a few options that are configurable.
clockSleep
is the time the program sleeps when updating the playback loop and inputcontrol loop. By default it's 1/60th of a second. Increase it for better control if you have better hardware.djIntersperse
is the time between DJ segments. This is useful for spacing out dj tracks to make it sound more natural. By default it's 1.32 seconds, which I've found to be the most accurate.logDeathInDays
is how old in days a log must be before it dies from the LogHistorian. The log files can get quite big, so it's best to delete old ones.volumeFloor
is the minimum volume int. Volume input from the arduino can have some electrical noise, so when at the lowest edge of the spectrum we lock it to a minimum to prevent it from jumping to silence at random. Configure this as you will.
If you wish for the MagicRadio to operate on boot of the raspberry pi without user interaction, here is a .service
file for linux/raspbian.
To install this, first move it to /lib/systemd/system/
, then run the following command
$ sudo systemctl enable magicradio.service
This will start MagicRadio.py
on boot.
Otherwise, just run it from the command line