mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-04 01:28:45 -04:00
refactor(tracker): send Songs/Patches etc. from Model to Player
This commit is contained in:
parent
14a0306064
commit
0ce5ca3003
132
tracker/model.go
132
tracker/model.go
@ -59,14 +59,6 @@ type (
|
|||||||
modelMessages chan<- interface{}
|
modelMessages chan<- interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelPatchChangedMessage struct {
|
|
||||||
sointu.Patch
|
|
||||||
}
|
|
||||||
|
|
||||||
ModelScoreChangedMessage struct {
|
|
||||||
sointu.Score
|
|
||||||
}
|
|
||||||
|
|
||||||
ModelPlayingChangedMessage struct {
|
ModelPlayingChangedMessage struct {
|
||||||
bool
|
bool
|
||||||
}
|
}
|
||||||
@ -75,8 +67,12 @@ type (
|
|||||||
ScoreRow
|
ScoreRow
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelSamplesPerRowChangedMessage struct {
|
ModelBPMChangedMessage struct {
|
||||||
BPM, RowsPerBeat int
|
int
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelRowsPerBeatChangedMessage struct {
|
||||||
|
int
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelPanicMessage struct {
|
ModelPanicMessage struct {
|
||||||
@ -88,11 +84,11 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
ModelNoteOnMessage struct {
|
ModelNoteOnMessage struct {
|
||||||
id NoteID
|
NoteID
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelNoteOffMessage struct {
|
ModelNoteOffMessage struct {
|
||||||
id NoteID
|
NoteID
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -127,9 +123,7 @@ func NewModel(modelMessages chan<- interface{}, playerMessages <-chan PlayerMess
|
|||||||
if recoveryFilePath != "" {
|
if recoveryFilePath != "" {
|
||||||
if bytes2, err := os.ReadFile(ret.d.RecoveryFilePath); err == nil {
|
if bytes2, err := os.ReadFile(ret.d.RecoveryFilePath); err == nil {
|
||||||
json.Unmarshal(bytes2, &ret.d)
|
json.Unmarshal(bytes2, &ret.d)
|
||||||
ret.notifyPatchChange()
|
ret.send(ret.d.Song.Copy())
|
||||||
ret.notifySamplesPerRowChange()
|
|
||||||
ret.notifyScoreChange()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
@ -185,9 +179,7 @@ func (m *Model) UnmarshalRecovery(bytes []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.d.ChangedSinceRecovery = false
|
m.d.ChangedSinceRecovery = false
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Copy())
|
||||||
m.notifySamplesPerRowChange()
|
|
||||||
m.notifyScoreChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) FilePath() string {
|
func (m *Model) FilePath() string {
|
||||||
@ -237,9 +229,8 @@ func (m *Model) SetOctave(value int) bool {
|
|||||||
|
|
||||||
func (m *Model) ProcessPlayerMessage(msg PlayerMessage) {
|
func (m *Model) ProcessPlayerMessage(msg PlayerMessage) {
|
||||||
m.d.PlayPosition = msg.SongRow
|
m.d.PlayPosition = msg.SongRow
|
||||||
|
m.d.Panic = msg.Panic
|
||||||
switch e := msg.Inner.(type) {
|
switch e := msg.Inner.(type) {
|
||||||
case PlayerCrashMessage:
|
|
||||||
m.d.Panic = true
|
|
||||||
case Recording:
|
case Recording:
|
||||||
if e.BPM == 0 {
|
if e.BPM == 0 {
|
||||||
e.BPM = float64(m.d.Song.BPM)
|
e.BPM = float64(m.d.Song.BPM)
|
||||||
@ -263,7 +254,7 @@ func (m *Model) SetInstrument(instrument sointu.Instrument) bool {
|
|||||||
m.assignUnitIDs(instrument.Units)
|
m.assignUnitIDs(instrument.Units)
|
||||||
m.d.Song.Patch[m.d.InstrIndex] = instrument
|
m.d.Song.Patch[m.d.InstrIndex] = instrument
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Patch.Copy())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,7 +276,7 @@ func (m *Model) SetInstrumentVoices(value int) {
|
|||||||
}
|
}
|
||||||
m.saveUndo("SetInstrumentVoices", 10)
|
m.saveUndo("SetInstrumentVoices", 10)
|
||||||
m.d.Song.Patch[m.d.InstrIndex].NumVoices = value
|
m.d.Song.Patch[m.d.InstrIndex].NumVoices = value
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Patch.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) MaxInstrumentVoices() int {
|
func (m *Model) MaxInstrumentVoices() int {
|
||||||
@ -325,7 +316,7 @@ func (m *Model) SetBPM(value int) {
|
|||||||
}
|
}
|
||||||
m.saveUndo("SetBPM", 100)
|
m.saveUndo("SetBPM", 100)
|
||||||
m.d.Song.BPM = value
|
m.d.Song.BPM = value
|
||||||
m.notifySamplesPerRowChange()
|
m.send(ModelBPMChangedMessage{value})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) SetRowsPerBeat(value int) {
|
func (m *Model) SetRowsPerBeat(value int) {
|
||||||
@ -340,7 +331,7 @@ func (m *Model) SetRowsPerBeat(value int) {
|
|||||||
}
|
}
|
||||||
m.saveUndo("SetRowsPerBeat", 10)
|
m.saveUndo("SetRowsPerBeat", 10)
|
||||||
m.d.Song.RowsPerBeat = value
|
m.d.Song.RowsPerBeat = value
|
||||||
m.notifySamplesPerRowChange()
|
m.send(ModelRowsPerBeatChangedMessage{value})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) AddTrack(after bool) {
|
func (m *Model) AddTrack(after bool) {
|
||||||
@ -360,7 +351,7 @@ func (m *Model) AddTrack(after bool) {
|
|||||||
}
|
}
|
||||||
m.d.Song.Score.Tracks = newTracks
|
m.d.Song.Score.Tracks = newTracks
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) CanAddTrack() bool {
|
func (m *Model) CanAddTrack() bool {
|
||||||
@ -382,7 +373,7 @@ func (m *Model) DeleteTrack(forward bool) {
|
|||||||
m.d.SelectionCorner = m.d.Cursor
|
m.d.SelectionCorner = m.d.Cursor
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.computePatternUseCounts()
|
m.computePatternUseCounts()
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) CanDeleteTrack() bool {
|
func (m *Model) CanDeleteTrack() bool {
|
||||||
@ -397,7 +388,7 @@ func (m *Model) SwapTracks(i, j int) {
|
|||||||
tracks := m.d.Song.Score.Tracks
|
tracks := m.d.Song.Score.Tracks
|
||||||
tracks[i], tracks[j] = tracks[j], tracks[i]
|
tracks[i], tracks[j] = tracks[j], tracks[i]
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) SetTrackVoices(value int) {
|
func (m *Model) SetTrackVoices(value int) {
|
||||||
@ -413,7 +404,7 @@ func (m *Model) SetTrackVoices(value int) {
|
|||||||
}
|
}
|
||||||
m.saveUndo("SetTrackVoices", 10)
|
m.saveUndo("SetTrackVoices", 10)
|
||||||
m.d.Song.Score.Tracks[m.d.Cursor.Track].NumVoices = value
|
m.d.Song.Score.Tracks[m.d.Cursor.Track].NumVoices = value
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) MaxTrackVoices() int {
|
func (m *Model) MaxTrackVoices() int {
|
||||||
@ -441,15 +432,15 @@ func (m *Model) AddInstrument(after bool) {
|
|||||||
m.d.UnitIndex = 0
|
m.d.UnitIndex = 0
|
||||||
m.d.ParamIndex = 0
|
m.d.ParamIndex = 0
|
||||||
m.d.Song.Patch = newInstruments
|
m.d.Song.Patch = newInstruments
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Patch.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) NoteOn(id NoteID) {
|
func (m *Model) NoteOn(id NoteID) {
|
||||||
m.modelMessages <- ModelNoteOnMessage{id}
|
m.send(ModelNoteOnMessage{id})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) NoteOff(id NoteID) {
|
func (m *Model) NoteOff(id NoteID) {
|
||||||
m.modelMessages <- ModelNoteOffMessage{id}
|
m.send(ModelNoteOffMessage{id})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) Playing() bool {
|
func (m *Model) Playing() bool {
|
||||||
@ -459,7 +450,7 @@ func (m *Model) Playing() bool {
|
|||||||
func (m *Model) SetPlaying(val bool) {
|
func (m *Model) SetPlaying(val bool) {
|
||||||
if m.d.Playing != val {
|
if m.d.Playing != val {
|
||||||
m.d.Playing = val
|
m.d.Playing = val
|
||||||
m.modelMessages <- ModelPlayingChangedMessage{val}
|
m.send(ModelPlayingChangedMessage{val})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -479,7 +470,7 @@ func (m *Model) SwapInstruments(i, j int) {
|
|||||||
instruments := m.d.Song.Patch
|
instruments := m.d.Song.Patch
|
||||||
instruments[i], instruments[j] = instruments[j], instruments[i]
|
instruments[i], instruments[j] = instruments[j], instruments[i]
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Patch.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) DeleteInstrument(forward bool) {
|
func (m *Model) DeleteInstrument(forward bool) {
|
||||||
@ -493,7 +484,7 @@ func (m *Model) DeleteInstrument(forward bool) {
|
|||||||
m.d.InstrIndex--
|
m.d.InstrIndex--
|
||||||
}
|
}
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Patch.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) CanDeleteInstrument() bool {
|
func (m *Model) CanDeleteInstrument() bool {
|
||||||
@ -530,7 +521,7 @@ func (m *Model) SetNote(iv byte) {
|
|||||||
tracks[m.d.Cursor.Track].Patterns = append(tracks[m.d.Cursor.Track].Patterns, nil)
|
tracks[m.d.Cursor.Track].Patterns = append(tracks[m.d.Cursor.Track].Patterns, nil)
|
||||||
}
|
}
|
||||||
tracks[m.d.Cursor.Track].Patterns[patIndex].Set(m.d.Cursor.Row, iv)
|
tracks[m.d.Cursor.Track].Patterns[patIndex].Set(m.d.Cursor.Row, iv)
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) AdjustPatternNumber(delta int, swap bool) {
|
func (m *Model) AdjustPatternNumber(delta int, swap bool) {
|
||||||
@ -616,14 +607,14 @@ func (m *Model) AdjustPatternNumber(delta int, swap bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.computePatternUseCounts()
|
m.computePatternUseCounts()
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) SetRecording(val bool) {
|
func (m *Model) SetRecording(val bool) {
|
||||||
if m.d.Recording != val {
|
if m.d.Recording != val {
|
||||||
m.d.Recording = val
|
m.d.Recording = val
|
||||||
m.d.InstrEnlarged = val
|
m.d.InstrEnlarged = val
|
||||||
m.modelMessages <- ModelRecordingMessage{val}
|
m.send(ModelRecordingMessage{val})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -634,7 +625,7 @@ func (m *Model) Recording() bool {
|
|||||||
func (m *Model) SetPanic(val bool) {
|
func (m *Model) SetPanic(val bool) {
|
||||||
if m.d.Panic != val {
|
if m.d.Panic != val {
|
||||||
m.d.Panic = val
|
m.d.Panic = val
|
||||||
m.modelMessages <- ModelPanicMessage{val}
|
m.send(ModelPanicMessage{val})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -652,14 +643,14 @@ func (m *Model) InstrEnlarged() bool {
|
|||||||
|
|
||||||
func (m *Model) PlayFromPosition(sr ScoreRow) {
|
func (m *Model) PlayFromPosition(sr ScoreRow) {
|
||||||
m.d.Playing = true
|
m.d.Playing = true
|
||||||
m.modelMessages <- ModelPlayFromPositionMessage{sr}
|
m.send(ModelPlayFromPositionMessage{sr})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) SetCurrentPattern(pat int) {
|
func (m *Model) SetCurrentPattern(pat int) {
|
||||||
m.saveUndo("SetCurrentPattern", 0)
|
m.saveUndo("SetCurrentPattern", 0)
|
||||||
m.d.Song.Score.Tracks[m.d.Cursor.Track].Order.Set(m.d.Cursor.Pattern, pat)
|
m.d.Song.Score.Tracks[m.d.Cursor.Track].Order.Set(m.d.Cursor.Pattern, pat)
|
||||||
m.computePatternUseCounts()
|
m.computePatternUseCounts()
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) IsPatternUnique(track, pattern int) bool {
|
func (m *Model) IsPatternUnique(track, pattern int) bool {
|
||||||
@ -684,7 +675,7 @@ func (m *Model) SetSongLength(value int) {
|
|||||||
m.d.Song.Score.Length = value
|
m.d.Song.Score.Length = value
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.computePatternUseCounts()
|
m.computePatternUseCounts()
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) SetRowsPerPattern(value int) {
|
func (m *Model) SetRowsPerPattern(value int) {
|
||||||
@ -700,7 +691,7 @@ func (m *Model) SetRowsPerPattern(value int) {
|
|||||||
m.saveUndo("SetRowsPerPattern", 10)
|
m.saveUndo("SetRowsPerPattern", 10)
|
||||||
m.d.Song.Score.RowsPerPattern = value
|
m.d.Song.Score.RowsPerPattern = value
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) SetUnitType(t string) {
|
func (m *Model) SetUnitType(t string) {
|
||||||
@ -717,7 +708,7 @@ func (m *Model) SetUnitType(t string) {
|
|||||||
oldID := m.Unit().ID
|
oldID := m.Unit().ID
|
||||||
m.Instrument().Units[m.d.UnitIndex] = unit
|
m.Instrument().Units[m.d.UnitIndex] = unit
|
||||||
m.Instrument().Units[m.d.UnitIndex].ID = oldID // keep the ID of the replaced unit
|
m.Instrument().Units[m.d.UnitIndex].ID = oldID // keep the ID of the replaced unit
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Patch.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) PasteUnits(units []sointu.Unit) {
|
func (m *Model) PasteUnits(units []sointu.Unit) {
|
||||||
@ -737,7 +728,7 @@ func (m *Model) PasteUnits(units []sointu.Unit) {
|
|||||||
m.d.Song.Patch[m.d.InstrIndex].Units = newUnits
|
m.d.Song.Patch[m.d.InstrIndex].Units = newUnits
|
||||||
m.d.ParamIndex = 0
|
m.d.ParamIndex = 0
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Patch.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) SetUnitIndex(value int) {
|
func (m *Model) SetUnitIndex(value int) {
|
||||||
@ -758,7 +749,7 @@ func (m *Model) AddUnit(after bool) {
|
|||||||
m.d.Song.Patch[m.d.InstrIndex].Units = newUnits
|
m.d.Song.Patch[m.d.InstrIndex].Units = newUnits
|
||||||
m.d.ParamIndex = 0
|
m.d.ParamIndex = 0
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Patch.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) AddOrderRow(after bool) {
|
func (m *Model) AddOrderRow(after bool) {
|
||||||
@ -779,7 +770,7 @@ func (m *Model) AddOrderRow(after bool) {
|
|||||||
m.d.SelectionCorner = m.d.Cursor
|
m.d.SelectionCorner = m.d.Cursor
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.computePatternUseCounts()
|
m.computePatternUseCounts()
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) DeleteOrderRow(forward bool) {
|
func (m *Model) DeleteOrderRow(forward bool) {
|
||||||
@ -802,7 +793,7 @@ func (m *Model) DeleteOrderRow(forward bool) {
|
|||||||
m.d.SelectionCorner = m.d.Cursor
|
m.d.SelectionCorner = m.d.Cursor
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.computePatternUseCounts()
|
m.computePatternUseCounts()
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) DeleteUnits(forward bool, a, b int) []sointu.Unit {
|
func (m *Model) DeleteUnits(forward bool, a, b int) []sointu.Unit {
|
||||||
@ -835,7 +826,7 @@ func (m *Model) DeleteUnits(forward bool, a, b int) []sointu.Unit {
|
|||||||
m.d.Song.Patch[m.d.InstrIndex].Units = newUnits
|
m.d.Song.Patch[m.d.InstrIndex].Units = newUnits
|
||||||
m.d.ParamIndex = 0
|
m.d.ParamIndex = 0
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Patch.Copy())
|
||||||
return deletedUnits
|
return deletedUnits
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -861,7 +852,7 @@ func (m *Model) ResetParam() {
|
|||||||
m.saveUndo("ResetParam", 0)
|
m.saveUndo("ResetParam", 0)
|
||||||
unit.Parameters[paramType.Name] = defaultValue
|
unit.Parameters[paramType.Name] = defaultValue
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Patch.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) SetParamIndex(value int) {
|
func (m *Model) SetParamIndex(value int) {
|
||||||
@ -886,7 +877,7 @@ func (m *Model) setGmDlsEntry(index int) {
|
|||||||
unit.Parameters["loopstart"] = entry.LoopStart
|
unit.Parameters["loopstart"] = entry.LoopStart
|
||||||
unit.Parameters["looplength"] = entry.LoopLength
|
unit.Parameters["looplength"] = entry.LoopLength
|
||||||
unit.Parameters["transpose"] = 64 + entry.SuggestedTranspose
|
unit.Parameters["transpose"] = 64 + entry.SuggestedTranspose
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Patch.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) setReverb(index int) {
|
func (m *Model) setReverb(index int) {
|
||||||
@ -903,7 +894,7 @@ func (m *Model) setReverb(index int) {
|
|||||||
unit.Parameters["notetracking"] = 0
|
unit.Parameters["notetracking"] = 0
|
||||||
unit.VarArgs = make([]int, len(entry.varArgs))
|
unit.VarArgs = make([]int, len(entry.varArgs))
|
||||||
copy(unit.VarArgs, entry.varArgs)
|
copy(unit.VarArgs, entry.varArgs)
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Patch.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) SwapUnits(i, j int) {
|
func (m *Model) SwapUnits(i, j int) {
|
||||||
@ -914,7 +905,7 @@ func (m *Model) SwapUnits(i, j int) {
|
|||||||
m.saveUndo("SwapUnits", 10)
|
m.saveUndo("SwapUnits", 10)
|
||||||
units[i], units[j] = units[j], units[i]
|
units[i], units[j] = units[j], units[i]
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Patch.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) getSelectionRange() (int, int, int, int) {
|
func (m *Model) getSelectionRange() (int, int, int, int) {
|
||||||
@ -974,7 +965,7 @@ func (m *Model) AdjustSelectionPitch(delta int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) DeleteSelection() {
|
func (m *Model) DeleteSelection() {
|
||||||
@ -1001,7 +992,7 @@ func (m *Model) DeleteSelection() {
|
|||||||
m.d.Song.Score.Tracks[c].Patterns[p][s.Row] = 1
|
m.d.Song.Score.Tracks[c].Patterns[p][s.Row] = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) DeletePatternSelection() {
|
func (m *Model) DeletePatternSelection() {
|
||||||
@ -1017,7 +1008,7 @@ func (m *Model) DeletePatternSelection() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.computePatternUseCounts()
|
m.computePatternUseCounts()
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) Undo() {
|
func (m *Model) Undo() {
|
||||||
@ -1341,7 +1332,7 @@ func (m *Model) RemoveUnusedData() {
|
|||||||
m.d.Song.Score.Tracks[trkIndex] = trk
|
m.d.Song.Score.Tracks[trkIndex] = trk
|
||||||
}
|
}
|
||||||
m.computePatternUseCounts()
|
m.computePatternUseCounts()
|
||||||
m.notifyScoreChange()
|
m.send(m.d.Song.Score.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) SetParam(value int) {
|
func (m *Model) SetParam(value int) {
|
||||||
@ -1388,7 +1379,7 @@ func (m *Model) SetParam(value int) {
|
|||||||
unit.Parameters[p.Name] = value
|
unit.Parameters[p.Name] = value
|
||||||
}
|
}
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.notifyPatchChange()
|
m.send(m.d.Song.Patch.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) setSongNoUndo(song sointu.Song) {
|
func (m *Model) setSongNoUndo(song sointu.Song) {
|
||||||
@ -1407,31 +1398,12 @@ func (m *Model) setSongNoUndo(song sointu.Song) {
|
|||||||
}
|
}
|
||||||
m.clampPositions()
|
m.clampPositions()
|
||||||
m.computePatternUseCounts()
|
m.computePatternUseCounts()
|
||||||
m.notifySamplesPerRowChange()
|
m.send(m.d.Song.Copy())
|
||||||
m.notifyPatchChange()
|
|
||||||
m.notifyScoreChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) notifyPatchChange() {
|
// send sends a message to the player
|
||||||
m.d.Panic = false
|
func (m *Model) send(message interface{}) {
|
||||||
select {
|
m.modelMessages <- message
|
||||||
case m.modelMessages <- ModelPatchChangedMessage{m.d.Song.Patch.Copy()}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) notifyScoreChange() {
|
|
||||||
select {
|
|
||||||
case m.modelMessages <- ModelScoreChangedMessage{m.d.Song.Score.Copy()}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) notifySamplesPerRowChange() {
|
|
||||||
select {
|
|
||||||
case m.modelMessages <- ModelSamplesPerRowChangedMessage{m.d.Song.BPM, m.d.Song.RowsPerBeat}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) saveUndo(undoType string, undoSkipping int) {
|
func (m *Model) saveUndo(undoType string, undoSkipping int) {
|
||||||
|
@ -18,14 +18,11 @@ type (
|
|||||||
voiceNoteID []int // the ID of the note that triggered the voice
|
voiceNoteID []int // the ID of the note that triggered the voice
|
||||||
voiceReleased []bool // is the voice released
|
voiceReleased []bool // is the voice released
|
||||||
synth sointu.Synth // the synth used to render audio
|
synth sointu.Synth // the synth used to render audio
|
||||||
patch sointu.Patch // the patch used to create the synth
|
song sointu.Song // the song being played
|
||||||
score sointu.Score // the score 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
|
||||||
position ScoreRow // the current position in the score
|
position ScoreRow // the current position in the score
|
||||||
samplesSinceEvent []int // how many samples have been played since the last event in each voice
|
samplesSinceEvent []int // how many samples have been played since the last event in each voice
|
||||||
samplesPerRow int // how many samples is one row equal to
|
|
||||||
bpm int // the current BPM
|
|
||||||
avgVolumeMeter VolumeAnalyzer // the volume analyzer used to calculate the average volume
|
avgVolumeMeter VolumeAnalyzer // the volume analyzer used to calculate the average volume
|
||||||
peakVolumeMeter VolumeAnalyzer // the volume analyzer used to calculate the peak volume
|
peakVolumeMeter VolumeAnalyzer // the volume analyzer used to calculate the peak volume
|
||||||
voiceStates [vm.MAX_VOICES]float32 // the current state of each voice
|
voiceStates [vm.MAX_VOICES]float32 // the current state of each voice
|
||||||
@ -55,18 +52,13 @@ type (
|
|||||||
Note byte
|
Note byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlayerPlayingMessage is sent to the model when the player starts or stops
|
|
||||||
// playing the score.
|
|
||||||
PlayerPlayingMessage struct {
|
|
||||||
bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// PlayerMessage is a message sent from the player to the model. The Inner
|
// PlayerMessage is a message sent from the player to the model. The Inner
|
||||||
// field can contain any message. AverageVolume, PeakVolume, SongRow and
|
// field can contain any message. Panic, AverageVolume, PeakVolume, SongRow
|
||||||
// VoiceStates transmitted frequently, with every message, so they are
|
// and VoiceStates transmitted frequently, with every message, so they are
|
||||||
// treated specially, to avoid boxing. All the rest messages can be boxed to
|
// treated specially, to avoid boxing. All the rest messages can be boxed to
|
||||||
// Inner interface{}
|
// Inner interface{}
|
||||||
PlayerMessage struct {
|
PlayerMessage struct {
|
||||||
|
Panic bool
|
||||||
AverageVolume Volume
|
AverageVolume Volume
|
||||||
PeakVolume Volume
|
PeakVolume Volume
|
||||||
SongRow ScoreRow
|
SongRow ScoreRow
|
||||||
@ -74,13 +66,17 @@ type (
|
|||||||
Inner interface{}
|
Inner interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PlayerPlayingMessage is sent to the model when the player starts or stops playing the score.
|
||||||
|
PlayerPlayingMessage struct {
|
||||||
|
bool
|
||||||
|
}
|
||||||
|
|
||||||
// PlayerCrashMessage is sent to the model when the player crashes.
|
// PlayerCrashMessage is sent to the model when the player crashes.
|
||||||
PlayerCrashMessage struct {
|
PlayerCrashMessage struct {
|
||||||
error
|
error
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlayerVolumeErrorMessage is sent to the model there is an error in the
|
// PlayerVolumeErrorMessage is sent to the model there is an error in the volume analyzer. The error is not fatal.
|
||||||
// volume analyzer. The error is not fatal.
|
|
||||||
PlayerVolumeErrorMessage struct {
|
PlayerVolumeErrorMessage struct {
|
||||||
error
|
error
|
||||||
}
|
}
|
||||||
@ -159,12 +155,12 @@ func (p *Player) Process(buffer sointu.AudioBuffer, context PlayerProcessContext
|
|||||||
if delta := midi.Frame - frame; midiOk && delta < framesUntilMidi {
|
if delta := midi.Frame - frame; midiOk && delta < framesUntilMidi {
|
||||||
framesUntilMidi = delta
|
framesUntilMidi = delta
|
||||||
}
|
}
|
||||||
if p.playing && p.rowtime >= p.samplesPerRow {
|
if p.playing && p.rowtime >= p.song.SamplesPerRow() {
|
||||||
p.advanceRow()
|
p.advanceRow()
|
||||||
}
|
}
|
||||||
timeUntilRowAdvance := math.MaxInt32
|
timeUntilRowAdvance := math.MaxInt32
|
||||||
if p.playing {
|
if p.playing {
|
||||||
timeUntilRowAdvance = p.samplesPerRow - p.rowtime
|
timeUntilRowAdvance = p.song.SamplesPerRow() - p.rowtime
|
||||||
}
|
}
|
||||||
var rendered, timeAdvanced int
|
var rendered, timeAdvanced int
|
||||||
var err error
|
var err error
|
||||||
@ -183,7 +179,7 @@ func (p *Player) Process(buffer sointu.AudioBuffer, context PlayerProcessContext
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.synth = nil
|
p.synth = nil
|
||||||
p.trySend(PlayerCrashMessage{fmt.Errorf("synth.Render: %w", err)})
|
p.send(PlayerCrashMessage{fmt.Errorf("synth.Render: %w", err)})
|
||||||
}
|
}
|
||||||
buffer = buffer[rendered:]
|
buffer = buffer[rendered:]
|
||||||
frame += rendered
|
frame += rendered
|
||||||
@ -205,29 +201,31 @@ func (p *Player) Process(buffer sointu.AudioBuffer, context PlayerProcessContext
|
|||||||
err2 := p.peakVolumeMeter.Update(oldBuffer)
|
err2 := p.peakVolumeMeter.Update(oldBuffer)
|
||||||
var msg interface{}
|
var msg interface{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg = PlayerVolumeErrorMessage{err}
|
msg = PlayerCrashMessage{err}
|
||||||
|
p.synth = nil
|
||||||
}
|
}
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
msg = PlayerVolumeErrorMessage{err}
|
msg = PlayerCrashMessage{err}
|
||||||
|
p.synth = nil
|
||||||
}
|
}
|
||||||
p.trySend(msg)
|
p.send(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// we were not able to fill the buffer with NUM_RENDER_TRIES attempts, destroy synth and throw an error
|
// we were not able to fill the buffer with NUM_RENDER_TRIES attempts, destroy synth and throw an error
|
||||||
p.synth = nil
|
p.synth = nil
|
||||||
p.trySend(PlayerCrashMessage{fmt.Errorf("synth did not fill the audio buffer even with %d render calls", NUM_RENDER_TRIES)})
|
p.send(PlayerCrashMessage{fmt.Errorf("synth did not fill the audio buffer even with %d render calls", NUM_RENDER_TRIES)})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) advanceRow() {
|
func (p *Player) advanceRow() {
|
||||||
if p.score.Length == 0 || p.score.RowsPerPattern == 0 {
|
if p.song.Score.Length == 0 || p.song.Score.RowsPerPattern == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.position.Row++ // advance row (this is why we subtracted one in Play())
|
p.position.Row++ // advance row (this is why we subtracted one in Play())
|
||||||
p.position = p.position.Wrap(p.score)
|
p.position = p.position.Wrap(p.song.Score)
|
||||||
p.trySend(nil) // just send volume and song row information
|
p.send(nil) // just send volume and song row information
|
||||||
lastVoice := 0
|
lastVoice := 0
|
||||||
for i, t := range p.score.Tracks {
|
for i, t := range p.song.Score.Tracks {
|
||||||
start := lastVoice
|
start := lastVoice
|
||||||
lastVoice = start + t.NumVoices
|
lastVoice = start + t.NumVoices
|
||||||
if p.position.Pattern < 0 || p.position.Pattern >= len(t.Order) {
|
if p.position.Pattern < 0 || p.position.Pattern >= len(t.Order) {
|
||||||
@ -265,44 +263,49 @@ loop:
|
|||||||
} else {
|
} else {
|
||||||
p.compileOrUpdateSynth()
|
p.compileOrUpdateSynth()
|
||||||
}
|
}
|
||||||
case ModelPatchChangedMessage:
|
case sointu.Song:
|
||||||
p.patch = m.Patch
|
p.song = m
|
||||||
p.compileOrUpdateSynth()
|
p.compileOrUpdateSynth()
|
||||||
case ModelScoreChangedMessage:
|
case sointu.Patch:
|
||||||
p.score = m.Score
|
p.song.Patch = m
|
||||||
|
p.compileOrUpdateSynth()
|
||||||
|
case sointu.Score:
|
||||||
|
p.song.Score = m
|
||||||
case ModelPlayingChangedMessage:
|
case ModelPlayingChangedMessage:
|
||||||
p.playing = m.bool
|
p.playing = bool(m.bool)
|
||||||
if !p.playing {
|
if !p.playing {
|
||||||
for i := range p.score.Tracks {
|
for i := range p.song.Score.Tracks {
|
||||||
p.releaseTrack(i)
|
p.releaseTrack(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ModelSamplesPerRowChangedMessage:
|
case ModelBPMChangedMessage:
|
||||||
p.samplesPerRow = 44100 * 60 / (m.BPM * m.RowsPerBeat)
|
p.song.BPM = m.int
|
||||||
p.bpm = m.BPM
|
p.compileOrUpdateSynth()
|
||||||
|
case ModelRowsPerBeatChangedMessage:
|
||||||
|
p.song.RowsPerBeat = m.int
|
||||||
p.compileOrUpdateSynth()
|
p.compileOrUpdateSynth()
|
||||||
case ModelPlayFromPositionMessage:
|
case ModelPlayFromPositionMessage:
|
||||||
p.playing = true
|
p.playing = true
|
||||||
p.position = m.ScoreRow
|
p.position = m.ScoreRow
|
||||||
p.position.Row--
|
p.position.Row--
|
||||||
p.rowtime = math.MaxInt
|
p.rowtime = math.MaxInt
|
||||||
for i, t := range p.score.Tracks {
|
for i, t := range p.song.Score.Tracks {
|
||||||
if !t.Effect {
|
if !t.Effect {
|
||||||
// when starting to play from another position, release only non-effect tracks
|
// when starting to play from another position, release only non-effect tracks
|
||||||
p.releaseTrack(i)
|
p.releaseTrack(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ModelNoteOnMessage:
|
case ModelNoteOnMessage:
|
||||||
if m.id.IsInstr {
|
if m.IsInstr {
|
||||||
p.triggerInstrument(m.id.Instr, m.id.Note)
|
p.triggerInstrument(m.Instr, m.Note)
|
||||||
} else {
|
} else {
|
||||||
p.triggerTrack(m.id.Track, m.id.Note)
|
p.triggerTrack(m.Track, m.Note)
|
||||||
}
|
}
|
||||||
case ModelNoteOffMessage:
|
case ModelNoteOffMessage:
|
||||||
if m.id.IsInstr {
|
if m.IsInstr {
|
||||||
p.releaseInstrument(m.id.Instr, m.id.Note)
|
p.releaseInstrument(m.Instr, m.Note)
|
||||||
} else {
|
} else {
|
||||||
p.releaseTrack(m.id.Track)
|
p.releaseTrack(m.Track)
|
||||||
}
|
}
|
||||||
case ModelRecordingMessage:
|
case ModelRecordingMessage:
|
||||||
if m.bool {
|
if m.bool {
|
||||||
@ -311,7 +314,7 @@ loop:
|
|||||||
} else {
|
} else {
|
||||||
if p.recState == recStateRecording && len(p.recording.Events) > 0 {
|
if p.recState == recStateRecording && len(p.recording.Events) > 0 {
|
||||||
p.recording.BPM, _ = context.BPM()
|
p.recording.BPM, _ = context.BPM()
|
||||||
p.trySend(p.recording)
|
p.send(p.recording)
|
||||||
}
|
}
|
||||||
p.recState = recStateNone
|
p.recState = recStateNone
|
||||||
}
|
}
|
||||||
@ -325,22 +328,22 @@ loop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) compileOrUpdateSynth() {
|
func (p *Player) compileOrUpdateSynth() {
|
||||||
if p.bpm <= 0 {
|
if p.song.BPM <= 0 {
|
||||||
return // bpm not set yet
|
return // bpm not set yet
|
||||||
}
|
}
|
||||||
if p.synth != nil {
|
if p.synth != nil {
|
||||||
err := p.synth.Update(p.patch, p.bpm)
|
err := p.synth.Update(p.song.Patch, p.song.BPM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.synth = nil
|
p.synth = nil
|
||||||
p.trySend(PlayerCrashMessage{fmt.Errorf("synth.Update: %w", err)})
|
p.send(PlayerCrashMessage{fmt.Errorf("synth.Update: %w", err)})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
p.synth, err = p.synther.Synth(p.patch, p.bpm)
|
p.synth, err = p.synther.Synth(p.song.Patch, p.song.BPM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.synth = nil
|
p.synth = nil
|
||||||
p.trySend(PlayerCrashMessage{fmt.Errorf("synther.Synth: %w", err)})
|
p.send(PlayerCrashMessage{fmt.Errorf("synther.Synth: %w", err)})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for i := 0; i < 32; i++ {
|
for i := 0; i < 32; i++ {
|
||||||
@ -350,9 +353,9 @@ func (p *Player) compileOrUpdateSynth() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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) trySend(message interface{}) {
|
func (p *Player) send(message interface{}) {
|
||||||
select {
|
select {
|
||||||
case p.playerMessages <- PlayerMessage{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, VoiceStates: p.voiceStates, Inner: message}:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -360,11 +363,11 @@ func (p *Player) trySend(message interface{}) {
|
|||||||
func (p *Player) triggerInstrument(instrument int, note byte) {
|
func (p *Player) triggerInstrument(instrument int, note byte) {
|
||||||
ID := idForInstrumentNote(instrument, note)
|
ID := idForInstrumentNote(instrument, note)
|
||||||
p.release(ID)
|
p.release(ID)
|
||||||
if p.patch == nil || instrument < 0 || instrument >= len(p.patch) {
|
if p.song.Patch == nil || instrument < 0 || instrument >= len(p.song.Patch) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
voiceStart := p.patch.FirstVoiceForInstrument(instrument)
|
voiceStart := p.song.Patch.FirstVoiceForInstrument(instrument)
|
||||||
voiceEnd := voiceStart + p.patch[instrument].NumVoices
|
voiceEnd := voiceStart + p.song.Patch[instrument].NumVoices
|
||||||
p.trigger(voiceStart, voiceEnd, note, ID)
|
p.trigger(voiceStart, voiceEnd, note, ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,8 +378,8 @@ func (p *Player) releaseInstrument(instrument int, note byte) {
|
|||||||
func (p *Player) triggerTrack(track int, note byte) {
|
func (p *Player) triggerTrack(track int, note byte) {
|
||||||
ID := idForTrack(track)
|
ID := idForTrack(track)
|
||||||
p.release(ID)
|
p.release(ID)
|
||||||
voiceStart := p.score.FirstVoiceForTrack(track)
|
voiceStart := p.song.Score.FirstVoiceForTrack(track)
|
||||||
voiceEnd := voiceStart + p.score.Tracks[track].NumVoices
|
voiceEnd := voiceStart + p.song.Score.Tracks[track].NumVoices
|
||||||
p.trigger(voiceStart, voiceEnd, note, ID)
|
p.trigger(voiceStart, voiceEnd, note, ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user