🧱 Basics #
Sound in Firefly Zero is constructed as a tree of nodes. There is already one node present, called OUT
. Everything connected to it will play on the speakers (or headphones) of the device. Some nodes, called sources, can produce a sound by generating it using math or by reading it from memory or file system. For example, to play note A in 4th octave (440 Hz) using a sine wave:
Loud sounds ahead!
Turn your volume down. Loud sounds may damage your hearing. Also, when you experiment with the sound by yourself, keep in mind that you won’t hear too low or too high frequences but they still can harm your hearing if played too loud.
#![no_std]
#![no_main]
use firefly_rust::audio;
#[no_mangle]
extern fn boot() {
audio::OUT.add_sine(audio::Freq::A4, 0.);
}
package main
import (
"github.com/firefly-zero/firefly-go/firefly"
"github.com/firefly-zero/firefly-go/firefly/audio"
)
func init() {
firefly.Boot = boot
}
func boot() {
audio.Out.AddSine(audio.A4, 0.)
}
#include "./vendor/firefly/firefly.c"
BOOT void boot()
{
add_sine(OUT, 440.0, 0.0);
}
If you want to visualize audio, try Friture, a free and open-source cross-platform program that can show frequencies of any audio you play. Playing the example above should show a sharp spike at 440 Hz on the “FFT Spectrum” panel.
You can connect up to 4 child nodes to a single node, including OUT
. For example, to play the chord C (notes C, E, and G):
audio::OUT.add_sine(audio::Freq::C4, 0.);
audio::OUT.add_sine(audio::Freq::E4, 0.);
audio::OUT.add_sine(audio::Freq::G4, 0.);
audio.Out.AddSine(audio.A4, 0.)
audio.Out.AddSine(audio.E4, 0.)
audio.Out.AddSine(audio.G4, 0.)
add_sine(OUT, 440.0, 0.0);
add_sine(OUT, 329.628, 0.0);
add_sine(OUT, 391.995, 0.0);
In between the source nodes and the output, you can add effect nodes that modify the sound that comes through them. For example, the Gain
node can be used to increase or decrease the gain (volume) of the sound:
let gain = audio::OUT.add_gain(0.5);
gain.add_sine(audio::Freq::A4, 0.);
gain := audio.Out.AddGain(0.5)
gain.AddSine(audio.A4, 0.)
AudioNode gain = add_gain(OUT, 0.5);
add_sine(gain, 440.0, 0.0);
Finally, you can change parameters of nodes over time using modulators. For example, use LinearModulator
to linearly raise gain value from 0 to 1 in a span of 2 seconds:
let gain = audio::OUT.add_gain(0.);
gain.modulate(audio::LinearModulator {
start: 0.,
end: 1.,
start_at: audio::Time::ZERO,
end_at: audio::Time::seconds(2),
});
gain.add_sine(audio::Freq::A4, 0.);
gain := audio.Out.AddGain(0.)
gain.Modulate(audio.LinearModulator {
Start: 0.,
End: 1.,
StartAt: audio.Seconds(0),
EndAt: audio.Seconds(2),
});
gain.AddSine(audio.A4, 0.)
AudioNode gain = add_gain(OUT, 0.0);
mod_linear(gain, LinearModulator {
.start = 0.0,
.end = 1.0,
.start_at = seconds(0),
.end_at = seconds(2)});
add_sine(gain, 440.0, 0.0);
💽 Playing audio files #
Apps can play WAV files from ROM. First, include the file into ROM using firefly.toml
:
[files]
muzak = { path = "muzak.wav" }
Now, pass the file path into add_file
method of an audio node:
audio::OUT.add_file("muzak");
audio.Out.AddFile("muzak")
add_file(OUT, "muzak")
🧰 Available nodes #
Sources:
-
empty
: empty audio source that is always stopped. -
zero
: audio source producing silence. -
file
: audio file from ROM. -
sine
: sine wave generator. -
square
: square wave generator. -
sawtooth
: sawtooth wave generator. -
triangle
: triangle wave generator. -
noise
: white noise generator.
The sine, square, triangle, sawtooth, and white noise are the most basic sounds. You can learn more about them and about how sound works in an amazing interactive tutorial Let’s learn about waveforms.
Playback control:
mute
: consumes the input nodes but produces silence.pause
: can be paused using modulation.loop
: resets the input if it stops.
Left and right channel mixing:
pan
: shift the audio to the left (0.0), right (1.0), or something in between.take_left
: convert stereo to mono by taking the left channel.take_right
: convert stereo to mono by taking the right channel.swap
: swap left and right channels of the stereo input.
Nodes mixing:
mix
: mix all the inputs. Stops only if all input sources stop. Most of the other nodes (including the rootOUT
node), if given multiple inputs, will behave the same asmix
.all_for_one
: same asmix
but stops when any of the sources stops.concat
: play the inputs one after the other, in the order as they added. An input is played if all previous inputs have stopped.
Misc:
clip
: clipping. Clamps the input amplitude. Can be used for hard distortion.gain
: increases or decreases gain by the given multiplier.track_position
: can be used to track how much time has passed since this node was added (or reset).high_pass
: high-pass filter.low_pass
: low-pass filter.
🎛 Available modulators #
LFOs:
-
SineModulator
: sine wave low-frequency oscillator. It looks like this:∿
. Parameters are oscillation frequencyfreq
, lowest produced valuelow
, and highest produced valuehigh
.
Envelopes:
-
LinearModulator
: linear (ramp up or down) envelope. It looks like this:⎽╱⎺
or⎺╲⎽
. The value beforestart_at
isstart
, the value afterend_at
isend
, and the value betweenstart_at
andend_at
changes linearly fromstart
toend
. -
HoldModulator
: hold envelope. It looks like this:⎽│⎺
or⎺│⎽
. The value beforetime
isbefore
and the value aftertime
isafter
. Equivalent toLinearModulator
withstart_at
being equal toend_at
.