From a2723829da813c4a659bcf7b4e85299d2cb635c1 Mon Sep 17 00:00:00 2001 From: vsariola <5684185+vsariola@users.noreply.github.com> Date: Wed, 12 May 2021 11:31:38 +0300 Subject: [PATCH] refactor: implement Order and Pattern types: slices returning default values for out of bound indices --- order.go | 21 +++++++++++++++++++ pattern.go | 21 +++++++++++++++++++ track.go | 10 ++++----- tracker/defaultsong.go | 2 +- tracker/gioui/trackeditor.go | 10 ++------- tracker/model.go | 35 +++++++------------------------ vm/compiler/bridge/bridge_test.go | 2 +- vm/patterns.go | 2 +- vm/patterns_test.go | 16 +++++++------- 9 files changed, 68 insertions(+), 51 deletions(-) create mode 100644 order.go create mode 100644 pattern.go diff --git a/order.go b/order.go new file mode 100644 index 0000000..8abaab2 --- /dev/null +++ b/order.go @@ -0,0 +1,21 @@ +package sointu + +// Order is the pattern order for a track, in practice just a slice of integers, +// but provides convenience functions that return -1 values for indices out of +// bounds of the array, and functions to increase the size of the slice only by +// necessary amount when a new item is added, filling the unused slots with -1s. +type Order []int + +func (s Order) Get(index int) int { + if index < 0 || index >= len(s) { + return -1 + } + return s[index] +} + +func (s *Order) Set(index, value int) { + for len(*s) <= index { + *s = append(*s, -1) + } + (*s)[index] = value +} diff --git a/pattern.go b/pattern.go new file mode 100644 index 0000000..80cdc8f --- /dev/null +++ b/pattern.go @@ -0,0 +1,21 @@ +package sointu + +// Pattern represents a single pattern of note, in practice just a slice of bytes, +// but provides convenience functions that return 1 values (hold) for indices out of +// bounds of the array, and functions to increase the size of the slice only by +// necessary amount when a new item is added, filling the unused slots with 1s. +type Pattern []byte + +func (s Pattern) Get(index int) byte { + if index < 0 || index >= len(s) { + return 1 + } + return s[index] +} + +func (s *Pattern) Set(index int, value byte) { + for len(*s) <= index { + *s = append(*s, 1) + } + (*s)[index] = value +} diff --git a/track.go b/track.go index 7e212f2..b7ff523 100644 --- a/track.go +++ b/track.go @@ -2,17 +2,17 @@ package sointu type Track struct { NumVoices int - Effect bool `yaml:",omitempty"` - Order []int `yaml:",flow"` - Patterns [][]byte `yaml:",flow"` + Effect bool `yaml:",omitempty"` + Order Order `yaml:",flow"` + Patterns []Pattern `yaml:",flow"` } func (t *Track) Copy() Track { order := make([]int, len(t.Order)) copy(order, t.Order) - patterns := make([][]byte, len(t.Patterns)) + patterns := make([]Pattern, len(t.Patterns)) for i, oldPat := range t.Patterns { - newPat := make([]byte, len(oldPat)) + newPat := make(Pattern, len(oldPat)) copy(newPat, oldPat) patterns[i] = newPat } diff --git a/tracker/defaultsong.go b/tracker/defaultsong.go index ef94f55..130e00d 100644 --- a/tracker/defaultsong.go +++ b/tracker/defaultsong.go @@ -63,7 +63,7 @@ var defaultSong = sointu.Song{ RowsPerPattern: 16, Length: 1, Tracks: []sointu.Track{ - {NumVoices: 1, Order: []int{0}, Patterns: [][]byte{{72, 0}}}, + {NumVoices: 1, Order: sointu.Order{0}, Patterns: []sointu.Pattern{{72, 0}}}, }, }, Patch: sointu.Patch{defaultInstrument, diff --git a/tracker/gioui/trackeditor.go b/tracker/gioui/trackeditor.go index 3e5bfc6..71e1498 100644 --- a/tracker/gioui/trackeditor.go +++ b/tracker/gioui/trackeditor.go @@ -418,10 +418,7 @@ func (te *TrackEditor) layoutTracks(gtx C, t *Tracker) D { for row := firstRow; row <= lastRow; row++ { pat := row / t.Song().Score.RowsPerPattern patRow := row % t.Song().Score.RowsPerPattern - s := -1 - if pat >= 0 && pat < len(trk.Order) { - s = trk.Order[pat] - } + s := trk.Order.Get(pat) if s < 0 { op.Offset(f32.Pt(0, trackRowHeight)).Add(gtx.Ops) continue @@ -442,10 +439,7 @@ func (te *TrackEditor) layoutTracks(gtx C, t *Tracker) D { } var c byte = 1 if s >= 0 && s < len(trk.Patterns) { - pattern := trk.Patterns[s] - if patRow >= 0 && patRow < len(pattern) { - c = pattern[patRow] - } + c = trk.Patterns[s].Get(patRow) } if trk.Effect { var text string diff --git a/tracker/model.go b/tracker/model.go index 686a824..2335b44 100644 --- a/tracker/model.go +++ b/tracker/model.go @@ -212,7 +212,7 @@ func (m *Model) AddTrack(after bool) { copy(newTracks[m.cursor.Track+1:], m.song.Score.Tracks[m.cursor.Track:]) newTracks[m.cursor.Track] = sointu.Track{ NumVoices: 1, - Patterns: [][]byte{}, + Patterns: []sointu.Pattern{}, } m.song.Score.Tracks = newTracks m.clampPositions() @@ -335,18 +335,11 @@ func (m *Model) CanDeleteInstrument() bool { func (m *Model) Note() byte { trk := m.song.Score.Tracks[m.cursor.Track] - if m.cursor.Pattern < 0 || m.cursor.Pattern >= len(trk.Order) { + pat := trk.Order.Get(m.cursor.Pattern) + if pat < 0 || pat >= len(trk.Patterns) { return 1 } - p := trk.Order[m.cursor.Pattern] - if p < 0 || p >= len(trk.Patterns) { - return 1 - } - pat := trk.Patterns[p] - if m.cursor.Row < 0 || m.cursor.Row >= len(pat) { - return 1 - } - return pat[m.cursor.Row] + return trk.Patterns[pat].Get(m.cursor.Row) } // SetCurrentNote sets the (note) value in current pattern under cursor to iv @@ -356,11 +349,7 @@ func (m *Model) SetNote(iv byte) { if m.cursor.Pattern < 0 || m.cursor.Row < 0 { return } - for len(tracks[m.cursor.Track].Order) <= m.cursor.Pattern { - tracks[m.cursor.Track].Order = append(tracks[m.cursor.Track].Order, -1) - } - order := tracks[m.cursor.Track].Order - patIndex := order[m.cursor.Pattern] + patIndex := tracks[m.cursor.Track].Order.Get(m.cursor.Pattern) if patIndex < 0 { patIndex = len(tracks[m.cursor.Track].Patterns) for _, pi := range tracks[m.cursor.Track].Order { @@ -368,26 +357,18 @@ func (m *Model) SetNote(iv byte) { patIndex = pi + 1 // we find a pattern that is not in the pattern table nor in the order list i.e. completely new pattern } } - tracks[m.cursor.Track].Order[m.cursor.Pattern] = patIndex + tracks[m.cursor.Track].Order.Set(m.cursor.Pattern, patIndex) } for len(tracks[m.cursor.Track].Patterns) <= patIndex { tracks[m.cursor.Track].Patterns = append(tracks[m.cursor.Track].Patterns, nil) } - patterns := tracks[m.cursor.Track].Patterns - for len(patterns[patIndex]) <= m.cursor.Row { - patterns[patIndex] = append(patterns[patIndex], 1) - } - patterns[patIndex][m.cursor.Row] = iv + tracks[m.cursor.Track].Patterns[patIndex].Set(m.cursor.Row, iv) m.notifyScoreChange() } func (m *Model) SetCurrentPattern(pat int) { m.saveUndo("SetCurrentPattern", 0) - track := &m.song.Score.Tracks[m.cursor.Track] - for len(track.Order) <= m.cursor.Pattern { - track.Order = append(track.Order, -1) - } - track.Order[m.cursor.Pattern] = pat + m.song.Score.Tracks[m.cursor.Track].Order.Set(m.cursor.Pattern, pat) m.computePatternUseCounts() m.notifyScoreChange() } diff --git a/vm/compiler/bridge/bridge_test.go b/vm/compiler/bridge/bridge_test.go index af01ddc..8fef881 100644 --- a/vm/compiler/bridge/bridge_test.go +++ b/vm/compiler/bridge/bridge_test.go @@ -38,7 +38,7 @@ func TestOscillatSine(t *testing.T) { sointu.Unit{Type: "mulp", Parameters: map[string]int{"stereo": 0}}, sointu.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}}, }}} - tracks := []sointu.Track{{NumVoices: 1, Order: []int{0}, Patterns: [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}}}} + tracks := []sointu.Track{{NumVoices: 1, Order: []int{0}, Patterns: []sointu.Pattern{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}}}} song := sointu.Song{BPM: 100, RowsPerBeat: 4, Score: sointu.Score{RowsPerPattern: 16, Length: 1, Tracks: tracks}, Patch: patch} synth, err := bridge.Synth(patch) if err != nil { diff --git a/vm/patterns.go b/vm/patterns.go index 9b8759b..98d3185 100644 --- a/vm/patterns.go +++ b/vm/patterns.go @@ -10,7 +10,7 @@ import ( // fixPatternLength makes sure that every pattern is the same length. During // composing. Patterns shorter than the given length are padded with 1 / "hold"; // patterns longer than the given length are cropped. -func fixPatternLength(patterns [][]byte, fixedLength int) [][]int { +func fixPatternLength(patterns []sointu.Pattern, fixedLength int) [][]int { patternData := make([]int, len(patterns)*fixedLength) ret := make([][]int, len(patterns)) for i, pat := range patterns { diff --git a/vm/patterns_test.go b/vm/patterns_test.go index 931a06f..e59223d 100644 --- a/vm/patterns_test.go +++ b/vm/patterns_test.go @@ -14,11 +14,11 @@ func TestPatternReusing(t *testing.T) { Length: 2, RowsPerPattern: 8, Tracks: []sointu.Track{{ - Patterns: [][]byte{{64, 1, 1, 1, 0, 0, 0, 0}, {72, 0, 0, 0, 0, 0, 0, 0}}, - Order: []int{0, 1}, + Patterns: []sointu.Pattern{{64, 1, 1, 1, 0, 0, 0, 0}, {72, 0, 0, 0, 0, 0, 0, 0}}, + Order: sointu.Order{0, 1}, }, { - Patterns: [][]byte{{64, 1, 1, 1, 0, 0, 0, 0}, {84, 0, 0, 0, 0, 0, 0, 0}}, - Order: []int{0, 1}, + Patterns: []sointu.Pattern{{64, 1, 1, 1, 0, 0, 0, 0}, {84, 0, 0, 0, 0, 0, 0, 0}}, + Order: sointu.Order{0, 1}, }}, }, } @@ -42,10 +42,10 @@ func TestUnnecessaryHolds(t *testing.T) { Length: 2, RowsPerPattern: 8, Tracks: []sointu.Track{{ - Patterns: [][]byte{{64, 1, 1, 1, 0, 1, 0, 0}, {72, 0, 1, 0, 1, 0, 0, 0}}, + Patterns: []sointu.Pattern{{64, 1, 1, 1, 0, 1, 0, 0}, {72, 0, 1, 0, 1, 0, 0, 0}}, Order: []int{0, 1}, }, { - Patterns: [][]byte{{64, 1, 1, 1, 0, 0, 1, 0}, {84, 0, 0, 0, 1, 1, 0, 0}}, + Patterns: []sointu.Pattern{{64, 1, 1, 1, 0, 0, 1, 0}, {84, 0, 0, 0, 1, 1, 0, 0}}, Order: []int{0, 1}, }}}, } @@ -69,10 +69,10 @@ func TestDontCares(t *testing.T) { Length: 2, RowsPerPattern: 8, Tracks: []sointu.Track{{ - Patterns: [][]byte{{64, 1, 1, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}}, + Patterns: []sointu.Pattern{{64, 1, 1, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}}, Order: []int{0, 1}, }, { - Patterns: [][]byte{{64, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 0, 0, 0, 0, 0}}, + Patterns: []sointu.Pattern{{64, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 0, 0, 0, 0, 0}}, Order: []int{0, 1}, }}, },