mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-23 07:24:47 -04:00
feat(asm&go4k): Preprocess asm code using go text/template
The preprocessing is done sointu-cli and (almost) nothing is done by the NASM preprocessor anymore (some .strucs are still there. Now, sointu-cli loads the .yml song, defines bunch of macros (go functions / variables) and passes the struct to text/template parses. This a lot more powerful way to generate .asm code than trying to fight with the nasm preprocessor. At the moment, tests pass but the repository is a bit of monster, as the library is still compiled using the old approach. Go should generate the library also from the templates.
This commit is contained in:
@ -9,6 +9,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
@ -24,6 +25,7 @@ func main() {
|
||||
help := flag.Bool("h", false, "Show help.")
|
||||
play := flag.Bool("p", false, "Play the input songs.")
|
||||
asmOut := flag.Bool("a", false, "Output the song as .asm file, to standard output unless otherwise specified.")
|
||||
tmplDir := flag.String("t", "", "Output the song as by parsing the templates in directory, to standard output unless otherwise specified.")
|
||||
jsonOut := flag.Bool("j", false, "Output the song as .json file, to standard output unless otherwise specified.")
|
||||
yamlOut := flag.Bool("y", false, "Output the song as .yml file, to standard output unless otherwise specified.")
|
||||
headerOut := flag.Bool("c", false, "Output .h C header file, to standard output unless otherwise specified.")
|
||||
@ -31,13 +33,15 @@ func main() {
|
||||
rawOut := flag.Bool("r", false, "Output the rendered song as .raw stereo float32 buffer, to standard output unless otherwise specified.")
|
||||
directory := flag.String("d", "", "Directory where to output all files. The directory and its parents are created if needed. By default, everything is placed in the same directory where the original song file is.")
|
||||
hold := flag.Int("o", -1, "New value to be used as the hold value")
|
||||
targetArch := flag.String("arch", runtime.GOARCH, "Target architecture. Defaults to OS architecture. Possible values: 386, amd64")
|
||||
targetOs := flag.String("os", runtime.GOOS, "Target OS. Defaults to current OS. Possible values: windows, darwin, linux (anything else is assumed linuxy)")
|
||||
flag.Usage = printUsage
|
||||
flag.Parse()
|
||||
if flag.NArg() == 0 || *help {
|
||||
flag.Usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
if !*asmOut && !*jsonOut && !*rawOut && !*headerOut && !*play && !*yamlOut {
|
||||
if !*asmOut && !*jsonOut && !*rawOut && !*headerOut && !*play && !*yamlOut && *tmplDir == "" {
|
||||
*play = true // if the user gives nothing to output, then the default behaviour is just to play the file
|
||||
}
|
||||
needsRendering := *play || *exactLength || *rawOut
|
||||
@ -129,7 +133,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
if *asmOut {
|
||||
asmCode, err := go4k.FormatAsm(&song)
|
||||
asmCode, err := go4k.Compile(&song, *targetArch, *targetOs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not format the song as asm file: %v", err)
|
||||
}
|
||||
|
364
go4k/go4k.go
364
go4k/go4k.go
@ -79,6 +79,26 @@ func Render(synth Synth, buffer []float32) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Patch) Encode() ([]string, []byte, []byte) {
|
||||
var code []byte
|
||||
var values []byte
|
||||
var jumpTable []string
|
||||
assignedIds := map[string]byte{}
|
||||
for _, instr := range p.Instruments {
|
||||
for _, unit := range instr.Units {
|
||||
if _, ok := assignedIds[unit.Type]; !ok {
|
||||
jumpTable = append(jumpTable, unit.Type)
|
||||
assignedIds[unit.Type] = byte(len(jumpTable) * 2)
|
||||
}
|
||||
stereo, unitValues := Encode(unit)
|
||||
code = append(code, stereo+assignedIds[unit.Type])
|
||||
values = append(values, unitValues...)
|
||||
}
|
||||
code = append(code, 0)
|
||||
}
|
||||
return jumpTable, code, values
|
||||
}
|
||||
|
||||
// UnitParameter documents one parameter that an unit takes
|
||||
type UnitParameter struct {
|
||||
Name string // thould be found with this name in the Unit.Parameters map
|
||||
@ -88,185 +108,175 @@ type UnitParameter struct {
|
||||
CanModulate bool // if this parameter can be modulated i.e. has a port number in "send" unit
|
||||
}
|
||||
|
||||
// UnitType documents the supported behaviour of one type of unit (oscillator, envelope etc.)
|
||||
type UnitType struct {
|
||||
Name string
|
||||
Parameters []UnitParameter
|
||||
func Encode(unit Unit) (byte, []byte) {
|
||||
var values []byte
|
||||
for _, v := range UnitTypes[unit.Type] {
|
||||
if v.CanSet && v.CanModulate {
|
||||
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 Sine:
|
||||
flags = 0x40
|
||||
case Trisaw:
|
||||
flags = 0x20
|
||||
case Pulse:
|
||||
flags = 0x10
|
||||
case Gate:
|
||||
flags = 0x04
|
||||
case 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(unit.Parameters["stereo"]), values
|
||||
}
|
||||
|
||||
// UnitTypes documents all the available unit types and if they support stereo variant
|
||||
// and what parameters they take.
|
||||
var UnitTypes = []UnitType{
|
||||
{
|
||||
Name: "add",
|
||||
Parameters: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}},
|
||||
{
|
||||
Name: "addp",
|
||||
Parameters: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}},
|
||||
{
|
||||
Name: "pop",
|
||||
Parameters: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}},
|
||||
{
|
||||
Name: "loadnote",
|
||||
Parameters: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}},
|
||||
{
|
||||
Name: "mul",
|
||||
Parameters: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}},
|
||||
{
|
||||
Name: "mulp",
|
||||
Parameters: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}},
|
||||
{
|
||||
Name: "push",
|
||||
Parameters: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}},
|
||||
{
|
||||
Name: "xch",
|
||||
Parameters: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}},
|
||||
{
|
||||
Name: "distort",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "drive", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}},
|
||||
{
|
||||
Name: "hold",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "holdfreq", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}},
|
||||
{
|
||||
Name: "crush",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "resolution", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}},
|
||||
{
|
||||
Name: "gain",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}},
|
||||
{
|
||||
Name: "invgain",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "invgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}},
|
||||
{
|
||||
Name: "filter",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "frequency", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "resonance", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "lowpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "bandpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "highpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "negbandpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "neghighpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}},
|
||||
{
|
||||
Name: "clip",
|
||||
Parameters: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}},
|
||||
{
|
||||
Name: "pan",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "panning", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}},
|
||||
{
|
||||
Name: "delay",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "pregain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "dry", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "feedback", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "damp", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "notetracking", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "delay", MinValue: 0, MaxValue: 255, CanSet: true, CanModulate: true},
|
||||
{Name: "count", MinValue: 0, MaxValue: 255, CanSet: true, CanModulate: false},
|
||||
}},
|
||||
{
|
||||
Name: "compressor",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "attack", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "release", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "invgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "threshold", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "ratio", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
}},
|
||||
{
|
||||
Name: "speed",
|
||||
Parameters: []UnitParameter{}},
|
||||
{
|
||||
Name: "out",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}},
|
||||
{
|
||||
Name: "outaux",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "outgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "auxgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
}},
|
||||
{
|
||||
Name: "aux",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "channel", MinValue: 0, MaxValue: 6, CanSet: true, CanModulate: false},
|
||||
}},
|
||||
{
|
||||
Name: "send",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "amount", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "voice", MinValue: 0, MaxValue: 32, CanSet: true, CanModulate: false},
|
||||
{Name: "unit", MinValue: 0, MaxValue: 63, CanSet: true, CanModulate: false},
|
||||
{Name: "port", MinValue: 0, MaxValue: 7, CanSet: true, CanModulate: false},
|
||||
{Name: "sendpop", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
}},
|
||||
{
|
||||
Name: "envelope",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "attack", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "decay", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "sustain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "release", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
}},
|
||||
{
|
||||
Name: "noise",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "shape", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
}},
|
||||
{
|
||||
Name: "oscillator",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "transpose", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "detune", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "phase", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "color", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "shape", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "type", MinValue: int(Sine), MaxValue: int(Sample), CanSet: true, CanModulate: false},
|
||||
{Name: "lfo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "unison", MinValue: 0, MaxValue: 3, CanSet: true, CanModulate: false},
|
||||
}},
|
||||
{
|
||||
Name: "loadval",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "value", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
}},
|
||||
{
|
||||
Name: "receive",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "left", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true},
|
||||
{Name: "right", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true},
|
||||
}},
|
||||
{
|
||||
Name: "in",
|
||||
Parameters: []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "channel", MinValue: 0, MaxValue: 6, CanSet: true, CanModulate: false},
|
||||
}},
|
||||
var UnitTypes = map[string]([]UnitParameter){
|
||||
"add": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}},
|
||||
"addp": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}},
|
||||
"pop": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}},
|
||||
"loadnote": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}},
|
||||
"mul": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}},
|
||||
"mulp": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}},
|
||||
"push": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}},
|
||||
"xch": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}},
|
||||
"distort": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "drive", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
||||
"hold": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "holdfreq", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
||||
"crush": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "resolution", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
||||
"gain": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
||||
"invgain": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "invgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
||||
"filter": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "frequency", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "resonance", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "lowpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "bandpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "highpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "negbandpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "neghighpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}},
|
||||
"clip": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}},
|
||||
"pan": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "panning", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
||||
"delay": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "pregain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "dry", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "feedback", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "damp", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "notetracking", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "delay", MinValue: 0, MaxValue: 255, CanSet: true, CanModulate: false},
|
||||
{Name: "count", MinValue: 0, MaxValue: 255, CanSet: true, CanModulate: false},
|
||||
{Name: "delaytime", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}},
|
||||
"compressor": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "attack", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "release", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "invgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "threshold", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "ratio", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
||||
"speed": []UnitParameter{},
|
||||
"out": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
||||
"outaux": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "outgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "auxgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
||||
"aux": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "channel", MinValue: 0, MaxValue: 6, CanSet: true, CanModulate: false}},
|
||||
"send": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "amount", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "voice", MinValue: 0, MaxValue: 32, CanSet: true, CanModulate: false},
|
||||
{Name: "unit", MinValue: 0, MaxValue: 63, CanSet: true, CanModulate: false},
|
||||
{Name: "port", MinValue: 0, MaxValue: 7, CanSet: true, CanModulate: false},
|
||||
{Name: "sendpop", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}},
|
||||
"envelope": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "attack", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "decay", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "sustain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "release", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
||||
"noise": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "shape", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
||||
"oscillator": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "transpose", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "detune", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "phase", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "color", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "shape", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{Name: "type", MinValue: int(Sine), MaxValue: int(Sample), CanSet: true, CanModulate: false},
|
||||
{Name: "lfo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "unison", MinValue: 0, MaxValue: 3, CanSet: true, CanModulate: false}},
|
||||
"loadval": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "value", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
|
||||
"receive": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "left", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true},
|
||||
{Name: "right", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}},
|
||||
"in": []UnitParameter{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{Name: "channel", MinValue: 0, MaxValue: 6, CanSet: true, CanModulate: false}},
|
||||
}
|
||||
|
627
go4k/macros.go
Normal file
627
go4k/macros.go
Normal file
@ -0,0 +1,627 @@
|
||||
package go4k
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
)
|
||||
|
||||
type OplistEntry struct {
|
||||
Type string
|
||||
NumParams int
|
||||
}
|
||||
|
||||
type Macros struct {
|
||||
Opcodes []OplistEntry
|
||||
Polyphony bool
|
||||
MultivoiceTracks bool
|
||||
PolyphonyBitmask int
|
||||
Stacklocs []string
|
||||
Output16Bit bool
|
||||
Clip bool
|
||||
Amd64 bool
|
||||
OS string
|
||||
DisableSections bool
|
||||
Sine int // TODO: how can we elegantly access global constants in template, without wrapping each one by one
|
||||
Trisaw int
|
||||
Pulse int
|
||||
Gate int
|
||||
Sample int
|
||||
usesFloatConst map[float32]bool
|
||||
usesIntConst map[int]bool
|
||||
floatConsts []float32
|
||||
intConsts []int
|
||||
calls map[string]bool
|
||||
stereo map[string]bool
|
||||
mono map[string]bool
|
||||
ops map[string]bool
|
||||
stackframes map[string][]string
|
||||
unitInputMap map[string](map[string]int)
|
||||
}
|
||||
|
||||
type PlayerMacros struct {
|
||||
Song *Song
|
||||
VoiceTrackBitmask int
|
||||
JumpTable []string
|
||||
Code []byte
|
||||
Values []byte
|
||||
Macros
|
||||
}
|
||||
|
||||
func NewPlayerMacros(song *Song, targetArch string, targetOS string) *PlayerMacros {
|
||||
unitInputMap := map[string](map[string]int){}
|
||||
for k, v := range UnitTypes {
|
||||
inputMap := map[string]int{}
|
||||
inputCount := 0
|
||||
for _, t := range v {
|
||||
if t.CanModulate {
|
||||
inputMap[t.Name] = inputCount
|
||||
inputCount++
|
||||
}
|
||||
}
|
||||
unitInputMap[k] = inputMap
|
||||
}
|
||||
jumpTable, code, values := song.Patch.Encode()
|
||||
amd64 := targetArch == "amd64"
|
||||
p := &PlayerMacros{
|
||||
Song: song,
|
||||
JumpTable: jumpTable,
|
||||
Code: code,
|
||||
Values: values,
|
||||
Macros: Macros{
|
||||
mono: map[string]bool{},
|
||||
stereo: map[string]bool{},
|
||||
calls: map[string]bool{},
|
||||
ops: map[string]bool{},
|
||||
usesFloatConst: map[float32]bool{},
|
||||
usesIntConst: map[int]bool{},
|
||||
stackframes: map[string][]string{},
|
||||
unitInputMap: unitInputMap,
|
||||
Amd64: amd64,
|
||||
OS: targetOS,
|
||||
Sine: Sine,
|
||||
Trisaw: Trisaw,
|
||||
Pulse: Pulse,
|
||||
Gate: Gate,
|
||||
Sample: Sample,
|
||||
}}
|
||||
for _, track := range song.Tracks {
|
||||
if track.NumVoices > 1 {
|
||||
p.MultivoiceTracks = true
|
||||
}
|
||||
}
|
||||
trackVoiceNumber := 0
|
||||
for _, t := range song.Tracks {
|
||||
for b := 0; b < t.NumVoices-1; b++ {
|
||||
p.VoiceTrackBitmask += 1 << trackVoiceNumber
|
||||
trackVoiceNumber++
|
||||
}
|
||||
trackVoiceNumber++ // set all bits except last one
|
||||
}
|
||||
totalVoices := 0
|
||||
for _, instr := range song.Patch.Instruments {
|
||||
if instr.NumVoices > 1 {
|
||||
p.Polyphony = true
|
||||
}
|
||||
for _, unit := range instr.Units {
|
||||
if !p.ops[unit.Type] {
|
||||
p.ops[unit.Type] = true
|
||||
numParams := 0
|
||||
for _, v := range UnitTypes[unit.Type] {
|
||||
if v.CanSet && v.CanModulate {
|
||||
numParams++
|
||||
}
|
||||
}
|
||||
p.Opcodes = append(p.Opcodes, OplistEntry{
|
||||
Type: unit.Type,
|
||||
NumParams: numParams,
|
||||
})
|
||||
}
|
||||
if unit.Parameters["stereo"] == 1 {
|
||||
p.stereo[unit.Type] = true
|
||||
} else {
|
||||
p.mono[unit.Type] = true
|
||||
}
|
||||
}
|
||||
totalVoices += instr.NumVoices
|
||||
for k := 0; k < instr.NumVoices-1; k++ {
|
||||
p.PolyphonyBitmask = (p.PolyphonyBitmask << 1) + 1
|
||||
}
|
||||
p.PolyphonyBitmask <<= 1
|
||||
}
|
||||
p.Output16Bit = song.Output16Bit
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Macros) Opcode(t string) bool {
|
||||
return p.ops[t]
|
||||
}
|
||||
|
||||
func (p *Macros) Stereo(t string) bool {
|
||||
return p.stereo[t]
|
||||
}
|
||||
|
||||
func (p *Macros) Mono(t string) bool {
|
||||
return p.mono[t]
|
||||
}
|
||||
|
||||
func (p *Macros) StereoAndMono(t string) bool {
|
||||
return p.stereo[t] && p.mono[t]
|
||||
}
|
||||
|
||||
// Macros and functions to accumulate constants automagically
|
||||
|
||||
func (p *Macros) Float(value float32) string {
|
||||
if _, ok := p.usesFloatConst[value]; !ok {
|
||||
p.usesFloatConst[value] = true
|
||||
p.floatConsts = append(p.floatConsts, value)
|
||||
}
|
||||
return nameForFloat(value)
|
||||
}
|
||||
|
||||
func (p *Macros) Int(value int) string {
|
||||
if _, ok := p.usesIntConst[value]; !ok {
|
||||
p.usesIntConst[value] = true
|
||||
p.intConsts = append(p.intConsts, value)
|
||||
}
|
||||
return nameForInt(value)
|
||||
}
|
||||
|
||||
func (p *Macros) Constants() string {
|
||||
var b strings.Builder
|
||||
for _, v := range p.floatConsts {
|
||||
fmt.Fprintf(&b, "%-23s dd 0x%x\n", nameForFloat(v), math.Float32bits(v))
|
||||
}
|
||||
for _, v := range p.intConsts {
|
||||
fmt.Fprintf(&b, "%-23s dd 0x%x\n", nameForInt(v), v)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func nameForFloat(value float32) string {
|
||||
s := fmt.Sprintf("%#g", value)
|
||||
s = strings.Replace(s, ".", "_", 1)
|
||||
s = strings.Replace(s, "-", "m", 1)
|
||||
s = strings.Replace(s, "+", "p", 1)
|
||||
return "FCONST_" + s
|
||||
}
|
||||
|
||||
func nameForInt(value int) string {
|
||||
return "ICONST_" + fmt.Sprintf("%d", value)
|
||||
}
|
||||
|
||||
func (p *Macros) PTRSIZE() int {
|
||||
if p.Amd64 {
|
||||
return 8
|
||||
}
|
||||
return 4
|
||||
}
|
||||
|
||||
func (p *Macros) DPTR() string {
|
||||
if p.Amd64 {
|
||||
return "dq"
|
||||
}
|
||||
return "dd"
|
||||
}
|
||||
|
||||
func (p *Macros) PTRWORD() string {
|
||||
if p.Amd64 {
|
||||
return "qword"
|
||||
}
|
||||
return "dword"
|
||||
}
|
||||
|
||||
func (p *Macros) AX() string {
|
||||
if p.Amd64 {
|
||||
return "rax"
|
||||
}
|
||||
return "eax"
|
||||
}
|
||||
|
||||
func (p *Macros) BX() string {
|
||||
if p.Amd64 {
|
||||
return "rbx"
|
||||
}
|
||||
return "ebx"
|
||||
}
|
||||
|
||||
func (p *Macros) CX() string {
|
||||
if p.Amd64 {
|
||||
return "rcx"
|
||||
}
|
||||
return "ecx"
|
||||
}
|
||||
|
||||
func (p *Macros) DX() string {
|
||||
if p.Amd64 {
|
||||
return "rdx"
|
||||
}
|
||||
return "edx"
|
||||
}
|
||||
|
||||
func (p *Macros) SI() string {
|
||||
if p.Amd64 {
|
||||
return "rsi"
|
||||
}
|
||||
return "esi"
|
||||
}
|
||||
|
||||
func (p *Macros) DI() string {
|
||||
if p.Amd64 {
|
||||
return "rdi"
|
||||
}
|
||||
return "edi"
|
||||
}
|
||||
|
||||
func (p *Macros) SP() string {
|
||||
if p.Amd64 {
|
||||
return "rsp"
|
||||
}
|
||||
return "esp"
|
||||
}
|
||||
|
||||
func (p *Macros) BP() string {
|
||||
if p.Amd64 {
|
||||
return "rbp"
|
||||
}
|
||||
return "ebp"
|
||||
}
|
||||
|
||||
func (p *Macros) WRK() string {
|
||||
return p.BP()
|
||||
}
|
||||
|
||||
func (p *Macros) VAL() string {
|
||||
return p.SI()
|
||||
}
|
||||
|
||||
func (p *Macros) COM() string {
|
||||
return p.BX()
|
||||
}
|
||||
|
||||
func (p *Macros) INP() string {
|
||||
return p.DX()
|
||||
}
|
||||
|
||||
func (p *Macros) SaveStack(scope string) string {
|
||||
p.stackframes[scope] = p.Stacklocs
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *Macros) Call(funcname string) (string, error) {
|
||||
p.calls[funcname] = true
|
||||
var s = make([]string, len(p.Stacklocs))
|
||||
copy(s, p.Stacklocs)
|
||||
p.stackframes[funcname] = s
|
||||
return "call " + funcname, nil
|
||||
}
|
||||
|
||||
func (p *Macros) TailCall(funcname string) (string, error) {
|
||||
p.calls[funcname] = true
|
||||
p.stackframes[funcname] = p.Stacklocs
|
||||
return "jmp " + funcname, nil
|
||||
}
|
||||
|
||||
func (p *Macros) SectText(name string) string {
|
||||
if p.OS == "windows" {
|
||||
if p.DisableSections {
|
||||
return "section .code align=1"
|
||||
}
|
||||
return fmt.Sprintf("section .%v code align=1", name)
|
||||
} else if p.OS == "darwin" {
|
||||
return "section .text align=1"
|
||||
} else {
|
||||
if p.DisableSections {
|
||||
return "section .text. progbits alloc exec nowrite align=1"
|
||||
}
|
||||
return fmt.Sprintf("section .text.%v progbits alloc exec nowrite align=1", name)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Macros) SectData(name string) string {
|
||||
if p.OS == "windows" || p.OS == "darwin" {
|
||||
if p.OS == "windows" && !p.DisableSections {
|
||||
return fmt.Sprintf("section .%v data align=1", name)
|
||||
}
|
||||
return "section .data align=1"
|
||||
} else {
|
||||
if !p.DisableSections {
|
||||
return fmt.Sprintf("section .data.%v progbits alloc noexec write align=1", name)
|
||||
}
|
||||
return "section .data. progbits alloc exec nowrite align=1"
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Macros) SectBss(name string) string {
|
||||
if p.OS == "windows" || p.OS == "darwin" {
|
||||
if p.OS == "windows" && !p.DisableSections {
|
||||
return fmt.Sprintf("section .%v bss align=256", name)
|
||||
}
|
||||
return "section .bss align=256"
|
||||
} else {
|
||||
if !p.DisableSections {
|
||||
return fmt.Sprintf("section .bss.%v progbits alloc noexec write align=256", name)
|
||||
}
|
||||
return "section .bss. progbits alloc exec nowrite align=256"
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Macros) Data(label string) string {
|
||||
return fmt.Sprintf("%v\n%v:", p.SectData(label), label)
|
||||
}
|
||||
|
||||
func (p *Macros) Func(funcname string, scope ...string) (string, error) {
|
||||
scopeName := funcname
|
||||
if len(scope) > 1 {
|
||||
return "", fmt.Errorf(`Func macro "%v" can take only one additional scope parameter, "%v" were given`, funcname, scope)
|
||||
} else if len(scope) > 0 {
|
||||
scopeName = scope[0]
|
||||
}
|
||||
p.Stacklocs = append(p.stackframes[scopeName], "retaddr_"+funcname)
|
||||
return fmt.Sprintf("%v\n%v:", p.SectText(funcname), funcname), nil
|
||||
}
|
||||
|
||||
func (p *Macros) HasCall(funcname string) bool {
|
||||
return p.calls[funcname]
|
||||
}
|
||||
|
||||
func (p *Macros) Push(value string, name string) string {
|
||||
p.Stacklocs = append(p.Stacklocs, name)
|
||||
return fmt.Sprintf("push %v ; Stack: %v ", value, p.FmtStack())
|
||||
}
|
||||
|
||||
func (p *Macros) PushRegs(params ...string) string {
|
||||
if p.Amd64 {
|
||||
var b strings.Builder
|
||||
for i := 0; i < len(params); i = i + 2 {
|
||||
b.WriteRune('\n')
|
||||
b.WriteString(p.Push(params[i], params[i+1]))
|
||||
}
|
||||
return b.String()
|
||||
} else {
|
||||
var pushadOrder = [...]string{"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi"}
|
||||
for _, name := range pushadOrder {
|
||||
for j := 0; j < len(params); j = j + 2 {
|
||||
if params[j] == name {
|
||||
name = params[j+1]
|
||||
}
|
||||
}
|
||||
p.Stacklocs = append(p.Stacklocs, name)
|
||||
}
|
||||
return fmt.Sprintf("\npushad ; Stack: %v", p.FmtStack())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Macros) PopRegs(params ...string) string {
|
||||
if p.Amd64 {
|
||||
var b strings.Builder
|
||||
for i := len(params) - 1; i >= 0; i-- {
|
||||
b.WriteRune('\n')
|
||||
b.WriteString(p.Pop(params[i]))
|
||||
}
|
||||
return b.String()
|
||||
} else {
|
||||
var regs = [...]string{"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi"}
|
||||
var b strings.Builder
|
||||
for i, name := range p.Stacklocs[len(p.Stacklocs)-8:] {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(regs[i])
|
||||
if regs[i] != name {
|
||||
b.WriteString(" = ")
|
||||
b.WriteString(name)
|
||||
}
|
||||
}
|
||||
p.Stacklocs = p.Stacklocs[:len(p.Stacklocs)-8]
|
||||
return fmt.Sprintf("\npopad ; Popped: %v. Stack: %v", b.String(), p.FmtStack())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Macros) Pop(register string) string {
|
||||
last := p.Stacklocs[len(p.Stacklocs)-1]
|
||||
p.Stacklocs = p.Stacklocs[:len(p.Stacklocs)-1]
|
||||
return fmt.Sprintf("pop %v ; %v = %v, Stack: %v ", register, register, last, p.FmtStack())
|
||||
}
|
||||
|
||||
func (p *Macros) Stack(name string) (string, error) {
|
||||
for i, k := range p.Stacklocs {
|
||||
if k == name {
|
||||
pos := len(p.Stacklocs) - i - 1
|
||||
if p.Amd64 {
|
||||
pos = pos * 8
|
||||
} else {
|
||||
pos = pos * 4
|
||||
}
|
||||
if pos != 0 {
|
||||
return fmt.Sprintf("%v + %v", p.SP(), pos), nil
|
||||
}
|
||||
return p.SP(), nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("unknown symbol %v", name)
|
||||
}
|
||||
|
||||
func (p *Macros) FmtStack() string {
|
||||
var b strings.Builder
|
||||
last := len(p.Stacklocs) - 1
|
||||
for i := range p.Stacklocs {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(p.Stacklocs[last-i])
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (p *Macros) ExportFunc(name string, params ...string) string {
|
||||
if !p.Amd64 {
|
||||
p.Stacklocs = append(params, "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)
|
||||
}
|
||||
}
|
||||
if p.OS == "darwin" {
|
||||
return fmt.Sprintf("%[1]v\nglobal _%[2]v\n_%[2]v:", p.SectText(name), name)
|
||||
}
|
||||
return fmt.Sprintf("%[1]v\nglobal %[2]v\n%[2]v:", p.SectText(name), name)
|
||||
}
|
||||
|
||||
func (p *Macros) Count(count int) []int {
|
||||
s := make([]int, count)
|
||||
for i := range s {
|
||||
s[i] = i
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *Macros) Sub(a int, b int) int {
|
||||
return a - b
|
||||
}
|
||||
|
||||
func (p *Macros) Input(unit string, port string) (string, error) {
|
||||
umap, ok := p.unitInputMap[unit]
|
||||
if !ok {
|
||||
return "", fmt.Errorf(`trying to find input for unknown unit "%v"`, unit)
|
||||
}
|
||||
i, ok := umap[port]
|
||||
if !ok {
|
||||
return "", fmt.Errorf(`trying to find input for unknown input "%v" for unit "%v"`, port, unit)
|
||||
}
|
||||
if i != 0 {
|
||||
return fmt.Sprintf("%v + %v", p.INP(), i*4), nil
|
||||
}
|
||||
return p.INP(), nil
|
||||
}
|
||||
|
||||
func (p *Macros) InputNumber(unit string, port string) (string, error) {
|
||||
umap, ok := p.unitInputMap[unit]
|
||||
if !ok {
|
||||
return "", fmt.Errorf(`trying to find InputNumber for unknown unit "%v"`, unit)
|
||||
}
|
||||
i, ok := umap[port]
|
||||
if !ok {
|
||||
return "", fmt.Errorf(`trying to find InputNumber for unknown input "%v" for unit "%v"`, port, unit)
|
||||
}
|
||||
return fmt.Sprintf("%v", i), nil
|
||||
}
|
||||
|
||||
func (p *Macros) Modulation(unit string, port string) (string, error) {
|
||||
umap, ok := p.unitInputMap[unit]
|
||||
if !ok {
|
||||
return "", fmt.Errorf(`trying to find input for unknown unit "%v"`, unit)
|
||||
}
|
||||
i, ok := umap[port]
|
||||
if !ok {
|
||||
return "", fmt.Errorf(`trying to find input for unknown input "%v" for unit "%v"`, port, unit)
|
||||
}
|
||||
return fmt.Sprintf("%v + %v", p.WRK(), i*4+32), nil
|
||||
}
|
||||
|
||||
func (p *Macros) Prepare(value string, regs ...string) (string, error) {
|
||||
if p.Amd64 {
|
||||
if len(regs) > 1 {
|
||||
return "", fmt.Errorf("macro Prepare cannot accept more than one register parameter")
|
||||
} else if len(regs) > 0 {
|
||||
return fmt.Sprintf("\nmov r9, qword %v\nlea r9, [r9 + %v]", value, regs[0]), nil
|
||||
}
|
||||
return fmt.Sprintf("\nmov r9, qword %v", value), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (p *Macros) Use(value string, regs ...string) (string, error) {
|
||||
if p.Amd64 {
|
||||
return "r9", nil
|
||||
}
|
||||
if len(regs) > 1 {
|
||||
return "", fmt.Errorf("macro Use cannot accept more than one register parameter")
|
||||
} else if len(regs) > 0 {
|
||||
return value + " + " + regs[0], nil
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (p *PlayerMacros) NumDelayLines() string {
|
||||
total := 0
|
||||
for _, instr := range p.Song.Patch.Instruments {
|
||||
for _, unit := range instr.Units {
|
||||
if unit.Type == "delay" {
|
||||
total += unit.Parameters["count"] * (1 + unit.Parameters["stereo"])
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%v", total)
|
||||
}
|
||||
|
||||
func (p *PlayerMacros) UsesDelayModulation() (bool, error) {
|
||||
for i, instrument := range p.Song.Patch.Instruments {
|
||||
for j, unit := range instrument.Units {
|
||||
if unit.Type == "send" {
|
||||
targetInstrument := i
|
||||
if unit.Parameters["voice"] > 0 {
|
||||
v, err := p.Song.Patch.InstrumentForVoice(unit.Parameters["voice"] - 1)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("INSTRUMENT #%v / SEND #%v targets voice %v, which does not exist", i, j, unit.Parameters["voice"])
|
||||
}
|
||||
targetInstrument = v
|
||||
}
|
||||
if unit.Parameters["unit"] < 0 || unit.Parameters["unit"] >= len(p.Song.Patch.Instruments[targetInstrument].Units) {
|
||||
return false, fmt.Errorf("INSTRUMENT #%v / SEND #%v target unit %v out of range", i, j, unit.Parameters["unit"])
|
||||
}
|
||||
if p.Song.Patch.Instruments[targetInstrument].Units[unit.Parameters["unit"]].Type == "delay" && unit.Parameters["port"] == 4 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (p *PlayerMacros) HasParamValue(unitType string, paramName string, value int) bool {
|
||||
for _, instr := range p.Song.Patch.Instruments {
|
||||
for _, unit := range instr.Units {
|
||||
if unit.Type == unitType {
|
||||
if unit.Parameters[paramName] == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PlayerMacros) HasParamValueOtherThan(unitType string, paramName string, value int) bool {
|
||||
for _, instr := range p.Song.Patch.Instruments {
|
||||
for _, unit := range instr.Units {
|
||||
if unit.Type == unitType {
|
||||
if unit.Parameters[paramName] != value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func Compile(song *Song, targetArch string, targetOs string) (string, error) {
|
||||
_, myname, _, _ := runtime.Caller(0)
|
||||
templateDir := filepath.Join(path.Dir(myname), "..", "templates", "*.asm")
|
||||
tmpl, err := template.New("base").Funcs(sprig.TxtFuncMap()).ParseGlob(templateDir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(`could not create template based on dir "%v": %v`, templateDir, err)
|
||||
}
|
||||
b := bytes.NewBufferString("")
|
||||
err = tmpl.ExecuteTemplate(b, "player.asm", NewPlayerMacros(song, targetArch, targetOs))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(`could not execute template "player.asm": %v`, err)
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
Reference in New Issue
Block a user