mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-27 10:50:23 -04:00
The old "native" compiler bridged version is now started with cmd/sointu-nativetrack, while the new pure-Go bytecode implemented bytecode interpreter is started with cmd/sointu-track Thus, you do not need any of the CMake / cgo stuff to run cmd/sointu-track
163 lines
3.8 KiB
Go
163 lines
3.8 KiB
Go
package sointu
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
)
|
|
|
|
// Patch is simply a list of instruments used in a song
|
|
type Patch []Instrument
|
|
|
|
func (p Patch) Copy() Patch {
|
|
instruments := make([]Instrument, len(p))
|
|
for i, instr := range p {
|
|
instruments[i] = instr.Copy()
|
|
}
|
|
return instruments
|
|
}
|
|
|
|
func (p Patch) NumVoices() int {
|
|
ret := 0
|
|
for _, i := range p {
|
|
ret += i.NumVoices
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (p Patch) NumDelayLines() int {
|
|
total := 0
|
|
for _, instr := range p {
|
|
for _, unit := range instr.Units {
|
|
if unit.Type == "delay" {
|
|
total += len(unit.VarArgs)
|
|
}
|
|
}
|
|
}
|
|
return total
|
|
}
|
|
|
|
func (p Patch) FirstVoiceForInstrument(instrIndex int) int {
|
|
ret := 0
|
|
for _, t := range p[:instrIndex] {
|
|
ret += t.NumVoices
|
|
}
|
|
return ret
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
func (p Patch) FindSendTarget(id int) (int, int, error) {
|
|
if id == 0 {
|
|
return 0, 0, errors.New("send targets unit id 0")
|
|
}
|
|
for i, instr := range p {
|
|
for u, unit := range instr.Units {
|
|
if unit.ID == id {
|
|
return i, u, nil
|
|
}
|
|
}
|
|
}
|
|
return 0, 0, fmt.Errorf("send targets an unit with id %v, could not find a unit with such an ID in the patch", id)
|
|
}
|
|
|
|
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 "send":
|
|
switch param {
|
|
case "voice":
|
|
if value == 0 {
|
|
return "auto"
|
|
}
|
|
return fmt.Sprintf("%v", value)
|
|
case "target":
|
|
instrIndex, unitIndex, err := p.FindSendTarget(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.FindSendTarget(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])
|
|
}
|
|
}
|
|
return ""
|
|
}
|