mirror of
https://github.com/vsariola/sointu.git
synced 2026-05-28 09:59:09 -04:00
draft multicore processing
This commit is contained in:
parent
c583156d1b
commit
7f03664870
5
audio.go
5
audio.go
@@ -62,6 +62,9 @@ type (
|
|||||||
// Release releases the currently playing note for a given voice. Called
|
// Release releases the currently playing note for a given voice. Called
|
||||||
// between synth.Renders.
|
// between synth.Renders.
|
||||||
Release(voice int)
|
Release(voice int)
|
||||||
|
|
||||||
|
// Close disposes the synth, freeing any resources. No other functions should be called after Close.
|
||||||
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synther compiles a given Patch into a Synth, throwing errors if the
|
// Synther compiles a given Patch into a Synth, throwing errors if the
|
||||||
@@ -69,6 +72,7 @@ type (
|
|||||||
Synther interface {
|
Synther interface {
|
||||||
Name() string // Name of the synther, e.g. "Go" or "Native"
|
Name() string // Name of the synther, e.g. "Go" or "Native"
|
||||||
Synth(patch Patch, bpm int) (Synth, error)
|
Synth(patch Patch, bpm int) (Synth, error)
|
||||||
|
SupportsParallelism() bool
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -83,6 +87,7 @@ func Play(synther Synther, song Song, progress func(float32)) (AudioBuffer, erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("sointu.Play failed: %v", err)
|
return nil, fmt.Errorf("sointu.Play failed: %v", err)
|
||||||
}
|
}
|
||||||
|
defer synth.Close()
|
||||||
curVoices := make([]int, len(song.Score.Tracks))
|
curVoices := make([]int, len(song.Score.Tracks))
|
||||||
for i := range curVoices {
|
for i := range curVoices {
|
||||||
curVoices[i] = song.Score.FirstVoiceForTrack(i)
|
curVoices[i] = song.Score.FirstVoiceForTrack(i)
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ import (
|
|||||||
|
|
||||||
var Synthers = []sointu.Synther{
|
var Synthers = []sointu.Synther{
|
||||||
vm.GoSynther{},
|
vm.GoSynther{},
|
||||||
|
vm.MakeParallelSynther(vm.GoSynther{}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,12 @@
|
|||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import "github.com/vsariola/sointu/vm/compiler/bridge"
|
import (
|
||||||
|
"github.com/vsariola/sointu/vm"
|
||||||
|
"github.com/vsariola/sointu/vm/compiler/bridge"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Synthers = append(Synthers, bridge.NativeSynther{})
|
Synthers = append(Synthers, bridge.NativeSynther{})
|
||||||
|
Synthers = append(Synthers, vm.MakeParallelSynther(bridge.NativeSynther{}))
|
||||||
}
|
}
|
||||||
|
|||||||
29
patch.go
29
patch.go
@@ -16,11 +16,12 @@ type (
|
|||||||
|
|
||||||
// Instrument includes a list of units consisting of the instrument, and the number of polyphonic voices for this instrument
|
// Instrument includes a list of units consisting of the instrument, and the number of polyphonic voices for this instrument
|
||||||
Instrument struct {
|
Instrument struct {
|
||||||
Name string `yaml:",omitempty"`
|
Name string `yaml:",omitempty"`
|
||||||
Comment string `yaml:",omitempty"`
|
Comment string `yaml:",omitempty"`
|
||||||
NumVoices int
|
NumVoices int
|
||||||
Units []Unit
|
Units []Unit
|
||||||
Mute bool `yaml:",omitempty"` // Mute is only used in the tracker for soloing/muting instruments; the compiled player ignores this field
|
Mute bool `yaml:",omitempty"` // Mute is only used in the tracker for soloing/muting instruments; the compiled player ignores this field
|
||||||
|
CoreBitMask uint `yaml:",omitempty"` // CoreBitMask tells which cores this instrument can run on; 0 means all cores
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unit is e.g. a filter, oscillator, envelope and its parameters
|
// Unit is e.g. a filter, oscillator, envelope and its parameters
|
||||||
@@ -347,13 +348,14 @@ func init() {
|
|||||||
|
|
||||||
// Copy makes a deep copy of a unit.
|
// Copy makes a deep copy of a unit.
|
||||||
func (u *Unit) Copy() Unit {
|
func (u *Unit) Copy() Unit {
|
||||||
parameters := make(map[string]int)
|
ret := *u
|
||||||
|
ret.Parameters = make(map[string]int, len(u.Parameters))
|
||||||
for k, v := range u.Parameters {
|
for k, v := range u.Parameters {
|
||||||
parameters[k] = v
|
ret.Parameters[k] = v
|
||||||
}
|
}
|
||||||
varArgs := make([]int, len(u.VarArgs))
|
ret.VarArgs = make([]int, len(u.VarArgs))
|
||||||
copy(varArgs, u.VarArgs)
|
copy(ret.VarArgs, u.VarArgs)
|
||||||
return Unit{Type: u.Type, Parameters: parameters, VarArgs: varArgs, ID: u.ID, Disabled: u.Disabled, Comment: u.Comment}
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
var stackUseSource = [2]StackUse{
|
var stackUseSource = [2]StackUse{
|
||||||
@@ -473,11 +475,12 @@ func (u *Unit) StackNeed() int {
|
|||||||
|
|
||||||
// Copy makes a deep copy of an Instrument
|
// Copy makes a deep copy of an Instrument
|
||||||
func (instr *Instrument) Copy() Instrument {
|
func (instr *Instrument) Copy() Instrument {
|
||||||
units := make([]Unit, len(instr.Units))
|
ret := *instr
|
||||||
|
ret.Units = make([]Unit, len(instr.Units))
|
||||||
for i, u := range instr.Units {
|
for i, u := range instr.Units {
|
||||||
units[i] = u.Copy()
|
ret.Units[i] = u.Copy()
|
||||||
}
|
}
|
||||||
return Instrument{Name: instr.Name, Comment: instr.Comment, NumVoices: instr.NumVoices, Units: units, Mute: instr.Mute}
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement the counter interface
|
// Implement the counter interface
|
||||||
|
|||||||
5
song.go
5
song.go
@@ -293,7 +293,10 @@ func (l Score) LengthInRows() int {
|
|||||||
|
|
||||||
// Copy makes a deep copy of a Score.
|
// Copy makes a deep copy of a Score.
|
||||||
func (s *Song) Copy() Song {
|
func (s *Song) Copy() Song {
|
||||||
return Song{BPM: s.BPM, RowsPerBeat: s.RowsPerBeat, Score: s.Score.Copy(), Patch: s.Patch.Copy()}
|
ret := *s
|
||||||
|
ret.Score = s.Score.Copy()
|
||||||
|
ret.Patch = s.Patch.Copy()
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assuming 44100 Hz playback speed, return the number of samples of each row of
|
// Assuming 44100 Hz playback speed, return the number of samples of each row of
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ type (
|
|||||||
InstrEditor Model
|
InstrEditor Model
|
||||||
InstrPresets Model
|
InstrPresets Model
|
||||||
InstrComment Model
|
InstrComment Model
|
||||||
|
Core1 Model
|
||||||
|
Core2 Model
|
||||||
|
Core3 Model
|
||||||
|
Core4 Model
|
||||||
)
|
)
|
||||||
|
|
||||||
func MakeBool(valueEnabler interface {
|
func MakeBool(valueEnabler interface {
|
||||||
@@ -66,6 +70,43 @@ func (v Bool) Enabled() bool {
|
|||||||
return v.enabler.Enabled()
|
return v.enabler.Enabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Core methods
|
||||||
|
|
||||||
|
func (m *Model) getCoresBit(bit int) bool {
|
||||||
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return m.d.Song.Patch[m.d.InstrIndex].CoreBitMask&(1<<bit) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) setCoresBit(bit int, value bool) {
|
||||||
|
if m.d.InstrIndex < 0 || m.d.InstrIndex >= len(m.d.Song.Patch) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer (*Model)(m).change("CoreBitMask", PatchChange, MinorChange)()
|
||||||
|
if value {
|
||||||
|
m.d.Song.Patch[m.d.InstrIndex].CoreBitMask |= (1 << bit)
|
||||||
|
} else {
|
||||||
|
m.d.Song.Patch[m.d.InstrIndex].CoreBitMask &^= (1 << bit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) Core1() Bool { return MakeEnabledBool((*Core1)(m)) }
|
||||||
|
func (m *Core1) Value() bool { return (*Model)(m).getCoresBit(0) }
|
||||||
|
func (m *Core1) SetValue(val bool) { (*Model)(m).setCoresBit(0, val) }
|
||||||
|
|
||||||
|
func (m *Model) Core2() Bool { return MakeEnabledBool((*Core2)(m)) }
|
||||||
|
func (m *Core2) Value() bool { return (*Model)(m).getCoresBit(1) }
|
||||||
|
func (m *Core2) SetValue(val bool) { (*Model)(m).setCoresBit(1, val) }
|
||||||
|
|
||||||
|
func (m *Model) Core3() Bool { return MakeEnabledBool((*Core3)(m)) }
|
||||||
|
func (m *Core3) Value() bool { return (*Model)(m).getCoresBit(2) }
|
||||||
|
func (m *Core3) SetValue(val bool) { (*Model)(m).setCoresBit(2, val) }
|
||||||
|
|
||||||
|
func (m *Model) Core4() Bool { return MakeEnabledBool((*Core4)(m)) }
|
||||||
|
func (m *Core4) Value() bool { return (*Model)(m).getCoresBit(3) }
|
||||||
|
func (m *Core4) SetValue(val bool) { (*Model)(m).setCoresBit(3, val) }
|
||||||
|
|
||||||
// Panic methods
|
// Panic methods
|
||||||
|
|
||||||
func (m *Model) Panic() Bool { return MakeEnabledBool((*Panic)(m)) }
|
func (m *Model) Panic() Bool { return MakeEnabledBool((*Panic)(m)) }
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type (
|
|||||||
list *layout.List
|
list *layout.List
|
||||||
soloBtn *Clickable
|
soloBtn *Clickable
|
||||||
muteBtn *Clickable
|
muteBtn *Clickable
|
||||||
|
coreBtns [4]*Clickable
|
||||||
soloHint string
|
soloHint string
|
||||||
unsoloHint string
|
unsoloHint string
|
||||||
muteHint string
|
muteHint string
|
||||||
@@ -38,6 +39,7 @@ func NewInstrumentProperties() *InstrumentProperties {
|
|||||||
muteBtn: new(Clickable),
|
muteBtn: new(Clickable),
|
||||||
voices: NewNumericUpDownState(),
|
voices: NewNumericUpDownState(),
|
||||||
splitInstrumentBtn: new(Clickable),
|
splitInstrumentBtn: new(Clickable),
|
||||||
|
coreBtns: [4]*Clickable{new(Clickable), new(Clickable), new(Clickable), new(Clickable)},
|
||||||
}
|
}
|
||||||
ret.soloHint = makeHint("Solo", " (%s)", "SoloToggle")
|
ret.soloHint = makeHint("Solo", " (%s)", "SoloToggle")
|
||||||
ret.unsoloHint = makeHint("Unsolo", " (%s)", "SoloToggle")
|
ret.unsoloHint = makeHint("Unsolo", " (%s)", "SoloToggle")
|
||||||
@@ -66,7 +68,21 @@ func (ip *InstrumentProperties) layout(gtx C) D {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ip.list.Layout(gtx, 9, func(gtx C, index int) D {
|
core1btn := ToggleIconBtn(tr.Core1(), tr.Theme, ip.coreBtns[0], icons.ImageCropSquare, icons.ImageFilter1, "Do not render instrument on core 1", "Render instrument on core 1")
|
||||||
|
core2btn := ToggleIconBtn(tr.Core2(), tr.Theme, ip.coreBtns[1], icons.ImageCropSquare, icons.ImageFilter2, "Do not render instrument on core 2", "Render instrument on core 2")
|
||||||
|
core3btn := ToggleIconBtn(tr.Core3(), tr.Theme, ip.coreBtns[2], icons.ImageCropSquare, icons.ImageFilter3, "Do not render instrument on core 3", "Render instrument on core 3")
|
||||||
|
core4btn := ToggleIconBtn(tr.Core4(), tr.Theme, ip.coreBtns[3], icons.ImageCropSquare, icons.ImageFilter4, "Do not render instrument on core 4", "Render instrument on core 4")
|
||||||
|
|
||||||
|
corebtnline := func(gtx C) D {
|
||||||
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||||
|
layout.Rigid(core1btn.Layout),
|
||||||
|
layout.Rigid(core2btn.Layout),
|
||||||
|
layout.Rigid(core3btn.Layout),
|
||||||
|
layout.Rigid(core4btn.Layout),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip.list.Layout(gtx, 11, func(gtx C, index int) D {
|
||||||
switch index {
|
switch index {
|
||||||
case 0:
|
case 0:
|
||||||
return layoutInstrumentPropertyLine(gtx, "Name", func(gtx C) D {
|
return layoutInstrumentPropertyLine(gtx, "Name", func(gtx C) D {
|
||||||
@@ -81,6 +97,8 @@ func (ip *InstrumentProperties) layout(gtx C) D {
|
|||||||
soloBtn := ToggleIconBtn(tr.Solo(), tr.Theme, ip.soloBtn, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, ip.soloHint, ip.unsoloHint)
|
soloBtn := ToggleIconBtn(tr.Solo(), tr.Theme, ip.soloBtn, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, ip.soloHint, ip.unsoloHint)
|
||||||
return layoutInstrumentPropertyLine(gtx, "Solo", soloBtn.Layout)
|
return layoutInstrumentPropertyLine(gtx, "Solo", soloBtn.Layout)
|
||||||
case 8:
|
case 8:
|
||||||
|
return layoutInstrumentPropertyLine(gtx, "Cores", corebtnline)
|
||||||
|
case 10:
|
||||||
return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx C) D {
|
return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx C) D {
|
||||||
return ip.commentEditor.Layout(gtx, tr.InstrumentComment(), tr.Theme, &tr.Theme.InstrumentEditor.InstrumentComment, "Comment")
|
return ip.commentEditor.Layout(gtx, tr.InstrumentComment(), tr.Theme, &tr.Theme.InstrumentEditor.InstrumentComment, "Comment")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -127,12 +127,12 @@ func (p *Player) Process(buffer sointu.AudioBuffer, context PlayerProcessContext
|
|||||||
if p.synth != nil {
|
if p.synth != nil {
|
||||||
rendered, timeAdvanced, err = p.synth.Render(buffer[:framesUntilEvent], timeUntilRowAdvance)
|
rendered, timeAdvanced, err = p.synth.Render(buffer[:framesUntilEvent], timeUntilRowAdvance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.synth = nil
|
p.destroySynth()
|
||||||
p.send(Alert{Message: fmt.Sprintf("synth.Render: %s", err.Error()), Priority: Error, Name: "PlayerCrash", Duration: defaultAlertDuration})
|
p.send(Alert{Message: fmt.Sprintf("synth.Render: %s", err.Error()), Priority: Error, Name: "PlayerCrash", Duration: defaultAlertDuration})
|
||||||
}
|
}
|
||||||
// for performance, we don't check for NaN of every sample, because typically NaNs propagate
|
// for performance, we don't check for NaN of every sample, because typically NaNs propagate
|
||||||
if rendered > 0 && (isNaN(buffer[0][0]) || isNaN(buffer[0][1]) || isInf(buffer[0][0]) || isInf(buffer[0][1])) {
|
if rendered > 0 && (isNaN(buffer[0][0]) || isNaN(buffer[0][1]) || isInf(buffer[0][0]) || isInf(buffer[0][1])) {
|
||||||
p.synth = nil
|
p.destroySynth()
|
||||||
p.send(Alert{Message: "Inf or NaN detected in synth output", Priority: Error, Name: "PlayerCrash", Duration: defaultAlertDuration})
|
p.send(Alert{Message: "Inf or NaN detected in synth output", Priority: Error, Name: "PlayerCrash", Duration: defaultAlertDuration})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -170,11 +170,18 @@ func (p *Player) Process(buffer sointu.AudioBuffer, context PlayerProcessContext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 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.destroySynth()
|
||||||
p.events = p.events[:0] // clear events, so we don't try to process them again
|
p.events = p.events[:0] // clear events, so we don't try to process them again
|
||||||
p.SendAlert("PlayerCrash", fmt.Sprintf("synth did not fill the audio buffer even with %d render calls", numRenderTries), Error)
|
p.SendAlert("PlayerCrash", fmt.Sprintf("synth did not fill the audio buffer even with %d render calls", numRenderTries), Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Player) destroySynth() {
|
||||||
|
if p.synth != nil {
|
||||||
|
p.synth.Close()
|
||||||
|
p.synth = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Player) advanceRow() {
|
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
|
||||||
@@ -227,7 +234,7 @@ loop:
|
|||||||
switch m := msg.(type) {
|
switch m := msg.(type) {
|
||||||
case PanicMsg:
|
case PanicMsg:
|
||||||
if m.bool {
|
if m.bool {
|
||||||
p.synth = nil
|
p.destroySynth()
|
||||||
} else {
|
} else {
|
||||||
p.compileOrUpdateSynth()
|
p.compileOrUpdateSynth()
|
||||||
}
|
}
|
||||||
@@ -283,7 +290,7 @@ loop:
|
|||||||
}
|
}
|
||||||
case sointu.Synther:
|
case sointu.Synther:
|
||||||
p.synther = m
|
p.synther = m
|
||||||
p.synth = nil
|
p.destroySynth()
|
||||||
p.compileOrUpdateSynth()
|
p.compileOrUpdateSynth()
|
||||||
default:
|
default:
|
||||||
// ignore unknown messages
|
// ignore unknown messages
|
||||||
@@ -355,7 +362,7 @@ func (p *Player) compileOrUpdateSynth() {
|
|||||||
if p.synth != nil {
|
if p.synth != nil {
|
||||||
err := p.synth.Update(p.song.Patch, p.song.BPM)
|
err := p.synth.Update(p.song.Patch, p.song.BPM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.synth = nil
|
p.destroySynth()
|
||||||
p.SendAlert("PlayerCrash", fmt.Sprintf("synth.Update: %v", err), Error)
|
p.SendAlert("PlayerCrash", fmt.Sprintf("synth.Update: %v", err), Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -363,7 +370,7 @@ func (p *Player) compileOrUpdateSynth() {
|
|||||||
var err error
|
var err error
|
||||||
p.synth, err = p.synther.Synth(p.song.Patch, p.song.BPM)
|
p.synth, err = p.synther.Synth(p.song.Patch, p.song.BPM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.synth = nil
|
p.destroySynth()
|
||||||
p.SendAlert("PlayerCrash", fmt.Sprintf("synther.Synth: %v", err), Error)
|
p.SendAlert("PlayerCrash", fmt.Sprintf("synther.Synth: %v", err), Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ type NativeSynther struct {
|
|||||||
|
|
||||||
type NativeSynth C.Synth
|
type NativeSynth C.Synth
|
||||||
|
|
||||||
func (s NativeSynther) Name() string { return "Native" }
|
func (s NativeSynther) Name() string { return "Native" }
|
||||||
|
func (s NativeSynther) SupportsParallelism() bool { return false }
|
||||||
|
|
||||||
func (s NativeSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
func (s NativeSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
||||||
synth, err := Synth(patch, bpm)
|
synth, err := Synth(patch, bpm)
|
||||||
@@ -67,6 +68,8 @@ func Synth(patch sointu.Patch, bpm int) (*NativeSynth, error) {
|
|||||||
return (*NativeSynth)(s), nil
|
return (*NativeSynth)(s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *NativeSynth) Close() {}
|
||||||
|
|
||||||
// Render renders until the buffer is full or the modulated time is reached, whichever
|
// Render renders until the buffer is full or the modulated time is reached, whichever
|
||||||
// happens first.
|
// happens first.
|
||||||
// Parameters:
|
// Parameters:
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ func TestRenderSamples(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
|
defer synth.Close()
|
||||||
synth.Trigger(0, 64)
|
synth.Trigger(0, 64)
|
||||||
buffer := make(sointu.AudioBuffer, su_max_samples)
|
buffer := make(sointu.AudioBuffer, su_max_samples)
|
||||||
err = buffer[:len(buffer)/2].Fill(synth)
|
err = buffer[:len(buffer)/2].Fill(synth)
|
||||||
@@ -162,6 +163,7 @@ func TestStackUnderflow(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
|
defer synth.Close()
|
||||||
buffer := make(sointu.AudioBuffer, 1)
|
buffer := make(sointu.AudioBuffer, 1)
|
||||||
err = buffer.Fill(synth)
|
err = buffer.Fill(synth)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -178,6 +180,7 @@ func TestStackBalancing(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
|
defer synth.Close()
|
||||||
buffer := make(sointu.AudioBuffer, 1)
|
buffer := make(sointu.AudioBuffer, 1)
|
||||||
err = buffer.Fill(synth)
|
err = buffer.Fill(synth)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -211,6 +214,7 @@ func TestStackOverflow(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
|
defer synth.Close()
|
||||||
buffer := make(sointu.AudioBuffer, 1)
|
buffer := make(sointu.AudioBuffer, 1)
|
||||||
err = buffer.Fill(synth)
|
err = buffer.Fill(synth)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -228,6 +232,7 @@ func TestDivideByZero(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
|
defer synth.Close()
|
||||||
buffer := make(sointu.AudioBuffer, 1)
|
buffer := make(sointu.AudioBuffer, 1)
|
||||||
err = buffer.Fill(synth)
|
err = buffer.Fill(synth)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -93,7 +93,8 @@ success:
|
|||||||
f.Read(su_sample_table[:])
|
f.Read(su_sample_table[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s GoSynther) Name() string { return "Go" }
|
func (s GoSynther) Name() string { return "Go" }
|
||||||
|
func (s GoSynther) SupportsParallelism() bool { return false }
|
||||||
|
|
||||||
func (s GoSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
func (s GoSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
||||||
bytecode, err := NewBytecode(patch, AllFeatures{}, bpm)
|
bytecode, err := NewBytecode(patch, AllFeatures{}, bpm)
|
||||||
@@ -115,6 +116,8 @@ func (s *GoSynth) Release(voiceIndex int) {
|
|||||||
s.state.voices[voiceIndex].sustain = false
|
s.state.voices[voiceIndex].sustain = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *GoSynth) Close() {}
|
||||||
|
|
||||||
func (s *GoSynth) Update(patch sointu.Patch, bpm int) error {
|
func (s *GoSynth) Update(patch sointu.Patch, bpm int) error {
|
||||||
bytecode, err := NewBytecode(patch, AllFeatures{}, bpm)
|
bytecode, err := NewBytecode(patch, AllFeatures{}, bpm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ func TestStackUnderflow(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
|
defer synth.Close()
|
||||||
buffer := make(sointu.AudioBuffer, 1)
|
buffer := make(sointu.AudioBuffer, 1)
|
||||||
err = buffer.Fill(synth)
|
err = buffer.Fill(synth)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -213,6 +214,7 @@ func TestStackBalancing(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
|
defer synth.Close()
|
||||||
buffer := make(sointu.AudioBuffer, 1)
|
buffer := make(sointu.AudioBuffer, 1)
|
||||||
err = buffer.Fill(synth)
|
err = buffer.Fill(synth)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
182
vm/parallel_synth.go
Normal file
182
vm/parallel_synth.go
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"math/bits"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/vsariola/sointu"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
ParallelSynth struct {
|
||||||
|
voiceMapping [][]int
|
||||||
|
synths []sointu.Synth
|
||||||
|
commands chan<- parallelSynthCommand // maxtime
|
||||||
|
results <-chan parallelSynthResult // rendered buffer
|
||||||
|
pool sync.Pool
|
||||||
|
synther sointu.Synther
|
||||||
|
}
|
||||||
|
|
||||||
|
ParallelSynther struct {
|
||||||
|
synther sointu.Synther
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
parallelSynthCommand struct {
|
||||||
|
core int
|
||||||
|
samples int
|
||||||
|
time int
|
||||||
|
}
|
||||||
|
|
||||||
|
parallelSynthResult struct {
|
||||||
|
buffer *sointu.AudioBuffer
|
||||||
|
samples int
|
||||||
|
time int
|
||||||
|
renderError error
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func MakeParallelSynther(synther sointu.Synther) ParallelSynther {
|
||||||
|
return ParallelSynther{synther: synther, name: "Parallel " + synther.Name()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ParallelSynther) Name() string { return s.name }
|
||||||
|
func (s ParallelSynther) SupportsParallelism() bool { return true }
|
||||||
|
|
||||||
|
func (s ParallelSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
||||||
|
patches, voiceMapping := splitPatchByCores(patch)
|
||||||
|
synths := make([]sointu.Synth, 0, len(patches))
|
||||||
|
for _, p := range patches {
|
||||||
|
synth, err := s.synther.Synth(p, bpm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
synths = append(synths, synth)
|
||||||
|
}
|
||||||
|
ret := &ParallelSynth{
|
||||||
|
synths: synths,
|
||||||
|
voiceMapping: voiceMapping,
|
||||||
|
pool: sync.Pool{New: func() any { ret := make(sointu.AudioBuffer, 0, 8096); return &ret }},
|
||||||
|
}
|
||||||
|
ret.startProcesses()
|
||||||
|
ret.synther = s.synther
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ParallelSynth) Update(patch sointu.Patch, bpm int) error {
|
||||||
|
patches, voiceMapping := splitPatchByCores(patch)
|
||||||
|
s.voiceMapping = voiceMapping
|
||||||
|
for i, p := range patches {
|
||||||
|
if len(s.synths) <= i {
|
||||||
|
synth, err := s.synther.Synth(p, bpm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.synths = append(s.synths, synth)
|
||||||
|
} else {
|
||||||
|
if err := s.synths[i].Update(p, bpm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ParallelSynth) startProcesses() {
|
||||||
|
maxProcs := runtime.GOMAXPROCS(0)
|
||||||
|
cmdChan := make(chan parallelSynthCommand, maxProcs)
|
||||||
|
s.commands = cmdChan
|
||||||
|
resultsChan := make(chan parallelSynthResult, maxProcs)
|
||||||
|
s.results = resultsChan
|
||||||
|
for i := 0; i < maxProcs; i++ {
|
||||||
|
go func(commandCh <-chan parallelSynthCommand, resultCh chan<- parallelSynthResult) {
|
||||||
|
for cmd := range commandCh {
|
||||||
|
buffer := s.pool.Get().(*sointu.AudioBuffer)
|
||||||
|
*buffer = append(*buffer, make(sointu.AudioBuffer, cmd.samples)...)
|
||||||
|
samples, time, renderError := s.synths[cmd.core].Render(*buffer, cmd.time)
|
||||||
|
resultCh <- parallelSynthResult{buffer: buffer, samples: samples, time: time, renderError: renderError}
|
||||||
|
}
|
||||||
|
}(cmdChan, resultsChan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ParallelSynth) Close() {
|
||||||
|
close(s.commands)
|
||||||
|
for _, synth := range s.synths {
|
||||||
|
synth.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ParallelSynth) Trigger(voiceIndex int, note byte) {
|
||||||
|
for core, synth := range s.synths {
|
||||||
|
if ind := s.voiceMapping[core][voiceIndex]; ind >= 0 {
|
||||||
|
synth.Trigger(ind, note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ParallelSynth) Release(voiceIndex int) {
|
||||||
|
for core, synth := range s.synths {
|
||||||
|
if ind := s.voiceMapping[core][voiceIndex]; ind >= 0 {
|
||||||
|
synth.Release(ind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ParallelSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, time int, renderError error) {
|
||||||
|
count := len(s.synths)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
s.commands <- parallelSynthCommand{core: i, samples: len(buffer), time: maxtime}
|
||||||
|
}
|
||||||
|
clear(buffer)
|
||||||
|
samples = math.MaxInt
|
||||||
|
time = math.MaxInt
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
result := <-s.results
|
||||||
|
if result.renderError != nil && renderError == nil {
|
||||||
|
renderError = result.renderError
|
||||||
|
}
|
||||||
|
samples = min(samples, result.samples)
|
||||||
|
time = min(time, result.time)
|
||||||
|
for j := 0; j < samples; j++ {
|
||||||
|
buffer[j][0] += (*result.buffer)[j][0]
|
||||||
|
buffer[j][1] += (*result.buffer)[j][1]
|
||||||
|
}
|
||||||
|
*result.buffer = (*result.buffer)[:0]
|
||||||
|
s.pool.Put(result.buffer)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitPatchByCores(patch sointu.Patch) ([]sointu.Patch, [][]int) {
|
||||||
|
maxCores := 1
|
||||||
|
for _, instr := range patch {
|
||||||
|
maxCores = max(bits.Len(instr.CoreBitMask), maxCores)
|
||||||
|
}
|
||||||
|
ret := make([]sointu.Patch, maxCores)
|
||||||
|
for core := 0; core < maxCores; core++ {
|
||||||
|
ret[core] = make(sointu.Patch, 0, len(patch))
|
||||||
|
}
|
||||||
|
voicemapping := make([][]int, maxCores)
|
||||||
|
for core := range maxCores {
|
||||||
|
voicemapping[core] = make([]int, patch.NumVoices())
|
||||||
|
for j := range voicemapping[core] {
|
||||||
|
voicemapping[core][j] = -1
|
||||||
|
}
|
||||||
|
coreVoice := 0
|
||||||
|
curVoice := 0
|
||||||
|
for _, instr := range patch {
|
||||||
|
if instr.CoreBitMask == 0 || (instr.CoreBitMask&(1<<core)) != 0 {
|
||||||
|
ret[core] = append(ret[core], instr)
|
||||||
|
for j := 0; j < instr.NumVoices; j++ {
|
||||||
|
voicemapping[core][curVoice+j] = coreVoice + j
|
||||||
|
}
|
||||||
|
coreVoice += instr.NumVoices
|
||||||
|
}
|
||||||
|
curVoice += instr.NumVoices
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, voicemapping
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user