lightshowpi/sound/fm.go

88 lines
1.7 KiB
Go

package sound
import (
"context"
"fmt"
"io"
"os/exec"
)
// FMConfig defines the tunables for an FM instance
type FMConfig struct {
Frequency float32
}
// FM is a sound.Output implementation that outputs to an FM frequency
type FM struct {
config *FMConfig
}
// NewFM creates a new FM instance with the given config
func NewFM(config FMConfig) *FM {
return &FM{
config: &config,
}
}
// Play the given input to the FM frequency
func (f *FM) Play(ctx context.Context, filePath string) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
soxCmd := exec.CommandContext(
ctx,
"sox",
filePath,
"-r", "22050",
"-c", "1",
"-b", "16",
"-t", "wav",
"-",
)
reader, err := soxCmd.StdoutPipe()
if err != nil {
return fmt.Errorf("failed to open STDOUT pipe for converting the input file to the FM transmitter format: %w", err)
}
err = soxCmd.Start()
if err != nil {
return fmt.Errorf("failed to start conversion for the input file for the FM transmitter: %w", err)
}
fmCmd := exec.CommandContext(
ctx,
"fm_transmitter",
"-f",
fmt.Sprintf("%.1f", f.config.Frequency),
"-",
)
writer, err := fmCmd.StdinPipe()
if err != nil {
return fmt.Errorf("failed to create STDIN pipe for FM transmitter: %w", err)
}
err = fmCmd.Start()
if err != nil {
return fmt.Errorf("failed to start FM transmitter command: %w", err)
}
_, err = io.Copy(writer, reader)
if err != nil {
return fmt.Errorf("failed to write audio output to FM transmitter: %w", err)
}
err = soxCmd.Wait()
if err != nil {
return fmt.Errorf("audio conversion command resulted in an error: %w", err)
}
err = fmCmd.Wait()
if err != nil {
return fmt.Errorf("FM transmitter command resulted in an error: %w", err)
}
return nil
}