mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-18 21:14:31 -04:00
feat: add the ability to use Sointu as a sync-tracker
There is a new "sync" opcode that saves the top-most signal every 256 samples to the new "syncBuffer" output. Additionally, you can enable saving the current fractional row as sync[0], avoiding calculating the beat in the shader, but also calculating the beat correctly when the beat is modulated.
This commit is contained in:
@ -70,17 +70,18 @@ func Synth(patch sointu.Patch) (*C.Synth, error) {
|
||||
// time > maxtime, as it is modulated and the time could advance by 2 or more, so the loop
|
||||
// exit condition would fire when the time is already past maxtime.
|
||||
// Under no conditions, nsamples >= len(buffer)/2 i.e. guaranteed to never overwrite the buffer.
|
||||
func (synth *C.Synth) Render(buffer []float32, maxtime int) (int, int, error) {
|
||||
func (synth *C.Synth) Render(buffer []float32, syncBuffer []float32, maxtime int) (int, int, int, error) {
|
||||
// TODO: syncBuffer is not getting passed to cgo; do we want to even try to support the syncing with the native bridge
|
||||
if len(buffer)%1 == 1 {
|
||||
return -1, -1, errors.New("RenderTime writes stereo signals, so buffer should have even length")
|
||||
return -1, -1, -1, errors.New("RenderTime writes stereo signals, so buffer should have even length")
|
||||
}
|
||||
samples := C.int(len(buffer) / 2)
|
||||
time := C.int(maxtime)
|
||||
errcode := int(C.su_render(synth, (*C.float)(&buffer[0]), &samples, &time))
|
||||
if errcode > 0 {
|
||||
return int(samples), int(time), &RenderError{errcode: errcode}
|
||||
return int(samples), 0, int(time), &RenderError{errcode: errcode}
|
||||
}
|
||||
return int(samples), int(time), nil
|
||||
return int(samples), 0, int(time), nil
|
||||
}
|
||||
|
||||
// Trigger is part of C.Synths' implementation of sointu.Synth interface
|
||||
|
@ -44,7 +44,7 @@ func TestOscillatSine(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Compiling patch failed: %v", err)
|
||||
}
|
||||
buffer, err := sointu.Play(synth, song)
|
||||
buffer, _, err := sointu.Play(synth, song)
|
||||
if err != nil {
|
||||
t.Fatalf("Render failed: %v", err)
|
||||
}
|
||||
@ -103,7 +103,7 @@ func TestAllRegressionTests(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Compiling patch failed: %v", err)
|
||||
}
|
||||
buffer, err := sointu.Play(synth, song)
|
||||
buffer, _, err := sointu.Play(synth, song)
|
||||
buffer = buffer[:song.Score.LengthInRows()*song.SamplesPerRow()*2] // extend to the nominal length always.
|
||||
if err != nil {
|
||||
t.Fatalf("Play failed: %v", err)
|
||||
|
@ -18,10 +18,11 @@ type Compiler struct {
|
||||
OS string
|
||||
Arch string
|
||||
Output16Bit bool
|
||||
RowSync bool
|
||||
}
|
||||
|
||||
// New returns a new compiler using the default .asm templates
|
||||
func New(os string, arch string, output16Bit bool) (*Compiler, error) {
|
||||
func New(os string, arch string, output16Bit bool, rowsync bool) (*Compiler, error) {
|
||||
_, myname, _, _ := runtime.Caller(0)
|
||||
var subdir string
|
||||
if arch == "386" || arch == "amd64" {
|
||||
@ -32,17 +33,17 @@ func New(os string, arch string, output16Bit bool) (*Compiler, error) {
|
||||
return nil, fmt.Errorf("compiler.New failed, because only amd64, 386 and wasm archs are supported (targeted architecture was %v)", arch)
|
||||
}
|
||||
templateDir := filepath.Join(path.Dir(myname), "..", "..", "templates", subdir)
|
||||
compiler, err := NewFromTemplates(os, arch, output16Bit, templateDir)
|
||||
compiler, err := NewFromTemplates(os, arch, output16Bit, rowsync, templateDir)
|
||||
return compiler, err
|
||||
}
|
||||
|
||||
func NewFromTemplates(os string, arch string, output16Bit bool, templateDirectory string) (*Compiler, error) {
|
||||
func NewFromTemplates(os string, arch string, output16Bit bool, rowsync bool, templateDirectory string) (*Compiler, error) {
|
||||
globPtrn := filepath.Join(templateDirectory, "*.*")
|
||||
tmpl, err := template.New("base").Funcs(sprig.TxtFuncMap()).ParseGlob(globPtrn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`could not create template based on directory "%v": %v`, templateDirectory, err)
|
||||
}
|
||||
return &Compiler{Template: tmpl, OS: os, Arch: arch, Output16Bit: output16Bit}, nil
|
||||
return &Compiler{Template: tmpl, OS: os, Arch: arch, RowSync: rowsync, Output16Bit: output16Bit}, nil
|
||||
}
|
||||
|
||||
func (com *Compiler) Library() (map[string]string, error) {
|
||||
|
@ -354,15 +354,25 @@ func (p *X86Macros) FmtStack() string {
|
||||
}
|
||||
|
||||
func (p *X86Macros) ExportFunc(name string, params ...string) string {
|
||||
if !p.Amd64 {
|
||||
reverseParams := make([]string, len(params))
|
||||
for i, param := range params {
|
||||
reverseParams[len(params)-1-i] = param
|
||||
}
|
||||
p.Stacklocs = append(reverseParams, "retaddr_"+name) // in 32-bit, we use stdcall and parameters are in the stack
|
||||
if p.OS == "windows" {
|
||||
return fmt.Sprintf("%[1]v\nglobal _%[2]v@%[3]v\n_%[2]v@%[3]v:", p.SectText(name), name, len(params)*4)
|
||||
}
|
||||
numRegisters := 0 // in 32-bit systems, we use stdcall: everything in stack
|
||||
switch {
|
||||
case p.Amd64 && p.OS == "windows":
|
||||
numRegisters = 4 // 64-bit windows has 4 parameters in registers, rest in stack
|
||||
case p.Amd64:
|
||||
numRegisters = 6 // System V ABI has 6 parameters in registers, rest in stack
|
||||
}
|
||||
if len(params) > numRegisters {
|
||||
params = params[numRegisters:]
|
||||
} else {
|
||||
params = nil
|
||||
}
|
||||
reverseParams := make([]string, len(params))
|
||||
for i, param := range params {
|
||||
reverseParams[len(params)-1-i] = param
|
||||
}
|
||||
p.Stacklocs = append(reverseParams, "retaddr_"+name) // in 32-bit, we use stdcall and parameters are in the stack
|
||||
if !p.Amd64 && p.OS == "windows" {
|
||||
return fmt.Sprintf("%[1]v\nglobal _%[2]v@%[3]v\n_%[2]v@%[3]v:", p.SectText(name), name, len(params)*4)
|
||||
}
|
||||
if p.OS == "darwin" {
|
||||
return fmt.Sprintf("%[1]v\nglobal _%[2]v\n_%[2]v:", p.SectText(name), name)
|
||||
|
@ -115,7 +115,7 @@ func (s *Interpreter) Update(patch sointu.Patch) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Interpreter) Render(buffer []float32, maxtime int) (samples int, time int, renderError error) {
|
||||
func (s *Interpreter) Render(buffer []float32, syncBuf []float32, maxtime int) (samples int, syncs int, time int, renderError error) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
renderError = fmt.Errorf("render panicced: %v", err)
|
||||
@ -133,6 +133,10 @@ func (s *Interpreter) Render(buffer []float32, maxtime int) (samples int, time i
|
||||
voicesRemaining := s.bytePatch.NumVoices
|
||||
voices := s.synth.voices[:]
|
||||
units := voices[0].units[:]
|
||||
if byte(s.synth.globalTime) == 0 { // every 256 samples
|
||||
syncBuf[0], syncBuf = float32(time), syncBuf[1:]
|
||||
syncs++
|
||||
}
|
||||
for voicesRemaining > 0 {
|
||||
op := commands[0]
|
||||
commands = commands[1:]
|
||||
@ -152,7 +156,7 @@ func (s *Interpreter) Render(buffer []float32, maxtime int) (samples int, time i
|
||||
}
|
||||
tcount := transformCounts[opNoStereo-1]
|
||||
if len(values) < tcount {
|
||||
return samples, time, errors.New("value stream ended prematurely")
|
||||
return samples, syncs, time, errors.New("value stream ended prematurely")
|
||||
}
|
||||
voice := &voices[0]
|
||||
unit := &units[0]
|
||||
@ -523,16 +527,20 @@ func (s *Interpreter) Render(buffer []float32, maxtime int) (samples int, time i
|
||||
if stereo {
|
||||
stack = append(stack, gain)
|
||||
}
|
||||
case opSync:
|
||||
if byte(s.synth.globalTime) == 0 { // every 256 samples
|
||||
syncBuf[0], syncBuf = float32(stack[l-1]), syncBuf[1:]
|
||||
}
|
||||
default:
|
||||
return samples, time, errors.New("invalid / unimplemented opcode")
|
||||
return samples, syncs, time, errors.New("invalid / unimplemented opcode")
|
||||
}
|
||||
units = units[1:]
|
||||
}
|
||||
if len(stack) < 4 {
|
||||
return samples, time, errors.New("stack underflow")
|
||||
return samples, syncs, time, errors.New("stack underflow")
|
||||
}
|
||||
if len(stack) > 4 {
|
||||
return samples, time, errors.New("stack not empty")
|
||||
return samples, syncs, time, errors.New("stack not empty")
|
||||
}
|
||||
buffer[0] = synth.outputs[0]
|
||||
buffer[1] = synth.outputs[1]
|
||||
@ -544,7 +552,7 @@ func (s *Interpreter) Render(buffer []float32, maxtime int) (samples int, time i
|
||||
s.synth.globalTime++
|
||||
}
|
||||
s.stack = stack[:0]
|
||||
return samples, time, nil
|
||||
return samples, syncs, time, nil
|
||||
}
|
||||
|
||||
func (s *synth) rand() float32 {
|
||||
|
@ -45,7 +45,7 @@ func TestAllRegressionTests(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Compiling patch failed: %v", err)
|
||||
}
|
||||
buffer, err := sointu.Play(synth, song)
|
||||
buffer, _, err := sointu.Play(synth, song)
|
||||
buffer = buffer[:song.Score.LengthInRows()*song.SamplesPerRow()*2] // extend to the nominal length always.
|
||||
if err != nil {
|
||||
t.Fatalf("Play failed: %v", err)
|
||||
|
@ -30,7 +30,8 @@ const (
|
||||
opReceive = 26
|
||||
opSend = 27
|
||||
opSpeed = 28
|
||||
opXch = 29
|
||||
opSync = 29
|
||||
opXch = 30
|
||||
)
|
||||
|
||||
var transformCounts = [...]int{0, 0, 1, 0, 5, 1, 4, 1, 5, 2, 1, 1, 0, 1, 0, 1, 0, 0, 2, 6, 1, 2, 1, 0, 0, 0, 1, 0, 0}
|
||||
var transformCounts = [...]int{0, 0, 1, 0, 5, 1, 4, 1, 5, 2, 1, 1, 0, 1, 0, 1, 0, 0, 2, 6, 1, 2, 1, 0, 0, 0, 1, 0, 0, 0}
|
||||
|
Reference in New Issue
Block a user