mirror of
https://github.com/vsariola/sointu.git
synced 2025-11-18 16:49:06 -05:00
drafting
This commit is contained in:
parent
7f03664870
commit
fa7901c7c6
@ -8,6 +8,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vsariola/sointu"
|
||||
"github.com/vsariola/sointu/vm"
|
||||
@ -16,7 +17,10 @@ import (
|
||||
type NativeSynther struct {
|
||||
}
|
||||
|
||||
type NativeSynth C.Synth
|
||||
type NativeSynth struct {
|
||||
csynth C.Synth
|
||||
cpuLoad sointu.CPULoad
|
||||
}
|
||||
|
||||
func (s NativeSynther) Name() string { return "Native" }
|
||||
func (s NativeSynther) SupportsParallelism() bool { return false }
|
||||
@ -46,7 +50,7 @@ func Synth(patch sointu.Patch, bpm int) (*NativeSynth, error) {
|
||||
s.Opcodes[0] = 0
|
||||
s.NumVoices = 1
|
||||
s.Polyphony = 0
|
||||
return (*NativeSynth)(s), nil
|
||||
return &NativeSynth{csynth: *s}, nil
|
||||
}
|
||||
for i, v := range comPatch.Opcodes {
|
||||
s.Opcodes[i] = (C.uchar)(v)
|
||||
@ -65,11 +69,19 @@ func Synth(patch sointu.Patch, bpm int) (*NativeSynth, error) {
|
||||
s.NumVoices = C.uint(comPatch.NumVoices)
|
||||
s.Polyphony = C.uint(comPatch.PolyphonyBitmask)
|
||||
s.RandSeed = 1
|
||||
return (*NativeSynth)(s), nil
|
||||
return &NativeSynth{csynth: *s}, nil
|
||||
}
|
||||
|
||||
func (s *NativeSynth) Close() {}
|
||||
|
||||
func (s *NativeSynth) NumCores() int { return 1 }
|
||||
func (s *NativeSynth) CPULoad(loads []sointu.CPULoad) {
|
||||
if len(loads) < 1 {
|
||||
return
|
||||
}
|
||||
loads[0] = s.cpuLoad
|
||||
}
|
||||
|
||||
// Render renders until the buffer is full or the modulated time is reached, whichever
|
||||
// happens first.
|
||||
// Parameters:
|
||||
@ -92,12 +104,14 @@ func (s *NativeSynth) Close() {}
|
||||
// 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 (bridgesynth *NativeSynth) Render(buffer sointu.AudioBuffer, maxtime int) (int, int, error) {
|
||||
synth := (*C.Synth)(bridgesynth)
|
||||
synth := &bridgesynth.csynth
|
||||
// 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")
|
||||
}
|
||||
samples := C.int(len(buffer))
|
||||
startTime := time.Now()
|
||||
defer func() { bridgesynth.cpuLoad.Update(time.Since(startTime), int64(samples)) }()
|
||||
time := C.int(maxtime)
|
||||
errcode := int(C.su_render(synth, (*C.float)(&buffer[0][0]), &samples, &time))
|
||||
if errcode > 0 {
|
||||
@ -108,7 +122,7 @@ func (bridgesynth *NativeSynth) Render(buffer sointu.AudioBuffer, maxtime int) (
|
||||
|
||||
// Trigger is part of C.Synths' implementation of sointu.Synth interface
|
||||
func (bridgesynth *NativeSynth) Trigger(voice int, note byte) {
|
||||
s := (*C.Synth)(bridgesynth)
|
||||
s := &bridgesynth.csynth
|
||||
if voice < 0 || voice >= len(s.SynthWrk.Voices) {
|
||||
return
|
||||
}
|
||||
@ -119,7 +133,7 @@ func (bridgesynth *NativeSynth) Trigger(voice int, note byte) {
|
||||
|
||||
// Release is part of C.Synths' implementation of sointu.Synth interface
|
||||
func (bridgesynth *NativeSynth) Release(voice int) {
|
||||
s := (*C.Synth)(bridgesynth)
|
||||
s := &bridgesynth.csynth
|
||||
if voice < 0 || voice >= len(s.SynthWrk.Voices) {
|
||||
return
|
||||
}
|
||||
@ -128,7 +142,7 @@ func (bridgesynth *NativeSynth) Release(voice int) {
|
||||
|
||||
// Update
|
||||
func (bridgesynth *NativeSynth) Update(patch sointu.Patch, bpm int) error {
|
||||
s := (*C.Synth)(bridgesynth)
|
||||
s := &bridgesynth.csynth
|
||||
if n := patch.NumDelayLines(); n > 128 {
|
||||
return fmt.Errorf("native bridge has currently a hard limit of 128 delaylines; patch uses %v", n)
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/vsariola/sointu"
|
||||
)
|
||||
@ -27,6 +28,7 @@ type (
|
||||
stack []float32
|
||||
state synthState
|
||||
delaylines []delayline
|
||||
cpuLoad sointu.CPULoad
|
||||
}
|
||||
|
||||
// GoSynther is a Synther implementation that can converts patches into
|
||||
@ -118,6 +120,14 @@ func (s *GoSynth) Release(voiceIndex int) {
|
||||
|
||||
func (s *GoSynth) Close() {}
|
||||
|
||||
func (s *GoSynth) NumCores() int { return 1 }
|
||||
func (s *GoSynth) CPULoad(loads []sointu.CPULoad) {
|
||||
if len(loads) < 1 {
|
||||
return
|
||||
}
|
||||
loads[0] = s.cpuLoad
|
||||
}
|
||||
|
||||
func (s *GoSynth) Update(patch sointu.Patch, bpm int) error {
|
||||
bytecode, err := NewBytecode(patch, AllFeatures{}, bpm)
|
||||
if err != nil {
|
||||
@ -146,7 +156,10 @@ func (s *GoSynth) Update(patch sointu.Patch, bpm int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *GoSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, time int, renderError error) {
|
||||
func (s *GoSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, renderTime int, renderError error) {
|
||||
startTime := time.Now()
|
||||
defer func() { s.cpuLoad.Update(time.Since(startTime), int64(samples)) }()
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
renderError = fmt.Errorf("render panicced: %v", err)
|
||||
@ -156,7 +169,7 @@ func (s *GoSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, t
|
||||
stack := s.stack[:]
|
||||
stack = append(stack, []float32{0, 0, 0, 0}...)
|
||||
synth := &s.state
|
||||
for time < maxtime && len(buffer) > 0 {
|
||||
for renderTime < maxtime && len(buffer) > 0 {
|
||||
opcodesInstr := s.bytecode.Opcodes
|
||||
operandsInstr := s.bytecode.Operands
|
||||
opcodes, operands := opcodesInstr, operandsInstr
|
||||
@ -185,7 +198,7 @@ func (s *GoSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, t
|
||||
}
|
||||
tcount := transformCounts[opNoStereo-1]
|
||||
if len(operands) < tcount {
|
||||
return samples, time, errors.New("operand stream ended prematurely")
|
||||
return samples, renderTime, errors.New("operand stream ended prematurely")
|
||||
}
|
||||
voice := &voices[0]
|
||||
unit := &units[0]
|
||||
@ -292,7 +305,7 @@ func (s *GoSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, t
|
||||
r := unit.state[0] + float32(math.Exp2(float64(stack[l-1]*2.206896551724138))-1)
|
||||
w := int(r+1.5) - 1
|
||||
unit.state[0] = r - float32(w)
|
||||
time += w
|
||||
renderTime += w
|
||||
stack = stack[:l-1]
|
||||
case opIn:
|
||||
var channel byte
|
||||
@ -584,26 +597,26 @@ func (s *GoSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, t
|
||||
case opSync:
|
||||
break
|
||||
default:
|
||||
return samples, time, errors.New("invalid / unimplemented opcode")
|
||||
return samples, renderTime, errors.New("invalid / unimplemented opcode")
|
||||
}
|
||||
units = units[1:]
|
||||
}
|
||||
if len(stack) < 4 {
|
||||
return samples, time, errors.New("stack underflow")
|
||||
return samples, renderTime, errors.New("stack underflow")
|
||||
}
|
||||
if len(stack) > 4 {
|
||||
return samples, time, errors.New("stack not empty")
|
||||
return samples, renderTime, errors.New("stack not empty")
|
||||
}
|
||||
buffer[0][0], buffer[0][1] = synth.outputs[0], synth.outputs[1]
|
||||
synth.outputs[0] = 0
|
||||
synth.outputs[1] = 0
|
||||
buffer = buffer[1:]
|
||||
samples++
|
||||
time++
|
||||
renderTime++
|
||||
s.state.globalTime++
|
||||
}
|
||||
s.stack = stack[:0]
|
||||
return samples, time, nil
|
||||
return samples, renderTime, nil
|
||||
}
|
||||
|
||||
func (s *synthState) rand() float32 {
|
||||
|
||||
@ -11,7 +11,7 @@ import (
|
||||
|
||||
type (
|
||||
ParallelSynth struct {
|
||||
voiceMapping [][]int
|
||||
voiceMapping voiceMapping
|
||||
synths []sointu.Synth
|
||||
commands chan<- parallelSynthCommand // maxtime
|
||||
results <-chan parallelSynthResult // rendered buffer
|
||||
@ -24,6 +24,8 @@ type (
|
||||
name string
|
||||
}
|
||||
|
||||
voiceMapping [MAX_CORES][MAX_VOICES]int
|
||||
|
||||
parallelSynthCommand struct {
|
||||
core int
|
||||
samples int
|
||||
@ -38,6 +40,8 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
const MAX_CORES = 4
|
||||
|
||||
func MakeParallelSynther(synther sointu.Synther) ParallelSynther {
|
||||
return ParallelSynther{synther: synther, name: "Parallel " + synther.Name()}
|
||||
}
|
||||
@ -67,16 +71,21 @@ func (s ParallelSynther) Synth(patch sointu.Patch, bpm int) (sointu.Synth, error
|
||||
|
||||
func (s *ParallelSynth) Update(patch sointu.Patch, bpm int) error {
|
||||
patches, voiceMapping := splitPatchByCores(patch)
|
||||
s.voiceMapping = voiceMapping
|
||||
if s.voiceMapping != voiceMapping {
|
||||
s.voiceMapping = voiceMapping
|
||||
s.closeSynths()
|
||||
}
|
||||
for i, p := range patches {
|
||||
if len(s.synths) <= i {
|
||||
synth, err := s.synther.Synth(p, bpm)
|
||||
if err != nil {
|
||||
s.closeSynths()
|
||||
return err
|
||||
}
|
||||
s.synths = append(s.synths, synth)
|
||||
} else {
|
||||
if err := s.synths[i].Update(p, bpm); err != nil {
|
||||
s.closeSynths()
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -104,9 +113,14 @@ func (s *ParallelSynth) startProcesses() {
|
||||
|
||||
func (s *ParallelSynth) Close() {
|
||||
close(s.commands)
|
||||
s.closeSynths()
|
||||
}
|
||||
|
||||
func (s *ParallelSynth) closeSynths() {
|
||||
for _, synth := range s.synths {
|
||||
synth.Close()
|
||||
}
|
||||
s.synths = s.synths[:0]
|
||||
}
|
||||
|
||||
func (s *ParallelSynth) Trigger(voiceIndex int, note byte) {
|
||||
@ -125,6 +139,23 @@ func (s *ParallelSynth) Release(voiceIndex int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ParallelSynth) NumCores() (coreCount int) {
|
||||
for i := range s.synths {
|
||||
coreCount += s.synths[i].NumCores()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *ParallelSynth) CPULoad(loads []sointu.CPULoad) {
|
||||
for _, synth := range s.synths {
|
||||
synth.CPULoad(loads)
|
||||
if len(loads) <= synth.NumCores() {
|
||||
return
|
||||
}
|
||||
loads = loads[synth.NumCores():]
|
||||
}
|
||||
}
|
||||
|
||||
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++ {
|
||||
@ -134,6 +165,9 @@ func (s *ParallelSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples
|
||||
samples = math.MaxInt
|
||||
time = math.MaxInt
|
||||
for i := 0; i < count; i++ {
|
||||
// We mix the results as they come, but the order doesn't matter. This
|
||||
// leads to slight indeterminism in the results, because the order of
|
||||
// floating point additions can change the least significant bits.
|
||||
result := <-s.results
|
||||
if result.renderError != nil && renderError == nil {
|
||||
renderError = result.renderError
|
||||
@ -150,28 +184,34 @@ func (s *ParallelSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples
|
||||
return
|
||||
}
|
||||
|
||||
func splitPatchByCores(patch sointu.Patch) ([]sointu.Patch, [][]int) {
|
||||
maxCores := 1
|
||||
func splitPatchByCores(patch sointu.Patch) ([]sointu.Patch, voiceMapping) {
|
||||
cores := 1
|
||||
for _, instr := range patch {
|
||||
maxCores = max(bits.Len(instr.CoreBitMask), maxCores)
|
||||
cores = max(bits.Len((uint)(instr.CoreMaskM1+1)), cores)
|
||||
}
|
||||
ret := make([]sointu.Patch, maxCores)
|
||||
for core := 0; core < maxCores; core++ {
|
||||
ret[core] = make(sointu.Patch, 0, len(patch))
|
||||
cores = min(cores, MAX_CORES)
|
||||
ret := make([]sointu.Patch, cores)
|
||||
for c := 0; c < cores; c++ {
|
||||
ret[c] = 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
|
||||
var voicemapping [MAX_CORES][MAX_VOICES]int
|
||||
for c := 0; c < MAX_CORES; c++ {
|
||||
for j := 0; j < MAX_VOICES; j++ {
|
||||
voicemapping[c][j] = -1
|
||||
}
|
||||
}
|
||||
for c := range cores {
|
||||
coreVoice := 0
|
||||
curVoice := 0
|
||||
for _, instr := range patch {
|
||||
if instr.CoreBitMask == 0 || (instr.CoreBitMask&(1<<core)) != 0 {
|
||||
ret[core] = append(ret[core], instr)
|
||||
mask := instr.CoreMaskM1 + 1
|
||||
if mask&(1<<c) != 0 {
|
||||
ret[c] = append(ret[c], instr)
|
||||
for j := 0; j < instr.NumVoices; j++ {
|
||||
voicemapping[core][curVoice+j] = coreVoice + j
|
||||
if coreVoice+j >= MAX_VOICES {
|
||||
break
|
||||
}
|
||||
voicemapping[c][curVoice+j] = coreVoice + j
|
||||
}
|
||||
coreVoice += instr.NumVoices
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user