mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-04 01:28:45 -04:00
feat: toggle button to duplicate non-unique patterns when changed
Closes #77.
This commit is contained in:
parent
3a7ab0416a
commit
10f021a497
@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
|
- A toggle button for copying non-unique patterns before editing. When enabled
|
||||||
|
and if the pattern is used in multiple places, the pattern is copied first.
|
||||||
|
([#77][i77])
|
||||||
- User can define own keybindings in
|
- User can define own keybindings in
|
||||||
`os.UserConfigDir()/sointu/keybindings.yaml` ([#94][i94], [#151][i151])
|
`os.UserConfigDir()/sointu/keybindings.yaml` ([#94][i94], [#151][i151])
|
||||||
- A small number above the instrument name identifies the MIDI channel /
|
- A small number above the instrument name identifies the MIDI channel /
|
||||||
@ -226,6 +229,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
[0.1.0]: https://github.com/vsariola/sointu/compare/4klang-3.11...v0.1.0
|
[0.1.0]: https://github.com/vsariola/sointu/compare/4klang-3.11...v0.1.0
|
||||||
[i65]: https://github.com/vsariola/sointu/issues/65
|
[i65]: https://github.com/vsariola/sointu/issues/65
|
||||||
[i68]: https://github.com/vsariola/sointu/issues/68
|
[i68]: https://github.com/vsariola/sointu/issues/68
|
||||||
|
[i77]: https://github.com/vsariola/sointu/issues/77
|
||||||
[i94]: https://github.com/vsariola/sointu/issues/94
|
[i94]: https://github.com/vsariola/sointu/issues/94
|
||||||
[i112]: https://github.com/vsariola/sointu/issues/112
|
[i112]: https://github.com/vsariola/sointu/issues/112
|
||||||
[i116]: https://github.com/vsariola/sointu/issues/116
|
[i116]: https://github.com/vsariola/sointu/issues/116
|
||||||
|
38
song.go
38
song.go
@ -137,7 +137,10 @@ func (s Track) Note(pos SongPos) byte {
|
|||||||
return s.Patterns[pat][pos.PatternRow]
|
return s.Patterns[pat][pos.PatternRow]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Track) SetNote(pos SongPos, note byte) {
|
// SetNote sets the note at the given position. If uniquePatterns is true, the
|
||||||
|
// pattern is copied to a new pattern if the pattern is used by more than one
|
||||||
|
// order row.
|
||||||
|
func (s *Track) SetNote(pos SongPos, note byte, uniquePatterns bool) {
|
||||||
if pos.OrderRow < 0 || pos.PatternRow < 0 {
|
if pos.OrderRow < 0 || pos.PatternRow < 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -163,13 +166,31 @@ func (s *Track) SetNote(pos SongPos, note byte) {
|
|||||||
for pat >= len(s.Patterns) {
|
for pat >= len(s.Patterns) {
|
||||||
s.Patterns = append(s.Patterns, Pattern{})
|
s.Patterns = append(s.Patterns, Pattern{})
|
||||||
}
|
}
|
||||||
if pos.PatternRow >= len(s.Patterns[pat]) && note == 1 {
|
if uniquePatterns {
|
||||||
return
|
uses := 0
|
||||||
|
maxPat := 0
|
||||||
|
for _, p := range s.Order {
|
||||||
|
if p == pat {
|
||||||
|
uses++
|
||||||
|
}
|
||||||
|
if p > maxPat {
|
||||||
|
maxPat = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if uses > 1 {
|
||||||
|
newPattern := append(Pattern{}, s.Patterns[pat]...)
|
||||||
|
pat = maxPat + 1
|
||||||
|
if pat >= 36 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for pat >= len(s.Patterns) {
|
||||||
|
s.Patterns = append(s.Patterns, Pattern{})
|
||||||
|
}
|
||||||
|
s.Patterns[pat] = newPattern
|
||||||
|
s.Order.Set(pos.OrderRow, pat)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for pos.PatternRow >= len(s.Patterns[pat]) {
|
s.Patterns[pat].Set(pos.PatternRow, note)
|
||||||
s.Patterns[pat] = append(s.Patterns[pat], 1)
|
|
||||||
}
|
|
||||||
s.Patterns[pat][pos.PatternRow] = note
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the value at index; or 1 is the index is out of range
|
// Get returns the value at index; or 1 is the index is out of range
|
||||||
@ -182,6 +203,9 @@ func (s Pattern) Get(index int) byte {
|
|||||||
|
|
||||||
// Set sets the value at index; appending 1s until the slice is long enough.
|
// Set sets the value at index; appending 1s until the slice is long enough.
|
||||||
func (s *Pattern) Set(index int, value byte) {
|
func (s *Pattern) Set(index int, value byte) {
|
||||||
|
if value == 1 && index >= len(*s) {
|
||||||
|
return
|
||||||
|
}
|
||||||
for len(*s) <= index {
|
for len(*s) <= index {
|
||||||
*s = append(*s, 1)
|
*s = append(*s, 1)
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ type (
|
|||||||
UnitSearching Model
|
UnitSearching Model
|
||||||
UnitDisabled Model
|
UnitDisabled Model
|
||||||
LoopToggle Model
|
LoopToggle Model
|
||||||
|
UniquePatterns Model
|
||||||
)
|
)
|
||||||
|
|
||||||
func (v Bool) Toggle() {
|
func (v Bool) Toggle() {
|
||||||
@ -45,6 +46,7 @@ func (m *Model) Follow() *Follow { return (*Follow)(m) }
|
|||||||
func (m *Model) UnitSearching() *UnitSearching { return (*UnitSearching)(m) }
|
func (m *Model) UnitSearching() *UnitSearching { return (*UnitSearching)(m) }
|
||||||
func (m *Model) UnitDisabled() *UnitDisabled { return (*UnitDisabled)(m) }
|
func (m *Model) UnitDisabled() *UnitDisabled { return (*UnitDisabled)(m) }
|
||||||
func (m *Model) LoopToggle() *LoopToggle { return (*LoopToggle)(m) }
|
func (m *Model) LoopToggle() *LoopToggle { return (*LoopToggle)(m) }
|
||||||
|
func (m *Model) UniquePatterns() *UniquePatterns { return (*UniquePatterns)(m) }
|
||||||
|
|
||||||
// Panic methods
|
// Panic methods
|
||||||
|
|
||||||
@ -185,3 +187,10 @@ func (t *LoopToggle) setValue(val bool) {
|
|||||||
m.setLoop(newLoop)
|
m.setLoop(newLoop)
|
||||||
}
|
}
|
||||||
func (m *LoopToggle) Enabled() bool { return true }
|
func (m *LoopToggle) Enabled() bool { return true }
|
||||||
|
|
||||||
|
// UniquePatterns methods
|
||||||
|
|
||||||
|
func (m *UniquePatterns) Bool() Bool { return Bool{m} }
|
||||||
|
func (m *UniquePatterns) Value() bool { return m.uniquePatterns }
|
||||||
|
func (m *UniquePatterns) setValue(val bool) { m.uniquePatterns = val }
|
||||||
|
func (m *UniquePatterns) Enabled() bool { return true }
|
||||||
|
@ -220,6 +220,8 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) {
|
|||||||
t.UnitDisabled().Bool().Toggle()
|
t.UnitDisabled().Bool().Toggle()
|
||||||
case "LoopToggle":
|
case "LoopToggle":
|
||||||
t.LoopToggle().Bool().Toggle()
|
t.LoopToggle().Bool().Toggle()
|
||||||
|
case "UniquePatternsToggle":
|
||||||
|
t.UniquePatterns().Bool().Toggle()
|
||||||
// Integers
|
// Integers
|
||||||
case "InstrumentVoicesAdd":
|
case "InstrumentVoicesAdd":
|
||||||
t.Model.InstrumentVoices().Int().Add(1)
|
t.Model.InstrumentVoices().Int().Add(1)
|
||||||
|
@ -59,13 +59,15 @@ type NoteEditor struct {
|
|||||||
SubtractOctaveBtn *ActionClickable
|
SubtractOctaveBtn *ActionClickable
|
||||||
NoteOffBtn *ActionClickable
|
NoteOffBtn *ActionClickable
|
||||||
EffectBtn *BoolClickable
|
EffectBtn *BoolClickable
|
||||||
|
UniqueBtn *BoolClickable
|
||||||
|
|
||||||
scrollTable *ScrollTable
|
scrollTable *ScrollTable
|
||||||
tag struct{}
|
tag struct{}
|
||||||
eventFilters []event.Filter
|
eventFilters []event.Filter
|
||||||
|
|
||||||
deleteTrackHint string
|
deleteTrackHint string
|
||||||
addTrackHint string
|
addTrackHint string
|
||||||
|
uniqueOffTip, uniqueOnTip string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNoteEditor(model *tracker.Model) *NoteEditor {
|
func NewNoteEditor(model *tracker.Model) *NoteEditor {
|
||||||
@ -79,6 +81,7 @@ func NewNoteEditor(model *tracker.Model) *NoteEditor {
|
|||||||
SubtractOctaveBtn: NewActionClickable(model.SubtractOctave()),
|
SubtractOctaveBtn: NewActionClickable(model.SubtractOctave()),
|
||||||
NoteOffBtn: NewActionClickable(model.EditNoteOff()),
|
NoteOffBtn: NewActionClickable(model.EditNoteOff()),
|
||||||
EffectBtn: NewBoolClickable(model.Effect().Bool()),
|
EffectBtn: NewBoolClickable(model.Effect().Bool()),
|
||||||
|
UniqueBtn: NewBoolClickable(model.UniquePatterns().Bool()),
|
||||||
scrollTable: NewScrollTable(
|
scrollTable: NewScrollTable(
|
||||||
model.Notes().Table(),
|
model.Notes().Table(),
|
||||||
model.Tracks().List(),
|
model.Tracks().List(),
|
||||||
@ -93,6 +96,8 @@ func NewNoteEditor(model *tracker.Model) *NoteEditor {
|
|||||||
}
|
}
|
||||||
ret.deleteTrackHint = makeHint("Delete\ntrack", "\n(%s)", "DeleteTrack")
|
ret.deleteTrackHint = makeHint("Delete\ntrack", "\n(%s)", "DeleteTrack")
|
||||||
ret.addTrackHint = makeHint("Add\ntrack", "\n(%s)", "AddTrack")
|
ret.addTrackHint = makeHint("Add\ntrack", "\n(%s)", "AddTrack")
|
||||||
|
ret.uniqueOnTip = makeHint("Duplicate non-unique patterns", " (%s)", "UniquePatternsToggle")
|
||||||
|
ret.uniqueOffTip = makeHint("Allow editing non-unique patterns", " (%s)", "UniquePatternsToggle")
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +150,7 @@ func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D {
|
|||||||
return in.Layout(gtx, numStyle.Layout)
|
return in.Layout(gtx, numStyle.Layout)
|
||||||
}
|
}
|
||||||
effectBtnStyle := ToggleButton(gtx, t.Theme, te.EffectBtn, "Hex")
|
effectBtnStyle := ToggleButton(gtx, t.Theme, te.EffectBtn, "Hex")
|
||||||
|
uniqueBtnStyle := ToggleIcon(gtx, t.Theme, te.UniqueBtn, icons.ToggleStarBorder, icons.ToggleStar, te.uniqueOffTip, te.uniqueOnTip)
|
||||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
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(func(gtx C) D { return layout.Dimensions{Size: image.Pt(gtx.Dp(unit.Dp(12)), 0)} }),
|
||||||
layout.Rigid(addSemitoneBtnStyle.Layout),
|
layout.Rigid(addSemitoneBtnStyle.Layout),
|
||||||
@ -153,6 +159,7 @@ func (te *NoteEditor) layoutButtons(gtx C, t *Tracker) D {
|
|||||||
layout.Rigid(subtractOctaveBtnStyle.Layout),
|
layout.Rigid(subtractOctaveBtnStyle.Layout),
|
||||||
layout.Rigid(noteOffBtnStyle.Layout),
|
layout.Rigid(noteOffBtnStyle.Layout),
|
||||||
layout.Rigid(effectBtnStyle.Layout),
|
layout.Rigid(effectBtnStyle.Layout),
|
||||||
|
layout.Rigid(uniqueBtnStyle.Layout),
|
||||||
layout.Rigid(Label(" Voices:", white, t.Theme.Shaper)),
|
layout.Rigid(Label(" Voices:", white, t.Theme.Shaper)),
|
||||||
layout.Rigid(voiceUpDown),
|
layout.Rigid(voiceUpDown),
|
||||||
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }),
|
||||||
|
@ -667,8 +667,8 @@ func (v *NoteRows) swap(i, j int) (ok bool) {
|
|||||||
for _, track := range v.d.Song.Score.Tracks {
|
for _, track := range v.d.Song.Score.Tracks {
|
||||||
n1 := track.Note(ipos)
|
n1 := track.Note(ipos)
|
||||||
n2 := track.Note(jpos)
|
n2 := track.Note(jpos)
|
||||||
track.SetNote(ipos, n2)
|
track.SetNote(ipos, n2, v.uniquePatterns)
|
||||||
track.SetNote(jpos, n1)
|
track.SetNote(jpos, n1, v.uniquePatterns)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -679,7 +679,7 @@ func (v *NoteRows) delete(i int) (ok bool) {
|
|||||||
}
|
}
|
||||||
pos := v.d.Song.Score.SongPos(i)
|
pos := v.d.Song.Score.SongPos(i)
|
||||||
for _, track := range v.d.Song.Score.Tracks {
|
for _, track := range v.d.Song.Score.Tracks {
|
||||||
track.SetNote(pos, 1)
|
track.SetNote(pos, 1, v.uniquePatterns)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -730,7 +730,7 @@ func (v *NoteRows) unmarshal(data []byte) (from, to int, err error) {
|
|||||||
for j, note := range arr {
|
for j, note := range arr {
|
||||||
y := j + from
|
y := j + from
|
||||||
pos := v.d.Song.Score.SongPos(y)
|
pos := v.d.Song.Score.SongPos(y)
|
||||||
v.d.Song.Score.Tracks[i].SetNote(pos, note)
|
v.d.Song.Score.Tracks[i].SetNote(pos, note, v.uniquePatterns)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -54,13 +54,14 @@ type (
|
|||||||
changeSeverity ChangeSeverity
|
changeSeverity ChangeSeverity
|
||||||
changeType ChangeType
|
changeType ChangeType
|
||||||
|
|
||||||
panic bool
|
panic bool
|
||||||
recording bool
|
recording bool
|
||||||
playing bool
|
playing bool
|
||||||
playPosition sointu.SongPos
|
playPosition sointu.SongPos
|
||||||
loop Loop
|
loop Loop
|
||||||
follow bool
|
follow bool
|
||||||
quitted bool
|
quitted bool
|
||||||
|
uniquePatterns bool
|
||||||
|
|
||||||
cachePatternUseCount [][]int
|
cachePatternUseCount [][]int
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ func (s *modelFuzzState) Iterate(yield func(string, func(p string, t *testing.T)
|
|||||||
s.IterateBool("Effect", s.model.Effect().Bool(), yield, seed)
|
s.IterateBool("Effect", s.model.Effect().Bool(), yield, seed)
|
||||||
s.IterateBool("CommentExpanded", s.model.CommentExpanded().Bool(), yield, seed)
|
s.IterateBool("CommentExpanded", s.model.CommentExpanded().Bool(), yield, seed)
|
||||||
s.IterateBool("Follow", s.model.Follow().Bool(), yield, seed)
|
s.IterateBool("Follow", s.model.Follow().Bool(), yield, seed)
|
||||||
|
s.IterateBool("UniquePatterns", s.model.UniquePatterns().Bool(), yield, seed)
|
||||||
// Strings
|
// Strings
|
||||||
s.IterateString("FilePath", s.model.FilePath().String(), yield, seed)
|
s.IterateString("FilePath", s.model.FilePath().String(), yield, seed)
|
||||||
s.IterateString("InstrumentName", s.model.InstrumentName().String(), yield, seed)
|
s.IterateString("InstrumentName", s.model.InstrumentName().String(), yield, seed)
|
||||||
|
@ -481,7 +481,7 @@ func (v *Notes) add(rect Rect, delta int) (ok bool) {
|
|||||||
newVal = 255
|
newVal = 255
|
||||||
}
|
}
|
||||||
// only do all sets after all gets, so we don't accidentally adjust single note multiple times
|
// only do all sets after all gets, so we don't accidentally adjust single note multiple times
|
||||||
defer v.d.Song.Score.Tracks[x].SetNote(pos, byte(newVal))
|
defer v.d.Song.Score.Tracks[x].SetNote(pos, byte(newVal), v.uniquePatterns)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -540,7 +540,7 @@ func (v *Notes) unmarshalAtCursor(data []byte) bool {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pos := v.d.Song.Score.SongPos(y)
|
pos := v.d.Song.Score.SongPos(y)
|
||||||
v.d.Song.Score.Tracks[x].SetNote(pos, q)
|
v.d.Song.Score.Tracks[x].SetNote(pos, q, v.uniquePatterns)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -562,7 +562,7 @@ func (v *Notes) unmarshalRange(rect Rect, data []byte) bool {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pos := v.d.Song.Score.SongPos(y)
|
pos := v.d.Song.Score.SongPos(y)
|
||||||
v.d.Song.Score.Tracks[x].SetNote(pos, a)
|
v.d.Song.Score.Tracks[x].SetNote(pos, a, v.uniquePatterns)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -609,7 +609,7 @@ func (m *Notes) SetValue(p Point, val byte) {
|
|||||||
}
|
}
|
||||||
track := &(m.d.Song.Score.Tracks[p.X])
|
track := &(m.d.Song.Score.Tracks[p.X])
|
||||||
pos := m.d.Song.Score.SongPos(p.Y)
|
pos := m.d.Song.Score.SongPos(p.Y)
|
||||||
(*track).SetNote(pos, val)
|
(*track).SetNote(pos, val, m.uniquePatterns)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Notes) FillNibble(value byte, lowNibble bool) {
|
func (v *Notes) FillNibble(value byte, lowNibble bool) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user