|
|
|
@@ -8,8 +8,12 @@ import ( |
|
|
|
"path/filepath" |
|
|
|
"time" |
|
|
|
|
|
|
|
"github.com/jan/fm-rds-tx/internal/audio" |
|
|
|
cfgpkg "github.com/jan/fm-rds-tx/internal/config" |
|
|
|
"github.com/jan/fm-rds-tx/internal/mpx" |
|
|
|
"github.com/jan/fm-rds-tx/internal/output" |
|
|
|
"github.com/jan/fm-rds-tx/internal/rds" |
|
|
|
"github.com/jan/fm-rds-tx/internal/stereo" |
|
|
|
) |
|
|
|
|
|
|
|
type Generator struct { |
|
|
|
@@ -37,20 +41,35 @@ func (g *Generator) GenerateFrame(duration time.Duration) *output.CompositeFrame |
|
|
|
Sequence: 1, |
|
|
|
} |
|
|
|
|
|
|
|
stereoEncoder := stereo.NewStereoEncoder(sampleRate) |
|
|
|
combiner := mpx.NewDefaultCombiner() |
|
|
|
combiner.PilotGain = g.cfg.FM.PilotLevel |
|
|
|
combiner.RDSGain = g.cfg.FM.RDSInjection |
|
|
|
|
|
|
|
rdsEnc, _ := rds.NewEncoder(rds.RDSConfig{ |
|
|
|
PI: 0x1234, |
|
|
|
PS: g.cfg.RDS.PS, |
|
|
|
RT: g.cfg.RDS.RadioText, |
|
|
|
PTY: uint8(g.cfg.RDS.PTY), |
|
|
|
SampleRate: sampleRate, |
|
|
|
}) |
|
|
|
rdsSamples := rdsEnc.Generate(samples) |
|
|
|
|
|
|
|
leftFreq := 1000.0 |
|
|
|
rightFreq := 1600.0 |
|
|
|
pilotFreq := 19000.0 |
|
|
|
rdsFreq := 57000.0 |
|
|
|
stereoCarrierFreq := 38000.0 |
|
|
|
|
|
|
|
for i := 0; i < samples; i++ { |
|
|
|
t := float64(i) / sampleRate |
|
|
|
left := 0.4 * math.Sin(2*math.Pi*leftFreq*t) |
|
|
|
right := 0.4 * math.Sin(2*math.Pi*rightFreq*t+math.Pi/3) |
|
|
|
mono := (left + right) / 2 |
|
|
|
stereo := (left - right) / 2 * 0.8 * math.Sin(2*math.Pi*38000*t) |
|
|
|
pilot := g.cfg.FM.PilotLevel * math.Sin(2*math.Pi*pilotFreq*t) |
|
|
|
rds := g.cfg.FM.RDSInjection * math.Sin(2*math.Pi*rdsFreq*t) |
|
|
|
composite := (mono + stereo + pilot + rds) * g.cfg.FM.OutputDrive |
|
|
|
left := audio.Sample(0.4 * math.Sin(2*math.Pi*leftFreq*t)) |
|
|
|
right := audio.Sample(0.4 * math.Sin(2*math.Pi*rightFreq*t+math.Pi/3)) |
|
|
|
comps := stereoEncoder.Encode(audio.NewFrame(left, right)) |
|
|
|
stereoDSB := comps.Stereo * math.Sin(2*math.Pi*stereoCarrierFreq*t) |
|
|
|
rdsValue := 0.0 |
|
|
|
if g.cfg.RDS.Enabled && i < len(rdsSamples) { |
|
|
|
rdsValue = rdsSamples[i] |
|
|
|
} |
|
|
|
composite := combiner.Combine(comps.Mono, stereoDSB, comps.Pilot, rdsValue) * g.cfg.FM.OutputDrive |
|
|
|
frame.Samples[i] = output.IQSample{I: float32(composite), Q: 0} |
|
|
|
} |
|
|
|
|
|
|
|
@@ -92,5 +111,5 @@ func (g *Generator) WriteFile(path string, duration time.Duration) error { |
|
|
|
} |
|
|
|
|
|
|
|
func (g *Generator) Summary(duration time.Duration) string { |
|
|
|
return fmt.Sprintf("offline frame: freq=%.1fMHz sampleRate=%d duration=%s outputDrive=%.2f", g.cfg.FM.FrequencyMHz, g.cfg.FM.CompositeRateHz, duration.String(), g.cfg.FM.OutputDrive) |
|
|
|
return fmt.Sprintf("offline frame: freq=%.1fMHz sampleRate=%d duration=%s outputDrive=%.2f stereo=%t rds=%t", g.cfg.FM.FrequencyMHz, g.cfg.FM.CompositeRateHz, duration.String(), g.cfg.FM.OutputDrive, g.cfg.FM.StereoEnabled, g.cfg.RDS.Enabled) |
|
|
|
} |