refactor(tracker): make struct to hold all per voice data in Player

This commit is contained in:
5684185+vsariola@users.noreply.github.com 2023-10-20 01:26:41 +03:00
parent 0ce5ca3003
commit a60814bab7
3 changed files with 34 additions and 53 deletions

View File

@ -48,7 +48,7 @@ type InstrumentEditor struct {
tag bool tag bool
wasFocused bool wasFocused bool
commentExpanded bool commentExpanded bool
voiceStates [vm.MAX_VOICES]float32 voiceLevels [vm.MAX_VOICES]float32
presetMenuItems []MenuItem presetMenuItems []MenuItem
presetMenu Menu presetMenu Menu
} }
@ -314,7 +314,7 @@ func (ie *InstrumentEditor) layoutInstrumentNames(gtx C, t *Tracker) D {
loopMax = vm.MAX_VOICES loopMax = vm.MAX_VOICES
} }
for j := 0; j < loopMax; j++ { for j := 0; j < loopMax; j++ {
vc := ie.voiceStates[voice] vc := ie.voiceLevels[voice]
if c < vc { if c < vc {
c = vc c = vc
} }

View File

@ -201,7 +201,7 @@ mainloop:
} }
t.lastAvgVolume = e.AverageVolume t.lastAvgVolume = e.AverageVolume
t.lastPeakVolume = e.PeakVolume t.lastPeakVolume = e.PeakVolume
t.InstrumentEditor.voiceStates = e.VoiceStates t.InstrumentEditor.voiceLevels = e.VoiceLevels
t.ProcessPlayerMessage(e) t.ProcessPlayerMessage(e)
w.Invalidate() w.Invalidate()
case e := <-w.Events(): case e := <-w.Events():

View File

@ -15,17 +15,15 @@ type (
// model via the playerMessages channel. The model sends messages to the // model via the playerMessages channel. The model sends messages to the
// player via the modelMessages channel. // player via the modelMessages channel.
Player struct { Player struct {
voiceNoteID []int // the ID of the note that triggered the voice synth sointu.Synth // the synth used to render audio
voiceReleased []bool // is the voice released song sointu.Song // the song being played
synth sointu.Synth // the synth used to render audio playing bool // is the player playing the score or not
song sointu.Song // the song being played rowtime int // how many samples have been played in the current row
playing bool // is the player playing the score or not position ScoreRow // the current position in the score
rowtime int // how many samples have been played in the current row avgVolumeMeter VolumeAnalyzer // the volume analyzer used to calculate the average volume
position ScoreRow // the current position in the score peakVolumeMeter VolumeAnalyzer // the volume analyzer used to calculate the peak volume
samplesSinceEvent []int // how many samples have been played since the last event in each voice voiceLevels [vm.MAX_VOICES]float32 // a level that can be used to visualize the volume of each voice
avgVolumeMeter VolumeAnalyzer // the volume analyzer used to calculate the average volume voices [vm.MAX_VOICES]voice
peakVolumeMeter VolumeAnalyzer // the volume analyzer used to calculate the peak volume
voiceStates [vm.MAX_VOICES]float32 // the current state of each voice
recState recState // is the recording off; are we waiting for a note; or are we recording recState recState // is the recording off; are we waiting for a note; or are we recording
recording Recording // the recorded MIDI events and BPM recording Recording // the recorded MIDI events and BPM
@ -62,7 +60,7 @@ type (
AverageVolume Volume AverageVolume Volume
PeakVolume Volume PeakVolume Volume
SongRow ScoreRow SongRow ScoreRow
VoiceStates [vm.MAX_VOICES]float32 VoiceLevels [vm.MAX_VOICES]float32
Inner interface{} Inner interface{}
} }
@ -85,13 +83,10 @@ type (
type ( type (
recState int recState int
voiceNote struct { voice struct {
voice int noteID int
note byte sustain bool
} samplesSinceEvent int
recordEvent struct {
frame int
} }
) )
@ -184,15 +179,15 @@ func (p *Player) Process(buffer sointu.AudioBuffer, context PlayerProcessContext
buffer = buffer[rendered:] buffer = buffer[rendered:]
frame += rendered frame += rendered
p.rowtime += timeAdvanced p.rowtime += timeAdvanced
for i := range p.samplesSinceEvent { for i := range p.voices {
p.samplesSinceEvent[i] += rendered p.voices[i].samplesSinceEvent += rendered
} }
alpha := float32(math.Exp(-float64(rendered) / 15000)) alpha := float32(math.Exp(-float64(rendered) / 15000))
for i, released := range p.voiceReleased { for i, state := range p.voices {
if released { if state.sustain {
p.voiceStates[i] *= alpha p.voiceLevels[i] = (p.voiceLevels[i]-0.5)*alpha + 0.5
} else { } else {
p.voiceStates[i] = (p.voiceStates[i]-0.5)*alpha + 0.5 p.voiceLevels[i] *= alpha
} }
} }
// when the buffer is full, return // when the buffer is full, return
@ -346,16 +341,13 @@ func (p *Player) compileOrUpdateSynth() {
p.send(PlayerCrashMessage{fmt.Errorf("synther.Synth: %w", err)}) p.send(PlayerCrashMessage{fmt.Errorf("synther.Synth: %w", err)})
return return
} }
for i := 0; i < 32; i++ {
p.synth.Release(i)
}
} }
} }
// all sends from player are always non-blocking, to ensure that the player thread cannot end up in a dead-lock // all sends from player are always non-blocking, to ensure that the player thread cannot end up in a dead-lock
func (p *Player) send(message interface{}) { func (p *Player) send(message interface{}) {
select { select {
case p.playerMessages <- PlayerMessage{Panic: p.synth == nil, AverageVolume: p.avgVolumeMeter.Level, PeakVolume: p.peakVolumeMeter.Level, SongRow: p.position, VoiceStates: p.voiceStates, Inner: message}: case p.playerMessages <- PlayerMessage{Panic: p.synth == nil, AverageVolume: p.avgVolumeMeter.Level, PeakVolume: p.peakVolumeMeter.Level, SongRow: p.position, VoiceLevels: p.voiceLevels, Inner: message}:
default: default:
} }
} }
@ -395,30 +387,19 @@ func (p *Player) trigger(voiceStart, voiceEnd int, note byte, ID int) {
oldestReleased := false oldestReleased := false
oldestVoice := 0 oldestVoice := 0
for i := voiceStart; i < voiceEnd; i++ { for i := voiceStart; i < voiceEnd; i++ {
for len(p.voiceReleased) <= i {
p.voiceReleased = append(p.voiceReleased, true)
}
for len(p.samplesSinceEvent) <= i {
p.samplesSinceEvent = append(p.samplesSinceEvent, 0)
}
for len(p.voiceNoteID) <= i {
p.voiceNoteID = append(p.voiceNoteID, 0)
}
// find a suitable voice to trigger. if the voice has been released, // find a suitable voice to trigger. if the voice has been released,
// then we prefer to trigger that over a voice that is still playing. in // then we prefer to trigger that over a voice that is still playing. in
// 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
if (p.voiceReleased[i] && !oldestReleased) || if (!p.voices[i].sustain && !oldestReleased) ||
(p.voiceReleased[i] == oldestReleased && p.samplesSinceEvent[i] >= age) { (!p.voices[i].sustain == oldestReleased && p.voices[i].samplesSinceEvent >= age) {
oldestVoice = i oldestVoice = i
oldestReleased = p.voiceReleased[i] oldestReleased = !p.voices[i].sustain
age = p.samplesSinceEvent[i] age = p.voices[i].samplesSinceEvent
} }
} }
p.voiceNoteID[oldestVoice] = ID p.voices[oldestVoice] = voice{noteID: ID, sustain: true, samplesSinceEvent: 0}
p.voiceReleased[oldestVoice] = false p.voiceLevels[oldestVoice] = 1.0
p.voiceStates[oldestVoice] = 1.0
p.samplesSinceEvent[oldestVoice] = 0
if p.synth != nil { if p.synth != nil {
p.synth.Trigger(oldestVoice, note) p.synth.Trigger(oldestVoice, note)
} }
@ -428,10 +409,10 @@ func (p *Player) release(ID int) {
if p.synth == nil { if p.synth == nil {
return return
} }
for i := 0; i < len(p.voiceNoteID); i++ { for i := range p.voices {
if p.voiceNoteID[i] == ID && !p.voiceReleased[i] { if p.voices[i].noteID == ID && p.voices[i].sustain {
p.voiceReleased[i] = true p.voices[i].sustain = false
p.samplesSinceEvent[i] = 0 p.voices[i].samplesSinceEvent = 0
p.synth.Release(i) p.synth.Release(i)
return return
} }