github.com/noriah/catnip@v1.8.5/processor/threaded.go (about) 1 package processor 2 3 import ( 4 "context" 5 "sync" 6 7 "github.com/noriah/catnip/dsp" 8 "github.com/noriah/catnip/dsp/window" 9 "github.com/noriah/catnip/fft" 10 "github.com/noriah/catnip/input" 11 ) 12 13 type threadedProcessor struct { 14 channelCount int 15 16 bars int 17 18 fftBufs [][]complex128 19 barBufs [][]float64 20 21 peaks []float64 22 kicks []chan bool 23 24 wg sync.WaitGroup 25 ctx context.Context 26 cancel context.CancelFunc 27 28 // Double-buffer the audio samples so we can read on it again while the code 29 // is processing it. 30 inputBufs [][]input.Sample 31 32 plans []*fft.Plan 33 34 anlz dsp.Analyzer 35 smth dsp.Smoother 36 out Output 37 } 38 39 func NewThreaded(cfg Config) *threadedProcessor { 40 vis := &threadedProcessor{ 41 channelCount: cfg.ChannelCount, 42 fftBufs: make([][]complex128, cfg.ChannelCount), 43 barBufs: make([][]float64, cfg.ChannelCount), 44 peaks: make([]float64, cfg.ChannelCount), 45 kicks: make([]chan bool, cfg.ChannelCount), 46 inputBufs: cfg.Buffers, 47 plans: make([]*fft.Plan, cfg.ChannelCount), 48 anlz: cfg.Analyzer, 49 smth: cfg.Smoother, 50 out: cfg.Output, 51 } 52 53 for idx := range vis.barBufs { 54 vis.barBufs[idx] = make([]float64, cfg.SampleSize) 55 vis.fftBufs[idx] = make([]complex128, cfg.SampleSize/2+1) 56 vis.kicks[idx] = make(chan bool, 1) 57 58 fft.InitPlan(&vis.plans[idx], vis.inputBufs[idx], vis.fftBufs[idx]) 59 } 60 61 return vis 62 } 63 64 func (vis *threadedProcessor) channelProcessor(ch int, kick <-chan bool) { 65 buffer := vis.inputBufs[ch] 66 plan := vis.plans[ch] 67 barBuf := vis.barBufs[ch] 68 fftBuf := vis.fftBufs[ch] 69 70 windower := window.Lanczos() 71 72 for { 73 select { 74 case <-vis.ctx.Done(): 75 return 76 case <-kick: 77 } 78 79 windower(buffer) 80 plan.Execute() 81 82 for i := range barBuf[:vis.bars] { 83 v := vis.anlz.ProcessBin(i, fftBuf) 84 v = vis.smth.SmoothBin(ch, i, v) 85 86 barBuf[i] = v 87 } 88 89 vis.wg.Done() 90 } 91 } 92 93 func (vis *threadedProcessor) Start(ctx context.Context, kickChan chan bool, mu *sync.Mutex) context.Context { 94 vis.ctx, vis.cancel = context.WithCancel(ctx) 95 96 for i, kick := range vis.kicks { 97 go vis.channelProcessor(i, kick) 98 } 99 100 return vis.ctx 101 } 102 103 func (vis *threadedProcessor) Stop() { 104 if vis.cancel != nil { 105 vis.cancel() 106 } 107 } 108 109 // Process runs one draw refresh with the visualizer on the termbox screen. 110 func (vis *threadedProcessor) Process() { 111 if n := vis.out.Bins(vis.channelCount); n != vis.bars { 112 vis.bars = vis.anlz.Recalculate(n) 113 } 114 115 vis.wg.Add(vis.channelCount) 116 117 for _, kick := range vis.kicks { 118 kick <- true 119 } 120 121 vis.wg.Wait() 122 123 vis.out.Write(vis.barBufs, vis.channelCount) 124 }