mirror of
https://github.com/vsariola/sointu.git
synced 2025-07-23 07:24:47 -04:00
drafting
This commit is contained in:
parent
3c6c24c6af
commit
53af773815
@ -2,176 +2,135 @@ package tracker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"slices"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
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
|
||||
forUnit map[int]derivedForUnit
|
||||
forTrack []derivedForTrack
|
||||
forPattern []derivedForPattern
|
||||
rail SignalRail // signal rail for the current patch
|
||||
patch []derivedInstrument
|
||||
tracks []derivedTrack
|
||||
railError RailError
|
||||
}
|
||||
|
||||
derivedForUnit struct {
|
||||
unit sointu.Unit
|
||||
instrument sointu.Instrument
|
||||
instrumentIndex int
|
||||
unitIndex int
|
||||
params []Parameter
|
||||
|
||||
// map param by Name
|
||||
forParameter map[string]derivedForParameter
|
||||
derivedInstrument struct {
|
||||
wires []Wire
|
||||
rails []Rail
|
||||
railsWidth int
|
||||
params [][]Parameter
|
||||
paramsWidth int
|
||||
}
|
||||
|
||||
derivedForParameter struct {
|
||||
sendTooltip string
|
||||
sendSources []sendSourceData
|
||||
}
|
||||
|
||||
sendSourceData struct {
|
||||
unitId int
|
||||
paramName string
|
||||
amount int
|
||||
instrumentIndex int
|
||||
instrumentName string
|
||||
}
|
||||
|
||||
derivedForTrack struct {
|
||||
instrumentRange []int
|
||||
tracksWithSameInstrument []int
|
||||
title string
|
||||
}
|
||||
|
||||
derivedForPattern struct {
|
||||
useCount []int
|
||||
derivedTrack struct {
|
||||
title string
|
||||
patternUseCounts []int
|
||||
}
|
||||
)
|
||||
|
||||
// public access functions
|
||||
// public methods to access the derived data
|
||||
|
||||
func (m *Model) UnitInfo(id int) (instrName string, units []sointu.Unit, unitIndex int, ok bool) {
|
||||
fu, ok := m.derived.forUnit[id]
|
||||
return fu.instrument.Name, fu.instrument.Units, fu.unitIndex, ok
|
||||
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].railsWidth
|
||||
}
|
||||
|
||||
func (m *Model) UnitHintInfo(id int) (instrIndex int, unitType string, ok bool) {
|
||||
fu, ok := m.derived.forUnit[id]
|
||||
return fu.instrumentIndex, fu.unit.Type, ok
|
||||
}
|
||||
|
||||
func (m *Model) ParameterInfo(unitId int, paramName string) (tooltip string, ok bool) {
|
||||
du, ok1 := m.derived.forUnit[unitId]
|
||||
if !ok1 {
|
||||
return "", false
|
||||
func (m *Model) Wires(yield func(wire Wire) bool) {
|
||||
i := m.d.InstrIndex
|
||||
if i < 0 || i >= len(m.derived.patch) {
|
||||
return
|
||||
}
|
||||
dp, ok2 := du.forParameter[paramName]
|
||||
if !ok2 {
|
||||
return "", false
|
||||
for _, wire := range m.derived.patch[i].wires {
|
||||
if !yield(wire) {
|
||||
return
|
||||
}
|
||||
}
|
||||
return dp.sendTooltip, len(dp.sendSources) > 0
|
||||
}
|
||||
|
||||
func (m *Model) TrackTitle(index int) string {
|
||||
if index < 0 || index >= len(m.derived.forTrack) {
|
||||
if index < 0 || index >= len(m.derived.tracks) {
|
||||
return ""
|
||||
}
|
||||
return m.derived.forTrack[index].title
|
||||
return m.derived.tracks[index].title
|
||||
}
|
||||
|
||||
func (m *Model) PatternUnique(track, pat int) bool {
|
||||
if track < 0 || track >= len(m.derived.forPattern) {
|
||||
if track < 0 || track >= len(m.derived.tracks) {
|
||||
return false
|
||||
}
|
||||
forPattern := m.derived.forPattern[track]
|
||||
if pat < 0 || pat >= len(forPattern.useCount) {
|
||||
if pat < 0 || pat >= len(m.derived.tracks[track].patternUseCounts) {
|
||||
return false
|
||||
}
|
||||
return forPattern.useCount[pat] == 1
|
||||
return m.derived.tracks[track].patternUseCounts[pat] <= 1
|
||||
}
|
||||
|
||||
// public getters with further model information
|
||||
func (e *RailError) Error() string { return e.Err.Error() }
|
||||
|
||||
func (m *Model) TracksWithSameInstrumentAsCurrent() []int {
|
||||
currentTrack := m.d.Cursor.Track
|
||||
if currentTrack >= len(m.derived.forTrack) {
|
||||
return nil
|
||||
}
|
||||
return m.derived.forTrack[currentTrack].tracksWithSameInstrument
|
||||
}
|
||||
|
||||
func (m *Model) CountNextTracksForCurrentInstrument() int {
|
||||
currentTrack := m.d.Cursor.Track
|
||||
count := 0
|
||||
for t := range m.TracksWithSameInstrumentAsCurrent() {
|
||||
if t > currentTrack {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
func (s *Rail) StackAfter() int { return s.PassThrough + s.StackUse.NumOutputs }
|
||||
|
||||
// init / update methods
|
||||
|
||||
func (m *Model) initDerivedData() {
|
||||
m.derived = derivedModelData{
|
||||
forUnit: make(map[int]derivedForUnit),
|
||||
forTrack: make([]derivedForTrack, 0),
|
||||
forPattern: make([]derivedForPattern, 0),
|
||||
}
|
||||
m.updateDerivedScoreData()
|
||||
m.updateDerivedPatchData()
|
||||
}
|
||||
|
||||
func (m *Model) updateDerivedScoreData() {
|
||||
m.derived.forTrack = m.derived.forTrack[:0]
|
||||
m.derived.forPattern = m.derived.forPattern[:0]
|
||||
for index, track := range m.d.Song.Score.Tracks {
|
||||
firstInstr, lastInstr, _ := m.instrumentRangeFor(index)
|
||||
m.derived.forTrack = append(
|
||||
m.derived.forTrack,
|
||||
derivedForTrack{
|
||||
instrumentRange: []int{firstInstr, lastInstr},
|
||||
tracksWithSameInstrument: slices.Collect(m.tracksWithSameInstrument(index)),
|
||||
title: m.buildTrackTitle(index),
|
||||
},
|
||||
)
|
||||
m.derived.forPattern = append(
|
||||
m.derived.forPattern,
|
||||
derivedForPattern{
|
||||
useCount: m.calcPatternUseCounts(track),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) updateDerivedPatchData() {
|
||||
m.SignalRail().update()
|
||||
clear(m.derived.forUnit)
|
||||
for i, instr := range m.d.Song.Patch {
|
||||
for u, unit := range instr.Units {
|
||||
m.derived.forUnit[unit.ID] = derivedForUnit{
|
||||
unit: unit,
|
||||
unitIndex: u,
|
||||
instrument: instr,
|
||||
instrumentIndex: i,
|
||||
params: m.deriveParams(&instr.Units[u]),
|
||||
forParameter: make(map[string]derivedForParameter),
|
||||
}
|
||||
m.updateDerivedParameterData(unit)
|
||||
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) deriveParams(unit *sointu.Unit) []Parameter {
|
||||
ret := make([]Parameter, 0, 10)
|
||||
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, unit := range instr.Units {
|
||||
m.derived.patch[i].params[u] = m.deriveParams(&unit, m.derived.patch[i].params[u])
|
||||
paramsWidth = max(paramsWidth, len(m.derived.patch[i].params[u]))
|
||||
}
|
||||
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
|
||||
@ -211,73 +170,6 @@ func (m *Model) deriveParams(unit *sointu.Unit) []Parameter {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (m *Model) updateDerivedParameterData(unit sointu.Unit) {
|
||||
fu := m.derived.forUnit[unit.ID]
|
||||
for name := range fu.unit.Parameters {
|
||||
sendSources := slices.Collect(m.collectSendSources(unit, name))
|
||||
fu.forParameter[name] = derivedForParameter{
|
||||
sendSources: sendSources,
|
||||
sendTooltip: m.buildSendTargetTooltip(fu.instrumentIndex, sendSources),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// internals...
|
||||
|
||||
func (m *Model) collectSendSources(unit sointu.Unit, paramName string) iter.Seq[sendSourceData] {
|
||||
return func(yield func(sendSourceData) bool) {
|
||||
for i, instr := range m.d.Song.Patch {
|
||||
for _, u := range instr.Units {
|
||||
if u.Type != "send" {
|
||||
continue
|
||||
}
|
||||
targetId, ok := u.Parameters["target"]
|
||||
if !ok || targetId != unit.ID {
|
||||
continue
|
||||
}
|
||||
port := u.Parameters["port"]
|
||||
unitParam, _, ok := sointu.FindParamForModulationPort(unit.Type, port)
|
||||
if !ok || unitParam.Name != paramName {
|
||||
continue
|
||||
}
|
||||
sourceData := sendSourceData{
|
||||
unitId: u.ID,
|
||||
paramName: paramName,
|
||||
instrumentIndex: i,
|
||||
instrumentName: instr.Name,
|
||||
amount: u.Parameters["amount"],
|
||||
}
|
||||
if !yield(sourceData) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) buildSendTargetTooltip(ownInstrIndex int, sendSources []sendSourceData) string {
|
||||
if len(sendSources) == 0 {
|
||||
return ""
|
||||
}
|
||||
amounts := ""
|
||||
for _, sendSource := range sendSources {
|
||||
sourceInfo := ""
|
||||
if sendSource.instrumentIndex != ownInstrIndex {
|
||||
sourceInfo = fmt.Sprintf(" from \"%s\"", sendSource.instrumentName)
|
||||
}
|
||||
if amounts == "" {
|
||||
amounts = fmt.Sprintf("x %d%s", sendSource.amount, sourceInfo)
|
||||
} else {
|
||||
amounts = fmt.Sprintf("%s, x %d%s", amounts, sendSource.amount, sourceInfo)
|
||||
}
|
||||
}
|
||||
count := "1 send"
|
||||
if len(sendSources) > 1 {
|
||||
count = fmt.Sprintf("%d sends", len(sendSources))
|
||||
}
|
||||
return fmt.Sprintf("%s [%s]", count, amounts)
|
||||
}
|
||||
|
||||
func (m *Model) instrumentRangeFor(trackIndex int) (int, int, error) {
|
||||
track := m.d.Song.Score.Tracks[trackIndex]
|
||||
if track.NumVoices <= 0 {
|
||||
@ -296,86 +188,158 @@ func (m *Model) instrumentRangeFor(trackIndex int) (int, int, error) {
|
||||
return firstIndex, lastIndex, nil
|
||||
}
|
||||
|
||||
func (m *Model) buildTrackTitle(track int) (title string) {
|
||||
title = "?"
|
||||
func (m *Model) buildTrackTitle(track int) string {
|
||||
if track < 0 || track >= len(m.d.Song.Score.Tracks) {
|
||||
return
|
||||
return "?"
|
||||
}
|
||||
firstIndex, lastIndex, err := m.instrumentRangeFor(track)
|
||||
if err != nil {
|
||||
return
|
||||
return "?"
|
||||
}
|
||||
switch diff := lastIndex - firstIndex; diff {
|
||||
case 0:
|
||||
title = m.d.Song.Patch[firstIndex].Name
|
||||
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:
|
||||
n1 := m.d.Song.Patch[firstIndex].Name
|
||||
n2 := m.d.Song.Patch[firstIndex+1].Name
|
||||
if len(n1) > 0 {
|
||||
n1 = string(n1[0])
|
||||
} else {
|
||||
n1 = "?"
|
||||
}
|
||||
if len(n2) > 0 {
|
||||
n2 = string(n2[0])
|
||||
} else {
|
||||
n2 = "?"
|
||||
}
|
||||
if diff > 1 {
|
||||
title = n1 + "/" + n2 + "..."
|
||||
} else {
|
||||
title = n1 + "/" + n2
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Model) instrumentForTrack(trackIndex int) (int, bool) {
|
||||
voiceIndex := m.d.Song.Score.FirstVoiceForTrack(trackIndex)
|
||||
instrument, err := m.d.Song.Patch.InstrumentForVoice(voiceIndex)
|
||||
return instrument, err == nil
|
||||
}
|
||||
|
||||
func (m *Model) tracksWithSameInstrument(trackIndex int) iter.Seq[int] {
|
||||
return func(yield func(int) bool) {
|
||||
|
||||
currentInstrument, currentExists := m.instrumentForTrack(trackIndex)
|
||||
if !currentExists {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < len(m.d.Song.Score.Tracks); i++ {
|
||||
instrument, exists := m.instrumentForTrack(i)
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
if instrument != currentInstrument {
|
||||
continue
|
||||
}
|
||||
if !yield(i) {
|
||||
return
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s/%s/...",
|
||||
nilIsQuestionMark(m.d.Song.Patch[firstIndex].Name),
|
||||
nilIsQuestionMark(m.d.Song.Patch[firstIndex+1].Name))
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) calcPatternUseCounts(track sointu.Track) []int {
|
||||
result := make([]int, len(m.d.Song.Score.Tracks))
|
||||
for j := range result {
|
||||
result[j] = 0
|
||||
func nilIsQuestionMark(s string) string {
|
||||
if len(s) == 0 {
|
||||
return "?"
|
||||
}
|
||||
for j := 0; j < m.d.Song.Score.Length; j++ {
|
||||
if j >= len(track.Order) {
|
||||
break
|
||||
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]++
|
||||
}
|
||||
p := track.Order[j]
|
||||
for len(result) <= p {
|
||||
result = append(result, 0)
|
||||
}
|
||||
if p < 0 {
|
||||
continue
|
||||
}
|
||||
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.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].railsWidth = 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 leave %d signals on stack, but no more signals available", scratch[0].instr, patch[scratch[0].instr].Name, scratch[0].unit, patch[scratch[0].instr].Units[scratch[0].unit].Type, len(scratch)),
|
||||
}
|
||||
}
|
||||
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.Type != "send" {
|
||||
continue
|
||||
}
|
||||
tI, tU, err := m.d.Song.Patch.FindUnit(unit.Parameters["target"])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
_, 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,
|
||||
Hint: "TBW",
|
||||
})
|
||||
} else {
|
||||
// remote send
|
||||
m.derived.patch[i].wires = append(m.derived.patch[i].wires, Wire{
|
||||
From: u,
|
||||
FromSet: true,
|
||||
Hint: "TBW",
|
||||
})
|
||||
m.derived.patch[tI].wires = append(m.derived.patch[tI].wires, Wire{
|
||||
To: Point{X: tX, Y: tU},
|
||||
ToSet: true,
|
||||
Hint: "TBW",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user