mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-18 21:14:31 -04:00
refactor(tracker): Rewrote the sequencer loop to use simple mutex
This commit is contained in:
@ -2,37 +2,44 @@ package tracker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"gioui.org/widget"
|
||||
"github.com/vsariola/sointu"
|
||||
"github.com/vsariola/sointu/audio"
|
||||
"github.com/vsariola/sointu/bridge"
|
||||
)
|
||||
|
||||
type Tracker struct {
|
||||
QuitButton *widget.Clickable
|
||||
song sointu.Song
|
||||
CursorRow int
|
||||
CursorColumn int
|
||||
DisplayPattern int
|
||||
PlayPattern int32
|
||||
PlayRow int32
|
||||
ActiveTrack int
|
||||
CurrentOctave byte
|
||||
Playing bool
|
||||
ticked chan struct{}
|
||||
setPlaying chan bool
|
||||
rowJump chan int
|
||||
patternJump chan int
|
||||
player audio.Player
|
||||
synth sointu.Synth
|
||||
playBuffer []float32
|
||||
closer chan struct{}
|
||||
QuitButton *widget.Clickable
|
||||
songPlayMutex sync.RWMutex // protects song and playing
|
||||
song sointu.Song
|
||||
Playing bool
|
||||
// protects PlayPattern and PlayRow
|
||||
playRowPatMutex sync.RWMutex // protects song and playing
|
||||
PlayPattern int
|
||||
PlayRow int
|
||||
CursorRow int
|
||||
CursorColumn int
|
||||
DisplayPattern int
|
||||
ActiveTrack int
|
||||
CurrentOctave byte
|
||||
|
||||
ticked chan struct{}
|
||||
setPlaying chan bool
|
||||
rowJump chan int
|
||||
patternJump chan int
|
||||
audioContext sointu.AudioContext
|
||||
synth sointu.Synth
|
||||
playBuffer []float32
|
||||
closer chan struct{}
|
||||
}
|
||||
|
||||
func (t *Tracker) LoadSong(song sointu.Song) error {
|
||||
if err := song.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid song: %w", err)
|
||||
}
|
||||
t.songPlayMutex.Lock()
|
||||
defer t.songPlayMutex.Unlock()
|
||||
t.song = song
|
||||
if synth, err := bridge.Synth(song.Patch); err != nil {
|
||||
fmt.Printf("error loading synth: %v\n", err)
|
||||
@ -44,15 +51,77 @@ func (t *Tracker) LoadSong(song sointu.Song) error {
|
||||
}
|
||||
|
||||
func (t *Tracker) Close() {
|
||||
t.player.Close()
|
||||
t.audioContext.Close()
|
||||
t.closer <- struct{}{}
|
||||
}
|
||||
|
||||
func New(player audio.Player) *Tracker {
|
||||
func (t *Tracker) TogglePlay() {
|
||||
t.songPlayMutex.Lock()
|
||||
defer t.songPlayMutex.Unlock()
|
||||
t.Playing = !t.Playing
|
||||
}
|
||||
|
||||
func (t *Tracker) sequencerLoop(closer <-chan struct{}) {
|
||||
output := t.audioContext.Output()
|
||||
defer output.Close()
|
||||
synth, err := bridge.Synth(t.song.Patch)
|
||||
if err != nil {
|
||||
panic("cannot create a synth with the default patch")
|
||||
}
|
||||
curVoices := make([]int, 32)
|
||||
sequencer := NewSequencer(synth, 44100*60/(4*t.song.BPM), func() ([]Note, bool) {
|
||||
t.songPlayMutex.RLock()
|
||||
defer t.songPlayMutex.RUnlock()
|
||||
if !t.Playing {
|
||||
return nil, false
|
||||
}
|
||||
t.playRowPatMutex.Lock()
|
||||
defer t.playRowPatMutex.Unlock()
|
||||
t.PlayRow++
|
||||
if t.PlayRow >= t.song.PatternRows() {
|
||||
t.PlayRow = 0
|
||||
t.PlayPattern++
|
||||
}
|
||||
if t.PlayPattern >= t.song.SequenceLength() {
|
||||
t.PlayPattern = 0
|
||||
}
|
||||
notes := make([]Note, 0, 32)
|
||||
for track := range t.song.Tracks {
|
||||
patternIndex := t.song.Tracks[track].Sequence[t.PlayPattern]
|
||||
note := t.song.Patterns[patternIndex][t.PlayRow]
|
||||
if note == 1 { // anything but hold causes an action.
|
||||
continue
|
||||
}
|
||||
notes = append(notes, Note{curVoices[track], 0})
|
||||
if note > 1 {
|
||||
curVoices[track]++
|
||||
first := t.song.FirstTrackVoice(track)
|
||||
if curVoices[track] >= first+t.song.Tracks[track].NumVoices {
|
||||
curVoices[track] = first
|
||||
}
|
||||
notes = append(notes, Note{curVoices[track], note})
|
||||
}
|
||||
}
|
||||
t.ticked <- struct{}{}
|
||||
return notes, true
|
||||
})
|
||||
buffer := make([]float32, 8192)
|
||||
for {
|
||||
select {
|
||||
case <-closer:
|
||||
return
|
||||
default:
|
||||
sequencer.ReadAudio(buffer)
|
||||
output.WriteAudio(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func New(audioContext sointu.AudioContext) *Tracker {
|
||||
t := &Tracker{
|
||||
QuitButton: new(widget.Clickable),
|
||||
CurrentOctave: 4,
|
||||
player: player,
|
||||
audioContext: audioContext,
|
||||
setPlaying: make(chan bool),
|
||||
rowJump: make(chan int),
|
||||
patternJump: make(chan int),
|
||||
|
Reference in New Issue
Block a user