package main import ( "sort" "strconv" "time" "sdr-visual-suite/internal/config" "sdr-visual-suite/internal/demod/gpudemod" "sdr-visual-suite/internal/dsp" ) func mustParseDuration(raw string, fallback time.Duration) time.Duration { if raw == "" { return fallback } if d, err := time.ParseDuration(raw); err == nil { return d } return fallback } func buildDecoderMap(cfg config.Config) map[string]string { out := map[string]string{} if cfg.Decoder.FT8Cmd != "" { out["FT8"] = cfg.Decoder.FT8Cmd } if cfg.Decoder.WSPRCmd != "" { out["WSPR"] = cfg.Decoder.WSPRCmd } if cfg.Decoder.DMRCmd != "" { out["DMR"] = cfg.Decoder.DMRCmd } if cfg.Decoder.DStarCmd != "" { out["D-STAR"] = cfg.Decoder.DStarCmd } if cfg.Decoder.FSKCmd != "" { out["FSK"] = cfg.Decoder.FSKCmd } if cfg.Decoder.PSKCmd != "" { out["PSK"] = cfg.Decoder.PSKCmd } return out } func decoderKeys(cfg config.Config) []string { m := buildDecoderMap(cfg) keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } sort.Strings(keys) return keys } func extractSignalIQ(iq []complex64, sampleRate int, centerHz float64, sigHz float64, bwHz float64) []complex64 { if len(iq) == 0 || sampleRate <= 0 { return nil } offset := sigHz - centerHz decimTarget := 200000 if decimTarget <= 0 { decimTarget = sampleRate } if gpudemod.Available() { if eng, err := gpudemod.New(len(iq), sampleRate); err == nil { defer eng.Close() if out, _, err := eng.ShiftFilterDecimate(iq, offset, bwHz, decimTarget); err == nil && len(out) > 0 { return out } } } shifted := dsp.FreqShift(iq, sampleRate, offset) cutoff := bwHz / 2 if cutoff < 200 { cutoff = 200 } if cutoff > float64(sampleRate)/2-1 { cutoff = float64(sampleRate)/2 - 1 } taps := dsp.LowpassFIR(cutoff, sampleRate, 101) filtered := dsp.ApplyFIR(shifted, taps) decim := sampleRate / decimTarget if decim < 1 { decim = 1 } return dsp.Decimate(filtered, decim) } func parseSince(raw string) (time.Time, error) { if raw == "" { return time.Time{}, nil } if ms, err := strconv.ParseInt(raw, 10, 64); err == nil { if ms > 1e12 { return time.UnixMilli(ms), nil } return time.Unix(ms, 0), nil } if t, err := time.Parse(time.RFC3339Nano, raw); err == nil { return t, nil } return time.Parse(time.RFC3339, raw) }