184 lines
5.1 KiB
Go
184 lines
5.1 KiB
Go
package waveform
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"math"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/canvas"
|
|
"fyne.io/fyne/v2/theme"
|
|
"fyne.io/fyne/v2/widget"
|
|
"github.com/disintegration/imaging"
|
|
)
|
|
|
|
//
|
|
// Widget code starts here
|
|
//
|
|
// A visualisation of an audio stream widget with themed or overridden background and foreground
|
|
//
|
|
type Waveform struct {
|
|
widget.BaseWidget // Inherit from BaseWidget
|
|
audioData []int32 // The data to display in the widget
|
|
StretchSamples bool
|
|
TransparentBackground bool
|
|
OverrideForeground bool
|
|
OverrideForegroundColor color.Color
|
|
OverrideBackground bool
|
|
OverrideBackgroundColor color.Color
|
|
minSize fyne.Size
|
|
}
|
|
|
|
//
|
|
// Create a Widget and Extend (initialiase) the BaseWidget
|
|
//
|
|
func NewWaveform(data []int32) *Waveform {
|
|
w := &Waveform{ // Create this widget with an initial text value
|
|
audioData: data,
|
|
StretchSamples: false,
|
|
TransparentBackground: false,
|
|
OverrideForeground: false,
|
|
OverrideForegroundColor: color.RGBA{0, 0, 0, 0xff},
|
|
OverrideBackground: false,
|
|
OverrideBackgroundColor: color.RGBA{0xff, 0xff, 0xff, 0xff},
|
|
minSize: fyne.NewSize(200, 64),
|
|
}
|
|
w.ExtendBaseWidget(w) // Initialiase the BaseWidget
|
|
return w
|
|
}
|
|
|
|
//
|
|
// Create the renderer. This is called by the fyne application
|
|
//
|
|
func (w *Waveform) CreateRenderer() fyne.WidgetRenderer {
|
|
// Pass this widget to the renderer so it can access the text field
|
|
return newWaveformRenderer(w)
|
|
}
|
|
|
|
//
|
|
// Set the minsize (default is 200x64)
|
|
//
|
|
func (w *Waveform) SetMinSize(newSize fyne.Size) {
|
|
// Pass this widget to the renderer so it can access the text field
|
|
w.minSize = newSize
|
|
}
|
|
|
|
//
|
|
// Widget Renderer code starts here
|
|
//
|
|
type waveformRenderer struct {
|
|
widget *Waveform // Reference to the widget holding the current state
|
|
background *canvas.Rectangle // A background rectangle
|
|
canvas *canvas.Raster // The waveform
|
|
}
|
|
|
|
//
|
|
// Create the renderer with a reference to the widget
|
|
// Note: The background and foreground colours are set from the current theme.
|
|
//
|
|
// Do not size or move canvas objects here.
|
|
//
|
|
func newWaveformRenderer(theWidget *Waveform) *waveformRenderer {
|
|
r := &waveformRenderer{
|
|
widget: theWidget,
|
|
background: canvas.NewRectangle(theme.BackgroundColor()),
|
|
}
|
|
r.canvas = canvas.NewRaster(r.audioDataToImage)
|
|
return r
|
|
}
|
|
|
|
// From arduino map() lol
|
|
func int32Map(x int32, in_min int32, in_max int32, out_min int32, out_max int32) int32 {
|
|
var _x = int64(x)
|
|
var _in_min = int64(in_min)
|
|
var _in_max = int64(in_max)
|
|
var _out_min = int64(out_min)
|
|
var _out_max = int64(out_max)
|
|
var r = int64((_x-_in_min)*(_out_max-_out_min)/(_in_max-_in_min) + _out_min)
|
|
return int32(r)
|
|
}
|
|
|
|
func (r *waveformRenderer) audioDataToImage(w, h int) image.Image {
|
|
foregroundColor := theme.ForegroundColor()
|
|
if r.widget.OverrideForeground {
|
|
foregroundColor = r.widget.OverrideForegroundColor
|
|
}
|
|
upLeft := image.Point{0, 0}
|
|
lowRight := image.Point{w, h}
|
|
if r.widget.StretchSamples {
|
|
lowRight = image.Point{len(r.widget.audioData), h}
|
|
}
|
|
|
|
img := image.NewRGBA(image.Rectangle{upLeft, lowRight})
|
|
i := int32(0)
|
|
for i = 0; i < int32(len(r.widget.audioData)); i++ {
|
|
sample := r.widget.audioData[i]
|
|
// /2 because we draw waveforms with 0 at the middle!
|
|
sampleheight := int32Map(sample, 0, math.MaxInt32, 0, int32(h/2))
|
|
y := int32(0)
|
|
//center line
|
|
img.Set(int(i), int(h/2), foregroundColor)
|
|
for y = int32(h); y > int32(h)-sampleheight; y-- {
|
|
img.Set(int(i), int(y)-(h/2), foregroundColor)
|
|
}
|
|
for y = int32(h / 2); y < sampleheight+(int32(h)/2); y++ {
|
|
img.Set(int(i), int(y), foregroundColor)
|
|
}
|
|
}
|
|
if r.widget.StretchSamples {
|
|
return imaging.Resize(img, w, h, imaging.NearestNeighbor)
|
|
}
|
|
return img
|
|
}
|
|
|
|
//
|
|
// The Refresh() method is called if the state of the widget changes or the
|
|
// theme is changed
|
|
//
|
|
// Note: The background and foreground colours are set from the current theme
|
|
//
|
|
func (r *waveformRenderer) Refresh() {
|
|
backgroundColor := theme.BackgroundColor()
|
|
if r.widget.OverrideBackground {
|
|
backgroundColor = color.RGBA{0, 0, 0xff, 0xff} //r.widget.OverrideBackgroundColor
|
|
}
|
|
r.background.FillColor = backgroundColor
|
|
r.background.Refresh() // Redraw the background first
|
|
r.canvas.Refresh() // Redraw the waveform on top
|
|
}
|
|
|
|
//
|
|
// Given the size required by the fyne application move and re-size the
|
|
// canvas objects.
|
|
//
|
|
func (r *waveformRenderer) Layout(s fyne.Size) {
|
|
// Make sure the background fills the widget
|
|
r.background.Resize(s)
|
|
r.canvas.Resize(s)
|
|
r.Refresh()
|
|
}
|
|
|
|
//
|
|
// Create a minimum size for the widget.
|
|
// The smallest size is the size of the text with a border defined by the theme padding
|
|
//
|
|
func (r *waveformRenderer) MinSize() fyne.Size {
|
|
// Default is set in newSpectrumRenderer, overridable by users
|
|
return r.widget.minSize
|
|
}
|
|
|
|
//
|
|
// Return a list of each canvas object.
|
|
//
|
|
func (r *waveformRenderer) Objects() []fyne.CanvasObject {
|
|
if r.widget.TransparentBackground {
|
|
return []fyne.CanvasObject{r.canvas}
|
|
}
|
|
return []fyne.CanvasObject{r.background, r.canvas}
|
|
}
|
|
|
|
//
|
|
// Cleanup if resources have been allocated
|
|
//
|
|
func (r *waveformRenderer) Destroy() {}
|