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() {}