Custom Audio Source Example
This example demonstrates how to implement the AudioSource interface to create custom audio generators for streaming.
What it does
This example shows three different custom audio source implementations:
- MultiToneSource - Mixes multiple sine waves (creates chords)
- SweepSource - Generates frequency sweeps (chirps)
- Built-in TestTone - Single frequency tone (for comparison)
Each demonstrates different aspects of the AudioSource interface.
Building
cd examples/custom-source
go build
Running
Generate an A major chord (440, 554, 659 Hz):
./custom-source -mode chord
Generate a frequency sweep from 220 to 880 Hz:
./custom-source -mode sweep
Generate a single 440 Hz tone:
./custom-source -mode single
Command-line options
-port- Server port (default: 8927)-mode- Source mode: chord, sweep, single (default: chord)-rate- Sample rate in Hz (default: 192000)-channels- Number of channels (default: 2)
AudioSource Interface
To create a custom audio source, implement this interface:
type AudioSource interface {
// Read fills the buffer with PCM samples (int32 for 24-bit audio)
Read(samples []int32) (int, error)
// SampleRate returns the sample rate
SampleRate() int
// Channels returns the number of channels
Channels() int
// Metadata returns title, artist, album
Metadata() (title, artist, album string)
// Close closes the audio source
Close() error
}
Implementation details
MultiToneSource
Generates multiple sine waves and mixes them together:
type MultiToneSource struct {
frequencies []float64
sampleRate int
channels int
sampleIndex uint64
mu sync.Mutex
}
func (s *MultiToneSource) Read(samples []int32) (int, error) {
// For each sample frame:
for i := 0; i < numFrames; i++ {
t := float64(s.sampleIndex + i) / float64(s.sampleRate)
// Mix all frequencies
var mixed float64
for _, freq := range s.frequencies {
mixed += math.Sin(2 * math.Pi * freq * t)
}
mixed /= float64(len(s.frequencies))
// Convert to 24-bit PCM (int32)
pcmValue := int32(mixed * 8388607 * 0.5)
// Write to all channels
for ch := 0; ch < s.channels; ch++ {
samples[i*s.channels + ch] = pcmValue
}
}
return len(samples), nil
}
SweepSource
Generates a time-varying frequency sweep:
func (s *SweepSource) Read(samples []int32) (int, error) {
for i := 0; i < numFrames; i++ {
t := float64(s.sampleIndex + i) / float64(s.sampleRate)
// Calculate current frequency based on progress
progress := math.Mod(t, s.duration) / s.duration
freq := s.startFreq + (s.endFreq - s.startFreq) * progress
// Generate sine wave at current frequency
sample := math.Sin(2 * math.Pi * freq * t)
pcmValue := int32(sample * 8388607 * 0.5)
// Write to all channels
for ch := 0; ch < s.channels; ch++ {
samples[i*s.channels + ch] = pcmValue
}
}
return len(samples), nil
}
Key concepts
- Sample format: Use
int32for 24-bit audio (range: -8388608 to 8388607) - Interleaving: For stereo (2 channels), samples are stored as [L, R, L, R, ...]
- Thread safety: Use mutex if internal state is accessed from multiple goroutines
- Continuous playback: Track
sampleIndexto maintain phase continuity
Use cases for custom sources
- File streaming: Read from MP3, FLAC, WAV files
- Live input: Capture from microphone or line-in
- Synthesizers: Generate music programmatically
- Network streams: Proxy audio from internet radio
- Audio processing: Apply effects, mixing, filtering
- Testing: Generate test signals for debugging
Next steps
- Implement a file-based source using
pkg/audio/decode - Add audio effects (reverb, echo, filters)
- Create a source that mixes multiple input sources
- Implement a source that reads from an audio device