Raspberry Pi Retrofits Vintage Radio for Streaming
2025-03-14 | By Travis Foss
License: See Original Project Amplifiers Barrel Audio Connectors Raspberry Pi SBC
Last summer I purchased an antique radio from a local auction sale for only a few dollars. The βradio is a Stewart Warner Model 205CK that I believe was made around 1941. After bringing home βthe radio and evaluating its restoration potential, I had an idea: what if I retrofit it with a Raspberry βPi to turn it into an internet radio? I had a few different Raspberry Pis sitting around unused, so I βthought this might be an interesting project. β
The first idea I had was to carefully remove the internal electronics from the original build and βthen see about placing the new electronics into the case. Prior to doing this, however, I βremembered I had a small amplifier board, 1738-DFR0675-EN-ND, that I could pull from another βproject to do some testing. The original speaker in the cabinet had an interesting set of βconnections that I hadnβt seen before, I believe they are just referred to as spring clips.β
These made it easy to evaluate to see if a small amplifier like this would provide enough power for βthe speaker. After plugging in the amplifier and powering it with a 12V battery, the sound was βpretty good, just not as loud as I thought it would be. To further investigate the project, I used my βmultimeter to measure the speaker's impedance. I found that it was reading approximately 7 βohms. While it may not be a perfect match for the amp, I believe it will likely work, as the amp has βspecifications for both 4-ohm and 8-ohm speaker configurations. During the amplifier test, I βnoticed how great the speaker sounded. The music, while not overly loud, filled the room with a βwarm and pleasant tone that was enjoyable to listen to. I was truly impressed by this.β
Now that I had the initial testing out of the way, I decided the next step was to figure out which βRaspberry Pi I wanted to use. I first started to think about the Raspberry Pi Zero 2 W that I hadnβt βused in a project yet. I decided to create some test code to see if I could make it work.β
While Iβve done some programming with a Raspberry Pi, I thought this project would be an βinteresting test case to see what using ChatGPT and Claude could do to help me speed up my βcoding time. I should state that I did use the free version of each of these LLMβs to create this βproject. β
To start, I went to Claudeβs website and posted the following Prompt.β
You are an experienced Python programmer that rarely makes mistakes. I am working on creating an antique radio into an internet radio using a Raspberry Pi zero 2w, an βPimoroni Audio DAC, and a Hi-Fi Bluetooth radio receiver from DFRobot. Can you help me with the βcode to get this up and running? I would like for it to be an internet radio along with an Airplay βreceiver as well.
In my time looking at various prompt engineering videos, Iβve heard from several people that if you βgive a role to the LLM, it will provide a better, more accurate output.β
For the initial test of the code, I decided to try to run the sound through my Bluetooth headphones βconnected to the Pi instead of the speaker. I figured this would be an easy way to test the code βprior to setting up all the hardware. β
After connecting my headphones to the Pi with Bluetooth, I copied and pasted the first batch of βcode that Claude sent over. β
import subprocess import os import signal import time from threading import Thread import vlc class VintageRadio:β β def init(self):β β # Initialize audio componentsβ β self.current_source = None β self.player = None β self.airplay_process = None β self.radio_stations = {β β '1': 'http://media-ice.musicradio.com/ClassicFMMP3',β β '2': 'http://icecast.vrtcdn.be/radio1-high.mp3',β β '3': 'http://stream.live.vc.bbcmedia.co.uk/bbc_radio_two'β β }β β β β # Set up audio output to Pimoroni DAC β os.environ['ALSA_CARD'] = 'sndrPihifiberry'β β β β def start_airplay(self):β β """Start Shairport-sync for AirPlay receiving"""β β if self.airplay_process is None:β β try:β β self.stop_radio()β β self.airplay_process = subprocess.Popen(β β ['shairport-sync', '-o', 'alsa', '--name', 'Vintage Radio'],β β stdout=subprocess.PIPE,β β stderr=subprocess.PIPE β )β β self.current_source = 'airplay'β β print("AirPlay receiver started")β β except Exception as e:β β print(f"Error starting AirPlay: {e}")β β β β def stop_airplay(self):β β """Stop AirPlay receiver"""β β if self.airplay_process:β β self.airplay_process.terminate()β β self.airplay_process = None β self.current_source = None β β β def play_radio(self, station_number):β β """Play internet radio station"""β β if station_number in self.radio_stations:β β try:β β self.stop_airplay()β β if self.player:β β self.player.stop()β β β β # Create a new VLC instanceβ β instance = vlc.Instance('--aout=alsa')β β self.player = instance.media_player_new()β β media = instance.media_new(self.radio_stations[station_number])β β self.player.set_media(media)β β self.player.play()β β self.current_source = 'radio'β β print(f"Playing radio station {station_number}")β β except Exception as e:β β print(f"Error playing radio: {e}")β β β β def stop_radio(self):β β """Stop radio playback"""β β if self.player:β β self.player.stop()β β self.player = None β self.current_source = None β β β def setup_bluetooth(self):β β """Initialize Bluetooth receiver"""β β try:β β # Enable Bluetooth audio sinkβ β subprocess.run(['sudo', 'bluetoothctl', 'discoverable', 'on'])β β subprocess.run(['sudo', 'bluetoothctl', 'pairable', 'on'])β β subprocess.run(['sudo', 'bluetoothctl', 'agent', 'NoInputNoOutput'])β β print("Bluetooth receiver ready")β β except Exception as e:β β print(f"Error setting up Bluetooth: {e}")β β β β def cleanup(self):β β """Clean up all audio processes"""β β self.stop_radio()β β self.stop_airplay()β β β def main():β β radio = VintageRadio()β β radio.setup_bluetooth()β β β β while True:β β print("\nVintage Radio Control")β β print("1. Play Radio Station 1 (Classic FM)")β β print("2. Play Radio Station 2 (Radio 1)")β β print("3. Play Radio Station 3 (BBC Radio 2)")β β print("4. Start AirPlay Receiver")β β print("5. Stop All")β β print("6. Exit")β β β β choice = input("Select an option: ")β β β β if choice in ['1', '2', '3']:β β radio.play_radio(choice)β β elif choice == '4':β β radio.start_airplay()β β elif choice == '5':β β radio.cleanup()β β elif choice == '6':β β radio.cleanup()β β break β β if name == "__main__":β β main()β
Prior to running the code, Claude also mentioned that I needed to update the Pi as well as install βsome other packages that werenβt included. β
These are the commands that it suggested
sudo apt-get update sudo apt-get install python3-vlc shairport-sync bluetooth bluez-tools
β# Add this to /boot/config.txtβ dtoverlay=hifiberry-dac
β# Edit /etc/bluetooth/main.confβ β# Add or modify:β Class = 0x200414β Enable = Source,Sink,Media,Socket
chmod +x radio_script.py
After implementing all these changes, I tried testing out the code by running the Python script. It βseemed like it was working correctly, however, I was unable to get any sound to play through my βheadphones. β
The next prompt I sent to Claude was:β
I havenβt integrated the DFRobot Bluetooth amp or the Pimoroni DAC, could you rewrite the code βso that I can test the radio over Bluetooth to some headphones.
After a few short moments, Claude gave me the next batch of code. I saved the new code and βthen ran it again only to find out I was still having issues with the sound working. At that point, I βdecided to do some testing on the Pi itself. I hooked up a screen and a keyboard and mouse to the βPi, however at this point Iβm not exactly sure what I did but the Pi would no longer power up. After βtrying several things, I resorted to changing to a Pi 4B instead of the Zero 2 W. I figured this would βhelp in the long run as I would need fewer extra parts to get the radio up and running. The Pi 4B βhad an Aux output on it, so I could directly connect between the Pi and the Bluetooth amp that I βwas looking at using.β
After setting up the new PI, I first tried connecting my headphones via Bluetooth and tried playing βsome YouTube videos. The sound came through the headphones loud and clear. β
Being that had worked, I went back to Claude and entered the following prompt:β
Can you remove all of the Bluetooth things from the code? In testing the Bluetooth portion of the Pi βis working correctly already without modification.
Also, can you change the radio station dictionary to this '1': {'name': 'Lofi 1', 'url': ββ'http://192.95.39.65:5607'}, '2': {'name': 'Lofi 2', 'url': 'https://cast.magicstreams.gr:9125'}, '3': ββ{'name': 'Lofi 3', 'url': 'http://5.39.82.157:8054'}β
Claude gave me another rendition of the code.β
This new code worked perfectly. I was able to switch between stations as well as stop and restart βthe radio by entering the numbers for each option in the terminal. β
After getting this part of the code working, I attempted to get an airplay function set up as well. βAlso, at this point, I had to switch from Claude to ChatGPT as I had hit my daily limit for requests. βI copied the code into ChatGPT and then asked if it could add the airplay receiver portion to the βcode. After much frustration and effort in trying to get it to work, I decided to stop working on that βportion of the code and try to get some other ideas working.β
The next idea I had for this build was to add a sound effect like an old-time radio tuning between βstations each time the station was changed. I asked ChatGPT if it was possible to add some sort βof sound effect between stations playing and it said that indeed it should be. It then instructed me βto search the web for a royalty-free sound effect of βradio tuning noiseβ or βAM radio staticβ. After βsearching the web, I was able to come across an MP3 that sounded very close to what I originally βhad in mind. β
The next step that was given was to install pygame on the Pi by running this command
Pip install pygame
After downloading the .mp3 file onto the Pi, I then asked how I could go about adding a random 5-βsecond clip of the .mp3 after a station had been selected. It then updated the code for me, and I βsaved it to the Pi. The first few renditions of the code were close to what I was expecting but not βquite right. The first kept playing the full one-minute plus .mp3 and the second rendition did play βfor 5 seconds as I wanted, however, it didnβt fade in/out like I was hoping. I then asked if it would βbe able to add a fade in/out sequence to the code. It added the correct fade out and in, but there βwas a half-second dead spot between the two. I then asked if it was able to overlap the sound clip βand the radio station a little bit so that it was more of a smooth transition. Once this prompt was βsent, I received the code that would ultimately end up in the final build. It did everything I needed βit to and faded in and out exactly like I wanted. After getting the code, I updated and added some βnew stations to the station dictionary. β
import pygame import time import random import vlc import subprocess import logging from threading import Thread from tyPing import Optional, Dict β# Set up logging with more detailed formatβ logging.basicConfig(β β level=logging.DEBUG,β β format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'β β)β logger = logging.getLogger(__name__)β class InternetRadio:β β def init(self):β β # Initialize pygame mixer with higher quality settingsβ β pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=4096)β β self.player: Optional[vlc.MediaPlayer] = None β self.airplay_process: Optional[subprocess.Popen] = None β self.current_source: Optional[str] = None β self.current_station: Optional[str] = None β self.vlc_volume = 0 # Track VLC player volume β # Enhanced radio stations dictionary with metadataβ β self.radio_stations: Dict[str, dict] = {β β '1': {β β 'name': 'Lofi 1',β β 'url': 'http://192.95.39.65:5607',β β 'description': 'Relaxing lofi beats'β β },β β '2': {β β 'name': 'MPR News',β β 'url': 'https://nis.stream.publicradio.org/nis.mp3',β β 'description': 'Minnesota Public Radio News'β β },β β '3': {β β 'name': 'Lofi 3',β β 'url': 'http://5.39.82.157:8054',β β 'description': 'Study beats and ambient sounds'β β },β β '4': {β β 'name': 'Minnesota Jazz Tracks',β β 'url': 'http://kbem-live.streamguys1.com/jazztracks_mp3',β β 'description': 'The Jazz of 10,000 Lakes'β β },β β '5': {β β 'name': 'Swing Street Radio',β β 'url': 'http://s6.voscast.com:11312',β β 'description': 'Swing Music'β β },β β '6': {β β 'name': 'Swing City Radio',β β 'url': 'http://bluford.torontocast.com:8006/stream',β β 'description': 'Catch your favorites of the 20s 30s and 40s'β β },β β '7': {β β 'name': 'Yesterday USA - Blue',β β 'url': 'https://media.classicairwaves.com:8018/stream',β β 'description': 'Welcome to Yesterday USA, where Old Time Radio Programs rule the day. βFor more than 37 years we have been playing classic old time radio shows hosted by volunteers βwho are no>β β },β β }β β def list_stations(self) -> None:β β """Display all available radio stations with their descriptions"""β β print("\n=== Available Radio Stations ===")β β for number, station in self.radio_stations.items():β β print(f"{number}. {station['name']} - {station['description']}")β β print("=" * 30 + "\n")β β def play_radio(self, station_number: str) -> None:β β """β β Play internet radio station with smooth transition effects β """β β if station_number not in self.radio_stations:β β logger.error(f"Invalid station number: {station_number}")β β print("Invalid station number. Please try again.")β β return β try:β β self.stop_airplay()β β self.current_station = station_number β # Create new VLC instance with initial volume at 0β β instance = vlc.Instance('--verbose=2')β β if self.player:β β self.player.stop()β β self.player = instance.media_player_new()β β url = self.radio_stations[station_number]['url']β β media = instance.media_new(url)β β self.player.set_media(media)β β # Start radio playback at 0 volumeβ β self.player.audio_set_volume(0)β β self.player.play()β β # Start transition effect in a separate threadβ β Thread(target=self._transition_audio).start()β β except Exception as e:β β logger.error(f"Error playing radio: {e}")β β print(f"Error playing radio station. Please try again.")β β def transitionaudio(self) -> None:β β """β β Handle the smooth transition between tuning noise and radio station β """β β try:β β # Play tuning noiseβ β pygame.mixer.music.load('radio_tuning.mp3')β β pygame.mixer.music.play()β β pygame.mixer.music.set_volume(1.0)β β # Wait for a moment with full tuning noiseβ β time.sleep(1.5)β β # Crossfade between tuning noise and radioβ β steps = 50 # More steps for smoother transition β fade_duration = 3.0 # Longer fade for more vintage feel β step_time = fade_duration / steps β for i in range(steps + 1):β β # Calculate volumes using sine wave for smooth transitionβ β tuning_volume = cos_volume = (steps - i) / steps β radio_volume = sin_volume = i / steps β # Update volumesβ β pygame.mixer.music.set_volume(tuning_volume)β β self.player.audio_set_volume(int(radio_volume * 100))β β time.sleep(step_time)β β # Ensure tuning noise is fully stoppedβ β pygame.mixer.music.stop()β β except Exception as e:β β logger.error(f"Error during audio transition: {e}")β β def start_airplay(self) -> None:β β """Start Shairport-sync for AirPlay receiving"""β β if self.airplay_process is None:β β try:β β self.stop_radio()β β self.airplay_process = subprocess.Popen(β β ['shairport-sync', '-o', 'alsa', '--name', 'Vintage Radio'],β β stdout=subprocess.PIPE,β β stderr=subprocess.PIPE,β β text=True β )β β self.current_source = 'airplay'β β logger.info("AirPlay receiver started successfully")β β print("AirPlay receiver is now active")β β except Exception as e:β β logger.error(f"Error starting AirPlay: {e}")β β print("Failed to start AirPlay receiver. Please check if shairport-sync is installed.")β β def stop_airplay(self) -> None:β β """Stop AirPlay receiver"""β β if self.airplay_process:β β self.airplay_process.terminate()β β self.airplay_process = None β self.current_source = None β logger.info("AirPlay receiver stopped")β β print("AirPlay receiver stopped.")β β def stop_radio(self) -> None:β β """Stop radio playback with fade out effect"""β β if self.player:β β # Fade out radioβ β current_volume = self.player.audio_get_volume()β β steps = 20β β for i in range(steps):β β volume = int(current_volume * (steps - i) / steps)β β self.player.audio_set_volume(volume)β β time.sleep(0.05)β β self.player.stop()β β self.player = None β self.current_station = None β logger.info("Radio playback stopped")β β print("Radio playback stopped.")β β def get_status(self) -> str:β β """Get detailed current status of the radio"""β β status = "\n=== Radio Status ===\n"β β if self.current_source == 'airplay':β β status += "Current mode: AirPlay\n"β β elif self.current_station:β β station = self.radio_stations[self.current_station]β β status += f"Current mode: Radio\n"β β status += f"Playing: {station['name']} - {station['description']}\n"β β else:β β status += "Current mode: Idle\n"β β return status + "=" * 20 + "\n"β def main():β β radio = InternetRadio()β β while True:β β print("\n=== Vintage Internet Radio Control ===")β β print("1. List Radio Stations")β β print("2. Play Radio Station")β β print("3. Start AirPlay Receiver")β β print("4. Stop Playback")β β print("5. Show Status")β β print("6. Exit")β β print("=" * 35)β β try:β β choice = input("\nSelect an option (1-6): ").strip()β β if choice == '1':β β radio.list_stations()β β elif choice == '2':β β radio.list_stations()β β station = input("Enter station number to play: ").strip()β β radio.play_radio(station)β β elif choice == '3':β β radio.start_airplay()β β elif choice == '4':β β radio.stop_radio()β β radio.stop_airplay()β β elif choice == '5':β β print(radio.get_status())β β elif choice == '6':β β print("\nStopPing all playback...")β β radio.stop_radio()β β radio.stop_airplay()β β print("Goodbye!")β β break β else:β β print("Invalid option, please enter a number between 1 and 6.")β β except KeyboardInterrupt:β β print("\nShutting down gracefully...")β β radio.stop_radio()β β radio.stop_airplay()β β break β except Exception as e:β β logger.error(f"Unexpected error: {e}")β β print("An error occurred. Please try again.")β if name == "__main__":β β main()β
At this point, I had saved 8 different renditions of the code to the pi, so that I could revert to older βones if I ran into issues. After getting the main controls working, the next task I asked ChatGPT βwas if it could set up a local webpage that I would be able to control the Pi from. It then set off βcreating a Flask page that would handle the controls. Prior to this build, I had never heard of Flask, βso I had to do a bit of research to learn more about it. Luckily, we already have a few projects on βMaker.io related to Flask. After a short time, ChatGPT gave me the following bit of code.β
from flask import Flask, render_template, request, jsonify import threading from radio8_setup import InternetRadio # Ensure your existing script is named properly app = Flask(__name__)β radio = InternetRadio()β @app.route('/')β def index():β β return render_template('index.html', stations=radio.radio_stations)β @app.route('/play/<station_number>', methods=['POST'])β def play_station(station_number):β β threading.Thread(target=radio.play_radio, args=(station_number,)).start()β β return jsonify({"status": "playing", "station": station_number})β @app.route('/stop', methods=['POST'])β def stop_radio():β β radio.stop_radio()β β return jsonify({"status": "stopped"})β @app.route('/volume', methods=['POST'])β def set_volume():β β volume = int(request.form['volume'])β β if radio.player:β β radio.player.audio_set_volume(volume)β β return jsonify({"status": "volume_set", "volume": volume})β if name == '__main__':β β app.run(host='0.0.0.0', port=5000, debug=True)
βFor the Flask program to work, I also needed to have an index.html file put together. After βprompting ChatGPT, here is the code that it gave me. I updated the background image to one I βfound online.β
<!DOCTYPE html>β β<html lang="en">β β<head>β β <meta charset="UTF-8">β β <meta name="viewport" content="width=device-width, initial-scale=1.0">β β <title>Antique Radio Controller</title>β β <style>β β body {β β background-image: url('https://wallpapercave.com/wp/wp8461192.jpg');β β background-size: cover;β β text-align: center;β β font-family: Arial, sans-serif;β β color: white;β β }β β .button {β β display: block;β β width: 200px;β β margin: 10px auto;β β padding: 10px;β β background-color: brown;β β color: white;β β border: none;β β cursor: pointer;β β }β β .slider {β β width: 50%;β β margin: 20px auto;β β }β β </style>β β</head>β β<body>β β <h1>Antique Radio Controller</h1>β β {% for key, station in stations.items() %}β β <button class="button" onclick="playStation('{{ key }}')">{{ station.name }}</button>β β {% endfor %}β β <br>β β <input type="range" min="0" max="100" class="slider" oninput="setVolume(this.value)">β β <br>β β <button class="button" onclick="stopRadio()">Stop</button>β β <script>β β function playStation(station) {β β fetch(`/play/${station}`, { method: 'POST' });β β }β β function stopRadio() {β β fetch('/stop', { method: 'POST' });β β }β β function setVolume(volume) {β β fetch('/volume', {β β method: 'POST',β β headers: {'Content-Type': 'application/x-www-form-urlencoded'},β β body: volume=${volume}β β });β β }β β </script>β β</body>β β</html>β
βNow that the Flask page was set up and the Python script was completed, the last task I had was βto find out which parts I wanted to use in the final build. I started looking at the DigiKey website for βanother audio amp that would be rather low-cost and possibly have a couple of different options βfor how the media is delivered. I discovered a HiFi Bluetooth amplifier from DFRobot, part 1738-βDFR0803-ND, which had everything I was hoping to find. It had options for both Bluetooth and βauxiliary inputs. This gave me the ability to connect to the radio over Bluetooth instead of Airplay βor via the internet radio that I was building via the aux input. This was the perfect choice for this βbuild. β
After selecting the amplifier, I wanted to use, I looked for a short audio plug-to-plug (aux cable) to βconnect the Raspberry Pi to the amplifier. I found part number TL1621-ND, which is 1 foot long.β
After getting these parts, I then dove into the build itself. My original thought was to take out the βoriginal internals to the radio, but after seeing what I would need to get this to work, I decided that βI would keep the radio as original as possible with the internals intact. I figured that way if βsomeone later wanted to restore the radio, everything was still in its original place.β
To start the hardware installation, I first looked for a Raspberry Pi 4 case suitable for a VESA mount βon Printables and began 3D printing it. Next, I found a case for the amplifier, which was listed as βthe Wuzhi ZK-502C case, also on Printables. I then took that file and added a few mounting tabs in βthe slicer software I was using. My plan was to mount the case to the bottom of the shelf below βthe Raspberry Pi, as I thought this would be the simplest solution. After getting both pieces βprinted, I dove into the assembly. There was already a passthrough hole drilled into the wooden βshelf at the back of the radio, so I decided to screw the Raspberry Pi case into the shelf next to βthis hole. Directly beneath it, I mounted the Hi-Fi amplifier close enough for the short aux cable I βfound to reach. From there I ran the speaker wire from the amp down to the speaker and βconnected both positive and negative wires to their respective terminals. Lastly, I ran the power βsupplies for both the amp and the Pi down to the bottom of the cabinet. From there I attached βboth power supplies to a dual connection extension cord so that I would only have to plug the one βcord into the wall to power both devices. β
At this stage, the complete build was assembled and sounded fantastic. However, I felt that βsomething was still missing from the front. While the project looked impressive overall, the βdisplay seemed to lack adequate backlighting. Initially, I considered using an LED strip for the βbacklighting, but after assessing the limited space available, I decided to explore other options. I βremembered that Adafruit had flexible LED filaments that they refer to NOODS. I found part 1528-ββ5504-ND which I thought would look pretty good. I connected the setup to my benchtop power βsupply to determine the appropriate resistor size for achieving the desired level of backlighting. βAfter conducting a series of tests, including positioning the filament within the radio below the βdisplay, I discovered that a 4.5-ohm resistor provided the perfect color I was aiming for at 3.3V. βThe reason I wanted to adjust it for that voltage is so I could directly connect it to the 3.3V pin on βthe Raspberry Pi for power. After getting the LED filament wired and put into place, I realized I βneeded something to hold it there. Instead of using glue to adhere it, I resorted to 3D printing a βlong rectangular piece that would just wedge it in place. That way it could be easily removed if βneeded.β
I should mention that I downloaded an app called Wuzhi Audio on my phone. This app allows me βto easily switch between Bluetooth and Aux input for the amplifier. This feature makes it even βeasier than I imagined to play songs from my phone on the radio.β
Here is the reveal of the final product and how it turned out.β
This is how the web control interface looks:β
And here is a short video of it in action:β
β
Overall, this build was a lot of fun, and I was able to test the viability of using LLMβs to help with βmy coding. I would say that looking back on this project, LLMβs are great at getting the groundwork βof a project. It helped get the project 80% of the way completed and it needed guidance along the βway to get it there, but it for sure helped speed up my coding.