github.com/noriah/catnip@v1.8.5/processor/processor.go (about)

     1  package processor
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/noriah/catnip/dsp"
     9  	"github.com/noriah/catnip/dsp/window"
    10  	"github.com/noriah/catnip/fft"
    11  	"github.com/noriah/catnip/input"
    12  )
    13  
    14  type Output interface {
    15  	Bins(int) int
    16  	Write([][]float64, int) error
    17  }
    18  
    19  type Processor interface {
    20  	Start(ctx context.Context, kickChan chan bool, mu *sync.Mutex) context.Context
    21  	Stop()
    22  	Process()
    23  }
    24  
    25  type Config struct {
    26  	SampleRate   float64          // rate at which samples are read
    27  	SampleSize   int              // number of samples per buffer
    28  	ChannelCount int              // number of channels
    29  	ProcessRate  int              // target framerate
    30  	Buffers      [][]input.Sample // sample buffers
    31  	Analyzer     dsp.Analyzer     // audio analyzer
    32  	Output       Output           // data output
    33  	Smoother     dsp.Smoother     // time smoother
    34  	Windower     window.Function  // data windower
    35  }
    36  
    37  type processor struct {
    38  	channelCount int
    39  	processRate  int
    40  
    41  	bars int
    42  
    43  	fftBufs [][]complex128
    44  	barBufs [][]float64
    45  
    46  	// Double-buffer the audio samples so we can read on it again while the code
    47  	// is processing it.
    48  	inputBufs [][]input.Sample
    49  
    50  	plans []*fft.Plan
    51  
    52  	mu        *sync.Mutex
    53  	ctxCancel context.CancelFunc
    54  
    55  	anlz  dsp.Analyzer
    56  	out   Output
    57  	smth  dsp.Smoother
    58  	wndwr window.Function
    59  }
    60  
    61  func New(cfg Config) *processor {
    62  
    63  	vis := &processor{
    64  		channelCount: cfg.ChannelCount,
    65  		processRate:  cfg.ProcessRate,
    66  		fftBufs:      make([][]complex128, cfg.ChannelCount),
    67  		barBufs:      make([][]float64, cfg.ChannelCount),
    68  		inputBufs:    cfg.Buffers,
    69  		plans:        make([]*fft.Plan, cfg.ChannelCount),
    70  		anlz:         cfg.Analyzer,
    71  		out:          cfg.Output,
    72  		smth:         cfg.Smoother,
    73  		wndwr:        cfg.Windower,
    74  	}
    75  
    76  	for idx := range vis.barBufs {
    77  		vis.barBufs[idx] = make([]float64, cfg.SampleSize)
    78  		vis.fftBufs[idx] = make([]complex128, cfg.SampleSize/2+1)
    79  
    80  		fft.InitPlan(&vis.plans[idx], vis.inputBufs[idx], vis.fftBufs[idx])
    81  	}
    82  
    83  	return vis
    84  }
    85  
    86  func (vis *processor) Start(ctx context.Context, kickChan chan bool, mu *sync.Mutex) context.Context {
    87  	newCtx, cancel := context.WithCancel(ctx)
    88  	vis.ctxCancel = cancel
    89  	vis.mu = mu
    90  	go vis.run(newCtx, kickChan)
    91  
    92  	return newCtx
    93  }
    94  
    95  func (vis *processor) Stop() {
    96  	vis.ctxCancel()
    97  }
    98  
    99  func (vis *processor) run(ctx context.Context, kickChan chan bool) {
   100  	if vis.processRate <= 0 {
   101  		// if we do not have a framerate set, allow at most 1 second per sampling
   102  		vis.processRate = 1
   103  	}
   104  
   105  	dur := time.Second / time.Duration(vis.processRate)
   106  	ticker := time.NewTicker(dur)
   107  	defer ticker.Stop()
   108  
   109  	for {
   110  		vis.Process()
   111  		select {
   112  		case <-ctx.Done():
   113  			return
   114  		case <-kickChan:
   115  		case <-ticker.C:
   116  			// default:
   117  		}
   118  		ticker.Reset(dur)
   119  	}
   120  }
   121  
   122  // Process runs processing on sample sets and calls Write on the output once per sample set.
   123  func (vis *processor) Process() {
   124  	vis.mu.Lock()
   125  	for idx := range vis.barBufs {
   126  		if vis.wndwr != nil {
   127  			vis.wndwr(vis.inputBufs[idx])
   128  		}
   129  		vis.plans[idx].Execute()
   130  	}
   131  	vis.mu.Unlock()
   132  
   133  	if n := vis.out.Bins(vis.channelCount); n != vis.bars {
   134  		vis.bars = vis.anlz.Recalculate(n)
   135  	}
   136  
   137  	for idx, fftBuf := range vis.fftBufs {
   138  		buf := vis.barBufs[idx]
   139  
   140  		for bIdx := range buf[:vis.bars] {
   141  			buf[bIdx] = vis.anlz.ProcessBin(bIdx, fftBuf)
   142  		}
   143  	}
   144  
   145  	if vis.smth != nil {
   146  		vis.smth.SmoothBuffers(vis.barBufs)
   147  	}
   148  
   149  	vis.out.Write(vis.barBufs, vis.channelCount)
   150  }