sointu/vm/featureset.go
5684185+vsariola@users.noreply.github.com 17312bbe4e feat: add ability to disable units temporarily
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.
2024-02-19 21:36:14 +02:00

205 lines
5.9 KiB
Go

package vm
import (
"sort"
"github.com/vsariola/sointu"
)
type (
// FeatureSet defines what opcodes / parameters are included in the compiled virtual machine
// It is used by the compiler to decide how to encode opcodes
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
SupportsGlobalSend() bool
}
// 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
AllFeatures struct {
}
// NecessaryFeatures returns true only if the patch actually needs the support for the feature
NecessaryFeatures struct {
opcodes map[string]int
instructions []string
supportsParamValue map[paramKey](map[int]bool)
supportsModulation map[paramKey]bool
globalSend bool
polyphony bool
}
)
type paramKey struct {
Unit string
Param string
}
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
}
}
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) SupportsGlobalSend() 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}]
}
func NecessaryFeaturesFor(patch sointu.Patch) NecessaryFeatures {
features := NecessaryFeatures{opcodes: map[string]int{}, supportsParamValue: map[paramKey](map[int]bool){}, supportsModulation: map[paramKey]bool{}}
for instrIndex, instrument := range patch {
for _, unit := range instrument.Units {
if unit.Type == "" || unit.Disabled {
continue
}
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 _, paramType := range sointu.UnitTypes[unit.Type] {
v := unit.Parameters[paramType.Name]
key := paramKey{unit.Type, paramType.Name}
if features.supportsParamValue[key] == nil {
features.supportsParamValue[key] = map[int]bool{}
}
features.supportsParamValue[key][v] = true
}
if unit.Type == "send" {
targetInstrIndex, targetUnitIndex, err := patch.FindUnit(unit.Parameters["target"])
if err != nil {
continue
}
targetUnit := patch[targetInstrIndex].Units[targetUnitIndex]
portList := sointu.Ports[targetUnit.Type]
portIndex := unit.Parameters["port"]
if portIndex < 0 || portIndex >= len(portList) {
continue
}
if targetInstrIndex != instrIndex || unit.Parameters["voice"] > 0 {
features.globalSend = true
}
features.supportsModulation[paramKey{targetUnit.Type, portList[portIndex]}] = true
}
}
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]
}
func (n NecessaryFeatures) SupportsGlobalSend() bool {
return n.globalSend
}