mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-20 22:14:35 -04:00
refactor(tracker): Player sends PlayerStatus to the Model
This commit is contained in:
parent
c77d541dc6
commit
4f2c73d0db
@ -6,7 +6,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/vsariola/sointu"
|
"github.com/vsariola/sointu"
|
||||||
"github.com/vsariola/sointu/vm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -59,11 +58,9 @@ type (
|
|||||||
// avoid allocations. All the infrequently passed messages can be boxed &
|
// avoid allocations. All the infrequently passed messages can be boxed &
|
||||||
// cast to any; casting pointer types to any is cheap (does not allocate).
|
// cast to any; casting pointer types to any is cheap (does not allocate).
|
||||||
MsgToModel struct {
|
MsgToModel struct {
|
||||||
HasPanicPosLevels bool
|
HasPanicPlayerStatus bool
|
||||||
Panic bool
|
Panic bool
|
||||||
SongPosition sointu.SongPos
|
PlayerStatus PlayerStatus
|
||||||
VoiceLevels [vm.MAX_VOICES]float32
|
|
||||||
CPULoad float64
|
|
||||||
|
|
||||||
HasDetectorResult bool
|
HasDetectorResult bool
|
||||||
DetectorResult DetectorResult
|
DetectorResult DetectorResult
|
||||||
|
@ -174,7 +174,7 @@ func (v *Instruments) Item(i int) (name string, maxLevel float32, mute bool, ok
|
|||||||
end = vm.MAX_VOICES
|
end = vm.MAX_VOICES
|
||||||
}
|
}
|
||||||
if start < end {
|
if start < end {
|
||||||
for _, level := range v.voiceLevels[start:end] {
|
for _, level := range v.playerStatus.VoiceLevels[start:end] {
|
||||||
if maxLevel < level {
|
if maxLevel < level {
|
||||||
maxLevel = level
|
maxLevel = level
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/vsariola/sointu"
|
"github.com/vsariola/sointu"
|
||||||
"github.com/vsariola/sointu/vm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Model implements the mutable state for the tracker program GUI.
|
// Model implements the mutable state for the tracker program GUI.
|
||||||
@ -58,7 +57,6 @@ type (
|
|||||||
panic bool
|
panic bool
|
||||||
recording bool
|
recording bool
|
||||||
playing bool
|
playing bool
|
||||||
playPosition sointu.SongPos
|
|
||||||
loop Loop
|
loop Loop
|
||||||
follow bool
|
follow bool
|
||||||
quitted bool
|
quitted bool
|
||||||
@ -68,8 +66,7 @@ type (
|
|||||||
// reordering or deleting instrument can delete track)
|
// reordering or deleting instrument can delete track)
|
||||||
linkInstrTrack bool
|
linkInstrTrack bool
|
||||||
|
|
||||||
voiceLevels [vm.MAX_VOICES]float32
|
playerStatus PlayerStatus
|
||||||
cpuLoad float64
|
|
||||||
|
|
||||||
signalAnalyzer *ScopeModel
|
signalAnalyzer *ScopeModel
|
||||||
detectorResult DetectorResult
|
detectorResult DetectorResult
|
||||||
@ -159,9 +156,9 @@ const (
|
|||||||
|
|
||||||
const maxUndo = 64
|
const maxUndo = 64
|
||||||
|
|
||||||
func (m *Model) PlayPosition() sointu.SongPos { return m.playPosition }
|
func (m *Model) PlayPosition() sointu.SongPos { return m.playerStatus.SongPos }
|
||||||
func (m *Model) Loop() Loop { return m.loop }
|
func (m *Model) Loop() Loop { return m.loop }
|
||||||
func (m *Model) PlaySongRow() int { return m.d.Song.Score.SongRow(m.playPosition) }
|
func (m *Model) PlaySongRow() int { return m.d.Song.Score.SongRow(m.playerStatus.SongPos) }
|
||||||
func (m *Model) ChangedSinceSave() bool { return m.d.ChangedSinceSave }
|
func (m *Model) ChangedSinceSave() bool { return m.d.ChangedSinceSave }
|
||||||
func (m *Model) Dialog() Dialog { return m.dialog }
|
func (m *Model) Dialog() Dialog { return m.dialog }
|
||||||
func (m *Model) Quitted() bool { return m.quitted }
|
func (m *Model) Quitted() bool { return m.quitted }
|
||||||
@ -332,19 +329,17 @@ func (m *Model) UnmarshalRecovery(bytes []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) ProcessMsg(msg MsgToModel) {
|
func (m *Model) ProcessMsg(msg MsgToModel) {
|
||||||
if msg.HasPanicPosLevels {
|
if msg.HasPanicPlayerStatus {
|
||||||
m.playPosition = msg.SongPosition
|
m.playerStatus = msg.PlayerStatus
|
||||||
m.voiceLevels = msg.VoiceLevels
|
|
||||||
if m.playing && m.follow {
|
if m.playing && m.follow {
|
||||||
m.d.Cursor.SongPos = msg.SongPosition
|
m.d.Cursor.SongPos = msg.PlayerStatus.SongPos
|
||||||
m.d.Cursor2.SongPos = msg.SongPosition
|
m.d.Cursor2.SongPos = msg.PlayerStatus.SongPos
|
||||||
TrySend(m.broker.ToGUI, any(MsgToGUI{
|
TrySend(m.broker.ToGUI, any(MsgToGUI{
|
||||||
Kind: GUIMessageCenterOnRow,
|
Kind: GUIMessageCenterOnRow,
|
||||||
Param: m.PlaySongRow(),
|
Param: m.PlaySongRow(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
m.panic = msg.Panic
|
m.panic = msg.Panic
|
||||||
m.cpuLoad = msg.CPULoad
|
|
||||||
}
|
}
|
||||||
if msg.HasDetectorResult {
|
if msg.HasDetectorResult {
|
||||||
m.detectorResult = msg.DetectorResult
|
m.detectorResult = msg.DetectorResult
|
||||||
@ -379,7 +374,7 @@ func (m *Model) ProcessMsg(msg MsgToModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) CPULoad() float64 { return m.cpuLoad }
|
func (m *Model) CPULoad() float64 { return m.playerStatus.CPULoad }
|
||||||
|
|
||||||
func (m *Model) SignalAnalyzer() *ScopeModel { return m.signalAnalyzer }
|
func (m *Model) SignalAnalyzer() *ScopeModel { return m.signalAnalyzer }
|
||||||
func (m *Model) Broker() *Broker { return m.broker }
|
func (m *Model) Broker() *Broker { return m.broker }
|
||||||
|
@ -22,8 +22,6 @@ type (
|
|||||||
song sointu.Song // the song being played
|
song sointu.Song // the song being played
|
||||||
playing bool // is the player playing the score or not
|
playing bool // is the player playing the score or not
|
||||||
rowtime int // how many samples have been played in the current row
|
rowtime int // how many samples have been played in the current row
|
||||||
songPos sointu.SongPos // the current position in the score
|
|
||||||
voiceLevels [vm.MAX_VOICES]float32 // a level that can be used to visualize the volume of each voice
|
|
||||||
voices [vm.MAX_VOICES]voice
|
voices [vm.MAX_VOICES]voice
|
||||||
loop Loop
|
loop Loop
|
||||||
|
|
||||||
@ -33,12 +31,20 @@ type (
|
|||||||
frameDeltas map[any]int64 // Player.frame (approx.)= event.Timestamp + frameDeltas[event.Source]
|
frameDeltas map[any]int64 // Player.frame (approx.)= event.Timestamp + frameDeltas[event.Source]
|
||||||
events NoteEventList
|
events NoteEventList
|
||||||
|
|
||||||
cpuload float64 // current CPU load of the player, used to adjust the render rate
|
status PlayerStatus // the part of the Player state that is communicated to the model to visualize what Player is doing
|
||||||
|
|
||||||
synther sointu.Synther // the synther used to create new synths
|
synther sointu.Synther // the synther used to create new synths
|
||||||
broker *Broker // the broker used to communicate with different parts of the tracker
|
broker *Broker // the broker used to communicate with different parts of the tracker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PlayerStatus is the part of the player state that is communicated to the
|
||||||
|
// model, for different visualizations of what is happening in the player.
|
||||||
|
PlayerStatus struct {
|
||||||
|
SongPos sointu.SongPos // the current position in the score
|
||||||
|
VoiceLevels [vm.MAX_VOICES]float32 // a level that can be used to visualize the volume of each voice
|
||||||
|
CPULoad float64 // current CPU load of the player, used to adjust the render rate
|
||||||
|
}
|
||||||
|
|
||||||
// PlayerProcessContext is the context given to the player when processing
|
// PlayerProcessContext is the context given to the player when processing
|
||||||
// audio. Currently it is only used to get BPM from the VSTI host.
|
// audio. Currently it is only used to get BPM from the VSTI host.
|
||||||
PlayerProcessContext interface {
|
PlayerProcessContext interface {
|
||||||
@ -144,9 +150,9 @@ func (p *Player) Process(buffer sointu.AudioBuffer, context PlayerProcessContext
|
|||||||
alpha := float32(math.Exp(-float64(rendered) / 15000))
|
alpha := float32(math.Exp(-float64(rendered) / 15000))
|
||||||
for i, state := range p.voices {
|
for i, state := range p.voices {
|
||||||
if state.sustain {
|
if state.sustain {
|
||||||
p.voiceLevels[i] = (p.voiceLevels[i]-0.5)*alpha + 0.5
|
p.status.VoiceLevels[i] = (p.status.VoiceLevels[i]-0.5)*alpha + 0.5
|
||||||
} else {
|
} else {
|
||||||
p.voiceLevels[i] *= alpha
|
p.status.VoiceLevels[i] *= alpha
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// when the buffer is full, return
|
// when the buffer is full, return
|
||||||
@ -166,14 +172,14 @@ func (p *Player) advanceRow() {
|
|||||||
if p.song.Score.Length == 0 || p.song.Score.RowsPerPattern == 0 {
|
if p.song.Score.Length == 0 || p.song.Score.RowsPerPattern == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
origPos := p.songPos
|
origPos := p.status.SongPos
|
||||||
p.songPos.PatternRow++ // advance row (this is why we subtracted one in Play())
|
p.status.SongPos.PatternRow++ // advance row (this is why we subtracted one in Play())
|
||||||
if p.loop.Length > 0 && p.songPos.PatternRow >= p.song.Score.RowsPerPattern && p.songPos.OrderRow == p.loop.Start+p.loop.Length-1 {
|
if p.loop.Length > 0 && p.status.SongPos.PatternRow >= p.song.Score.RowsPerPattern && p.status.SongPos.OrderRow == p.loop.Start+p.loop.Length-1 {
|
||||||
p.songPos.PatternRow = 0
|
p.status.SongPos.PatternRow = 0
|
||||||
p.songPos.OrderRow = p.loop.Start
|
p.status.SongPos.OrderRow = p.loop.Start
|
||||||
}
|
}
|
||||||
p.songPos = p.song.Score.Clamp(p.songPos)
|
p.status.SongPos = p.song.Score.Clamp(p.status.SongPos)
|
||||||
if p.songPos == origPos {
|
if p.status.SongPos == origPos {
|
||||||
p.send(IsPlayingMsg{bool: false})
|
p.send(IsPlayingMsg{bool: false})
|
||||||
p.playing = false
|
p.playing = false
|
||||||
for i := range p.song.Score.Tracks {
|
for i := range p.song.Score.Tracks {
|
||||||
@ -182,7 +188,7 @@ func (p *Player) advanceRow() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for i, t := range p.song.Score.Tracks {
|
for i, t := range p.song.Score.Tracks {
|
||||||
n := t.Note(p.songPos)
|
n := t.Note(p.status.SongPos)
|
||||||
switch {
|
switch {
|
||||||
case n == 0:
|
case n == 0:
|
||||||
p.processNoteEvent(NoteEvent{Channel: i, IsTrack: true, Source: p, On: false})
|
p.processNoteEvent(NoteEvent{Channel: i, IsTrack: true, Source: p, On: false})
|
||||||
@ -233,8 +239,8 @@ loop:
|
|||||||
p.compileOrUpdateSynth()
|
p.compileOrUpdateSynth()
|
||||||
case StartPlayMsg:
|
case StartPlayMsg:
|
||||||
p.playing = true
|
p.playing = true
|
||||||
p.songPos = m.SongPos
|
p.status.SongPos = m.SongPos
|
||||||
p.songPos.PatternRow--
|
p.status.SongPos.PatternRow--
|
||||||
p.rowtime = math.MaxInt
|
p.rowtime = math.MaxInt
|
||||||
for i, t := range p.song.Score.Tracks {
|
for i, t := range p.song.Score.Tracks {
|
||||||
if !t.Effect {
|
if !t.Effect {
|
||||||
@ -352,7 +358,7 @@ func (p *Player) compileOrUpdateSynth() {
|
|||||||
|
|
||||||
// all sendTargets from player are always non-blocking, to ensure that the player thread cannot end up in a dead-lock
|
// all sendTargets 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{}) {
|
||||||
TrySend(p.broker.ToModel, MsgToModel{HasPanicPosLevels: true, Panic: p.synth == nil, SongPosition: p.songPos, VoiceLevels: p.voiceLevels, CPULoad: p.cpuload, Data: message})
|
TrySend(p.broker.ToModel, MsgToModel{HasPanicPlayerStatus: true, Panic: p.synth == nil, PlayerStatus: p.status, Data: message})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) processNoteEvent(ev NoteEvent) {
|
func (p *Player) processNoteEvent(ev NoteEvent) {
|
||||||
@ -408,7 +414,7 @@ func (p *Player) processNoteEvent(ev NoteEvent) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.voices[oldestVoice] = voice{triggerEvent: ev, sustain: true, samplesSinceEvent: 0}
|
p.voices[oldestVoice] = voice{triggerEvent: ev, sustain: true, samplesSinceEvent: 0}
|
||||||
p.voiceLevels[oldestVoice] = 1.0
|
p.status.VoiceLevels[oldestVoice] = 1.0
|
||||||
p.synth.Trigger(oldestVoice, ev.Note)
|
p.synth.Trigger(oldestVoice, ev.Note)
|
||||||
TrySend(p.broker.ToModel, MsgToModel{TriggerChannel: instrIndex + 1})
|
TrySend(p.broker.ToModel, MsgToModel{TriggerChannel: instrIndex + 1})
|
||||||
}
|
}
|
||||||
@ -421,5 +427,5 @@ func (p *Player) updateCPULoad(duration time.Duration, frames int64) {
|
|||||||
songtime := float64(frames) / 44100
|
songtime := float64(frames) / 44100
|
||||||
newload := realtime / songtime
|
newload := realtime / songtime
|
||||||
alpha := math.Exp(-songtime) // smoothing factor, time constant of 1 second
|
alpha := math.Exp(-songtime) // smoothing factor, time constant of 1 second
|
||||||
p.cpuload = float64(p.cpuload)*alpha + newload*(1-alpha)
|
p.status.CPULoad = float64(p.status.CPULoad)*alpha + newload*(1-alpha)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user