mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
refactor(tracker): make struct to hold all per voice data in Player
This commit is contained in:
parent
0ce5ca3003
commit
a60814bab7
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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():
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user