| @@ -8,8 +8,12 @@ import ( | |||||
| "path/filepath" | "path/filepath" | ||||
| "time" | "time" | ||||
| "github.com/jan/fm-rds-tx/internal/audio" | |||||
| cfgpkg "github.com/jan/fm-rds-tx/internal/config" | 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/output" | ||||
| "github.com/jan/fm-rds-tx/internal/rds" | |||||
| "github.com/jan/fm-rds-tx/internal/stereo" | |||||
| ) | ) | ||||
| type Generator struct { | type Generator struct { | ||||
| @@ -37,20 +41,35 @@ func (g *Generator) GenerateFrame(duration time.Duration) *output.CompositeFrame | |||||
| Sequence: 1, | 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 | leftFreq := 1000.0 | ||||
| rightFreq := 1600.0 | rightFreq := 1600.0 | ||||
| pilotFreq := 19000.0 | |||||
| rdsFreq := 57000.0 | |||||
| stereoCarrierFreq := 38000.0 | |||||
| for i := 0; i < samples; i++ { | for i := 0; i < samples; i++ { | ||||
| t := float64(i) / sampleRate | 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} | 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 { | 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) | |||||
| } | } | ||||