Merge pull request #429 from dweymouth/feature/peak-meter
Add a peak meter visualization
This commit is contained in:
@@ -124,6 +124,11 @@ type TranscodingConfig struct {
|
||||
ForceRawFile bool
|
||||
}
|
||||
|
||||
type PeakMeterConfig struct {
|
||||
WindowHeight int
|
||||
WindowWidth int
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Application AppConfig
|
||||
Servers []*ServerConfig
|
||||
@@ -141,6 +146,7 @@ type Config struct {
|
||||
ReplayGain ReplayGainConfig
|
||||
Transcoding TranscodingConfig
|
||||
Theme ThemeConfig
|
||||
PeakMeter PeakMeterConfig
|
||||
}
|
||||
|
||||
var SupportedStartupPages = []string{"Albums", "Favorites", "Playlists"}
|
||||
@@ -221,6 +227,10 @@ func DefaultConfig(appVersionTag string) *Config {
|
||||
Theme: ThemeConfig{
|
||||
Appearance: "Dark",
|
||||
},
|
||||
PeakMeter: PeakMeterConfig{
|
||||
WindowWidth: 375,
|
||||
WindowHeight: 100,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ type Player struct {
|
||||
prePausedState player.State
|
||||
clientName string
|
||||
equalizer Equalizer
|
||||
peaksEnabled bool
|
||||
|
||||
bgCancel context.CancelFunc
|
||||
|
||||
@@ -126,6 +127,7 @@ func (p *Player) Init(maxCacheMB int) error {
|
||||
if err := m.Initialize(); err != nil {
|
||||
return fmt.Errorf("error initializing mpv: %s", err.Error())
|
||||
}
|
||||
|
||||
p.mpv = m
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
@@ -355,20 +357,7 @@ func (p *Player) SetAudioDevice(deviceName string) error {
|
||||
|
||||
func (p *Player) SetEqualizer(eq Equalizer) error {
|
||||
p.equalizer = eq
|
||||
if eq == nil || !eq.IsEnabled() {
|
||||
return p.mpv.SetPropertyString("af", "")
|
||||
}
|
||||
af := ""
|
||||
if math.Abs(eq.Preamp()) > 0.01 {
|
||||
af = fmt.Sprintf("volume=volume=%0.1fdB", eq.Preamp())
|
||||
}
|
||||
eqAF := eq.Curve().String()
|
||||
if af == "" {
|
||||
af = eqAF
|
||||
} else if eqAF != "" {
|
||||
af = fmt.Sprintf("%s,%s", af, eqAF)
|
||||
}
|
||||
return p.mpv.SetPropertyString("af", af)
|
||||
return p.setAF()
|
||||
}
|
||||
|
||||
func (p *Player) Equalizer() Equalizer {
|
||||
@@ -453,6 +442,34 @@ func (p *Player) Destroy() {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Player) SetPeaksEnabled(enabled bool) error {
|
||||
if p.peaksEnabled == enabled {
|
||||
return nil
|
||||
}
|
||||
p.peaksEnabled = enabled
|
||||
return p.setAF()
|
||||
}
|
||||
|
||||
func (p *Player) GetPeaks() (float64, float64, float64, float64) {
|
||||
nInf := math.Inf(-1)
|
||||
if p.status.State != player.Playing {
|
||||
return nInf, nInf, nInf, nInf
|
||||
}
|
||||
prop, err := p.mpv.GetProperty("af-metadata/astats", mpv.FORMAT_NODE)
|
||||
if err != nil {
|
||||
return nInf, nInf, nInf, nInf
|
||||
}
|
||||
m := prop.(*mpv.Node).Data.(map[string]*mpv.Node)
|
||||
if lPeakNode, ok := m["lavfi.astats.1.Peak_level"]; ok {
|
||||
lPeak, _ := strconv.ParseFloat(lPeakNode.Data.(string), 64)
|
||||
rPeak, _ := strconv.ParseFloat(m["lavfi.astats.2.Peak_level"].Data.(string), 64)
|
||||
lRMS, _ := strconv.ParseFloat(m["lavfi.astats.1.RMS_level"].Data.(string), 64)
|
||||
rRMS, _ := strconv.ParseFloat(m["lavfi.astats.2.RMS_level"].Data.(string), 64)
|
||||
return lPeak, rPeak, lRMS, rRMS
|
||||
}
|
||||
return nInf, nInf, nInf, nInf
|
||||
}
|
||||
|
||||
// sets the state and invokes callbacks, if triggered
|
||||
func (p *Player) setState(s player.State) {
|
||||
switch {
|
||||
@@ -478,6 +495,26 @@ func (p *Player) setState(s player.State) {
|
||||
p.status.State = s
|
||||
}
|
||||
|
||||
func (p *Player) setAF() error {
|
||||
af := ""
|
||||
if p.peaksEnabled {
|
||||
af = "@astats:astats=metadata=1:reset=1:measure_overall=none"
|
||||
}
|
||||
eq := p.equalizer
|
||||
if eq == nil || !eq.IsEnabled() {
|
||||
return p.mpv.SetPropertyString("af", af)
|
||||
} else if p.peaksEnabled {
|
||||
af = af + ","
|
||||
}
|
||||
if math.Abs(eq.Preamp()) > 0.01 {
|
||||
af = fmt.Sprintf("%svolume=volume=%0.1fdB", af, eq.Preamp())
|
||||
}
|
||||
if eqAF := eq.Curve().String(); eqAF != "" {
|
||||
af = fmt.Sprintf("%s,%s", af, eqAF)
|
||||
}
|
||||
return p.mpv.SetPropertyString("af", af)
|
||||
}
|
||||
|
||||
func (p *Player) eventHandler(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -74,7 +74,6 @@ func main() {
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
mainWindow.ShowAndRun()
|
||||
|
||||
log.Println("Running shutdown tasks...")
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
"or when": "or when",
|
||||
"Password": "Password",
|
||||
"Paused": "Paused",
|
||||
"Peak Meter": "Peak Meter",
|
||||
"percent of track is played": "percent of track is played",
|
||||
"Play": "Play",
|
||||
"Play Artist Radio": "Play Artist Radio",
|
||||
|
||||
@@ -135,6 +135,12 @@ func (b *BrowsingPane) AddSettingsMenuItem(label string, action func()) {
|
||||
fyne.NewMenuItem(label, action))
|
||||
}
|
||||
|
||||
func (b *BrowsingPane) AddSettingsSubmenu(label string, menu *fyne.Menu) {
|
||||
item := fyne.NewMenuItem(label, nil)
|
||||
item.ChildMenu = menu
|
||||
b.settingsMenu.Items = append(b.settingsMenu.Items, item)
|
||||
}
|
||||
|
||||
func (b *BrowsingPane) AddSettingsMenuSeparator() {
|
||||
b.settingsMenu.Items = append(b.settingsMenu.Items,
|
||||
fyne.NewMenuItemSeparator())
|
||||
|
||||
@@ -36,6 +36,7 @@ type NavigationHandler func(Route)
|
||||
type CurPageFunc func() Route
|
||||
|
||||
type Controller struct {
|
||||
visualizationData
|
||||
AppVersion string
|
||||
App *backend.App
|
||||
MainWindow fyne.Window
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/lang"
|
||||
"github.com/dweymouth/supersonic/backend/player"
|
||||
"github.com/dweymouth/supersonic/ui/util"
|
||||
"github.com/dweymouth/supersonic/ui/visualizations"
|
||||
)
|
||||
|
||||
// embedded in parent controller struct
|
||||
type visualizationData struct {
|
||||
peakMeter *visualizations.PeakMeter
|
||||
|
||||
visualizationAnim *fyne.Animation
|
||||
}
|
||||
|
||||
func (c *Controller) InitVisualizations() {
|
||||
c.App.LocalPlayer.OnStopped(c.stopVisualizationAnim)
|
||||
c.App.LocalPlayer.OnPaused(c.stopVisualizationAnim)
|
||||
c.App.LocalPlayer.OnPlaying(func() {
|
||||
if c.peakMeter != nil {
|
||||
c.startVisualizationAnim()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Controller) ShowPeakMeter() {
|
||||
if c.peakMeter != nil {
|
||||
return
|
||||
}
|
||||
win := fyne.CurrentApp().NewWindow(lang.L("Peak Meter"))
|
||||
win.SetCloseIntercept(func() {
|
||||
c.stopVisualizationAnim()
|
||||
c.peakMeter = nil
|
||||
win.Close()
|
||||
util.SaveWindowSize(win,
|
||||
&c.App.Config.PeakMeter.WindowWidth,
|
||||
&c.App.Config.PeakMeter.WindowHeight)
|
||||
})
|
||||
if c.App.Config.PeakMeter.WindowHeight > 0 {
|
||||
win.Resize(fyne.NewSize(
|
||||
float32(c.App.Config.PeakMeter.WindowWidth),
|
||||
float32(c.App.Config.PeakMeter.WindowHeight)))
|
||||
}
|
||||
c.peakMeter = visualizations.NewPeakMeter()
|
||||
win.SetContent(c.peakMeter)
|
||||
if c.App.LocalPlayer.GetStatus().State == player.Playing {
|
||||
c.startVisualizationAnim()
|
||||
} else {
|
||||
// TODO: why is this needed?
|
||||
c.peakMeter.Refresh()
|
||||
}
|
||||
win.Show()
|
||||
}
|
||||
|
||||
func (c *Controller) stopVisualizationAnim() {
|
||||
if c.visualizationAnim != nil {
|
||||
c.visualizationAnim.Stop()
|
||||
c.visualizationAnim = nil
|
||||
c.App.LocalPlayer.SetPeaksEnabled(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) startVisualizationAnim() {
|
||||
if c.visualizationAnim == nil {
|
||||
c.App.LocalPlayer.SetPeaksEnabled(true)
|
||||
c.visualizationAnim = fyne.NewAnimation(
|
||||
time.Duration(math.MaxInt64), /*until stopped*/
|
||||
c.tickVisualizations)
|
||||
c.visualizationAnim.Start()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) tickVisualizations(_ float32) {
|
||||
lP, rP, lRMS, rRMS := c.App.LocalPlayer.GetPeaks()
|
||||
if c.visualizationData.peakMeter != nil {
|
||||
c.visualizationData.peakMeter.UpdatePeaks(lP, rP, lRMS, rRMS)
|
||||
}
|
||||
}
|
||||
+10
-5
@@ -3,7 +3,6 @@ package ui
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -16,11 +15,13 @@ import (
|
||||
"github.com/dweymouth/supersonic/ui/dialogs"
|
||||
"github.com/dweymouth/supersonic/ui/os"
|
||||
"github.com/dweymouth/supersonic/ui/theme"
|
||||
"github.com/dweymouth/supersonic/ui/util"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
"fyne.io/fyne/v2/lang"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
@@ -80,6 +81,7 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
|
||||
MainWindow: m.Window,
|
||||
App: app,
|
||||
}
|
||||
m.Controller.InitVisualizations()
|
||||
m.BrowsingPane = browsing.NewBrowsingPane(app, m.Controller, func() { m.Router.NavigateTo(m.StartupPage()) })
|
||||
m.Router = browsing.NewRouter(app, m.Controller, m.BrowsingPane)
|
||||
// inject controller dependencies
|
||||
@@ -136,6 +138,10 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
|
||||
m.BrowsingPane.AddSettingsMenuItem("Switch Servers", func() { app.ServerManager.Logout(false) })
|
||||
m.BrowsingPane.AddSettingsMenuItem("Rescan Library", func() { app.ServerManager.Server.RescanLibrary() })
|
||||
m.BrowsingPane.AddSettingsMenuSeparator()
|
||||
m.BrowsingPane.AddSettingsSubmenu(lang.L("Visualizations"),
|
||||
fyne.NewMenu("", []*fyne.MenuItem{
|
||||
fyne.NewMenuItem(lang.L("Peak Meter"), m.Controller.ShowPeakMeter),
|
||||
}...))
|
||||
m.BrowsingPane.AddSettingsMenuItem("Check for Updates", func() {
|
||||
go func() {
|
||||
if t := app.UpdateChecker.CheckLatestVersionTag(); t != "" && t != app.VersionTag() {
|
||||
@@ -429,8 +435,7 @@ func (m *MainWindow) Quit() {
|
||||
}
|
||||
|
||||
func (m *MainWindow) SaveWindowSize() {
|
||||
// round sizes to even to avoid Wayland issues with 2x scaling factor
|
||||
// https://github.com/dweymouth/supersonic/issues/212
|
||||
m.App.Config.Application.WindowHeight = int(math.RoundToEven(float64(m.Window.Canvas().Size().Height)))
|
||||
m.App.Config.Application.WindowWidth = int(math.RoundToEven(float64(m.Window.Canvas().Size().Width)))
|
||||
util.SaveWindowSize(m.Window,
|
||||
&m.App.Config.Application.WindowWidth,
|
||||
&m.App.Config.Application.WindowHeight)
|
||||
}
|
||||
|
||||
+2
-2
@@ -117,7 +117,7 @@ func (m *MyTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Col
|
||||
// average the Foreground and Disabled colors
|
||||
foreground := colorOrDefault(colors.Foreground, defColors.Foreground, theme.ColorNameForeground, variant)
|
||||
disabled := colorOrDefault(colors.Disabled, defColors.Disabled, theme.ColorNameDisabled, variant)
|
||||
return blendColors(foreground, disabled, 0.33)
|
||||
return BlendColors(foreground, disabled, 0.33)
|
||||
case ColorNameHoveredIconButton:
|
||||
if variant == theme.VariantDark {
|
||||
return color.White
|
||||
@@ -273,7 +273,7 @@ func (m *MyTheme) getVariant() fyne.ThemeVariant {
|
||||
return fyne.CurrentApp().Settings().ThemeVariant()
|
||||
}
|
||||
|
||||
func blendColors(a, b color.Color, fractionA float64) color.Color {
|
||||
func BlendColors(a, b color.Color, fractionA float64) color.Color {
|
||||
ra, ga, ba, aa := a.RGBA()
|
||||
rb, gb, bb, ab := b.RGBA()
|
||||
|
||||
|
||||
@@ -257,6 +257,13 @@ func NewTrailingAlignLabel() *widget.Label {
|
||||
return rt
|
||||
}
|
||||
|
||||
func SaveWindowSize(w fyne.Window, wPtr, hPtr *int) {
|
||||
// round sizes to even to avoid Wayland issues with 2x scaling factor
|
||||
// https://github.com/dweymouth/supersonic/issues/212
|
||||
*wPtr = int(math.RoundToEven(float64(w.Canvas().Size().Width)))
|
||||
*hPtr = int(math.RoundToEven(float64(w.Canvas().Size().Height)))
|
||||
}
|
||||
|
||||
type HSpace struct {
|
||||
widget.BaseWidget
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
package visualizations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
||||
)
|
||||
|
||||
const (
|
||||
meterRangeDB = 62
|
||||
rmsSmoothingFactor = 0.8
|
||||
peakHoldFrames = 60
|
||||
|
||||
noiseFloorDB = -96
|
||||
)
|
||||
|
||||
type PeakMeter struct {
|
||||
widget.BaseWidget
|
||||
lPeak float64
|
||||
rPeak float64
|
||||
lRMS float64
|
||||
rRMS float64
|
||||
lPeakHold float64
|
||||
rPeakHold float64
|
||||
lPeakHoldFrame uint64
|
||||
rPeakHoldFrame uint64
|
||||
frameCounter uint64
|
||||
}
|
||||
|
||||
func NewPeakMeter() *PeakMeter {
|
||||
p := &PeakMeter{
|
||||
lPeak: noiseFloorDB,
|
||||
rPeak: noiseFloorDB,
|
||||
lRMS: noiseFloorDB,
|
||||
rRMS: noiseFloorDB,
|
||||
lPeakHold: noiseFloorDB,
|
||||
rPeakHold: noiseFloorDB,
|
||||
}
|
||||
p.ExtendBaseWidget(p)
|
||||
return p
|
||||
}
|
||||
|
||||
// UpdatePeaks updates the peaks that are displayed in the meter.
|
||||
// This function is expected to be called from a fyne.Animation callback,
|
||||
// running at 60 Hz
|
||||
func (p *PeakMeter) UpdatePeaks(lPeak, rPeak, lRMS, rRMS float64) {
|
||||
p.lPeak = lPeak
|
||||
p.rPeak = rPeak
|
||||
lRMS = math.Max(noiseFloorDB, lRMS)
|
||||
rRMS = math.Max(noiseFloorDB, rRMS)
|
||||
p.lRMS = rmsSmoothingFactor*p.lRMS + (1-rmsSmoothingFactor)*lRMS
|
||||
p.rRMS = rmsSmoothingFactor*p.rRMS + (1-rmsSmoothingFactor)*rRMS
|
||||
|
||||
if lPeak > p.lPeakHold || p.frameCounter-p.lPeakHoldFrame > peakHoldFrames {
|
||||
p.lPeakHold = lPeak
|
||||
p.lPeakHoldFrame = p.frameCounter
|
||||
}
|
||||
if rPeak > p.rPeakHold || p.frameCounter-p.rPeakHoldFrame > peakHoldFrames {
|
||||
p.rPeakHold = rPeak
|
||||
p.rPeakHoldFrame = p.frameCounter
|
||||
}
|
||||
|
||||
p.frameCounter++
|
||||
p.Refresh()
|
||||
}
|
||||
|
||||
func (p *PeakMeter) CreateRenderer() fyne.WidgetRenderer {
|
||||
return newPeakMeterRenderer(p)
|
||||
}
|
||||
|
||||
type peakMeterRenderer struct {
|
||||
p *PeakMeter
|
||||
|
||||
lLabel canvas.Text
|
||||
rLabel canvas.Text
|
||||
lPeakRect canvas.Rectangle
|
||||
rPeakRect canvas.Rectangle
|
||||
lRMSRect canvas.Rectangle
|
||||
rRMSRect canvas.Rectangle
|
||||
lPeakHoldRect canvas.Rectangle
|
||||
rPeakHoldRect canvas.Rectangle
|
||||
|
||||
rulerLines []canvas.Rectangle
|
||||
rulerLabels []canvas.Text
|
||||
|
||||
objects []fyne.CanvasObject
|
||||
|
||||
fgColor color.Color
|
||||
bgColor color.Color
|
||||
ruleColor color.Color
|
||||
}
|
||||
|
||||
func newPeakMeterRenderer(pm *PeakMeter) *peakMeterRenderer {
|
||||
p := &peakMeterRenderer{p: pm}
|
||||
p.lLabel.Text = "L"
|
||||
p.rLabel.Text = "R"
|
||||
numRules := int(math.Ceil(float64(meterRangeDB) / 10))
|
||||
p.rulerLines = make([]canvas.Rectangle, numRules)
|
||||
p.rulerLabels = make([]canvas.Text, numRules)
|
||||
x := 0
|
||||
for i := range p.rulerLabels {
|
||||
p.rulerLabels[i].Text = fmt.Sprintf("%d dB", x)
|
||||
p.rulerLabels[i].Resize(p.rulerLabels[i].MinSize())
|
||||
x -= 10
|
||||
}
|
||||
p.Layout(pm.Size())
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (l *peakMeterRenderer) MinSize() fyne.Size {
|
||||
return fyne.NewSize(275, 75)
|
||||
}
|
||||
|
||||
func (l *peakMeterRenderer) Layout(size fyne.Size) {
|
||||
topSpacing := float32(5)
|
||||
lrLabelWidth := float32(20)
|
||||
overflowWidth := float32(10)
|
||||
ruleLabelHeight := float32(10)
|
||||
meterWidth := size.Width - lrLabelWidth - overflowWidth - topSpacing
|
||||
|
||||
lPeakWidth := float32(math.Max(0, meterRangeDB+l.p.lPeak)/meterRangeDB) * meterWidth
|
||||
rPeakWidth := float32(math.Max(0, meterRangeDB+l.p.rPeak)/meterRangeDB) * meterWidth
|
||||
lRMSWidth := float32(math.Max(0, meterRangeDB+l.p.lRMS)/meterRangeDB) * meterWidth
|
||||
rRMSWidth := float32(math.Max(0, meterRangeDB+l.p.rRMS)/meterRangeDB) * meterWidth
|
||||
lPeakHoldPos := float32(math.Max(0, meterRangeDB+l.p.lPeakHold)/meterRangeDB) * meterWidth
|
||||
rPeakHoldPos := float32(math.Max(0, meterRangeDB+l.p.rPeakHold)/meterRangeDB) * meterWidth
|
||||
|
||||
barSpacing := float32(2)
|
||||
barHeight := size.Height/2 - barSpacing - ruleLabelHeight
|
||||
|
||||
labelMin := l.lLabel.MinSize()
|
||||
l.lLabel.Move(fyne.NewPos(4, (barHeight-labelMin.Height)/2+topSpacing))
|
||||
l.lLabel.Resize(l.lLabel.MinSize())
|
||||
l.rLabel.Move(fyne.NewPos(4, barHeight+barSpacing+topSpacing+(barHeight-labelMin.Height)/2))
|
||||
|
||||
l.lPeakRect.Move(fyne.NewPos(lrLabelWidth, topSpacing))
|
||||
l.lPeakRect.Resize(fyne.NewSize(lPeakWidth, barHeight))
|
||||
l.rPeakRect.Move(fyne.NewPos(lrLabelWidth, barHeight+barSpacing+topSpacing))
|
||||
l.rPeakRect.Resize(fyne.NewSize(rPeakWidth, barHeight))
|
||||
l.lRMSRect.Move(fyne.NewPos(lrLabelWidth, topSpacing))
|
||||
l.lRMSRect.Resize(fyne.NewSize(lRMSWidth, barHeight))
|
||||
l.rRMSRect.Move(fyne.NewPos(lrLabelWidth, barHeight+barSpacing+topSpacing))
|
||||
l.rRMSRect.Resize(fyne.NewSize(rRMSWidth, barHeight))
|
||||
|
||||
peakHoldWidth := theme.SeparatorThicknessSize() * 2
|
||||
l.lPeakHoldRect.Move(fyne.NewPos(lPeakHoldPos+lrLabelWidth, topSpacing))
|
||||
l.lPeakHoldRect.Resize(fyne.NewSize(peakHoldWidth, barHeight))
|
||||
l.rPeakHoldRect.Move(fyne.NewPos(rPeakHoldPos+lrLabelWidth, barHeight+barSpacing+topSpacing))
|
||||
l.rPeakHoldRect.Resize(fyne.NewSize(peakHoldWidth, barHeight))
|
||||
|
||||
ruleWidth := peakHoldWidth * 0.667
|
||||
x := lrLabelWidth + meterWidth
|
||||
for i := range l.rulerLines {
|
||||
bottom := (barHeight + barSpacing) * 2
|
||||
l.rulerLines[i].Move(fyne.NewPos(x, topSpacing))
|
||||
l.rulerLines[i].Resize(fyne.NewSize(ruleWidth, bottom))
|
||||
l.rulerLabels[i].Move(fyne.NewPos(x-10, bottom+topSpacing))
|
||||
x -= meterWidth * (10 / float32(meterRangeDB))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *peakMeterRenderer) Refresh() {
|
||||
foreground := theme.ForegroundColor()
|
||||
background := theme.BackgroundColor()
|
||||
errC := theme.ErrorColor()
|
||||
c := theme.PrimaryColor().(color.NRGBA)
|
||||
c.A = 128
|
||||
l.lLabel.Color = foreground
|
||||
l.rLabel.Color = foreground
|
||||
l.lLabel.TextSize = 16
|
||||
l.rLabel.TextSize = 16
|
||||
l.lLabel.TextStyle.Bold = true
|
||||
l.rLabel.TextStyle.Bold = true
|
||||
l.lPeakRect.FillColor = c
|
||||
l.rPeakRect.FillColor = c
|
||||
l.lRMSRect.FillColor = c
|
||||
l.rRMSRect.FillColor = c
|
||||
|
||||
if l.p.lPeakHold >= -0.00001 {
|
||||
l.lPeakHoldRect.FillColor = errC
|
||||
} else {
|
||||
l.lPeakHoldRect.FillColor = foreground
|
||||
}
|
||||
if l.p.rPeakHold >= -0.00001 {
|
||||
l.rPeakHoldRect.FillColor = errC
|
||||
} else {
|
||||
l.rPeakHoldRect.FillColor = foreground
|
||||
}
|
||||
|
||||
if foreground != l.fgColor || background != l.bgColor {
|
||||
l.ruleColor = myTheme.BlendColors(foreground, background, 0.5)
|
||||
l.fgColor = foreground
|
||||
l.bgColor = background
|
||||
}
|
||||
for i := range l.rulerLines {
|
||||
l.rulerLines[i].FillColor = l.ruleColor
|
||||
l.rulerLabels[i].TextSize = 11
|
||||
}
|
||||
|
||||
l.Layout(l.p.Size())
|
||||
}
|
||||
|
||||
func (l *peakMeterRenderer) Objects() []fyne.CanvasObject {
|
||||
if l.objects == nil {
|
||||
l.objects = make([]fyne.CanvasObject, 0, 6+len(l.rulerLines))
|
||||
for i := range l.rulerLines {
|
||||
l.objects = append(l.objects, &l.rulerLines[i], &l.rulerLabels[i])
|
||||
}
|
||||
l.objects = append(l.objects,
|
||||
&l.lLabel, &l.rLabel,
|
||||
&l.lPeakRect, &l.rPeakRect,
|
||||
&l.lRMSRect, &l.rRMSRect,
|
||||
&l.lPeakHoldRect, &l.rPeakHoldRect)
|
||||
}
|
||||
return l.objects
|
||||
}
|
||||
|
||||
func (l *peakMeterRenderer) Destroy() {
|
||||
}
|
||||
Reference in New Issue
Block a user