refactor(tracker): group Model methods, with each group in one source file

This commit is contained in:
5684185+vsariola@users.noreply.github.com
2026-01-25 13:08:45 +02:00
parent b93304adab
commit 86ca3fb300
44 changed files with 4813 additions and 4482 deletions

View File

@ -53,7 +53,7 @@ type (
func NewInstrumentEditor(m *tracker.Model) *InstrumentEditor {
ret := &InstrumentEditor{
dragList: NewDragList(m.Units(), layout.Vertical),
dragList: NewDragList(m.Unit().List(), layout.Vertical),
addUnitBtn: new(Clickable),
searchEditor: NewEditor(true, true, text.Start),
DeleteUnitBtn: new(Clickable),
@ -62,9 +62,9 @@ func NewInstrumentEditor(m *tracker.Model) *InstrumentEditor {
CopyUnitBtn: new(Clickable),
SelectTypeBtn: new(Clickable),
commentEditor: NewEditor(true, true, text.Start),
paramTable: NewScrollTable(m.Params().Table(), m.ParamVertList().List(), m.Units()),
searchList: NewDragList(m.SearchResults(), layout.Vertical),
searching: m.UnitSearching(),
paramTable: NewScrollTable(m.Params().Table(), m.Params().Columns(), m.Unit().List()),
searchList: NewDragList(m.Unit().SearchResults(), layout.Vertical),
searching: m.Unit().Searching(),
}
ret.caser = cases.Title(language.English)
ret.copyHint = makeHint("Copy unit", " (%s)", "Copy")
@ -95,9 +95,9 @@ func (ul *InstrumentEditor) layoutList(gtx C) D {
element := func(gtx C, i int) D {
gtx.Constraints.Max.Y = gtx.Dp(20)
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
u := t.Unit(i)
u := t.Unit().Item(i)
editorStyle := t.Theme.InstrumentEditor.UnitList.Name
signalError := t.RailError()
signalError := t.Unit().RailError()
switch {
case u.Disabled:
editorStyle = t.Theme.InstrumentEditor.UnitList.NameDisabled
@ -107,7 +107,7 @@ func (ul *InstrumentEditor) layoutList(gtx C) D {
unitName := func(gtx C) D {
if i == ul.dragList.TrackerList.Selected() {
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
return ul.searchEditor.Layout(gtx, t.Model.UnitSearch(), t.Theme, &editorStyle, "---")
return ul.searchEditor.Layout(gtx, t.Model.Unit().SearchTerm(), t.Theme, &editorStyle, "---")
} else {
text := u.Type
if text == "" {
@ -169,40 +169,40 @@ func (ul *InstrumentEditor) update(gtx C) {
case key.NameRightArrow:
t.PatchPanel.instrEditor.paramTable.RowTitleList.Focus()
case key.NameDeleteBackward:
t.SetSelectedUnitType("")
t.UnitSearching().SetValue(true)
t.Unit().SetType("")
t.Unit().Searching().SetValue(true)
ul.searchEditor.Focus()
case key.NameEnter, key.NameReturn:
t.Model.AddUnit(e.Modifiers.Contain(key.ModCtrl)).Do()
t.UnitSearching().SetValue(true)
t.Model.Unit().Add(e.Modifiers.Contain(key.ModCtrl)).Do()
t.Unit().Searching().SetValue(true)
ul.searchEditor.Focus()
}
}
}
str := t.Model.UnitSearch()
str := t.Model.Unit().SearchTerm()
for ev := ul.searchEditor.Update(gtx, str); ev != EditorEventNone; ev = ul.searchEditor.Update(gtx, str) {
if ev == EditorEventSubmit {
if str.Value() != "" {
for _, n := range sointu.UnitNames {
if strings.HasPrefix(n, str.Value()) {
t.SetSelectedUnitType(n)
t.Unit().SetType(n)
break
}
}
} else {
t.SetSelectedUnitType("")
t.Unit().SetType("")
}
}
ul.dragList.Focus()
t.UnitSearching().SetValue(false)
t.Unit().Searching().SetValue(false)
}
for ul.addUnitBtn.Clicked(gtx) {
t.AddUnit(false).Do()
t.UnitSearching().SetValue(true)
t.Unit().Add(false).Do()
t.Unit().Searching().SetValue(true)
ul.searchEditor.Focus()
}
for ul.CopyUnitBtn.Clicked(gtx) {
if contents, ok := t.Units().CopyElements(); ok {
if contents, ok := t.Unit().List().CopyElements(); ok {
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
t.Alerts().Add("Unit(s) copied to clipboard", tracker.Info)
}
@ -211,9 +211,9 @@ func (ul *InstrumentEditor) update(gtx C) {
ul.ChooseUnitType(t)
}
for ul.ClearUnitBtn.Clicked(gtx) {
t.ClearUnit().Do()
t.UnitSearch().SetValue("")
t.UnitSearching().SetValue(true)
t.Unit().Clear().Do()
t.Unit().SearchTerm().SetValue("")
t.Unit().Searching().SetValue(true)
ul.searchList.Focus()
}
for {
@ -228,7 +228,7 @@ func (ul *InstrumentEditor) update(gtx C) {
if e, ok := e.(key.Event); ok && e.State == key.Press {
switch e.Name {
case key.NameEscape:
t.UnitSearching().SetValue(false)
t.Unit().Searching().SetValue(false)
ul.paramTable.RowTitleList.Focus()
case key.NameEnter, key.NameReturn:
ul.ChooseUnitType(t)
@ -288,8 +288,8 @@ func (pe *InstrumentEditor) layoutTable(gtx C) D {
}
func (pe *InstrumentEditor) ChooseUnitType(t *Tracker) {
if ut, ok := t.SearchResult(pe.searchList.TrackerList.Selected()); ok {
t.SetSelectedUnitType(ut)
if ut, ok := t.Unit().SearchResult(pe.searchList.TrackerList.Selected()); ok {
t.Unit().SetType(ut)
pe.paramTable.RowTitleList.Focus()
}
}
@ -305,9 +305,9 @@ func (pe *InstrumentEditor) layoutRack(gtx C) D {
cellWidth := gtx.Dp(t.Theme.UnitEditor.Width)
cellHeight := gtx.Dp(t.Theme.UnitEditor.Height)
rowTitleLabelWidth := gtx.Dp(t.Theme.UnitEditor.UnitList.LabelWidth)
rowTitleSignalWidth := gtx.Dp(t.Theme.SignalRail.SignalWidth) * t.RailWidth()
rowTitleSignalWidth := gtx.Dp(t.Theme.SignalRail.SignalWidth) * t.Unit().RailWidth()
rowTitleWidth := rowTitleLabelWidth + rowTitleSignalWidth
signalError := t.RailError()
signalError := t.Unit().RailError()
columnTitleHeight := gtx.Dp(0)
for i := range pe.Parameters {
for len(pe.Parameters[i]) < width {
@ -321,7 +321,7 @@ func (pe *InstrumentEditor) layoutRack(gtx C) D {
if y < 0 || y >= len(pe.Parameters) {
return D{}
}
item := t.Unit(y)
item := t.Unit().Item(y)
sr := Rail(t.Theme, item.Signals)
label := Label(t.Theme, &t.Theme.UnitEditor.UnitList.Name, item.Type)
switch {
@ -360,20 +360,20 @@ func (pe *InstrumentEditor) layoutRack(gtx C) D {
}
param := t.Model.Params().Item(point)
paramStyle := Param(param, t.Theme, pe.Parameters[y][x], pe.paramTable.Table.Cursor() == point, t.Unit(y).Disabled)
paramStyle := Param(param, t.Theme, pe.Parameters[y][x], pe.paramTable.Table.Cursor() == point, t.Unit().Item(y).Disabled)
paramStyle.Layout(gtx)
if x == t.Model.Params().RowWidth(y) {
if y == cursor.Y {
return layout.W.Layout(gtx, func(gtx C) D {
for pe.commentEditor.Update(gtx, t.UnitComment()) != EditorEventNone {
for pe.commentEditor.Update(gtx, t.Unit().Comment()) != EditorEventNone {
t.FocusPrev(gtx, false)
}
gtx.Constraints.Max.X = 1e6
gtx.Constraints.Min.Y = 0
return pe.commentEditor.Layout(gtx, t.UnitComment(), t.Theme, &t.Theme.InstrumentEditor.UnitComment, "---")
return pe.commentEditor.Layout(gtx, t.Unit().Comment(), t.Theme, &t.Theme.InstrumentEditor.UnitComment, "---")
})
} else {
comment := t.Unit(y).Comment
comment := t.Unit().Item(y).Comment
if comment != "" {
style := t.Theme.InstrumentEditor.UnitComment.AsLabelStyle()
label := Label(t.Theme, &style, comment)
@ -408,7 +408,7 @@ func (pe *InstrumentEditor) drawSignals(gtx C, rowTitleWidth int) {
gtx.Constraints.Max = gtx.Constraints.Max.Sub(p)
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Push(gtx.Ops).Pop()
defer op.Offset(image.Pt(-colP.Offset, -rowP.Offset)).Push(gtx.Ops).Pop()
for wire := range t.Wires {
for wire := range t.Params().Wires {
clr := t.Theme.UnitEditor.WireColor
if wire.Highlight {
clr = t.Theme.UnitEditor.WireHighlight
@ -516,9 +516,9 @@ func mulVec(a, b f32.Point) f32.Point {
func (pe *InstrumentEditor) layoutFooter(gtx C) D {
t := TrackerFromContext(gtx)
deleteUnitBtn := ActionIconBtn(t.DeleteUnit(), t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, "Delete unit (Ctrl+Backspace)")
deleteUnitBtn := ActionIconBtn(t.Unit().Delete(), t.Theme, pe.DeleteUnitBtn, icons.ActionDelete, "Delete unit (Ctrl+Backspace)")
copyUnitBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, pe.CopyUnitBtn, icons.ContentContentCopy, pe.copyHint)
disableUnitBtn := ToggleIconBtn(t.UnitDisabled(), t.Theme, pe.DisableUnitBtn, icons.AVVolumeUp, icons.AVVolumeOff, pe.disableUnitHint, pe.enableUnitHint)
disableUnitBtn := ToggleIconBtn(t.Unit().Disabled(), t.Theme, pe.DisableUnitBtn, icons.AVVolumeUp, icons.AVVolumeOff, pe.disableUnitHint, pe.enableUnitHint)
clearUnitBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, pe.ClearUnitBtn, icons.ContentClear, "Clear unit")
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(deleteUnitBtn.Layout),
@ -531,7 +531,7 @@ func (pe *InstrumentEditor) layoutFooter(gtx C) D {
func (pe *InstrumentEditor) layoutUnitTypeChooser(gtx C) D {
t := TrackerFromContext(gtx)
element := func(gtx C, i int) D {
name, _ := t.SearchResult(i)
name, _ := t.Unit().SearchResult(i)
w := Label(t.Theme, &t.Theme.UnitEditor.Chooser, name)
if i == pe.searchList.TrackerList.Selected() {
return pe.SelectTypeBtn.Layout(gtx, w.Layout)

View File

@ -36,8 +36,8 @@ func NewInstrumentPresets(m *tracker.Model) *InstrumentPresets {
builtinPresetsBtn: new(Clickable),
saveUserPreset: new(Clickable),
deleteUserPreset: new(Clickable),
dirList: NewDragList(m.PresetDirList().List(), layout.Vertical),
resultList: NewDragList(m.PresetResultList().List(), layout.Vertical),
dirList: NewDragList(m.Preset().DirList(), layout.Vertical),
resultList: NewDragList(m.Preset().SearchResultList(), layout.Vertical),
}
}
@ -88,13 +88,13 @@ func (ip *InstrumentPresets) layout(gtx C) D {
ip.update(gtx)
// get tracker from values
tr := TrackerFromContext(gtx)
gmDlsBtn := ToggleBtn(tr.NoGmDls(), tr.Theme, ip.gmDlsBtn, "No gm.dls", "Exclude presets using gm.dls")
userPresetsFilterBtn := ToggleBtn(tr.UserPresetFilter(), tr.Theme, ip.userPresetsBtn, "User", "Show only user presets")
builtinPresetsFilterBtn := ToggleBtn(tr.BuiltinPresetsFilter(), tr.Theme, ip.builtinPresetsBtn, "Builtin", "Show only builtin presets")
saveUserPresetBtn := ActionIconBtn(tr.SaveAsUserPreset(), tr.Theme, ip.saveUserPreset, icons.ContentSave, "Save instrument as user preset")
deleteUserPresetBtn := ActionIconBtn(tr.TryDeleteUserPreset(), tr.Theme, ip.deleteUserPreset, icons.ActionDelete, "Delete user preset")
gmDlsBtn := ToggleBtn(tr.Preset().NoGmDls(), tr.Theme, ip.gmDlsBtn, "No gm.dls", "Exclude presets using gm.dls")
userPresetsFilterBtn := ToggleBtn(tr.Preset().UserFilter(), tr.Theme, ip.userPresetsBtn, "User", "Show only user presets")
builtinPresetsFilterBtn := ToggleBtn(tr.Preset().BuiltinFilter(), tr.Theme, ip.builtinPresetsBtn, "Builtin", "Show only builtin presets")
saveUserPresetBtn := ActionIconBtn(tr.Preset().Save(), tr.Theme, ip.saveUserPreset, icons.ContentSave, "Save instrument as user preset")
deleteUserPresetBtn := ActionIconBtn(tr.Preset().Delete(), tr.Theme, ip.deleteUserPreset, icons.ActionDelete, "Delete user preset")
dirElem := func(gtx C, i int) D {
return Label(tr.Theme, &tr.Theme.InstrumentEditor.Presets.Directory, tr.Model.PresetDirList().Value(i)).Layout(gtx)
return Label(tr.Theme, &tr.Theme.InstrumentEditor.Presets.Directory, tr.Model.Preset().Dir(i)).Layout(gtx)
}
dirs := func(gtx C) D {
gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(140), gtx.Constraints.Max.Y))
@ -108,7 +108,7 @@ func (ip *InstrumentPresets) layout(gtx C) D {
}
resultElem := func(gtx C, i int) D {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
n, d, u := tr.Model.PresetResultList().Value(i)
n, d, u := tr.Model.Preset().SearchResult(i)
if u {
ln := Label(tr.Theme, &tr.Theme.InstrumentEditor.Presets.Results.User, n)
ld := Label(tr.Theme, &tr.Theme.InstrumentEditor.Presets.Results.UserDir, d)
@ -121,7 +121,7 @@ func (ip *InstrumentPresets) layout(gtx C) D {
return Label(tr.Theme, &tr.Theme.InstrumentEditor.Presets.Results.Builtin, n).Layout(gtx)
}
floatButtons := func(gtx C) D {
if tr.Model.DeleteUserPreset().Enabled() {
if tr.Model.Preset().Delete().Enabled() {
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(deleteUserPresetBtn.Layout),
layout.Rigid(saveUserPresetBtn.Layout),
@ -189,10 +189,10 @@ func (ip *InstrumentPresets) layoutSearch(gtx C) D {
})
}
ed := func(gtx C) D {
return ip.searchEditor.Layout(gtx, tr.Model.PresetSearchString(), tr.Theme, &tr.Theme.InstrumentEditor.UnitComment, "Search presets")
return ip.searchEditor.Layout(gtx, tr.Preset().SearchTerm(), tr.Theme, &tr.Theme.InstrumentEditor.UnitComment, "Search presets")
}
clr := func(gtx C) D {
btn := ActionIconBtn(tr.ClearPresetSearch(), tr.Theme, ip.clearSearchBtn, icons.ContentClear, "Clear search")
btn := ActionIconBtn(tr.Preset().ClearSearch(), tr.Theme, ip.clearSearchBtn, icons.ContentClear, "Clear search")
return btn.Layout(gtx)
}
w := func(gtx C) D {

View File

@ -58,20 +58,20 @@ func (ip *InstrumentProperties) layout(gtx C) D {
// get tracker from values
tr := TrackerFromContext(gtx)
voiceLine := func(gtx C) D {
splitInstrumentBtn := ActionIconBtn(tr.SplitInstrument(), tr.Theme, ip.splitInstrumentBtn, icons.CommunicationCallSplit, ip.splitInstrumentHint)
splitInstrumentBtn := ActionIconBtn(tr.Instrument().Split(), tr.Theme, ip.splitInstrumentBtn, icons.CommunicationCallSplit, ip.splitInstrumentHint)
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(func(gtx C) D {
instrumentVoices := NumUpDown(tr.Model.InstrumentVoices(), tr.Theme, ip.voices, "Number of voices for this instrument")
instrumentVoices := NumUpDown(tr.Model.Instrument().Voices(), tr.Theme, ip.voices, "Number of voices for this instrument")
return instrumentVoices.Layout(gtx)
}),
layout.Rigid(splitInstrumentBtn.Layout),
)
}
thread1btn := ToggleIconBtn(tr.Thread1(), tr.Theme, ip.threadBtns[0], icons.ImageCropSquare, icons.ImageFilter1, "Do not render instrument on thread 1", "Render instrument on thread 1")
thread2btn := ToggleIconBtn(tr.Thread2(), tr.Theme, ip.threadBtns[1], icons.ImageCropSquare, icons.ImageFilter2, "Do not render instrument on thread 2", "Render instrument on thread 2")
thread3btn := ToggleIconBtn(tr.Thread3(), tr.Theme, ip.threadBtns[2], icons.ImageCropSquare, icons.ImageFilter3, "Do not render instrument on thread 3", "Render instrument on thread 3")
thread4btn := ToggleIconBtn(tr.Thread4(), tr.Theme, ip.threadBtns[3], icons.ImageCropSquare, icons.ImageFilter4, "Do not render instrument on thread 4", "Render instrument on thread 4")
thread1btn := ToggleIconBtn(tr.Instrument().Thread1(), tr.Theme, ip.threadBtns[0], icons.ImageCropSquare, icons.ImageFilter1, "Do not render instrument on thread 1", "Render instrument on thread 1")
thread2btn := ToggleIconBtn(tr.Instrument().Thread2(), tr.Theme, ip.threadBtns[1], icons.ImageCropSquare, icons.ImageFilter2, "Do not render instrument on thread 2", "Render instrument on thread 2")
thread3btn := ToggleIconBtn(tr.Instrument().Thread3(), tr.Theme, ip.threadBtns[2], icons.ImageCropSquare, icons.ImageFilter3, "Do not render instrument on thread 3", "Render instrument on thread 3")
thread4btn := ToggleIconBtn(tr.Instrument().Thread4(), tr.Theme, ip.threadBtns[3], icons.ImageCropSquare, icons.ImageFilter4, "Do not render instrument on thread 4", "Render instrument on thread 4")
threadbtnline := func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
@ -86,21 +86,21 @@ func (ip *InstrumentProperties) layout(gtx C) D {
switch index {
case 0:
return layoutInstrumentPropertyLine(gtx, "Name", func(gtx C) D {
return ip.nameEditor.Layout(gtx, tr.InstrumentName(), tr.Theme, &tr.Theme.InstrumentEditor.InstrumentComment, "Instr")
return ip.nameEditor.Layout(gtx, tr.Instrument().Name(), tr.Theme, &tr.Theme.InstrumentEditor.InstrumentComment, "Instr")
})
case 2:
return layoutInstrumentPropertyLine(gtx, "Voices", voiceLine)
case 4:
muteBtn := ToggleIconBtn(tr.Mute(), tr.Theme, ip.muteBtn, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, ip.muteHint, ip.unmuteHint)
muteBtn := ToggleIconBtn(tr.Instrument().Mute(), tr.Theme, ip.muteBtn, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, ip.muteHint, ip.unmuteHint)
return layoutInstrumentPropertyLine(gtx, "Mute", muteBtn.Layout)
case 6:
soloBtn := ToggleIconBtn(tr.Solo(), tr.Theme, ip.soloBtn, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, ip.soloHint, ip.unsoloHint)
soloBtn := ToggleIconBtn(tr.Instrument().Solo(), tr.Theme, ip.soloBtn, icons.ToggleCheckBoxOutlineBlank, icons.ToggleCheckBox, ip.soloHint, ip.unsoloHint)
return layoutInstrumentPropertyLine(gtx, "Solo", soloBtn.Layout)
case 8:
return layoutInstrumentPropertyLine(gtx, "Thread", threadbtnline)
case 10:
return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx C) D {
return ip.commentEditor.Layout(gtx, tr.InstrumentComment(), tr.Theme, &tr.Theme.InstrumentEditor.InstrumentComment, "Comment")
return ip.commentEditor.Layout(gtx, tr.Instrument().Comment(), tr.Theme, &tr.Theme.InstrumentEditor.InstrumentComment, "Comment")
})
default: // odd valued list items are dividers
px := max(gtx.Dp(unit.Dp(1)), 1)

View File

@ -102,93 +102,93 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
switch action {
// Actions
case "AddTrack":
t.AddTrack().Do()
t.Track().Add().Do()
case "DeleteTrack":
t.DeleteTrack().Do()
t.Track().Delete().Do()
case "AddInstrument":
t.AddInstrument().Do()
t.Instrument().Add().Do()
case "DeleteInstrument":
t.DeleteInstrument().Do()
t.Instrument().Delete().Do()
case "AddUnitAfter":
t.AddUnit(false).Do()
t.Unit().Add(false).Do()
case "AddUnitBefore":
t.AddUnit(true).Do()
t.Unit().Add(true).Do()
case "DeleteUnit":
t.DeleteUnit().Do()
t.Unit().Delete().Do()
case "ClearUnit":
t.ClearUnit().Do()
t.Unit().Clear().Do()
case "Undo":
t.Undo().Do()
t.History().Undo().Do()
case "Redo":
t.Redo().Do()
t.History().Redo().Do()
case "AddSemitone":
t.AddSemitone().Do()
t.Note().AddSemitone().Do()
case "SubtractSemitone":
t.SubtractSemitone().Do()
t.Note().SubtractSemitone().Do()
case "AddOctave":
t.AddOctave().Do()
t.Note().AddOctave().Do()
case "SubtractOctave":
t.SubtractOctave().Do()
t.Note().SubtractOctave().Do()
case "EditNoteOff":
t.EditNoteOff().Do()
t.Note().NoteOff().Do()
case "RemoveUnused":
t.RemoveUnused().Do()
t.Order().RemoveUnusedPatterns().Do()
case "PlayCurrentPosFollow":
t.Follow().SetValue(true)
t.PlayCurrentPos().Do()
t.Play().IsFollowing().SetValue(true)
t.Play().FromCurrentPos().Do()
case "PlayCurrentPosUnfollow":
t.Follow().SetValue(false)
t.PlayCurrentPos().Do()
t.Play().IsFollowing().SetValue(false)
t.Play().FromCurrentPos().Do()
case "PlaySongStartFollow":
t.Follow().SetValue(true)
t.PlaySongStart().Do()
t.Play().IsFollowing().SetValue(true)
t.Play().FromBeginning().Do()
case "PlaySongStartUnfollow":
t.Follow().SetValue(false)
t.PlaySongStart().Do()
t.Play().IsFollowing().SetValue(false)
t.Play().FromBeginning().Do()
case "PlaySelectedFollow":
t.Follow().SetValue(true)
t.PlaySelected().Do()
t.Play().IsFollowing().SetValue(true)
t.Play().FromSelected().Do()
case "PlaySelectedUnfollow":
t.Follow().SetValue(false)
t.PlaySelected().Do()
t.Play().IsFollowing().SetValue(false)
t.Play().FromSelected().Do()
case "PlayLoopFollow":
t.Follow().SetValue(true)
t.PlayFromLoopStart().Do()
t.Play().IsFollowing().SetValue(true)
t.Play().FromLoopBeginning().Do()
case "PlayLoopUnfollow":
t.Follow().SetValue(false)
t.PlayFromLoopStart().Do()
t.Play().IsFollowing().SetValue(false)
t.Play().FromLoopBeginning().Do()
case "StopPlaying":
t.StopPlaying().Do()
t.Play().Stop().Do()
case "AddOrderRowBefore":
t.AddOrderRow(true).Do()
t.Order().AddRow(true).Do()
case "AddOrderRowAfter":
t.AddOrderRow(false).Do()
t.Order().AddRow(false).Do()
case "DeleteOrderRowBackwards":
t.DeleteOrderRow(true).Do()
t.Order().DeleteRow(true).Do()
case "DeleteOrderRowForwards":
t.DeleteOrderRow(false).Do()
t.Order().DeleteRow(false).Do()
case "NewSong":
t.NewSong().Do()
t.Song().New().Do()
case "OpenSong":
t.OpenSong().Do()
t.Song().Open().Do()
case "Quit":
if canQuit {
t.RequestQuit().Do()
}
case "SaveSong":
t.SaveSong().Do()
t.Song().Save().Do()
case "SaveSongAs":
t.SaveSongAs().Do()
t.Song().SaveAs().Do()
case "ExportWav":
t.Export().Do()
t.Song().Export().Do()
case "ExportFloat":
t.ExportFloat().Do()
t.Song().ExportFloat().Do()
case "ExportInt16":
t.ExportInt16().Do()
t.Song().ExportInt16().Do()
case "SplitTrack":
t.SplitTrack().Do()
t.Track().Split().Do()
case "SplitInstrument":
t.SplitInstrument().Do()
t.Instrument().Split().Do()
case "ShowManual":
t.ShowManual().Do()
case "AskHelp":
@ -199,72 +199,72 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
t.ShowLicense().Do()
// Booleans
case "PanicToggle":
t.Panic().Toggle()
t.Play().Panicked().Toggle()
case "RecordingToggle":
t.IsRecording().Toggle()
t.Play().IsRecording().Toggle()
case "PlayingToggleFollow":
t.Follow().SetValue(true)
t.Playing().Toggle()
t.Play().IsFollowing().SetValue(true)
t.Play().Started().Toggle()
case "PlayingToggleUnfollow":
t.Follow().SetValue(false)
t.Playing().Toggle()
t.Play().IsFollowing().SetValue(false)
t.Play().Started().Toggle()
case "InstrEnlargedToggle":
t.InstrEnlarged().Toggle()
t.Play().TrackerHidden().Toggle()
case "LinkInstrTrackToggle":
t.LinkInstrTrack().Toggle()
t.Track().LinkInstrument().Toggle()
case "FollowToggle":
t.Follow().Toggle()
t.Play().IsFollowing().Toggle()
case "UnitDisabledToggle":
t.UnitDisabled().Toggle()
t.Unit().Disabled().Toggle()
case "LoopToggle":
t.LoopToggle().Toggle()
t.Play().IsLooping().Toggle()
case "UniquePatternsToggle":
t.UniquePatterns().Toggle()
t.Note().UniquePatterns().Toggle()
case "MuteToggle":
t.Mute().Toggle()
t.Instrument().Mute().Toggle()
case "SoloToggle":
t.Solo().Toggle()
t.Instrument().Solo().Toggle()
// Integers
case "InstrumentVoicesAdd":
t.Model.InstrumentVoices().Add(1)
t.Instrument().Voices().Add(1)
case "InstrumentVoicesSubtract":
t.Model.InstrumentVoices().Add(-1)
t.Instrument().Voices().Add(-1)
case "TrackVoicesAdd":
t.TrackVoices().Add(1)
t.Track().Voices().Add(1)
case "TrackVoicesSubtract":
t.TrackVoices().Add(-1)
t.Track().Voices().Add(-1)
case "SongLengthAdd":
t.SongLength().Add(1)
t.Song().Length().Add(1)
case "SongLengthSubtract":
t.SongLength().Add(-1)
t.Song().Length().Add(-1)
case "BPMAdd":
t.BPM().Add(1)
t.Song().BPM().Add(1)
case "BPMSubtract":
t.BPM().Add(-1)
t.Song().BPM().Add(-1)
case "RowsPerPatternAdd":
t.RowsPerPattern().Add(1)
t.Song().RowsPerPattern().Add(1)
case "RowsPerPatternSubtract":
t.RowsPerPattern().Add(-1)
t.Song().RowsPerPattern().Add(-1)
case "RowsPerBeatAdd":
t.RowsPerBeat().Add(1)
t.Song().RowsPerBeat().Add(1)
case "RowsPerBeatSubtract":
t.RowsPerBeat().Add(-1)
t.Song().RowsPerBeat().Add(-1)
case "StepAdd":
t.Step().Add(1)
t.Note().Step().Add(1)
case "StepSubtract":
t.Step().Add(-1)
t.Note().Step().Add(-1)
case "OctaveAdd":
t.Octave().Add(1)
t.Note().Octave().Add(1)
case "OctaveSubtract":
t.Octave().Add(-1)
t.Note().Octave().Add(-1)
// Other miscellaneous
case "Paste":
gtx.Execute(clipboard.ReadCmd{Tag: t})
case "OrderEditorFocus":
t.InstrEnlarged().SetValue(false)
t.Play().TrackerHidden().SetValue(false)
gtx.Execute(key.FocusCmd{Tag: t.OrderEditor.scrollTable})
case "TrackEditorFocus":
t.InstrEnlarged().SetValue(false)
t.Play().TrackerHidden().SetValue(false)
gtx.Execute(key.FocusCmd{Tag: t.TrackEditor.scrollTable})
case "InstrumentListFocus":
gtx.Execute(key.FocusCmd{Tag: t.PatchPanel.instrList.instrumentDragList})
@ -289,8 +289,8 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
if err != nil {
break
}
instr := t.Model.Instruments().Selected()
n := noteAsValue(t.Model.Octave().Value(), val-12)
instr := t.Model.Instrument().List().Selected()
n := noteAsValue(t.Model.Note().Octave().Value(), val-12)
t.KeyNoteMap.Press(e.Name, tracker.NoteEvent{Channel: instr, Note: n})
}
}

View File

@ -92,9 +92,9 @@ func NewNoteEditor(model *tracker.Model) *NoteEditor {
UniqueBtn: new(Clickable),
TrackMidiInBtn: new(Clickable),
scrollTable: NewScrollTable(
model.Notes().Table(),
model.Tracks(),
model.NoteRows(),
model.Note().Table(),
model.Track().List(),
model.Note().RowList(),
),
}
for k, a := range keyBindingMap {
@ -137,10 +137,10 @@ func (te *NoteEditor) Layout(gtx layout.Context) layout.Dimensions {
for gtx.Focused(te.scrollTable) && len(t.noteEvents) > 0 {
ev := t.noteEvents[0]
ev.IsTrack = true
ev.Channel = t.Model.Notes().Cursor().X
ev.Channel = t.Model.Note().Cursor().X
ev.Source = te
if ev.On {
t.Model.Notes().Input(ev.Note)
t.Model.Note().Input(ev.Note)
}
copy(t.noteEvents, t.noteEvents[1:])
t.noteEvents = t.noteEvents[:len(t.noteEvents)-1]
@ -163,22 +163,22 @@ func (te *NoteEditor) Layout(gtx layout.Context) layout.Dimensions {
func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D {
return Surface{Height: 4, Focus: te.scrollTable.TreeFocused(gtx)}.Layout(gtx, func(gtx C) D {
addSemitoneBtn := ActionBtn(t.AddSemitone(), t.Theme, te.AddSemitoneBtn, "+1", "Add semitone")
subtractSemitoneBtn := ActionBtn(t.SubtractSemitone(), t.Theme, te.SubtractSemitoneBtn, "-1", "Subtract semitone")
addOctaveBtn := ActionBtn(t.AddOctave(), t.Theme, te.AddOctaveBtn, "+12", "Add octave")
subtractOctaveBtn := ActionBtn(t.SubtractOctave(), t.Theme, te.SubtractOctaveBtn, "-12", "Subtract octave")
noteOffBtn := ActionBtn(t.EditNoteOff(), t.Theme, te.NoteOffBtn, "Note Off", "")
deleteTrackBtn := ActionIconBtn(t.DeleteTrack(), t.Theme, te.DeleteTrackBtn, icons.ActionDelete, te.deleteTrackHint)
splitTrackBtn := ActionIconBtn(t.SplitTrack(), t.Theme, te.SplitTrackBtn, icons.CommunicationCallSplit, te.splitTrackHint)
newTrackBtn := ActionIconBtn(t.AddTrack(), t.Theme, te.NewTrackBtn, icons.ContentAdd, te.addTrackHint)
trackVoices := NumUpDown(t.Model.TrackVoices(), t.Theme, te.TrackVoices, "Track voices")
addSemitoneBtn := ActionBtn(t.Note().AddSemitone(), t.Theme, te.AddSemitoneBtn, "+1", "Add semitone")
subtractSemitoneBtn := ActionBtn(t.Note().SubtractSemitone(), t.Theme, te.SubtractSemitoneBtn, "-1", "Subtract semitone")
addOctaveBtn := ActionBtn(t.Note().AddOctave(), t.Theme, te.AddOctaveBtn, "+12", "Add octave")
subtractOctaveBtn := ActionBtn(t.Note().SubtractOctave(), t.Theme, te.SubtractOctaveBtn, "-12", "Subtract octave")
noteOffBtn := ActionBtn(t.Note().NoteOff(), t.Theme, te.NoteOffBtn, "Note Off", "")
deleteTrackBtn := ActionIconBtn(t.Track().Delete(), t.Theme, te.DeleteTrackBtn, icons.ActionDelete, te.deleteTrackHint)
splitTrackBtn := ActionIconBtn(t.Track().Split(), t.Theme, te.SplitTrackBtn, icons.CommunicationCallSplit, te.splitTrackHint)
newTrackBtn := ActionIconBtn(t.Track().Add(), t.Theme, te.NewTrackBtn, icons.ContentAdd, te.addTrackHint)
trackVoices := NumUpDown(t.Model.Track().Voices(), t.Theme, te.TrackVoices, "Track voices")
in := layout.UniformInset(unit.Dp(1))
trackVoicesInsetted := func(gtx C) D {
return in.Layout(gtx, trackVoices.Layout)
}
effectBtn := ToggleBtn(t.Effect(), t.Theme, te.EffectBtn, "Hex", "Input notes as hex values")
uniqueBtn := ToggleIconBtn(t.UniquePatterns(), t.Theme, te.UniqueBtn, icons.ToggleStarBorder, icons.ToggleStar, te.uniqueOffTip, te.uniqueOnTip)
midiInBtn := ToggleBtn(t.TrackMidiIn(), t.Theme, te.TrackMidiInBtn, "MIDI", "Input notes from MIDI keyboard")
effectBtn := ToggleBtn(t.Track().Effect(), t.Theme, te.EffectBtn, "Hex", "Input notes as hex values")
uniqueBtn := ToggleIconBtn(t.Note().UniquePatterns(), t.Theme, te.UniqueBtn, icons.ToggleStarBorder, icons.ToggleStar, te.uniqueOffTip, te.uniqueOnTip)
midiInBtn := ToggleBtn(t.MIDI().InputtingNotes(), t.Theme, te.TrackMidiInBtn, "MIDI", "Input notes from MIDI keyboard")
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(func(gtx C) D { return layout.Dimensions{Size: image.Pt(gtx.Dp(unit.Dp(12)), 0)} }),
layout.Rigid(addSemitoneBtn.Layout),
@ -220,13 +220,13 @@ var notes = []string{
func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
defer clip.Rect{Max: gtx.Constraints.Max}.Push(gtx.Ops).Pop()
beatMarkerDensity := t.RowsPerBeat().Value()
beatMarkerDensity := t.Song().RowsPerBeat().Value()
switch beatMarkerDensity {
case 0, 1, 2:
beatMarkerDensity = 4
}
playSongRow := t.PlaySongRow()
playSongRow := t.Play().SongRow()
pxWidth := gtx.Dp(trackColWidth)
pxHeight := gtx.Dp(trackRowHeight)
pxPatMarkWidth := gtx.Dp(trackPatMarkWidth)
@ -235,7 +235,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
colTitle := func(gtx C, i int) D {
h := gtx.Dp(trackColTitleHeight)
gtx.Constraints = layout.Exact(image.Pt(pxWidth, h))
Label(t.Theme, &t.Theme.NoteEditor.TrackTitle, t.Model.TrackTitle(i)).Layout(gtx)
Label(t.Theme, &t.Theme.NoteEditor.TrackTitle, t.Model.Track().Item(i).Title).Layout(gtx)
return D{Size: image.Pt(pxWidth, h)}
}
@ -245,7 +245,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
} else if mod(j, beatMarkerDensity) == 0 {
paint.FillShape(gtx.Ops, t.Theme.NoteEditor.OneBeat, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, pxHeight)}.Op())
}
if t.Model.Playing().Value() && j == playSongRow {
if t.Model.Play().Started().Value() && j == playSongRow {
paint.FillShape(gtx.Ops, t.Theme.NoteEditor.Play, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, pxHeight)}.Op())
}
return D{}
@ -256,14 +256,14 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
patternRowOp := colorOp(gtx, t.Theme.NoteEditor.PatternRow.Color)
rowTitle := func(gtx C, j int) D {
rpp := max(t.RowsPerPattern().Value(), 1)
rpp := max(t.Song().RowsPerPattern().Value(), 1)
pat := j / rpp
row := j % rpp
w := pxPatMarkWidth + pxRowMarkWidth
defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop()
if row == 0 {
op := orderRowOp
if l := t.Loop(); pat >= l.Start && pat < l.Start+l.Length {
if l := t.Play().Loop(); pat >= l.Start && pat < l.Start+l.Length {
op = loopColorOp
}
widget.Label{}.Layout(gtx, t.Theme.Material.Shaper, t.Theme.NoteEditor.OrderRow.Font, t.Theme.NoteEditor.OrderRow.TextSize, hexStr[pat&255], op)
@ -276,7 +276,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
cursor := te.scrollTable.Table.Cursor()
drawSelection := cursor != te.scrollTable.Table.Cursor2()
selection := te.scrollTable.Table.Range()
hasTrackMidiIn := t.Model.TrackMidiIn().Value()
hasTrackMidiIn := t.MIDI().InputtingNotes().Value()
patternNoOp := colorOp(gtx, t.Theme.NoteEditor.PatternNo.Color)
uniqueOp := colorOp(gtx, t.Theme.NoteEditor.Unique.Color)
@ -305,7 +305,7 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
}
// draw the pattern marker
rpp := max(t.RowsPerPattern().Value(), 1)
rpp := max(t.Song().RowsPerPattern().Value(), 1)
pat := y / rpp
row := y % rpp
defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop()
@ -313,13 +313,13 @@ func (te *NoteEditor) layoutTracks(gtx C, t *Tracker) D {
if row == 0 { // draw the pattern marker
widget.Label{}.Layout(gtx, t.Theme.Material.Shaper, t.Theme.NoteEditor.PatternNo.Font, t.Theme.NoteEditor.PatternNo.TextSize, patternIndexToString(s), patternNoOp)
}
if row == 1 && t.Model.PatternUnique(x, s) { // draw a * if the pattern is unique
if row == 1 && t.Order().PatternUnique(x, s) { // draw a * if the pattern is unique
widget.Label{}.Layout(gtx, t.Theme.Material.Shaper, t.Theme.NoteEditor.Unique.Font, t.Theme.NoteEditor.Unique.TextSize, "*", uniqueOp)
}
op := noteOp
val := noteName[byte(t.Model.Notes().Value(tracker.Point{X: x, Y: y}))]
if t.Model.Notes().Effect(x) {
val = noteHex[byte(t.Model.Notes().Value(tracker.Point{X: x, Y: y}))]
val := noteName[byte(t.Model.Note().At(tracker.Point{X: x, Y: y}))]
if t.Model.Track().Item(x).Effect {
val = noteHex[byte(t.Model.Note().At(tracker.Point{X: x, Y: y}))]
}
widget.Label{Alignment: text.Middle}.Layout(gtx, t.Theme.Material.Shaper, t.Theme.NoteEditor.Note.Font, t.Theme.NoteEditor.Note.TextSize, val, op)
return D{Size: image.Pt(pxWidth, pxHeight)}
@ -347,9 +347,9 @@ func colorOp(gtx C, c color.NRGBA) op.CallOp {
func (te *NoteEditor) paintColumnCell(gtx C, x int, t *Tracker, c color.NRGBA) {
cw := gtx.Constraints.Min.X
cx := 0
if t.Model.Notes().Effect(x) {
if t.Model.Track().Item(x).Effect {
cw /= 2
if t.Model.Notes().LowNibble() {
if t.Model.Note().LowNibble() {
cx += cw
}
}
@ -373,9 +373,9 @@ func noteAsValue(octave, note int) byte {
func (te *NoteEditor) command(t *Tracker, e key.Event) {
var n byte
if t.Model.Notes().Effect(te.scrollTable.Table.Cursor().X) {
if t.Model.Track().Item(te.scrollTable.Table.Cursor().X).Effect {
if nibbleValue, err := strconv.ParseInt(string(e.Name), 16, 8); err == nil {
ev := t.Model.Notes().InputNibble(byte(nibbleValue))
ev := t.Model.Note().InputNibble(byte(nibbleValue))
t.KeyNoteMap.Press(e.Name, ev)
}
} else {
@ -384,7 +384,7 @@ func (te *NoteEditor) command(t *Tracker, e key.Event) {
return
}
if action == "NoteOff" {
ev := t.Model.Notes().Input(0)
ev := t.Model.Note().Input(0)
t.KeyNoteMap.Press(e.Name, ev)
return
}
@ -393,8 +393,8 @@ func (te *NoteEditor) command(t *Tracker, e key.Event) {
if err != nil {
return
}
n = noteAsValue(t.Octave().Value(), val-12)
ev := t.Model.Notes().Input(n)
n = noteAsValue(t.Note().Octave().Value(), val-12)
ev := t.Model.Note().Input(n)
t.KeyNoteMap.Press(e.Name, ev)
}
}

View File

@ -42,8 +42,8 @@ func NewOrderEditor(m *tracker.Model) *OrderEditor {
return &OrderEditor{
scrollTable: NewScrollTable(
m.Order().Table(),
m.Tracks(),
m.OrderRows(),
m.Track().List(),
m.Order().RowList(),
),
}
}
@ -67,12 +67,12 @@ func (oe *OrderEditor) Layout(gtx C) D {
defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop()
defer op.Affine(f32.Affine2D{}.Rotate(f32.Pt(0, 0), -90*math.Pi/180).Offset(f32.Point{X: 0, Y: float32(h)})).Push(gtx.Ops).Pop()
gtx.Constraints = layout.Exact(image.Pt(1e6, 1e6))
Label(t.Theme, &t.Theme.OrderEditor.TrackTitle, t.Model.TrackTitle(i)).Layout(gtx)
Label(t.Theme, &t.Theme.OrderEditor.TrackTitle, t.Model.Track().Item(i).Title).Layout(gtx)
return D{Size: image.Pt(gtx.Dp(patternCellWidth), h)}
}
rowTitleBg := func(gtx C, j int) D {
if t.Model.Playing().Value() && j == t.PlayPosition().OrderRow {
if t.Model.Play().Started().Value() && j == t.Play().Position().OrderRow {
paint.FillShape(gtx.Ops, t.Theme.OrderEditor.Play, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, gtx.Dp(patternCellHeight))}.Op())
}
return D{}
@ -84,7 +84,7 @@ func (oe *OrderEditor) Layout(gtx C) D {
rowTitle := func(gtx C, j int) D {
w := gtx.Dp(unit.Dp(30))
callOp := rowMarkerPatternTextColorOp
if l := t.Loop(); j >= l.Start && j < l.Start+l.Length {
if l := t.Play().Loop(); j >= l.Start && j < l.Start+l.Length {
callOp = loopMarkerColorOp
}
defer op.Offset(image.Pt(0, -2)).Push(gtx.Ops).Pop()
@ -184,14 +184,14 @@ func (oe *OrderEditor) command(t *Tracker, e key.Event) {
switch e.Name {
case key.NameDeleteBackward:
if e.Modifiers.Contain(key.ModShortcut) {
t.Model.DeleteOrderRow(true).Do()
t.Model.Order().DeleteRow(true).Do()
}
case key.NameDeleteForward:
if e.Modifiers.Contain(key.ModShortcut) {
t.Model.DeleteOrderRow(false).Do()
t.Model.Order().DeleteRow(false).Do()
}
case key.NameReturn:
t.Model.AddOrderRow(e.Modifiers.Contain(key.ModShortcut)).Do()
t.Model.Order().AddRow(e.Modifiers.Contain(key.ModShortcut)).Do()
}
if iv, err := strconv.Atoi(string(e.Name)); err == nil {
t.Model.Order().SetValue(oe.scrollTable.Table.Cursor(), iv)

View File

@ -6,7 +6,6 @@ import (
"gioui.org/layout"
"gioui.org/unit"
"github.com/vsariola/sointu/tracker"
)
type (
@ -20,12 +19,11 @@ type (
Oscilloscope struct {
Theme *Theme
Model *tracker.ScopeModel
State *OscilloscopeState
}
)
func NewOscilloscope(model *tracker.Model) *OscilloscopeState {
func NewOscilloscope() *OscilloscopeState {
return &OscilloscopeState{
plot: NewPlot(plotRange{0, 1}, plotRange{-1, 1}, 0),
onceBtn: new(Clickable),
@ -35,10 +33,9 @@ func NewOscilloscope(model *tracker.Model) *OscilloscopeState {
}
}
func Scope(th *Theme, m *tracker.ScopeModel, st *OscilloscopeState) Oscilloscope {
func Scope(th *Theme, st *OscilloscopeState) Oscilloscope {
return Oscilloscope{
Theme: th,
Model: m,
State: st,
}
}
@ -48,15 +45,15 @@ func (s *Oscilloscope) Layout(gtx C) D {
leftSpacer := layout.Spacer{Width: unit.Dp(6), Height: unit.Dp(24)}.Layout
rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout
triggerChannel := NumUpDown(s.Model.TriggerChannel(), s.Theme, s.State.triggerChannelNumber, "Trigger channel")
lengthInBeats := NumUpDown(s.Model.LengthInBeats(), s.Theme, s.State.lengthInBeatsNumber, "Buffer length in beats")
triggerChannel := NumUpDown(t.Scope().TriggerChannel(), s.Theme, s.State.triggerChannelNumber, "Trigger channel")
lengthInBeats := NumUpDown(t.Scope().LengthInBeats(), s.Theme, s.State.lengthInBeatsNumber, "Buffer length in beats")
onceBtn := ToggleBtn(s.Model.Once(), s.Theme, s.State.onceBtn, "Once", "Trigger once on next event")
wrapBtn := ToggleBtn(s.Model.Wrap(), s.Theme, s.State.wrapBtn, "Wrap", "Wrap buffer when full")
onceBtn := ToggleBtn(t.Scope().Once(), s.Theme, s.State.onceBtn, "Once", "Trigger once on next event")
wrapBtn := ToggleBtn(t.Scope().Wrap(), s.Theme, s.State.wrapBtn, "Wrap", "Wrap buffer when full")
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Flexed(1, func(gtx C) D {
w := s.Model.Waveform()
w := t.Scope().Waveform()
cx := float32(w.Cursor) / float32(len(w.Buffer))
data := func(chn int, xr plotRange) (yr plotRange, ok bool) {
@ -65,9 +62,10 @@ func (s *Oscilloscope) Layout(gtx C) D {
if x1 > x2 {
return plotRange{}, false
}
step := max((x2-x1)/1000, 1) // if the range is too large, sample only ~ 1000 points
y1 := float32(math.Inf(-1))
y2 := float32(math.Inf(+1))
for i := x1; i <= x2; i++ {
for i := x1; i <= x2; i += step {
sample := w.Buffer[i][chn]
y1 = max(y1, sample)
y2 = min(y2, sample)
@ -75,9 +73,9 @@ func (s *Oscilloscope) Layout(gtx C) D {
return plotRange{-y1, -y2}, true
}
rpb := max(t.Model.RowsPerBeat().Value(), 1)
rpb := max(t.Song().RowsPerBeat().Value(), 1)
xticks := func(r plotRange, count int, yield func(pos float32, label string)) {
l := s.Model.LengthInBeats().Value() * rpb
l := t.Scope().LengthInBeats().Value() * rpb
a := max(int(math.Ceil(float64(r.a*float32(l)))), 0)
b := min(int(math.Floor(float64(r.b*float32(l)))), l)
step := 1

View File

@ -128,9 +128,9 @@ func (p ParamWidget) Layout(gtx C) D {
title := Label(p.Theme, &p.Theme.UnitEditor.Name, p.Parameter.Name())
t := TrackerFromContext(gtx)
widget := func(gtx C) D {
if port, ok := p.Parameter.Port(); t.IsChoosingSendTarget() && ok {
if port, ok := p.Parameter.Port(); t.Params().IsChoosingSendTarget() && ok {
for p.State.clickable.Clicked(gtx) {
t.ChooseSendTarget(p.Parameter.UnitID(), port).Do()
t.Params().ChooseSendTarget(p.Parameter.UnitID(), port).Do()
}
k := Port(p.Theme, p.State)
return k.Layout(gtx)
@ -144,7 +144,7 @@ func (p ParamWidget) Layout(gtx C) D {
return s.Layout(gtx)
case tracker.IDParameter:
for p.State.clickable.Clicked(gtx) {
t.ChooseSendSource(p.Parameter.UnitID()).Do()
t.Params().ChooseSendSource(p.Parameter.UnitID()).Do()
}
btn := Btn(t.Theme, &t.Theme.Button.Text, &p.State.clickable, "Set", p.Parameter.Hint().Label)
if p.Disabled {

View File

@ -75,9 +75,9 @@ func (pp *PatchPanel) Layout(gtx C) D {
tr := TrackerFromContext(gtx)
bottom := func(gtx C) D {
switch {
case tr.InstrComment().Value():
case tr.Instrument().Tab().Value() == int(tracker.InstrumentCommentTab):
return pp.instrProps.layout(gtx)
case tr.InstrPresets().Value():
case tr.Instrument().Tab().Value() == int(tracker.InstrumentPresetsTab):
return pp.instrPresets.layout(gtx)
default: // editor
return pp.instrEditor.layout(gtx)
@ -92,9 +92,9 @@ func (pp *PatchPanel) Layout(gtx C) D {
func (pp *PatchPanel) BottomTags(level int, yield TagYieldFunc) bool {
switch {
case pp.InstrComment().Value():
case pp.Instrument().Tab().Value() == int(tracker.InstrumentCommentTab):
return pp.instrProps.Tags(level, yield)
case pp.InstrPresets().Value():
case pp.Instrument().Tab().Value() == int(tracker.InstrumentPresetsTab):
return pp.instrPresets.Tags(level, yield)
default: // editor
return pp.instrEditor.Tags(level, yield)
@ -143,18 +143,18 @@ func MakeInstrumentTools(m *tracker.Model) InstrumentTools {
func (it *InstrumentTools) Layout(gtx C) D {
t := TrackerFromContext(gtx)
it.update(gtx, t)
editorBtn := TabBtn(t.Model.InstrEditor(), t.Theme, it.EditorTab, "Editor", "")
presetsBtn := TabBtn(t.Model.InstrPresets(), t.Theme, it.PresetsTab, "Presets", "")
commentBtn := TabBtn(t.Model.InstrComment(), t.Theme, it.CommentTab, "Properties", "")
octave := NumUpDown(t.Model.Octave(), t.Theme, t.OctaveNumberInput, "Octave")
linkInstrTrackBtn := ToggleIconBtn(t.Model.LinkInstrTrack(), t.Theme, it.linkInstrTrackBtn, icons.NotificationSyncDisabled, icons.NotificationSync, it.linkDisabledHint, it.linkEnabledHint)
instrEnlargedBtn := ToggleIconBtn(t.Model.InstrEnlarged(), t.Theme, it.enlargeBtn, icons.NavigationFullscreen, icons.NavigationFullscreenExit, it.enlargeHint, it.shrinkHint)
addInstrumentBtn := ActionIconBtn(t.Model.AddInstrument(), t.Theme, it.newInstrumentBtn, icons.ContentAdd, it.addInstrumentHint)
editorBtn := TabBtn(tracker.MakeBool((*editorTab)(t.Model)), t.Theme, it.EditorTab, "Editor", "")
presetsBtn := TabBtn(tracker.MakeBool((*presetsTab)(t.Model)), t.Theme, it.PresetsTab, "Presets", "")
commentBtn := TabBtn(tracker.MakeBool((*commentTab)(t.Model)), t.Theme, it.CommentTab, "Properties", "")
octave := NumUpDown(t.Note().Octave(), t.Theme, t.OctaveNumberInput, "Octave")
linkInstrTrackBtn := ToggleIconBtn(t.Track().LinkInstrument(), t.Theme, it.linkInstrTrackBtn, icons.NotificationSyncDisabled, icons.NotificationSync, it.linkDisabledHint, it.linkEnabledHint)
instrEnlargedBtn := ToggleIconBtn(t.Play().TrackerHidden(), t.Theme, it.enlargeBtn, icons.NavigationFullscreen, icons.NavigationFullscreenExit, it.enlargeHint, it.shrinkHint)
addInstrumentBtn := ActionIconBtn(t.Model.Instrument().Add(), t.Theme, it.newInstrumentBtn, icons.ContentAdd, it.addInstrumentHint)
saveInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, it.saveInstrumentBtn, icons.ContentSave, "Save instrument")
loadInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, it.loadInstrumentBtn, icons.FileFolderOpen, "Load instrument")
copyInstrumentBtn := IconBtn(t.Theme, &t.Theme.IconButton.Enabled, it.copyInstrumentBtn, icons.ContentContentCopy, "Copy instrument")
deleteInstrumentBtn := ActionIconBtn(t.DeleteInstrument(), t.Theme, it.deleteInstrumentBtn, icons.ActionDelete, it.deleteInstrumentHint)
deleteInstrumentBtn := ActionIconBtn(t.Instrument().Delete(), t.Theme, it.deleteInstrumentBtn, icons.ActionDelete, it.deleteInstrumentHint)
btns := func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(layout.Spacer{Width: 6}.Layout),
@ -177,26 +177,58 @@ func (it *InstrumentTools) Layout(gtx C) D {
return Surface{Height: 4, Focus: t.PatchPanel.TreeFocused(gtx)}.Layout(gtx, btns)
}
type (
editorTab tracker.Model
presetsTab tracker.Model
commentTab tracker.Model
)
func (e *editorTab) Value() bool {
return (*tracker.Model)(e).Instrument().Tab().Value() == int(tracker.InstrumentEditorTab)
}
func (e *editorTab) SetValue(val bool) {
if val {
(*tracker.Model)(e).Instrument().Tab().SetValue(int(tracker.InstrumentEditorTab))
}
}
func (p *presetsTab) Value() bool {
return (*tracker.Model)(p).Instrument().Tab().Value() == int(tracker.InstrumentPresetsTab)
}
func (p *presetsTab) SetValue(val bool) {
if val {
(*tracker.Model)(p).Instrument().Tab().SetValue(int(tracker.InstrumentPresetsTab))
}
}
func (c *commentTab) Value() bool {
return (*tracker.Model)(c).Instrument().Tab().Value() == int(tracker.InstrumentCommentTab)
}
func (c *commentTab) SetValue(val bool) {
if val {
(*tracker.Model)(c).Instrument().Tab().SetValue(int(tracker.InstrumentCommentTab))
}
}
func (it *InstrumentTools) update(gtx C, tr *Tracker) {
for it.copyInstrumentBtn.Clicked(gtx) {
if contents, ok := tr.Instruments().CopyElements(); ok {
if contents, ok := tr.Instrument().List().CopyElements(); ok {
gtx.Execute(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(bytes.NewReader(contents))})
tr.Alerts().Add("Instrument copied to clipboard", tracker.Info)
}
}
for it.saveInstrumentBtn.Clicked(gtx) {
writer, err := tr.Explorer.CreateFile(tr.InstrumentName().Value() + ".yml")
writer, err := tr.Explorer.CreateFile(tr.Instrument().Name().Value() + ".yml")
if err != nil {
continue
}
tr.SaveInstrument(writer)
tr.Instrument().Write(writer)
}
for it.loadInstrumentBtn.Clicked(gtx) {
reader, err := tr.Explorer.ChooseFile(".yml", ".json", ".4ki", ".4kp")
if err != nil {
continue
}
tr.LoadInstrument(reader)
tr.Instrument().Read(reader)
}
}
@ -208,7 +240,7 @@ func (it *InstrumentTools) Tags(level int, yield TagYieldFunc) bool {
func MakeInstrList(model *tracker.Model) InstrumentList {
return InstrumentList{
instrumentDragList: NewDragList(model.Instruments(), layout.Horizontal),
instrumentDragList: NewDragList(model.Instrument().List(), layout.Horizontal),
nameEditor: NewEditor(true, true, text.Middle),
}
}
@ -221,7 +253,7 @@ func (il *InstrumentList) Layout(gtx C) D {
element := func(gtx C, i int) D {
grabhandle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, strconv.Itoa(i+1))
label := func(gtx C) D {
name, level, mute, ok := t.Instrument(i)
name, level, mute, ok := t.Instrument().Item(i)
if !ok {
labelStyle := Label(t.Theme, &t.Theme.InstrumentEditor.InstrumentList.Number, "")
return layout.Center.Layout(gtx, labelStyle.Layout)
@ -233,12 +265,12 @@ func (il *InstrumentList) Layout(gtx C) D {
s.Color = color.NRGBA{R: 255, G: k, B: 255, A: 255}
}
if i == il.instrumentDragList.TrackerList.Selected() {
for il.nameEditor.Update(gtx, t.InstrumentName()) != EditorEventNone {
for il.nameEditor.Update(gtx, t.Instrument().Name()) != EditorEventNone {
il.instrumentDragList.Focus()
}
return layout.Center.Layout(gtx, func(gtx C) D {
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
return il.nameEditor.Layout(gtx, t.InstrumentName(), t.Theme, &s, "Instr")
return il.nameEditor.Layout(gtx, t.Instrument().Name(), t.Theme, &s, "Instr")
})
}
if name == "" {
@ -280,9 +312,9 @@ func (il *InstrumentList) update(gtx C, t *Tracker) {
case key.NameDownArrow:
var tagged Tagged
switch {
case t.InstrComment().Value():
case t.Instrument().Tab().Value() == int(tracker.InstrumentCommentTab):
tagged = &t.PatchPanel.instrProps
case t.InstrPresets().Value():
case t.Instrument().Tab().Value() == int(tracker.InstrumentPresetsTab):
tagged = &t.PatchPanel.instrPresets
default: // editor
tagged = &t.PatchPanel.instrEditor

View File

@ -61,7 +61,7 @@ func NewSongPanel(tr *Tracker) *SongPanel {
RowsPerBeat: NewNumericUpDownState(),
Step: NewNumericUpDownState(),
SongLength: NewNumericUpDownState(),
Scope: NewOscilloscope(tr.Model),
Scope: NewOscilloscope(),
MenuBar: NewMenuBar(tr),
PlayBar: NewPlayBar(),
@ -88,14 +88,14 @@ func NewSongPanel(tr *Tracker) *SongPanel {
func (s *SongPanel) Update(gtx C, t *Tracker) {
for s.WeightingTypeBtn.Clicked(gtx) {
t.Model.DetectorWeighting().SetValue((t.DetectorWeighting().Value() + 1) % int(tracker.NumWeightingTypes))
t.Model.Detector().Weighting().SetValue((t.Detector().Weighting().Value() + 1) % int(tracker.NumWeightingTypes))
}
for s.OversamplingBtn.Clicked(gtx) {
t.Model.Oversampling().SetValue(!t.Oversampling().Value())
t.Model.Detector().Oversampling().SetValue(!t.Detector().Oversampling().Value())
}
for s.SynthBtn.Clicked(gtx) {
r := t.Model.SyntherIndex().Range()
t.Model.SyntherIndex().SetValue((t.SyntherIndex().Value()+1)%(r.Max-r.Min+1) + r.Min)
r := t.Model.Play().SyntherIndex().Range()
t.Model.Play().SyntherIndex().SetValue((t.Play().SyntherIndex().Value()+1)%(r.Max-r.Min+1) + r.Min)
}
}
@ -114,7 +114,7 @@ func (t *SongPanel) layoutSongOptions(gtx C) D {
paint.FillShape(gtx.Ops, tr.Theme.SongPanel.Bg, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op())
var weightingTxt string
switch tracker.WeightingType(tr.Model.DetectorWeighting().Value()) {
switch tracker.WeightingType(tr.Model.Detector().Weighting().Value()) {
case tracker.KWeighting:
weightingTxt = "K-weight (LUFS)"
case tracker.AWeighting:
@ -128,14 +128,14 @@ func (t *SongPanel) layoutSongOptions(gtx C) D {
weightingBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.WeightingTypeBtn, weightingTxt, "")
oversamplingTxt := "Sample peak"
if tr.Model.Oversampling().Value() {
if tr.Model.Detector().Oversampling().Value() {
oversamplingTxt = "True peak"
}
oversamplingBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.OversamplingBtn, oversamplingTxt, "")
cpuSmallLabel := func(gtx C) D {
var a [vm.MAX_THREADS]sointu.CPULoad
c := tr.Model.CPULoad(a[:])
c := tr.Play().CPULoad(a[:])
if c < 1 {
return D{}
}
@ -150,7 +150,7 @@ func (t *SongPanel) layoutSongOptions(gtx C) D {
cpuEnlargedWidget := func(gtx C) D {
var sb strings.Builder
var a [vm.MAX_THREADS]sointu.CPULoad
c := tr.Model.CPULoad(a[:])
c := tr.Play().CPULoad(a[:])
high := false
for i := range c {
if i > 0 {
@ -169,35 +169,35 @@ func (t *SongPanel) layoutSongOptions(gtx C) D {
return cpuLabel.Layout(gtx)
}
synthBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.SynthBtn, tr.Model.SyntherName(), "")
synthBtn := Btn(tr.Theme, &tr.Theme.Button.Text, t.SynthBtn, tr.Model.Play().SyntherName(), "")
listItem := func(gtx C, index int) D {
switch index {
case 0:
return t.SongSettingsExpander.Layout(gtx, tr.Theme, "Song",
func(gtx C) D {
return Label(tr.Theme, &tr.Theme.SongPanel.RowHeader, strconv.Itoa(tr.BPM().Value())+" BPM").Layout(gtx)
return Label(tr.Theme, &tr.Theme.SongPanel.RowHeader, strconv.Itoa(tr.Song().BPM().Value())+" BPM").Layout(gtx)
},
func(gtx C) D {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
bpm := NumUpDown(tr.BPM(), tr.Theme, t.BPM, "BPM")
bpm := NumUpDown(tr.Song().BPM(), tr.Theme, t.BPM, "BPM")
return layoutSongOptionRow(gtx, tr.Theme, "BPM", bpm.Layout)
}),
layout.Rigid(func(gtx C) D {
songLength := NumUpDown(tr.SongLength(), tr.Theme, t.SongLength, "Song length")
songLength := NumUpDown(tr.Song().Length(), tr.Theme, t.SongLength, "Song length")
return layoutSongOptionRow(gtx, tr.Theme, "Song length", songLength.Layout)
}),
layout.Rigid(func(gtx C) D {
rowsPerPattern := NumUpDown(tr.RowsPerPattern(), tr.Theme, t.RowsPerPattern, "Rows per pattern")
rowsPerPattern := NumUpDown(tr.Song().RowsPerPattern(), tr.Theme, t.RowsPerPattern, "Rows per pattern")
return layoutSongOptionRow(gtx, tr.Theme, "Rows per pat", rowsPerPattern.Layout)
}),
layout.Rigid(func(gtx C) D {
rowsPerBeat := NumUpDown(tr.RowsPerBeat(), tr.Theme, t.RowsPerBeat, "Rows per beat")
rowsPerBeat := NumUpDown(tr.Song().RowsPerBeat(), tr.Theme, t.RowsPerBeat, "Rows per beat")
return layoutSongOptionRow(gtx, tr.Theme, "Rows per beat", rowsPerBeat.Layout)
}),
layout.Rigid(func(gtx C) D {
step := NumUpDown(tr.Step(), tr.Theme, t.Step, "Cursor step")
step := NumUpDown(tr.Note().Step(), tr.Theme, t.Step, "Cursor step")
return layoutSongOptionRow(gtx, tr.Theme, "Cursor step", step.Layout)
}),
)
@ -214,25 +214,25 @@ func (t *SongPanel) layoutSongOptions(gtx C) D {
case 2:
return t.LoudnessExpander.Layout(gtx, tr.Theme, "Loudness",
func(gtx C) D {
loudness := tr.Model.DetectorResult().Loudness[tracker.LoudnessShortTerm]
loudness := tr.Model.Detector().Result().Loudness[tracker.LoudnessShortTerm]
return dbLabel(tr.Theme, loudness).Layout(gtx)
},
func(gtx C) D {
return layout.Flex{Axis: layout.Vertical, Alignment: layout.End}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "Momentary", dbLabel(tr.Theme, tr.Model.DetectorResult().Loudness[tracker.LoudnessMomentary]).Layout)
return layoutSongOptionRow(gtx, tr.Theme, "Momentary", dbLabel(tr.Theme, tr.Model.Detector().Result().Loudness[tracker.LoudnessMomentary]).Layout)
}),
layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "Short term", dbLabel(tr.Theme, tr.Model.DetectorResult().Loudness[tracker.LoudnessShortTerm]).Layout)
return layoutSongOptionRow(gtx, tr.Theme, "Short term", dbLabel(tr.Theme, tr.Model.Detector().Result().Loudness[tracker.LoudnessShortTerm]).Layout)
}),
layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "Integrated", dbLabel(tr.Theme, tr.Model.DetectorResult().Loudness[tracker.LoudnessIntegrated]).Layout)
return layoutSongOptionRow(gtx, tr.Theme, "Integrated", dbLabel(tr.Theme, tr.Model.Detector().Result().Loudness[tracker.LoudnessIntegrated]).Layout)
}),
layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "Max. momentary", dbLabel(tr.Theme, tr.Model.DetectorResult().Loudness[tracker.LoudnessMaxMomentary]).Layout)
return layoutSongOptionRow(gtx, tr.Theme, "Max. momentary", dbLabel(tr.Theme, tr.Model.Detector().Result().Loudness[tracker.LoudnessMaxMomentary]).Layout)
}),
layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "Max. short term", dbLabel(tr.Theme, tr.Model.DetectorResult().Loudness[tracker.LoudnessMaxShortTerm]).Layout)
return layoutSongOptionRow(gtx, tr.Theme, "Max. short term", dbLabel(tr.Theme, tr.Model.Detector().Result().Loudness[tracker.LoudnessMaxShortTerm]).Layout)
}),
layout.Rigid(func(gtx C) D {
gtx.Constraints.Min.X = 0
@ -244,23 +244,23 @@ func (t *SongPanel) layoutSongOptions(gtx C) D {
case 3:
return t.PeakExpander.Layout(gtx, tr.Theme, "Peaks",
func(gtx C) D {
maxPeak := max(tr.Model.DetectorResult().Peaks[tracker.PeakShortTerm][0], tr.Model.DetectorResult().Peaks[tracker.PeakShortTerm][1])
maxPeak := max(tr.Model.Detector().Result().Peaks[tracker.PeakShortTerm][0], tr.Model.Detector().Result().Peaks[tracker.PeakShortTerm][1])
return dbLabel(tr.Theme, maxPeak).Layout(gtx)
},
func(gtx C) D {
return layout.Flex{Axis: layout.Vertical, Alignment: layout.End}.Layout(gtx,
// no need to show momentary peak, it does not have too much meaning
layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "Short term L", dbLabel(tr.Theme, tr.Model.DetectorResult().Peaks[tracker.PeakShortTerm][0]).Layout)
return layoutSongOptionRow(gtx, tr.Theme, "Short term L", dbLabel(tr.Theme, tr.Model.Detector().Result().Peaks[tracker.PeakShortTerm][0]).Layout)
}),
layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "Short term R", dbLabel(tr.Theme, tr.Model.DetectorResult().Peaks[tracker.PeakShortTerm][1]).Layout)
return layoutSongOptionRow(gtx, tr.Theme, "Short term R", dbLabel(tr.Theme, tr.Model.Detector().Result().Peaks[tracker.PeakShortTerm][1]).Layout)
}),
layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "Integrated L", dbLabel(tr.Theme, tr.Model.DetectorResult().Peaks[tracker.PeakIntegrated][0]).Layout)
return layoutSongOptionRow(gtx, tr.Theme, "Integrated L", dbLabel(tr.Theme, tr.Model.Detector().Result().Peaks[tracker.PeakIntegrated][0]).Layout)
}),
layout.Rigid(func(gtx C) D {
return layoutSongOptionRow(gtx, tr.Theme, "Integrated R", dbLabel(tr.Theme, tr.Model.DetectorResult().Peaks[tracker.PeakIntegrated][1]).Layout)
return layoutSongOptionRow(gtx, tr.Theme, "Integrated R", dbLabel(tr.Theme, tr.Model.Detector().Result().Peaks[tracker.PeakIntegrated][1]).Layout)
}),
layout.Rigid(func(gtx C) D {
gtx.Constraints.Min.X = 0
@ -270,7 +270,7 @@ func (t *SongPanel) layoutSongOptions(gtx C) D {
},
)
case 4:
scope := Scope(tr.Theme, tr.Model.SignalAnalyzer(), t.Scope)
scope := Scope(tr.Theme, t.Scope)
scopeScaleBar := func(gtx C) D {
return t.ScopeScaleBar.Layout(gtx, scope.Layout)
}
@ -289,7 +289,7 @@ func (t *SongPanel) layoutSongOptions(gtx C) D {
gtx.Constraints.Min = gtx.Constraints.Max
dims := t.List.Layout(gtx, 7, listItem)
t.ScrollBar.Layout(gtx, &tr.Theme.SongPanel.ScrollBar, 7, &t.List.Position)
tr.SpecAnEnabled().SetValue(t.SpectrumExpander.Expanded)
tr.Spectrum().Enabled().SetValue(t.SpectrumExpander.Expanded)
return dims
}
@ -478,9 +478,9 @@ func NewMenuBar(tr *Tracker) *MenuBar {
PanicBtn: new(Clickable),
panicHint: makeHint("Panic", " (%s)", "PanicToggle"),
}
for input := range tr.MIDI.InputDevices {
for input := range tr.MIDI().InputDevices {
ret.midiMenuItems = append(ret.midiMenuItems,
MenuItem(tr.SelectMidiInput(input), input, "", icons.ImageControlPoint),
MenuItem(tr.MIDI().Open(input), input, "", icons.ImageControlPoint),
)
}
return ret
@ -495,11 +495,11 @@ func (t *MenuBar) Layout(gtx C) D {
fileBtn := MenuBtn(tr.Theme, &t.MenuStates[0], &t.Clickables[0], "File")
fileFC := layout.Rigid(func(gtx C) D {
items := [...]ActionMenuItem{
MenuItem(tr.NewSong(), "New Song", keyActionMap["NewSong"], icons.ContentClear),
MenuItem(tr.OpenSong(), "Open Song", keyActionMap["OpenSong"], icons.FileFolder),
MenuItem(tr.SaveSong(), "Save Song", keyActionMap["SaveSong"], icons.ContentSave),
MenuItem(tr.SaveSongAs(), "Save Song As...", keyActionMap["SaveSongAs"], icons.ContentSave),
MenuItem(tr.Export(), "Export Wav...", keyActionMap["ExportWav"], icons.ImageAudiotrack),
MenuItem(tr.Song().New(), "New Song", keyActionMap["NewSong"], icons.ContentClear),
MenuItem(tr.Song().Open(), "Open Song", keyActionMap["OpenSong"], icons.FileFolder),
MenuItem(tr.Song().Save(), "Save Song", keyActionMap["SaveSong"], icons.ContentSave),
MenuItem(tr.Song().SaveAs(), "Save Song As...", keyActionMap["SaveSongAs"], icons.ContentSave),
MenuItem(tr.Song().Export(), "Export Wav...", keyActionMap["ExportWav"], icons.ImageAudiotrack),
MenuItem(tr.RequestQuit(), "Quit", keyActionMap["Quit"], icons.ActionExitToApp),
}
if !canQuit {
@ -510,9 +510,9 @@ func (t *MenuBar) Layout(gtx C) D {
editBtn := MenuBtn(tr.Theme, &t.MenuStates[1], &t.Clickables[1], "Edit")
editFC := layout.Rigid(func(gtx C) D {
return editBtn.Layout(gtx,
MenuItem(tr.Undo(), "Undo", keyActionMap["Undo"], icons.ContentUndo),
MenuItem(tr.Redo(), "Redo", keyActionMap["Redo"], icons.ContentRedo),
MenuItem(tr.RemoveUnused(), "Remove unused data", keyActionMap["RemoveUnused"], icons.ImageCrop),
MenuItem(tr.History().Undo(), "Undo", keyActionMap["Undo"], icons.ContentUndo),
MenuItem(tr.History().Redo(), "Redo", keyActionMap["Redo"], icons.ContentRedo),
MenuItem(tr.Order().RemoveUnusedPatterns(), "Remove unused data", keyActionMap["RemoveUnused"], icons.ImageCrop),
)
})
midiBtn := MenuBtn(tr.Theme, &t.MenuStates[2], &t.Clickables[2], "MIDI")
@ -527,8 +527,8 @@ func (t *MenuBar) Layout(gtx C) D {
MenuItem(tr.ReportBug(), "Report bug", keyActionMap["ReportBug"], icons.ActionBugReport),
MenuItem(tr.ShowLicense(), "License", keyActionMap["ShowLicense"], icons.ActionCopyright))
})
panicBtn := ToggleIconBtn(tr.Panic(), tr.Theme, t.PanicBtn, icons.AlertErrorOutline, icons.AlertError, t.panicHint, t.panicHint)
if tr.Panic().Value() {
panicBtn := ToggleIconBtn(tr.Play().Panicked(), tr.Theme, t.PanicBtn, icons.AlertErrorOutline, icons.AlertError, t.panicHint, t.panicHint)
if tr.Play().Panicked().Value() {
panicBtn.Style = &tr.Theme.IconButton.Error
}
panicFC := layout.Flexed(1, func(gtx C) D { return layout.E.Layout(gtx, panicBtn.Layout) })
@ -574,11 +574,11 @@ func NewPlayBar() *PlayBar {
func (pb *PlayBar) Layout(gtx C) D {
tr := TrackerFromContext(gtx)
playBtn := ToggleIconBtn(tr.Playing(), tr.Theme, pb.PlayingBtn, icons.AVPlayArrow, icons.AVStop, pb.playHint, pb.stopHint)
rewindBtn := ActionIconBtn(tr.PlaySongStart(), tr.Theme, pb.RewindBtn, icons.AVFastRewind, pb.rewindHint)
recordBtn := ToggleIconBtn(tr.IsRecording(), tr.Theme, pb.RecordBtn, icons.AVFiberManualRecord, icons.AVFiberSmartRecord, pb.recordHint, pb.stopRecordHint)
followBtn := ToggleIconBtn(tr.Follow(), tr.Theme, pb.FollowBtn, icons.ActionSpeakerNotesOff, icons.ActionSpeakerNotes, pb.followOffHint, pb.followOnHint)
loopBtn := ToggleIconBtn(tr.LoopToggle(), tr.Theme, pb.LoopBtn, icons.NavigationArrowForward, icons.AVLoop, pb.loopOffHint, pb.loopOnHint)
playBtn := ToggleIconBtn(tr.Play().Started(), tr.Theme, pb.PlayingBtn, icons.AVPlayArrow, icons.AVStop, pb.playHint, pb.stopHint)
rewindBtn := ActionIconBtn(tr.Play().FromBeginning(), tr.Theme, pb.RewindBtn, icons.AVFastRewind, pb.rewindHint)
recordBtn := ToggleIconBtn(tr.Play().IsRecording(), tr.Theme, pb.RecordBtn, icons.AVFiberManualRecord, icons.AVFiberSmartRecord, pb.recordHint, pb.stopRecordHint)
followBtn := ToggleIconBtn(tr.Play().IsFollowing(), tr.Theme, pb.FollowBtn, icons.ActionSpeakerNotesOff, icons.ActionSpeakerNotes, pb.followOffHint, pb.followOnHint)
loopBtn := ToggleIconBtn(tr.Play().IsLooping(), tr.Theme, pb.LoopBtn, icons.NavigationArrowForward, icons.AVLoop, pb.loopOffHint, pb.loopOnHint)
return Surface{Height: 4}.Layout(gtx, func(gtx C) D {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,

View File

@ -40,29 +40,29 @@ func (s *SpectrumState) Layout(gtx C) D {
rightSpacer := layout.Spacer{Width: unit.Dp(6)}.Layout
var chnModeTxt string = "???"
switch tracker.SpecChnMode(t.Model.SpecAnChannelsInt().Value()) {
switch tracker.SpecChnMode(t.Model.Spectrum().Channels().Value()) {
case tracker.SpecChnModeSum:
chnModeTxt = "Sum"
case tracker.SpecChnModeSeparate:
chnModeTxt = "Separate"
}
resolution := NumUpDown(t.Model.SpecAnResolution(), t.Theme, s.resolutionNumber, "Resolution")
resolution := NumUpDown(t.Model.Spectrum().Resolution(), t.Theme, s.resolutionNumber, "Resolution")
chnModeBtn := Btn(t.Theme, &t.Theme.Button.Text, s.chnModeBtn, chnModeTxt, "Channel mode")
speed := NumUpDown(t.Model.SpecAnSpeed(), t.Theme, s.speed, "Speed")
speed := NumUpDown(t.Model.Spectrum().Speed(), t.Theme, s.speed, "Speed")
numchns := 0
speclen := len(t.Model.Spectrum()[0])
speclen := len(t.Model.Spectrum().Result()[0])
if speclen > 0 {
numchns = 1
if len(t.Model.Spectrum()[1]) == speclen {
if len(t.Model.Spectrum().Result()[1]) == speclen {
numchns = 2
}
}
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Flexed(1, func(gtx C) D {
biquad, biquadok := t.Model.BiquadCoeffs()
biquad, biquadok := t.Model.Spectrum().BiquadCoeffs()
data := func(chn int, xr plotRange) (yr plotRange, ok bool) {
if chn == 2 {
if xr.a >= 0 {
@ -88,16 +88,16 @@ func (s *SpectrumState) Layout(gtx C) D {
y2 := float32(math.Inf(+1))
switch {
case x2 <= x1+1 && x2 < speclen-1: // perform smoothstep interpolation when we are overlapping only a few bins
l := t.Model.Spectrum()[chn][x1]
r := t.Model.Spectrum()[chn][x1+1]
l := t.Model.Spectrum().Result()[chn][x1]
r := t.Model.Spectrum().Result()[chn][x1+1]
y1 = smoothInterpolate(l, r, float32(f1))
l = t.Model.Spectrum()[chn][x2]
r = t.Model.Spectrum()[chn][x2+1]
l = t.Model.Spectrum().Result()[chn][x2]
r = t.Model.Spectrum().Result()[chn][x2+1]
y2 = smoothInterpolate(l, r, float32(f2))
y1, y2 = max(y1, y2), min(y1, y2)
default:
for i := x1; i <= x2; i++ {
sample := t.Model.Spectrum()[chn][i]
sample := t.Model.Spectrum().Result()[chn][i]
y1 = max(y1, sample)
y2 = min(y2, sample)
}
@ -210,8 +210,8 @@ func nextPowerOfTwo(v int) int {
func (s *SpectrumState) Update(gtx C) {
t := TrackerFromContext(gtx)
for s.chnModeBtn.Clicked(gtx) {
t.Model.SpecAnChannelsInt().SetValue((t.SpecAnChannelsInt().Value() + 1) % int(tracker.NumSpecChnModes))
t.Model.Spectrum().Channels().SetValue((t.Model.Spectrum().Channels().Value() + 1) % int(tracker.NumSpecChnModes))
}
s.resolutionNumber.Update(gtx, t.Model.SpecAnResolution())
s.speed.Update(gtx, t.Model.SpecAnSpeed())
s.resolutionNumber.Update(gtx, t.Model.Spectrum().Resolution())
s.speed.Update(gtx, t.Model.Spectrum().Speed())
}

View File

@ -94,7 +94,7 @@ func NewTracker(model *tracker.Model) *Tracker {
Model: model,
filePathString: model.FilePath(),
filePathString: model.Song().FilePath(),
}
t.SongPanel = NewSongPanel(t)
t.KeyNoteMap = MakeKeyboard[key.Name](model.Broker())
@ -185,12 +185,12 @@ func (t *Tracker) Main() {
}
acks <- struct{}{}
case <-recoveryTicker.C:
t.SaveRecovery()
t.History().SaveRecovery()
}
}
}
recoveryTicker.Stop()
t.SaveRecovery()
t.History().SaveRecovery()
close(t.Broker().FinishedGUI)
}
@ -226,7 +226,7 @@ func (t *Tracker) Layout(gtx layout.Context) {
paint.Fill(gtx.Ops, t.Theme.Material.Bg)
event.Op(gtx.Ops, t) // area for capturing scroll events
if t.InstrEnlarged().Value() {
if t.Play().TrackerHidden().Value() {
t.layoutTop(gtx)
} else {
t.VerticalSplit.Layout(gtx,
@ -263,14 +263,14 @@ func (t *Tracker) Layout(gtx layout.Context) {
case key.Event:
t.KeyEvent(e, gtx)
case transfer.DataEvent:
t.ReadSong(e.Open())
t.Song().Read(e.Open())
}
}
// if no-one else handled the note events, we handle them here
for len(t.noteEvents) > 0 {
ev := t.noteEvents[0]
ev.IsTrack = false
ev.Channel = t.Model.Instruments().Selected()
ev.Channel = t.Model.Instrument().List().Selected()
ev.Source = t
copy(t.noteEvents, t.noteEvents[1:])
t.noteEvents = t.noteEvents[:len(t.noteEvents)-1]
@ -285,49 +285,49 @@ func (t *Tracker) showDialog(gtx C) {
switch t.Dialog() {
case tracker.NewSongChanges, tracker.OpenSongChanges, tracker.QuitChanges:
dialog := MakeDialog(t.Theme, t.DialogState, "Save changes to song?", "Your changes will be lost if you don't save them.",
DialogBtn("Save", t.SaveSong()),
DialogBtn("Don't save", t.DiscardSong()),
DialogBtn("Cancel", t.Cancel()),
DialogBtn("Save", t.Song().Save()),
DialogBtn("Don't save", t.Song().Discard()),
DialogBtn("Cancel", t.CancelDialog()),
)
dialog.Layout(gtx)
case tracker.Export:
dialog := MakeDialog(t.Theme, t.DialogState, "Export format", "Choose the sample format for the exported .wav file.",
DialogBtn("Int16", t.ExportInt16()),
DialogBtn("Float32", t.ExportFloat()),
DialogBtn("Cancel", t.Cancel()),
DialogBtn("Int16", t.Song().ExportInt16()),
DialogBtn("Float32", t.Song().ExportFloat()),
DialogBtn("Cancel", t.CancelDialog()),
)
dialog.Layout(gtx)
case tracker.OpenSongOpenExplorer:
t.explorerChooseFile(t.ReadSong, ".yml", ".json")
t.explorerChooseFile(t.Song().Read, ".yml", ".json")
case tracker.NewSongSaveExplorer, tracker.OpenSongSaveExplorer, tracker.QuitSaveExplorer, tracker.SaveAsExplorer:
filename := t.filePathString.Value()
if filename == "" {
filename = "song.yml"
}
t.explorerCreateFile(t.WriteSong, filename)
t.explorerCreateFile(t.Song().Write, filename)
case tracker.ExportFloatExplorer, tracker.ExportInt16Explorer:
filename := "song.wav"
if p := t.filePathString.Value(); p != "" {
filename = p[:len(p)-len(filepath.Ext(p))] + ".wav"
}
t.explorerCreateFile(func(wc io.WriteCloser) {
t.WriteWav(wc, t.Dialog() == tracker.ExportInt16Explorer)
t.Song().WriteWav(wc, t.Dialog() == tracker.ExportInt16Explorer)
}, filename)
case tracker.License:
dialog := MakeDialog(t.Theme, t.DialogState, "License", sointu.License,
DialogBtn("Close", t.Cancel()),
DialogBtn("Close", t.CancelDialog()),
)
dialog.Layout(gtx)
case tracker.DeleteUserPresetDialog:
dialog := MakeDialog(t.Theme, t.DialogState, "Delete user preset?", "Are you sure you want to delete the selected user preset?\nThis action cannot be undone.",
DialogBtn("Delete", t.DeleteUserPreset()),
DialogBtn("Cancel", t.Cancel()),
DialogBtn("Delete", t.Preset().ConfirmDelete()),
DialogBtn("Cancel", t.CancelDialog()),
)
dialog.Layout(gtx)
case tracker.OverwriteUserPresetDialog:
dialog := MakeDialog(t.Theme, t.DialogState, "Overwrite user preset?", "Are you sure you want to overwrite the existing user preset with the same name?",
DialogBtn("Save", t.OverwriteUserPreset()),
DialogBtn("Cancel", t.Cancel()),
DialogBtn("Save", t.Preset().Overwrite()),
DialogBtn("Cancel", t.CancelDialog()),
)
dialog.Layout(gtx)
}
@ -342,7 +342,7 @@ func (t *Tracker) explorerChooseFile(success func(io.ReadCloser), extensions ...
if err == nil {
success(file)
} else {
t.Cancel().Do()
t.CancelDialog().Do()
if err != explorer.ErrUserDecline {
t.Alerts().Add(err.Error(), tracker.Error)
}
@ -360,7 +360,7 @@ func (t *Tracker) explorerCreateFile(success func(io.WriteCloser), filename stri
if err == nil {
success(file)
} else {
t.Cancel().Do()
t.CancelDialog().Do()
if err != explorer.ErrUserDecline {
t.Alerts().Add(err.Error(), tracker.Error)
}
@ -416,7 +416,7 @@ func (t *Tracker) openUrl(url string) {
func (t *Tracker) Tags(curLevel int, yield TagYieldFunc) bool {
ret := t.PatchPanel.Tags(curLevel+1, yield)
if !t.InstrEnlarged().Value() {
if !t.Play().TrackerHidden().Value() {
ret = ret && t.OrderEditor.Tags(curLevel+1, yield) &&
t.TrackEditor.Tags(curLevel+1, yield)
}