diff --git a/go4k/algorithms.go b/go4k/algorithms.go new file mode 100644 index 0000000..0051fb9 --- /dev/null +++ b/go4k/algorithms.go @@ -0,0 +1,133 @@ +package go4k + +// FindSuperIntArray finds a small super array containing all +// the subarrays passed to it. Returns the super array and indices where +// the subarrays can be found. For example: +// FindSuperIntArray([][]int{{4,5,6},{1,2,3},{3,4}}) +// returns {1,2,3,4,5,6},{3,0,2} +// Implemented using a greedy search, so does not necessarily find +// the true optimal (the problem is NP-hard and analogous to traveling +// salesman problem). +// +// Used to construct a small delay time table without unnecessary repetition +// of delay times. +func FindSuperIntArray(arrays [][]int) ([]int, []int) { + // If we go past MAX_MERGES, the algorithm could get slow and hang the computer + // So this is a safety limit: after this problem size, just merge any arrays + // until we get into more manageable range + const maxMerges = 1000 + min := func(a int, b int) int { + if a < b { + return a + } + return b + } + overlap := func(a []int, b []int) (int, int) { + minShift := len(a) + for shift := len(a) - 1; shift >= 0; shift-- { + overlapping := true + for k := shift; k < min(len(a), len(b)+shift); k++ { + if a[k] != b[k-shift] { + overlapping = false + break + } + } + if overlapping { + minShift = shift + } + } + overlap := min(len(a)-minShift, len(b)) + return overlap, minShift + } + sliceNumbers := make([]int, len(arrays)) + startIndices := make([]int, len(arrays)) + var processedArrays [][]int + for i := range arrays { + if len(arrays[i]) == 0 { + // Zero length arrays do not need to be processed at all + // They will 'start' at index 0 always as they have no length. + sliceNumbers[i] = -1 + } else { + sliceNumbers[i] = len(processedArrays) + processedArrays = append(processedArrays, arrays[i]) + } + } + if len(processedArrays) == 0 { + return []int{}, startIndices // no arrays with len>0 to process, just return empty array and all indices as 0 + } + for len(processedArrays) > 1 { // there's at least two candidates that could be be merged + maxO, maxI, maxJ, maxS := -1, -1, -1, -1 + if len(processedArrays) < maxMerges { + // find the pair i,j that results in the largest overlap with array i coming first, followed by potentially overlapping array j + for i := range processedArrays { + for j := range processedArrays { + if i == j { + continue + } + overlap, shift := overlap(processedArrays[i], processedArrays[j]) + if overlap > maxO { + maxI, maxJ, maxO, maxS = i, j, overlap, shift + } + } + } + } else { + // The task is daunting, we have over MAX_MERGES overlaps to test. Just merge two first ones until the task is more manageable size + overlap, shift := overlap(processedArrays[0], processedArrays[1]) + maxI, maxJ, maxO, maxS = 0, 1, overlap, shift + } + for k := range sliceNumbers { + if sliceNumbers[k] == maxJ { + // update slice pointers to point maxI instead of maxJ (maxJ will be appended to maxI, taking overlap into account) + sliceNumbers[k] = maxI + startIndices[k] += maxS // the array j starts at index maxS in array i + } + if sliceNumbers[k] > maxJ { + // pointers maxJ reduced by 1 as maxJ will be deleted + sliceNumbers[k]-- + } + } + // if array j was not entirely included within array j + if maxO < len(processedArrays[maxJ]) { + // append array maxJ to array maxI, without duplicating the overlapping part + processedArrays[maxI] = append(processedArrays[maxI], processedArrays[maxJ][maxO:]...) + } + // finally, remove element maxJ from processedArrays + processedArrays = append(processedArrays[:maxJ], processedArrays[maxJ+1:]...) + } + return processedArrays[0], startIndices // there should be only one slice left in the arrays after the loop +} + +// ConstructDelayTimeTable tries to construct the delay times table +// abusing overlapping between different delay times tables as much +// as possible. Especially: if two delay units use exactly the same +// delay times, they appear in the table only once. +func ConstructDelayTimeTable(patch Patch) ([]int, [][]int) { + ind := make([][]int, len(patch)) + var subarrays [][]int + // flatten the delay times into one array of arrays + // saving the indices where they were placed + for i, instr := range patch { + ind[i] = make([]int, len(instr.Units)) + for j, unit := range instr.Units { + // only include delay times for delays. Only delays + // should use delay times + if unit.Type == "delay" { + ind[i][j] = len(subarrays) + subarrays = append(subarrays, unit.DelayTimes) + } + } + } + delayTable, indices := FindSuperIntArray(subarrays) + // cancel the flattening, so unitindices can be used to + // to find the index of each delay in the delay table + unitindices := make([][]int, len(patch)) + for i, instr := range patch { + unitindices[i] = make([]int, len(instr.Units)) + for j, unit := range instr.Units { + if unit.Type == "delay" { + unitindices[i][j] = indices[ind[i][j]] + } + } + } + return delayTable, unitindices +} diff --git a/go4k/algorithms_test.go b/go4k/algorithms_test.go new file mode 100644 index 0000000..a0a0730 --- /dev/null +++ b/go4k/algorithms_test.go @@ -0,0 +1,36 @@ +package go4k_test + +import ( + "fmt" + "reflect" + "testing" + + "github.com/vsariola/sointu/go4k" +) + +func TestFindSuperIntArray(t *testing.T) { + var tests = []struct { + input [][]int + wantSuper []int + wantIndices []int + }{ + {[][]int{}, []int{}, []int{}}, + {[][]int{nil, nil}, []int{}, []int{0, 0}}, + {[][]int{{3, 4, 5}, {1, 2, 3}}, []int{1, 2, 3, 4, 5}, []int{2, 0}}, + {[][]int{{3, 4, 5}, {1, 2, 3}, nil}, []int{1, 2, 3, 4, 5}, []int{2, 0, 0}}, + {[][]int{{3, 4, 5}, {1, 2, 3}, {}}, []int{1, 2, 3, 4, 5}, []int{2, 0, 0}}, + {[][]int{{3, 4, 5}, {1, 2, 3}, {2, 3}}, []int{1, 2, 3, 4, 5}, []int{2, 0, 1}}, + {[][]int{{1, 2, 3, 4, 5}, {1, 2, 3}}, []int{1, 2, 3, 4, 5}, []int{0, 0}}, + {[][]int{{1, 2, 3, 4, 5}, {2, 3}}, []int{1, 2, 3, 4, 5}, []int{0, 1}}, + {[][]int{{1, 2, 3, 4, 5}, {2, 3}, {5, 6, 7}}, []int{1, 2, 3, 4, 5, 6, 7}, []int{0, 1, 4}}, + {[][]int{{1, 2, 3, 4}, {3, 4, 1}, {2, 3, 4, 5}}, []int{3, 4, 1, 2, 3, 4, 5}, []int{2, 0, 3}}, + } + for i, tt := range tests { + t.Run(fmt.Sprintf("TestFindSuperIntArray %d", i), func(t *testing.T) { + super, indices := go4k.FindSuperIntArray(tt.input) + if !reflect.DeepEqual(super, tt.wantSuper) || !reflect.DeepEqual(indices, tt.wantIndices) { + t.Errorf("FindSuperIntArray(%v) got (%v,%v), want (%v,%v)", tt.input, super, indices, tt.wantSuper, tt.wantIndices) + } + }) + } +} diff --git a/go4k/bridge/bridge.go b/go4k/bridge/bridge.go index 74f85ca..32ab8db 100644 --- a/go4k/bridge/bridge.go +++ b/go4k/bridge/bridge.go @@ -82,11 +82,14 @@ func (synth *C.Synth) Render(buffer []float32, maxtime int) (int, int, error) { func Synth(patch go4k.Patch) (*C.Synth, error) { s := new(C.Synth) sampleno := 0 - delaytimeno := 0 totalVoices := 0 commands := make([]byte, 0) values := make([]byte, 0) polyphonyBitmask := 0 + delayTable, delayIndices := go4k.ConstructDelayTimeTable(patch) + for i, v := range delayTable { + s.DelayTimes[i] = C.ushort(v) + } for insid, instr := range patch { if len(instr.Units) > 63 { return nil, errors.New("An instrument can have a maximum of 63 units") @@ -106,11 +109,7 @@ func Synth(patch go4k.Patch) (*C.Synth, error) { if unit.Stereo && len(unit.DelayTimes)%2 != 0 { return nil, errors.New("Stereo delays should have even number of delaytimes") } - values = append(values, byte(delaytimeno)) - for _, v := range unit.DelayTimes { - s.DelayTimes[delaytimeno] = C.ushort(v) - delaytimeno++ - } + values = append(values, byte(delayIndices[insid][unitid])) count := len(unit.DelayTimes) if unit.Stereo { count /= 2