mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-25 18:00:37 -04:00
Quite often the user wants to experiment what particular unit(s) add to the sound. This commit adds ability to disable any set of units temporarily, without actually deleting them. Ctrl-D disables and re-enables the units. Disabled units are considered non-existent in the patch. Closes #116.
532 lines
20 KiB
Go
532 lines
20 KiB
Go
package sointu
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
)
|
|
|
|
type (
|
|
// Patch is simply a list of instruments used in a song
|
|
Patch []Instrument
|
|
|
|
// Instrument includes a list of units consisting of the instrument, and the number of polyphonic voices for this instrument
|
|
Instrument struct {
|
|
Name string `yaml:",omitempty"`
|
|
Comment string `yaml:",omitempty"`
|
|
NumVoices int
|
|
Units []Unit
|
|
}
|
|
|
|
// Unit is e.g. a filter, oscillator, envelope and its parameters
|
|
Unit struct {
|
|
// Type is the type of the unit, e.g. "add","oscillator" or "envelope".
|
|
// Always in lowercase. "" type should be ignored, no invalid types should
|
|
// be used.
|
|
Type string `yaml:",omitempty"`
|
|
|
|
// ID should be a unique ID for this unit, used by SEND units to target
|
|
// specific units. ID = 0 means that no ID has been given to a unit and thus
|
|
// cannot be targeted by SENDs. When possible, units that are not targeted
|
|
// by any SENDs should be cleaned from having IDs, e.g. to keep the exported
|
|
// data clean.
|
|
ID int `yaml:",omitempty"`
|
|
|
|
// Parameters is a map[string]int of parameters of a unit. For example, for
|
|
// an oscillator, unit.Type == "oscillator" and unit.Parameters["attack"]
|
|
// could be 64. Most parameters are either limites to 0 and 1 (e.g. stereo
|
|
// parameters) or between 0 and 128, inclusive.
|
|
Parameters map[string]int `yaml:",flow"`
|
|
|
|
// VarArgs is a list containing the variable number arguments that some
|
|
// units require, most notably the DELAY units. For example, for a DELAY
|
|
// unit, VarArgs is the delaytimes, in samples, of the different delaylines
|
|
// in the unit.
|
|
VarArgs []int `yaml:",flow,omitempty"`
|
|
|
|
// Disabled is a flag that can be set to true to disable the unit.
|
|
// Disabled units are considered to be not present in the patch.
|
|
Disabled bool `yaml:",omitempty"`
|
|
}
|
|
|
|
// 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
|
|
CanSet bool // if this parameter can be set before hand i.e. through the gui
|
|
CanModulate bool // if this parameter can be modulated i.e. has a port number in "send" unit
|
|
}
|
|
)
|
|
|
|
// 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, 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}},
|
|
"dbgain": []UnitParameter{
|
|
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
|
{Name: "decibels", 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: 2, 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: "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},
|
|
{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: "frequency", MinValue: 0, MaxValue: -1, CanSet: false, 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: "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}},
|
|
"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}},
|
|
"sync": []UnitParameter{},
|
|
}
|
|
|
|
// When unit.Type = "oscillator", its unit.Parameter["Type"] tells the type of
|
|
// the oscillator. There is five different oscillator types, so these consts
|
|
// just enumerate them.
|
|
const (
|
|
Sine = iota
|
|
Trisaw = iota
|
|
Pulse = iota
|
|
Gate = iota
|
|
Sample = iota
|
|
)
|
|
|
|
// UnitNames is a list of all the names of units, sorted
|
|
// alphabetically.
|
|
var UnitNames []string
|
|
|
|
func init() {
|
|
UnitNames = make([]string, 0, len(UnitTypes))
|
|
for k := range UnitTypes {
|
|
UnitNames = append(UnitNames, k)
|
|
}
|
|
sort.Strings(UnitNames)
|
|
}
|
|
|
|
// Ports is static map allowing quickly finding the parameters of a unit that
|
|
// can be modulated. This is populated based on the UnitTypes list during
|
|
// init(). Thus, should be immutable, but Go not supporting that, then this will
|
|
// have to suffice: DO NOT EVER CHANGE THIS MAP.
|
|
var Ports = make(map[string]([]string))
|
|
|
|
func init() {
|
|
for name, unitType := range UnitTypes {
|
|
unitPorts := make([]string, 0)
|
|
for _, param := range unitType {
|
|
if param.CanModulate {
|
|
unitPorts = append(unitPorts, param.Name)
|
|
}
|
|
}
|
|
Ports[name] = unitPorts
|
|
}
|
|
}
|
|
|
|
// Copy makes a deep copy of a unit.
|
|
func (u *Unit) Copy() Unit {
|
|
parameters := make(map[string]int)
|
|
for k, v := range u.Parameters {
|
|
parameters[k] = v
|
|
}
|
|
varArgs := make([]int, len(u.VarArgs))
|
|
copy(varArgs, u.VarArgs)
|
|
return Unit{Type: u.Type, Parameters: parameters, VarArgs: varArgs, ID: u.ID, Disabled: u.Disabled}
|
|
}
|
|
|
|
// StackChange returns how this unit will affect the signal stack. "pop" and
|
|
// "addp" and such will consume the topmost signal, and thus return -1 (or -2,
|
|
// if the unit is a stereo unit). On the other hand, "oscillator" and "envelope"
|
|
// will produce a signal, and thus return 1 (or 2, if the unit is a stereo
|
|
// unit). Effects that just change the topmost signal and will not change the
|
|
// number of signals on the stack and thus return 0.
|
|
func (u *Unit) StackChange() int {
|
|
if u.Disabled {
|
|
return 0
|
|
}
|
|
switch u.Type {
|
|
case "addp", "mulp", "pop", "out", "outaux", "aux":
|
|
return -1 - u.Parameters["stereo"]
|
|
case "envelope", "oscillator", "push", "noise", "receive", "loadnote", "loadval", "in", "compressor":
|
|
return 1 + u.Parameters["stereo"]
|
|
case "pan":
|
|
return 1 - u.Parameters["stereo"]
|
|
case "speed":
|
|
return -1
|
|
case "send":
|
|
return (-1 - u.Parameters["stereo"]) * u.Parameters["sendpop"]
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// StackNeed returns the number of signals that should be on the stack before
|
|
// this unit is executed. Used to prevent stack underflow. Units producing
|
|
// signals do not care what is on the stack before and will return 0.
|
|
func (u *Unit) StackNeed() int {
|
|
if u.Disabled {
|
|
return 0
|
|
}
|
|
switch u.Type {
|
|
case "", "envelope", "oscillator", "noise", "receive", "loadnote", "loadval", "in":
|
|
return 0
|
|
case "mulp", "mul", "add", "addp", "xch":
|
|
return 2 * (1 + u.Parameters["stereo"])
|
|
case "speed":
|
|
return 1
|
|
}
|
|
return 1 + u.Parameters["stereo"]
|
|
}
|
|
|
|
// Copy makes a deep copy of an Instrument
|
|
func (instr *Instrument) Copy() Instrument {
|
|
units := make([]Unit, len(instr.Units))
|
|
for i, u := range instr.Units {
|
|
units[i] = u.Copy()
|
|
}
|
|
return Instrument{Name: instr.Name, Comment: instr.Comment, NumVoices: instr.NumVoices, Units: units}
|
|
}
|
|
|
|
// Copy makes a deep copy of a Patch.
|
|
func (p Patch) Copy() Patch {
|
|
instruments := make([]Instrument, len(p))
|
|
for i, instr := range p {
|
|
instruments[i] = instr.Copy()
|
|
}
|
|
return instruments
|
|
}
|
|
|
|
// NumVoices returns the total number of voices used in the patch; summing the
|
|
// voices of every instrument
|
|
func (p Patch) NumVoices() int {
|
|
ret := 0
|
|
for _, i := range p {
|
|
ret += i.NumVoices
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// NumDelayLines return the total number of delay lines used in the patch;
|
|
// summing the number of delay lines of every delay unit in every instrument
|
|
func (p Patch) NumDelayLines() int {
|
|
total := 0
|
|
for _, instr := range p {
|
|
for _, unit := range instr.Units {
|
|
if unit.Type == "delay" {
|
|
total += len(unit.VarArgs) * instr.NumVoices
|
|
}
|
|
}
|
|
}
|
|
return total
|
|
}
|
|
|
|
// NumSyns return the total number of sync outputs used in the patch; summing
|
|
// the number of sync outputs of every sync unit in every instrument
|
|
func (p Patch) NumSyncs() int {
|
|
total := 0
|
|
for _, instr := range p {
|
|
for _, unit := range instr.Units {
|
|
if unit.Type == "sync" {
|
|
total += instr.NumVoices
|
|
}
|
|
}
|
|
}
|
|
return total
|
|
}
|
|
|
|
// FirstVoiceForInstrument returns the index of the first voice of given
|
|
// instrument. For example, if the Patch has three instruments (0, 1 and 2),
|
|
// with 1, 3, 2 voices, respectively, then FirstVoiceForInstrument(0) returns 0,
|
|
// FirstVoiceForInstrument(1) returns 1 and FirstVoiceForInstrument(2) returns
|
|
// 4. Essentially computes just the cumulative sum.
|
|
func (p Patch) FirstVoiceForInstrument(instrIndex int) int {
|
|
ret := 0
|
|
for _, t := range p[:instrIndex] {
|
|
ret += t.NumVoices
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// InstrumentForVoice returns the instrument number for the given voice index.
|
|
// For example, if the Patch has three instruments (0, 1 and 2), with 1, 3, 2
|
|
// voices, respectively, then InstrumentForVoice(0) returns 0,
|
|
// InstrumentForVoice(1) returns 1 and InstrumentForVoice(3) returns 1.
|
|
func (p Patch) InstrumentForVoice(voice int) (int, error) {
|
|
if voice < 0 {
|
|
return 0, errors.New("voice cannot be negative")
|
|
}
|
|
for i, instr := range p {
|
|
if voice < instr.NumVoices {
|
|
return i, nil
|
|
}
|
|
voice -= instr.NumVoices
|
|
}
|
|
return 0, errors.New("voice number is beyond the total voices of an instrument")
|
|
}
|
|
|
|
// FindUnit searches the instrument index and unit index for a unit with the
|
|
// given id. Two units should never have the same id, but if they do, then the
|
|
// first match is returned. Id 0 is interpreted as "no id", thus searching for
|
|
// id 0 returns an error. Error is also returned if the searched id is not
|
|
// found. FindUnit considers disabled units as non-existent.
|
|
func (p Patch) FindUnit(id int) (instrIndex int, unitIndex int, err error) {
|
|
if id == 0 {
|
|
return 0, 0, errors.New("FindUnit called with id 0")
|
|
}
|
|
for i, instr := range p {
|
|
for u, unit := range instr.Units {
|
|
if unit.ID == id && !unit.Disabled {
|
|
return i, u, nil
|
|
}
|
|
}
|
|
}
|
|
return 0, 0, fmt.Errorf("could not find a unit with id %v", id)
|
|
}
|
|
|
|
// ParamHintString returns a human readable string representing the current
|
|
// value of a given unit parameter.
|
|
func (p Patch) ParamHintString(instrIndex, unitIndex int, param string) string {
|
|
if instrIndex < 0 || instrIndex >= len(p) {
|
|
return ""
|
|
}
|
|
instr := p[instrIndex]
|
|
if unitIndex < 0 || unitIndex >= len(instr.Units) {
|
|
return ""
|
|
}
|
|
unit := instr.Units[unitIndex]
|
|
value := unit.Parameters[param]
|
|
switch unit.Type {
|
|
case "envelope":
|
|
switch param {
|
|
case "attack":
|
|
return engineeringTime(math.Pow(2, 24*float64(value)/128) / 44100)
|
|
case "decay":
|
|
return engineeringTime(math.Pow(2, 24*float64(value)/128) / 44100 * (1 - float64(unit.Parameters["sustain"])/128))
|
|
case "release":
|
|
return engineeringTime(math.Pow(2, 24*float64(value)/128) / 44100 * float64(unit.Parameters["sustain"]) / 128)
|
|
}
|
|
case "oscillator":
|
|
switch param {
|
|
case "type":
|
|
switch value {
|
|
case Sine:
|
|
return "Sine"
|
|
case Trisaw:
|
|
return "Trisaw"
|
|
case Pulse:
|
|
return "Pulse"
|
|
case Gate:
|
|
return "Gate"
|
|
case Sample:
|
|
return "Sample"
|
|
default:
|
|
return "Unknown"
|
|
}
|
|
case "transpose":
|
|
relvalue := value - 64
|
|
octaves := relvalue / 12
|
|
semitones := relvalue % 12
|
|
if octaves != 0 {
|
|
return fmt.Sprintf("%v oct, %v st", octaves, semitones)
|
|
}
|
|
return fmt.Sprintf("%v st", semitones)
|
|
case "detune":
|
|
return fmt.Sprintf("%v st", float32(value-64)/64.0)
|
|
}
|
|
case "compressor":
|
|
switch param {
|
|
case "attack":
|
|
fallthrough
|
|
case "release":
|
|
alpha := math.Pow(2, -24*float64(value)/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)
|
|
case "ratio":
|
|
return fmt.Sprintf("1 : %.3f", 1-float64(value)/128)
|
|
}
|
|
case "loadval":
|
|
switch param {
|
|
case "value":
|
|
return fmt.Sprintf("%.2f", float32(value)/64-1)
|
|
}
|
|
case "send":
|
|
switch param {
|
|
case "amount":
|
|
return fmt.Sprintf("%.2f", float32(value)/64-1)
|
|
case "voice":
|
|
if value == 0 {
|
|
targetIndex, _, err := p.FindUnit(unit.Parameters["target"])
|
|
if err == nil && targetIndex != instrIndex {
|
|
return "all"
|
|
}
|
|
return "self"
|
|
}
|
|
return fmt.Sprintf("%v", value)
|
|
case "target":
|
|
instrIndex, unitIndex, err := p.FindUnit(unit.Parameters["target"])
|
|
if err != nil {
|
|
return "invalid target"
|
|
}
|
|
instr := p[instrIndex]
|
|
unit := instr.Units[unitIndex]
|
|
return fmt.Sprintf("%v / %v%v", instr.Name, unit.Type, unitIndex)
|
|
case "port":
|
|
instrIndex, unitIndex, err := p.FindUnit(unit.Parameters["target"])
|
|
if err != nil {
|
|
return fmt.Sprintf("%v ???", value)
|
|
}
|
|
portList := Ports[p[instrIndex].Units[unitIndex].Type]
|
|
if value < 0 || value >= len(portList) {
|
|
return fmt.Sprintf("%v ???", value)
|
|
}
|
|
return fmt.Sprintf(portList[value])
|
|
}
|
|
case "delay":
|
|
switch param {
|
|
case "notetracking":
|
|
switch value {
|
|
case 0:
|
|
return "fixed"
|
|
case 1:
|
|
return "tracks pitch"
|
|
case 2:
|
|
return "tracks BPM"
|
|
}
|
|
}
|
|
case "in", "aux":
|
|
switch param {
|
|
case "channel":
|
|
switch value {
|
|
case 0:
|
|
return "left"
|
|
case 1:
|
|
return "right"
|
|
case 2:
|
|
return "aux1 left"
|
|
case 3:
|
|
return "aux1 right"
|
|
case 4:
|
|
return "aux2 left"
|
|
case 5:
|
|
return "aux2 right"
|
|
case 6:
|
|
return "aux3 left"
|
|
case 7:
|
|
return "aux3 right"
|
|
}
|
|
}
|
|
case "dbgain":
|
|
switch param {
|
|
case "decibels":
|
|
return fmt.Sprintf("%.2f dB", 40*(float32(value)/64-1))
|
|
}
|
|
case "crush":
|
|
switch param {
|
|
case "resolution":
|
|
return fmt.Sprintf("%v bits", 24*float32(value)/128)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func engineeringTime(sec float64) string {
|
|
if sec < 1e-3 {
|
|
return fmt.Sprintf("%.2f us", sec*1e6)
|
|
} else if sec < 1 {
|
|
return fmt.Sprintf("%.2f ms", sec*1e3)
|
|
}
|
|
return fmt.Sprintf("%.2f s", sec)
|
|
}
|