package app import ( "context" "encoding/binary" "fmt" "path/filepath" "time" cfgpkg "github.com/jan/fm-rds-tx/internal/config" offpkg "github.com/jan/fm-rds-tx/internal/offline" "github.com/jan/fm-rds-tx/internal/output" "github.com/jan/fm-rds-tx/internal/platform" ) func RunSimulatedTransmit(cfg cfgpkg.Config, outPath string, duration time.Duration) (string, error) { if outPath == "" { outPath = filepath.Join("build", "sim", "simulated-soapy.iqf32") } fileBackend, err := output.NewFileBackend(outPath, binary.LittleEndian, output.BackendInfo{ Name: "simulated-soapy-file", Description: "simulated soapy sink to file", }) if err != nil { return "", err } defer fileBackend.Close(context.Background()) soapyCfg := platform.SoapyConfig{ BackendConfig: output.BackendConfig{ SampleRateHz: float64(cfg.FM.CompositeRateHz), Channels: 2, IQLevel: float32(cfg.FM.OutputDrive), }, Driver: "simulated", Device: cfg.Backend.Device, CenterFreqHz: cfg.FM.FrequencyMHz * 1_000_000, Simulated: true, SimulationPath: outPath, } backend := platform.NewSoapyBackend(soapyCfg, platform.NewSimulatedDriver(fileBackend)) if err := backend.Configure(context.Background(), soapyCfg.BackendConfig); err != nil { return "", err } gen := offpkg.NewGenerator(cfg) frame := gen.GenerateFrame(duration) if _, err := backend.Write(context.Background(), frame); err != nil { return "", err } if err := backend.Flush(context.Background()); err != nil { return "", err } return fmt.Sprintf("simulated transmit: backend=%s output=%s duration=%s input=%s", backend.Info().Name, outPath, duration, inputLabel(cfg)), nil } func inputLabel(cfg cfgpkg.Config) string { if cfg.Audio.InputPath != "" { return cfg.Audio.InputPath } return "tones" }