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  }