mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-03 09:08:18 -04:00
feat(sointu): add SynthService for recompiling the synth when needed
This commit is contained in:
parent
6307dd51de
commit
5e7bd75b36
@ -13,6 +13,14 @@ import (
|
|||||||
"github.com/vsariola/sointu/compiler"
|
"github.com/vsariola/sointu/compiler"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type BridgeService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s BridgeService) Compile(patch sointu.Patch) (sointu.Synth, error) {
|
||||||
|
synth, err := Synth(patch)
|
||||||
|
return synth, err
|
||||||
|
}
|
||||||
|
|
||||||
func Synth(patch sointu.Patch) (*C.Synth, error) {
|
func Synth(patch sointu.Patch) (*C.Synth, error) {
|
||||||
s := new(C.Synth)
|
s := new(C.Synth)
|
||||||
comPatch, err := compiler.Encode(&patch, compiler.AllFeatures{})
|
comPatch, err := compiler.Encode(&patch, compiler.AllFeatures{})
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"gioui.org/app"
|
"gioui.org/app"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
|
"github.com/vsariola/sointu/bridge"
|
||||||
"github.com/vsariola/sointu/oto"
|
"github.com/vsariola/sointu/oto"
|
||||||
"github.com/vsariola/sointu/tracker"
|
"github.com/vsariola/sointu/tracker"
|
||||||
)
|
)
|
||||||
@ -17,12 +18,13 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer audioContext.Close()
|
defer audioContext.Close()
|
||||||
|
synthService := bridge.BridgeService{}
|
||||||
go func() {
|
go func() {
|
||||||
w := app.NewWindow(
|
w := app.NewWindow(
|
||||||
app.Size(unit.Dp(800), unit.Dp(600)),
|
app.Size(unit.Dp(800), unit.Dp(600)),
|
||||||
app.Title("Sointu Tracker"),
|
app.Title("Sointu Tracker"),
|
||||||
)
|
)
|
||||||
t := tracker.New(audioContext)
|
t := tracker.New(audioContext, synthService)
|
||||||
defer t.Close()
|
defer t.Close()
|
||||||
if err := t.Run(w); err != nil {
|
if err := t.Run(w); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
@ -120,6 +120,10 @@ func Render(synth Synth, buffer []float32) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SynthService interface {
|
||||||
|
Compile(patch Patch) (Synth, error)
|
||||||
|
}
|
||||||
|
|
||||||
type AudioSink interface {
|
type AudioSink interface {
|
||||||
WriteAudio(buffer []float32) (err error)
|
WriteAudio(buffer []float32) (err error)
|
||||||
Close() error
|
Close() error
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package tracker
|
package tracker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
@ -24,6 +23,7 @@ type Sequencer struct {
|
|||||||
// that the synth is not changed when audio is being read
|
// that the synth is not changed when audio is being read
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
synth sointu.Synth
|
synth sointu.Synth
|
||||||
|
service sointu.SynthService
|
||||||
// this iterator is a bit unconventional in the sense that it might return
|
// this iterator is a bit unconventional in the sense that it might return
|
||||||
// hasNext false, but might still return hasNext true in future attempts if
|
// hasNext false, but might still return hasNext true in future attempts if
|
||||||
// new rows become available.
|
// new rows become available.
|
||||||
@ -37,11 +37,11 @@ type Note struct {
|
|||||||
Note byte
|
Note byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSequencer(synth sointu.Synth, rowLength int, iterator func() ([]Note, bool)) *Sequencer {
|
func NewSequencer(service sointu.SynthService, iterator func() ([]Note, bool)) *Sequencer {
|
||||||
return &Sequencer{
|
return &Sequencer{
|
||||||
synth: synth,
|
service: service,
|
||||||
iterator: iterator,
|
iterator: iterator,
|
||||||
rowLength: rowLength,
|
rowLength: math.MaxInt32,
|
||||||
rowTime: math.MaxInt32,
|
rowTime: math.MaxInt32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,9 +49,6 @@ func NewSequencer(synth sointu.Synth, rowLength int, iterator func() ([]Note, bo
|
|||||||
func (s *Sequencer) ReadAudio(buffer []float32) (int, error) {
|
func (s *Sequencer) ReadAudio(buffer []float32) (int, error) {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
if s.synth == nil {
|
|
||||||
return 0, errors.New("cannot Sequencer.ReadAudio; synth is nil")
|
|
||||||
}
|
|
||||||
totalRendered := 0
|
totalRendered := 0
|
||||||
for i := 0; i < SEQUENCER_MAX_READ_TRIES; i++ {
|
for i := 0; i < SEQUENCER_MAX_READ_TRIES; i++ {
|
||||||
gotRow := true
|
gotRow := true
|
||||||
@ -75,11 +72,21 @@ func (s *Sequencer) ReadAudio(buffer []float32) (int, error) {
|
|||||||
if !gotRow {
|
if !gotRow {
|
||||||
rowTimeRemaining = math.MaxInt32
|
rowTimeRemaining = math.MaxInt32
|
||||||
}
|
}
|
||||||
|
if s.synth != nil {
|
||||||
rendered, timeAdvanced, err := s.synth.Render(buffer[totalRendered*2:], rowTimeRemaining)
|
rendered, timeAdvanced, err := s.synth.Render(buffer[totalRendered*2:], rowTimeRemaining)
|
||||||
|
if err != nil {
|
||||||
|
s.synth = nil
|
||||||
|
}
|
||||||
totalRendered += rendered
|
totalRendered += rendered
|
||||||
s.rowTime += timeAdvanced
|
s.rowTime += timeAdvanced
|
||||||
if err != nil {
|
} else {
|
||||||
return totalRendered * 2, fmt.Errorf("synth.Render failed: %v", err)
|
for totalRendered*2 < len(buffer) && rowTimeRemaining > 0 {
|
||||||
|
buffer[totalRendered*2] = 0
|
||||||
|
buffer[totalRendered*2+1] = 0
|
||||||
|
totalRendered++
|
||||||
|
s.rowTime++
|
||||||
|
rowTimeRemaining--
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if totalRendered*2 >= len(buffer) {
|
if totalRendered*2 >= len(buffer) {
|
||||||
return totalRendered * 2, nil
|
return totalRendered * 2, nil
|
||||||
@ -93,7 +100,9 @@ func (s *Sequencer) SetPatch(patch sointu.Patch) {
|
|||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
if s.synth != nil {
|
if s.synth != nil {
|
||||||
s.synth.Update(patch)
|
s.synth.Update(patch)
|
||||||
} // TODO: what is s.synth is nil?
|
} else {
|
||||||
|
s.synth, _ = s.service.Compile(patch)
|
||||||
|
}
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,9 +128,11 @@ func (s *Sequencer) Release(voice int) {
|
|||||||
|
|
||||||
// doNote is the internal trigger/release function that is not thread safe
|
// doNote is the internal trigger/release function that is not thread safe
|
||||||
func (s *Sequencer) doNote(voice int, note byte) {
|
func (s *Sequencer) doNote(voice int, note byte) {
|
||||||
|
if s.synth != nil {
|
||||||
if note == 0 {
|
if note == 0 {
|
||||||
s.synth.Release(voice)
|
s.synth.Release(voice)
|
||||||
} else {
|
} else {
|
||||||
s.synth.Trigger(voice, note)
|
s.synth.Trigger(voice, note)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"gioui.org/widget"
|
"gioui.org/widget"
|
||||||
"gioui.org/widget/material"
|
"gioui.org/widget/material"
|
||||||
"github.com/vsariola/sointu"
|
"github.com/vsariola/sointu"
|
||||||
"github.com/vsariola/sointu/bridge"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Tracker struct {
|
type Tracker struct {
|
||||||
@ -70,7 +69,6 @@ type Tracker struct {
|
|||||||
rowJump chan int
|
rowJump chan int
|
||||||
patternJump chan int
|
patternJump chan int
|
||||||
audioContext sointu.AudioContext
|
audioContext sointu.AudioContext
|
||||||
synth sointu.Synth
|
|
||||||
playBuffer []float32
|
playBuffer []float32
|
||||||
closer chan struct{}
|
closer chan struct{}
|
||||||
undoStack []sointu.Song
|
undoStack []sointu.Song
|
||||||
@ -111,51 +109,17 @@ func (t *Tracker) TogglePlay() {
|
|||||||
func (t *Tracker) sequencerLoop(closer <-chan struct{}) {
|
func (t *Tracker) sequencerLoop(closer <-chan struct{}) {
|
||||||
output := t.audioContext.Output()
|
output := t.audioContext.Output()
|
||||||
defer output.Close()
|
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)
|
|
||||||
t.sequencer = NewSequencer(synth, t.song.SamplesPerRow(), func() ([]Note, bool) {
|
|
||||||
t.playRowPatMutex.Lock()
|
|
||||||
if !t.Playing {
|
|
||||||
t.playRowPatMutex.Unlock()
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
t.PlayPosition.Row++
|
|
||||||
t.PlayPosition.Wrap(t.song)
|
|
||||||
if t.NoteTracking {
|
|
||||||
t.Cursor.SongRow = t.PlayPosition
|
|
||||||
t.SelectionCorner.SongRow = t.PlayPosition
|
|
||||||
}
|
|
||||||
notes := make([]Note, 0, 32)
|
|
||||||
for track := range t.song.Tracks {
|
|
||||||
patternIndex := t.song.Tracks[track].Sequence[t.PlayPosition.Pattern]
|
|
||||||
note := t.song.Tracks[track].Patterns[patternIndex][t.PlayPosition.Row]
|
|
||||||
if note == 1 { // anything but hold causes an action.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
first := t.song.FirstTrackVoice(track)
|
|
||||||
notes = append(notes, Note{first + curVoices[track], 0})
|
|
||||||
if note > 1 {
|
|
||||||
curVoices[track]++
|
|
||||||
if curVoices[track] >= t.song.Tracks[track].NumVoices {
|
|
||||||
curVoices[track] = 0
|
|
||||||
}
|
|
||||||
notes = append(notes, Note{first + curVoices[track], note})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.playRowPatMutex.Unlock()
|
|
||||||
t.ticked <- struct{}{}
|
|
||||||
return notes, true
|
|
||||||
})
|
|
||||||
buffer := make([]float32, 8192)
|
buffer := make([]float32, 8192)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-closer:
|
case <-closer:
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
t.sequencer.ReadAudio(buffer)
|
read, _ := t.sequencer.ReadAudio(buffer)
|
||||||
|
for read < len(buffer) {
|
||||||
|
buffer[read] = 0
|
||||||
|
read++
|
||||||
|
}
|
||||||
output.WriteAudio(buffer)
|
output.WriteAudio(buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -448,7 +412,7 @@ func (t *Tracker) DeleteSelection() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(audioContext sointu.AudioContext) *Tracker {
|
func New(audioContext sointu.AudioContext, synthService sointu.SynthService) *Tracker {
|
||||||
t := &Tracker{
|
t := &Tracker{
|
||||||
Theme: material.NewTheme(gofont.Collection()),
|
Theme: material.NewTheme(gofont.Collection()),
|
||||||
QuitButton: new(widget.Clickable),
|
QuitButton: new(widget.Clickable),
|
||||||
@ -495,6 +459,40 @@ func New(audioContext sointu.AudioContext) *Tracker {
|
|||||||
for range allUnits {
|
for range allUnits {
|
||||||
t.ChooseUnitTypeBtns = append(t.ChooseUnitTypeBtns, new(widget.Clickable))
|
t.ChooseUnitTypeBtns = append(t.ChooseUnitTypeBtns, new(widget.Clickable))
|
||||||
}
|
}
|
||||||
|
curVoices := make([]int, 32)
|
||||||
|
t.sequencer = NewSequencer(synthService, func() ([]Note, bool) {
|
||||||
|
t.playRowPatMutex.Lock()
|
||||||
|
if !t.Playing {
|
||||||
|
t.playRowPatMutex.Unlock()
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
t.PlayPosition.Row++
|
||||||
|
t.PlayPosition.Wrap(t.song)
|
||||||
|
if t.NoteTracking {
|
||||||
|
t.Cursor.SongRow = t.PlayPosition
|
||||||
|
t.SelectionCorner.SongRow = t.PlayPosition
|
||||||
|
}
|
||||||
|
notes := make([]Note, 0, 32)
|
||||||
|
for track := range t.song.Tracks {
|
||||||
|
patternIndex := t.song.Tracks[track].Sequence[t.PlayPosition.Pattern]
|
||||||
|
note := t.song.Tracks[track].Patterns[patternIndex][t.PlayPosition.Row]
|
||||||
|
if note == 1 { // anything but hold causes an action.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
first := t.song.FirstTrackVoice(track)
|
||||||
|
notes = append(notes, Note{first + curVoices[track], 0})
|
||||||
|
if note > 1 {
|
||||||
|
curVoices[track]++
|
||||||
|
if curVoices[track] >= t.song.Tracks[track].NumVoices {
|
||||||
|
curVoices[track] = 0
|
||||||
|
}
|
||||||
|
notes = append(notes, Note{first + curVoices[track], note})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.playRowPatMutex.Unlock()
|
||||||
|
t.ticked <- struct{}{}
|
||||||
|
return notes, true
|
||||||
|
})
|
||||||
go t.sequencerLoop(t.closer)
|
go t.sequencerLoop(t.closer)
|
||||||
if err := t.LoadSong(defaultSong.Copy()); err != nil {
|
if err := t.LoadSong(defaultSong.Copy()); err != nil {
|
||||||
panic(fmt.Errorf("cannot load default song: %w", err))
|
panic(fmt.Errorf("cannot load default song: %w", err))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user