8.2 KiB
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:
- https://tweaked.cc/library/cc.audio.dfpwm.html
- https://tweaked.cc/peripheral/speaker.html
- https://tweaked.cc/guide/speaker_audio.html
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, default1.0.pitch(number, optional): semitones0–24, default12.- Returns: boolean —
falseif 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, default1.0.pitch(number, optional): speed multiplier0.5–2.0, default1.0.- Returns: boolean —
falseif another sound already started this tick or audio is currently playing. - Throws: if the sound name is invalid.
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 —
trueif the buffer accepted the data,falseif 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 thespeaker_audio_emptyevent before retrying. - Samples are re-encoded before playback.
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:
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:
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:
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:
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:
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.