mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -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:
parent
92c8b70fd2
commit
2ad61ff6b2
7
go.mod
7
go.mod
@ -4,6 +4,13 @@ go 1.15
|
||||
|
||||
require (
|
||||
gioui.org v0.0.0-20201106195654-dbc0796d0207
|
||||
github.com/Masterminds/goutils v1.1.0 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||
github.com/google/uuid v1.1.2 // indirect
|
||||
github.com/hajimehoshi/oto v0.6.6
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.11 // indirect
|
||||
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
)
|
||||
|
18
go.sum
18
go.sum
@ -2,10 +2,27 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
|
||||
gioui.org v0.0.0-20201106195654-dbc0796d0207 h1:tB+woXgNaCiudnpU7QmRD7J92YrBz7R4NAGgEjOnEzQ=
|
||||
gioui.org v0.0.0-20201106195654-dbc0796d0207/go.mod h1:Y+uS7hHMvku1Q+ooaoq6fYD5B2LGoT8JtFgvmYmRzTw=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
|
||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hajimehoshi/oto v0.6.6 h1:HYSZ8cYZqOL4iHugvbcfhNN2smiSOsBMaoSBi4nnWcw=
|
||||
github.com/hajimehoshi/oto v0.6.6/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
|
||||
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 h1:n9HxLrNxWWtEb1cA950nuEEj3QnKbtsCJ6KjcgisNUs=
|
||||
@ -31,5 +48,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -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
|
||||
}
|
205
templates/arithmetic.asm
Normal file
205
templates/arithmetic.asm
Normal file
@ -0,0 +1,205 @@
|
||||
{{- if .Opcode "pop"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; POP opcode: remove (discard) the topmost signal from the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
{{- if .Mono "pop" -}}
|
||||
; Mono: a -> (empty)
|
||||
{{- end}}
|
||||
{{- if .Stereo "pop" -}}
|
||||
; Stereo: a b -> (empty)
|
||||
{{- end}}
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_pop" "Opcode"}}
|
||||
{{- if .StereoAndMono "pop"}}
|
||||
jnc su_op_pop_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "pop"}}
|
||||
fstp st0
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "pop"}}
|
||||
su_op_pop_mono:
|
||||
{{- end}}
|
||||
fstp st0
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "add"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; ADD opcode: add the two top most signals on the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
{{- if .Mono "add"}}
|
||||
; Mono: a b -> a+b b
|
||||
{{- end}}
|
||||
{{- if .Stereo "add" -}}
|
||||
; Stereo: a b c d -> a+c b+d c d
|
||||
{{- end}}
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_add" "Opcode"}}
|
||||
{{- if .StereoAndMono "add"}}
|
||||
jnc su_op_add_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "add"}}
|
||||
fadd st0, st2
|
||||
fxch
|
||||
fadd st0, st3
|
||||
fxch
|
||||
ret
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "add"}}
|
||||
su_op_add_mono:
|
||||
{{- end}}
|
||||
{{- if .Mono "add"}}
|
||||
fadd st1
|
||||
{{- end}}
|
||||
{{- if .Mono "add"}}
|
||||
ret
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "addp"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; ADDP opcode: add the two top most signals on the stack and pop
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: a b -> a+b
|
||||
; Stereo: a b c d -> a+c b+d
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_addp" "Opcode"}}
|
||||
{{- if .StereoAndMono "addp"}}
|
||||
jnc su_op_addp_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "addp"}}
|
||||
faddp st2, st0
|
||||
faddp st2, st0
|
||||
ret
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "addp"}}
|
||||
su_op_addp_mono:
|
||||
{{- end}}
|
||||
{{- if (.Mono "addp")}}
|
||||
faddp st1, st0
|
||||
ret
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "loadnote"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; LOADNOTE opcode: load the current note, scaled to [-1,1]
|
||||
;-------------------------------------------------------------------------------
|
||||
{{if (.Mono "loadnote") -}} ; Mono: (empty) -> n, where n is the note{{end}}
|
||||
{{if (.Stereo "loadnote") -}}; Stereo: (empty) -> n n{{end}}
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_loadnote" "Opcode"}}
|
||||
{{- if .StereoAndMono "loadnote"}}
|
||||
jnc su_op_loadnote_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "loadnote"}}
|
||||
call su_op_loadnote_mono
|
||||
su_op_loadnote_mono:
|
||||
{{- end}}
|
||||
fild dword [{{.INP}}-su_voice.inputs+su_voice.note]
|
||||
{{.Prepare (.Float 0.0078125)}}
|
||||
fmul dword [{{.Use (.Float 0.0078125)}}] ; s=n/128.0
|
||||
{{.Prepare (.Float 0.5)}}
|
||||
fsub dword [{{.Use (.Float 0.5)}}] ; s-.5
|
||||
fadd st0, st0 ; 2*s-1
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "mul"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; MUL opcode: multiply the two top most signals on the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: a b -> a*b a
|
||||
; Stereo: a b c d -> a*c b*d c d
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_mul" "Opcode"}}
|
||||
jnc su_op_mul_mono
|
||||
fmul st0, st2
|
||||
fxch
|
||||
fadd st0, st3
|
||||
fxch
|
||||
ret
|
||||
su_op_mul_mono:
|
||||
fmul st1
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "mulp"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; MULP opcode: multiply the two top most signals on the stack and pop
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: a b -> a*b
|
||||
; Stereo: a b c d -> a*c b*d
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_mulp" "Opcode"}}
|
||||
{{- if .StereoAndMono "mulp"}}
|
||||
jnc su_op_mulp_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "mulp"}}
|
||||
fmulp st2, st0
|
||||
fmulp st2, st0
|
||||
ret
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "mulp"}}
|
||||
su_op_mulp_mono:
|
||||
{{- end}}
|
||||
{{- if .Mono "mulp"}}
|
||||
fmulp st1
|
||||
ret
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "push"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; PUSH opcode: push the topmost signal on the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: a -> a a
|
||||
; Stereo: a b -> a b a b
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_push" "Opcode"}}
|
||||
{{- if .StereoAndMono "push"}}
|
||||
jnc su_op_push_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "push"}}
|
||||
fld st1
|
||||
fld st1
|
||||
ret
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "push"}}
|
||||
su_op_push_mono:
|
||||
{{- end}}
|
||||
{{- if .Mono "push"}}
|
||||
fld st0
|
||||
ret
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "xch"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; XCH opcode: exchange the signals on the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: a b -> b a
|
||||
; stereo: a b c d -> c d a b
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_xch" "Opcode"}}
|
||||
{{- if .StereoAndMono "xch"}}
|
||||
jnc su_op_xch_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "xch"}}
|
||||
fxch st0, st2 ; c b a d
|
||||
fxch st0, st1 ; b c a d
|
||||
fxch st0, st3 ; d c a b
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "xch"}}
|
||||
su_op_xch_mono:
|
||||
{{- end}}
|
||||
fxch st0, st1
|
||||
ret
|
||||
{{end}}
|
393
templates/effects.asm
Normal file
393
templates/effects.asm
Normal file
@ -0,0 +1,393 @@
|
||||
{{- if .Opcode "distort"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; DISTORT opcode: apply distortion on the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: x -> x*a/(1-a+(2*a-1)*abs(x)) where x is clamped first
|
||||
; Stereo: l r -> l*a/(1-a+(2*a-1)*abs(l)) r*a/(1-a+(2*a-1)*abs(r))
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_distort" "Opcode"}}
|
||||
{{- if .Stereo "distort" -}}
|
||||
{{.Call "su_effects_stereohelper"}}
|
||||
{{- end}}
|
||||
fld dword [{{.Input "distort" "drive"}}]
|
||||
{{.TailCall "su_waveshaper"}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "hold"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; HOLD opcode: sample and hold the signal, reducing sample rate
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono version: holds the signal at a rate defined by the freq parameter
|
||||
; Stereo version: holds both channels
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_hold" "Opcode"}}
|
||||
{{- if .Stereo "hold"}}
|
||||
{{.Call "su_effects_stereohelper"}}
|
||||
{{- end}}
|
||||
fld dword [{{.Input "hold" "holdfreq"}}] ; f x
|
||||
fmul st0, st0 ; f^2 x
|
||||
fchs ; -f^2 x
|
||||
fadd dword [{{.WRK}}] ; p-f^2 x
|
||||
fst dword [{{.WRK}}] ; p <- p-f^2
|
||||
fldz ; 0 p x
|
||||
fucomip st1 ; p x
|
||||
fstp dword [{{.SP}}-4] ; t=p, x
|
||||
jc short su_op_hold_holding ; if (0 < p) goto holding
|
||||
fld1 ; 1 x
|
||||
fadd dword [{{.SP}}-4] ; 1+t x
|
||||
fstp dword [{{.WRK}}] ; x
|
||||
fst dword [{{.WRK}}+4] ; save holded value
|
||||
ret ; x
|
||||
su_op_hold_holding:
|
||||
fstp st0 ;
|
||||
fld dword [{{.WRK}}+4] ; x
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "crush"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; CRUSH opcode: quantize the signal to finite number of levels
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: x -> e*int(x/e)
|
||||
; Stereo: l r -> e*int(l/e) e*int(r/e)
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_crush" "Opcode"}}
|
||||
{{- if .Stereo "crush"}}
|
||||
{{.Call "su_effects_stereohelper"}}
|
||||
{{- end}}
|
||||
fdiv dword [{{.Input "crush" "resolution"}}]
|
||||
frndint
|
||||
fmul dword [{{.Input "crush" "resolution"}}]
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "gain"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; GAIN opcode: apply gain on the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: x -> x*g
|
||||
; Stereo: l r -> l*g r*g
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_gain" "Opcode"}}
|
||||
{{- if .Stereo "gain"}}
|
||||
fld dword [{{.Input "gain" "gain"}}] ; g l (r)
|
||||
{{- if .Mono "invgain"}}
|
||||
jnc su_op_gain_mono
|
||||
{{- end}}
|
||||
fmul st2, st0 ; g l r/g
|
||||
su_op_gain_mono:
|
||||
fmulp st1, st0 ; l/g (r/)
|
||||
ret
|
||||
{{- else}}
|
||||
fmul dword [{{.Input "gain" "gain"}}]
|
||||
ret
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "invgain"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; INVGAIN opcode: apply inverse gain on the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: x -> x/g
|
||||
; Stereo: l r -> l/g r/g
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_invgain" "Opcode"}}
|
||||
{{- if .Stereo "invgain"}}
|
||||
fld dword [{{.Input "invgain" "invgain"}}] ; g l (r)
|
||||
{{- if .Mono "invgain"}}
|
||||
jnc su_op_invgain_mono
|
||||
{{- end}}
|
||||
fdiv st2, st0 ; g l r/g
|
||||
su_op_invgain_mono:
|
||||
fdivp st1, st0 ; l/g (r/)
|
||||
ret
|
||||
{{- else}}
|
||||
fdiv dword [{{.Input "invgain" "invgain"}}]
|
||||
ret
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "filter"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; FILTER opcode: perform low/high/band-pass/notch etc. filtering on the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: x -> filtered(x)
|
||||
; Stereo: l r -> filtered(l) filtered(r)
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_filter" "Opcode"}}
|
||||
lodsb ; load the flags to al
|
||||
{{- if .Stereo "filter"}}
|
||||
{{.Call "su_effects_stereohelper"}}
|
||||
{{- end}}
|
||||
fld dword [{{.Input "filter" "resonance"}}] ; r x
|
||||
fld dword [{{.Input "filter" "frequency"}}]; f r x
|
||||
fmul st0, st0 ; f2 x (square the input so we never get negative and also have a smoother behaviour in the lower frequencies)
|
||||
fst dword [{{.SP}}-4] ; f2 r x
|
||||
fmul dword [{{.WRK}}+8] ; f2*b r x
|
||||
fadd dword [{{.WRK}}] ; f2*b+l r x
|
||||
fst dword [{{.WRK}}] ; l'=f2*b+l r x
|
||||
fsubp st2, st0 ; r x-l'
|
||||
fmul dword [{{.WRK}}+8] ; r*b x-l'
|
||||
fsubp st1, st0 ; x-l'-r*b
|
||||
fst dword [{{.WRK}}+4] ; h'=x-l'-r*b
|
||||
fmul dword [{{.SP}}-4] ; f2*h'
|
||||
fadd dword [{{.WRK}}+8] ; f2*h'+b
|
||||
fstp dword [{{.WRK}}+8] ; b'=f2*h'+b
|
||||
fldz ; 0
|
||||
{{- if .HasParamValue "filter" "lowpass" 1}}
|
||||
test al, byte 0x40
|
||||
jz short su_op_filter_skiplowpass
|
||||
fadd dword [{{.WRK}}]
|
||||
su_op_filter_skiplowpass:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "filter" "bandpass" 1}}
|
||||
test al, byte 0x20
|
||||
jz short su_op_filter_skipbandpass
|
||||
fadd dword [{{.WRK}}+8]
|
||||
su_op_filter_skipbandpass:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "filter" "highpass" 1}}
|
||||
test al, byte 0x10
|
||||
jz short su_op_filter_skiphighpass
|
||||
fadd dword [{{.WRK}}+4]
|
||||
su_op_filter_skiphighpass:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "filter" "negbandpass" 1}}
|
||||
test al, byte 0x08
|
||||
jz short su_op_filter_skipnegbandpass
|
||||
fsub dword [{{.WRK}}+8]
|
||||
su_op_filter_skipnegbandpass:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "filter" "neghighpass" 1}}
|
||||
test al, byte 0x04
|
||||
jz short su_op_filter_skipneghighpass
|
||||
fsub dword [{{.WRK}}+4]
|
||||
su_op_filter_skipneghighpass:
|
||||
{{- end}}
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "clip"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; CLIP opcode: clips the signal into [-1,1] range
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: x -> min(max(x,-1),1)
|
||||
; Stereo: l r -> min(max(l,-1),1) min(max(r,-1),1)
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_clip" "Opcode"}}
|
||||
{{- if .Stereo "clip"}}
|
||||
{{.Call "su_effects_stereohelper"}}
|
||||
{{- end}}
|
||||
{{.TailCall "su_clip"}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "pan" -}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; PAN opcode: pan the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: s -> s*(1-p) s*p
|
||||
; Stereo: l r -> l*(1-p) r*p
|
||||
;
|
||||
; where p is the panning in [0,1] range
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_pan" "Opcode"}}
|
||||
{{- if .Stereo "pan"}}
|
||||
jc su_op_pan_do ; this time, if this is mono op...
|
||||
fld st0 ; ...we duplicate the mono into stereo first
|
||||
su_op_pan_do:
|
||||
fld dword [{{.Input "pan" "panning"}}] ; p l r
|
||||
fld1 ; 1 p l r
|
||||
fsub st1 ; 1-p p l r
|
||||
fmulp st2 ; p (1-p)*l r
|
||||
fmulp st2 ; (1-p)*l p*r
|
||||
ret
|
||||
{{- else}}
|
||||
fld dword [{{.Input "pan" "panning"}}] ; p s
|
||||
fmul st1 ; p*s s
|
||||
fsub st1, st0 ; p*s s-p*s
|
||||
; Equal to
|
||||
; s*p s*(1-p)
|
||||
fxch ; s*(1-p) s*p SHOULD PROBABLY DELETE, WHY BOTHER
|
||||
ret
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "delay"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; DELAY opcode: adds delay effect to the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: perform delay on ST0, using delaycount delaylines starting
|
||||
; at delayindex from the delaytable
|
||||
; Stereo: perform delay on ST1, using delaycount delaylines starting
|
||||
; at delayindex + delaycount from the delaytable (so the right delays
|
||||
; can be different)
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_delay" "Opcode"}}
|
||||
lodsw ; al = delay index, ah = delay count
|
||||
{{- .PushRegs .VAL "DelayVal" .COM "DelayCom" | indent 4}}
|
||||
movzx ebx, al
|
||||
; %ifdef RUNTIME_TABLES ; when using runtime tables, delaytimes is pulled from the stack so can be a pointer to heap
|
||||
; mov _SI, [{{.SP}} + su_stack.delaytimes + PUSH_REG_SIZE(2)]
|
||||
; lea _BX, [_SI + _BX*2]
|
||||
; %else
|
||||
{{.Prepare "su_delay_times" | indent 4}}
|
||||
lea {{.BX}},[{{.Use "su_delay_times"}} + {{.BX}}*2] ; BX now points to the right position within delay time table
|
||||
movzx esi, word [{{.Stack "GlobalTick"}}] ; notice that we load word, so we wrap at 65536
|
||||
mov {{.CX}}, {{.PTRWORD}} [{{.Stack "DelayWorkSpace"}}] ; {{.WRK}} is now the separate delay workspace, as they require a lot more space
|
||||
{{- if .StereoAndMono "delay"}}
|
||||
jnc su_op_delay_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "delay"}}
|
||||
push {{.AX}} ; save _ah (delay count)
|
||||
fxch ; r l
|
||||
call su_op_delay_do ; D(r) l process delay for the right channel
|
||||
pop {{.AX}} ; restore the count for second run
|
||||
fxch ; l D(r)
|
||||
su_op_delay_mono: ; flow into mono delay
|
||||
{{- end}}
|
||||
call su_op_delay_do ; when stereo delay is not enabled, we could inline this to save 5 bytes, but I expect stereo delay to be farely popular so maybe not worth the hassle
|
||||
mov {{.PTRWORD}} [{{.Stack "DelayWorkSpace"}}],{{.CX}} ; move delay workspace pointer back to stack.
|
||||
{{- .PopRegs .VAL .COM | indent 4}}
|
||||
{{- if .UsesDelayModulation}}
|
||||
xor eax, eax
|
||||
mov dword [{{.Modulation "delay" "delaytime"}}], eax
|
||||
{{- end}}
|
||||
ret
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_op_delay_do: executes the actual delay
|
||||
;-------------------------------------------------------------------------------
|
||||
; Pseudocode:
|
||||
; q = dr*x
|
||||
; for (i = 0;i < count;i++)
|
||||
; s = b[(t-delaytime[i+offset])&65535]
|
||||
; q += s
|
||||
; o[i] = o[i]*da+s*(1-da)
|
||||
; b[t] = f*o[i] +p^2*x
|
||||
; Perform dc-filtering q and output q
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_delay_do"}} ; x y
|
||||
fld st0
|
||||
fmul dword [{{.Input "delay" "pregain"}}] ; p*x y
|
||||
fmul dword [{{.Input "delay" "pregain"}}] ; p*p*x y
|
||||
fxch ; y p*p*x
|
||||
fmul dword [{{.Input "delay" "dry"}}] ; dr*y p*p*x
|
||||
su_op_delay_loop:
|
||||
{{- if or .UsesDelayModulation (.HasParamValue "delay" "notetracking" 1)}} ; delaytime modulation or note syncing require computing the delay time in floats
|
||||
fild word [{{.BX}}] ; k dr*y p*p*x, where k = delay time
|
||||
{{- if .HasParamValue "delay" "notetracking" 1}}
|
||||
test ah, 1 ; note syncing is the least significant bit of ah, 0 = ON, 1 = OFF
|
||||
jne su_op_delay_skipnotesync
|
||||
fild dword [{{.INP}}-su_voice.inputs+su_voice.note]
|
||||
{{.Int 0x3DAAAAAA | .Prepare | indent 8}}
|
||||
fmul dword [{{.Int 0x3DAAAAAA | .Use}}]
|
||||
{{.Call "su_power"}}
|
||||
fdivp st1, st0 ; use 10787 for delaytime to have neutral transpose
|
||||
su_op_delay_skipnotesync:
|
||||
{{- end}}
|
||||
{{- if .UsesDelayModulation}}
|
||||
fld dword [{{.Modulation "delay" "delaytime"}}]
|
||||
{{- .Float 32767.0 | .Prepare | indent 8}}
|
||||
fmul dword [{{.Float 32767.0 | .Use}}] ; scale it up, as the modulations would be too small otherwise
|
||||
faddp st1, st0
|
||||
{{- end}}
|
||||
fistp dword [{{.SP}}-4] ; dr*y p*p*x, dword [{{.SP}}-4] = integer amount of delay (samples)
|
||||
mov edi, esi ; edi = esi = current time
|
||||
sub di, word [{{.SP}}-4] ; we perform the math in 16-bit to wrap around
|
||||
{{- else}}
|
||||
mov edi, esi
|
||||
sub di, word [{{.BX}}] ; we perform the math in 16-bit to wrap around
|
||||
{{- end}}
|
||||
fld dword [{{.CX}}+su_delayline_wrk.buffer+{{.DI}}*4]; s dr*y p*p*x, where s is the sample from delay buffer
|
||||
fadd st1, st0 ; s dr*y+s p*p*x (add comb output to current output)
|
||||
fld1 ; 1 s dr*y+s p*p*x
|
||||
fsub dword [{{.Input "delay" "damp"}}] ; 1-da s dr*y+s p*p*x
|
||||
fmulp st1, st0 ; s*(1-da) dr*y+s p*p*x
|
||||
fld dword [{{.Input "delay" "damp"}}] ; da s*(1-da) dr*y+s p*p*x
|
||||
fmul dword [{{.CX}}+su_delayline_wrk.filtstate] ; o*da s*(1-da) dr*y+s p*p*x, where o is stored
|
||||
faddp st1, st0 ; o*da+s*(1-da) dr*y+s p*p*x
|
||||
fst dword [{{.CX}}+su_delayline_wrk.filtstate] ; o'=o*da+s*(1-da), o' dr*y+s p*p*x
|
||||
fmul dword [{{.Input "delay" "feedback"}}] ; f*o' dr*y+s p*p*x
|
||||
fadd st0, st2 ; f*o'+p*p*x dr*y+s p*p*x
|
||||
fstp dword [{{.CX}}+su_delayline_wrk.buffer+{{.SI}}*4]; save f*o'+p*p*x to delay buffer
|
||||
add {{.BX}},2 ; move to next index
|
||||
add {{.CX}}, su_delayline_wrk.size ; go to next delay delay workspace
|
||||
sub ah, 2
|
||||
jg su_op_delay_loop ; if ah > 0, goto loop
|
||||
fstp st1 ; dr*y+s1+s2+s3+...
|
||||
; DC-filtering
|
||||
fld dword [{{.CX}}+su_delayline_wrk.dcout] ; o s
|
||||
{{- .Float 0.99609375 | .Prepare | indent 4}}
|
||||
fmul dword [{{.Float 0.99609375 | .Use}}] ; c*o s
|
||||
fsub dword [{{.CX}}+su_delayline_wrk.dcin] ; c*o-i s
|
||||
fxch ; s c*o-i
|
||||
fst dword [{{.CX}}+su_delayline_wrk.dcin] ; i'=s, s c*o-i
|
||||
faddp st1 ; s+c*o-i
|
||||
{{- .Float 0.5 | .Prepare | indent 4}}
|
||||
fadd dword [{{.Float 0.5 | .Use}}] ; add and sub small offset to prevent denormalization
|
||||
fsub dword [{{.Float 0.5 | .Use}}]
|
||||
fst dword [{{.CX}}+su_delayline_wrk.dcout] ; o'=s+c*o-i
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "compressor"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; COMPRES opcode: push compressor gain to stack
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: push g on stack, where g is a suitable gain for the signal
|
||||
; you can either MULP to compress the signal or SEND it to a GAIN
|
||||
; somewhere else for compressor side-chaining.
|
||||
; Stereo: push g g on stack, where g is calculated using l^2 + r^2
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_compressor" "Opcode"}}
|
||||
fdiv dword [{{.Input "compressor" "invgain"}}]; l/g, we'll call this pre inverse gained signal x from now on
|
||||
fld st0 ; x x
|
||||
fmul st0, st0 ; x^2 x
|
||||
{{- if .StereoAndMono "compressor"}}
|
||||
jnc su_op_compressor_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "compressor"}}
|
||||
fld st2 ; r x^2 l/g r
|
||||
fdiv dword [{{.Input "compressor" "invgain"}}]; r/g, we'll call this pre inverse gained signal y from now on
|
||||
fst st3 ; y x^2 l/g r/g
|
||||
fmul st0, st0 ; y^2 x^2 l/g r/g
|
||||
faddp st1, st0 ; y^2+x^2 l/g r/g
|
||||
call su_op_compressor_mono ; So, for stereo, we square both left & right and add them up
|
||||
fld st0 ; and return the computed gain two times, ready for MULP STEREO
|
||||
ret
|
||||
su_op_compressor_mono:
|
||||
{{- end}}
|
||||
fld dword [{{.WRK}}] ; l x^2 x
|
||||
fucomi st0, st1
|
||||
setnb al ; if (st0 >= st1) al = 1; else al = 0;
|
||||
fsubp st1, st0 ; x^2-l x
|
||||
{{.Call "su_nonlinear_map"}} ; c x^2-l x, c is either attack or release parameter mapped in a nonlinear way
|
||||
fmulp st1, st0 ; c*(x^2-l) x
|
||||
fadd dword [{{.WRK}}] ; l+c*(x^2-l) x // we could've kept level in the stack and save a few bytes, but su_env_map uses 3 stack (c + 2 temp), so the stack was getting quite big.
|
||||
fst dword [{{.WRK}}] ; l'=l+c*(x^2-l), l' x
|
||||
fld dword [{{.Input "compressor" "threshold"}}] ; t l' x
|
||||
fmul st0, st0 ; t*t l' x
|
||||
fxch ; l' t*t x
|
||||
fucomi st0, st1 ; if l' < t*t
|
||||
fcmovb st0, st1 ; l'=t*t
|
||||
fdivp st1, st0 ; t*t/l' x
|
||||
fld dword [{{.Input "compressor" "ratio"}}] ; r t*t/l' x
|
||||
{{.Float 0.5 | .Prepare | indent 4}}
|
||||
fmul dword [{{.Float 0.5 | .Use}}] ; p=r/2 t*t/l' x
|
||||
fxch ; t*t/l' p x
|
||||
fyl2x ; p*log2(t*t/l') x
|
||||
{{.TailCall "su_power"}} ; 2^(p*log2(t*t/l')) x
|
||||
; tail call ; Equal to:
|
||||
; (t*t/l')^p x
|
||||
; if ratio is at minimum => p=0 => 1 x
|
||||
; if ratio is at maximum => p=0.5 => t/x => t/x*x=t
|
||||
{{- end}}
|
23
templates/flowcontrol.asm
Normal file
23
templates/flowcontrol.asm
Normal file
@ -0,0 +1,23 @@
|
||||
{{- if .Opcode "speed" -}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; SPEED opcode: modulate the speed (bpm) of the song based on ST0
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: adds or subtracts the ticks, a value of 0.5 is neutral & will7
|
||||
; result in no speed change.
|
||||
; There is no STEREO version.
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_speed" "Opcode"}}
|
||||
{{- .Float 2.206896551724138 | .Prepare | indent 4}}
|
||||
fmul dword [{{.Float 2.206896551724138 | .Use}}] ; (2*s-1)*64/24, let's call this p from now on
|
||||
{{.Call "su_power"}}
|
||||
fld1 ; 1 2^p
|
||||
fsubp st1, st0 ; 2^p-1, the player is advancing 1 tick by its own
|
||||
fadd dword [{{.WRK}}] ; t+2^p-1, t is the remainder from previous rounds as ticks have to be rounded to 1
|
||||
push {{.AX}}
|
||||
fist dword [{{.SP}}] ; Main stack: k=int(t+2^p-1)
|
||||
fisub dword [{{.SP}}] ; t+2^p-1-k, the remainder
|
||||
pop {{.AX}}
|
||||
add dword [{{.Stack "Sample"}}], eax ; add the whole ticks to row tick count
|
||||
fstp dword [{{.WRK}}] ; save the remainder for future
|
||||
ret
|
||||
{{end}}
|
58
templates/gmdls.asm
Normal file
58
templates/gmdls.asm
Normal file
@ -0,0 +1,58 @@
|
||||
{{- if eq .OS "windows"}}
|
||||
{{.ExportFunc "su_load_gmdls"}}
|
||||
{{- if .Amd64}}
|
||||
extern OpenFile ; requires windows
|
||||
extern ReadFile ; requires windows
|
||||
; Win64 ABI: RCX, RDX, R8, and R9
|
||||
sub rsp, 40 ; Win64 ABI requires "shadow space" + space for one parameter.
|
||||
mov rdx, qword su_sample_table
|
||||
mov rcx, qword su_gmdls_path1
|
||||
su_gmdls_pathloop:
|
||||
xor r8,r8 ; OF_READ
|
||||
push rdx ; &ofstruct, blatantly reuse the sample table
|
||||
push rcx
|
||||
call OpenFile ; eax = OpenFile(path,&ofstruct,OF_READ)
|
||||
pop rcx
|
||||
add rcx, su_gmdls_path2 - su_gmdls_path1 ; if we ever get to third, then crash
|
||||
pop rdx
|
||||
cmp eax, -1 ; ecx == INVALID?
|
||||
je su_gmdls_pathloop
|
||||
movsxd rcx, eax
|
||||
mov qword [rsp+32], 0
|
||||
mov r9, rdx
|
||||
mov r8d, 3440660 ; number of bytes to read
|
||||
call ReadFile ; Readfile(handle,&su_sample_table,SAMPLE_TABLE_SIZE,&bytes_read,NULL)
|
||||
add rsp, 40 ; shadow space, as required by Win64 ABI
|
||||
ret
|
||||
{{- else}}
|
||||
mov edx, su_sample_table
|
||||
mov ecx, su_gmdls_path1
|
||||
su_gmdls_pathloop:
|
||||
push 0 ; OF_READ
|
||||
push edx ; &ofstruct, blatantly reuse the sample table
|
||||
push ecx ; path
|
||||
call _OpenFile@12 ; eax = OpenFile(path,&ofstruct,OF_READ)
|
||||
add ecx, su_gmdls_path2 - su_gmdls_path1 ; if we ever get to third, then crash
|
||||
cmp eax, -1 ; eax == INVALID?
|
||||
je su_gmdls_pathloop
|
||||
push 0 ; NULL
|
||||
push edx ; &bytes_read, reusing sample table again; it does not matter that the first four bytes are trashed
|
||||
push 3440660 ; number of bytes to read
|
||||
push edx ; here we actually pass the sample table to readfile
|
||||
push eax ; handle to file
|
||||
call _ReadFile@20 ; Readfile(handle,&su_sample_table,SAMPLE_TABLE_SIZE,&bytes_read,NULL)
|
||||
ret
|
||||
extern _OpenFile@12 ; requires windows
|
||||
extern _ReadFile@20 ; requires windows
|
||||
{{end}}
|
||||
|
||||
{{.Data "su_gmdls_path1"}}
|
||||
db 'drivers/gm.dls',0
|
||||
su_gmdls_path2:
|
||||
db 'drivers/etc/gm.dls',0
|
||||
|
||||
{{.SectBss "susamtable"}}
|
||||
su_sample_table:
|
||||
resb 3440660 ; size of gmdls.
|
||||
|
||||
{{end}}
|
304
templates/library.asm
Normal file
304
templates/library.asm
Normal file
@ -0,0 +1,304 @@
|
||||
; source file for compiling sointu as a library
|
||||
%define SU_DISABLE_PLAYER
|
||||
|
||||
%include "sointu/header.inc"
|
||||
|
||||
; use every opcode
|
||||
USE_ADD
|
||||
USE_ADDP
|
||||
USE_POP
|
||||
USE_LOADNOTE
|
||||
USE_MUL
|
||||
USE_MULP
|
||||
USE_PUSH
|
||||
USE_XCH
|
||||
USE_DISTORT
|
||||
USE_HOLD
|
||||
USE_CRUSH
|
||||
USE_GAIN
|
||||
USE_INVGAIN
|
||||
USE_FILTER
|
||||
USE_CLIP
|
||||
USE_PAN
|
||||
USE_DELAY
|
||||
USE_COMPRES
|
||||
USE_SPEED
|
||||
USE_OUT
|
||||
USE_OUTAUX
|
||||
USE_AUX
|
||||
USE_SEND
|
||||
USE_ENVELOPE
|
||||
USE_NOISE
|
||||
USE_OSCILLAT
|
||||
USE_LOAD_VAL
|
||||
USE_RECEIVE
|
||||
USE_IN
|
||||
|
||||
; include stereo variant of each opcode
|
||||
%define INCLUDE_STEREO_ADD
|
||||
%define INCLUDE_STEREO_ADDP
|
||||
%define INCLUDE_STEREO_POP
|
||||
%define INCLUDE_STEREO_LOADNOTE
|
||||
%define INCLUDE_STEREO_MUL
|
||||
%define INCLUDE_STEREO_MULP
|
||||
%define INCLUDE_STEREO_PUSH
|
||||
%define INCLUDE_STEREO_XCH
|
||||
%define INCLUDE_STEREO_DISTORT
|
||||
%define INCLUDE_STEREO_HOLD
|
||||
%define INCLUDE_STEREO_CRUSH
|
||||
%define INCLUDE_STEREO_GAIN
|
||||
%define INCLUDE_STEREO_INVGAIN
|
||||
%define INCLUDE_STEREO_FILTER
|
||||
%define INCLUDE_STEREO_CLIP
|
||||
%define INCLUDE_STEREO_PAN
|
||||
%define INCLUDE_STEREO_DELAY
|
||||
%define INCLUDE_STEREO_COMPRES
|
||||
%define INCLUDE_STEREO_SPEED
|
||||
%define INCLUDE_STEREO_OUT
|
||||
%define INCLUDE_STEREO_OUTAUX
|
||||
%define INCLUDE_STEREO_AUX
|
||||
%define INCLUDE_STEREO_SEND
|
||||
%define INCLUDE_STEREO_ENVELOPE
|
||||
%define INCLUDE_STEREO_NOISE
|
||||
%define INCLUDE_STEREO_OSCILLAT
|
||||
%define INCLUDE_STEREO_LOADVAL
|
||||
%define INCLUDE_STEREO_RECEIVE
|
||||
%define INCLUDE_STEREO_IN
|
||||
|
||||
; include all features inside all opcodes
|
||||
%define INCLUDE_TRISAW
|
||||
%define INCLUDE_SINE
|
||||
%define INCLUDE_PULSE
|
||||
%define INCLUDE_GATE
|
||||
%define INCLUDE_UNISONS
|
||||
%define INCLUDE_POLYPHONY
|
||||
%define INCLUDE_MULTIVOICE_TRACKS
|
||||
%define INCLUDE_DELAY_MODULATION
|
||||
%define INCLUDE_LOWPASS
|
||||
%define INCLUDE_BANDPASS
|
||||
%define INCLUDE_HIGHPASS
|
||||
%define INCLUDE_NEGBANDPASS
|
||||
%define INCLUDE_NEGHIGHPASS
|
||||
%define INCLUDE_GLOBAL_SEND
|
||||
%define INCLUDE_DELAY_NOTETRACKING
|
||||
%define INCLUDE_DELAY_FLOAT_TIME
|
||||
|
||||
%ifidn __OUTPUT_FORMAT__,win32
|
||||
%define INCLUDE_SAMPLES
|
||||
%define INCLUDE_GMDLS
|
||||
%endif
|
||||
%ifidn __OUTPUT_FORMAT__,win64
|
||||
%define INCLUDE_SAMPLES
|
||||
%define INCLUDE_GMDLS
|
||||
%endif
|
||||
|
||||
%include "sointu/footer.inc"
|
||||
|
||||
section .text
|
||||
|
||||
struc su_sampleoff
|
||||
.start resd 1
|
||||
.loopstart resw 1
|
||||
.looplength resw 1
|
||||
.size:
|
||||
endstruc
|
||||
|
||||
struc su_synth
|
||||
.synthwrk resb su_synthworkspace.size
|
||||
.delaywrks resb su_delayline_wrk.size * 64
|
||||
.delaytimes resw 768
|
||||
.sampleoffs resb su_sampleoff.size * 256
|
||||
.randseed resd 1
|
||||
.globaltime resd 1
|
||||
.commands resb 32 * 64
|
||||
.values resb 32 * 64 * 8
|
||||
.polyphony resd 1
|
||||
.numvoices resd 1
|
||||
endstruc
|
||||
|
||||
SECT_TEXT(sursampl)
|
||||
|
||||
EXPORT MANGLE_FUNC(su_render,16)
|
||||
%if BITS == 32 ; stdcall
|
||||
pushad ; push registers
|
||||
mov ecx, [esp + 4 + 32] ; ecx = &synthState
|
||||
mov edx, [esp + 8 + 32] ; edx = &buffer
|
||||
mov esi, [esp + 12 + 32] ; esi = &samples
|
||||
mov ebx, [esp + 16 + 32] ; ebx = &time
|
||||
%else
|
||||
%ifidn __OUTPUT_FORMAT__,win64 ; win64 ABI: rcx = &synth, rdx = &buffer, r8 = &bufsize, r9 = &time
|
||||
push_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile
|
||||
mov rsi, r8 ; rsi = &samples
|
||||
mov rbx, r9 ; rbx = &time
|
||||
%else ; System V ABI: rdi = &synth, rsi = &buffer, rdx = &samples, rcx = &time
|
||||
push_registers rbx, rbp ; System V ABI: these registers are non-volatile
|
||||
mov rbx, rcx ; rbx points to time
|
||||
xchg rsi, rdx ; rdx points to buffer, rsi points to samples
|
||||
mov rcx, rdi ; rcx = &Synthstate
|
||||
%endif
|
||||
%endif
|
||||
sub _SP,108 ; allocate space on stack for the FPU state
|
||||
fsave [_SP] ; save the FPU state to stack & reset the FPU
|
||||
push _SI ; push the pointer to samples
|
||||
push _BX ; push the pointer to time
|
||||
xor eax, eax ; samplenumber starts at 0
|
||||
push _AX ; push samplenumber to stack
|
||||
mov esi, [_SI] ; zero extend dereferenced pointer
|
||||
push _SI ; push bufsize
|
||||
push _DX ; push bufptr
|
||||
push _CX ; this takes place of the voicetrack
|
||||
lea _AX, [_CX + su_synth.sampleoffs]
|
||||
push _AX
|
||||
lea _AX, [_CX + su_synth.delaytimes]
|
||||
push _AX
|
||||
mov eax, [_CX + su_synth.randseed]
|
||||
push _AX ; randseed
|
||||
mov eax, [_CX + su_synth.globaltime]
|
||||
push _AX ; global tick time
|
||||
mov ebx, dword [_BX] ; zero extend dereferenced pointer
|
||||
push _BX ; the nominal rowlength should be time_in
|
||||
xor eax, eax ; rowtick starts at 0
|
||||
su_render_samples_loop:
|
||||
push _DI
|
||||
fnstsw [_SP] ; store the FPU status flag to stack top
|
||||
pop _DI ; _DI = FPU status flag
|
||||
and _DI, 0011100001000101b ; mask TOP pointer, stack error, zero divide and invalid operation
|
||||
test _DI,_DI ; all the aforementioned bits should be 0!
|
||||
jne su_render_samples_time_finish ; otherwise, we exit due to error
|
||||
cmp eax, [_SP] ; if rowtick >= maxtime
|
||||
jge su_render_samples_time_finish ; goto finish
|
||||
mov ecx, [_SP + PTRSIZE*7] ; ecx = buffer length in samples
|
||||
cmp [_SP + PTRSIZE*8], ecx ; if samples >= maxsamples
|
||||
jge su_render_samples_time_finish ; goto finish
|
||||
inc eax ; time++
|
||||
inc dword [_SP + PTRSIZE*8] ; samples++
|
||||
mov _CX, [_SP + PTRSIZE*5]
|
||||
push _AX ; push rowtick
|
||||
mov eax, [_CX + su_synth.polyphony]
|
||||
push _AX ;polyphony
|
||||
mov eax, [_CX + su_synth.numvoices]
|
||||
push _AX ;numvoices
|
||||
lea _DX, [_CX+ su_synth.synthwrk]
|
||||
lea COM, [_CX+ su_synth.commands]
|
||||
lea VAL, [_CX+ su_synth.values]
|
||||
lea WRK, [_DX + su_synthworkspace.voices]
|
||||
lea _CX, [_CX+ su_synth.delaywrks - su_delayline_wrk.filtstate]
|
||||
call MANGLE_FUNC(su_run_vm,0)
|
||||
pop _AX
|
||||
pop _AX
|
||||
mov _DI, [_SP + PTRSIZE*7] ; edi containts buffer ptr
|
||||
mov _CX, [_SP + PTRSIZE*6]
|
||||
lea _SI, [_CX + su_synth.synthwrk + su_synthworkspace.left]
|
||||
movsd ; copy left channel to output buffer
|
||||
movsd ; copy right channel to output buffer
|
||||
mov [_SP + PTRSIZE*7], _DI ; save back the updated ptr
|
||||
lea _DI, [_SI-8]
|
||||
xor eax, eax
|
||||
stosd ; clear left channel so the VM is ready to write them again
|
||||
stosd ; clear right channel so the VM is ready to write them again
|
||||
pop _AX
|
||||
inc dword [_SP + PTRSIZE] ; increment global time, used by delays
|
||||
jmp su_render_samples_loop
|
||||
su_render_samples_time_finish:
|
||||
pop _CX
|
||||
pop _BX
|
||||
pop _DX
|
||||
pop _CX ; discard delaytimes ptr
|
||||
pop _CX ; discard samplesoffs ptr
|
||||
pop _CX
|
||||
mov [_CX + su_synth.randseed], edx
|
||||
mov [_CX + su_synth.globaltime], ebx
|
||||
pop _BX
|
||||
pop _BX
|
||||
pop _DX
|
||||
pop _BX ; pop the pointer to time
|
||||
pop _SI ; pop the pointer to samples
|
||||
mov dword [_SI], edx ; *samples = samples rendered
|
||||
mov dword [_BX], eax ; *time = time ticks rendered
|
||||
mov _AX,_DI ; _DI was the masked FPU status flag, _AX is return value
|
||||
frstor [_SP] ; restore fpu state
|
||||
add _SP,108 ; rewind the stack allocate for FPU state
|
||||
%if BITS == 32 ; stdcall
|
||||
mov [_SP + 28],eax ; we want to return eax, but popad pops everything, so put eax to stack for popad to pop
|
||||
popad
|
||||
ret 16
|
||||
%else
|
||||
%ifidn __OUTPUT_FORMAT__,win64
|
||||
pop_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile
|
||||
%else
|
||||
pop_registers rbx, rbp ; System V ABI: these registers are non-volatile
|
||||
%endif
|
||||
ret
|
||||
%endif
|
||||
|
||||
SECT_DATA(opcodeid)
|
||||
|
||||
; Arithmetic opcode ids
|
||||
EXPORT MANGLE_DATA(su_add_id)
|
||||
dd ADD_ID
|
||||
EXPORT MANGLE_DATA(su_addp_id)
|
||||
dd ADDP_ID
|
||||
EXPORT MANGLE_DATA(su_pop_id)
|
||||
dd POP_ID
|
||||
EXPORT MANGLE_DATA(su_loadnote_id)
|
||||
dd LOADNOTE_ID
|
||||
EXPORT MANGLE_DATA(su_mul_id)
|
||||
dd MUL_ID
|
||||
EXPORT MANGLE_DATA(su_mulp_id)
|
||||
dd MULP_ID
|
||||
EXPORT MANGLE_DATA(su_push_id)
|
||||
dd PUSH_ID
|
||||
EXPORT MANGLE_DATA(su_xch_id)
|
||||
dd XCH_ID
|
||||
|
||||
; Effect opcode ids
|
||||
EXPORT MANGLE_DATA(su_distort_id)
|
||||
dd DISTORT_ID
|
||||
EXPORT MANGLE_DATA(su_hold_id)
|
||||
dd HOLD_ID
|
||||
EXPORT MANGLE_DATA(su_crush_id)
|
||||
dd CRUSH_ID
|
||||
EXPORT MANGLE_DATA(su_gain_id)
|
||||
dd GAIN_ID
|
||||
EXPORT MANGLE_DATA(su_invgain_id)
|
||||
dd INVGAIN_ID
|
||||
EXPORT MANGLE_DATA(su_filter_id)
|
||||
dd FILTER_ID
|
||||
EXPORT MANGLE_DATA(su_clip_id)
|
||||
dd CLIP_ID
|
||||
EXPORT MANGLE_DATA(su_pan_id)
|
||||
dd PAN_ID
|
||||
EXPORT MANGLE_DATA(su_delay_id)
|
||||
dd DELAY_ID
|
||||
EXPORT MANGLE_DATA(su_compres_id)
|
||||
dd COMPRES_ID
|
||||
|
||||
; Flowcontrol opcode ids
|
||||
EXPORT MANGLE_DATA(su_advance_id)
|
||||
dd SU_ADVANCE_ID
|
||||
EXPORT MANGLE_DATA(su_speed_id)
|
||||
dd SPEED_ID
|
||||
|
||||
; Sink opcode ids
|
||||
EXPORT MANGLE_DATA(su_out_id)
|
||||
dd OUT_ID
|
||||
EXPORT MANGLE_DATA(su_outaux_id)
|
||||
dd OUTAUX_ID
|
||||
EXPORT MANGLE_DATA(su_aux_id)
|
||||
dd AUX_ID
|
||||
EXPORT MANGLE_DATA(su_send_id)
|
||||
dd SEND_ID
|
||||
|
||||
; Source opcode ids
|
||||
EXPORT MANGLE_DATA(su_envelope_id)
|
||||
dd ENVELOPE_ID
|
||||
EXPORT MANGLE_DATA(su_noise_id)
|
||||
dd NOISE_ID
|
||||
EXPORT MANGLE_DATA(su_oscillat_id)
|
||||
dd OSCILLAT_ID
|
||||
EXPORT MANGLE_DATA(su_loadval_id)
|
||||
dd LOADVAL_ID
|
||||
EXPORT MANGLE_DATA(su_receive_id)
|
||||
dd RECEIVE_ID
|
||||
EXPORT MANGLE_DATA(su_in_id)
|
||||
dd IN_ID
|
44
templates/output_sound.asm
Normal file
44
templates/output_sound.asm
Normal file
@ -0,0 +1,44 @@
|
||||
{{- if not .Output16Bit }}
|
||||
{{- if not .Clip }}
|
||||
mov {{.DI}}, [{{.Stack "OutputBufPtr"}}] ; edi containts ptr
|
||||
mov {{.SI}}, {{.PTRWORD}} su_synth_obj + su_synthworkspace.left
|
||||
movsd ; copy left channel to output buffer
|
||||
movsd ; copy right channel to output buffer
|
||||
mov [{{.Stack "OutputBufPtr"}}], {{.DI}} ; save back the updated ptr
|
||||
lea {{.DI}}, [{{.SI}}-8]
|
||||
xor eax, eax
|
||||
stosd ; clear left channel so the VM is ready to write them again
|
||||
stosd ; clear right channel so the VM is ready to write them again
|
||||
{{ else }}
|
||||
mov {{.SI}}, qword [{{.Stack "OutputBufPtr"}}] ; esi points to the output buffer
|
||||
xor ecx,ecx
|
||||
xor eax,eax
|
||||
%%loop: ; loop over two channels, left & right
|
||||
do fld dword [,su_synth_obj+su_synthworkspace.left,_CX*4,]
|
||||
{{.Call "su_clip"}}
|
||||
fstp dword [_SI]
|
||||
do mov dword [,su_synth_obj+su_synthworkspace.left,_CX*4,{],eax} ; clear the sample so the VM is ready to write it
|
||||
add _SI,4
|
||||
cmp ecx,2
|
||||
jl %%loop
|
||||
mov dword [_SP+su_stack.bufferptr - su_stack.output_sound], _SI ; save esi back to stack
|
||||
{{ end }}
|
||||
{{- else}}
|
||||
mov {{.SI}}, [{{.Stack "OutputBufPtr"}}] ; esi points to the output buffer
|
||||
mov {{.DI}}, {{.PTRWORD}} su_synth_obj+su_synthworkspace.left
|
||||
mov ecx, 2
|
||||
output_sound16bit_loop: ; loop over two channels, left & right
|
||||
fld dword [{{.DI}}]
|
||||
{{.Call "su_clip"}}
|
||||
{{- .Float 32767.0 | .Prepare | indent 16}}
|
||||
fmul dword [{{.Float 32767.0 | .Use}}]
|
||||
push {{.AX}}
|
||||
fistp dword [{{.SP}}]
|
||||
pop {{.AX}}
|
||||
mov word [{{.SI}}],ax ; // store integer converted right sample
|
||||
xor eax,eax
|
||||
stosd
|
||||
add {{.SI}},2
|
||||
loop output_sound16bit_loop
|
||||
mov [{{.Stack "OutputBufPtr"}}], {{.SI}} ; save esi back to stack
|
||||
{{- end }}
|
190
templates/patch.asm
Normal file
190
templates/patch.asm
Normal file
@ -0,0 +1,190 @@
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_run_vm function: runs the entire virtual machine once, creating 1 sample
|
||||
;-------------------------------------------------------------------------------
|
||||
; Input: su_synth_obj.left : Set to 0 before calling
|
||||
; su_synth_obj.right : Set to 0 before calling
|
||||
; _CX : Pointer to delay workspace (if needed)
|
||||
; _DX : Pointer to synth object
|
||||
; COM : Pointer to command stream
|
||||
; VAL : Pointer to value stream
|
||||
; WRK : Pointer to the last workspace processed
|
||||
; Output: su_synth_obj.left : left sample
|
||||
; su_synth_obj.right : right sample
|
||||
; Dirty: everything
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_run_vm"}}
|
||||
{{- .PushRegs .CX "DelayWorkSpace" .DX "Synth" .COM "CommandStream" .WRK "Voice" .VAL "ValueStream" | indent 4}}
|
||||
su_run_vm_loop: ; loop until all voices done
|
||||
movzx edi, byte [{{.COM}}] ; edi = command byte
|
||||
inc {{.COM}} ; move to next instruction
|
||||
add {{.WRK}}, su_unit.size ; move WRK to next unit
|
||||
shr edi, 1 ; shift out the LSB bit = stereo bit
|
||||
je su_run_vm_advance ; the opcode is zero, jump to advance
|
||||
mov {{.INP}}, [{{.Stack "Voice"}}] ; reset INP to point to the inputs part of voice
|
||||
add {{.INP}}, su_voice.inputs
|
||||
xor ecx, ecx ; counter = 0
|
||||
xor eax, eax ; clear out high bits of eax, as lodsb only sets al
|
||||
su_transform_values_loop:
|
||||
{{- .Prepare "su_vm_transformcounts" | indent 4}}
|
||||
cmp cl, byte [{{.Use "su_vm_transformcounts"}}+{{.DI}}] ; compare the counter to the value in the param count table
|
||||
je su_transform_values_out
|
||||
lodsb ; load the byte value from VAL stream
|
||||
push {{.AX}} ; push it to memory so FPU can read it
|
||||
fild dword [{{.SP}}] ; load the value to FPU stack
|
||||
{{- .Prepare (.Float 0.0078125) | indent 4}}
|
||||
fmul dword [{{.Use (.Float 0.0078125)}}] ; divide it by 128 (0 => 0, 128 => 1.0)
|
||||
fadd dword [{{.WRK}}+su_unit.ports+{{.CX}}*4] ; add the modulations in the current workspace
|
||||
fstp dword [{{.INP}}+{{.CX}}*4] ; store the modulated value in the inputs section of voice
|
||||
xor eax, eax
|
||||
mov dword [{{.WRK}}+su_unit.ports+{{.CX}}*4], eax ; clear out the modulation ports
|
||||
pop {{.AX}}
|
||||
inc ecx
|
||||
jmp su_transform_values_loop
|
||||
su_transform_values_out:
|
||||
bt dword [{{.COM}}-1],0 ; LSB of COM = stereo bit => carry
|
||||
{{- .SaveStack "Opcode"}}
|
||||
{{- .Prepare "su_vm_jumptable" | indent 4}}
|
||||
call [{{.Use "su_vm_jumptable"}}+{{.DI}}*{{.PTRSIZE}}] ; call the function corresponding to the instruction
|
||||
jmp su_run_vm_loop
|
||||
su_run_vm_advance:
|
||||
{{- if .Polyphony}}
|
||||
mov {{.WRK}}, [{{.Stack "Voice"}}] ; WRK points to start of current voice
|
||||
add {{.WRK}}, su_voice.size ; move to next voice
|
||||
mov [{{.Stack "Voice"}}], {{.WRK}} ; update the pointer in the stack to point to the new voice
|
||||
mov ecx, [{{.Stack "VoicesRemain"}}] ; ecx = how many voices remain to process
|
||||
dec ecx ; decrement number of voices to process
|
||||
bt dword [{{.Stack "PolyphonyBitmask"}}], ecx ; if voice bit of su_polyphonism not set
|
||||
jnc su_op_advance_next_instrument ; goto next_instrument
|
||||
mov {{.VAL}}, [{{.Stack "ValueStream"}}] ; if it was set, then repeat the opcodes for the current voice
|
||||
mov {{.COM}}, [{{.Stack "CommandStream"}}]
|
||||
su_op_advance_next_instrument:
|
||||
mov [{{.Stack "ValueStream"}}], {{.VAL}} ; save current VAL as a checkpoint
|
||||
mov [{{.Stack "CommandStream"}}], {{.COM}} ; save current COM as a checkpoint
|
||||
su_op_advance_finish:
|
||||
mov [{{.Stack "VoicesRemain"}}], ecx
|
||||
jne su_run_vm_loop ; ZF was set by dec ecx
|
||||
{{- else}}
|
||||
mov {{.WRK}}, {{.PTRWORD}} [{{.Stack "Voice"}}] ; load pointer to voice to register
|
||||
add {{.WRK}}, su_voice.size ; shift it to point to following voice
|
||||
mov {{.PTRWORD}} [{{.Stack "Voice"}}], {{.WRK}} ; save back to stack
|
||||
dec dword [{{.Stack "VoicesRemain"}}] ; voices--
|
||||
jne su_run_vm_loop ; if there's more voices to process, goto vm_loop
|
||||
{{- end}}
|
||||
{{- .PopRegs .CX .DX .COM .WRK .VAL | indent 4}}
|
||||
ret
|
||||
|
||||
{{- template "arithmetic.asm" .}}
|
||||
{{- template "effects.asm" .}}
|
||||
{{- template "flowcontrol.asm" .}}
|
||||
{{- template "sinks.asm" .}}
|
||||
{{- template "sources.asm" .}}
|
||||
{{- template "gmdls.asm" .}}
|
||||
|
||||
{{- if .HasCall "su_nonlinear_map"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_nonlinear_map function: returns 2^(-24*x) of parameter number _AX
|
||||
;-------------------------------------------------------------------------------
|
||||
; Input: _AX : parameter number (e.g. for envelope: 0 = attac, 1 = decay...)
|
||||
; INP : pointer to transformed values
|
||||
; Output: st0 : 2^(-24*x), where x is the parameter in the range 0-1
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_nonlinear_map"}}
|
||||
fld dword [{{.INP}}+{{.AX}}*4] ; x, where x is the parameter in the range 0-1
|
||||
{{.Prepare (.Int 24)}}
|
||||
fimul dword [{{.Use (.Int 24)}}] ; 24*x
|
||||
fchs ; -24*x
|
||||
|
||||
{{end}}
|
||||
|
||||
{{- if or (.HasCall "su_power") (.HasCall "su_nonlinear_map")}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_power function: computes 2^x
|
||||
;-------------------------------------------------------------------------------
|
||||
; Input: st0 : x
|
||||
; Output: st0 : 2^x
|
||||
;-------------------------------------------------------------------------------
|
||||
{{- if not (.HasCall "su_nonlinear_map")}}{{.SectText "su_power"}}{{end}}
|
||||
su_power:
|
||||
fld1 ; 1 x
|
||||
fld st1 ; x 1 x
|
||||
fprem ; mod(x,1) 1 x
|
||||
f2xm1 ; 2^mod(x,1)-1 1 x
|
||||
faddp st1,st0 ; 2^mod(x,1) x
|
||||
fscale ; 2^mod(x,1)*2^trunc(x) x
|
||||
; Equal to:
|
||||
; 2^x x
|
||||
fstp st1 ; 2^x
|
||||
ret
|
||||
|
||||
{{end}}
|
||||
|
||||
{{- if .HasCall "su_effects_stereohelper" }}
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_effects_stereohelper: moves the workspace to next, does the filtering for
|
||||
; right channel (pulling the calling address from stack), rewinds the
|
||||
; workspace and returns
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_effects_stereohelper"}}
|
||||
jnc su_effects_stereohelper_mono ; carry is still the stereo bit
|
||||
add {{.WRK}}, 16
|
||||
fxch ; r l
|
||||
call [{{.SP}}] ; call whoever called me...
|
||||
fxch ; l r
|
||||
sub {{.WRK}}, 16 ; move WRK back to where it was
|
||||
su_effects_stereohelper_mono:
|
||||
ret ; return to process l/mono sound
|
||||
|
||||
{{end}}
|
||||
|
||||
{{- if .HasCall "su_waveshaper" }}
|
||||
{{.Func "su_waveshaper"}}
|
||||
fxch ; x a
|
||||
{{.Call "su_clip"}}
|
||||
fxch ; a x' (from now on just called x)
|
||||
fld st0 ; a a x
|
||||
{{.Prepare (.Float 0.5)}}
|
||||
fsub dword [{{.Use (.Float 0.5)}}] ; a-.5 a x
|
||||
fadd st0 ; 2*a-1 a x
|
||||
fld st2 ; x 2*a-1 a x
|
||||
fabs ; abs(x) 2*a-1 a x
|
||||
fmulp st1 ; (2*a-1)*abs(x) a x
|
||||
fld1 ; 1 (2*a-1)*abs(x) a x
|
||||
faddp st1 ; 1+(2*a-1)*abs(x) a x
|
||||
fsub st1 ; 1-a+(2*a-1)*abs(x) a x
|
||||
fdivp st1, st0 ; a/(1-a+(2*a-1)*abs(x)) x
|
||||
fmulp st1 ; x*a/(1-a+(2*a-1)*abs(x))
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
{{- if .HasCall "su_clip"}}
|
||||
{{.Func "su_clip"}}
|
||||
fld1 ; 1 x a
|
||||
fucomi st1 ; if (1 <= x)
|
||||
jbe short su_clip_do ; goto Clip_Do
|
||||
fchs ; -1 x a
|
||||
fucomi st1 ; if (-1 < x)
|
||||
fcmovb st0, st1 ; x x a
|
||||
su_clip_do:
|
||||
fstp st1 ; x' a, where x' = clamp(x)
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; The opcode table jump table. This is constructed to only include the opcodes
|
||||
; that are used so that the jump table is as small as possible.
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_vm_jumptable_offset"}}
|
||||
su_vm_jumptable equ $ - {{.PTRSIZE}} ; Advance is not in the opcode table
|
||||
{{- $x := .}}
|
||||
{{- range .Opcodes}}
|
||||
{{$x.DPTR}} su_op_{{.Type}}
|
||||
{{- end}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; The number of transformed parameters each opcode takes
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_vm_transformcounts_offset"}}
|
||||
su_vm_transformcounts equ $ - 1 ; Advance is not in the opcode table
|
||||
{{- range .Opcodes}}
|
||||
db {{.NumParams}}
|
||||
{{- end}}
|
237
templates/player.asm
Normal file
237
templates/player.asm
Normal file
@ -0,0 +1,237 @@
|
||||
{{template "structs.asm" .}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; Uninitialized data: The synth object
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.SectBss "synth_object"}}
|
||||
su_synth_obj:
|
||||
resb su_synthworkspace.size
|
||||
resb {{.NumDelayLines}}*su_delayline_wrk.size
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_render_song function: the entry point for the synth
|
||||
;-------------------------------------------------------------------------------
|
||||
; Has the signature su_render_song(void *ptr), where ptr is a pointer to
|
||||
; the output buffer. Renders the compile time hard-coded song to the buffer.
|
||||
; Stack: output_ptr
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.ExportFunc "su_render_song" "OutputBufPtr"}}
|
||||
{{- if .Amd64}}
|
||||
{{- if eq .OS "windows"}}
|
||||
{{- .PushRegs "rcx" "OutputBufPtr" "rdi" "NonVolatileRsi" "rsi" "NonVolatile" "rbx" "NonVolatileRbx" "rbp" "NonVolatileRbp" | indent 4}} ; rcx = ptr to buf. rdi,rsi,rbx,rbp nonvolatile
|
||||
{{- else}} ; SystemV amd64 ABI, linux mac or hopefully something similar
|
||||
{{- .PushRegs "rdi" "OutputBufPtr" "rbx" "NonVolatileRbx" "rbp" "NonVolatileRbp" | indent 4}}
|
||||
{{- end}}
|
||||
{{- else}}
|
||||
{{- .PushRegs | indent 4}}
|
||||
{{- end}}
|
||||
{{- $prologsize := len .Stacklocs}}
|
||||
xor eax, eax
|
||||
{{- if .MultivoiceTracks}}
|
||||
{{.Push (.VoiceTrackBitmask | printf "%v") "VoiceTrackBitmask"}}
|
||||
{{- end}}
|
||||
{{.Push "1" "RandSeed"}}
|
||||
{{.Push .AX "GlobalTick"}}
|
||||
su_render_rowloop: ; loop through every row in the song
|
||||
{{.Push .AX "Row"}}
|
||||
{{.Call "su_update_voices"}} ; update instruments for the new row
|
||||
xor eax, eax ; ecx is the current sample within row
|
||||
su_render_sampleloop: ; loop through every sample in the row
|
||||
{{.Push .AX "Sample"}}
|
||||
{{- if .Polyphony}}
|
||||
{{.Push (.PolyphonyBitmask | printf "%v") "PolyphonyBitmask"}} ; does the next voice reuse the current opcodes?
|
||||
{{- end}}
|
||||
{{.Push (.Song.Patch.TotalVoices | printf "%v") "VoicesRemain"}}
|
||||
mov {{.DX}}, {{.PTRWORD}} su_synth_obj ; {{.DX}} points to the synth object
|
||||
mov {{.COM}}, {{.PTRWORD}} su_patch_code ; COM points to vm code
|
||||
mov {{.VAL}}, {{.PTRWORD}} su_patch_parameters ; VAL points to unit params
|
||||
{{- if .Opcode "delay"}}
|
||||
lea {{.CX}}, [{{.DX}} + su_synthworkspace.size - su_delayline_wrk.filtstate]
|
||||
{{- end}}
|
||||
lea {{.WRK}}, [{{.DX}} + su_synthworkspace.voices] ; WRK points to the first voice
|
||||
{{.Call "su_run_vm"}} ; run through the VM code
|
||||
{{.Pop .AX}}
|
||||
{{- if .Polyphony}}
|
||||
{{.Pop .AX}}
|
||||
{{- end}}
|
||||
{{- template "output_sound.asm" .}} ; *ptr++ = left, *ptr++ = right
|
||||
{{.Pop .AX}}
|
||||
inc dword [{{.Stack "GlobalTick"}}] ; increment global time, used by delays
|
||||
inc eax
|
||||
cmp eax, {{.Song.SamplesPerRow}}
|
||||
jl su_render_sampleloop
|
||||
{{.Pop .AX}} ; Stack: pushad ptr
|
||||
inc eax
|
||||
cmp eax, {{.Song.TotalRows}}
|
||||
jl su_render_rowloop
|
||||
; rewind the stack the entropy of multiple pop {{.AX}} is probably lower than add
|
||||
{{- $x := .}}
|
||||
{{- range (.Sub (len .Stacklocs) $prologsize | .Count)}}
|
||||
{{$x.Pop $x.AX}}
|
||||
{{- end}}
|
||||
{{- if .Amd64}}
|
||||
{{- if eq .OS "windows"}}
|
||||
; Windows64 ABI, rdi rsi rbx rbp non-volatile
|
||||
{{- .PopRegs "rcx" "rdi" "rsi" "rbx" "rbp" | indent 4}}
|
||||
{{- else}}
|
||||
; SystemV64 ABI (linux mac or hopefully something similar), rbx rbp non-volatile
|
||||
{{- .PopRegs "rdi" "rbx" "rbp" | indent 4}}
|
||||
{{- end}}
|
||||
ret
|
||||
{{- else}}
|
||||
{{- .PopRegs | indent 4}}
|
||||
ret 4
|
||||
{{- end}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_update_voices function: polyphonic & chord implementation
|
||||
;-------------------------------------------------------------------------------
|
||||
; Input: eax : current row within song
|
||||
; Dirty: pretty much everything
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_update_voices"}}
|
||||
{{- if .MultivoiceTracks}}
|
||||
; The more complicated implementation: one track can trigger multiple voices
|
||||
xor edx, edx
|
||||
mov ebx, {{.Song.PatternRows}} ; we could do xor ebx,ebx; mov bl,PATTERN_SIZE, but that would limit patternsize to 256...
|
||||
div ebx ; eax = current pattern, edx = current row in pattern
|
||||
{{.Prepare "su_tracks"}}
|
||||
lea {{.SI}}, [{{.Use "su_tracks"}}+{{.AX}}] ; esi points to the pattern data for current track
|
||||
xor eax, eax ; eax is the first voice of next track
|
||||
xor ebx, ebx ; ebx is the first voice of current track
|
||||
mov {{.BP}}, {{.PTRWORD}} su_synth_obj ; ebp points to the current_voiceno array
|
||||
su_update_voices_trackloop:
|
||||
movzx eax, byte [{{.SI}}] ; eax = current pattern
|
||||
imul eax, {{.Song.PatternRows}} ; eax = offset to current pattern data
|
||||
{{- .Prepare "su_patterns" .AX | indent 4}}
|
||||
movzx eax,byte [{{.Use "su_patterns" .AX}},{{.DX}}] ; eax = note
|
||||
push {{.DX}} ; Stack: ptrnrow
|
||||
xor edx, edx ; edx=0
|
||||
mov ecx, ebx ; ecx=first voice of the track to be done
|
||||
su_calculate_voices_loop: ; do {
|
||||
bt dword [{{.Stack "VoiceTrackBitmask"}}],ecx ; test voicetrack_bitmask// notice that the incs don't set carry
|
||||
inc edx ; edx++ // edx=numvoices
|
||||
inc ecx ; ecx++ // ecx=the first voice of next track
|
||||
jc su_calculate_voices_loop ; } while bit ecx-1 of bitmask is on
|
||||
push {{.CX}} ; Stack: next_instr ptrnrow
|
||||
cmp al, {{.Song.Hold}} ; anything but hold causes action
|
||||
je short su_update_voices_nexttrack
|
||||
mov cl, byte [{{.BP}}]
|
||||
mov edi, ecx
|
||||
add edi, ebx
|
||||
shl edi, 12 ; each unit = 64 bytes and there are 1<<MAX_UNITS_SHIFT units + small header
|
||||
{{- .Prepare "su_synth_obj" | indent 4}}
|
||||
inc dword [{{.Use "su_synth_obj"}} + su_synthworkspace.voices + su_voice.release + {{.DI}}] ; set the voice currently active to release; notice that it could increment any number of times
|
||||
cmp al, {{.Song.Hold}} ; if cl < HLD (no new note triggered)
|
||||
jl su_update_voices_nexttrack ; goto nexttrack
|
||||
inc ecx ; curvoice++
|
||||
cmp ecx, edx ; if (curvoice >= num_voices)
|
||||
jl su_update_voices_skipreset
|
||||
xor ecx,ecx ; curvoice = 0
|
||||
su_update_voices_skipreset:
|
||||
mov byte [{{.BP}}],cl
|
||||
add ecx, ebx
|
||||
shl ecx, 12 ; each unit = 64 bytes and there are 1<<6 units + small header
|
||||
lea {{.DI}},[{{.Use "su_synth_obj"}} + su_synthworkspace.voices + {{.CX}}]
|
||||
stosd ; save note
|
||||
mov ecx, (su_voice.size - su_voice.release)/4
|
||||
xor eax, eax
|
||||
rep stosd ; clear the workspace of the new voice, retriggering oscillators
|
||||
su_update_voices_nexttrack:
|
||||
pop {{.BX}} ; ebx=first voice of next instrument, Stack: ptrnrow
|
||||
pop {{.DX}} ; edx=patrnrow
|
||||
add {{.SI}}, {{.Song.SequenceLength}}
|
||||
inc {{.BP}}
|
||||
{{- $addrname := len .Song.Tracks | printf "su_synth_obj + %v"}}
|
||||
{{- .Prepare $addrname | indent 8}}
|
||||
cmp {{.BP}},{{.Use $addrname}}
|
||||
jl su_update_voices_trackloop
|
||||
ret
|
||||
{{- else}}
|
||||
; The simple implementation: each track triggers always the same voice
|
||||
xor edx, edx
|
||||
xor ebx, ebx
|
||||
mov bl, {{.Song.PatternRows}} ; rows per pattern
|
||||
div ebx ; eax = current pattern, edx = current row in pattern
|
||||
{{- .Prepare "su_tracks" | indent 4}}
|
||||
lea {{.SI}}, [{{.Use "su_tracks"}}+{{.AX}}]; esi points to the pattern data for current track
|
||||
mov {{.DI}}, {{.PTRWORD}} su_synth_obj+su_synthworkspace.voices
|
||||
mov bl, {{len .Song.Tracks}} ; MAX_TRACKS is always <= 32 so this is ok
|
||||
su_update_voices_trackloop:
|
||||
movzx eax, byte [{{.SI}}] ; eax = current pattern
|
||||
imul eax, {{.Song.PatternRows}} ; multiply by rows per pattern, eax = offset to current pattern data
|
||||
{{- .Prepare "su_patterns" .AX | indent 8}}
|
||||
movzx eax, byte [{{.Use "su_patterns" .AX}} + {{.DX}}] ; ecx = note
|
||||
cmp al, {{.Song.Hold}} ; anything but hold causes action
|
||||
je short su_update_voices_nexttrack
|
||||
inc dword [{{.DI}}+su_voice.release] ; set the voice currently active to release; notice that it could increment any number of times
|
||||
jb su_update_voices_nexttrack ; if cl < HLD (no new note triggered) goto nexttrack
|
||||
su_update_voices_retrigger:
|
||||
stosd ; save note
|
||||
mov ecx, (su_voice.size - su_voice.release)/4 ; could be xor ecx, ecx; mov ch,...>>8, but will it actually be smaller after compression?
|
||||
xor eax, eax
|
||||
rep stosd ; clear the workspace of the new voice, retriggering oscillators
|
||||
jmp short su_update_voices_skipadd
|
||||
su_update_voices_nexttrack:
|
||||
add {{.DI}}, su_voice.size
|
||||
su_update_voices_skipadd:
|
||||
add {{.SI}}, {{.Song.SequenceLength}}
|
||||
dec ebx
|
||||
jnz short su_update_voices_trackloop
|
||||
ret
|
||||
{{- end}}
|
||||
|
||||
{{template "patch.asm" .}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; Patterns
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_patterns"}}
|
||||
{{- range .Song.Patterns}}
|
||||
db {{. | toStrings | join ","}}
|
||||
{{- end}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; Tracks
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_tracks"}}
|
||||
{{- range .Song.Tracks}}
|
||||
db {{.Sequence | toStrings | join ","}}
|
||||
{{- end}}
|
||||
|
||||
{{- if gt (.Song.Patch.SampleOffsets | len) 0}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; Sample offsets
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_sample_offsets"}}
|
||||
{{- range .Song.Patch.SampleOffsets}}
|
||||
dd {{.Start}}
|
||||
dw {{.LoopStart}}
|
||||
dw {{.LoopLength}}
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
{{- if gt (.Song.Patch.DelayTimes | len ) 0}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; Delay times
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_delay_times"}}
|
||||
dw {{.Song.Patch.DelayTimes | toStrings | join ","}}
|
||||
{{end}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; The code for this patch, basically indices to vm jump table
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_patch_code"}}
|
||||
db {{.Code | toStrings | join ","}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; The parameters / inputs to each opcode
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_patch_parameters"}}
|
||||
db {{.Values | toStrings | join ","}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; Constants
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.SectData "constants"}}
|
||||
{{.Constants}}
|
126
templates/sinks.asm
Normal file
126
templates/sinks.asm
Normal file
@ -0,0 +1,126 @@
|
||||
{{- if .Opcode "out"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; OUT opcode: outputs and pops the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
{{- if .Mono "out"}}
|
||||
; Mono: add ST0 to main left port, then pop
|
||||
{{- end}}
|
||||
{{- if .Stereo "out"}}
|
||||
; Stereo: add ST0 to left out and ST1 to right out, then pop
|
||||
{{- end}}
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_out" "Opcode"}} ; l r
|
||||
mov {{.AX}}, [{{.Stack "Synth"}}] ; AX points to the synth object
|
||||
{{- if .StereoAndMono "out" }}
|
||||
jnc su_op_out_mono
|
||||
{{- end }}
|
||||
{{- if .Stereo "out" }}
|
||||
call su_op_out_mono
|
||||
add {{.AX}}, 4 ; shift from left to right channel
|
||||
su_op_out_mono:
|
||||
{{- end}}
|
||||
fmul dword [{{.Input "out" "gain"}}] ; multiply by gain
|
||||
fadd dword [{{.AX}} + su_synthworkspace.left] ; add current value of the output
|
||||
fstp dword [{{.AX}} + su_synthworkspace.left] ; store the new value of the output
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "outaux"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; OUTAUX opcode: outputs to main and aux1 outputs and pops the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: add outgain*ST0 to main left port and auxgain*ST0 to aux1 left
|
||||
; Stereo: also add outgain*ST1 to main right port and auxgain*ST1 to aux1 right
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_outaux" "Opcode"}} ; l r
|
||||
mov {{.AX}}, [{{.Stack "Synth"}}]
|
||||
{{- if .StereoAndMono "outaux" }}
|
||||
jnc su_op_outaux_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "outaux" }}
|
||||
call su_op_outaux_mono
|
||||
add {{.AX}}, 4
|
||||
su_op_outaux_mono:
|
||||
{{- end}}
|
||||
fld st0 ; l l
|
||||
fmul dword [{{.Input "outaux" "outgain"}}] ; g*l
|
||||
fadd dword [{{.AX}} + su_synthworkspace.left] ; g*l+o
|
||||
fstp dword [{{.AX}} + su_synthworkspace.left] ; o'=g*l+o
|
||||
fmul dword [{{.Input "outaux" "auxgain"}}] ; h*l
|
||||
fadd dword [{{.AX}} + su_synthworkspace.aux] ; h*l+a
|
||||
fstp dword [{{.AX}} + su_synthworkspace.aux] ; a'=h*l+a
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "aux"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; AUX opcode: outputs the signal to aux (or main) port and pops the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: add gain*ST0 to left port
|
||||
; Stereo: also add gain*ST1 to right port
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_aux" "Opcode"}} ; l r
|
||||
lodsb
|
||||
mov {{.DI}}, [{{.Stack "Synth"}}]
|
||||
{{- if .StereoAndMono "aux" }}
|
||||
jnc su_op_aux_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "aux" }}
|
||||
call su_op_aux_mono
|
||||
add {{.DI}}, 4
|
||||
su_op_aux_mono:
|
||||
{{- end}}
|
||||
fmul dword [{{.Input "aux" "gain"}}] ; g*l
|
||||
fadd dword [{{.DI}} + su_synthworkspace.left + {{.AX}}*4] ; g*l+o
|
||||
fstp dword [{{.DI}} + su_synthworkspace.left + {{.AX}}*4] ; o'=g*l+o
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "send"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; SEND opcode: adds the signal to a port
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: adds signal to a memory address, defined by a word in VAL stream
|
||||
; Stereo: also add right signal to the following address
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_send" "Opcode"}}
|
||||
lodsw
|
||||
mov {{.CX}}, [{{.Stack "Voice"}}] ; load pointer to voice
|
||||
{{- if .StereoAndMono "send"}}
|
||||
jnc su_op_send_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "send"}}
|
||||
mov {{.DI}}, {{.AX}}
|
||||
inc {{.AX}} ; send the right channel first
|
||||
fxch ; r l
|
||||
call su_op_send_mono ; (r) l
|
||||
mov {{.AX}}, {{.DI}} ; move back to original address
|
||||
test {{.AX}}, 0x8 ; if r was not popped and is still in the stack
|
||||
jnz su_op_send_mono
|
||||
fxch ; swap them back: l r
|
||||
su_op_send_mono:
|
||||
{{- end}}
|
||||
{{- if .HasParamValueOtherThan "send" "voice" 0}}
|
||||
test {{.AX}}, 0x8000
|
||||
jz su_op_send_skipglobal
|
||||
mov {{.CX}}, [{{.Stack "Synth"}}]
|
||||
su_op_send_skipglobal:
|
||||
{{- end}}
|
||||
test {{.AX}}, 0x8 ; if the SEND_POP bit is not set
|
||||
jnz su_op_send_skippush
|
||||
fld st0 ; duplicate the signal on stack: s s
|
||||
su_op_send_skippush: ; there is signal s, but maybe also another: s (s)
|
||||
fld dword [{{.Input "send" "amount"}}] ; a l (l)
|
||||
{{- .Float 0.5 | .Prepare | indent 4}}
|
||||
fsub dword [{{.Float 0.5 | .Use}}] ; a-.5 l (l)
|
||||
fadd st0 ; g=2*a-1 l (l)
|
||||
and ah, 0x7f ; eax = send address, clear the global bit
|
||||
or al, 0x8 ; set the POP bit always, at the same time shifting to ports instead of wrk
|
||||
fmulp st1, st0 ; g*l (l)
|
||||
fadd dword [{{.CX}} + {{.AX}}*4] ; g*l+L (l),where L is the current value
|
||||
fstp dword [{{.CX}} + {{.AX}}*4] ; (l)
|
||||
ret
|
||||
{{end}}
|
415
templates/sources.asm
Normal file
415
templates/sources.asm
Normal file
@ -0,0 +1,415 @@
|
||||
{{if .Opcode "envelope" -}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; ENVELOPE opcode: pushes an ADSR envelope value on stack [0,1]
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: push the envelope value on stack
|
||||
; Stereo: push the envelope valeu on stack twice
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_envelope" "Opcode"}}
|
||||
{{- if .StereoAndMono "envelope"}}
|
||||
jnc su_op_envelope_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "envelope"}}
|
||||
call su_op_envelope_mono
|
||||
fld st0
|
||||
ret
|
||||
su_op_envelope_mono:
|
||||
{{- end}}
|
||||
mov eax, dword [{{.INP}}-su_voice.inputs+su_voice.release] ; eax = su_instrument.release
|
||||
test eax, eax ; if (eax == 0)
|
||||
je su_op_envelope_process ; goto process
|
||||
mov dword [{{.WRK}}], {{.InputNumber "envelope" "release"}} ; [state]=RELEASE
|
||||
su_op_envelope_process:
|
||||
mov eax, dword [{{.WRK}}] ; al=[state]
|
||||
fld dword [{{.WRK}}+4] ; x=[level]
|
||||
cmp al, {{.InputNumber "envelope" "sustain"}} ; if (al==SUSTAIN)
|
||||
je short su_op_envelope_leave2 ; goto leave2
|
||||
su_op_envelope_attac:
|
||||
cmp al, {{.InputNumber "envelope" "attack"}} ; if (al!=ATTAC)
|
||||
jne short su_op_envelope_decay ; goto decay
|
||||
{{.Call "su_nonlinear_map"}} ; a x, where a=attack
|
||||
faddp st1, st0 ; a+x
|
||||
fld1 ; 1 a+x
|
||||
fucomi st1 ; if (a+x<=1) // is attack complete?
|
||||
fcmovnb st0, st1 ; a+x a+x
|
||||
jbe short su_op_envelope_statechange ; else goto statechange
|
||||
su_op_envelope_decay:
|
||||
cmp al, {{.InputNumber "envelope" "decay"}} ; if (al!=DECAY)
|
||||
jne short su_op_envelope_release ; goto release
|
||||
{{.Call "su_nonlinear_map"}} ; d x, where d=decay
|
||||
fsubp st1, st0 ; x-d
|
||||
fld dword [{{.Input "envelope" "sustain"}}] ; s x-d, where s=sustain
|
||||
fucomi st1 ; if (x-d>s) // is decay complete?
|
||||
fcmovb st0, st1 ; x-d x-d
|
||||
jnc short su_op_envelope_statechange ; else goto statechange
|
||||
su_op_envelope_release:
|
||||
cmp al, {{.InputNumber "envelope" "release"}} ; if (al!=RELEASE)
|
||||
jne short su_op_envelope_leave ; goto leave
|
||||
{{.Call "su_nonlinear_map"}} ; r x, where r=release
|
||||
fsubp st1, st0 ; x-r
|
||||
fldz ; 0 x-r
|
||||
fucomi st1 ; if (x-r>0) // is release complete?
|
||||
fcmovb st0, st1 ; x-r x-r, then goto leave
|
||||
jc short su_op_envelope_leave
|
||||
su_op_envelope_statechange:
|
||||
inc dword [{{.WRK}}] ; [state]++
|
||||
su_op_envelope_leave:
|
||||
fstp st1 ; x', where x' is the new value
|
||||
fst dword [{{.WRK}}+4] ; [level]=x'
|
||||
su_op_envelope_leave2:
|
||||
fmul dword [{{.Input "envelope" "gain"}}] ; [gain]*x'
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "noise"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; NOISE opcode: creates noise
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: push a random value [-1,1] value on stack
|
||||
; Stereo: push two (differeent) random values on stack
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_noise" "Opcode"}}
|
||||
lea {{.CX}},[{{.Stack "RandSeed"}}]
|
||||
{{- if .StereoAndMono "noise"}}
|
||||
jnc su_op_noise_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "noise"}}
|
||||
call su_op_noise_mono
|
||||
su_op_noise_mono:
|
||||
{{- end}}
|
||||
imul eax, [{{.CX}}],16007
|
||||
mov [{{.CX}}],eax
|
||||
fild dword [{{.CX}}]
|
||||
{{- .Prepare (.Int 2147483648)}}
|
||||
fidiv dword [{{.Use (.Int 2147483648)}}] ; 65536*32768
|
||||
fld dword [{{.Input "noise" "shape"}}]
|
||||
{{.Call "su_waveshaper"}}
|
||||
fld dword [{{.Input "noise" "gain"}}]
|
||||
fmulp st1, st0
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "oscillator"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; OSCILLAT opcode: oscillator, the heart of the synth
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: push oscillator value on stack
|
||||
; Stereo: push l r on stack, where l has opposite detune compared to r
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_oscillator" "Opcode"}}
|
||||
lodsb ; load the flags
|
||||
%ifdef RUNTIME_TABLES
|
||||
%ifdef INCLUDE_SAMPLES
|
||||
mov {{.DI}}, [{{.SP}} + su_stack.sampleoffs]; we need to put this in a register, as the stereo & unisons screw the stack positions
|
||||
%endif ; ain't we lucky that {{.DI}} was unused throughout
|
||||
%endif
|
||||
fld dword [{{.Input "oscillator" "detune"}}] ; e, where e is the detune [0,1]
|
||||
{{- .Prepare (.Float 0.5)}}
|
||||
fsub dword [{{.Use (.Float 0.5)}}] ; e-.5
|
||||
fadd st0, st0 ; d=2*e-.5, where d is the detune [-1,1]
|
||||
{{- if .StereoAndMono "oscillator"}}
|
||||
jnc su_op_oscillat_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "oscillator"}}
|
||||
fld st0 ; d d
|
||||
call su_op_oscillat_mono ; r d
|
||||
add {{.WRK}}, 4 ; state vars: r1 l1 r2 l2 r3 l3 r4 l4, for the unison osc phases
|
||||
fxch ; d r
|
||||
fchs ; -d r, negate the detune for second round
|
||||
su_op_oscillat_mono:
|
||||
{{- end}}
|
||||
{{- if .HasParamValueOtherThan "oscillator" "unison" 0}}
|
||||
{{.PushRegs .AX "" .WRK "OscWRK" .AX "OscFlags"}}
|
||||
fldz ; 0 d
|
||||
fxch ; d a=0, "accumulated signal"
|
||||
su_op_oscillat_unison_loop:
|
||||
fst dword [{{.SP}}] ; save the current detune, d. We could keep it in fpu stack but it was getting big.
|
||||
call su_op_oscillat_single ; s a
|
||||
faddp st1, st0 ; a+=s
|
||||
test al, 3
|
||||
je su_op_oscillat_unison_out
|
||||
add {{.WRK}}, 8
|
||||
fld dword [{{.Input "oscillator" "phase"}}] ; p s
|
||||
{{.Int 0x3DAAAAAA | .Prepare}}
|
||||
fadd dword [{{.Int 0x3DAAAAAA | .Use}}] ; 1/128 p s, add some little phase offset to unison oscillators so they don't start in sync
|
||||
fstp dword [{{.Input "oscillator" "phase"}}] ; s note that this changes the phase for second, possible stereo run. That's probably ok
|
||||
fld dword [{{.SP}}] ; d s
|
||||
{{.Float 0.5 | .Prepare}}
|
||||
fmul dword [{{.Float 0.5 | .Use}}] ; .5*d s // negate and halve the detune of each oscillator
|
||||
fchs ; -.5*d s // negate and halve the detune of each oscillator
|
||||
dec eax
|
||||
jmp short su_op_oscillat_unison_loop
|
||||
su_op_oscillat_unison_out:
|
||||
{{.PopRegs .AX .WRK .AX}}
|
||||
ret
|
||||
su_op_oscillat_single:
|
||||
{{- end}}
|
||||
fld dword [{{.Input "oscillator" "transpose"}}]
|
||||
{{- .Float 0.5 | .Prepare}}
|
||||
fsub dword [{{.Float 0.5 | .Use}}]
|
||||
{{- .Float 0.0078125 | .Prepare}}
|
||||
fdiv dword [{{.Float 0.0078125 | .Use}}]
|
||||
faddp st1
|
||||
test al, byte 0x08
|
||||
jnz su_op_oscillat_skipnote
|
||||
fiadd dword [{{.INP}}-su_voice.inputs+su_voice.note] ; // st0 is note, st1 is t+d offset
|
||||
su_op_oscillat_skipnote:
|
||||
{{- .Int 0x3DAAAAAA | .Prepare}}
|
||||
fmul dword [{{.Int 0x3DAAAAAA | .Use}}]
|
||||
{{.Call "su_power"}}
|
||||
test al, byte 0x08
|
||||
jz short su_op_oscillat_normalize_note
|
||||
{{- .Float 0.000038 | .Prepare}}
|
||||
fmul dword [{{.Float 0.000038 | .Use}}] ; // st0 is now frequency for lfo
|
||||
jmp short su_op_oscillat_normalized
|
||||
su_op_oscillat_normalize_note:
|
||||
{{- .Float 0.000092696138 | .Prepare}}
|
||||
fmul dword [{{.Float 0.000092696138 | .Use}}] ; // st0 is now frequency
|
||||
su_op_oscillat_normalized:
|
||||
fadd dword [{{.WRK}}]
|
||||
fst dword [{{.WRK}}]
|
||||
fadd dword [{{.Input "oscillator" "phase"}}]
|
||||
{{- if .HasParamValue "oscillator" "type" .Sample}}
|
||||
test al, byte 0x80
|
||||
jz short su_op_oscillat_not_sample
|
||||
{{.Call "su_oscillat_sample"}}
|
||||
jmp su_op_oscillat_shaping ; skip the rest to avoid color phase normalization and colorloading
|
||||
su_op_oscillat_not_sample:
|
||||
{{- end}}
|
||||
fld1
|
||||
fadd st1, st0
|
||||
fxch
|
||||
fprem
|
||||
fstp st1
|
||||
fld dword [{{.Input "oscillator" "color"}}] ; // c p
|
||||
; every oscillator test included if needed
|
||||
{{- if .HasParamValue "oscillator" "type" .Sine}}
|
||||
test al, byte 0x40
|
||||
jz short su_op_oscillat_notsine
|
||||
{{.Call "su_oscillat_sine"}}
|
||||
su_op_oscillat_notsine:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "oscillator" "type" .Trisaw}}
|
||||
test al, byte 0x20
|
||||
jz short su_op_oscillat_not_trisaw
|
||||
{{.Call "su_oscillat_trisaw"}}
|
||||
su_op_oscillat_not_trisaw:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "oscillator" "type" .Pulse}}
|
||||
test al, byte 0x10
|
||||
jz short su_op_oscillat_not_pulse
|
||||
{{.Call "su_oscillat_pulse"}}
|
||||
su_op_oscillat_not_pulse:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "oscillator" "type" .Gate}}
|
||||
test al, byte 0x04
|
||||
jz short su_op_oscillat_not_gate
|
||||
{{.Call "su_oscillat_gate"}}
|
||||
jmp su_op_oscillat_gain ; skip waveshaping as the shape parameter is reused for gateshigh
|
||||
su_op_oscillat_not_gate:
|
||||
{{- end}}
|
||||
su_op_oscillat_shaping:
|
||||
; finally, shape the oscillator and apply gain
|
||||
fld dword [{{.Input "oscillator" "shape"}}]
|
||||
{{.Call "su_waveshaper"}}
|
||||
su_op_oscillat_gain:
|
||||
fld dword [{{.Input "oscillator" "gain"}}]
|
||||
fmulp st1, st0
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasCall "su_oscillat_pulse"}}
|
||||
{{.Func "su_oscillat_pulse"}}
|
||||
fucomi st1 ; // c p
|
||||
fld1
|
||||
jnc short su_oscillat_pulse_up ; // +1 c p
|
||||
fchs ; // -1 c p
|
||||
su_oscillat_pulse_up:
|
||||
fstp st1 ; // +-1 p
|
||||
fstp st1 ; // +-1
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasCall "su_oscillat_trisaw"}}
|
||||
{{.Func "su_oscillat_trisaw"}}
|
||||
fucomi st1 ; // c p
|
||||
jnc short su_oscillat_trisaw_up
|
||||
fld1 ; // 1 c p
|
||||
fsubr st2, st0 ; // 1 c 1-p
|
||||
fsubrp st1, st0 ; // 1-c 1-p
|
||||
su_oscillat_trisaw_up:
|
||||
fdivp st1, st0 ; // tp'/tc
|
||||
fadd st0 ; // 2*''
|
||||
fld1 ; // 1 2*''
|
||||
fsubp st1, st0 ; // 2*''-1
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasCall "su_oscillat_sine"}}
|
||||
{{.Func "su_oscillat_sine"}}
|
||||
fucomi st1 ; // c p
|
||||
jnc short su_oscillat_sine_do
|
||||
fstp st1
|
||||
fsub st0, st0 ; // 0
|
||||
ret
|
||||
su_oscillat_sine_do:
|
||||
fdivp st1, st0 ; // p/c
|
||||
fldpi ; // pi p
|
||||
fadd st0 ; // 2*pi p
|
||||
fmulp st1, st0 ; // 2*pi*p
|
||||
fsin ; // sin(2*pi*p)
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasCall "su_oscillat_gate"}}
|
||||
{{.Func "su_oscillat_gate"}}
|
||||
fxch ; p c
|
||||
fstp st1 ; p
|
||||
{{- .Float 16.0 | .Prepare | indent 4}}
|
||||
fmul dword [{{.Float 16.0 | .Use}}] ; 16*p
|
||||
push {{.AX}}
|
||||
push {{.AX}}
|
||||
fistp dword [{{.SP}}] ; s=int(16*p), stack empty
|
||||
fld1 ; 1
|
||||
pop {{.AX}}
|
||||
and al, 0xf ; ax=int(16*p) & 15, stack: 1
|
||||
bt word [{{.VAL}}-4],ax ; if bit ax of the gate word is set
|
||||
jc go4kVCO_gate_bit ; goto gate_bit
|
||||
fsub st0, st0 ; stack: 0
|
||||
go4kVCO_gate_bit: ; stack: 0/1, let's call it x
|
||||
fld dword [{{.WRK}}+16] ; g x, g is gatestate, x is the input to this filter 0/1
|
||||
fsub st1 ; g-x x
|
||||
{{- .Float 0.99609375 | .Prepare | indent 4}}
|
||||
fmul dword [{{.Float 0.99609375 | .Use}}] ; c(g-x) x
|
||||
faddp st1, st0 ; x+c(g-x)
|
||||
fst dword [{{.WRK}}+16]; g'=x+c(g-x) NOTE THAT UNISON 2 & UNISON 3 ALSO USE {{.WRK}}+16, so gate and unison 2 & 3 don't work. Probably should delete that low pass altogether
|
||||
pop {{.AX}} ; Another way to see this (c~0.996)
|
||||
ret ; g'=cg+(1-c)x
|
||||
; This is a low-pass to smooth the gate transitions
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .HasCall "su_oscillat_sample"}}
|
||||
{{.Func "su_oscillat_sample"}}
|
||||
{{- .PushRegs .AX "SampleAx" .DX "SampleDx" .CX "SampleCx" .BX "SampleBx" | indent 4}} ; edx must be saved, eax & ecx if this is stereo osc
|
||||
push {{.AX}}
|
||||
mov al, byte [{{.VAL}}-4] ; reuse "color" as the sample number
|
||||
%ifdef RUNTIME_TABLES ; when using RUNTIME_TABLES, assumed the sample_offset ptr is in {{.DI}}
|
||||
do{lea {{.DI}}, [}, {{.DI}}, {{.AX}}*8,] ; edi points now to the sample table entry
|
||||
%else
|
||||
{{- .Prepare "su_sample_offsets" | indent 4}}
|
||||
lea {{.DI}}, [{{.Use "su_sample_offsets"}} + {{.AX}}*8]; edi points now to the sample table entry
|
||||
%endif
|
||||
{{- .Float 84.28074964676522 | .Prepare | indent 4}}
|
||||
fmul dword [{{.Float 84.28074964676522 | .Use}}] ; p*r
|
||||
fistp dword [{{.SP}}]
|
||||
pop {{.DX}} ; edx is now the sample number
|
||||
movzx ebx, word [{{.DI}} + 4] ; ecx = loopstart
|
||||
sub edx, ebx ; if sample number < loop start
|
||||
jl su_oscillat_sample_not_looping ; then we're not looping yet
|
||||
mov eax, edx ; eax = sample number
|
||||
movzx ecx, word [{{.DI}} + 6] ; edi is now the loop length
|
||||
xor edx, edx ; div wants edx to be empty
|
||||
div ecx ; edx is now the remainder
|
||||
su_oscillat_sample_not_looping:
|
||||
add edx, ebx ; sampleno += loopstart
|
||||
add edx, dword [{{.DI}}]
|
||||
{{- .Prepare "su_sample_table" | indent 4}}
|
||||
fild word [{{.Use "su_sample_table"}} + {{.DX}}*2]
|
||||
{{- .Float 32767.0 | .Prepare | indent 4}}
|
||||
fdiv dword [{{.Float 32767.0 | .Use}}]
|
||||
{{- .PopRegs .AX .DX .CX .BX | indent 4}}
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "loadval"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; LOADVAL opcode
|
||||
;-------------------------------------------------------------------------------
|
||||
{{- if .Mono "loadval"}}
|
||||
; Mono: push 2*v-1 on stack, where v is the input to port "value"
|
||||
{{- end}}
|
||||
{{- if .Stereo "loadval"}}
|
||||
; Stereo: push 2*v-1 twice on stack
|
||||
{{- end}}
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_loadval" "Opcode"}}
|
||||
{{- if .StereoAndMono "loadval" }}
|
||||
jnc su_op_loadval_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "loadval" }}
|
||||
call su_op_loadval_mono
|
||||
su_op_loadval_mono:
|
||||
{{- end }}
|
||||
fld dword [{{.Input "loadval" "value"}}] ; v
|
||||
{{- .Float 0.5 | .Prepare | indent 4}}
|
||||
fsub dword [{{.Float 0.5 | .Use}}]
|
||||
fadd st0 ; 2*v-1
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "receive"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; RECEIVE opcode
|
||||
;-------------------------------------------------------------------------------
|
||||
{{- if .Mono "receive"}}
|
||||
; Mono: push l on stack, where l is the left channel received
|
||||
{{- end}}
|
||||
{{- if .Stereo "receive"}}
|
||||
; Stereo: push l r on stack
|
||||
{{- end}}
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_receive" "Opcode"}}
|
||||
lea {{.DI}}, [{{.WRK}}+su_unit.ports]
|
||||
{{- if .StereoAndMono "receive"}}
|
||||
jnc su_op_receive_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "receive"}}
|
||||
xor ecx,ecx
|
||||
fld dword [{{.DI}}+4]
|
||||
mov dword [{{.DI}}+4],ecx
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "receive"}}
|
||||
su_op_receive_mono:
|
||||
xor ecx,ecx
|
||||
{{- end}}
|
||||
fld dword [{{.DI}}]
|
||||
mov dword [{{.DI}}],ecx
|
||||
ret
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "in"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; IN opcode: inputs and clears a global port
|
||||
;-------------------------------------------------------------------------------
|
||||
; Mono: push the left channel of a global port (out or aux)
|
||||
; Stereo: also push the right channel (stack in l r order)
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_in" "Opcode"}}
|
||||
lodsb
|
||||
mov {{.DI}}, [{{.Stack "Synth"}}]
|
||||
{{- if .StereoAndMono "in"}}
|
||||
jnc su_op_in_mono
|
||||
{{- end}}
|
||||
{{- if .Stereo "in"}}
|
||||
xor ecx, ecx ; we cannot xor before jnc, so we have to do it mono & stereo. LAHF / SAHF could do it, but is the same number of bytes with more entropy
|
||||
fld dword [{{.DI}} + su_synthworkspace.right + {{.AX}}*4]
|
||||
mov dword [{{.DI}} + su_synthworkspace.right + {{.AX}}*4], ecx
|
||||
{{- end}}
|
||||
{{- if .StereoAndMono "in"}}
|
||||
su_op_in_mono:
|
||||
xor ecx, ecx
|
||||
{{- end}}
|
||||
fld dword [{{.DI}} + su_synthworkspace.left + {{.AX}}*4]
|
||||
mov dword [{{.DI}} + su_synthworkspace.left + {{.AX}}*4], ecx
|
||||
ret
|
||||
{{end}}
|
43
templates/structs.asm
Normal file
43
templates/structs.asm
Normal file
@ -0,0 +1,43 @@
|
||||
;-------------------------------------------------------------------------------
|
||||
; unit struct
|
||||
;-------------------------------------------------------------------------------
|
||||
struc su_unit
|
||||
.state resd 8
|
||||
.ports resd 8
|
||||
.size:
|
||||
endstruc
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; voice struct
|
||||
;-------------------------------------------------------------------------------
|
||||
struc su_voice
|
||||
.note resd 1
|
||||
.release resd 1
|
||||
.inputs resd 8
|
||||
.reserved resd 6 ; this is done to so the whole voice is 2^n long, see polyphonic player
|
||||
.workspace resb 63 * su_unit.size
|
||||
.size:
|
||||
endstruc
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; synthworkspace struct
|
||||
;-------------------------------------------------------------------------------
|
||||
struc su_synthworkspace
|
||||
.curvoices resb 32 ; these are used by the multitrack player to store which voice is playing on which track
|
||||
.left resd 1
|
||||
.right resd 1
|
||||
.aux resd 6 ; 3 auxiliary signals
|
||||
.voices resb 32 * su_voice.size
|
||||
.size:
|
||||
endstruc
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; su_delayline_wrk struct
|
||||
;-------------------------------------------------------------------------------
|
||||
struc su_delayline_wrk
|
||||
.dcin resd 1
|
||||
.dcout resd 1
|
||||
.filtstate resd 1
|
||||
.buffer resd 65536
|
||||
.size:
|
||||
endstruc
|
@ -1,33 +1,30 @@
|
||||
function(regression_test testname)
|
||||
function(regression_test testname)
|
||||
|
||||
if(${ARGC} LESS 4)
|
||||
set(source ${testname}.yml)
|
||||
set(asmfile ${testname}.asm)
|
||||
set (headerfile ${CMAKE_CURRENT_BINARY_DIR}/${testname}.h)
|
||||
add_custom_command(
|
||||
PRE_BUILD
|
||||
OUTPUT ${headerfile}
|
||||
COMMAND go run ${PROJECT_SOURCE_DIR}/go4k/cmd/sointu-cli/main.go -c -w -d ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${source}
|
||||
DEPENDS ${source}
|
||||
)
|
||||
add_custom_command(
|
||||
PRE_BUILD
|
||||
|
||||
if(DEFINED CMAKE_C_SIZEOF_DATA_PTR AND CMAKE_C_SIZEOF_DATA_PTR EQUAL 8)
|
||||
set(arch "-arch=amd64")
|
||||
elseif(DEFINED CMAKE_CXX_SIZEOF_DATA_PTR AND CMAKE_CXX_SIZEOF_DATA_PTR EQUAL 8)
|
||||
set(arch "-arch=amd64")
|
||||
else()
|
||||
set(arch "-arch=386")
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${asmfile}
|
||||
COMMAND go run ${PROJECT_SOURCE_DIR}/go4k/cmd/sointu-cli/main.go -a -w -d ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${source}
|
||||
DEPENDS ${source}
|
||||
COMMAND ${sointuexe} -a -c -w ${arch} -d ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${source}
|
||||
DEPENDS ${source} ${sointuexe}
|
||||
)
|
||||
add_executable(${testname} test_renderer.c ${headerfile} ${asmfile})
|
||||
|
||||
add_executable(${testname} test_renderer.c ${asmfile})
|
||||
target_compile_definitions(${testname} PUBLIC TEST_HEADER=<${testname}.h>)
|
||||
else()
|
||||
set(source ${ARGV3})
|
||||
add_executable(${testname} ${source} test_renderer.c)
|
||||
endif()
|
||||
|
||||
# the tests include the entire ASM but we still want to rebuild when they change
|
||||
file(GLOB SOINTU ${PROJECT_SOURCE_DIR}/include/sointu/*.inc
|
||||
${PROJECT_SOURCE_DIR}/include/sointu/win32/*.inc
|
||||
${PROJECT_SOURCE_DIR}/include/sointu/win64/*.inc)
|
||||
set_source_files_properties(${source}.asm PROPERTIES OBJECT_DEPENDS "${SOINTU}")
|
||||
set_source_files_properties(${FOURKLANG} PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
endif()
|
||||
|
||||
add_test(${testname} ${testname})
|
||||
target_link_libraries(${testname} ${HEADERLIB})
|
||||
@ -39,11 +36,11 @@ function(regression_test testname)
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${rawinput} ${rawoutput}
|
||||
)
|
||||
|
||||
target_include_directories(${testname} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_compile_definitions(${testname} PUBLIC TEST_NAME="${testname}")
|
||||
|
||||
add_dependencies(${testname} ${testname}_rawcopy)
|
||||
|
||||
target_include_directories(${testname} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_compile_definitions(${testname} PUBLIC TEST_NAME="${testname}")
|
||||
|
||||
if(ARGC GREATER 1)
|
||||
if (ARGV1)
|
||||
message("${testname} requires ${ARGV1}")
|
||||
@ -59,6 +56,27 @@ function(regression_test testname)
|
||||
endif()
|
||||
endfunction(regression_test)
|
||||
|
||||
if(WIN32)
|
||||
set(sointuexe ${CMAKE_CURRENT_BINARY_DIR}/sointu-cli.exe)
|
||||
else()
|
||||
set(sointuexe ${CMAKE_CURRENT_BINARY_DIR}/sointu-cli)
|
||||
endif()
|
||||
|
||||
# the tests include the entire ASM but we still want to rebuild when they change
|
||||
file(GLOB templates ${PROJECT_SOURCE_DIR}/templates/*.asm)
|
||||
file(GLOB go4k "${PROJECT_SOURCE_DIR}/go4k/*.go" "${PROJECT_SOURCE_DIR}/go4k/cmd/sointu-cli/*.go")
|
||||
|
||||
message("templates=${templates}")
|
||||
message("go4k=${go4k}")
|
||||
|
||||
# Build sointu-cli only once because go run has everytime quite a bit of delay when
|
||||
# starting
|
||||
add_custom_command(
|
||||
OUTPUT ${sointuexe}
|
||||
COMMAND go build -o ${sointuexe} ${PROJECT_SOURCE_DIR}/go4k/cmd/sointu-cli/main.go
|
||||
DEPENDS "${templates}" "${go4k}"
|
||||
)
|
||||
|
||||
regression_test(test_envelope "" ENVELOPE)
|
||||
regression_test(test_envelope_stereo ENVELOPE)
|
||||
regression_test(test_loadval "" LOADVAL)
|
||||
|
@ -24,6 +24,6 @@ patch:
|
||||
- type: oscillator
|
||||
parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 50, type: 0, unison: 0}
|
||||
- type: send
|
||||
parameters: {amount: 65, port: 5, sendpop: 1, stereo: 0, unit: 3, voice: 0}
|
||||
parameters: {amount: 65, port: 4, sendpop: 1, stereo: 0, unit: 3, voice: 0}
|
||||
delaytimes: [1000]
|
||||
sampleoffsets: []
|
||||
|
@ -1,16 +1,17 @@
|
||||
#include <stdio.h>
|
||||
#if defined (_WIN32)
|
||||
#define _CRT_SECURE_NO_DEPRECATE
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#if defined (_WIN32)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
|
||||
#include TEST_HEADER
|
||||
SUsample buf[SU_BUFFER_LENGTH];
|
||||
@ -20,7 +21,6 @@ int main(int argc, char* argv[]) {
|
||||
FILE* f;
|
||||
char filename[256];
|
||||
int n;
|
||||
int retval;
|
||||
char test_name[] = TEST_NAME;
|
||||
char expected_output_folder[] = "expected_output/";
|
||||
char actual_output_folder[] = "actual_output/";
|
||||
@ -64,7 +64,7 @@ int main(int argc, char* argv[]) {
|
||||
max_diff = 0.0f;
|
||||
|
||||
for (n = 0; n < SU_BUFFER_LENGTH; n++) {
|
||||
diff = fabs((float)(buf[n] - filebuf[n])/SU_SAMPLE_RANGE);
|
||||
diff = (float)fabs((float)(buf[n] - filebuf[n])/SU_SAMPLE_RANGE);
|
||||
if (diff > 1e-3f || isnan(diff)) {
|
||||
printf("Sointu rendered different wave than expected\n");
|
||||
goto fail;
|
||||
|
Loading…
Reference in New Issue
Block a user