feat(tracker, gioui): add visual indicators to show which instruments are playing

Closes #44
This commit is contained in:
vsariola 2021-04-11 17:51:34 +03:00
parent 1eca428801
commit b4705c941f
2 changed files with 46 additions and 13 deletions

View File

@ -3,6 +3,8 @@ package gioui
import (
"fmt"
"image"
"image/color"
"math"
"strconv"
"time"
@ -129,6 +131,21 @@ func (t *Tracker) layoutInstrumentNames(gtx C) D {
grabhandle.Text = ":::"
}
label := func(gtx C) D {
c := 0.0
voice := t.Song().Patch.FirstVoiceForInstrument(i)
for j := 0; j < t.Song().Patch[i].NumVoices; j++ {
released, event := t.player.VoiceState(voice)
vc := math.Exp(-float64(event)/15000) * .5
if !released {
vc += .5
}
if c < vc {
c = vc
}
voice++
}
k := byte(255 - c*127)
color := color.NRGBA{R: 255, G: k, B: 255, A: 255}
if i == t.InstrIndex() {
for _, ev := range t.InstrumentNameEditor.Events() {
_, ok := ev.(widget.SubmitEvent)
@ -141,7 +158,7 @@ func (t *Tracker) layoutInstrumentNames(gtx C) D {
t.InstrumentNameEditor.SetText(n)
}
editor := material.Editor(t.Theme, t.InstrumentNameEditor, "Instr")
editor.Color = instrumentNameColor
editor.Color = color
editor.HintColor = instrumentNameHintColor
editor.TextSize = unit.Dp(12)
dims := layout.Center.Layout(gtx, editor.Layout)
@ -152,7 +169,7 @@ func (t *Tracker) layoutInstrumentNames(gtx C) D {
if text == "" {
text = "Instr"
}
labelStyle := LabelStyle{Text: text, ShadeColor: black, Color: white, FontSize: unit.Sp(12)}
labelStyle := LabelStyle{Text: text, ShadeColor: black, Color: color, FontSize: unit.Sp(12)}
return layout.Center.Layout(gtx, labelStyle.Layout)
}
return layout.Inset{Left: unit.Dp(6), Right: unit.Dp(6)}.Layout(gtx, func(gtx C) D {

View File

@ -13,12 +13,13 @@ type Player struct {
playCmds chan uint64
mutex sync.Mutex
runningID uint32
voiceNoteID []uint32
voiceReleased []bool
synth sointu.Synth
patch sointu.Patch
mutex sync.Mutex
runningID uint32
voiceNoteID []uint32
voiceReleased []int32
synth sointu.Synth
patch sointu.Patch
samplesSinceEvent []int32
synthNotNil int32
}
@ -66,6 +67,13 @@ func (p *Player) Enabled() bool {
return atomic.LoadInt32(&p.synthNotNil) == 1
}
func (p *Player) VoiceState(voice int) (bool, int) {
if voice >= len(p.samplesSinceEvent) || voice >= len(p.voiceReleased) {
return true, math.MaxInt32
}
return atomic.LoadInt32(&p.voiceReleased[voice]) == 1, int(atomic.LoadInt32(&p.samplesSinceEvent[voice]))
}
func NewPlayer(service sointu.SynthService, closer <-chan struct{}, patchs <-chan sointu.Patch, scores <-chan sointu.Score, samplesPerRows <-chan int, posChanged chan<- struct{}, syncOutput chan<- []float32, outputs ...chan<- []float32) *Player {
p := &Player{playCmds: make(chan uint64, 16)}
go func() {
@ -189,6 +197,9 @@ func NewPlayer(service sointu.SynthService, closer <-chan struct{}, patchs <-cha
default:
}
}
for i := range p.samplesSinceEvent {
atomic.AddInt32(&p.samplesSinceEvent[i], int32(timeAdvanced))
}
for _, o := range outputs {
o <- buffer[:rendered*2]
}
@ -243,7 +254,10 @@ func (p *Player) trigger(voiceStart, voiceEnd int, note byte) uint32 {
oldestVoice := 0
for i := voiceStart; i < voiceEnd; i++ {
for len(p.voiceReleased) <= i {
p.voiceReleased = append(p.voiceReleased, true)
p.voiceReleased = append(p.voiceReleased, 1)
}
for len(p.samplesSinceEvent) <= i {
p.samplesSinceEvent = append(p.samplesSinceEvent, 0)
}
for len(p.voiceNoteID) <= i {
p.voiceNoteID = append(p.voiceNoteID, 0)
@ -253,7 +267,7 @@ func (p *Player) trigger(voiceStart, voiceEnd int, note byte) uint32 {
// case two voices are both playing or or both are released, we prefer
// the older one
id := p.voiceNoteID[i]
isReleased := p.voiceReleased[i]
isReleased := atomic.LoadInt32(&p.voiceReleased[i]) == 1
if id < oldestID && (oldestReleased == isReleased) || (!oldestReleased && isReleased) {
oldestVoice = i
oldestID = id
@ -261,7 +275,8 @@ func (p *Player) trigger(voiceStart, voiceEnd int, note byte) uint32 {
}
}
p.voiceNoteID[oldestVoice] = newID
p.voiceReleased[oldestVoice] = false
atomic.StoreInt32(&p.voiceReleased[oldestVoice], 0)
atomic.StoreInt32(&p.samplesSinceEvent[oldestVoice], 0)
if p.synth != nil {
p.synth.Trigger(oldestVoice, note)
}
@ -273,8 +288,9 @@ func (p *Player) release(ID uint32) {
return
}
for i := 0; i < len(p.voiceNoteID); i++ {
if p.voiceNoteID[i] == ID && !p.voiceReleased[i] {
p.voiceReleased[i] = true
if p.voiceNoteID[i] == ID && atomic.LoadInt32(&p.voiceReleased[i]) != 1 {
atomic.StoreInt32(&p.voiceReleased[i], 1)
atomic.StoreInt32(&p.samplesSinceEvent[i], 0)
p.synth.Release(i)
return
}