🎉 live server seems to be working now
This commit is contained in:
227
third_party/sendspin-go/examples/custom-source/main.go
vendored
Normal file
227
third_party/sendspin-go/examples/custom-source/main.go
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
// ABOUTME: Custom AudioSource implementation example
|
||||
// ABOUTME: Demonstrates how to create custom audio sources for Sendspin
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sendspin/sendspin-go/pkg/sendspin"
|
||||
)
|
||||
|
||||
// MultiToneSource generates multiple sine waves at different frequencies
|
||||
// This demonstrates how to implement the AudioSource interface
|
||||
type MultiToneSource struct {
|
||||
frequencies []float64
|
||||
sampleRate int
|
||||
channels int
|
||||
sampleIndex uint64
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewMultiTone creates a source that mixes multiple frequencies
|
||||
func NewMultiTone(sampleRate, channels int, frequencies []float64) *MultiToneSource {
|
||||
return &MultiToneSource{
|
||||
frequencies: frequencies,
|
||||
sampleRate: sampleRate,
|
||||
channels: channels,
|
||||
}
|
||||
}
|
||||
|
||||
// Read implements AudioSource.Read
|
||||
// Generates PCM samples by mixing multiple sine waves
|
||||
func (s *MultiToneSource) Read(samples []int32) (int, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
numFrames := len(samples) / s.channels
|
||||
|
||||
for i := 0; i < numFrames; i++ {
|
||||
// Calculate time for this sample
|
||||
t := float64(s.sampleIndex+uint64(i)) / float64(s.sampleRate)
|
||||
|
||||
// Mix all frequencies together
|
||||
var mixedSample float64
|
||||
for _, freq := range s.frequencies {
|
||||
mixedSample += math.Sin(2 * math.Pi * freq * t)
|
||||
}
|
||||
|
||||
// Average the mixed signal
|
||||
if len(s.frequencies) > 0 {
|
||||
mixedSample /= float64(len(s.frequencies))
|
||||
}
|
||||
|
||||
// Convert to 24-bit PCM (int32)
|
||||
// Scale to 24-bit range with 50% volume to prevent clipping
|
||||
const max24bit = 8388607 // 2^23 - 1
|
||||
pcmValue := int32(mixedSample * max24bit * 0.5)
|
||||
|
||||
// Duplicate to all channels
|
||||
for ch := 0; ch < s.channels; ch++ {
|
||||
samples[i*s.channels+ch] = pcmValue
|
||||
}
|
||||
}
|
||||
|
||||
s.sampleIndex += uint64(numFrames)
|
||||
|
||||
return len(samples), nil
|
||||
}
|
||||
|
||||
// SampleRate implements AudioSource.SampleRate
|
||||
func (s *MultiToneSource) SampleRate() int {
|
||||
return s.sampleRate
|
||||
}
|
||||
|
||||
// Channels implements AudioSource.Channels
|
||||
func (s *MultiToneSource) Channels() int {
|
||||
return s.channels
|
||||
}
|
||||
|
||||
// Metadata implements AudioSource.Metadata
|
||||
func (s *MultiToneSource) Metadata() (title, artist, album string) {
|
||||
return "Multi-Tone Test Signal", "Sendspin Examples", "Custom Sources"
|
||||
}
|
||||
|
||||
// Close implements AudioSource.Close
|
||||
func (s *MultiToneSource) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SweepSource generates a frequency sweep (chirp)
|
||||
// Demonstrates time-varying audio generation
|
||||
type SweepSource struct {
|
||||
startFreq float64
|
||||
endFreq float64
|
||||
duration float64 // seconds
|
||||
sampleRate int
|
||||
channels int
|
||||
sampleIndex uint64
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewSweep creates a frequency sweep source
|
||||
func NewSweep(sampleRate, channels int, startFreq, endFreq, duration float64) *SweepSource {
|
||||
return &SweepSource{
|
||||
startFreq: startFreq,
|
||||
endFreq: endFreq,
|
||||
duration: duration,
|
||||
sampleRate: sampleRate,
|
||||
channels: channels,
|
||||
}
|
||||
}
|
||||
|
||||
// Read implements AudioSource.Read
|
||||
func (s *SweepSource) Read(samples []int32) (int, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
numFrames := len(samples) / s.channels
|
||||
|
||||
for i := 0; i < numFrames; i++ {
|
||||
t := float64(s.sampleIndex+uint64(i)) / float64(s.sampleRate)
|
||||
|
||||
// Loop the sweep
|
||||
t = math.Mod(t, s.duration)
|
||||
|
||||
// Linear frequency sweep
|
||||
progress := t / s.duration
|
||||
freq := s.startFreq + (s.endFreq-s.startFreq)*progress
|
||||
|
||||
// Generate sine wave at current frequency
|
||||
sample := math.Sin(2 * math.Pi * freq * t)
|
||||
|
||||
// Convert to 24-bit PCM
|
||||
const max24bit = 8388607
|
||||
pcmValue := int32(sample * max24bit * 0.5)
|
||||
|
||||
// Duplicate to all channels
|
||||
for ch := 0; ch < s.channels; ch++ {
|
||||
samples[i*s.channels+ch] = pcmValue
|
||||
}
|
||||
}
|
||||
|
||||
s.sampleIndex += uint64(numFrames)
|
||||
|
||||
return len(samples), nil
|
||||
}
|
||||
|
||||
func (s *SweepSource) SampleRate() int { return s.sampleRate }
|
||||
func (s *SweepSource) Channels() int { return s.channels }
|
||||
func (s *SweepSource) Metadata() (string, string, string) {
|
||||
return "Frequency Sweep", "Sendspin Examples", "Custom Sources"
|
||||
}
|
||||
func (s *SweepSource) Close() error { return nil }
|
||||
|
||||
func main() {
|
||||
port := flag.Int("port", 8927, "Server port")
|
||||
mode := flag.String("mode", "chord", "Source mode: chord, sweep, single")
|
||||
sampleRate := flag.Int("rate", 192000, "Sample rate (Hz)")
|
||||
channels := flag.Int("channels", 2, "Number of channels")
|
||||
flag.Parse()
|
||||
|
||||
var source sendspin.AudioSource
|
||||
|
||||
// Create audio source based on mode
|
||||
switch *mode {
|
||||
case "chord":
|
||||
// A major chord: A4, C#5, E5
|
||||
frequencies := []float64{440.0, 554.37, 659.25}
|
||||
source = NewMultiTone(*sampleRate, *channels, frequencies)
|
||||
log.Printf("Creating A major chord: %v Hz", frequencies)
|
||||
|
||||
case "sweep":
|
||||
// Sweep from 220 Hz (A3) to 880 Hz (A5) over 5 seconds
|
||||
source = NewSweep(*sampleRate, *channels, 220.0, 880.0, 5.0)
|
||||
log.Printf("Creating frequency sweep: 220 - 880 Hz over 5 seconds")
|
||||
|
||||
case "single":
|
||||
// Single 440 Hz tone (same as basic example)
|
||||
source = sendspin.NewTestTone(*sampleRate, *channels)
|
||||
log.Printf("Creating single tone: 440 Hz")
|
||||
|
||||
default:
|
||||
log.Fatalf("Invalid mode: %s (use: chord, sweep, single)", *mode)
|
||||
}
|
||||
|
||||
config := sendspin.ServerConfig{
|
||||
Port: *port,
|
||||
Name: "Custom Source Example",
|
||||
Source: source,
|
||||
EnableMDNS: true,
|
||||
Debug: false,
|
||||
}
|
||||
|
||||
server, err := sendspin.NewServer(config)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create server: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Starting server on port %d...", *port)
|
||||
log.Printf("Audio: %dHz, %d channels, 24-bit", *sampleRate, *channels)
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
if err := server.Start(); err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
log.Printf("\nServer running. Press Ctrl+C to stop")
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
select {
|
||||
case <-sigChan:
|
||||
log.Printf("Shutting down...")
|
||||
case err := <-errChan:
|
||||
log.Printf("Server error: %v", err)
|
||||
}
|
||||
|
||||
server.Stop()
|
||||
log.Printf("Server stopped")
|
||||
}
|
||||
Reference in New Issue
Block a user