From 6a331f7a576a8490011cd8bb40bc847cf243e916 Mon Sep 17 00:00:00 2001 From: "5684185+vsariola@users.noreply.github.com" <5684185+vsariola@users.noreply.github.com> Date: Sun, 15 Mar 2026 10:29:24 +0200 Subject: [PATCH] refactor: put all info about different unit types into UnitTypes map --- patch.go | 647 ++++++++++++++++++++++++++------------------- tracker/derived.go | 4 +- tracker/model.go | 2 +- tracker/params.go | 6 +- tracker/song.go | 51 +--- tracker/unit.go | 7 +- vm/bytecode.go | 2 +- vm/featureset.go | 4 +- 8 files changed, 392 insertions(+), 331 deletions(-) diff --git a/patch.go b/patch.go index fa365f9..bbabdd5 100644 --- a/patch.go +++ b/patch.go @@ -81,15 +81,11 @@ type ( ParamMap map[string]int - // UnitParameter documents one parameter that an unit takes - UnitParameter struct { - Name string // thould be found with this name in the Unit.Parameters map - MinValue int // minimum value of the parameter, inclusive - MaxValue int // maximum value of the parameter, inclusive - Neutral int // neutral value of the parameter - CanSet bool // if true, then this parameter can be set through the gui - CanModulate bool // if true, then this parameter can be modulated i.e. has a port number in "send" unit - DisplayFunc UnitParameterDisplayFunc + // UnitType documents the parameters and stack use of a unit type + UnitType struct { + Params []UnitParameter + DefaultVarArgs []int + StackUse func(*Unit) StackUse } // StackUse documents how a unit will affect the signal stack. @@ -99,133 +95,358 @@ type ( NumOutputs int // NumOutputs is the number of outputs produced by the unit. This is used to determine how many outputs are needed for the unit. } + // UnitParameter documents one parameter that an unit takes + UnitParameter struct { + Name string // thould be found with this name in the Unit.Parameters map + MinValue int // minimum value of the parameter, inclusive + MaxValue int // maximum value of the parameter, inclusive + Neutral int // neutral value of the parameter + Default int // the default value of the parameter + CanSet bool // if true, then this parameter can be set through the gui + CanModulate bool // if true, then this parameter can be modulated i.e. has a port number in "send" unit + DisplayFunc UnitParameterDisplayFunc + } + UnitParameterDisplayFunc func(int) (value string, unit string) ) -// UnitTypes documents all the available unit types and if they support stereo variant -// and what parameters they take. -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, Neutral: 64, 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, DisplayFunc: func(v int) (string, string) { return formatFloat(24 * float64(v) / 128), "bits" }}}, - "gain": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}}, - "invgain": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "invgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(128/float64(v)), 'g', 3, 64), "dB" }}}, - "dbgain": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "decibels", MinValue: 0, Neutral: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return formatFloat(40 * (float64(v)/64 - 1)), "dB" }}}, - "filter": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "frequency", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: filterFrequencyDispFunc}, - {Name: "resonance", MinValue: 0, Neutral: 128, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { - return strconv.FormatFloat(toDecibel(128/float64(v)), 'g', 3, 64), "Q dB" - }}, - {Name: "lowpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "bandpass", MinValue: -1, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "highpass", MinValue: -1, 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, Neutral: 64, 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: 2, CanSet: true, CanModulate: false, DisplayFunc: arrDispFunc(noteTrackingNames[:])}, - {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, DisplayFunc: compressorTimeDispFunc}, - {Name: "release", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: compressorTimeDispFunc}, - {Name: "invgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { - return strconv.FormatFloat(toDecibel(128/float64(v)), 'g', 3, 64), "dB" - }}, - {Name: "threshold", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { - return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" - }}, - {Name: "ratio", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return formatFloat(1 - float64(v)/128), "" }}}, - "speed": []UnitParameter{}, - "out": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}}, - "outaux": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "outgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}, - {Name: "auxgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}}, - "aux": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}, - {Name: "channel", MinValue: 0, MaxValue: 6, CanSet: true, CanModulate: false, DisplayFunc: arrDispFunc(channelNames[:])}}, - "send": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "amount", MinValue: 0, Neutral: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return formatFloat(float64(v)/64 - 1), "" }}, - {Name: "voice", MinValue: 0, MaxValue: 32, CanSet: true, CanModulate: false, DisplayFunc: sendVoiceDispFunc}, - {Name: "target", MinValue: 0, MaxValue: math.MaxInt32, 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, DisplayFunc: func(v int) (string, string) { return engineeringTime(math.Pow(2, 24*float64(v)/128) / 44100) }}, - {Name: "decay", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return engineeringTime(math.Pow(2, 24*float64(v)/128) / 44100) }}, - {Name: "sustain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}, - {Name: "release", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return engineeringTime(math.Pow(2, 24*float64(v)/128) / 44100) }}, - {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}}, - "noise": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "shape", MinValue: 0, Neutral: 64, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}}, - "oscillator": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "transpose", MinValue: 0, Neutral: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: oscillatorTransposeDispFunc}, - {Name: "detune", MinValue: 0, Neutral: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return formatFloat(float64(v-64) / 64), "st" }}, - {Name: "phase", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { - return strconv.FormatFloat(float64(v)/128*360, 'f', 1, 64), "°" - }}, - {Name: "color", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "shape", MinValue: 0, Neutral: 64, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}, - {Name: "frequency", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}, - {Name: "type", MinValue: int(Sine), MaxValue: int(Sample), CanSet: true, CanModulate: false, DisplayFunc: arrDispFunc(oscTypes[:])}, - {Name: "lfo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "unison", MinValue: 0, MaxValue: 3, CanSet: true, CanModulate: false}, - {Name: "samplestart", MinValue: 0, MaxValue: 1720329, CanSet: true, CanModulate: false}, - {Name: "loopstart", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false}, - {Name: "looplength", MinValue: 0, MaxValue: 65535, 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, DisplayFunc: func(v int) (string, string) { return formatFloat(float64(v)/64 - 1), "" }}}, - "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, DisplayFunc: arrDispFunc(channelNames[:])}}, - "sync": []UnitParameter{}, - "belleq": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "frequency", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return belleqFrequencyDisplay(v) }}, - {Name: "bandwidth", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return belleqBandwidthDisplay(v) }}, - {Name: "gain", MinValue: 0, Neutral: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return belleqGainDisplay(v) }}}, +// UnitTypes documents all the available unit types and if they support stereo +// variant and what parameters they take. If you add a new unit type, add it +// here and also add its opcode to vm/opcodes.go by running "go generate ./vm" +// in the terminal. +var UnitTypes = map[string]UnitType{ + "add": { + Params: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + StackUse: func(unit *Unit) StackUse { + if stereo, ok := unit.Parameters["stereo"]; ok && stereo == 1 { + return StackUse{Inputs: [][]int{{0, 2}, {1, 3}, {2}, {3}}, Modifies: []bool{false, false, true, true}, NumOutputs: 4} + } + return StackUse{Inputs: [][]int{{0, 1}, {1}}, Modifies: []bool{false, true}, NumOutputs: 2} + }, + }, + "addp": { + Params: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + StackUse: func(u *Unit) StackUse { + if stereo, ok := u.Parameters["stereo"]; ok && stereo == 1 { + return StackUse{Inputs: [][]int{{0}, {1}, {0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 2} + } + return StackUse{Inputs: [][]int{{0}, {0}}, Modifies: []bool{true}, NumOutputs: 1} + }, + }, + "mul": { + Params: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + StackUse: func(unit *Unit) StackUse { + if stereo, ok := unit.Parameters["stereo"]; ok && stereo == 1 { + return StackUse{Inputs: [][]int{{0, 2}, {1, 3}, {2}, {3}}, Modifies: []bool{false, false, true, true}, NumOutputs: 4} + } + return StackUse{Inputs: [][]int{{0, 1}, {1}}, Modifies: []bool{false, true}, NumOutputs: 2} + }, + }, + "mulp": { + Params: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + StackUse: func(u *Unit) StackUse { + if stereo, ok := u.Parameters["stereo"]; ok && stereo == 1 { + return StackUse{Inputs: [][]int{{0}, {1}, {0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 2} + } + return StackUse{Inputs: [][]int{{0}, {0}}, Modifies: []bool{true}, NumOutputs: 1} + }, + }, + "xch": { + Params: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + StackUse: func(u *Unit) StackUse { + if stereo, ok := u.Parameters["stereo"]; ok && stereo == 1 { + return StackUse{Inputs: [][]int{{2}, {3}, {0}, {1}}, Modifies: []bool{false, false, false, false}, NumOutputs: 4} + } + return StackUse{Inputs: [][]int{{1}, {0}}, Modifies: []bool{false, false}, NumOutputs: 2} + }, + }, + "push": { + Params: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + StackUse: func(u *Unit) StackUse { + if stereo, ok := u.Parameters["stereo"]; ok && stereo == 1 { + return StackUse{Inputs: [][]int{{0, 2}, {1, 3}}, Modifies: []bool{false, false, false, false}, NumOutputs: 4} + } + return StackUse{Inputs: [][]int{{0, 1}}, Modifies: []bool{false, false}, NumOutputs: 2} + }, + }, + "pop": { + Params: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + StackUse: stackUseSink, + }, + "loadnote": { + Params: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + StackUse: stackUseSource, + }, + "distort": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "drive", MinValue: 0, Neutral: 64, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true}, + }, + StackUse: stackUseEffect, + }, + "hold": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "holdfreq", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true}, + }, + StackUse: stackUseEffect, + }, + "crush": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "resolution", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return formatFloat(24 * float64(v) / 128), "bits" }}, + }, + StackUse: stackUseEffect, + }, + "gain": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "gain", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}, + }, + StackUse: stackUseEffect, + }, + "invgain": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "invgain", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(128/float64(v)), 'g', 3, 64), "dB" }}, + }, + StackUse: stackUseEffect, + }, + "dbgain": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "decibels", MinValue: 0, Neutral: 64, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return formatFloat(40 * (float64(v)/64 - 1)), "dB" }}, + }, + StackUse: stackUseEffect, + }, + "filter": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "frequency", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { + // https://www.musicdsp.org/en/latest/Filters/23-state-variable.html + // calls it cutoff" but it's actually the location of the + // resonance peak + freq := float64(v) / 128 + return strconv.FormatFloat(math.Asin(freq*freq/2)/math.Pi*44100, 'f', 0, 64), "Hz" + }, + }, + {Name: "resonance", MinValue: 0, Default: 64, Neutral: 128, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { + return strconv.FormatFloat(toDecibel(128/float64(v)), 'g', 3, 64), "Q dB" + }}, + {Name: "lowpass", MinValue: 0, Default: 1, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "bandpass", MinValue: -1, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "highpass", MinValue: -1, MaxValue: 1, CanSet: true, CanModulate: false}, + }, + StackUse: stackUseEffect, + }, + "clip": { + Params: []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + StackUse: stackUseEffect, + }, + "pan": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "panning", MinValue: 0, Neutral: 64, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true}, + }, + StackUse: func(u *Unit) StackUse { + if stereo, ok := u.Parameters["stereo"]; ok && stereo == 1 { + return StackUse{Inputs: [][]int{{0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 2} + } + return StackUse{Inputs: [][]int{{0, 1}}, Modifies: []bool{true, true}, NumOutputs: 2} + }, + }, + "delay": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "pregain", MinValue: 0, Default: 40, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "dry", MinValue: 0, Default: 128, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "feedback", MinValue: 0, Default: 96, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "damp", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "notetracking", MinValue: 0, Default: 2, MaxValue: 2, CanSet: true, CanModulate: false, DisplayFunc: arrDispFunc([]string{"fixed", "pitch", "BPM"})}, + {Name: "delaytime", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}, + }, + DefaultVarArgs: []int{48}, + StackUse: stackUseEffect, + }, + "compressor": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "attack", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: compressorTimeDispFunc}, + {Name: "release", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: compressorTimeDispFunc}, + {Name: "invgain", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { + return strconv.FormatFloat(toDecibel(128/float64(v)), 'g', 3, 64), "dB" + }}, + {Name: "threshold", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { + return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" + }}, + {Name: "ratio", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return formatFloat(1 - float64(v)/128), "" }}, + }, + StackUse: func(u *Unit) StackUse { + if stereo, ok := u.Parameters["stereo"]; ok && stereo == 1 { + return StackUse{Inputs: [][]int{{0, 2, 3}, {1, 2, 3}}, Modifies: []bool{false, false, true, true}, NumOutputs: 4} + } + return StackUse{Inputs: [][]int{{0, 1}}, Modifies: []bool{false, true}, NumOutputs: 2} + }, + }, + "speed": { + Params: []UnitParameter{}, + StackUse: func(u *Unit) StackUse { return StackUse{Inputs: [][]int{{0}}, Modifies: []bool{true}, NumOutputs: 0} }, + }, + "out": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, Default: 1, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "gain", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}, + }, + StackUse: stackUseSink, + }, + "outaux": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, Default: 1, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "outgain", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}, + {Name: "auxgain", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}, + }, + StackUse: stackUseSink, + }, + "aux": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, Default: 1, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "gain", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}, + {Name: "channel", MinValue: 0, Default: 2, MaxValue: 6, CanSet: true, CanModulate: false, DisplayFunc: arrDispFunc(channelNames[:])}, + }, + StackUse: stackUseSink, + }, + "send": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "amount", MinValue: 0, Neutral: 64, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return formatFloat(float64(v)/64 - 1), "" }}, + {Name: "voice", MinValue: 0, MaxValue: 32, CanSet: true, CanModulate: false, DisplayFunc: func(v int) (string, string) { + if v == 0 { + return "default", "" + } + return strconv.Itoa(v), "" + }}, + {Name: "target", MinValue: 0, MaxValue: math.MaxInt32, CanSet: true, CanModulate: false}, + {Name: "port", MinValue: 0, MaxValue: 7, CanSet: true, CanModulate: false}, + {Name: "sendpop", MinValue: 0, Default: 1, MaxValue: 1, CanSet: true, CanModulate: false}, + }, + StackUse: func(u *Unit) StackUse { + ret := StackUse{Inputs: [][]int{{0}}, Modifies: []bool{true}, NumOutputs: 1} + if stereo, ok := u.Parameters["stereo"]; ok && stereo == 1 { + ret = StackUse{Inputs: [][]int{{0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 2} + } + if sendpop, ok := u.Parameters["sendpop"]; ok && sendpop == 1 { + ret.NumOutputs = 0 + } + return ret + }, + }, + "envelope": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "attack", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return engineeringTime(math.Pow(2, 24*float64(v)/128) / 44100) }}, + {Name: "decay", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return engineeringTime(math.Pow(2, 24*float64(v)/128) / 44100) }}, + {Name: "sustain", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}, + {Name: "release", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return engineeringTime(math.Pow(2, 24*float64(v)/128) / 44100) }}, + {Name: "gain", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}, + }, + StackUse: stackUseSource, + }, + "noise": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "shape", MinValue: 0, Neutral: 64, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "gain", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}, + }, + StackUse: stackUseSource, + }, + "oscillator": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "transpose", MinValue: 0, Neutral: 64, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { + relvalue := v - 64 + if relvalue%12 == 0 { + return strconv.Itoa(relvalue / 12), "oct" + } + return strconv.Itoa(relvalue), "st" + }}, + {Name: "detune", MinValue: 0, Neutral: 64, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return formatFloat(float64(v-64) / 64), "st" }}, + {Name: "phase", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { + return strconv.FormatFloat(float64(v)/128*360, 'f', 1, 64), "°" + }}, + {Name: "color", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "shape", MinValue: 0, Neutral: 64, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "gain", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(toDecibel(float64(v)/128), 'g', 3, 64), "dB" }}, + {Name: "frequency", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}, + {Name: "type", MinValue: int(Sine), Default: int(Sine), MaxValue: int(Sample), CanSet: true, CanModulate: false, DisplayFunc: arrDispFunc([]string{"sine", "trisaw", "pulse", "gate", "sample"})}, + {Name: "lfo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "unison", MinValue: 0, MaxValue: 3, CanSet: true, CanModulate: false}, + {Name: "samplestart", MinValue: 0, MaxValue: 1720329, CanSet: true, CanModulate: false}, + {Name: "loopstart", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false}, + {Name: "looplength", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false}, + }, + StackUse: stackUseSource, + }, + "loadval": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "value", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return formatFloat(float64(v)/64 - 1), "" }}, + }, + StackUse: stackUseSource, + }, + "receive": { + Params: []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}, + }, + StackUse: stackUseSource, + }, + "in": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, Default: 1, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "channel", MinValue: 0, Default: 2, MaxValue: 6, CanSet: true, CanModulate: false, DisplayFunc: arrDispFunc(channelNames[:])}, + }, + StackUse: stackUseSource, + }, + "sync": { + Params: []UnitParameter{}, + StackUse: func(u *Unit) StackUse { return StackUse{Inputs: [][]int{{0}}, Modifies: []bool{false}, NumOutputs: 1} }, + }, + "belleq": { + Params: []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "frequency", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { + freq := float64(v) / 128 + return strconv.FormatFloat(44100*2*freq*freq/math.Pi/2, 'f', 0, 64), "Hz" + }}, + {Name: "bandwidth", MinValue: 0, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { return strconv.FormatFloat(1/(4*float64(v)/128), 'f', 2, 64), "Q" }}, + {Name: "gain", MinValue: 0, Neutral: 64, Default: 64, MaxValue: 128, CanSet: true, CanModulate: true, DisplayFunc: func(v int) (string, string) { + return strconv.FormatFloat(40*(float64(v)/64-1), 'f', 2, 64), "dB" + }}, + }, + StackUse: stackUseEffect, + }, +} + +func stackUseSource(u *Unit) StackUse { + if stereo, ok := u.Parameters["stereo"]; ok && stereo == 1 { + return StackUse{Inputs: [][]int{}, Modifies: []bool{true, true}, NumOutputs: 2} + } + return StackUse{Inputs: [][]int{}, Modifies: []bool{true}, NumOutputs: 1} +} + +func stackUseSink(u *Unit) StackUse { + if stereo, ok := u.Parameters["stereo"]; ok && stereo == 1 { + return StackUse{Inputs: [][]int{{0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 0} + } + return StackUse{Inputs: [][]int{{0}}, Modifies: []bool{true}, NumOutputs: 0} +} + +func stackUseEffect(u *Unit) StackUse { + if stereo, ok := u.Parameters["stereo"]; ok && stereo == 1 { + return StackUse{Inputs: [][]int{{0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 2} + } + return StackUse{Inputs: [][]int{{0}}, Modifies: []bool{true}, NumOutputs: 1} } // compile errors if interface is not implemented. @@ -253,8 +474,6 @@ func (a *ParamMap) UnmarshalYAML(value *yaml.Node) error { } var channelNames = [...]string{"left", "right", "aux1 left", "aux1 right", "aux2 left", "aux2 right", "aux3 left", "aux3 right"} -var noteTrackingNames = [...]string{"fixed", "pitch", "BPM"} -var oscTypes = [...]string{"sine", "trisaw", "pulse", "gate", "sample"} func arrDispFunc(arr []string) UnitParameterDisplayFunc { return func(v int) (string, string) { @@ -265,54 +484,12 @@ func arrDispFunc(arr []string) UnitParameterDisplayFunc { } } -func filterFrequencyDispFunc(v int) (string, string) { - // In https://www.musicdsp.org/en/latest/Filters/23-state-variable.html, - // they call it "cutoff" but it's actually the location of the resonance - // peak - freq := float64(v) / 128 - p := freq * freq - f := math.Asin(p/2) / math.Pi * 44100 - return strconv.FormatFloat(f, 'f', 0, 64), "Hz" -} - -func belleqFrequencyDisplay(v int) (string, string) { - freq := float64(v) / 128 - p := 2 * freq * freq - f := 44100 * p / math.Pi / 2 - return strconv.FormatFloat(f, 'f', 0, 64), "Hz" -} - -func belleqBandwidthDisplay(v int) (string, string) { - p := float64(v) / 128 - Q := 1 / (4 * p) - return strconv.FormatFloat(Q, 'f', 2, 64), "Q" -} - -func belleqGainDisplay(v int) (string, string) { - return strconv.FormatFloat(40*(float64(v)/64-1), 'f', 2, 64), "dB" -} - func compressorTimeDispFunc(v int) (string, string) { alpha := math.Pow(2, -24*float64(v)/128) // alpha is the "smoothing factor" of first order low pass iir sec := -1 / (44100 * math.Log(1-alpha)) // from smoothing factor to time constant, https://en.wikipedia.org/wiki/Exponential_smoothing return engineeringTime(sec) } -func oscillatorTransposeDispFunc(v int) (string, string) { - relvalue := v - 64 - if relvalue%12 == 0 { - return strconv.Itoa(relvalue / 12), "oct" - } - return strconv.Itoa(relvalue), "st" -} - -func sendVoiceDispFunc(v int) (string, string) { - if v == 0 { - return "default", "" - } - return strconv.Itoa(v), "" -} - func engineeringTime(sec float64) (string, string) { if sec < 1e-3 { return fmt.Sprintf("%.2f", sec*1e6), "us" @@ -367,7 +544,7 @@ var Ports = make(map[string]([]string)) func init() { for name, unitType := range UnitTypes { unitPorts := make([]string, 0) - for _, param := range unitType { + for _, param := range unitType.Params { if param.CanModulate { unitPorts = append(unitPorts, param.Name) } @@ -376,6 +553,24 @@ func init() { } } +func MakeUnit(unitType string) Unit { + if ut, ok := UnitTypes[unitType]; ok { + ret := Unit{ + Type: unitType, + Parameters: make(map[string]int), + VarArgs: make([]int, len(ut.DefaultVarArgs)), + } + copy(ret.VarArgs, ut.DefaultVarArgs) + for _, p := range ut.Params { + if p.Default > 0 { + ret.Parameters[p.Name] = p.Default + } + } + return ret + } + return Unit{Parameters: make(map[string]int)} +} + // Copy makes a deep copy of a unit. func (u *Unit) Copy() Unit { ret := *u @@ -388,102 +583,14 @@ func (u *Unit) Copy() Unit { return ret } -var stackUseSource = [2]StackUse{ - {Inputs: [][]int{}, Modifies: []bool{true}, NumOutputs: 1}, // mono - {Inputs: [][]int{}, Modifies: []bool{true, true}, NumOutputs: 2}, // stereo -} -var stackUseSink = [2]StackUse{ - {Inputs: [][]int{{0}}, Modifies: []bool{true}, NumOutputs: 0}, // mono - {Inputs: [][]int{{0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 0}, // stereo -} -var stackUseEffect = [2]StackUse{ - {Inputs: [][]int{{0}}, Modifies: []bool{true}, NumOutputs: 1}, // mono - {Inputs: [][]int{{0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 2}, // stereo -} -var stackUseMonoStereo = map[string][2]StackUse{ - "add": { - {Inputs: [][]int{{0, 1}, {1}}, Modifies: []bool{false, true}, NumOutputs: 2}, - {Inputs: [][]int{{0, 2}, {1, 3}, {2}, {3}}, Modifies: []bool{false, false, true, true}, NumOutputs: 4}, - }, - "mul": { - {Inputs: [][]int{{0, 1}, {1}}, Modifies: []bool{false, true}, NumOutputs: 2}, - {Inputs: [][]int{{0, 2}, {1, 3}, {2}, {3}}, Modifies: []bool{false, false, true, true}, NumOutputs: 4}, - }, - "addp": { - {Inputs: [][]int{{0}, {0}}, Modifies: []bool{true}, NumOutputs: 1}, - {Inputs: [][]int{{0}, {1}, {0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 2}, - }, - "mulp": { - {Inputs: [][]int{{0}, {0}}, Modifies: []bool{true}, NumOutputs: 1}, - {Inputs: [][]int{{0}, {1}, {0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 2}, - }, - "xch": { - {Inputs: [][]int{{1}, {0}}, Modifies: []bool{false, false}, NumOutputs: 2}, - {Inputs: [][]int{{2}, {3}, {0}, {1}}, Modifies: []bool{false, false, false, false}, NumOutputs: 4}, - }, - "push": { - {Inputs: [][]int{{0, 1}}, Modifies: []bool{false, false}, NumOutputs: 2}, - {Inputs: [][]int{{0, 2}, {1, 3}}, Modifies: []bool{false, false, false, false}, NumOutputs: 4}, - }, - "pop": stackUseSink, - "envelope": stackUseSource, - "oscillator": stackUseSource, - "noise": stackUseSource, - "loadnote": stackUseSource, - "loadval": stackUseSource, - "receive": stackUseSource, - "in": stackUseSource, - "out": stackUseSink, - "outaux": stackUseSink, - "aux": stackUseSink, - "distort": stackUseEffect, - "hold": stackUseEffect, - "crush": stackUseEffect, - "gain": stackUseEffect, - "invgain": stackUseEffect, - "dbgain": stackUseEffect, - "filter": stackUseEffect, - "clip": stackUseEffect, - "delay": stackUseEffect, - "compressor": { - {Inputs: [][]int{{0, 1}}, Modifies: []bool{false, true}, NumOutputs: 2}, // mono - {Inputs: [][]int{{0, 2, 3}, {1, 2, 3}}, Modifies: []bool{false, false, true, true}, NumOutputs: 4}, // stereo - }, - "pan": { - {Inputs: [][]int{{0, 1}}, Modifies: []bool{true, true}, NumOutputs: 2}, // mono - {Inputs: [][]int{{0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 2}, // mono - }, - "speed": { - {Inputs: [][]int{{0}}, Modifies: []bool{true}, NumOutputs: 0}, - {}, - }, - "sync": { - {Inputs: [][]int{{0}}, Modifies: []bool{false}, NumOutputs: 1}, - {}, - }, - "belleq": stackUseEffect, -} -var stackUseSendNoPop = [2]StackUse{ - {Inputs: [][]int{{0}}, Modifies: []bool{true}, NumOutputs: 1}, - {Inputs: [][]int{{0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 2}, -} -var stackUseSendPop = [2]StackUse{ - {Inputs: [][]int{{0}}, Modifies: []bool{true}, NumOutputs: 0}, // mono - {Inputs: [][]int{{0}, {1}}, Modifies: []bool{true, true}, NumOutputs: 0}, // stereo -} - func (u *Unit) StackUse() StackUse { if u.Disabled { return StackUse{} } - if u.Type == "send" { - // "send" unit is special, it has a different stack use depending on sendpop - if u.Parameters["sendpop"] == 0 { - return stackUseSendNoPop[u.Parameters["stereo"]] - } - return stackUseSendPop[u.Parameters["stereo"]] + if ut, ok := UnitTypes[u.Type]; ok { + return ut.StackUse(u) } - return stackUseMonoStereo[u.Type][u.Parameters["stereo"]] + return StackUse{} } // StackChange returns how this unit will affect the signal stack. "pop" and @@ -638,7 +745,7 @@ func FindParamForModulationPort(unitName string, index int) (up UnitParameter, u if !ok { return UnitParameter{}, 0, false } - for i, param := range unitType { + for i, param := range unitType.Params { if !param.CanModulate { continue } diff --git a/tracker/derived.go b/tracker/derived.go index 1ee2a8e..2683288 100644 --- a/tracker/derived.go +++ b/tracker/derived.go @@ -116,7 +116,7 @@ func (m *Model) deriveParams(unit *sointu.Unit, ret []Parameter) []Parameter { return ret } portIndex := 0 - for i, up := range unitType { + for i, up := range unitType.Params { if !up.CanSet && !up.CanModulate { continue // skip parameters that cannot be set or modulated } @@ -131,7 +131,7 @@ func (m *Model) deriveParams(unit *sointu.Unit, ret []Parameter) []Parameter { portIndex++ q = portIndex } - ret = append(ret, Parameter{m: m, unit: unit, up: &unitType[i], vtable: &namedParameter{}, port: q}) + ret = append(ret, Parameter{m: m, unit: unit, up: &unitType.Params[i], vtable: &namedParameter{}, port: q}) } if unit.Type == "oscillator" && unit.Parameters["type"] == sointu.Sample { ret = append(ret, Parameter{m: m, unit: unit, vtable: &gmDlsEntryParameter{}}) diff --git a/tracker/model.go b/tracker/model.go index 3fcbf9a..5c8f7a1 100644 --- a/tracker/model.go +++ b/tracker/model.go @@ -512,7 +512,7 @@ var validParameters = map[string](map[string]bool){} func init() { for name, unitType := range sointu.UnitTypes { validParameters[name] = map[string]bool{} - for _, param := range unitType { + for _, param := range unitType.Params { validParameters[name][param.Name] = true } } diff --git a/tracker/params.go b/tracker/params.go index 53b68ae..f2cf794 100644 --- a/tracker/params.go +++ b/tracker/params.go @@ -409,12 +409,8 @@ func (n *namedParameter) RoundToGrid(p *Parameter, val int, up bool) int { return roundToGrid(val, 8, up) } func (n *namedParameter) Reset(p *Parameter) { - v, ok := defaultUnits[p.unit.Type].Parameters[p.up.Name] - if !ok || p.unit.Parameters[p.up.Name] == v { - return - } defer p.m.change("Reset"+p.Name(), PatchChange, MinorChange)() - p.unit.Parameters[p.up.Name] = v + p.unit.Parameters[p.up.Name] = p.up.Default } // GmDlsEntry is a single sample entry from the gm.dls file diff --git a/tracker/song.go b/tracker/song.go index 78b1bc8..6b57f11 100644 --- a/tracker/song.go +++ b/tracker/song.go @@ -154,53 +154,16 @@ func (m *SongModel) reset() { m.d.ChangedSinceSave = false } -var defaultUnits = map[string]sointu.Unit{ - "envelope": {Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 64, "gain": 64}}, - "oscillator": {Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 64, "shape": 64, "gain": 64, "type": sointu.Sine}}, - "noise": {Type: "noise", Parameters: map[string]int{"stereo": 0, "shape": 64, "gain": 64}}, - "mulp": {Type: "mulp", Parameters: map[string]int{"stereo": 0}}, - "mul": {Type: "mul", Parameters: map[string]int{"stereo": 0}}, - "add": {Type: "add", Parameters: map[string]int{"stereo": 0}}, - "addp": {Type: "addp", Parameters: map[string]int{"stereo": 0}}, - "push": {Type: "push", Parameters: map[string]int{"stereo": 0}}, - "pop": {Type: "pop", Parameters: map[string]int{"stereo": 0}}, - "xch": {Type: "xch", Parameters: map[string]int{"stereo": 0}}, - "receive": {Type: "receive", Parameters: map[string]int{"stereo": 0}}, - "loadnote": {Type: "loadnote", Parameters: map[string]int{"stereo": 0}}, - "loadval": {Type: "loadval", Parameters: map[string]int{"stereo": 0, "value": 64}}, - "pan": {Type: "pan", Parameters: map[string]int{"stereo": 0, "panning": 64}}, - "gain": {Type: "gain", Parameters: map[string]int{"stereo": 0, "gain": 64}}, - "invgain": {Type: "invgain", Parameters: map[string]int{"stereo": 0, "invgain": 64}}, - "dbgain": {Type: "dbgain", Parameters: map[string]int{"stereo": 0, "decibels": 64}}, - "crush": {Type: "crush", Parameters: map[string]int{"stereo": 0, "resolution": 64}}, - "clip": {Type: "clip", Parameters: map[string]int{"stereo": 0}}, - "hold": {Type: "hold", Parameters: map[string]int{"stereo": 0, "holdfreq": 64}}, - "distort": {Type: "distort", Parameters: map[string]int{"stereo": 0, "drive": 64}}, - "filter": {Type: "filter", Parameters: map[string]int{"stereo": 0, "frequency": 64, "resonance": 64, "lowpass": 1, "bandpass": 0, "highpass": 0}}, - "out": {Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 64}}, - "outaux": {Type: "outaux", Parameters: map[string]int{"stereo": 1, "outgain": 64, "auxgain": 64}}, - "aux": {Type: "aux", Parameters: map[string]int{"stereo": 1, "gain": 64, "channel": 2}}, - "delay": {Type: "delay", - Parameters: map[string]int{"damp": 0, "dry": 128, "feedback": 96, "notetracking": 2, "pregain": 40, "stereo": 0}, - VarArgs: []int{48}}, - "in": {Type: "in", Parameters: map[string]int{"stereo": 1, "channel": 2}}, - "speed": {Type: "speed", Parameters: map[string]int{}}, - "compressor": {Type: "compressor", Parameters: map[string]int{"stereo": 0, "attack": 64, "release": 64, "invgain": 64, "threshold": 64, "ratio": 64}}, - "send": {Type: "send", Parameters: map[string]int{"stereo": 0, "amount": 64, "voice": 0, "unit": 0, "port": 0, "sendpop": 1}}, - "sync": {Type: "sync", Parameters: map[string]int{}}, - "belleq": {Type: "belleq", Parameters: map[string]int{"stereo": 0, "frequency": 64, "bandwidth": 64, "gain": 64}}, -} - var defaultInstrument = sointu.Instrument{ Name: "Instr", NumVoices: 1, Units: []sointu.Unit{ - defaultUnits["envelope"], - defaultUnits["oscillator"], - defaultUnits["mulp"], - defaultUnits["delay"], - defaultUnits["pan"], - defaultUnits["outaux"], + sointu.MakeUnit("envelope"), + sointu.MakeUnit("oscillator"), + sointu.MakeUnit("mulp"), + sointu.MakeUnit("delay"), + sointu.MakeUnit("pan"), + sointu.MakeUnit("outaux"), }, } @@ -216,7 +179,7 @@ var defaultSong = sointu.Song{ }, Patch: sointu.Patch{defaultInstrument, {Name: "Global", NumVoices: 1, Units: []sointu.Unit{ - defaultUnits["in"], + sointu.MakeUnit("in"), {Type: "delay", Parameters: map[string]int{"damp": 64, "dry": 128, "feedback": 125, "notetracking": 0, "pregain": 40, "stereo": 1}, VarArgs: []int{1116, 1188, 1276, 1356, 1422, 1492, 1556, 1618, diff --git a/tracker/unit.go b/tracker/unit.go index 6555b89..0b3ef78 100644 --- a/tracker/unit.go +++ b/tracker/unit.go @@ -270,12 +270,7 @@ func (m *UnitModel) SetType(t string) { for len(m.d.Song.Patch[m.d.InstrIndex].Units) <= m.d.UnitIndex { m.d.Song.Patch[m.d.InstrIndex].Units = append(m.d.Song.Patch[m.d.InstrIndex].Units, sointu.Unit{}) } - unit, ok := defaultUnits[t] - if !ok { // if the type is invalid, we just set it to empty unit - unit = sointu.Unit{Parameters: make(map[string]int)} - } else { - unit = unit.Copy() - } + unit := sointu.MakeUnit(t) oldUnit := m.d.Song.Patch[m.d.InstrIndex].Units[m.d.UnitIndex] if oldUnit.Type == unit.Type { return diff --git a/vm/bytecode.go b/vm/bytecode.go index 01361e0..7a57a4f 100644 --- a/vm/bytecode.go +++ b/vm/bytecode.go @@ -262,7 +262,7 @@ func (b *bytecodeBuilder) operand(operands ...int) { // defOperands appends the operands to the stream for all parameters that can be // modulated and set func (b *bytecodeBuilder) defOperands(unit sointu.Unit) { - for _, v := range sointu.UnitTypes[unit.Type] { + for _, v := range sointu.UnitTypes[unit.Type].Params { if v.CanModulate && v.CanSet { b.Operands = append(b.Operands, byte(unit.Parameters[v.Name])) } diff --git a/vm/featureset.go b/vm/featureset.go index 59cd82c..e38fb96 100644 --- a/vm/featureset.go +++ b/vm/featureset.go @@ -58,7 +58,7 @@ func init() { for k, v := range sointu.UnitTypes { inputCount := 0 transformCount := 0 - for _, t := range v { + for _, t := range v.Params { if t.CanModulate { allInputs[paramKey{k, t.Name}] = inputCount inputCount++ @@ -125,7 +125,7 @@ func NecessaryFeaturesFor(patch sointu.Patch) NecessaryFeatures { 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 _, paramType := range sointu.UnitTypes[unit.Type] { + for _, paramType := range sointu.UnitTypes[unit.Type].Params { v := unit.Parameters[paramType.Name] key := paramKey{unit.Type, paramType.Name} if features.supportsParamValue[key] == nil {