mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-03 00:58:26 -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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -129,6 +131,21 @@ func (t *Tracker) layoutInstrumentNames(gtx C) D {
|
|||||||
grabhandle.Text = ":::"
|
grabhandle.Text = ":::"
|
||||||
}
|
}
|
||||||
label := func(gtx C) D {
|
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() {
|
if i == t.InstrIndex() {
|
||||||
for _, ev := range t.InstrumentNameEditor.Events() {
|
for _, ev := range t.InstrumentNameEditor.Events() {
|
||||||
_, ok := ev.(widget.SubmitEvent)
|
_, ok := ev.(widget.SubmitEvent)
|
||||||
@ -141,7 +158,7 @@ func (t *Tracker) layoutInstrumentNames(gtx C) D {
|
|||||||
t.InstrumentNameEditor.SetText(n)
|
t.InstrumentNameEditor.SetText(n)
|
||||||
}
|
}
|
||||||
editor := material.Editor(t.Theme, t.InstrumentNameEditor, "Instr")
|
editor := material.Editor(t.Theme, t.InstrumentNameEditor, "Instr")
|
||||||
editor.Color = instrumentNameColor
|
editor.Color = color
|
||||||
editor.HintColor = instrumentNameHintColor
|
editor.HintColor = instrumentNameHintColor
|
||||||
editor.TextSize = unit.Dp(12)
|
editor.TextSize = unit.Dp(12)
|
||||||
dims := layout.Center.Layout(gtx, editor.Layout)
|
dims := layout.Center.Layout(gtx, editor.Layout)
|
||||||
@ -152,7 +169,7 @@ func (t *Tracker) layoutInstrumentNames(gtx C) D {
|
|||||||
if text == "" {
|
if text == "" {
|
||||||
text = "Instr"
|
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.Center.Layout(gtx, labelStyle.Layout)
|
||||||
}
|
}
|
||||||
return layout.Inset{Left: unit.Dp(6), Right: unit.Dp(6)}.Layout(gtx, func(gtx C) D {
|
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
|
playCmds chan uint64
|
||||||
|
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
runningID uint32
|
runningID uint32
|
||||||
voiceNoteID []uint32
|
voiceNoteID []uint32
|
||||||
voiceReleased []bool
|
voiceReleased []int32
|
||||||
synth sointu.Synth
|
synth sointu.Synth
|
||||||
patch sointu.Patch
|
patch sointu.Patch
|
||||||
|
samplesSinceEvent []int32
|
||||||
|
|
||||||
synthNotNil int32
|
synthNotNil int32
|
||||||
}
|
}
|
||||||
@ -66,6 +67,13 @@ func (p *Player) Enabled() bool {
|
|||||||
return atomic.LoadInt32(&p.synthNotNil) == 1
|
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 {
|
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)}
|
p := &Player{playCmds: make(chan uint64, 16)}
|
||||||
go func() {
|
go func() {
|
||||||
@ -189,6 +197,9 @@ func NewPlayer(service sointu.SynthService, closer <-chan struct{}, patchs <-cha
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for i := range p.samplesSinceEvent {
|
||||||
|
atomic.AddInt32(&p.samplesSinceEvent[i], int32(timeAdvanced))
|
||||||
|
}
|
||||||
for _, o := range outputs {
|
for _, o := range outputs {
|
||||||
o <- buffer[:rendered*2]
|
o <- buffer[:rendered*2]
|
||||||
}
|
}
|
||||||
@ -243,7 +254,10 @@ func (p *Player) trigger(voiceStart, voiceEnd int, note byte) uint32 {
|
|||||||
oldestVoice := 0
|
oldestVoice := 0
|
||||||
for i := voiceStart; i < voiceEnd; i++ {
|
for i := voiceStart; i < voiceEnd; i++ {
|
||||||
for len(p.voiceReleased) <= 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 {
|
for len(p.voiceNoteID) <= i {
|
||||||
p.voiceNoteID = append(p.voiceNoteID, 0)
|
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
|
// case two voices are both playing or or both are released, we prefer
|
||||||
// the older one
|
// the older one
|
||||||
id := p.voiceNoteID[i]
|
id := p.voiceNoteID[i]
|
||||||
isReleased := p.voiceReleased[i]
|
isReleased := atomic.LoadInt32(&p.voiceReleased[i]) == 1
|
||||||
if id < oldestID && (oldestReleased == isReleased) || (!oldestReleased && isReleased) {
|
if id < oldestID && (oldestReleased == isReleased) || (!oldestReleased && isReleased) {
|
||||||
oldestVoice = i
|
oldestVoice = i
|
||||||
oldestID = id
|
oldestID = id
|
||||||
@ -261,7 +275,8 @@ func (p *Player) trigger(voiceStart, voiceEnd int, note byte) uint32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.voiceNoteID[oldestVoice] = newID
|
p.voiceNoteID[oldestVoice] = newID
|
||||||
p.voiceReleased[oldestVoice] = false
|
atomic.StoreInt32(&p.voiceReleased[oldestVoice], 0)
|
||||||
|
atomic.StoreInt32(&p.samplesSinceEvent[oldestVoice], 0)
|
||||||
if p.synth != nil {
|
if p.synth != nil {
|
||||||
p.synth.Trigger(oldestVoice, note)
|
p.synth.Trigger(oldestVoice, note)
|
||||||
}
|
}
|
||||||
@ -273,8 +288,9 @@ func (p *Player) release(ID uint32) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for i := 0; i < len(p.voiceNoteID); i++ {
|
for i := 0; i < len(p.voiceNoteID); i++ {
|
||||||
if p.voiceNoteID[i] == ID && !p.voiceReleased[i] {
|
if p.voiceNoteID[i] == ID && atomic.LoadInt32(&p.voiceReleased[i]) != 1 {
|
||||||
p.voiceReleased[i] = true
|
atomic.StoreInt32(&p.voiceReleased[i], 1)
|
||||||
|
atomic.StoreInt32(&p.samplesSinceEvent[i], 0)
|
||||||
p.synth.Release(i)
|
p.synth.Release(i)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user