sointu/compiler/encoded_patch.go

145 lines
4.4 KiB
Go

package compiler
import (
"errors"
"fmt"
"github.com/vsariola/sointu"
)
type EncodedPatch struct {
Commands []byte
Values []byte
DelayTimes []uint16
SampleOffsets []SampleOffset
PolyphonyBitmask uint32
NumVoices uint32
}
type SampleOffset struct {
Start uint32
LoopStart uint16
LoopLength uint16
}
func Encode(patch *sointu.Patch, featureSet FeatureSet) (*EncodedPatch, error) {
var c EncodedPatch
sampleOffsetMap := map[SampleOffset]int{}
for _, instr := range patch.Instruments {
if len(instr.Units) > 63 {
return nil, errors.New("An instrument can have a maximum of 63 units")
}
if instr.NumVoices < 1 {
return nil, errors.New("Each instrument must have at least 1 voice")
}
for _, unit := range instr.Units {
if unit.Type == "oscillator" && unit.Parameters["type"] == 4 {
s := SampleOffset{Start: uint32(unit.Parameters["start"]), LoopStart: uint16(unit.Parameters["loopstart"]), LoopLength: uint16(unit.Parameters["looplength"])}
index, ok := sampleOffsetMap[s]
if !ok {
index = len(c.SampleOffsets)
sampleOffsetMap[s] = index
c.SampleOffsets = append(c.SampleOffsets, s)
}
unit.Parameters["color"] = index
}
if unit.Type == "delay" {
unit.Parameters["delay"] = len(c.DelayTimes)
if unit.Parameters["stereo"] == 1 {
unit.Parameters["count"] = len(unit.VarArgs) / 2
} else {
unit.Parameters["count"] = len(unit.VarArgs)
}
for _, v := range unit.VarArgs {
c.DelayTimes = append(c.DelayTimes, uint16(v))
}
}
command, values, err := EncodeUnit(unit, featureSet)
if err != nil {
return nil, fmt.Errorf(`encoding unit failed: %v`, err)
}
c.Commands = append(c.Commands, command)
c.Values = append(c.Values, values...)
}
c.Commands = append(c.Commands, byte(0)) // advance
c.NumVoices += uint32(instr.NumVoices)
for k := 0; k < instr.NumVoices-1; k++ {
c.PolyphonyBitmask = (c.PolyphonyBitmask << 1) + 1
}
c.PolyphonyBitmask <<= 1
}
if c.NumVoices > 32 {
return nil, fmt.Errorf("Sointu does not support more than 32 concurrent voices; patch uses %v", c.NumVoices)
}
return &c, nil
}
func EncodeUnit(unit sointu.Unit, featureSet FeatureSet) (byte, []byte, error) {
opcode, ok := featureSet.Opcode(unit.Type)
if !ok {
return 0, nil, fmt.Errorf(`the targeted virtual machine is not configured to support unit type "%v"`, unit.Type)
}
var values []byte
for _, v := range sointu.UnitTypes[unit.Type] {
if v.CanModulate && v.CanSet {
values = append(values, byte(unit.Parameters[v.Name]))
}
}
if unit.Type == "aux" {
values = append(values, byte(unit.Parameters["channel"]))
} else if unit.Type == "in" {
values = append(values, byte(unit.Parameters["channel"]))
} else if unit.Type == "oscillator" {
flags := 0
switch unit.Parameters["type"] {
case sointu.Sine:
flags = 0x40
case sointu.Trisaw:
flags = 0x20
case sointu.Pulse:
flags = 0x10
case sointu.Gate:
flags = 0x04
case sointu.Sample:
flags = 0x80
}
if unit.Parameters["lfo"] == 1 {
flags += 0x08
}
flags += unit.Parameters["unison"]
values = append(values, byte(flags))
} else if unit.Type == "filter" {
flags := 0
if unit.Parameters["lowpass"] == 1 {
flags += 0x40
}
if unit.Parameters["bandpass"] == 1 {
flags += 0x20
}
if unit.Parameters["highpass"] == 1 {
flags += 0x10
}
if unit.Parameters["negbandpass"] == 1 {
flags += 0x08
}
if unit.Parameters["neghighpass"] == 1 {
flags += 0x04
}
values = append(values, byte(flags))
} else if unit.Type == "send" {
address := ((unit.Parameters["unit"] + 1) << 4) + unit.Parameters["port"] // each unit is 16 dwords, 8 workspace followed by 8 ports. +1 is for skipping the note/release/inputs
if unit.Parameters["voice"] > 0 {
address += 0x8000 + 16 + (unit.Parameters["voice"]-1)*1024 // global send, +16 is for skipping the out/aux ports
}
if unit.Parameters["sendpop"] == 1 {
address += 0x8
}
values = append(values, byte(address&255), byte(address>>8))
} else if unit.Type == "delay" {
countTrack := (unit.Parameters["count"] << 1) - 1 + unit.Parameters["notetracking"] // 1 means no note tracking and 1 delay, 2 means notetracking with 1 delay, 3 means no note tracking and 2 delays etc.
values = append(values, byte(unit.Parameters["delay"]), byte(countTrack))
}
return byte(opcode + unit.Parameters["stereo"]), values, nil
}