Creating the Aetheryte Radio
Engineering the Aetheryte Radio
The Motivation
When I'm in the zone at work, I rely heavily on ambient noise. Specifically, I've spent the last six years obsessively looping a YouTube video titled "aetheryte asmr," which captures the crystalline humming of the teleportation crystals from Final Fantasy XIV.
However, relying on a browser tab for six years revealed several minor significant flaws:
| Feature | YouTube Experience | Desired Experience |
|---|---|---|
| Looping | Unreliable; occasionally breaks and auto-plays random videos. | Infinite and unbreakable. |
| Seamlessness | Noticeable gaps/cuts every ~7 minutes. | Perfectly gapless playback. |
| Resources | High CPU/RAM usage for a simple audio loop. | Lightweight and efficient. |
| Control | Static recording. | Dynamic, generative ambiance. |
Note: I previously spent an absurd amount of time rewriting my music player's internals just to achieve gapless playback, so the lack of seamlessness was particularly grating.
The Quest for Assets
I eventually decided to stop settling and build my own solution. To do this, I needed the raw game files.
The "Brute Force" Phase
I discovered that FFXIV isn't heavily encrypted. Using a Java-based FFXIV Data Explorer, I was able to extract compressed assets from the game's installation directory. I had a hash table mapping files to English names, but I didn't use it.
My "Genius" Plan:
- Export 4,000
.wavfiles. - Load them all into
mpv. - Manually press the left/right arrow keys to listen to every single one.
"I spent four hours scrubbing through 4,000 audio assets... I didn't even find the right ones before I had to go to sleep at 3 AM."
The "Work Smarter" Phase
I realized I should leverage the existing modding community. By using xivlauncher (recommended by my roommate, who has a staggering 180 days of playtime), I gained access to several plugins:
vfxeditorsoundfilterThe MVP
The soundfilter plugin allows you to see the name of the asset currently playing in real-time. By teleporting to an Aetheryte in-game, I instantly identified the target file:
bgcommon/sound/fst/placednpc_ethelight_big_loop.scd/0
Technical Implementation
The asset was a container. Index 0 was the low-frequency "hum," while other indices contained "whirs." I decided to recreate this dynamically using the Web Audio API rather than just mixing it in Logic Pro.
The Architecture
Audio processing generally follows a "node" philosophy—similar to modular synthesizers—where samples are produced, transformed, and consumed.
1. The Hum (The Foundation)
The hum is a nearly perfect loop. With some help from a friend to ensure sample-accurate looping (preventing that annoying "click" by resetting the offset in the sink node), I built a simple chain:
2. The Whirs (The Life)
To prevent the brain from detecting a repeating pattern, FFXIV triggers "whir" sounds at random intervals, pitches, and volumes. I translated the game's internal min/max parameters into JavaScript logic:
Randomization Parameters:
- Pitch (Playback Rate):
- Volume (Gain):
// Logic for generating a random whir event
const whirIndex = Math.floor(Math.random() * whirs.length);
const whirPlaybackRate = Math.random() * (1 - 0.794) + 0.794;
const whirGainValue = Math.random() * (1 - 0.6) + 0.6;
The Code Structure
Setting up the hum involves allocating the node, setting metadata, and connecting it before starting playback:
const humSource = audioContext.createBufferSource();
// Asset mapping: "assets/hum.wav.opus.aac" -> "assets/hum.wav.opus"
humSource.loop = true;
humSource.connect(humGainNode);
humSource.start(0);
To handle the whirs, I avoided while(true) loops to prevent blocking the main thread. Instead, I used a recursive setTimeout to schedule the next random event.
Project Checklist:
- Extract raw
.scdassets - Identify "Hum" vs "Whir" tracks
- Implement gapless Web Audio loop
- Program random pitch/gain modulation
- Schedule asynchronous triggers via
setTimeout
Figure 1: Conceptual representation of the generative audio flow.