# ComputerCraft Audio Guide Consolidated reference for playing audio on CC: Tweaked. Covers the `cc.audio.dfpwm` library, the `speaker` peripheral, and the practical guide to streaming PCM audio. Sources: - - - --- ## 1. Audio fundamentals ### PCM (Pulse-code Modulation) Sound is an analog, continuous signal. Computers need discrete data, so audio is **sampled** at a fixed rate and each sample **quantized** to a representable value. A long, uniformly sampled list of amplitudes is called **PCM**. ### Speaker specifications CC: Tweaked speakers play back: - **48,000 samples per second** (48 kHz) - each sample is an **integer between -128 and 127** (8-bit resolution) So one second of audio = 48,000 samples. One minute ≈ 2.88 million samples — which is why streaming (processing in chunks) is preferred over building one giant buffer. ### DFPWM **DFPWM (Dynamic Filter Pulse Width Modulation)** is a codec by GreaseMonkey, the de facto standard audio format in the ComputerCraft / OpenComputers world. It encodes at **1 bit per sample**, making it compact for storage while remaining simple enough for real-time processing. CC: Tweaked ships the built-in `cc.audio.dfpwm` module to encode/decode it. You convert DFPWM → PCM amplitudes, then feed those to the speaker. --- ## 2. `cc.audio.dfpwm` library `require("cc.audio.dfpwm")` — converts between DFPWM streams and PCM amplitude tables. > **Stateful warning:** Encoders and decoders keep internal state about the current > stream. Reusing one encoder/decoder across multiple streams — or using different > ones on the same stream — produces corrupt audio. One stream = one > encoder/decoder. ### `make_encoder()` Creates a new encoder for converting PCM audio to DFPWM. - **Returns:** a function. Call it with a table of amplitude values (-128 to 127); it returns encoded DFPWM data as a string. - Use this (not `encode`) when writing multiple chunks to the same location. ### `encode(input)` Convenience function to encode a whole file at once. - **`input`** — table of amplitude numbers. - **Returns:** string of encoded DFPWM data. - Prefer `make_encoder()` for chunked/multi-write output. ### `make_decoder()` Creates a new decoder for converting DFPWM data to PCM amplitudes. - **Returns:** a function. Call it with a DFPWM string; it returns a table of amplitude values (-128 to 127). ### `decode(input)` Convenience function to decode a whole file at once. - **`input`** — string of DFPWM data. - **Returns:** table of amplitude values. - For larger files, use `make_decoder()` with chunked reading instead. --- ## 3. `speaker` peripheral Three ways to produce audio, increasing in complexity: note block notes, Minecraft sounds, and arbitrary PCM streaming. ### `playNote(instrument [, volume [, pitch]])` — *New in 1.80pr1* Plays a note block note. - **`instrument`** (string, required): `"harp"`, `"basedrum"`, `"snare"`, `"hat"`, `"bass"`, `"flute"`, `"bell"`, `"guitar"`, `"chime"`, `"xylophone"`, `"iron_xylophone"`, `"cow_bell"`, `"didgeridoo"`, `"bit"`, `"banjo"`, `"pling"`. - **`volume`** (number, optional): `0.0`–`3.0`, default `1.0`. - **`pitch`** (number, optional): semitones `0`–`24`, default `12`. - **Returns:** boolean — `false` if the 8-notes-per-tick limit was reached. - **Throws:** if the instrument doesn't exist. ### `playSound(name [, volume [, pitch]])` — *New in 1.80pr1* Plays a Minecraft sound effect (vanilla or modded). - **`name`** (string, required): sound id, e.g. `"entity.creeper.primed"`. - **`volume`** (number, optional): `0.0`–`3.0`, default `1.0`. - **`pitch`** (number, optional): speed multiplier `0.5`–`2.0`, default `1.0`. - **Returns:** boolean — `false` if another sound already started this tick or audio is currently playing. - **Throws:** if the sound name is invalid. ```lua local speaker = peripheral.find("speaker") speaker.playSound("entity.creeper.primed") ``` ### `playAudio(audio [, volume])` — *New in 1.100* Streams arbitrary audio as 8-bit PCM samples at 48 kHz. - **`audio`** (table, required): list of amplitude values between -128 and 127. - **`volume`** (number, optional): if omitted, reuses the previous volume. - **Returns:** boolean — `true` if the buffer accepted the data, `false` if the speaker's backlog is full. - **Throws:** if the audio data is malformed. - **Notes:** - Keep chunks to a max of **128 × 1024 (128k) samples** per call to minimize stuttering. - When it returns `false`, wait for the **`speaker_audio_empty`** event before retrying. - Samples are re-encoded before playback. ```lua local dfpwm = require("cc.audio.dfpwm") local speaker = peripheral.find("speaker") local decoder = dfpwm.make_decoder() for chunk in io.lines("data/example.dfpwm", 16 * 1024) do local buffer = decoder(chunk) while not speaker.playAudio(buffer) do os.pullEvent("speaker_audio_empty") end end ``` ### `stop()` — *New in 1.100* Stops all playback from this speaker: clears the queued audio and halts any active sound. Returns nothing. --- ## 4. The core streaming pattern This is the loop you'll reuse for almost all PCM playback: ```lua while not speaker.playAudio(buffer) do os.pullEvent("speaker_audio_empty") end ``` The speaker rejects new chunks when its backlog is too large — `playAudio` returns `false`. Wait for `speaker_audio_empty`, then retry the same buffer. --- ## 5. Examples ### Generate a sine wave (in-memory buffer) 220 Hz hum, ~2.7 seconds: ```lua local speaker = peripheral.find("speaker") local buffer = {} local t, dt = 0, 2 * math.pi * 220 / 48000 for i = 1, 128 * 1024 do buffer[i] = math.floor(math.sin(t) * 127) t = (t + dt) % (math.pi * 2) end speaker.playAudio(buffer) ``` ### Stream an endless sine wave (chunked) Avoids allocating millions of samples up front: ```lua local speaker = peripheral.find("speaker") local t, dt = 0, 2 * math.pi * 220 / 48000 while true do local buffer = {} for i = 1, 16 * 1024 * 8 do buffer[i] = math.floor(math.sin(t) * 127) t = (t + dt) % (math.pi * 2) end while not speaker.playAudio(buffer) do os.pullEvent("speaker_audio_empty") end end ``` ### Play a DFPWM file Read in 16 KiB chunks, decode, play: ```lua local dfpwm = require("cc.audio.dfpwm") local speaker = peripheral.find("speaker") local decoder = dfpwm.make_decoder() for chunk in io.lines("data/example.dfpwm", 16 * 1024) do local buffer = decoder(chunk) while not speaker.playAudio(buffer) do os.pullEvent("speaker_audio_empty") end end ``` ### Delay / echo effect (ring buffer) PCM's simplicity lets you mix and manipulate samples directly. Mix streams by adding amplitudes; change playback rate by dropping samples; etc. This adds a 1.5-second delay. Scale values (0.6 original + 0.4 delayed) to avoid clipping past -128..127: ```lua local dfpwm = require("cc.audio.dfpwm") local speaker = peripheral.find("speaker") local samples_i, samples_n = 1, 48000 * 1.5 local samples = {} for i = 1, samples_n do samples[i] = 0 end local decoder = dfpwm.make_decoder() for chunk in io.lines("data/example.dfpwm", 16 * 1024) do local buffer = decoder(chunk) for i = 1, #buffer do local original_value = buffer[i] buffer[i] = original_value * 0.6 + samples[samples_i] * 0.4 samples[samples_i] = original_value samples_i = samples_i + 1 if samples_i > samples_n then samples_i = 1 end end while not speaker.playAudio(buffer) do os.pullEvent("speaker_audio_empty") end sleep(0.05) end ``` --- ## 6. Quick reference | Need | Use | | --- | --- | | Note block tone | `speaker.playNote(instrument, vol, pitch)` | | Minecraft sound effect | `speaker.playSound(name, vol, pitch)` | | Arbitrary PCM playback | `speaker.playAudio(buffer, vol)` | | Stop everything | `speaker.stop()` | | Decode DFPWM → PCM | `dfpwm.make_decoder()` / `dfpwm.decode(s)` | | Encode PCM → DFPWM | `dfpwm.make_encoder()` / `dfpwm.encode(t)` | | Backpressure signal | `os.pullEvent("speaker_audio_empty")` | **Key constants:** 48 kHz sample rate · 8-bit samples (-128..127) · ≤128k samples per `playAudio` call · 16 KiB is a common DFPWM chunk size.