mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-18 21:14:31 -04:00
refactor(go): Move everything from go4k to root package sointu
This commit is contained in:
81
compiler/compiler.go
Normal file
81
compiler/compiler.go
Normal file
@ -0,0 +1,81 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
"github.com/vsariola/sointu"
|
||||
)
|
||||
|
||||
//go:generate go run generate.go
|
||||
|
||||
type Compiler struct {
|
||||
Template *template.Template
|
||||
Amd64 bool
|
||||
OS string
|
||||
DisableSections bool
|
||||
}
|
||||
|
||||
// New returns a new compiler using the default .asm templates
|
||||
func New() (*Compiler, error) {
|
||||
_, myname, _, _ := runtime.Caller(0)
|
||||
templateDir := filepath.Join(path.Dir(myname), "..", "templates")
|
||||
compiler, err := NewFromTemplates(templateDir)
|
||||
return compiler, err
|
||||
}
|
||||
|
||||
func NewFromTemplates(directory string) (*Compiler, error) {
|
||||
globPtrn := filepath.Join(directory, "*.*")
|
||||
tmpl, err := template.New("base").Funcs(sprig.TxtFuncMap()).ParseGlob(globPtrn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`could not create template based on directory "%v": %v`, directory, err)
|
||||
}
|
||||
return &Compiler{Template: tmpl, Amd64: runtime.GOARCH == "amd64", OS: runtime.GOOS}, nil
|
||||
}
|
||||
|
||||
func (com *Compiler) compile(templateName string, data interface{}) (string, error) {
|
||||
result := bytes.NewBufferString("")
|
||||
err := com.Template.ExecuteTemplate(result, templateName, data)
|
||||
return result.String(), err
|
||||
}
|
||||
|
||||
func (com *Compiler) Library() (map[string]string, error) {
|
||||
features := AllFeatures{}
|
||||
m := NewMacros(*com, features)
|
||||
m.Library = true
|
||||
asmCode, err := com.compile("library.asm", m)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`could not execute template "library.asm": %v`, err)
|
||||
}
|
||||
|
||||
m = NewMacros(*com, features)
|
||||
m.Library = true
|
||||
header, err := com.compile("library.h", &m)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`could not execute template "library.h": %v`, err)
|
||||
}
|
||||
return map[string]string{"asm": asmCode, "h": header}, nil
|
||||
}
|
||||
|
||||
func (com *Compiler) Player(song *sointu.Song, maxSamples int) (map[string]string, error) {
|
||||
features := NecessaryFeaturesFor(song.Patch)
|
||||
encodedPatch, err := Encode(&song.Patch, features)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`could not encode patch: %v`, err)
|
||||
}
|
||||
asmCode, err := com.compile("player.asm", NewPlayerMacros(*com, features, song, encodedPatch, maxSamples))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`could not execute template "player.asm": %v`, err)
|
||||
}
|
||||
|
||||
header, err := com.compile("player.h", NewPlayerMacros(*com, features, song, encodedPatch, maxSamples))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`could not execute template "player.h": %v`, err)
|
||||
}
|
||||
return map[string]string{"asm": asmCode, "h": header}, nil
|
||||
}
|
144
compiler/encoded_patch.go
Normal file
144
compiler/encoded_patch.go
Normal file
@ -0,0 +1,144 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/vsariola/sointu"
|
||||
)
|
||||
|
||||
type EncodedPatch struct {
|
||||
Commands []byte
|
||||
Values []byte
|
||||
DelayTimes []uint16
|
||||
SampleOffsets []SampleOffset
|
||||
PolyphonyBitmask uint32
|
||||
NumVoices uint32
|
||||
}
|
||||
|
||||
type SampleOffset struct {
|
||||
Start uint32
|
||||
LoopStart uint16
|
||||
LoopLength uint16
|
||||
}
|
||||
|
||||
func Encode(patch *sointu.Patch, featureSet FeatureSet) (*EncodedPatch, error) {
|
||||
var c EncodedPatch
|
||||
sampleOffsetMap := map[SampleOffset]int{}
|
||||
for _, instr := range patch.Instruments {
|
||||
if len(instr.Units) > 63 {
|
||||
return nil, errors.New("An instrument can have a maximum of 63 units")
|
||||
}
|
||||
if instr.NumVoices < 1 {
|
||||
return nil, errors.New("Each instrument must have at least 1 voice")
|
||||
}
|
||||
for _, unit := range instr.Units {
|
||||
if unit.Type == "oscillator" && unit.Parameters["type"] == 4 {
|
||||
s := SampleOffset{Start: uint32(unit.Parameters["start"]), LoopStart: uint16(unit.Parameters["loopstart"]), LoopLength: uint16(unit.Parameters["looplength"])}
|
||||
index, ok := sampleOffsetMap[s]
|
||||
if !ok {
|
||||
index = len(c.SampleOffsets)
|
||||
sampleOffsetMap[s] = index
|
||||
c.SampleOffsets = append(c.SampleOffsets, s)
|
||||
}
|
||||
unit.Parameters["color"] = index
|
||||
}
|
||||
if unit.Type == "delay" {
|
||||
unit.Parameters["delay"] = len(c.DelayTimes)
|
||||
if unit.Parameters["stereo"] == 1 {
|
||||
unit.Parameters["count"] = len(unit.VarArgs) / 2
|
||||
} else {
|
||||
unit.Parameters["count"] = len(unit.VarArgs)
|
||||
}
|
||||
for _, v := range unit.VarArgs {
|
||||
c.DelayTimes = append(c.DelayTimes, uint16(v))
|
||||
}
|
||||
}
|
||||
command, values, err := EncodeUnit(unit, featureSet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`encoding unit failed: %v`, err)
|
||||
}
|
||||
c.Commands = append(c.Commands, command)
|
||||
c.Values = append(c.Values, values...)
|
||||
}
|
||||
c.Commands = append(c.Commands, byte(0)) // advance
|
||||
c.NumVoices += uint32(instr.NumVoices)
|
||||
for k := 0; k < instr.NumVoices-1; k++ {
|
||||
c.PolyphonyBitmask = (c.PolyphonyBitmask << 1) + 1
|
||||
}
|
||||
c.PolyphonyBitmask <<= 1
|
||||
}
|
||||
if c.NumVoices > 32 {
|
||||
return nil, fmt.Errorf("Sointu does not support more than 32 concurrent voices; patch uses %v", c.NumVoices)
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func EncodeUnit(unit sointu.Unit, featureSet FeatureSet) (byte, []byte, error) {
|
||||
opcode, ok := featureSet.Opcode(unit.Type)
|
||||
if !ok {
|
||||
return 0, nil, fmt.Errorf(`the targeted virtual machine is not configured to support unit type "%v"`, unit.Type)
|
||||
}
|
||||
var values []byte
|
||||
for _, v := range sointu.UnitTypes[unit.Type] {
|
||||
if v.CanModulate && v.CanSet {
|
||||
values = append(values, byte(unit.Parameters[v.Name]))
|
||||
}
|
||||
}
|
||||
if unit.Type == "aux" {
|
||||
values = append(values, byte(unit.Parameters["channel"]))
|
||||
} else if unit.Type == "in" {
|
||||
values = append(values, byte(unit.Parameters["channel"]))
|
||||
} else if unit.Type == "oscillator" {
|
||||
flags := 0
|
||||
switch unit.Parameters["type"] {
|
||||
case sointu.Sine:
|
||||
flags = 0x40
|
||||
case sointu.Trisaw:
|
||||
flags = 0x20
|
||||
case sointu.Pulse:
|
||||
flags = 0x10
|
||||
case sointu.Gate:
|
||||
flags = 0x04
|
||||
case sointu.Sample:
|
||||
flags = 0x80
|
||||
}
|
||||
if unit.Parameters["lfo"] == 1 {
|
||||
flags += 0x08
|
||||
}
|
||||
flags += unit.Parameters["unison"]
|
||||
values = append(values, byte(flags))
|
||||
} else if unit.Type == "filter" {
|
||||
flags := 0
|
||||
if unit.Parameters["lowpass"] == 1 {
|
||||
flags += 0x40
|
||||
}
|
||||
if unit.Parameters["bandpass"] == 1 {
|
||||
flags += 0x20
|
||||
}
|
||||
if unit.Parameters["highpass"] == 1 {
|
||||
flags += 0x10
|
||||
}
|
||||
if unit.Parameters["negbandpass"] == 1 {
|
||||
flags += 0x08
|
||||
}
|
||||
if unit.Parameters["neghighpass"] == 1 {
|
||||
flags += 0x04
|
||||
}
|
||||
values = append(values, byte(flags))
|
||||
} else if unit.Type == "send" {
|
||||
address := ((unit.Parameters["unit"] + 1) << 4) + unit.Parameters["port"] // each unit is 16 dwords, 8 workspace followed by 8 ports. +1 is for skipping the note/release/inputs
|
||||
if unit.Parameters["voice"] > 0 {
|
||||
address += 0x8000 + 16 + (unit.Parameters["voice"]-1)*1024 // global send, +16 is for skipping the out/aux ports
|
||||
}
|
||||
if unit.Parameters["sendpop"] == 1 {
|
||||
address += 0x8
|
||||
}
|
||||
values = append(values, byte(address&255), byte(address>>8))
|
||||
} else if unit.Type == "delay" {
|
||||
countTrack := (unit.Parameters["count"] << 1) - 1 + unit.Parameters["notetracking"] // 1 means no note tracking and 1 delay, 2 means notetracking with 1 delay, 3 means no note tracking and 2 delays etc.
|
||||
values = append(values, byte(unit.Parameters["delay"]), byte(countTrack))
|
||||
}
|
||||
return byte(opcode + unit.Parameters["stereo"]), values, nil
|
||||
}
|
206
compiler/featureset.go
Normal file
206
compiler/featureset.go
Normal file
@ -0,0 +1,206 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/vsariola/sointu"
|
||||
)
|
||||
|
||||
// FeatureSet defines what opcodes / parameters are included in the compiled virtual machine
|
||||
// It is used by the compiler to decide how to encode opcodes
|
||||
type FeatureSet interface {
|
||||
Opcode(unitType string) (int, bool)
|
||||
TransformCount(unitType string) int
|
||||
Instructions() []string
|
||||
InputNumber(unitType string, paramName string) int
|
||||
SupportsParamValue(unitType string, paramName string, value int) bool
|
||||
SupportsParamValueOtherThan(unitType string, paramName string, value int) bool
|
||||
SupportsModulation(unitType string, paramName string) bool
|
||||
SupportsPolyphony() bool
|
||||
}
|
||||
|
||||
type Instruction struct {
|
||||
Name string
|
||||
TransformCount int
|
||||
}
|
||||
|
||||
type paramKey struct {
|
||||
Unit string
|
||||
Param string
|
||||
}
|
||||
|
||||
type paramValueKey struct {
|
||||
Unit string
|
||||
Param string
|
||||
Value int
|
||||
}
|
||||
|
||||
// AllFeatures is used by the library compilation / bridging to configure a virtual machine
|
||||
// that supports every conceivable parameter, so it needs no members and just returns "true" to all
|
||||
// queries about what it supports. Contrast this NecessaryFeatures that only returns true if the patch
|
||||
// needs support for that feature
|
||||
type AllFeatures struct {
|
||||
}
|
||||
|
||||
func (_ AllFeatures) SupportsParamValue(unit string, paramName string, value int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (_ AllFeatures) SupportsParamValueOtherThan(unit string, paramName string, value int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (_ AllFeatures) SupportsModulation(unit string, port string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (_ AllFeatures) SupportsPolyphony() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (_ AllFeatures) Opcode(unitType string) (int, bool) {
|
||||
code, ok := allOpcodes[unitType]
|
||||
return code, ok
|
||||
}
|
||||
|
||||
func (_ AllFeatures) TransformCount(unitType string) int {
|
||||
return allTransformCounts[unitType]
|
||||
}
|
||||
|
||||
func (_ AllFeatures) Instructions() []string {
|
||||
return allInstructions
|
||||
}
|
||||
|
||||
func (_ AllFeatures) InputNumber(unitType string, paramName string) int {
|
||||
return allInputs[paramKey{unitType, paramName}]
|
||||
}
|
||||
|
||||
var allOpcodes map[string]int
|
||||
var allInstructions []string
|
||||
var allInputs map[paramKey]int
|
||||
var allTransformCounts map[string]int
|
||||
|
||||
func init() {
|
||||
allInstructions = make([]string, len(sointu.UnitTypes))
|
||||
allOpcodes = map[string]int{}
|
||||
allTransformCounts = map[string]int{}
|
||||
allInputs = map[paramKey]int{}
|
||||
i := 0
|
||||
for k, v := range sointu.UnitTypes {
|
||||
inputCount := 0
|
||||
transformCount := 0
|
||||
for _, t := range v {
|
||||
if t.CanModulate {
|
||||
allInputs[paramKey{k, t.Name}] = inputCount
|
||||
inputCount++
|
||||
}
|
||||
if t.CanModulate && t.CanSet {
|
||||
transformCount++
|
||||
}
|
||||
}
|
||||
allInstructions[i] = k // Opcode 0 is reserved for instrument advance, so opcodes start from 1
|
||||
allTransformCounts[k] = transformCount
|
||||
i++
|
||||
}
|
||||
sort.Strings(allInstructions) // sort the opcodes to have predictable ordering, as maps don't guarantee the order the items
|
||||
for i, instruction := range allInstructions {
|
||||
allOpcodes[instruction] = (i + 1) * 2 // make a map to find out the opcode number based on the type
|
||||
}
|
||||
}
|
||||
|
||||
// NecessaryFeatures returns true only if the patch actually needs the support for the feature
|
||||
type NecessaryFeatures struct {
|
||||
opcodes map[string]int
|
||||
instructions []string
|
||||
supportsParamValue map[paramKey](map[int]bool)
|
||||
supportsModulation map[paramKey]bool
|
||||
polyphony bool
|
||||
}
|
||||
|
||||
func NecessaryFeaturesFor(patch sointu.Patch) NecessaryFeatures {
|
||||
features := NecessaryFeatures{opcodes: map[string]int{}, supportsParamValue: map[paramKey](map[int]bool){}, supportsModulation: map[paramKey]bool{}}
|
||||
for instrNo, instrument := range patch.Instruments {
|
||||
for _, unit := range instrument.Units {
|
||||
if _, ok := features.opcodes[unit.Type]; !ok {
|
||||
features.instructions = append(features.instructions, unit.Type)
|
||||
features.opcodes[unit.Type] = len(features.instructions) * 2 // note that the first opcode gets value 1, as 0 is always reserved for advance
|
||||
}
|
||||
for k, v := range unit.Parameters {
|
||||
key := paramKey{unit.Type, k}
|
||||
if features.supportsParamValue[key] == nil {
|
||||
features.supportsParamValue[key] = map[int]bool{}
|
||||
}
|
||||
features.supportsParamValue[key][v] = true
|
||||
}
|
||||
if unit.Type == "send" {
|
||||
targetInstrument := instrNo
|
||||
if unit.Parameters["voice"] > 0 {
|
||||
v, err := patch.InstrumentForVoice(unit.Parameters["voice"] - 1)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
targetInstrument = v
|
||||
}
|
||||
if unit.Parameters["unit"] < 0 || unit.Parameters["unit"] >= len(patch.Instruments[targetInstrument].Units) {
|
||||
continue // send is modulating outside the range of the target instrument; probably a bug in patch, but at least it's not modulating the uniType we're after
|
||||
}
|
||||
targetUnit := patch.Instruments[targetInstrument].Units[unit.Parameters["unit"]]
|
||||
modulatedPortNo := 0
|
||||
for _, v := range sointu.UnitTypes[targetUnit.Type] {
|
||||
if v.CanModulate {
|
||||
if modulatedPortNo == unit.Parameters["port"] {
|
||||
features.supportsModulation[paramKey{targetUnit.Type, v.Name}] = true
|
||||
}
|
||||
modulatedPortNo++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if instrument.NumVoices > 1 {
|
||||
features.polyphony = true
|
||||
}
|
||||
}
|
||||
return features
|
||||
}
|
||||
|
||||
func (n NecessaryFeatures) SupportsParamValue(unit string, paramName string, value int) bool {
|
||||
m, ok := n.supportsParamValue[paramKey{unit, paramName}]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return m[value]
|
||||
}
|
||||
|
||||
func (n NecessaryFeatures) SupportsParamValueOtherThan(unit string, paramName string, value int) bool {
|
||||
for paramValue := range n.supportsParamValue[paramKey{unit, paramName}] {
|
||||
if paramValue != value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (n NecessaryFeatures) SupportsModulation(unit string, param string) bool {
|
||||
return n.supportsModulation[paramKey{unit, param}]
|
||||
}
|
||||
|
||||
func (n NecessaryFeatures) SupportsPolyphony() bool {
|
||||
return n.polyphony
|
||||
}
|
||||
|
||||
func (n NecessaryFeatures) Opcode(unitType string) (int, bool) {
|
||||
code, ok := n.opcodes[unitType]
|
||||
return code, ok
|
||||
}
|
||||
|
||||
func (n NecessaryFeatures) Instructions() []string {
|
||||
return n.instructions
|
||||
}
|
||||
|
||||
func (n NecessaryFeatures) InputNumber(unitType string, paramName string) int {
|
||||
return allInputs[paramKey{unitType, paramName}]
|
||||
}
|
||||
|
||||
func (_ NecessaryFeatures) TransformCount(unitType string) int {
|
||||
return allTransformCounts[unitType]
|
||||
}
|
61
compiler/generate.go
Normal file
61
compiler/generate.go
Normal file
@ -0,0 +1,61 @@
|
||||
// The following directive is necessary to make the package coherent:
|
||||
|
||||
// +build ignore
|
||||
|
||||
// This program generates the library headers and assembly files for the library
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/vsariola/sointu/compiler"
|
||||
)
|
||||
|
||||
func main() {
|
||||
targetArch := flag.String("arch", runtime.GOARCH, "Target architecture. Defaults to Go architecture. Possible values: amd64, 386 (anything else is assumed 386)")
|
||||
targetOs := flag.String("os", runtime.GOOS, "Target OS. Defaults to current Go OS. Possible values: windows, darwin, linux (anything else is assumed linux)")
|
||||
flag.Usage = printUsage
|
||||
flag.Parse()
|
||||
|
||||
if flag.NArg() != 1 {
|
||||
flag.Usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
comp, err := compiler.New()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, `error creating compiler: %v`, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
comp.Amd64 = *targetArch == "amd64"
|
||||
comp.OS = *targetOs
|
||||
|
||||
library, err := comp.Library()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, `error compiling library: %v`, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
filenames := map[string]string{"h": "sointu.h", "asm": "sointu.asm"}
|
||||
|
||||
for t, contents := range library {
|
||||
filename := filenames[t]
|
||||
err := ioutil.WriteFile(filepath.Join(flag.Args()[0], filename), []byte(contents), os.ModePerm)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, `could not write to file "%v": %v`, filename, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Fprintf(os.Stderr, "Sointu command line utility for generating the library .asm and .h files.\nUsage: %s [flags] outputDirectory\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
}
|
480
compiler/macros.go
Normal file
480
compiler/macros.go
Normal file
@ -0,0 +1,480 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/vsariola/sointu"
|
||||
)
|
||||
|
||||
type OplistEntry struct {
|
||||
Type string
|
||||
NumParams int
|
||||
}
|
||||
|
||||
type Macros struct {
|
||||
Stacklocs []string
|
||||
Output16Bit bool
|
||||
Clip bool
|
||||
Library 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
|
||||
stackframes map[string][]string
|
||||
FeatureSet
|
||||
Compiler
|
||||
}
|
||||
|
||||
func NewMacros(c Compiler, f FeatureSet) *Macros {
|
||||
return &Macros{
|
||||
calls: map[string]bool{},
|
||||
usesFloatConst: map[float32]bool{},
|
||||
usesIntConst: map[int]bool{},
|
||||
stackframes: map[string][]string{},
|
||||
Sine: sointu.Sine,
|
||||
Trisaw: sointu.Trisaw,
|
||||
Pulse: sointu.Pulse,
|
||||
Gate: sointu.Gate,
|
||||
Sample: sointu.Sample,
|
||||
Compiler: c,
|
||||
FeatureSet: f,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Macros) HasOp(instruction string) bool {
|
||||
_, ok := p.Opcode(instruction)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (p *Macros) Stereo(unitType string) bool {
|
||||
return p.SupportsParamValue(unitType, "stereo", 1)
|
||||
}
|
||||
|
||||
func (p *Macros) Mono(unitType string) bool {
|
||||
return p.SupportsParamValue(unitType, "stereo", 0)
|
||||
}
|
||||
|
||||
func (p *Macros) StereoAndMono(unitType string) bool {
|
||||
return p.Stereo(unitType) && p.Mono(unitType)
|
||||
}
|
||||
|
||||
// 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) SaveFPUState() string {
|
||||
i := 0
|
||||
for ; i < 108; i += p.PTRSIZE() {
|
||||
p.Stacklocs = append(p.Stacklocs, fmt.Sprintf("F%v", i))
|
||||
}
|
||||
return fmt.Sprintf("sub %[1]v, %[2]v\nfsave [%[1]v]", p.SP(), i)
|
||||
}
|
||||
|
||||
func (p *Macros) LoadFPUState() string {
|
||||
i := 0
|
||||
for ; i < 108; i += p.PTRSIZE() {
|
||||
p.Stacklocs = p.Stacklocs[:len(p.Stacklocs)-1]
|
||||
}
|
||||
return fmt.Sprintf("frstor [%[1]v]\nadd %[1]v, %[2]v", p.SP(), i)
|
||||
}
|
||||
|
||||
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 {
|
||||
reverseParams := make([]string, len(params))
|
||||
for i, param := range params {
|
||||
reverseParams[len(params)-1-i] = param
|
||||
}
|
||||
p.Stacklocs = append(reverseParams, "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) Input(unit string, port string) (string, error) {
|
||||
i := p.InputNumber(unit, port)
|
||||
if i != 0 {
|
||||
return fmt.Sprintf("%v + %v", p.INP(), i*4), nil
|
||||
}
|
||||
return p.INP(), nil
|
||||
}
|
||||
|
||||
func (p *Macros) Modulation(unit string, port string) (string, error) {
|
||||
i := p.InputNumber(unit, port)
|
||||
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
|
||||
}
|
||||
|
||||
type PlayerMacros struct {
|
||||
Song *sointu.Song
|
||||
VoiceTrackBitmask int
|
||||
MaxSamples int
|
||||
Macros
|
||||
EncodedPatch
|
||||
}
|
||||
|
||||
func NewPlayerMacros(c Compiler, f FeatureSet, s *sointu.Song, e *EncodedPatch, maxSamples int) *PlayerMacros {
|
||||
if maxSamples == 0 {
|
||||
maxSamples = s.SamplesPerRow() * s.TotalRows()
|
||||
}
|
||||
macros := *NewMacros(c, f)
|
||||
macros.Output16Bit = s.Output16Bit // TODO: should we actually store output16bit in Songs or not?
|
||||
p := PlayerMacros{Song: s, Macros: macros, MaxSamples: maxSamples, EncodedPatch: *e}
|
||||
trackVoiceNumber := 0
|
||||
for _, t := range s.Tracks {
|
||||
for b := 0; b < t.NumVoices-1; b++ {
|
||||
p.VoiceTrackBitmask += 1 << trackVoiceNumber
|
||||
trackVoiceNumber++
|
||||
}
|
||||
trackVoiceNumber++ // set all bits except last one
|
||||
}
|
||||
return &p
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
Reference in New Issue
Block a user