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

     1  package graphic
     2  
     3  import (
     4  	"context"
     5  	"sync/atomic"
     6  
     7  	"github.com/noriah/catnip/dsp"
     8  	"github.com/noriah/catnip/util"
     9  
    10  	"github.com/nsf/termbox-go"
    11  )
    12  
    13  // Constants
    14  const (
    15  
    16  	// Bar Constants
    17  
    18  	SpaceRune = '\u0020'
    19  
    20  	BarRuneV = '\u2580'
    21  	BarRune  = '\u2588'
    22  	BarRuneH = '\u2590'
    23  
    24  	StyleReverse = termbox.AttrReverse
    25  
    26  	// NumRunes number of runes for sub step bars
    27  	NumRunes = 8
    28  
    29  	// ScalingWindow in seconds
    30  	ScalingWindow = 1.5
    31  	// PeakThreshold is the threshold to not draw if the peak is less.
    32  	PeakThreshold = 0.001
    33  )
    34  
    35  // DrawType is the type.
    36  type DrawType int
    37  
    38  // draw types
    39  const (
    40  	DrawMin DrawType = iota
    41  	DrawUp
    42  	DrawUpDown
    43  	DrawDown
    44  	DrawLeft
    45  	DrawLeftRight
    46  	DrawRight
    47  	DrawUpDownSplit
    48  	DrawLeftRightSplit
    49  	DrawUpDownSplitVert
    50  	DrawMax
    51  
    52  	// DrawDefault is the default draw type.
    53  	DrawDefault = DrawUpDown
    54  )
    55  
    56  // Styles is the structure for the styles that Display will draw using.
    57  type Styles struct {
    58  	Foreground termbox.Attribute
    59  	Background termbox.Attribute
    60  	CenterLine termbox.Attribute
    61  }
    62  
    63  // DefaultStyles returns the default styles.
    64  func DefaultStyles() Styles {
    65  	return Styles{
    66  		Foreground: termbox.ColorDefault,
    67  		Background: termbox.ColorDefault,
    68  		CenterLine: termbox.ColorMagenta,
    69  	}
    70  }
    71  
    72  // StylesFromUInt16 converts 3 uint16 values to styles.
    73  func StylesFromUInt16(fg, bg, center uint16) Styles {
    74  	return Styles{
    75  		Foreground: termbox.Attribute(fg),
    76  		Background: termbox.Attribute(bg),
    77  		CenterLine: termbox.Attribute(center),
    78  	}
    79  }
    80  
    81  // AsUInt16s converts the styles to 3 uint16 values.
    82  func (s Styles) AsUInt16s() (fg, bg, center uint16) {
    83  	fg = uint16(s.Foreground)
    84  	bg = uint16(s.Background)
    85  	center = uint16(s.CenterLine)
    86  	return
    87  }
    88  
    89  // Display handles drawing our visualizer.
    90  type Display struct {
    91  	Smoother    dsp.Smoother
    92  	running     uint32
    93  	barSize     int
    94  	spaceSize   int
    95  	binSize     int
    96  	baseSize    int
    97  	termWidth   int
    98  	termHeight  int
    99  	trackZero   int
   100  	invertDraw  bool
   101  	window      *util.MovingWindow
   102  	drawType    DrawType
   103  	styles      Styles
   104  	styleBuffer []termbox.Attribute
   105  	ctx         context.Context
   106  	cancel      context.CancelFunc
   107  }
   108  
   109  func NewDisplay() *Display {
   110  	return &Display{}
   111  }
   112  
   113  // Init initializes the display.
   114  // Should be called before any other display method.
   115  func (d *Display) Init(sampleRate float64, sampleSize int) error {
   116  	// make a large buffer as this could be as big as the screen width/height.
   117  
   118  	windowSize := ((int(ScalingWindow * sampleRate)) / sampleSize) * 2
   119  	d.window = util.NewMovingWindow(windowSize)
   120  
   121  	d.styleBuffer = make([]termbox.Attribute, 4096)
   122  
   123  	// Prevent crash on Tmux.
   124  	prevState, err := normalizeTerminal()
   125  	if err != nil {
   126  		return err
   127  	}
   128  	defer prevState()
   129  
   130  	if err := termbox.Init(); err != nil {
   131  		return err
   132  	}
   133  
   134  	termbox.SetInputMode(termbox.InputAlt)
   135  	termbox.SetOutputMode(termbox.Output256)
   136  	termbox.HideCursor()
   137  
   138  	d.termWidth, d.termHeight = termbox.Size()
   139  
   140  	return nil
   141  }
   142  
   143  // Close will stop display and clean up the terminal.
   144  func (d *Display) Close() error {
   145  	termbox.Close()
   146  	return nil
   147  }
   148  
   149  // Start display is bad.
   150  func (d *Display) Start(ctx context.Context) context.Context {
   151  	d.ctx, d.cancel = context.WithCancel(ctx)
   152  
   153  	go d.inputProcessor()
   154  
   155  	return d.ctx
   156  }
   157  
   158  // Stop display not work.
   159  func (d *Display) Stop() error {
   160  	if atomic.CompareAndSwapUint32(&d.running, 1, 0) {
   161  		termbox.Interrupt()
   162  	}
   163  
   164  	return nil
   165  }
   166  
   167  // Draw takes data and draws.
   168  func (d *Display) Write(buffers [][]float64, channels int) error {
   169  
   170  	peak := 0.0
   171  	bins := d.binsInternal(channels, bufferLength(buffers))
   172  
   173  	for i := 0; i < channels; i++ {
   174  		for _, val := range buffers[i][:bins] {
   175  			if val > peak {
   176  				peak = val
   177  			}
   178  		}
   179  	}
   180  
   181  	scale := 1.0
   182  
   183  	if peak >= PeakThreshold {
   184  		d.trackZero = 0
   185  
   186  		// do some scaling if we are above the PeakThreshold
   187  		d.window.Update(peak)
   188  
   189  	} else {
   190  		if d.trackZero++; d.trackZero == 5 {
   191  			d.window.Recalculate()
   192  		}
   193  	}
   194  
   195  	vMean, vSD := d.window.Stats()
   196  
   197  	if t := vMean + (2.0 * vSD); t > 1.0 {
   198  		scale = t
   199  	}
   200  
   201  	switch d.drawType {
   202  	case DrawUp:
   203  		d.drawUp(buffers, channels, scale)
   204  
   205  	case DrawUpDown:
   206  		d.drawUpDown(buffers, channels, scale)
   207  
   208  	case DrawUpDownSplit:
   209  		d.drawUpDownSplit(buffers, channels, scale)
   210  
   211  	case DrawUpDownSplitVert:
   212  		d.drawUpDownSplitVert(buffers, channels, scale)
   213  
   214  	case DrawDown:
   215  		d.drawDown(buffers, channels, scale)
   216  
   217  	case DrawLeft:
   218  		d.drawLeft(buffers, channels, scale)
   219  
   220  	case DrawLeftRight:
   221  		d.drawLeftRight(buffers, channels, scale)
   222  
   223  	case DrawLeftRightSplit:
   224  		d.drawLeftRightSplit(buffers, channels, scale)
   225  
   226  	case DrawRight:
   227  		d.drawRight(buffers, channels, scale)
   228  
   229  	default:
   230  		return nil
   231  	}
   232  
   233  	termbox.Flush()
   234  
   235  	termbox.Clear(d.styles.Foreground, d.styles.Background)
   236  
   237  	return nil
   238  }
   239  
   240  // SetSizes takes a bar size and spacing size.
   241  // Returns number of bars able to show.
   242  func (d *Display) SetSizes(bar, space int) {
   243  	bar = intMax(bar, 1)
   244  	space = intMax(space, 0)
   245  
   246  	d.barSize = bar
   247  	d.spaceSize = space
   248  	d.binSize = bar + space
   249  }
   250  
   251  // AdjustSizes modifies the bar and space size by barDelta and spaceDelta.
   252  func (d *Display) AdjustSizes(barDelta, spaceDelta int) {
   253  	d.SetSizes(d.barSize+barDelta, d.spaceSize+spaceDelta)
   254  }
   255  
   256  // SetBase will set the base size.
   257  func (d *Display) SetBase(size int) {
   258  	size = intMax(size, 0)
   259  	d.baseSize = size
   260  
   261  	d.updateStyleBuffer()
   262  }
   263  
   264  // AdjustBase will change the base by delta units
   265  func (d *Display) AdjustBase(delta int) {
   266  	d.SetBase(d.baseSize + delta)
   267  }
   268  
   269  func (d *Display) SetStyles(styles Styles) {
   270  	d.styles = styles
   271  
   272  	d.updateStyleBuffer()
   273  }
   274  
   275  // SetDrawType sets the draw type for future draws
   276  func (d *Display) SetDrawType(dt DrawType) {
   277  	switch {
   278  	case dt <= DrawMin:
   279  		d.drawType = DrawMax - 1
   280  	case dt >= DrawMax:
   281  		d.drawType = DrawMin + 1
   282  	default:
   283  		d.drawType = dt
   284  	}
   285  
   286  	d.updateStyleBuffer()
   287  }
   288  
   289  func (d *Display) SetInvertDraw(invert bool) {
   290  	d.invertDraw = invert
   291  }
   292  
   293  // Bins returns the number of bars we will draw.
   294  func (d *Display) Bins(chCount int) int {
   295  
   296  	switch d.drawType {
   297  	case DrawUp, DrawDown:
   298  		return (d.termWidth / d.binSize) / chCount
   299  	case DrawUpDownSplit, DrawUpDownSplitVert:
   300  		return (d.termWidth / d.binSize) / 2
   301  	case DrawUpDown:
   302  		return d.termWidth / d.binSize
   303  	case DrawLeft, DrawRight:
   304  		return (d.termHeight / d.binSize) / chCount
   305  	case DrawLeftRightSplit:
   306  		return (d.termHeight / d.binSize) / 2
   307  	case DrawLeftRight:
   308  		return d.termHeight / d.binSize
   309  	default:
   310  		return 0
   311  	}
   312  }
   313  
   314  func bufferLength(buffers [][]float64) int {
   315  	return len(buffers[0])
   316  }
   317  
   318  func (d *Display) binsInternal(chCount, bufLen int) int {
   319  	bins := d.Bins(chCount)
   320  	if bins >= bufLen {
   321  		bins = bufLen - 1
   322  	}
   323  	return bins
   324  }
   325  
   326  func (d *Display) inputProcessor() {
   327  	if d.cancel != nil {
   328  		defer d.cancel()
   329  	}
   330  
   331  	atomic.StoreUint32(&d.running, 1)
   332  	defer atomic.StoreUint32(&d.running, 0)
   333  
   334  	for {
   335  		ev := termbox.PollEvent()
   336  
   337  		switch ev.Type {
   338  		case termbox.EventKey:
   339  			switch ev.Key {
   340  
   341  			case termbox.KeySpace:
   342  				d.SetDrawType(d.drawType + 1)
   343  
   344  			case termbox.KeyCtrlC:
   345  				return
   346  
   347  			default:
   348  				switch ev.Ch {
   349  				case 'b', 'B':
   350  					d.SetDrawType(d.drawType - 1)
   351  
   352  				case 'n', 'N':
   353  					d.SetDrawType(d.drawType + 1)
   354  
   355  				case 'w', 'W':
   356  					d.AdjustSizes(1, 0)
   357  
   358  				case 'd', 'D':
   359  					d.AdjustSizes(0, 1)
   360  
   361  				case 's', 'S':
   362  					d.AdjustSizes(-1, 0)
   363  
   364  				case 'a', 'A':
   365  					d.AdjustSizes(0, -1)
   366  
   367  				case 'i', 'I':
   368  					d.SetInvertDraw(!d.invertDraw)
   369  
   370  				case 'r', 'R':
   371  					d.window.Drop(d.window.Cap())
   372  
   373  				case '+', '=':
   374  					d.AdjustBase(1)
   375  
   376  				case '-', '_':
   377  					d.AdjustBase(-1)
   378  
   379  				case '[':
   380  					d.Smoother.SetMethod(d.Smoother.GetMethod() - 1)
   381  
   382  				case ']':
   383  					d.Smoother.SetMethod(d.Smoother.GetMethod() + 1)
   384  
   385  				case 'q', 'Q':
   386  					return
   387  
   388  				default:
   389  				} // switch ev.Ch
   390  			} // switch ev.Key
   391  
   392  		case termbox.EventResize:
   393  			d.termWidth = ev.Width
   394  			d.termHeight = ev.Height
   395  			d.updateStyleBuffer()
   396  
   397  		case termbox.EventInterrupt:
   398  			return
   399  
   400  		default:
   401  		} // switch ev.Type
   402  
   403  		// check if we need to exit
   404  		select {
   405  		case <-d.ctx.Done():
   406  			return
   407  		default:
   408  		}
   409  	} // for
   410  }
   411  
   412  func intMax(x1, x2 int) int {
   413  	if x1 < x2 {
   414  		return x2
   415  	}
   416  	return x1
   417  }
   418  
   419  func intMin(x1, x2 int) int {
   420  	if x1 > x2 {
   421  		return x2
   422  	}
   423  	return x1
   424  }
   425  
   426  func (d *Display) updateStyleBuffer() {
   427  	switch d.drawType {
   428  	case DrawUp:
   429  		d.fillStyleBuffer(d.termHeight-d.baseSize, d.baseSize, 0)
   430  
   431  	case DrawUpDown, DrawUpDownSplit, DrawUpDownSplitVert:
   432  		centerStart := intMax((d.termHeight-d.baseSize)/2, 0)
   433  		centerStop := centerStart + d.baseSize
   434  		d.fillStyleBuffer(centerStart, d.baseSize, d.termHeight-centerStop)
   435  
   436  	case DrawDown:
   437  		d.fillStyleBuffer(0, d.baseSize, d.termHeight-d.baseSize)
   438  
   439  	case DrawLeft:
   440  		d.fillStyleBuffer(d.termWidth-d.baseSize, d.baseSize, 0)
   441  
   442  	case DrawLeftRight, DrawLeftRightSplit:
   443  		centerStart := intMax((d.termWidth-d.baseSize)/2, 0)
   444  		centerStop := centerStart + d.baseSize
   445  		d.fillStyleBuffer(centerStart, d.baseSize, d.termWidth-centerStop)
   446  
   447  	case DrawRight:
   448  		d.fillStyleBuffer(0, d.baseSize, d.termWidth-d.baseSize)
   449  	}
   450  }
   451  
   452  func (d *Display) fillStyleBuffer(left, center, right int) {
   453  	i := 0
   454  	for stop := left; i < stop; i++ {
   455  		d.styleBuffer[i] = d.styles.Foreground
   456  	}
   457  
   458  	for stop := i + center; i < stop; i++ {
   459  		d.styleBuffer[i] = d.styles.CenterLine
   460  	}
   461  
   462  	for stop := i + right; i < stop; i++ {
   463  		d.styleBuffer[i] = d.styles.Foreground
   464  	}
   465  }
   466  
   467  func sizeAndCap(value float64, space int, zeroBase bool, baseRune rune) (int, rune) {
   468  	steps, stop := int(value*NumRunes), space*NumRunes
   469  
   470  	if zeroBase {
   471  		if steps < stop {
   472  			return space - (steps / NumRunes), baseRune + rune(steps%NumRunes)
   473  		}
   474  
   475  		return 0, baseRune
   476  	}
   477  
   478  	if steps < stop {
   479  		return steps / NumRunes, baseRune - rune(steps%NumRunes)
   480  	}
   481  
   482  	return space, baseRune
   483  }
   484  
   485  // DRAWING METHODS
   486  
   487  // drawUp will draw up.
   488  func (d *Display) drawUp(bins [][]float64, channelCount int, scale float64) {
   489  	binCount := d.binsInternal(channelCount, bufferLength(bins))
   490  	barSpace := intMax(d.termHeight-d.baseSize, 0)
   491  	scale = float64(barSpace) / scale
   492  
   493  	paddedWidth := (d.binSize * binCount * channelCount) - d.spaceSize
   494  	paddedWidth = intMax(intMin(paddedWidth, d.termWidth), 0)
   495  
   496  	channelWidth := d.binSize * binCount
   497  	edgeOffset := (d.termWidth - paddedWidth) / 2
   498  
   499  	for xSet, chBins := range bins {
   500  
   501  		for xBar := 0; xBar < binCount; xBar++ {
   502  
   503  			xBin := (xBar * (1 - xSet)) + (((binCount - 1) - xBar) * xSet)
   504  
   505  			if d.invertDraw {
   506  				xBin = binCount - 1 - xBin
   507  			}
   508  
   509  			start, bCap := sizeAndCap(chBins[xBin]*scale, barSpace, true, BarRuneV)
   510  
   511  			xCol := (xBar * d.binSize) + (channelWidth * xSet) + edgeOffset
   512  			lCol := xCol + d.barSize
   513  
   514  			for ; xCol < lCol; xCol++ {
   515  
   516  				if bCap > BarRuneV {
   517  					termbox.SetCell(xCol, start-1, bCap, d.styles.Foreground, d.styles.Background)
   518  				}
   519  
   520  				for xRow := start; xRow < d.termHeight; xRow++ {
   521  					termbox.SetCell(xCol, xRow, BarRune, d.styleBuffer[xRow], d.styles.Background)
   522  				}
   523  			}
   524  		}
   525  	}
   526  }
   527  
   528  // drawUpDown will draw up and down.
   529  func (d *Display) drawUpDown(bins [][]float64, channelCount int, scale float64) {
   530  	binCount := d.binsInternal(channelCount, bufferLength(bins))
   531  	centerStart := intMax((d.termHeight-d.baseSize)/2, 0)
   532  	centerStop := centerStart + d.baseSize
   533  
   534  	scale = float64(intMin(centerStart, d.termHeight-centerStop)) / scale
   535  
   536  	edgeOffset := intMax((d.termWidth-((d.binSize*binCount)-d.spaceSize))/2, 0)
   537  
   538  	setCount := channelCount
   539  
   540  	for xBar := 0; xBar < binCount; xBar++ {
   541  
   542  		lStart, lCap := sizeAndCap(bins[0][xBar]*scale, centerStart, true, BarRuneV)
   543  		rStop, rCap := sizeAndCap(bins[1%setCount][xBar]*scale, centerStart, false, BarRune)
   544  		if rStop += centerStop; rStop >= d.termHeight {
   545  			rStop = d.termHeight
   546  			rCap = BarRune
   547  		}
   548  
   549  		xCol := xBar
   550  		if d.invertDraw {
   551  			xCol = binCount - 1 - xCol
   552  		}
   553  
   554  		xCol = xCol*d.binSize + edgeOffset
   555  		lCol := intMin(xCol+d.barSize, d.termWidth)
   556  
   557  		for ; xCol < lCol; xCol++ {
   558  
   559  			if lCap > BarRuneV {
   560  				termbox.SetCell(xCol, lStart-1, lCap, d.styles.Foreground, d.styles.Background)
   561  			}
   562  
   563  			for xRow := lStart; xRow < rStop; xRow++ {
   564  				termbox.SetCell(xCol, xRow, BarRune, d.styleBuffer[xRow], d.styles.Background)
   565  			}
   566  
   567  			// last part of right bars.
   568  			if rCap < BarRune {
   569  				termbox.SetCell(xCol, rStop, rCap, StyleReverse, d.styles.Foreground)
   570  			}
   571  		}
   572  	}
   573  }
   574  
   575  // drawUpDownSplit will draw up and down split down the middle for left and
   576  // right channels.
   577  func (d *Display) drawUpDownSplit(bins [][]float64, channelCount int, scale float64) {
   578  	binCount := d.binsInternal(2, bufferLength(bins))
   579  	centerStart := intMax((d.termHeight-d.baseSize)/2, 0)
   580  	centerStop := centerStart + d.baseSize
   581  
   582  	scale = float64(intMin(centerStart, d.termHeight-centerStop)) / scale
   583  
   584  	paddedWidth := (d.binSize * binCount * 2) - d.spaceSize
   585  	paddedWidth = intMax(intMin(paddedWidth, d.termWidth), 0)
   586  
   587  	channelWidth := d.binSize * binCount
   588  	edgeOffset := (d.termWidth - paddedWidth) / 2
   589  
   590  	for xSide := 0; xSide < 2; xSide++ {
   591  
   592  		for xBar := 0; xBar < binCount; xBar++ {
   593  
   594  			xBin := (xBar * (1 - xSide)) + (((binCount - 1) - xBar) * xSide)
   595  
   596  			if d.invertDraw {
   597  				xBin = binCount - 1 - xBin
   598  			}
   599  
   600  			start, tCap := sizeAndCap(bins[xSide%channelCount][xBin]*scale, centerStart, true, BarRuneV)
   601  			stop, bCap := sizeAndCap(bins[xSide%channelCount][xBin]*scale, centerStart, false, BarRune)
   602  			if stop += centerStop; stop >= d.termHeight {
   603  				stop = d.termHeight
   604  				bCap = BarRune
   605  			}
   606  
   607  			xCol := (xBar * d.binSize) + (channelWidth * xSide) + edgeOffset
   608  			lCol := xCol + d.barSize
   609  
   610  			for ; xCol < lCol; xCol++ {
   611  
   612  				if tCap > BarRuneV {
   613  					termbox.SetCell(xCol, start-1, tCap, d.styles.Foreground, d.styles.Background)
   614  				}
   615  
   616  				for xRow := start; xRow < stop; xRow++ {
   617  					termbox.SetCell(xCol, xRow, BarRune, d.styleBuffer[xRow], d.styles.Background)
   618  				}
   619  
   620  				if bCap < BarRune {
   621  					termbox.SetCell(xCol, stop, bCap, StyleReverse, d.styles.Foreground)
   622  				}
   623  			}
   624  		}
   625  	}
   626  }
   627  
   628  // drawUpDownSplitVert will draw up and down split down the middle for left and
   629  // right channels.
   630  func (d *Display) drawUpDownSplitVert(bins [][]float64, channelCount int, scale float64) {
   631  	binCount := d.binsInternal(2, bufferLength(bins))
   632  	centerStart := intMax((d.termHeight-d.baseSize)/2, 0)
   633  	centerStop := centerStart + d.baseSize
   634  
   635  	scale = float64(intMin(centerStart, d.termHeight-centerStop)) / scale
   636  
   637  	paddedWidth := (d.binSize * binCount * 2) - d.spaceSize
   638  	paddedWidth = intMax(intMin(paddedWidth, d.termWidth), 0)
   639  
   640  	channelWidth := d.binSize * binCount
   641  	edgeOffset := (d.termWidth - paddedWidth) / 2
   642  
   643  	for xSide := 0; xSide < 2; xSide++ {
   644  
   645  		for xBar := 0; xBar < binCount; xBar++ {
   646  
   647  			xBin := (xBar * (1 - xSide)) + (((binCount - 1) - xBar) * xSide)
   648  
   649  			if d.invertDraw {
   650  				xBin = binCount - 1 - xBin
   651  			}
   652  
   653  			start, tCap := sizeAndCap(bins[0][xBin]*scale, centerStart, true, BarRuneV)
   654  			stop, bCap := sizeAndCap(bins[1%channelCount][xBin]*scale, centerStart, false, BarRune)
   655  			if stop += centerStop; stop >= d.termHeight {
   656  				stop = d.termHeight
   657  				bCap = BarRune
   658  			}
   659  
   660  			xCol := (xBar * d.binSize) + (channelWidth * xSide) + edgeOffset
   661  			lCol := xCol + d.barSize
   662  
   663  			for ; xCol < lCol; xCol++ {
   664  
   665  				if tCap > BarRuneV {
   666  					termbox.SetCell(xCol, start-1, tCap, d.styles.Foreground, d.styles.Background)
   667  				}
   668  
   669  				for xRow := start; xRow < stop; xRow++ {
   670  					termbox.SetCell(xCol, xRow, BarRune, d.styleBuffer[xRow], d.styles.Background)
   671  				}
   672  
   673  				if bCap < BarRune {
   674  					termbox.SetCell(xCol, stop, bCap, StyleReverse, d.styles.Foreground)
   675  				}
   676  			}
   677  		}
   678  	}
   679  }
   680  
   681  // drawDown will draw down.
   682  func (d *Display) drawDown(bins [][]float64, channelCount int, scale float64) {
   683  	binCount := d.binsInternal(channelCount, bufferLength(bins))
   684  	barSpace := intMax(d.termHeight-d.baseSize, 0)
   685  	scale = float64(barSpace) / scale
   686  
   687  	paddedWidth := (d.binSize * binCount * channelCount) - d.spaceSize
   688  	paddedWidth = intMax(intMin(paddedWidth, d.termWidth), 0)
   689  
   690  	channelWidth := d.binSize * binCount
   691  	edgeOffset := (d.termWidth - paddedWidth) / 2
   692  
   693  	for xSet, chBins := range bins {
   694  
   695  		for xBar := 0; xBar < binCount; xBar++ {
   696  
   697  			xBin := (xBar * (1 - xSet)) + (((binCount - 1) - xBar) * xSet)
   698  
   699  			if d.invertDraw {
   700  				xBin = binCount - 1 - xBin
   701  			}
   702  
   703  			stop, bCap := sizeAndCap(chBins[xBin]*scale, barSpace, false, BarRune)
   704  			if stop += d.baseSize; stop >= d.termHeight {
   705  				stop = d.termHeight
   706  				bCap = BarRune
   707  			}
   708  
   709  			xCol := (xBar * d.binSize) + (channelWidth * xSet) + edgeOffset
   710  			lCol := xCol + d.barSize
   711  
   712  			for ; xCol < lCol; xCol++ {
   713  
   714  				for xRow := 0; xRow < stop; xRow++ {
   715  					termbox.SetCell(xCol, xRow, BarRune, d.styleBuffer[xRow], d.styles.Background)
   716  				}
   717  
   718  				if bCap < BarRune {
   719  					termbox.SetCell(xCol, stop, bCap, StyleReverse, d.styles.Foreground)
   720  				}
   721  			}
   722  		}
   723  	}
   724  }
   725  
   726  func (d *Display) drawLeft(bins [][]float64, channelCount int, scale float64) {
   727  	binCount := d.binsInternal(channelCount, bufferLength(bins))
   728  	barSpace := intMax(d.termWidth-d.baseSize, 0)
   729  	scale = float64(barSpace) / scale
   730  
   731  	paddedWidth := (d.binSize * binCount * channelCount) - d.spaceSize
   732  	paddedWidth = intMax(intMin(paddedWidth, d.termHeight), 0)
   733  
   734  	channelWidth := d.binSize * binCount
   735  	edgeOffset := (d.termHeight - paddedWidth) / 2
   736  
   737  	for xSet, chBins := range bins {
   738  
   739  		for xBar := 0; xBar < binCount; xBar++ {
   740  
   741  			xBin := (xBar * (1 - xSet)) + (((binCount - 1) - xBar) * xSet)
   742  
   743  			if d.invertDraw {
   744  				xBin = binCount - 1 - xBin
   745  			}
   746  
   747  			start, bCap := sizeAndCap(chBins[xBin]*scale, barSpace, true, BarRune)
   748  
   749  			xRow := (xBar * d.binSize) + (channelWidth * xSet) + edgeOffset
   750  			lRow := xRow + d.barSize
   751  
   752  			for ; xRow < lRow; xRow++ {
   753  
   754  				if bCap > BarRune {
   755  					termbox.SetCell(start-1, xRow, bCap, StyleReverse, d.styles.Background)
   756  				}
   757  
   758  				for xCol := start; xCol < d.termWidth; xCol++ {
   759  					termbox.SetCell(xCol, xRow, BarRune, d.styleBuffer[xCol], d.styles.Background)
   760  				}
   761  			}
   762  		}
   763  	}
   764  }
   765  
   766  // drawLeftRight will draw left and right.
   767  func (d *Display) drawLeftRight(bins [][]float64, channelCount int, scale float64) {
   768  	binCount := d.binsInternal(channelCount, bufferLength(bins))
   769  	centerStart := intMax((d.termWidth-d.baseSize)/2, 0)
   770  	centerStop := centerStart + d.baseSize
   771  
   772  	scale = float64(intMin(centerStart, d.termWidth-centerStop)) / scale
   773  
   774  	edgeOffset := intMax((d.termHeight-((d.binSize*binCount)-d.spaceSize))/2, 0)
   775  
   776  	setCount := channelCount
   777  
   778  	for xBar := 0; xBar < binCount; xBar++ {
   779  
   780  		// draw higher frequencies at the top
   781  		xBin := binCount - 1 - xBar
   782  
   783  		lStart, lCap := sizeAndCap(bins[0][xBin]*scale, centerStart, true, BarRune)
   784  		rStop, rCap := sizeAndCap(bins[1%setCount][xBin]*scale, centerStart, false, BarRuneH)
   785  		if rStop += centerStop; rStop >= d.termWidth {
   786  			rStop = d.termWidth
   787  			rCap = BarRuneH
   788  		}
   789  
   790  		xRow := xBar
   791  		if d.invertDraw {
   792  			xRow = binCount - 1 - xRow
   793  		}
   794  
   795  		xRow = xRow*d.binSize + edgeOffset
   796  
   797  		lRow := intMin(xRow+d.barSize, d.termHeight)
   798  
   799  		for ; xRow < lRow; xRow++ {
   800  
   801  			if lCap > BarRune {
   802  				termbox.SetCell(lStart-1, xRow, lCap, StyleReverse, d.styles.Background)
   803  			}
   804  
   805  			for xCol := lStart; xCol < rStop; xCol++ {
   806  				termbox.SetCell(xCol, xRow, BarRune, d.styleBuffer[xCol], d.styles.Background)
   807  			}
   808  
   809  			if rCap < BarRuneH {
   810  				termbox.SetCell(rStop, xRow, rCap, d.styles.Foreground, d.styles.Foreground)
   811  			}
   812  		}
   813  	}
   814  }
   815  
   816  // drawLeftRight will draw left and right.
   817  func (d *Display) drawLeftRightSplit(bins [][]float64, channelCount int, scale float64) {
   818  	binCount := d.binsInternal(2, bufferLength(bins))
   819  	centerStart := intMax((d.termWidth-d.baseSize)/2, 0)
   820  	centerStop := centerStart + d.baseSize
   821  
   822  	scale = float64(intMin(centerStart, d.termWidth-centerStop)) / scale
   823  
   824  	paddedWidth := (d.binSize * binCount * 2) - d.spaceSize
   825  	paddedWidth = intMax(intMin(paddedWidth, d.termHeight), 0)
   826  
   827  	channelWidth := d.binSize * binCount
   828  	edgeOffset := (d.termHeight - paddedWidth) / 2
   829  
   830  	for xSide := 0; xSide < 2; xSide++ {
   831  
   832  		for xBar := 0; xBar < binCount; xBar++ {
   833  
   834  			xBin := (xBar * (1 - xSide)) + (((binCount - 1) - xBar) * xSide)
   835  
   836  			if d.invertDraw {
   837  				xBin = binCount - 1 - xBin
   838  			}
   839  
   840  			start, lCap := sizeAndCap(bins[xSide%channelCount][xBin]*scale, centerStart, true, BarRune)
   841  			stop, rCap := sizeAndCap(bins[xSide%channelCount][xBin]*scale, centerStart, false, BarRuneH)
   842  			if stop += centerStop; stop >= d.termWidth {
   843  				stop = d.termWidth
   844  				rCap = BarRuneH
   845  			}
   846  
   847  			xRow := (xBar * d.binSize) + (channelWidth * xSide) + edgeOffset
   848  			lRow := xRow + d.barSize
   849  
   850  			for ; xRow < lRow; xRow++ {
   851  
   852  				if lCap > BarRune {
   853  					termbox.SetCell(start-1, xRow, lCap, StyleReverse, d.styles.Background)
   854  				}
   855  
   856  				for xCol := start; xCol < stop; xCol++ {
   857  					termbox.SetCell(xCol, xRow, BarRune, d.styleBuffer[xCol], d.styles.Background)
   858  				}
   859  
   860  				if rCap < BarRuneH {
   861  					termbox.SetCell(stop, xRow, rCap, d.styles.Foreground, d.styles.Foreground)
   862  				}
   863  			}
   864  		}
   865  	}
   866  }
   867  
   868  func (d *Display) drawRight(bins [][]float64, channelCount int, scale float64) {
   869  	binCount := d.binsInternal(channelCount, bufferLength(bins))
   870  	barSpace := intMax(d.termWidth-d.baseSize, 0)
   871  	scale = float64(barSpace) / scale
   872  
   873  	paddedWidth := (d.binSize * binCount * channelCount) - d.spaceSize
   874  	paddedWidth = intMax(intMin(paddedWidth, d.termHeight), 0)
   875  
   876  	channelWidth := d.binSize * binCount
   877  	edgeOffset := (d.termHeight - paddedWidth) / 2
   878  
   879  	for xSet, chBins := range bins {
   880  
   881  		for xBar := 0; xBar < binCount; xBar++ {
   882  
   883  			xBin := (xBar * (1 - xSet)) + (((binCount - 1) - xBar) * xSet)
   884  
   885  			if d.invertDraw {
   886  				xBin = binCount - 1 - xBin
   887  			}
   888  
   889  			stop, bCap := sizeAndCap(chBins[xBin]*scale, barSpace, false, BarRuneH)
   890  			if stop += d.baseSize; stop >= d.termWidth {
   891  				stop = d.termWidth
   892  				bCap = BarRune
   893  			}
   894  
   895  			xRow := (xBar * d.binSize) + (channelWidth * xSet) + edgeOffset
   896  			lRow := xRow + d.barSize
   897  
   898  			for ; xRow < lRow; xRow++ {
   899  
   900  				for xCol := 0; xCol < stop; xCol++ {
   901  					termbox.SetCell(xCol, xRow, BarRune, d.styleBuffer[xCol], d.styles.Background)
   902  				}
   903  
   904  				if bCap < BarRuneH {
   905  					termbox.SetCell(stop, xRow, bCap, d.styles.Foreground, d.styles.Foreground)
   906  				}
   907  			}
   908  		}
   909  	}
   910  }