Creating the Aetheryte Radio

Hacker News Top Tools

Summary

A tutorial on creating a randomized audio component using the Web Audio API and JavaScript to simulate a radio from the game FFXIV, with randomized whirs and hums for a lifelike effect.

No content available
Original Article
View Cached Full Text

Cached at: 08/29/26, 03:37 PM

# creating the aetheryte radio Source: [https://haz.ee/posts/aetheryte-radio.html](https://haz.ee/posts/aetheryte-radio.html) ffxiv plays the whirs at random intervals, pitches, and gain \(volume\)\. these are all added to create an illusion of life\(?\) that make it harder to detect when an asset is repeating\. thankfully, ffxiv also provided values that gave the min and max values for each of the random parameters, so it was easy to translate into javascript: ``` const whirIndex = Math.floor(Math.random() * whirs.length); const whirPlaybackRate = Math.random() * (1 - 0.794) + 0.794; whirGainNode.gain.value = Math.random() * (1 - 0.6) + 0.6; ``` if you've ever built a graph by hand the flow is usually something like: allocate a node, set metadata, and connect\. i do the following to setup the hum: ``` const humSource = audioContext.createBufferSource(); humSource.buffer = await loadSample( audioContext, isSafari ? "assets/hum.wav.opus.aac" : "assets/hum.wav.opus", ); humSource.playbackRate.value = 0.63; humSource.connect(humGainNode); humSource.loop = true; humSource.start(0); ``` it's actually ok that i connect the node before i set loop, because the node doesn't produce samples until i call`start`\. the other part of the ceremony is the whir loop\. it's not actually a loop using conventional loop control flow\. instead of using a`while \(true\)`with a random "sleep" in between, i instead use`setTimeout`to schedule the next whir at a random interval\. ``` function chooseWhir() { const whirSource = audioContext.createBufferSource(); const whirIndex = Math.floor(Math.random() * whirs.length); const whirPlaybackRate = Math.random() * (1 - 0.794) + 0.794; whirGainNode.gain.value = Math.random() * (1 - 0.6) + 0.6; whirSource.buffer = whirs[whirIndex]; whirSource.playbackRate.value = whirPlaybackRate; whirSource.connect(whirGainNode); whirSource.start(0); whirSource.onended = () => { whirSource.disconnect(whirGainNode); const nextWhirDelay = Math.floor(Math.random() * 2001); setTimeout(chooseWhir, nextWhirDelay); }; } ``` same deal here: create a source node, select a random asset, playback rate \(pitch\), gain \(volume\), and connect it to the gain node \(which stays constant in this process and only has it's value changed\.\) the key here is that when the source asset ends, instead of looping, we remove that node from the graph and add a new one \(by calling`chooseWhir`again\.\) because there is an expected delay before the next whir, i'm ok with adding whatever latency is added by doing the random calculation, it's likely marginal\.

Similar Articles

Show HN: Chiptune Radio

Hacker News Top

Chiptune Radio is a side project by Aleph Void, LLC that streams chiptune music composed for classic sound hardware via a continuous Twitch broadcast.

MiniMax Music 3 Infinite Radio

Reddit r/LocalLLaMA

An open-source, self-hosted AI music generation radio tool called TAPEDECK that uses MiniMax Music 3 to create endless, personalized radio stations from a user's music library on a local GPU.

Infinite Music Glitch on my Arduino with Magenta Realtime 2

Reddit r/LocalLLaMA

A developer built a local voice-controlled music system using an ESP32 microcontroller, a MacBook, Magenta Realtime 2 for real-time music generation, MLX Whisper for transcription, and a Qwen model for tool calling, enabling conversational control over music elements like genre and instruments.

@ClaudeDevs: /radio

X AI KOLs Following

ClaudeDevs announces a new /radio feature for Claude, likely an audio or streaming mode.