There is something irreplaceable about the physical ritual of vinyl — the weight of a record in your hands, the deliberate act of placing it, the anticipation before the music starts. Digital music gave us infinite choice but took away that ceremony.
Punarnaada brings it back. It's a fully functional smart gramophone that uses custom NFC discs as "records." Each disc is programmed to link to a Spotify playlist, album, artist, or song. Place the disc on the spinning platter, and the gramophone authenticates with Spotify and begins streaming - through real built-in speakers, with the platter spinning in that familiar, nostalgic motion.
But the real challenge - and the heart of this project - was the physical form. How do you build a gramophone's iconic flared horn by hand?
- NFC Record System -Place a custom NFC disc on the platter to instantly trigger any Spotify content (playlist, album, artist, song, or radio station)
- Spotify Streaming -Full OAuth 2.0 authentication and Spotify Web API integration over Wi-Fi via ESP32
- Kerf-Bent Birch Horn -Laser-cut birch plywood panels, kerf-bent to form the compound curve of a gramophone horn
- Spinning Platter -DC motor with gear mechanism spins the platter during playback for authentic gramophone aesthetics
- Built-in Hi-Fi Audio -PCM5102 I2S DAC → TPA3116D Class-D amplifier → 5W 8Ω stereo speakers
- Companion Android App -Flutter-based app to write Spotify URIs onto NFC discs and control playback remotely
- 3D-Printed Brass Fittings -Decorative column feet, horn bracket, neck mount -painted to match period aesthetics
The records have their URIs stored in the NFC Stickers. That makes it possible to have any material as the record.
The below photo shows few different types of Records: Cardboard, Clear and Black Acrylic.
How It Works -User Flow
1. PROGRAM A RECORD
- Open companion app →
- Paste Spotify playlist/album/song URL →
- Tap "Write" →
- Place blank NFC disc on platter →
- ESP32 writes URI to disc ✓
2. PLAY MUSIC
- Place programmed NFC disc on platter →
- RC522 reads disc →
- ESP32 reconstructs Spotify URI →
- Spotify API starts playback →
- Platter spins, speakers play, display shows track info ✓
Inside the clean wooden body lives a surprisingly capable embedded system: two custom ESP32 boards, a high-fidelity I2S DAC, a class-D stereo amplifier, an RC522 RFID reader hidden beneath the platter, a 2.8" TFT display showing track info, a DC motor for platter rotation, and a custom power management board. Everything is interconnected with IDC ribbon cables for tidy internal wiring.
The companion Flutter app runs on Android and lets you "cut" new NFC records — write any Spotify URI to a blank NFC tag — in seconds.
Why the ESP32? The MCU at the Heart of ResonanceResonance is fundamentally an ESP32 project. The choice of MCU wasn't incidental — the ESP32's unique combination of capabilities is what makes the entire concept possible on a single affordable chip.
Wi-Fi + SPI + PWM + HTTP server — all at once. The main control board runs Wi-Fi for Spotify OAuth and API calls, drives SPI peripherals with carefully managed chip-select lines, outputs PWM to spin the platter motor, and simultaneously runs a WebServer instance to receive commands from the companion app. On a lesser MCU, this would require external co-processors or compromise. The ESP32's dual-core Xtensa LX6 architecture handles all of this concurrently without contention.
Bluetooth A2DP audio. The second ESP32 leverages its integrated Bluetooth stack to receive A2DP audio - the same wireless audio protocol used by Bluetooth speakers - and routes it directly to the PCM5102 I2S DAC via the hardware I2S peripheral. This is not a feature available on most microcontrollers at any price point. The ESP32 makes it a three-line firmware setup.
NFC tag reading and writing via SPI. The RC522 RFID module communicates over SPI.
Sufficient processing headroom. Spotify OAuth involves parsing JSON responses, managing token refresh cycles, and constructing REST API calls over HTTPS. These are computationally non-trivial tasks. The ESP32's 240 MHz dual-core processor and 520 KB SRAM handle them while still keeping the NFC polling loop, display rendering, and HTTP server responsive.
No other microcontroller at the ESP32's price and form factor integrates Wi-Fi, Bluetooth, I2S, SPI, PWM, and the processing headroom for TLS/HTTPS in a single package. Resonance would not exist in its current form without it.
Dual-ESP32 Architecture: A Deliberate Design DecisionResonance uses two ESP32 modules — not because one wasn't capable enough, but because separating concerns between two dedicated processors produces a cleaner, more robust system.
Why two boards?
Wi-Fi and Bluetooth coexist on the ESP32's shared 2.4 GHz radio, but they compete for airtime. Running active Wi-Fi (for Spotify API polling and HTTP serving) simultaneously with Bluetooth A2DP audio streaming on a single ESP32 results in audio dropouts and degraded throughput on both. The solution is physical separation: one ESP32 owns Wi-Fi exclusively, the other owns Bluetooth audio exclusively.
Interference isolation. SPI-based peripherals (RFID, TFT) generate switching noise that can corrupt sensitive audio signals. By placing the I2S DAC and audio amplifier on a physically separate board, far from the SPI bus and Wi-Fi antenna, audio quality is significantly improved.
Fault isolation and development clarity. Each board has a single, well-defined responsibility. The main board can be reflashed and debugged independently of the audio board. The audio board firmware is deliberately minimal — it does one thing and does it well.
This architecture demonstrates how two ESP32s, communicating indirectly through Bluetooth A2DP (one as the source trigger, one as the audio sink), can function as a cohesive system with complementary roles — a pattern applicable to a wide range of distributed embedded IoT products.
ESP32 Capabilities Pushed in This ProjectResonance is not a "blink an LED" ESP32 project. It exercises a broad cross-section of the ESP32's hardware and software capabilities simultaneously:
The ESP32's Wi-Fi stack handles Spotify OAuth 2.0 token refresh and all Web API calls over HTTPS; including JSON parsing of track metadata returned by Spotify's endpoints. Alongside this, a WebServer instance runs continuously on port 80, serving REST endpoints that the companion Flutter app uses to send playback commands over the local network.
The ESP32 is not a supporting player in Punarnaada - it is the instrument. Every user-facing feature of this gramophone, from the moment an NFC disc touches the platter to the moment audio comes through the speakers, flows through the ESP32's peripherals, radio, and processor.
Punarnaada is built around a modular 3-board custom PCB architecture:
Board 1: Main Control Board (ESP32-WROOM)
The central brain. Connects to Wi-Fi, handles Spotify OAuth token refresh, scans NFC discs via the RC522 module (SPI), drives the platter motor with PWM, and runs an HTTP server so the Flutter companion app can send commands.
Functions:
- Runs the firmware that communicates with the Spotify Web API to fetch playlists, track details, and playback control.
- Interfaces with an RFID reader that detects NFC-tagged records, mapping them to Spotify playlists.
- Drives the DC motor that enables record-like physical motion, reinforcing the turntable aesthetic.
The RC522 RFID module is used to read NFC tags that are programmed with Spotify playlists. It communicates with the ESP32 via SPI, allowing Punarnaada to detect which record is placed on the turntable. The RFID module is connected using a IDC Connector for neat wiring.
Board 2: Bluetooth Audio Board (ESP32)
A dedicated audio node. Receives audio via Bluetooth A2DP, routes it through the PCM5102 I2S DAC, and drives the TPA3116D class-D amplifier. Isolated from the main board to prevent audio noise from interfering with Wi-Fi/SPI operations.
Key Components & Functions:
- ESP32 Module - Acts as a Bluetooth receiver, enabling Punarnaada to function as a wireless speaker system.
- PCM5102 I2S DAC - A high-fidelity digital-to-analog converter that translates the ESP32's I2S audio output into rich, noise-free analog sound.
The speakers are primarily controlled by the Bluetooth Board, which has the PCM5102 I2S DAC. The I2S DAC converts the digital signals from the ESP32 into Stereo Analog Signals. This signal is then amplified by the external TPA3116D Stereo Amplifier. The amplifier is connected to 5W 8Ohm Speakers.
The PCM5102 I2S DAC is part of the Punarnaada Bluetooth Speaker Board, while the Amplifier and Speakers are externally connected through screw terminals. The TPA3116D Amplifier Board operates on 12-24V, this power is provided by the Power Management Board.
Board 3: Power Management Board
A MP1684 buck converter provides regulated 12V (for the TPA3116D amplifier) and 5V (for ESP32 modules, RC522, and motor). Power is distributed to all boards via JST terminals.
- Mini MP1684 Buck Converter - Provides regulated 12V and 5V outputs, supplying both the amplifier (high-power rail) and the control boards (logic rail).
- Power Distribution - Routes power to the main board, audio board, DC Motor, Speakers through JST Terminals
Schematics
All four PCBs were designed in KiCad.
CAD DesignAll mechanical and enclosure parts were designed in Autodesk Fusion 360.
1. Gramophone Horn (Kerf-Bent Birch Plywood)
The horn is the most challenging and distinctive element of this build. A gramophone horn is a compound-curved flare -not achievable with standard flat-sheet bending. The solution: kerf bending.
What is kerf bending?
Kerf bending is the technique of cutting a series of closely-spaced slots (kerfs) into a flat sheet of material. The slots reduce the material's stiffness locally, allowing it to flex and curve. By controlling the spacing, depth, and pattern of the kerfs, you can precisely control the bend radius and direction.
Horn Design Process:
1. The horn profile was modelled in Fusion 360 as a rotational solid -the classic gramophone flare geometry
2. The horn was "unrolled" into flat panel segments that could be cut from sheet material
3. Each panel was given a kerf pattern optimised for 3mm birch plywood -the slot spacing was calculated to allow the panel to achieve the required curvature without cracking
4. Panels were exported as DXF files for the laser cutter
Horn Fabrication:
- Material: 3mm birch plywood
- Machine: XTools S1
- The kerf slots were cut to ~80% depth (not through the full thickness), leaving a thin flexible skin on the outer face
- After cutting, each panel was wetted slightly to increase flexibility, then bent around a form and allowed to dry in shape
- Multiple bent panels were joined and reinforced with brass binding strips, giving the horn its segmented, ribbed aesthetic that references the construction of early acoustic horns
2. Octagonal Base (CNC-Milled Plywood)
The body of the gramophone is an octagonal cabinet, referencing the form of antique gramophone bases. Designed in Fusion 360 and on a OMAX Waterjet Cutter CNC from 12mm plywood.
Multiple cross-section layers were stacked and glued to build up the height and give the base a solid, substantial appearance. After assembly, the base was sanded and finished with walnut wood stain.
3. 3D-Printed Components (PLA)
All 3D-printed parts were designed in Fusion 360 to serve both structural and decorative roles. Printed on BambuLab printers (A1, A1 Mini, P1S) and finished with metallic brass-effect paint:
Rotary Module
- The rotating platter that simulates the vinyl record motion, designed to hold the NFC records and provide a tactile interaction. It includes a DC motor for rotation and a bearing for smooth movement.There is inner stationary core which houses the RFID Reader and the outer geared rotating platter which is meshed to the DC Motor gear.
Main Board Firmware
Written in Arduino IDE with ESP32 board support.
Core responsibilities:
- Connect to Wi-Fi and authenticate with Spotify via OAuth 2.0 (refresh token flow)
- Run an HTTP server on port 80 to receive commands from the companion app
- Continuously poll the RC522 for new NFC discs; read Spotify URI from two RFID blocks
- Write Spotify URIs to NFC tags when requested by the companion app
- Control platter motor via PWM during playback
Spotify API
To connect the ESP32 with the Spotify API, you can use the HTTPClient library to make GET and POST requests. The library allows you to send requests to the Spotify API endpoints and receive responses in JSON format. You can then parse the JSON data to extract relevant information, such as track names, artist names, and album art URLs. This data can be displayed on an OLED screen or sent to a connected device for further processing.
For this, I have used a library called Spotify_Esp32
To use the library, you need to include it in your Arduino sketch and initialize it with your Spotify credentials. The library provides functions to authenticate with the Spotify API, fetch track information, and control playback on connected devices.
Here is a simple example of how to use the library to fetch the currently playing track:
/*
An example of how to authenticate with Spotify without using a refresh token and print Artist and Track via Serial
In this example your current track will be printed to the serial and as soon as you listen to a new track that tracks information will be printed.
15.03.2024
Created by: Finian Landes
16.03.2024
edited by: Sascha Seidel
* added getting artist and trackname to print it Serial
Documentation: https://github.com/FinianLandes/Spotify_Esp32
*/
// Include the required libraries
#include < Arduino.h>
#include < WiFi.h>
#include < SpotifyEsp32.h>
char* SSID = "YOUR WIFI SSID";
const char* PASSWORD = "YOUR WIFI PASSWORD";
const char* CLIENT_ID = "YOUR CLIENT ID FROM THE SPOTIFY DASHBOARD";
const char* CLIENT_SECRET = "YOUR CLIENT SECRET FROM THE SPOTIFY DASHBOARD";
const char* REFRESH_TOKEN = "YOUR REFRESH TOKEN";
Spotify sp(CLIENT_ID, CLIENT_SECRET);
void setup() {
Serial.begin(115200);
connect_to_wifi();
sp.begin();
while(!sp.is_auth()){
sp.handle_client();
}
Serial.println("Authenticated");
}
void loop() {
static String lastArtist;
static String lastTrackname;
String currentArtist = sp.current_artist_names();
String currentTrackname = sp.current_track_name();
if (lastArtist != currentArtist && currentArtist != "Something went wrong" && !currentArtist.isEmpty()) {
lastArtist = currentArtist;
Serial.println("Artist: " + lastArtist);
}
if (lastTrackname != currentTrackname && currentTrackname != "Something went wrong" && currentTrackname != "null") {
lastTrackname = currentTrackname;
Serial.println("Track: " + lastTrackname);
}
}
void connect_to_wifi(){
WiFi.begin(SSID, PASSWORD);
Serial.print("Connecting to WiFi...");
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.printf("\nConnected to WiFi\n");
}
CopyIn this example, the ESP32 connects to Wi-Fi, authenticates with the Spotify API using the provided credentials, and continuously checks for the currently playing track. When a new track is detected, it prints the artist and track name to the serial monitor.
The code for this is attached in the project.
Bluetooth Audio Board Firmware
Receives Bluetooth A2DP audio and streams it through the PCM5102 I2S DAC. Simple, single-purpose, and deliberately isolated from the main board.
#include "AudioTools.h"
#include "BluetoothA2DPSink.h"
I2SStream i2s;
BluetoothA2DPSink a2dp_sink(i2s);
void setup() {
auto cfg = i2s.defaultConfig();
cfg.pin_bck = 27; // I2S Clock
cfg.pin_ws = 12; // I2S Word Select
cfg.pin_data = 14; // I2S Data
i2s.begin(cfg);
// Device appears as "Punarnaada" in Bluetooth list
a2dp_sink.start("Punarnaada");
}
void loop() {}Companion Android App (Flutter)
To create a Flutter app that communicates with the ESP32, you need to set up a Flutter project in Android Studio. Once the project is created, you can use the http package to access the WebSerial API and establish a connection with the ESP32. The app should have a simple user interface that allows users to input track, album, or playlist links and send them to the ESP32.
In the app, you can use a TextField widget to allow users to enter the URI and a button to send the URI to the ESP32. The app should also handle the response from the ESP32 and display it to the user.
//This is a simple Flutter app that takes a Spotify playlist link, extracts the playlist ID, and sends it to an ESP32 device over HTTP.
//The ESP32 is expected to be running a server that can handle the incoming request.
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(SpotifySenderApp());
}
class SpotifySenderApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: SpotifyInputScreen(),
);
}
}
class SpotifyInputScreen extends StatefulWidget {
@override
_SpotifyInputScreenState createState() => _SpotifyInputScreenState();
}
class _SpotifyInputScreenState extends State {
final TextEditingController _controller = TextEditingController();
String _parsedUri = '';
String _statusMessage = '';
final esp32Ip = '192.168.33.175'; // <-- replace with your ESP32's IP!
void _parseAndSend() async {
final input = _controller.text.trim();
final regex = RegExp(r"(playlist\/|playlist:)([a-zA-Z0-9]+)");
final match = regex.firstMatch(input);
if (match != null) {
final playlistId = match.group(2);
final uri = "$playlistId";
setState(() {
_parsedUri = uri;
_statusMessage = 'Sending...';
});
try {
final response =
// await http.post(
// Uri.parse('http://192.168.33.17/uri'),
// body: {'uri': 'spotify:playlist:abc'}
// );
await http.post(
Uri.parse('http://192.168.33.175/uri'),
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: {'uri': uri},
);
if (response.statusCode == 200) {
setState(() {
_statusMessage = 'ESP32 says: ${response.body}';
});
} else {
setState(() {
_statusMessage = 'HTTP Error ${response.statusCode}: ${response.body}';
});
}
} catch (e) {
setState(() {
_statusMessage = 'Failed to send: $e';
});
}
} else {
setState(() {
_parsedUri = '';
_statusMessage = 'Invalid Spotify playlist link.';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Spotify URI Sender')),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
TextField(
controller: _controller,
decoration: InputDecoration(
labelText: 'Paste Spotify playlist link',
border: OutlineInputBorder(),
),
),
SizedBox(height: 16),
ElevatedButton(onPressed: _parseAndSend, child: Text('Parse and Send')),
SizedBox(height: 16),
if (_parsedUri.isNotEmpty) Text('Parsed URI: $_parsedUri'),
SizedBox(height: 8),
Text(_statusMessage),
],
),
),
);
}
}
CopyIn this code, we have a simple Flutter app that takes a Spotify playlist link, extracts the playlist ID, and sends it to an ESP32 device over HTTP. The ESP32 is expected to be running a server that can handle the incoming request.
The above images shows the User Interface of the App. The below image shows the Serial Monitor of the Arduino IDE when I send the Spotify link to the ESP32 from the App. The communication is POST requests based as mentioned earlier.
You can see the URI is recieved successfully and you can see the track URI. The ESP32 device waits for NFC Tag and then writes the URI to the NFC Card when detected.
The above image shows successfully writing a Spotify ID to a NFC Tag
You can also detect the URI and send it to Spotify Web API. The below image shows when a NFC Tag with the Spotify URI is detected and sent to the Spotify
This app allows to send track, album or playlist links to the ESP32 and the esp32 sends a put request to Spotify Web API
In this app, I have used a simple text field to enter the URI and a button to send the URI to the ESP32. The app uses the http package to send a POST request to the ESP32 with the URI as a parameter.
The app connects to the ESP32's HTTP server over the local Wi-Fi network. It allows the user to:
1. Enter any Spotify URI (playlist, album, song, artist, or radio station)
2. Tap Write -the ESP32 waits for an NFC disc to be placed and writes the URI to it
3. Use on-screen buttons to control playback remotely (Play, Pause, Next, Previous)
On the Flutter app side, HTTP POST requests are sent using the Dart http package. When the user provides a Spotify playlist or track URI, the app constructs an HTTP POST request targeting the ESP32s local IP address (e.g., http://192.168.xx.xx/uri). This request contains headers like Content-Type: application/x-www-form-urlencoded and a body with the required data (such as the playlist URI).
The ESP32, running a local web server, listens for these incoming requests on predefined routes (like /uri, /play, /pause). When a request arrives, it parses the data, acts on it, and sends back an HTTP response — typically including a status code (200 OK) and an optional message, confirming that the request was successfully received or processed.
The HTTP endpoints on the ESP32:
| Endpoint | Method | Action |
| `/uri` | POST | Write Spotify URI to NFC disc (body: `uri=spotify:playlist:xxx`) |
| `/play` | POST | Start / resume playback |
| `/pause` | POST | Pause playback |
| `/next` | POST | Skip to next track |
| `/prev` | POST | Previous track |
Punarnaada started as a question: what would it look like if the ritual of vinyl survived into the age of streaming? The answer turned out to be equal parts woodworking, digital fabrication, embedded systems, and stubbornness.
Every element of this build pushed into unfamiliar territory. Kerf-bending birch plywood into a compound-curved gramophone horn required more failed test panels than I'd like to admit. Getting two ESP32s to divide labour cleanly — one managing Wi-Fi, Spotify, NFC, and the display; the other handling Bluetooth audio and the I2S DAC meant rethinking the architecture more than once. Fitting OAuth token refresh, SPI bus sharing, HTTP serving, PWM motor control, and live display rendering into a single coherent firmware loop was its own puzzle.
But the moment you place a hand-painted NFC disc on the spinning platter and music starts flowing through the horn —-the same motion your great-grandparents made with a shellac record - all of that effort collapses into a single satisfying second.
That is what makes a project worth building: not just that it works, but that it means something when it does. Resonance is a machine that makes the invisible act of streaming music feel physical, deliberate, and alive again. It proves that a $5 microcontroller, a sheet of birch plywood, and a handful of NFC tags can recover something that was thought to be lost to convenience.





_4YUDWziWQ8.png?auto=compress%2Cformat&w=48&h=48&fit=fill&bg=ffffff)



_t9PF3orMPd.png?auto=compress%2Cformat&w=40&h=40&fit=fillmax&bg=fff&dpr=2)





_Ujn5WoVOOu.png?auto=compress%2Cformat&w=40&h=40&fit=fillmax&bg=fff&dpr=2)
Comments