// // rhlibrary // // The Radio Helsinki Rivendell Library // // // Copyright (C) 2016 Christian Pointner // // This file is part of rhlibrary. // // rhlibrary is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // rhlibrary is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with rhlibrary. If not, see . // package main import ( "time" "code.helsinki.at/rhrd-go/player" "github.com/gotk3/gotk3/glib" "github.com/gotk3/gotk3/gtk" ) const ( meterDBFloor float64 = 72 // -72 db is the lowest VU value meterSegmentsGreen float64 = 30 meterSegmentsOrange float64 = 5 meterSegmentsRed float64 = 1 meterSegmentsTotal float64 = meterSegmentsGreen + meterSegmentsOrange + meterSegmentsRed ) var ( meterBarCSS *gtk.CssProvider = nil ) func getMeterBarCSS() (*gtk.CssProvider, error) { if meterBarCSS != nil { return meterBarCSS, nil } var err error if meterBarCSS, err = gtk.CssProviderNew(); err != nil { return nil, err } err = meterBarCSS.LoadFromData(` .level-bar { -GtkLevelBar-min-block-width: 13; -GtkLevelBar-min-block-height: 8; } .level-bar.trough { border: 0; padding: 0; border-radius: 0; background-image: unset; } .level-bar.fill-block { border-color: #323232 } .level-bar.fill-block.level-green { border-color: #006600; background-image: linear-gradient(to bottom, #36B736, #006600); } .level-bar.fill-block.level-orange { border-color: #AE6600; background-image: linear-gradient(to bottom, #FFAC36, #AE6600); } .level-bar.fill-block.level-red { border-color: #AE0000; background-image: linear-gradient(to bottom, #FF3636, #AE0000); }`) if err != nil { return nil, err } return meterBarCSS, nil } type vuMeterLevel struct { green float64 orange float64 red float64 } func (lvl *vuMeterLevel) setLevel(valdb float64) { if valdb < -1*meterDBFloor { valdb = -1 * meterDBFloor } val := valdb + meterDBFloor if val < 0 { val = 0 } else { val = (val / meterDBFloor) * meterSegmentsTotal } lvl.green = val lvl.orange = val - meterSegmentsGreen lvl.red = lvl.orange - meterSegmentsOrange if lvl.orange < 0 { lvl.orange = 0 } if lvl.red < 0 { lvl.red = 0 } return } type vuMeter struct { label *gtk.Label green *gtk.LevelBar orange *gtk.LevelBar red *gtk.LevelBar } func createMeterBar(segments float64) (bar *gtk.LevelBar, err error) { if bar, err = gtk.LevelBarNewForInterval(0, segments); err != nil { return } bar.SetMode(gtk.LEVEL_BAR_MODE_DISCRETE) bar.RemoveOffsetValue(gtk.LEVEL_BAR_OFFSET_LOW) bar.RemoveOffsetValue(gtk.LEVEL_BAR_OFFSET_HIGH) bar.SetHExpand(false) bar.SetVExpand(false) var sc *gtk.StyleContext if sc, err = bar.GetStyleContext(); err != nil { return } var cp *gtk.CssProvider if cp, err = getMeterBarCSS(); err != nil { return } sc.AddProvider(cp, 600) // TOOD: hardcoded value return } func newVUMeter(ch string) (meter *vuMeter, err error) { meter = &vuMeter{} if meter.label, err = gtk.LabelNew(ch); err != nil { return } if err = setCssStyle(&meter.label.Widget, ".label { color: #a2a2a2; padding: 0px 2px 0px 0px; font-size: 4.5px; font-variant: small-caps; font-weight: bold; }"); err != nil { return nil, err } if meter.green, err = createMeterBar(meterSegmentsGreen); err != nil { return } if meter.orange, err = createMeterBar(meterSegmentsOrange); err != nil { return } if meter.red, err = createMeterBar(meterSegmentsRed); err != nil { return } meter.green.AddOffsetValue("green", 0) meter.orange.AddOffsetValue("orange", 0) meter.red.AddOffsetValue("red", 0) return } func (m *vuMeter) SetValue(lvl *vuMeterLevel) { m.green.SetValue(lvl.green) m.orange.SetValue(lvl.orange) m.red.SetValue(lvl.red) } func updateVUMeterLevels(left, right *vuMeter, data player.Meter) { ch := len(data) lval := &vuMeterLevel{0, 0, 0} rval := &vuMeterLevel{0, 0, 0} if ch >= 2 { lval.setLevel(data[0].Peak) rval.setLevel(data[1].Peak) } else if ch == 1 { lval.setLevel(data[0].Peak) rval = lval } left.SetValue(lval) right.SetValue(rval) } func addVUMeterGrid(frame *gtk.Frame, p *player.PlayerChan) (err error) { var grid *gtk.Grid if grid, err = gtk.GridNew(); err != nil { return } grid.SetRowSpacing(2) var left, right *vuMeter if left, err = newVUMeter("L"); err != nil { return } if right, err = newVUMeter("R"); err != nil { return } p.AddUpdateHandler(func(_ time.Duration, _ time.Duration, meter player.Meter, _ interface{}) bool { glib.IdleAdd(func() { updateVUMeterLevels(left, right, meter) }) return true }, nil) grid.Attach(left.label, 1, 1, 1, 1) grid.Attach(left.green, 2, 1, 1, 1) grid.Attach(left.orange, 3, 1, 1, 1) grid.Attach(left.red, 4, 1, 1, 1) grid.Attach(right.label, 1, 2, 1, 1) grid.Attach(right.green, 2, 2, 1, 1) grid.Attach(right.orange, 3, 2, 1, 1) grid.Attach(right.red, 4, 2, 1, 1) grid.SetRowHomogeneous(true) frame.Add(grid) return } func getVUMeterWidget(p *player.PlayerChan) (gtk.IWidget, error) { frame, err := gtk.FrameNew("") if err != nil { return nil, err } if err = setCssStyle(&frame.Widget, ".frame { background-color: #2a2a2a; border: 1px solid; border-color: #a1a1a1; border-radius: 5px; padding: 11px; }"); err != nil { return nil, err } if err = addVUMeterGrid(frame, p); err != nil { return nil, err } return frame, nil }