mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
refactor(vm): rename BytePatch to Bytecode
This commit is contained in:
parent
ccd283d2ea
commit
87604dd92e
@ -7,33 +7,55 @@ import (
|
|||||||
"github.com/vsariola/sointu"
|
"github.com/vsariola/sointu"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BytePatch is the compiler Sointu VM bytecode & data (delay times, sample
|
type (
|
||||||
// offsets) ready to interpret or from which the ASM/WASM code can be generate.
|
// Bytecode is the Sointu VM bytecode & data (delay times, sample offsets)
|
||||||
//
|
// which is executed by the synthesizer. It is generated from a Sointu patch.
|
||||||
// PolyphonyBitmask is a rather peculiar bitmask used by Sointu VM to store the
|
Bytecode struct {
|
||||||
// information about which voices use which instruments: bit MAXVOICES - n - 1
|
// Commands is the bytecode, which is a sequence of opcode bytes, one
|
||||||
// corresponds to voice n. If the bit 1, the next voice uses the same
|
// per unit in the patch. A byte of 0 denotes the end of an instrument,
|
||||||
// instrument. If the bit 0, the next voice uses different instrument. For
|
// at which point if that instrument has more than one voice, the
|
||||||
// example, if first instrument has 3 voices, second instrument has 2 voices,
|
// commands are repeated for each voice.
|
||||||
// and third instrument four voices, the PolyphonyBitmask is:
|
Commands []byte
|
||||||
//
|
|
||||||
// (MSB) 110101110 (LSB)
|
|
||||||
type BytePatch struct {
|
|
||||||
Commands []byte
|
|
||||||
Values []byte
|
|
||||||
DelayTimes []uint16
|
|
||||||
SampleOffsets []SampleOffset
|
|
||||||
PolyphonyBitmask uint32
|
|
||||||
NumVoices uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type SampleOffset struct {
|
// Values are the operands of the opcodes. Every opcode reads 0 or more
|
||||||
Start uint32
|
// values from the value sequence.
|
||||||
LoopStart uint16
|
Values []byte
|
||||||
LoopLength uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type bytePatchBuilder struct {
|
// DelayTimes is a table of delay times in samples. The delay times are
|
||||||
|
// used by the delay units in the patch. The delay unit only stores
|
||||||
|
// index and count of delay lines, and the delay times are looked up
|
||||||
|
// from this table. This way multiple reverb units do not have to repeat
|
||||||
|
// the same delay times.
|
||||||
|
DelayTimes []uint16
|
||||||
|
|
||||||
|
// SampleOffsets is a table of sample offsets, which tell where to find
|
||||||
|
// a particular sample in the sample data loaded from gm.dls. The sample
|
||||||
|
// offsets are used by the oscillator units that are configured to use
|
||||||
|
// samples. The unit only stores the index pointing to this table.
|
||||||
|
SampleOffsets []SampleOffset
|
||||||
|
|
||||||
|
// PolyphonyBitmask is a rather peculiar bitmask used by Sointu VM to store
|
||||||
|
// the information about which voices use which instruments: bit MAXVOICES -
|
||||||
|
// n - 1 corresponds to voice n. If the bit 1, the next voice uses the same
|
||||||
|
// instrument. If the bit 0, the next voice uses different instrument. For
|
||||||
|
// example, if first instrument has 3 voices, second instrument has 2
|
||||||
|
// voices, and third instrument four voices, the PolyphonyBitmask is: (MSB)
|
||||||
|
// 110101110 (LSB)
|
||||||
|
PolyphonyBitmask uint32
|
||||||
|
|
||||||
|
// NumVoices is the total number of voices in the patch
|
||||||
|
NumVoices uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// SampleOffset is an entry in the sample offset table
|
||||||
|
SampleOffset struct {
|
||||||
|
Start uint32 // start offset in words (1 word = 2 bytes)
|
||||||
|
LoopStart uint16 // loop start offset in words, relative to Start
|
||||||
|
LoopLength uint16 // loop length in words
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type bytecodeBuilder struct {
|
||||||
sampleOffsetMap map[SampleOffset]int
|
sampleOffsetMap map[SampleOffset]int
|
||||||
globalAddrs map[int]uint16
|
globalAddrs map[int]uint16
|
||||||
globalFixups map[int]([]int)
|
globalFixups map[int]([]int)
|
||||||
@ -42,14 +64,14 @@ type bytePatchBuilder struct {
|
|||||||
voiceNo int
|
voiceNo int
|
||||||
delayIndices [][]int
|
delayIndices [][]int
|
||||||
unitNo int
|
unitNo int
|
||||||
BytePatch
|
Bytecode
|
||||||
}
|
}
|
||||||
|
|
||||||
func Encode(patch sointu.Patch, featureSet FeatureSet, bpm int) (*BytePatch, error) {
|
func Encode(patch sointu.Patch, featureSet FeatureSet, bpm int) (*Bytecode, error) {
|
||||||
if patch.NumVoices() > 32 {
|
if patch.NumVoices() > 32 {
|
||||||
return nil, fmt.Errorf("Sointu does not support more than 32 concurrent voices; patch uses %v", patch.NumVoices())
|
return nil, fmt.Errorf("Sointu does not support more than 32 concurrent voices; patch uses %v", patch.NumVoices())
|
||||||
}
|
}
|
||||||
b := newBytePatchBuilder(patch, bpm)
|
b := newBytecodeBuilder(patch, bpm)
|
||||||
for instrIndex, instr := range patch {
|
for instrIndex, instr := range patch {
|
||||||
if instr.NumVoices < 1 {
|
if instr.NumVoices < 1 {
|
||||||
return nil, errors.New("Each instrument must have at least 1 voice")
|
return nil, errors.New("Each instrument must have at least 1 voice")
|
||||||
@ -186,10 +208,10 @@ func Encode(patch sointu.Patch, featureSet FeatureSet, bpm int) (*BytePatch, err
|
|||||||
}
|
}
|
||||||
b.cmdFinish(instr)
|
b.cmdFinish(instr)
|
||||||
}
|
}
|
||||||
return &b.BytePatch, nil
|
return &b.Bytecode, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBytePatchBuilder(patch sointu.Patch, bpm int) *bytePatchBuilder {
|
func newBytecodeBuilder(patch sointu.Patch, bpm int) *bytecodeBuilder {
|
||||||
var polyphonyBitmask uint32 = 0
|
var polyphonyBitmask uint32 = 0
|
||||||
for _, instr := range patch {
|
for _, instr := range patch {
|
||||||
for j := 0; j < instr.NumVoices-1; j++ {
|
for j := 0; j < instr.NumVoices-1; j++ {
|
||||||
@ -202,8 +224,8 @@ func newBytePatchBuilder(patch sointu.Patch, bpm int) *bytePatchBuilder {
|
|||||||
for i, d := range delayTimesInt {
|
for i, d := range delayTimesInt {
|
||||||
delayTimesU16[i] = uint16(d)
|
delayTimesU16[i] = uint16(d)
|
||||||
}
|
}
|
||||||
c := bytePatchBuilder{
|
c := bytecodeBuilder{
|
||||||
BytePatch: BytePatch{PolyphonyBitmask: polyphonyBitmask, NumVoices: uint32(patch.NumVoices()), DelayTimes: delayTimesU16},
|
Bytecode: Bytecode{PolyphonyBitmask: polyphonyBitmask, NumVoices: uint32(patch.NumVoices()), DelayTimes: delayTimesU16},
|
||||||
sampleOffsetMap: map[SampleOffset]int{},
|
sampleOffsetMap: map[SampleOffset]int{},
|
||||||
globalAddrs: map[int]uint16{},
|
globalAddrs: map[int]uint16{},
|
||||||
globalFixups: map[int]([]int){},
|
globalFixups: map[int]([]int){},
|
||||||
@ -214,14 +236,14 @@ func newBytePatchBuilder(patch sointu.Patch, bpm int) *bytePatchBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// cmd adds a command to the bytecode, and increments the unit number
|
// cmd adds a command to the bytecode, and increments the unit number
|
||||||
func (b *bytePatchBuilder) cmd(opcode int) {
|
func (b *bytecodeBuilder) cmd(opcode int) {
|
||||||
b.Commands = append(b.Commands, byte(opcode))
|
b.Commands = append(b.Commands, byte(opcode))
|
||||||
b.unitNo++
|
b.unitNo++
|
||||||
}
|
}
|
||||||
|
|
||||||
// cmdFinish adds a command to the bytecode that marks the end of an instrument, resets the unit number and increments the voice number
|
// cmdFinish adds a command to the bytecode that marks the end of an instrument, resets the unit number and increments the voice number
|
||||||
// local addresses are forgotten when instrument ends
|
// local addresses are forgotten when instrument ends
|
||||||
func (b *bytePatchBuilder) cmdFinish(instr sointu.Instrument) {
|
func (b *bytecodeBuilder) cmdFinish(instr sointu.Instrument) {
|
||||||
b.Commands = append(b.Commands, 0)
|
b.Commands = append(b.Commands, 0)
|
||||||
b.unitNo = 0
|
b.unitNo = 0
|
||||||
b.voiceNo += instr.NumVoices
|
b.voiceNo += instr.NumVoices
|
||||||
@ -230,14 +252,14 @@ func (b *bytePatchBuilder) cmdFinish(instr sointu.Instrument) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// vals appends values to the value stream
|
// vals appends values to the value stream
|
||||||
func (b *bytePatchBuilder) vals(values ...int) {
|
func (b *bytecodeBuilder) vals(values ...int) {
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
b.Values = append(b.Values, byte(v))
|
b.Values = append(b.Values, byte(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultVals appends the values to the value stream for all parameters that can be modulated and set
|
// defaultVals appends the values to the value stream for all parameters that can be modulated and set
|
||||||
func (b *bytePatchBuilder) defaultVals(unit sointu.Unit) {
|
func (b *bytecodeBuilder) defaultVals(unit sointu.Unit) {
|
||||||
for _, v := range sointu.UnitTypes[unit.Type] {
|
for _, v := range sointu.UnitTypes[unit.Type] {
|
||||||
if v.CanModulate && v.CanSet {
|
if v.CanModulate && v.CanSet {
|
||||||
b.Values = append(b.Values, byte(unit.Parameters[v.Name]))
|
b.Values = append(b.Values, byte(unit.Parameters[v.Name]))
|
||||||
@ -246,7 +268,7 @@ func (b *bytePatchBuilder) defaultVals(unit sointu.Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// localIDRef adds a reference to a local id label to the value stream; if the targeted ID has not been seen yet, it is added to the fixup list
|
// localIDRef adds a reference to a local id label to the value stream; if the targeted ID has not been seen yet, it is added to the fixup list
|
||||||
func (b *bytePatchBuilder) localIDRef(id int, addr int) {
|
func (b *bytecodeBuilder) localIDRef(id int, addr int) {
|
||||||
if v, ok := b.localAddrs[id]; ok {
|
if v, ok := b.localAddrs[id]; ok {
|
||||||
addr += int(v)
|
addr += int(v)
|
||||||
} else {
|
} else {
|
||||||
@ -256,7 +278,7 @@ func (b *bytePatchBuilder) localIDRef(id int, addr int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// globalIDRef adds a reference to a global id label to the value stream; if the targeted ID has not been seen yet, it is added to the fixup list
|
// globalIDRef adds a reference to a global id label to the value stream; if the targeted ID has not been seen yet, it is added to the fixup list
|
||||||
func (b *bytePatchBuilder) globalIDRef(id int, addr int) {
|
func (b *bytecodeBuilder) globalIDRef(id int, addr int) {
|
||||||
if v, ok := b.globalAddrs[id]; ok {
|
if v, ok := b.globalAddrs[id]; ok {
|
||||||
addr += int(v)
|
addr += int(v)
|
||||||
} else {
|
} else {
|
||||||
@ -266,7 +288,7 @@ func (b *bytePatchBuilder) globalIDRef(id int, addr int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// idLabel adds a label to the value stream for the given id; all earlier references to the id are fixed up
|
// idLabel adds a label to the value stream for the given id; all earlier references to the id are fixed up
|
||||||
func (b *bytePatchBuilder) idLabel(id int) {
|
func (b *bytecodeBuilder) idLabel(id int) {
|
||||||
localAddr := uint16((b.unitNo + 1) << 4)
|
localAddr := uint16((b.unitNo + 1) << 4)
|
||||||
b.fixUp(b.localFixups[id], localAddr)
|
b.fixUp(b.localFixups[id], localAddr)
|
||||||
b.localFixups[id] = nil
|
b.localFixups[id] = nil
|
||||||
@ -278,7 +300,7 @@ func (b *bytePatchBuilder) idLabel(id int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fixUp fixes up the references to the given id with the given delta
|
// fixUp fixes up the references to the given id with the given delta
|
||||||
func (b *bytePatchBuilder) fixUp(positions []int, delta uint16) {
|
func (b *bytecodeBuilder) fixUp(positions []int, delta uint16) {
|
||||||
for _, pos := range positions {
|
for _, pos := range positions {
|
||||||
orig := (uint16(b.Values[pos+1]) << 8) + uint16(b.Values[pos])
|
orig := (uint16(b.Values[pos+1]) << 8) + uint16(b.Values[pos])
|
||||||
new := orig + delta
|
new := orig + delta
|
||||||
@ -288,7 +310,7 @@ func (b *bytePatchBuilder) fixUp(positions []int, delta uint16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getSampleIndex returns the index of the sample in the sample offset table; if the sample has not been seen yet, it is added to the table
|
// getSampleIndex returns the index of the sample in the sample offset table; if the sample has not been seen yet, it is added to the table
|
||||||
func (b *bytePatchBuilder) getSampleIndex(unit sointu.Unit) int {
|
func (b *bytecodeBuilder) getSampleIndex(unit sointu.Unit) int {
|
||||||
s := SampleOffset{Start: uint32(unit.Parameters["samplestart"]), LoopStart: uint16(unit.Parameters["loopstart"]), LoopLength: uint16(unit.Parameters["looplength"])}
|
s := SampleOffset{Start: uint32(unit.Parameters["samplestart"]), LoopStart: uint16(unit.Parameters["loopstart"]), LoopLength: uint16(unit.Parameters["looplength"])}
|
||||||
if s.LoopLength == 0 {
|
if s.LoopLength == 0 {
|
||||||
// hacky quick fix: looplength 0 causes div by zero so avoid crashing
|
// hacky quick fix: looplength 0 causes div by zero so avoid crashing
|
@ -108,7 +108,7 @@ func (com *Compiler) Song(song *sointu.Song) (map[string]string, error) {
|
|||||||
FeatureSetMacros
|
FeatureSetMacros
|
||||||
X86Macros
|
X86Macros
|
||||||
SongMacros
|
SongMacros
|
||||||
*vm.BytePatch
|
*vm.Bytecode
|
||||||
Patterns [][]byte
|
Patterns [][]byte
|
||||||
Sequences [][]byte
|
Sequences [][]byte
|
||||||
PatternLength int
|
PatternLength int
|
||||||
@ -123,7 +123,7 @@ func (com *Compiler) Song(song *sointu.Song) (map[string]string, error) {
|
|||||||
FeatureSetMacros
|
FeatureSetMacros
|
||||||
WasmMacros
|
WasmMacros
|
||||||
SongMacros
|
SongMacros
|
||||||
*vm.BytePatch
|
*vm.Bytecode
|
||||||
Patterns [][]byte
|
Patterns [][]byte
|
||||||
Sequences [][]byte
|
Sequences [][]byte
|
||||||
PatternLength int
|
PatternLength int
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
// number of signals, so be warned that if you compose patches for it, they
|
// number of signals, so be warned that if you compose patches for it, they
|
||||||
// might not work with the x87 implementation, as it has only 8-level stack.
|
// might not work with the x87 implementation, as it has only 8-level stack.
|
||||||
type GoSynth struct {
|
type GoSynth struct {
|
||||||
bytePatch BytePatch
|
bytecode Bytecode
|
||||||
stack []float32
|
stack []float32
|
||||||
synth synth
|
synth synth
|
||||||
delaylines []delayline
|
delaylines []delayline
|
||||||
@ -88,11 +88,11 @@ success:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
func Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
||||||
bytePatch, err := Encode(patch, AllFeatures{}, bpm)
|
bytecode, err := Encode(patch, AllFeatures{}, bpm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error compiling %v", err)
|
return nil, fmt.Errorf("error compiling %v", err)
|
||||||
}
|
}
|
||||||
ret := &GoSynth{bytePatch: *bytePatch, stack: make([]float32, 0, 4), delaylines: make([]delayline, patch.NumDelayLines())}
|
ret := &GoSynth{bytecode: *bytecode, stack: make([]float32, 0, 4), delaylines: make([]delayline, patch.NumDelayLines())}
|
||||||
ret.synth.randSeed = 1
|
ret.synth.randSeed = 1
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
@ -113,20 +113,20 @@ func (s *GoSynth) Release(voiceIndex int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *GoSynth) Update(patch sointu.Patch, bpm int) error {
|
func (s *GoSynth) Update(patch sointu.Patch, bpm int) error {
|
||||||
bytePatch, err := Encode(patch, AllFeatures{}, bpm)
|
bytecode, err := Encode(patch, AllFeatures{}, bpm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error compiling %v", err)
|
return fmt.Errorf("error compiling %v", err)
|
||||||
}
|
}
|
||||||
needsRefresh := len(bytePatch.Commands) != len(s.bytePatch.Commands)
|
needsRefresh := len(bytecode.Commands) != len(s.bytecode.Commands)
|
||||||
if !needsRefresh {
|
if !needsRefresh {
|
||||||
for i, c := range bytePatch.Commands {
|
for i, c := range bytecode.Commands {
|
||||||
if s.bytePatch.Commands[i] != c {
|
if s.bytecode.Commands[i] != c {
|
||||||
needsRefresh = true
|
needsRefresh = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.bytePatch = *bytePatch
|
s.bytecode = *bytecode
|
||||||
for len(s.delaylines) < patch.NumDelayLines() {
|
for len(s.delaylines) < patch.NumDelayLines() {
|
||||||
s.delaylines = append(s.delaylines, delayline{})
|
s.delaylines = append(s.delaylines, delayline{})
|
||||||
}
|
}
|
||||||
@ -151,11 +151,11 @@ func (s *GoSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, t
|
|||||||
stack = append(stack, []float32{0, 0, 0, 0}...)
|
stack = append(stack, []float32{0, 0, 0, 0}...)
|
||||||
synth := &s.synth
|
synth := &s.synth
|
||||||
for time < maxtime && len(buffer) > 0 {
|
for time < maxtime && len(buffer) > 0 {
|
||||||
commandInstr := s.bytePatch.Commands
|
commandInstr := s.bytecode.Commands
|
||||||
valuesInstr := s.bytePatch.Values
|
valuesInstr := s.bytecode.Values
|
||||||
commands, values := commandInstr, valuesInstr
|
commands, values := commandInstr, valuesInstr
|
||||||
delaylines := s.delaylines
|
delaylines := s.delaylines
|
||||||
voicesRemaining := s.bytePatch.NumVoices
|
voicesRemaining := s.bytecode.NumVoices
|
||||||
voices := s.synth.voices[:]
|
voices := s.synth.voices[:]
|
||||||
units := voices[0].units[:]
|
units := voices[0].units[:]
|
||||||
for voicesRemaining > 0 {
|
for voicesRemaining > 0 {
|
||||||
@ -170,7 +170,7 @@ func (s *GoSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, t
|
|||||||
voices = voices[1:]
|
voices = voices[1:]
|
||||||
units = voices[0].units[:]
|
units = voices[0].units[:]
|
||||||
}
|
}
|
||||||
if mask := uint32(1) << uint32(voicesRemaining); s.bytePatch.PolyphonyBitmask&mask == mask {
|
if mask := uint32(1) << uint32(voicesRemaining); s.bytecode.PolyphonyBitmask&mask == mask {
|
||||||
commands, values = commandInstr, valuesInstr
|
commands, values = commandInstr, valuesInstr
|
||||||
} else {
|
} else {
|
||||||
commandInstr, valuesInstr = commands, values
|
commandInstr, valuesInstr = commands, values
|
||||||
@ -461,7 +461,7 @@ func (s *GoSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, t
|
|||||||
phase := *statevar
|
phase := *statevar
|
||||||
phase += params[2]
|
phase += params[2]
|
||||||
sampleno := valuesAtTransform[3] // reuse color as the sample number
|
sampleno := valuesAtTransform[3] // reuse color as the sample number
|
||||||
sampleoffset := s.bytePatch.SampleOffsets[sampleno]
|
sampleoffset := s.bytecode.SampleOffsets[sampleno]
|
||||||
sampleindex := int(phase*84.28074964676522 + 0.5)
|
sampleindex := int(phase*84.28074964676522 + 0.5)
|
||||||
loopstart := int(sampleoffset.LoopStart)
|
loopstart := int(sampleoffset.LoopStart)
|
||||||
if sampleindex >= loopstart {
|
if sampleindex >= loopstart {
|
||||||
@ -531,7 +531,7 @@ func (s *GoSynth) Render(buffer sointu.AudioBuffer, maxtime int) (samples int, t
|
|||||||
output := params[1] * signal // dry output
|
output := params[1] * signal // dry output
|
||||||
for j := byte(0); j < count; j += 2 {
|
for j := byte(0); j < count; j += 2 {
|
||||||
d, delaylines = &delaylines[0], delaylines[1:]
|
d, delaylines = &delaylines[0], delaylines[1:]
|
||||||
delay := float32(s.bytePatch.DelayTimes[index]) + unit.ports[4]*32767
|
delay := float32(s.bytecode.DelayTimes[index]) + unit.ports[4]*32767
|
||||||
if count&1 == 0 {
|
if count&1 == 0 {
|
||||||
delay /= float32(math.Exp2(float64(voice.note) * 0.083333333333))
|
delay /= float32(math.Exp2(float64(voice.note) * 0.083333333333))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user