mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-27 19:00:25 -04:00
fix: changes after review (see PR #176)
This commit is contained in:
parent
6f1cb5e7ea
commit
7a76680506
18
patch.go
18
patch.go
@ -465,21 +465,19 @@ func (p Patch) FindUnit(id int) (instrIndex int, unitIndex int, err error) {
|
||||
return 0, 0, fmt.Errorf("could not find a unit with id %v", id)
|
||||
}
|
||||
|
||||
func FindParamForModulationPort(unitName string, index int) *UnitParameter {
|
||||
// qm210: couldn't see a function yet that matches the parameter index to the modulateable param.
|
||||
// Not sure whether *UnitParameters is good here, would this make them mutable?
|
||||
func FindParamForModulationPort(unitName string, index int) (UnitParameter, bool) {
|
||||
unitType, ok := UnitTypes[unitName]
|
||||
if !ok {
|
||||
return nil
|
||||
return UnitParameter{}, false
|
||||
}
|
||||
for _, param := range unitType {
|
||||
if !param.CanModulate {
|
||||
continue
|
||||
}
|
||||
if index == 0 {
|
||||
return ¶m
|
||||
}
|
||||
if param.CanModulate {
|
||||
index--
|
||||
return param, true
|
||||
}
|
||||
index--
|
||||
}
|
||||
// index outside range
|
||||
return nil
|
||||
return UnitParameter{}, false
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package tracker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/vsariola/sointu"
|
||||
"iter"
|
||||
"slices"
|
||||
@ -13,10 +14,34 @@ import (
|
||||
*/
|
||||
|
||||
type (
|
||||
derivedModelData struct {
|
||||
// map Unit by ID, other entities by their respective index
|
||||
forUnit map[int]derivedForUnit
|
||||
forTrack []derivedForTrack
|
||||
forPattern []derivedForPattern
|
||||
}
|
||||
|
||||
derivedForUnit struct {
|
||||
unit *sointu.Unit
|
||||
instrument *sointu.Instrument
|
||||
sends []*sointu.Unit
|
||||
unit sointu.Unit
|
||||
instrument sointu.Instrument
|
||||
instrumentIndex int
|
||||
unitIndex int
|
||||
|
||||
// map param by Name
|
||||
forParameter map[string]derivedForParameter
|
||||
}
|
||||
|
||||
derivedForParameter struct {
|
||||
sendTooltip string
|
||||
sendSources []sendSourceData
|
||||
}
|
||||
|
||||
sendSourceData struct {
|
||||
unitId int
|
||||
paramName string
|
||||
amount int
|
||||
instrumentIndex int
|
||||
instrumentName string
|
||||
}
|
||||
|
||||
derivedForTrack struct {
|
||||
@ -25,68 +50,75 @@ type (
|
||||
title string
|
||||
}
|
||||
|
||||
derivedModelData struct {
|
||||
// map unit by ID
|
||||
forUnit map[int]derivedForUnit
|
||||
// map track by index
|
||||
forTrack map[int]derivedForTrack
|
||||
derivedForPattern struct {
|
||||
useCount []int
|
||||
}
|
||||
)
|
||||
|
||||
// public access functions
|
||||
|
||||
func (m *Model) forUnitById(id int) *derivedForUnit {
|
||||
func (m *Model) InstrumentForUnit(id int) (sointu.Instrument, int, bool) {
|
||||
forUnit, ok := m.derived.forUnit[id]
|
||||
if !ok {
|
||||
return nil
|
||||
return sointu.Instrument{}, -1, false
|
||||
}
|
||||
return &forUnit
|
||||
return forUnit.instrument, forUnit.instrumentIndex, true
|
||||
}
|
||||
|
||||
func (m *Model) InstrumentForUnit(id int) *sointu.Instrument {
|
||||
fu := m.forUnitById(id)
|
||||
if fu == nil {
|
||||
return nil
|
||||
}
|
||||
return fu.instrument
|
||||
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 (m *Model) UnitById(id int) *sointu.Unit {
|
||||
fu := m.forUnitById(id)
|
||||
if fu == nil {
|
||||
return nil
|
||||
}
|
||||
return fu.unit
|
||||
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) SendTargetsForUnit(id int) []*sointu.Unit {
|
||||
fu := m.forUnitById(id)
|
||||
if fu == nil {
|
||||
return nil
|
||||
func (m *Model) ParameterInfo(unitId int, paramName string) (isSendTarget bool, tooltip string, exists bool) {
|
||||
du, ok1 := m.derived.forUnit[unitId]
|
||||
if !ok1 {
|
||||
return false, "", false
|
||||
}
|
||||
return fu.sends
|
||||
}
|
||||
|
||||
func (m *Model) forTrackByIndex(index int) *derivedForTrack {
|
||||
forTrack, ok := m.derived.forTrack[index]
|
||||
if !ok {
|
||||
return nil
|
||||
dp, ok2 := du.forParameter[paramName]
|
||||
if !ok2 {
|
||||
return false, "", false
|
||||
}
|
||||
return &forTrack
|
||||
return len(dp.sendSources) > 0, dp.sendTooltip, true
|
||||
}
|
||||
|
||||
func (m *Model) TrackTitle(index int) string {
|
||||
ft := m.forTrackByIndex(index)
|
||||
if ft == nil {
|
||||
if index < 0 || index > len(m.derived.forTrack) {
|
||||
return ""
|
||||
}
|
||||
return ft.title
|
||||
return m.derived.forTrack[index].title
|
||||
}
|
||||
|
||||
func (m *Model) PatternUseCount(index int) []int {
|
||||
if index < 0 || index > len(m.derived.forPattern) {
|
||||
return nil
|
||||
}
|
||||
return m.derived.forPattern[index].useCount
|
||||
}
|
||||
|
||||
func (m *Model) PatternUnique(t, p int) bool {
|
||||
if t < 0 || t > len(m.derived.forPattern) {
|
||||
return false
|
||||
}
|
||||
forPattern := m.derived.forPattern[t]
|
||||
if p < 0 || p >= len(forPattern.useCount) {
|
||||
return false
|
||||
}
|
||||
return forPattern.useCount[p] == 1
|
||||
}
|
||||
|
||||
// public getters with further model information
|
||||
|
||||
func (m *Model) TracksWithSameInstrumentAsCurrent() []int {
|
||||
currentTrack := m.d.Cursor.Track
|
||||
if currentTrack > len(m.derived.forTrack) {
|
||||
return nil
|
||||
}
|
||||
return m.derived.forTrack[currentTrack].tracksWithSameInstrument
|
||||
}
|
||||
|
||||
@ -105,41 +137,68 @@ func (m *Model) CountNextTracksForCurrentInstrument() int {
|
||||
|
||||
func (m *Model) initDerivedData() {
|
||||
m.derived = derivedModelData{
|
||||
forUnit: make(map[int]derivedForUnit),
|
||||
forTrack: make(map[int]derivedForTrack),
|
||||
forUnit: make(map[int]derivedForUnit),
|
||||
forTrack: make([]derivedForTrack, 0),
|
||||
forPattern: make([]derivedForPattern, 0),
|
||||
}
|
||||
m.updateDerivedScoreData()
|
||||
m.updateDerivedPatchData()
|
||||
}
|
||||
|
||||
func (m *Model) updateDerivedScoreData() {
|
||||
for index, _ := range m.d.Song.Score.Tracks {
|
||||
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[index] = derivedForTrack{
|
||||
instrumentRange: []int{firstInstr, lastInstr},
|
||||
tracksWithSameInstrument: slices.Collect(m.tracksWithSameInstrument(index)),
|
||||
title: m.buildTrackTitle(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() {
|
||||
for _, instr := range m.d.Song.Patch {
|
||||
for _, unit := range instr.Units {
|
||||
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,
|
||||
instrument: &instr,
|
||||
sends: slices.Collect(m.collectSendsTo(unit)),
|
||||
unit: unit,
|
||||
unitIndex: u,
|
||||
instrument: instr,
|
||||
instrumentIndex: i,
|
||||
forParameter: make(map[string]derivedForParameter),
|
||||
}
|
||||
m.updateDerivedParameterData(unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) collectSendsTo(unit sointu.Unit) iter.Seq[*sointu.Unit] {
|
||||
return func(yield func(*sointu.Unit) bool) {
|
||||
for _, instr := range m.d.Song.Patch {
|
||||
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
|
||||
@ -148,7 +207,19 @@ func (m *Model) collectSendsTo(unit sointu.Unit) iter.Seq[*sointu.Unit] {
|
||||
if !ok || targetId != unit.ID {
|
||||
continue
|
||||
}
|
||||
if !yield(&u) {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -156,8 +227,34 @@ func (m *Model) collectSendsTo(unit sointu.Unit) iter.Seq[*sointu.Unit] {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
@ -233,3 +330,24 @@ func (m *Model) tracksWithSameInstrument(trackIndex int) iter.Seq[int] {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) calcPatternUseCounts(track sointu.Track) []int {
|
||||
result := make([]int, len(m.d.Song.Score.Tracks))
|
||||
for j, _ := range result {
|
||||
result[j] = 0
|
||||
}
|
||||
for j := 0; j < m.d.Song.Score.Length; j++ {
|
||||
if j >= len(track.Order) {
|
||||
break
|
||||
}
|
||||
p := track.Order[j]
|
||||
for len(result) <= p {
|
||||
result = append(result, 0)
|
||||
}
|
||||
if p < 0 {
|
||||
continue
|
||||
}
|
||||
result[p]++
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
@ -293,7 +293,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
|
||||
}
|
||||
// draw the corresponding "fake cursors" for instrument-track-groups (for polyphony)
|
||||
if hasTrackMidiIn {
|
||||
for trackIndex := range t.Model.TracksWithSameInstrumentAsCurrent() {
|
||||
for _, trackIndex := range t.Model.TracksWithSameInstrumentAsCurrent() {
|
||||
if x == trackIndex && y == cursor.Y {
|
||||
te.paintColumnCell(gtx, x, t, cursorNeighborForTrackMidiInColor)
|
||||
}
|
||||
@ -310,7 +310,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
|
||||
paint.ColorOp{Color: trackerPatMarker}.Add(gtx.Ops)
|
||||
widget.Label{}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, patternIndexToString(s), op.CallOp{})
|
||||
}
|
||||
if row == 1 && t.Model.Notes().Unique(x, s) { // draw a * if the pattern is unique
|
||||
if row == 1 && t.Model.PatternUnique(x, s) { // draw a * if the pattern is unique
|
||||
paint.ColorOp{Color: mediumEmphasisTextColor}.Add(gtx.Ops)
|
||||
widget.Label{}.Layout(gtx, t.Theme.Shaper, trackerFont, trackerFontSize, "*", op.CallOp{})
|
||||
}
|
||||
|
@ -74,3 +74,4 @@ var warningColor = color.NRGBA{R: 251, G: 192, B: 45, A: 255}
|
||||
var dialogBgColor = color.NRGBA{R: 0, G: 0, B: 0, A: 224}
|
||||
|
||||
var paramIsSendTargetColor = color.NRGBA{R: 120, G: 120, B: 210, A: 255}
|
||||
var paramValueInvalidColor = color.NRGBA{R: 120, G: 120, B: 120, A: 190}
|
||||
|
@ -14,16 +14,14 @@ import (
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
"gioui.org/x/component"
|
||||
sointu "github.com/vsariola/sointu"
|
||||
"github.com/vsariola/sointu"
|
||||
"github.com/vsariola/sointu/tracker"
|
||||
"golang.org/x/exp/shiny/materialdesign/icons"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
"image"
|
||||
"io"
|
||||
"iter"
|
||||
"math"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type UnitEditor struct {
|
||||
@ -252,7 +250,6 @@ type ParameterStyle struct {
|
||||
Theme *material.Theme
|
||||
SendTargetTheme *material.Theme
|
||||
Focus bool
|
||||
sends []sointu.Unit
|
||||
}
|
||||
|
||||
func (t *Tracker) ParamStyle(th *material.Theme, paramWidget *ParameterWidget) ParameterStyle {
|
||||
@ -271,7 +268,7 @@ func (t *Tracker) ParamStyle(th *material.Theme, paramWidget *ParameterWidget) P
|
||||
}
|
||||
|
||||
func (p ParameterStyle) Layout(gtx C) D {
|
||||
sends := slices.Collect(p.findSends())
|
||||
isSendTarget, info := p.tryDerivedParameterInfo()
|
||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||
layout.Rigid(func(gtx C) D {
|
||||
gtx.Constraints.Min.X = gtx.Dp(unit.Dp(110))
|
||||
@ -302,7 +299,7 @@ func (p ParameterStyle) Layout(gtx C) D {
|
||||
}
|
||||
sliderStyle := material.Slider(p.Theme, &p.w.floatWidget)
|
||||
sliderStyle.Color = p.Theme.Fg
|
||||
if len(sends) > 0 {
|
||||
if isSendTarget {
|
||||
sliderStyle.Color = paramIsSendTargetColor
|
||||
}
|
||||
r := image.Rectangle{Max: gtx.Constraints.Min}
|
||||
@ -348,16 +345,14 @@ func (p ParameterStyle) Layout(gtx C) D {
|
||||
var unitItems []MenuItem
|
||||
instrName := "<instr>"
|
||||
unitName := "<unit>"
|
||||
targetI, targetU, err := p.tracker.FindUnit(p.w.Parameter.Value())
|
||||
if err == nil {
|
||||
targetInstrument := p.tracker.Instrument(targetI)
|
||||
instrName = targetInstrument.Name
|
||||
units := targetInstrument.Units
|
||||
unitName = unitNameFor(targetU, units[targetU])
|
||||
targetInstrName, units, targetUnitIndex, ok := p.tracker.UnitInfo(p.w.Parameter.Value())
|
||||
if ok {
|
||||
instrName = targetInstrName
|
||||
unitName = buildUnitLabel(targetUnitIndex, units[targetUnitIndex])
|
||||
unitItems = make([]MenuItem, len(units))
|
||||
for j, unit := range units {
|
||||
id := unit.ID
|
||||
unitItems[j].Text = unitNameFor(j, unit)
|
||||
unitItems[j].Text = buildUnitLabel(j, unit)
|
||||
unitItems[j].IconBytes = icons.NavigationChevronRight
|
||||
unitItems[j].Doer = tracker.Allow(func() {
|
||||
tracker.Int{IntData: p.w.Parameter}.Set(id)
|
||||
@ -378,8 +373,12 @@ func (p ParameterStyle) Layout(gtx C) D {
|
||||
}),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
if p.w.Parameter.Type() != tracker.IDParameter {
|
||||
label := Label(p.w.Parameter.Hint(), white, p.tracker.Theme.Shaper)
|
||||
info := p.buildSendTargetTooltip(sends)
|
||||
color := white
|
||||
hint := p.w.Parameter.Hint()
|
||||
if !hint.Valid {
|
||||
color = paramValueInvalidColor
|
||||
}
|
||||
label := Label(hint.Label, color, p.tracker.Theme.Shaper)
|
||||
if info == "" {
|
||||
return label(gtx)
|
||||
}
|
||||
@ -391,7 +390,7 @@ func (p ParameterStyle) Layout(gtx C) D {
|
||||
)
|
||||
}
|
||||
|
||||
func unitNameFor(index int, u sointu.Unit) string {
|
||||
func buildUnitLabel(index int, u sointu.Unit) string {
|
||||
text := u.Type
|
||||
if u.Comment != "" {
|
||||
text = fmt.Sprintf("%s \"%s\"", text, u.Comment)
|
||||
@ -399,47 +398,11 @@ func unitNameFor(index int, u sointu.Unit) string {
|
||||
return fmt.Sprintf("%d: %s", index, text)
|
||||
}
|
||||
|
||||
func (p ParameterStyle) findSends() iter.Seq[sointu.Unit] {
|
||||
return func(yield func(sointu.Unit) bool) {
|
||||
param, ok := (p.w.Parameter).(tracker.NamedParameter)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for _, send := range p.sends {
|
||||
port := send.Parameters["port"]
|
||||
unitParam := sointu.FindParamForModulationPort(param.Unit().Type, port)
|
||||
if unitParam.Name != param.Name() {
|
||||
continue
|
||||
}
|
||||
if !yield(send) {
|
||||
return
|
||||
}
|
||||
}
|
||||
func (p ParameterStyle) tryDerivedParameterInfo() (isSendTarget bool, sendInfo string) {
|
||||
param, ok := (p.w.Parameter).(tracker.NamedParameter)
|
||||
if !ok {
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
|
||||
func (p ParameterStyle) buildSendTargetTooltip(sends []sointu.Unit) string {
|
||||
if len(sends) == 0 {
|
||||
return ""
|
||||
}
|
||||
targetParam := (p.w.Parameter).(tracker.NamedParameter)
|
||||
targetInstr := p.tracker.Model.InstrumentForUnit(targetParam.Unit().ID)
|
||||
amounts := ""
|
||||
for i := 0; i < len(sends); i++ {
|
||||
sourceInstr := p.tracker.Model.InstrumentForUnit(sends[0].ID)
|
||||
sourceInfo := ""
|
||||
if sourceInstr != targetInstr {
|
||||
sourceInfo = fmt.Sprintf(" from \"%s\"", sourceInstr.Name)
|
||||
}
|
||||
if amounts == "" {
|
||||
amounts = fmt.Sprintf("x %d%s", sends[i].Parameters["amount"], sourceInfo)
|
||||
} else {
|
||||
amounts = fmt.Sprintf("%s, x %d%s", amounts, sends[i].Parameters["amount"], sourceInfo)
|
||||
}
|
||||
}
|
||||
count := "1 send"
|
||||
if len(sends) > 1 {
|
||||
count = fmt.Sprintf("%d sends")
|
||||
}
|
||||
return fmt.Sprintf("%s [%s]", count, amounts)
|
||||
isSendTarget, sendInfo, _ = p.tracker.ParameterInfo(param.Unit().ID, param.Name())
|
||||
return isSendTarget, sendInfo
|
||||
}
|
||||
|
@ -68,8 +68,6 @@ type (
|
||||
// reordering or deleting instrument can delete track)
|
||||
linkInstrTrack bool
|
||||
|
||||
cachePatternUseCount [][]int
|
||||
|
||||
voiceLevels [vm.MAX_VOICES]float32
|
||||
|
||||
signalAnalyzer *ScopeModel
|
||||
@ -234,7 +232,6 @@ func (m *Model) change(kind string, t ChangeType, severity ChangeSeverity) func(
|
||||
m.d.ChangedSinceSave = true
|
||||
m.d.ChangedSinceRecovery = true
|
||||
if m.changeType&ScoreChange != 0 {
|
||||
m.updatePatternUseCount()
|
||||
m.d.Cursor.SongPos = m.d.Song.Score.Clamp(m.d.Cursor.SongPos)
|
||||
m.d.Cursor2.SongPos = m.d.Song.Score.Clamp(m.d.Cursor2.SongPos)
|
||||
m.updateDerivedScoreData()
|
||||
@ -343,7 +340,7 @@ func (m *Model) UnmarshalRecovery(bytes []byte) {
|
||||
}
|
||||
m.d.ChangedSinceRecovery = false
|
||||
trySend(m.broker.ToPlayer, any(m.d.Song.Copy()))
|
||||
m.updatePatternUseCount()
|
||||
m.initDerivedData()
|
||||
}
|
||||
|
||||
func (m *Model) ProcessMsg(msg MsgToModel) {
|
||||
@ -409,20 +406,6 @@ func (n NoteID) NoteOff() {
|
||||
trySend(n.model.broker.ToPlayer, any(NoteOffMsg{n}))
|
||||
}
|
||||
|
||||
func (m *Model) FindUnit(id int) (instrIndex, unitIndex int, err error) {
|
||||
// TODO: this only used for choosing send target; find a better way for this
|
||||
return m.d.Song.Patch.FindUnit(id)
|
||||
}
|
||||
|
||||
func (m *Model) Instrument(index int) sointu.Instrument {
|
||||
// TODO: this only used for choosing send target; find a better way for this
|
||||
// we make a copy just so that the gui can't accidentally modify the song
|
||||
if index < 0 || index >= len(m.d.Song.Patch) {
|
||||
return sointu.Instrument{}
|
||||
}
|
||||
return m.d.Song.Patch[index].Copy()
|
||||
}
|
||||
|
||||
func (d *modelData) Copy() modelData {
|
||||
ret := *d
|
||||
ret.Song = d.Song.Copy()
|
||||
@ -568,30 +551,6 @@ func (m *Model) fixUnitParams() {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) updatePatternUseCount() {
|
||||
for i, track := range m.d.Song.Score.Tracks {
|
||||
for len(m.cachePatternUseCount) <= i {
|
||||
m.cachePatternUseCount = append(m.cachePatternUseCount, nil)
|
||||
}
|
||||
for j := range m.cachePatternUseCount[i] {
|
||||
m.cachePatternUseCount[i][j] = 0
|
||||
}
|
||||
for j := 0; j < m.d.Song.Score.Length; j++ {
|
||||
if j >= len(track.Order) {
|
||||
break
|
||||
}
|
||||
p := track.Order[j]
|
||||
for len(m.cachePatternUseCount[i]) <= p {
|
||||
m.cachePatternUseCount[i] = append(m.cachePatternUseCount[i], 0)
|
||||
}
|
||||
if p < 0 {
|
||||
continue
|
||||
}
|
||||
m.cachePatternUseCount[i][p]++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func clamp(a, min, max int) int {
|
||||
if a > max {
|
||||
return max
|
||||
|
@ -15,7 +15,7 @@ type (
|
||||
IntData
|
||||
Type() ParameterType
|
||||
Name() string
|
||||
Hint() string
|
||||
Hint() ParameterHint
|
||||
LargeStep() int
|
||||
Reset()
|
||||
}
|
||||
@ -44,6 +44,11 @@ type (
|
||||
ParamYieldFunc func(param Parameter) bool
|
||||
|
||||
ParameterType int
|
||||
|
||||
ParameterHint struct {
|
||||
Label string
|
||||
Valid bool
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
@ -170,31 +175,34 @@ func (p NamedParameter) Type() ParameterType {
|
||||
return IntegerParameter
|
||||
}
|
||||
|
||||
func (p NamedParameter) Hint() string {
|
||||
func (p NamedParameter) Hint() ParameterHint {
|
||||
val := p.Value()
|
||||
label := strconv.Itoa(val)
|
||||
if p.up.DisplayFunc != nil {
|
||||
valueInUnits, units := p.up.DisplayFunc(val)
|
||||
return fmt.Sprintf("%d / %s %s", val, valueInUnits, units)
|
||||
label = fmt.Sprintf("%d / %s %s", val, valueInUnits, units)
|
||||
}
|
||||
if p.unit.Type == "send" && p.up.Name == "voice" && val == 0 {
|
||||
targetIndex, _, err := p.m.FindUnit(p.unit.Parameters["target"])
|
||||
if err == nil && targetIndex != p.m.d.InstrIndex {
|
||||
return "all"
|
||||
if p.unit.Type == "send" {
|
||||
instrIndex, targetType, ok := p.m.UnitHintInfo(p.unit.Parameters["target"])
|
||||
if p.up.Name == "voice" && val == 0 {
|
||||
if ok && instrIndex != p.m.d.InstrIndex {
|
||||
label = "all"
|
||||
} else {
|
||||
label = "self"
|
||||
}
|
||||
}
|
||||
if p.up.Name == "port" {
|
||||
if !ok {
|
||||
return ParameterHint{label, false}
|
||||
}
|
||||
portList := sointu.Ports[targetType]
|
||||
if val < 0 || val >= len(portList) {
|
||||
return ParameterHint{label, false}
|
||||
}
|
||||
label = fmt.Sprintf(portList[val])
|
||||
}
|
||||
return "self"
|
||||
}
|
||||
if p.unit.Type == "send" && p.up.Name == "port" {
|
||||
instrIndex, unitIndex, err := p.m.FindUnit(p.unit.Parameters["target"])
|
||||
if err != nil {
|
||||
return strconv.Itoa(val)
|
||||
}
|
||||
portList := sointu.Ports[p.m.d.Song.Patch[instrIndex].Units[unitIndex].Type]
|
||||
if val < 0 || val >= len(portList) {
|
||||
return strconv.Itoa(val)
|
||||
}
|
||||
return fmt.Sprintf(portList[val])
|
||||
}
|
||||
return strconv.Itoa(val)
|
||||
return ParameterHint{label, true}
|
||||
}
|
||||
|
||||
func (p NamedParameter) LargeStep() int {
|
||||
@ -235,11 +243,12 @@ func (p GmDlsEntryParameter) setValue(v int) {
|
||||
p.unit.Parameters["transpose"] = 64 + e.SuggestedTranspose
|
||||
}
|
||||
|
||||
func (p GmDlsEntryParameter) Hint() string {
|
||||
func (p GmDlsEntryParameter) Hint() ParameterHint {
|
||||
label := "0 / custom"
|
||||
if v := p.Value(); v > 0 {
|
||||
return fmt.Sprintf("%v / %v", v, GmDlsEntries[v-1].Name)
|
||||
label = fmt.Sprintf("%v / %v", v, GmDlsEntries[v-1].Name)
|
||||
}
|
||||
return "0 / custom"
|
||||
return ParameterHint{label, true}
|
||||
}
|
||||
|
||||
// DelayTimeParameter
|
||||
@ -267,7 +276,7 @@ func (p DelayTimeParameter) Range() intRange {
|
||||
return intRange{Min: 1, Max: 65535}
|
||||
}
|
||||
|
||||
func (p DelayTimeParameter) Hint() string {
|
||||
func (p DelayTimeParameter) Hint() ParameterHint {
|
||||
val := p.Value()
|
||||
var text string
|
||||
switch p.unit.Parameters["notetracking"] {
|
||||
@ -309,7 +318,7 @@ func (p DelayTimeParameter) Hint() string {
|
||||
text += " L"
|
||||
}
|
||||
}
|
||||
return text
|
||||
return ParameterHint{text, true}
|
||||
}
|
||||
|
||||
// DelayLinesParameter
|
||||
@ -319,7 +328,10 @@ func (p DelayLinesParameter) Type() ParameterType { return IntegerParameter }
|
||||
func (p DelayLinesParameter) Range() intRange { return intRange{Min: 1, Max: 32} }
|
||||
func (p DelayLinesParameter) LargeStep() int { return 4 }
|
||||
func (p DelayLinesParameter) Reset() { return }
|
||||
func (p DelayLinesParameter) Hint() string { return strconv.Itoa(p.Value()) }
|
||||
|
||||
func (p DelayLinesParameter) Hint() ParameterHint {
|
||||
return ParameterHint{strconv.Itoa(p.Value()), true}
|
||||
}
|
||||
|
||||
func (p DelayLinesParameter) Value() int {
|
||||
val := len(p.unit.VarArgs)
|
||||
@ -366,10 +378,11 @@ func (p ReverbParameter) setValue(v int) {
|
||||
copy(p.unit.VarArgs, entry.varArgs)
|
||||
}
|
||||
|
||||
func (p ReverbParameter) Hint() string {
|
||||
func (p ReverbParameter) Hint() ParameterHint {
|
||||
i := p.Value()
|
||||
label := "0 / custom"
|
||||
if i > 0 {
|
||||
return fmt.Sprintf("%v / %v", i, reverbs[i-1].name)
|
||||
label = fmt.Sprintf("%v / %v", i, reverbs[i-1].name)
|
||||
}
|
||||
return "0 / custom"
|
||||
return ParameterHint{label, true}
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ import (
|
||||
type (
|
||||
// Player is the audio player for the tracker, run in a separate thread. It
|
||||
// is controlled by messages from the model and MIDI messages via the
|
||||
// context, typically from the VSTI host. The player sends messages to the
|
||||
// model via the playerMessages channel. The model sends messages to the
|
||||
// context, typically from the VSTI host. The player sendTargets messages to the
|
||||
// model via the playerMessages channel. The model sendTargets messages to the
|
||||
// player via the modelMessages channel.
|
||||
Player struct {
|
||||
synth sointu.Synth // the synth used to render audio
|
||||
@ -344,7 +344,7 @@ func (p *Player) compileOrUpdateSynth() {
|
||||
}
|
||||
}
|
||||
|
||||
// all sends from player are always non-blocking, to ensure that the player thread cannot end up in a dead-lock
|
||||
// all sendTargets from player are always non-blocking, to ensure that the player thread cannot end up in a dead-lock
|
||||
func (p *Player) send(message interface{}) {
|
||||
trySend(p.broker.ToModel, MsgToModel{HasPanicPosLevels: true, Panic: p.synth == nil, SongPosition: p.songPos, VoiceLevels: p.voiceLevels, Data: message})
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ type (
|
||||
LoopStart int // loop start offset in words
|
||||
LoopLength int // loop length in words
|
||||
SuggestedTranspose int // suggested transpose in semitones, so that all samples play at same pitch
|
||||
Name string // sample name
|
||||
Name string // sample Name
|
||||
}
|
||||
|
||||
InstrumentPresetYieldFunc func(index int, item string) (ok bool)
|
||||
|
@ -576,13 +576,6 @@ func (m *Notes) LowNibble() bool {
|
||||
return m.d.LowNibble
|
||||
}
|
||||
|
||||
func (m *Notes) Unique(t, p int) bool {
|
||||
if t < 0 || t >= len(m.cachePatternUseCount) || p < 0 || p >= len(m.cachePatternUseCount[t]) {
|
||||
return false
|
||||
}
|
||||
return m.cachePatternUseCount[t][p] == 1
|
||||
}
|
||||
|
||||
func (m *Notes) SetValue(p Point, val byte) {
|
||||
defer m.change("SetValue", MinorChange)()
|
||||
if p.Y < 0 || p.X < 0 || p.X >= len(m.d.Song.Score.Tracks) {
|
||||
|
Loading…
Reference in New Issue
Block a user