mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-19 21:44:38 -04:00
Also removed the negbandpass & neghighpass parameters and replaced them with bandpass & highpass set to -1, to fit the switches better to the GUI. Closes #51, closes #173
357 lines
9.9 KiB
Go
357 lines
9.9 KiB
Go
package tracker
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/vsariola/sointu"
|
|
)
|
|
|
|
type (
|
|
Rail struct {
|
|
PassThrough int
|
|
Send bool
|
|
StackUse sointu.StackUse
|
|
}
|
|
|
|
Wire struct {
|
|
From int
|
|
FromSet bool
|
|
To Point
|
|
ToSet bool
|
|
Hint string
|
|
Highlight bool
|
|
}
|
|
|
|
RailError struct {
|
|
InstrIndex, UnitIndex int
|
|
Err error
|
|
}
|
|
|
|
// derivedModelData contains useful information derived from the modelData,
|
|
// cached for performance and/or easy access. This needs to be updated when
|
|
// corresponding part of the model changes.
|
|
derivedModelData struct {
|
|
// map Unit by ID, other entities by their respective index
|
|
patch []derivedInstrument
|
|
tracks []derivedTrack
|
|
railError RailError
|
|
}
|
|
|
|
derivedInstrument struct {
|
|
wires []Wire
|
|
rails []Rail
|
|
railWidth int
|
|
params [][]Parameter
|
|
paramsWidth int
|
|
}
|
|
|
|
derivedTrack struct {
|
|
title string
|
|
patternUseCounts []int
|
|
}
|
|
)
|
|
|
|
// public methods to access the derived data
|
|
|
|
func (s *Model) RailError() RailError { return s.derived.railError }
|
|
|
|
func (s *Model) RailWidth() int {
|
|
i := s.d.InstrIndex
|
|
if i < 0 || i >= len(s.derived.patch) {
|
|
return 0
|
|
}
|
|
return s.derived.patch[i].railWidth
|
|
}
|
|
|
|
func (m *Model) Wires(yield func(wire Wire) bool) {
|
|
i := m.d.InstrIndex
|
|
if i < 0 || i >= len(m.derived.patch) {
|
|
return
|
|
}
|
|
for _, wire := range m.derived.patch[i].wires {
|
|
wire.Highlight = (wire.FromSet && m.d.UnitIndex == wire.From) || (wire.ToSet && m.d.UnitIndex == wire.To.Y && m.d.ParamIndex == wire.To.X)
|
|
if !yield(wire) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *Model) TrackTitle(index int) string {
|
|
if index < 0 || index >= len(m.derived.tracks) {
|
|
return ""
|
|
}
|
|
return m.derived.tracks[index].title
|
|
}
|
|
|
|
func (m *Model) PatternUnique(track, pat int) bool {
|
|
if track < 0 || track >= len(m.derived.tracks) {
|
|
return false
|
|
}
|
|
if pat < 0 || pat >= len(m.derived.tracks[track].patternUseCounts) {
|
|
return false
|
|
}
|
|
return m.derived.tracks[track].patternUseCounts[pat] <= 1
|
|
}
|
|
|
|
func (e *RailError) Error() string { return e.Err.Error() }
|
|
|
|
func (s *Rail) StackAfter() int { return s.PassThrough + s.StackUse.NumOutputs }
|
|
|
|
// init / update methods
|
|
|
|
func (m *Model) updateDeriveData(changeType ChangeType) {
|
|
setSliceLength(&m.derived.tracks, len(m.d.Song.Score.Tracks))
|
|
if changeType&ScoreChange != 0 {
|
|
for index, track := range m.d.Song.Score.Tracks {
|
|
m.derived.tracks[index].patternUseCounts = m.buildPatternUseCounts(track)
|
|
}
|
|
}
|
|
if changeType&ScoreChange != 0 || changeType&PatchChange != 0 {
|
|
for index := range m.d.Song.Score.Tracks {
|
|
m.derived.tracks[index].title = m.buildTrackTitle(index)
|
|
}
|
|
}
|
|
setSliceLength(&m.derived.patch, len(m.d.Song.Patch))
|
|
if changeType&PatchChange != 0 {
|
|
m.updateParams()
|
|
m.updateRails()
|
|
m.updateWires()
|
|
}
|
|
}
|
|
|
|
func (m *Model) updateParams() {
|
|
for i, instr := range m.d.Song.Patch {
|
|
setSliceLength(&m.derived.patch[i].params, len(instr.Units))
|
|
paramsWidth := 0
|
|
for u := range instr.Units {
|
|
p := m.deriveParams(&instr.Units[u], m.derived.patch[i].params[u])
|
|
m.derived.patch[i].params[u] = p
|
|
paramsWidth = max(paramsWidth, len(p))
|
|
}
|
|
m.derived.patch[i].paramsWidth = paramsWidth
|
|
}
|
|
}
|
|
|
|
func (m *Model) deriveParams(unit *sointu.Unit, ret []Parameter) []Parameter {
|
|
ret = ret[:0] // reset the slice
|
|
unitType, ok := sointu.UnitTypes[unit.Type]
|
|
if !ok {
|
|
return ret
|
|
}
|
|
portIndex := 0
|
|
for i, up := range unitType {
|
|
if !up.CanSet && !up.CanModulate {
|
|
continue // skip parameters that cannot be set or modulated
|
|
}
|
|
if unit.Type == "oscillator" && unit.Parameters["type"] != sointu.Sample && (up.Name == "samplestart" || up.Name == "loopstart" || up.Name == "looplength") {
|
|
continue // don't show the sample related params unless necessary
|
|
}
|
|
if unit.Type == "send" && up.Name == "port" {
|
|
continue
|
|
}
|
|
q := 0
|
|
if up.CanModulate {
|
|
portIndex++
|
|
q = portIndex
|
|
}
|
|
ret = append(ret, Parameter{m: m, unit: unit, up: &unitType[i], vtable: &namedParameter{}, port: q})
|
|
}
|
|
if unit.Type == "oscillator" && unit.Parameters["type"] == sointu.Sample {
|
|
ret = append(ret, Parameter{m: m, unit: unit, vtable: &gmDlsEntryParameter{}})
|
|
}
|
|
if unit.Type == "delay" {
|
|
if unit.Parameters["stereo"] == 1 && len(unit.VarArgs)%2 == 1 {
|
|
unit.VarArgs = append(unit.VarArgs, 1)
|
|
}
|
|
ret = append(ret,
|
|
Parameter{m: m, unit: unit, vtable: &reverbParameter{}},
|
|
Parameter{m: m, unit: unit, vtable: &delayLinesParameter{}})
|
|
for i := range unit.VarArgs {
|
|
ret = append(ret, Parameter{m: m, unit: unit, index: i, vtable: &delayTimeParameter{}})
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (m *Model) instrumentRangeFor(trackIndex int) (int, int, error) {
|
|
track := m.d.Song.Score.Tracks[trackIndex]
|
|
if track.NumVoices <= 0 {
|
|
return 0, 0, fmt.Errorf("track %d has no voices", trackIndex)
|
|
}
|
|
firstVoice := m.d.Song.Score.FirstVoiceForTrack(trackIndex)
|
|
lastVoice := firstVoice + track.NumVoices - 1
|
|
firstIndex, err1 := m.d.Song.Patch.InstrumentForVoice(firstVoice)
|
|
if err1 != nil {
|
|
return trackIndex, trackIndex, err1
|
|
}
|
|
lastIndex, err2 := m.d.Song.Patch.InstrumentForVoice(lastVoice)
|
|
if err2 != nil {
|
|
return trackIndex, trackIndex, err2
|
|
}
|
|
return firstIndex, lastIndex, nil
|
|
}
|
|
|
|
func (m *Model) buildTrackTitle(track int) string {
|
|
if track < 0 || track >= len(m.d.Song.Score.Tracks) {
|
|
return "?"
|
|
}
|
|
firstIndex, lastIndex, err := m.instrumentRangeFor(track)
|
|
if err != nil {
|
|
return "?"
|
|
}
|
|
switch diff := lastIndex - firstIndex; diff {
|
|
case 0:
|
|
return nilIsQuestionMark(m.d.Song.Patch[firstIndex].Name)
|
|
case 1:
|
|
return fmt.Sprintf("%s/%s",
|
|
nilIsQuestionMark(m.d.Song.Patch[firstIndex].Name),
|
|
nilIsQuestionMark(m.d.Song.Patch[firstIndex+1].Name))
|
|
default:
|
|
return fmt.Sprintf("%s/%s/...",
|
|
nilIsQuestionMark(m.d.Song.Patch[firstIndex].Name),
|
|
nilIsQuestionMark(m.d.Song.Patch[firstIndex+1].Name))
|
|
}
|
|
}
|
|
|
|
func nilIsQuestionMark(s string) string {
|
|
if len(s) == 0 {
|
|
return "?"
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (m *Model) buildPatternUseCounts(track sointu.Track) []int {
|
|
result := make([]int, 0, len(track.Patterns))
|
|
for j := range min(len(track.Order), m.d.Song.Score.Length) {
|
|
if p := track.Order[j]; p >= 0 {
|
|
for len(result) <= p {
|
|
result = append(result, 0)
|
|
}
|
|
result[p]++
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (m *Model) updateRails() {
|
|
type stackElem struct{ instr, unit int }
|
|
scratchArray := [32]stackElem{}
|
|
scratch := scratchArray[:0]
|
|
m.derived.railError = RailError{}
|
|
for i, instr := range m.d.Song.Patch {
|
|
setSliceLength(&m.derived.patch[i].rails, len(instr.Units))
|
|
start := len(scratch)
|
|
maxWidth := 0
|
|
for u, unit := range instr.Units {
|
|
stackUse := unit.StackUse()
|
|
numInputs := len(stackUse.Inputs)
|
|
if len(scratch) < numInputs {
|
|
if m.derived.railError == (RailError{}) {
|
|
m.derived.railError = RailError{
|
|
InstrIndex: i,
|
|
UnitIndex: u,
|
|
Err: fmt.Errorf("%s unit in instrument %d / %s needs %d inputs, but got only %d", unit.Type, i, instr.Name, numInputs, len(scratch)),
|
|
}
|
|
}
|
|
scratch = scratch[:0]
|
|
} else {
|
|
scratch = scratch[:len(scratch)-numInputs]
|
|
}
|
|
m.derived.patch[i].rails[u] = Rail{
|
|
PassThrough: len(scratch),
|
|
StackUse: stackUse,
|
|
Send: !unit.Disabled && unit.Type == "send",
|
|
}
|
|
maxWidth = max(maxWidth, len(scratch)+max(len(stackUse.Inputs), stackUse.NumOutputs))
|
|
for range stackUse.NumOutputs {
|
|
scratch = append(scratch, stackElem{instr: i, unit: u})
|
|
}
|
|
}
|
|
m.derived.patch[i].railWidth = maxWidth
|
|
diff := len(scratch) - start
|
|
if instr.NumVoices > 1 && diff != 0 {
|
|
if diff < 0 {
|
|
morepop := (instr.NumVoices - 1) * diff
|
|
if morepop > len(scratch) {
|
|
if m.derived.railError == (RailError{}) {
|
|
m.derived.railError = RailError{
|
|
InstrIndex: i,
|
|
UnitIndex: -1,
|
|
Err: fmt.Errorf("each voice of instrument %d / %s consumes %d signals, but there was not enough signals available", i, instr.Name, -diff),
|
|
}
|
|
}
|
|
scratch = scratch[:0]
|
|
} else {
|
|
scratch = scratch[:len(scratch)-morepop]
|
|
}
|
|
} else {
|
|
for range (instr.NumVoices - 1) * diff {
|
|
scratch = append(scratch, scratch[len(scratch)-diff])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if len(scratch) > 0 && m.derived.railError == (RailError{}) {
|
|
patch := m.d.Song.Patch
|
|
m.derived.railError = RailError{
|
|
InstrIndex: scratch[0].instr,
|
|
UnitIndex: scratch[0].unit,
|
|
Err: fmt.Errorf("instrument %d / %s unit %d / %s leaves a signal on stack", scratch[0].instr, patch[scratch[0].instr].Name, scratch[0].unit, patch[scratch[0].instr].Units[scratch[0].unit].Type),
|
|
}
|
|
}
|
|
if m.derived.railError.Err != nil {
|
|
m.Alerts().AddNamed("RailError", m.derived.railError.Error(), Error)
|
|
}
|
|
}
|
|
|
|
func (m *Model) updateWires() {
|
|
for i := range m.d.Song.Patch {
|
|
m.derived.patch[i].wires = m.derived.patch[i].wires[:0] // reset the wires
|
|
}
|
|
for i, instr := range m.d.Song.Patch {
|
|
for u, unit := range instr.Units {
|
|
if unit.Disabled || unit.Type != "send" {
|
|
continue
|
|
}
|
|
tI, tU, err := m.d.Song.Patch.FindUnit(unit.Parameters["target"])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
up, tX, ok := sointu.FindParamForModulationPort(m.d.Song.Patch[tI].Units[tU].Type, unit.Parameters["port"])
|
|
if !ok {
|
|
continue
|
|
}
|
|
if tI == i {
|
|
// local send
|
|
m.derived.patch[i].wires = append(m.derived.patch[i].wires, Wire{
|
|
From: u,
|
|
FromSet: true,
|
|
To: Point{X: tX, Y: tU},
|
|
ToSet: true,
|
|
})
|
|
} else {
|
|
// remote send
|
|
m.derived.patch[i].wires = append(m.derived.patch[i].wires, Wire{
|
|
From: u,
|
|
FromSet: true,
|
|
Hint: fmt.Sprintf("To instrument #%d (%s), unit #%d (%s), port %s", tI, m.d.Song.Patch[tI].Name, tU, m.d.Song.Patch[tI].Units[tU].Type, up.Name),
|
|
})
|
|
toPt := Point{X: tX, Y: tU}
|
|
hint := fmt.Sprintf("From instrument #%d (%s), send #%d", i, m.d.Song.Patch[i].Name, u)
|
|
for i, w := range m.derived.patch[tI].wires {
|
|
if !w.FromSet && w.ToSet && w.To == toPt {
|
|
m.derived.patch[tI].wires[i].Hint += "; " + hint
|
|
goto skipAppend
|
|
}
|
|
}
|
|
m.derived.patch[tI].wires = append(m.derived.patch[tI].wires, Wire{
|
|
To: toPt,
|
|
ToSet: true,
|
|
Hint: hint,
|
|
})
|
|
skipAppend:
|
|
}
|
|
}
|
|
}
|
|
}
|