mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
feat(tracker, gioui): add visual indicators to show which instruments are playing
Closes #44
This commit is contained in:
parent
1eca428801
commit
b4705c941f
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user