|
- package recorder
-
- import (
- "sync"
- "time"
- )
-
- type iqBlock struct {
- t0 time.Time
- samples []complex64
- }
-
- // Ring keeps recent IQ blocks for preroll capture.
- type Ring struct {
- mu sync.RWMutex
- blocks []iqBlock
- maxSamples int
- total int
- sampleRate int
- }
-
- func NewRing(sampleRate int, blockSize int, seconds int) *Ring {
- if seconds <= 0 {
- seconds = 5
- }
- if sampleRate <= 0 {
- sampleRate = 2_048_000
- }
- if blockSize <= 0 {
- blockSize = 2048
- }
- maxSamples := sampleRate * seconds
- minSamples := blockSize * 2
- if minSamples < blockSize {
- minSamples = blockSize
- }
- if maxSamples < minSamples {
- maxSamples = minSamples
- }
- return &Ring{maxSamples: maxSamples, sampleRate: sampleRate}
- }
-
- func (r *Ring) Reset(sampleRate int, blockSize int, seconds int) {
- *r = *NewRing(sampleRate, blockSize, seconds)
- }
-
- func (r *Ring) Push(t0 time.Time, samples []complex64) {
- if r == nil || len(samples) == 0 {
- return
- }
- r.mu.Lock()
- defer r.mu.Unlock()
- cp := append([]complex64(nil), samples...)
- r.blocks = append(r.blocks, iqBlock{t0: t0, samples: cp})
- r.total += len(cp)
- for r.total > r.maxSamples && len(r.blocks) > 0 {
- overflow := r.total - r.maxSamples
- head := r.blocks[0]
- if overflow >= len(head.samples) {
- r.total -= len(head.samples)
- r.blocks = r.blocks[1:]
- continue
- }
- trim := overflow
- advance := time.Duration(float64(trim) / float64(r.sampleRate) * float64(time.Second))
- head.t0 = head.t0.Add(advance)
- head.samples = head.samples[trim:]
- r.blocks[0] = head
- r.total -= trim
- }
- }
-
- func (r *Ring) MaxSamples() int {
- if r == nil {
- return 0
- }
- r.mu.RLock()
- defer r.mu.RUnlock()
- return r.maxSamples
- }
-
- // Slice returns IQ samples between [start,end] (best-effort).
- func (r *Ring) Slice(start, end time.Time) []complex64 {
- if r == nil || end.Before(start) {
- return nil
- }
- r.mu.RLock()
- defer r.mu.RUnlock()
- var out []complex64
- for _, b := range r.blocks {
- blockDur := time.Duration(float64(len(b.samples)) / float64(r.sampleRate) * float64(time.Second))
- bEnd := b.t0.Add(blockDur)
- if bEnd.Before(start) || b.t0.After(end) {
- continue
- }
- // compute overlap
- oStart := maxTime(start, b.t0)
- oEnd := minTime(end, bEnd)
- if oEnd.Before(oStart) {
- continue
- }
- startIdx := int(float64(oStart.Sub(b.t0)) / float64(time.Second) * float64(r.sampleRate))
- endIdx := int(float64(oEnd.Sub(b.t0)) / float64(time.Second) * float64(r.sampleRate))
- if startIdx < 0 {
- startIdx = 0
- }
- if endIdx > len(b.samples) {
- endIdx = len(b.samples)
- }
- if endIdx > startIdx {
- out = append(out, b.samples[startIdx:endIdx]...)
- }
- }
- return out
- }
-
- func minTime(a, b time.Time) time.Time {
- if a.Before(b) {
- return a
- }
- return b
- }
-
- func maxTime(a, b time.Time) time.Time {
- if a.After(b) {
- return a
- }
- return b
- }
|