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