1/* 2mini.test.mjs - <short description TODO> 3Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/mini/test/mini.test.mjs> 4This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. 5*/ 6 7import { getLeafLocation, getLeafLocations, mini, mini2ast } from '../mini.mjs'; 8import '@strudel/core/euclid.mjs'; 9import { Fraction } from '@strudel/core/index.mjs'; 10import { describe, expect, it } from 'vitest'; 11 12describe('mini', () => { 13 const minV = (v) => mini(v).sortHapsByPart().firstCycleValues; 14 const minS = (v) => mini(v).sortHapsByPart().showFirstCycle; 15 it('supports single elements', () => { 16 expect(minV('a')).toEqual(['a']); 17 }); 18 it('supports rest', () => { 19 expect(minV('~')).toEqual([]); 20 }); 21 it('supports cat', () => { 22 expect(minS('a b')).toEqual(['a: 0 - 1/2', 'b: 1/2 - 1']); 23 expect(minS('a b c')).toEqual(['a: 0 - 1/3', 'b: 1/3 - 2/3', 'c: 2/3 - 1']); 24 }); 25 it('supports fast', () => { 26 expect(minS('a*3 b')).toEqual(minS('[a a a] b')); 27 }); 28 it('supports patterned fast', () => { 29 expect(minS('[a*<3 5>]*2')).toEqual(minS('[a a a] [a a a a a]')); 30 }); 31 it('supports slow', () => { 32 expect(minS('[a a a]/3 b')).toEqual(minS('a b')); 33 }); 34 it('supports patterned slow', () => { 35 expect(minS('[a a a a a a a a]/[2 4]')).toEqual(minS('[a a] a')); 36 }); 37 it('supports patterned fast', () => { 38 expect(minS('[a*<3 5>]*2')).toEqual(minS('[a a a] [a a a a a]')); 39 }); 40 it('supports slowcat', () => { 41 expect(minV('<a b>')).toEqual(['a']); 42 }); 43 it('supports division', () => { 44 expect(minS('a/2')).toEqual(['a: 0 - 2']); 45 expect(minS('[c3 d3]/2')).toEqual(['c3: 0 - 1']); 46 }); 47 it('supports multiplication', () => { 48 expect(minS('c3*2')).toEqual(['c3: 0 - 1/2', 'c3: 1/2 - 1']); 49 expect(minV('[c3 d3]*2')).toEqual(['c3', 'd3', 'c3', 'd3']); 50 }); 51 it('supports brackets', () => { 52 expect(minS('c3 [d3 e3]')).toEqual(['c3: 0 - 1/2', 'd3: 1/2 - 3/4', 'e3: 3/4 - 1']); 53 expect(minS('c3 [d3 [e3 f3]]')).toEqual(['c3: 0 - 1/2', 'd3: 1/2 - 3/4', 'e3: 3/4 - 7/8', 'f3: 7/8 - 1']); 54 }); 55 it('supports curly brackets', () => { 56 expect(minS('{a b, c d e}*3')).toEqual(minS('[a b a b a b, c d e c d e]')); 57 expect(minS('{a b, c [d e] f}*3')).toEqual(minS('[a b a b a b, c [d e] f c [d e] f]')); 58 expect(minS('{a b c, d e}*2')).toEqual(minS('[a b c a b c, d e d e d e]')); 59 }); 60 it('supports curly brackets with explicit step-per-cycle', () => { 61 expect(minS('{a b, c d e}%3')).toEqual(minS('[a b a, c d e]')); 62 expect(minS('{a b, c d e}%5')).toEqual(minS('[a b a b a, c d e c d]')); 63 expect(minS('{a b, c d e}%6')).toEqual(minS('[a b a b a b, c d e c d e]')); 64 }); 65 it('supports commas', () => { 66 expect(minS('c3,e3,g3')).toEqual(['c3: 0 - 1', 'e3: 0 - 1', 'g3: 0 - 1']); 67 expect(minS('[c3,e3,g3] f3')).toEqual(['c3: 0 - 1/2', 'e3: 0 - 1/2', 'g3: 0 - 1/2', 'f3: 1/2 - 1']); 68 }); 69 it('supports elongation', () => { 70 expect(minS('a@3 b')).toEqual(['a: 0 - 3/4', 'b: 3/4 - 1']); 71 expect(minS('a@2 b@3')).toEqual(['a: 0 - 2/5', 'b: 2/5 - 1']); 72 }); 73 it('supports replication', () => { 74 expect(minS('a!3 b')).toEqual(['a: 0 - 1/4', 'a: 1/4 - 1/2', 'a: 1/2 - 3/4', 'b: 3/4 - 1']); 75 expect(minS('[<a b c>]!3 d')).toEqual(minS('<a b c> <a b c> <a b c> d')); 76 }); 77 it('supports replication via repeated !', () => { 78 expect(minS('a ! ! b')).toEqual(['a: 0 - 1/4', 'a: 1/4 - 1/2', 'a: 1/2 - 3/4', 'b: 3/4 - 1']); 79 expect(minS('[<a b c>]!! d')).toEqual(minS('<a b c> <a b c> <a b c> d')); 80 }); 81 it('supports euclidean rhythms', () => { 82 expect(minS('a(3, 8)')).toEqual(['a: 0 - 1/8', 'a: 3/8 - 1/2', 'a: 3/4 - 7/8']); 83 }); 84 it('supports patterning euclidean rhythms', () => { 85 expect(minS('[a(<3 5>, <8 16>)]*2')).toEqual(minS('a(3,8) a(5,16)')); 86 }); 87 it("reproduces Toussaint's example euclidean algorithms", () => { 88 const checkEuclid = function (spec, target) { 89 expect(minS(`x(${spec[0]},${spec[1]})`)).toEqual(minS(target)); 90 }; 91 checkEuclid([1, 2], 'x ~'); 92 checkEuclid([1, 3], 'x ~ ~'); 93 checkEuclid([1, 4], 'x ~ ~ ~'); 94 checkEuclid([4, 12], 'x ~ ~ x ~ ~ x ~ ~ x ~ ~'); 95 checkEuclid([2, 5], 'x ~ x ~ ~'); 96 // checkEuclid([3, 4], "x ~ x x"); // Toussaint is wrong.. 97 checkEuclid([3, 4], 'x x x ~'); // correction 98 checkEuclid([3, 5], 'x ~ x ~ x'); 99 checkEuclid([3, 7], 'x ~ x ~ x ~ ~'); 100 checkEuclid([3, 8], 'x ~ ~ x ~ ~ x ~'); 101 checkEuclid([4, 7], 'x ~ x ~ x ~ x'); 102 checkEuclid([4, 9], 'x ~ x ~ x ~ x ~ ~'); 103 checkEuclid([4, 11], 'x ~ ~ x ~ ~ x ~ ~ x ~'); 104 // checkEuclid([5, 6], "x ~ x x x x"); // Toussaint is wrong.. 105 checkEuclid([5, 6], 'x x x x x ~'); // correction 106 checkEuclid([5, 7], 'x ~ x x ~ x x'); 107 checkEuclid([5, 8], 'x ~ x x ~ x x ~'); 108 checkEuclid([5, 9], 'x ~ x ~ x ~ x ~ x'); 109 checkEuclid([5, 11], 'x ~ x ~ x ~ x ~ x ~ ~'); 110 checkEuclid([5, 12], 'x ~ ~ x ~ x ~ ~ x ~ x ~'); 111 // checkEuclid([5, 16], "x ~ ~ x ~ ~ x ~ ~ x ~ ~ x ~ ~ ~ ~"); // Toussaint is wrong.. 112 checkEuclid([5, 16], 'x ~ ~ x ~ ~ x ~ ~ x ~ ~ x ~ ~ ~'); // correction 113 // checkEuclid([7, 8], "x ~ x x x x x x"); // Toussaint is wrong.. 114 checkEuclid([7, 8], 'x x x x x x x ~'); // Correction 115 checkEuclid([7, 12], 'x ~ x x ~ x ~ x x ~ x ~'); 116 checkEuclid([7, 16], 'x ~ ~ x ~ x ~ x ~ ~ x ~ x ~ x ~'); 117 checkEuclid([9, 16], 'x ~ x x ~ x ~ x ~ x x ~ x ~ x ~'); 118 checkEuclid([11, 24], 'x ~ ~ x ~ x ~ x ~ x ~ x ~ ~ x ~ x ~ x ~ x ~ x ~'); 119 checkEuclid([13, 24], 'x ~ x x ~ x ~ x ~ x ~ x ~ x x ~ x ~ x ~ x ~ x ~'); 120 }); 121 it('supports the - alias for ~', () => { 122 expect(minS('a - b [- c]')).toEqual(minS('a ~ b [~ c]')); 123 }); 124 it('supports the ? operator', () => { 125 expect( 126 mini('a?') 127 .queryArc(0, 20) 128 .map((hap) => hap.whole.begin), 129 ).toEqual( 130 mini('a') 131 .degradeBy(0.5) 132 .queryArc(0, 20) 133 .map((hap) => hap.whole.begin), 134 ); 135 }); 136 // testing things that involve pseudo-randomness, so there's a probability we could fail by chance. 137 // these next few tests work with the current PRNG, and are intended to succeed with p > 0.99 even if the PRNG changes 138 // (as long as the PRNG has a relatively-uniform distribution of values) 139 it('supports degradeBy with default of 50%', () => { 140 const haps = mini('a?').queryArc(0, 1000); 141 expect(459 <= haps.length && haps.length <= 541).toBe(true); 142 // 'Number of elements did not fall in 99% confidence interval for binomial with p=0.5', 143 }); 144 it('supports degradeBy with an argument', () => { 145 const haps = mini('a?0.8').queryArc(0, 1000); 146 expect(haps.length > 0).toBe(true); 147 // 'Should have had at least one element when degradeBy was set at 0.8'); 148 expect(haps.length < 230).toBe(true); 149 // 'Had too many cycles remaining after degradeBy 0.8'); 150 }); 151 it('supports multiple independent uses of the random choice operator ("|")', () => { 152 const numCycles = 1000; 153 const values = mini('[a|b] [a|b]') 154 .queryArc(0, numCycles) 155 .map((e) => e.value); 156 const observed = { aa: 0, ab: 0, ba: 0, bb: 0 }; 157 for (let i = 0; i < values.length; i += 2) { 158 const chunk = values.slice(i, i + 2); 159 observed[chunk.join('')]++; 160 } 161 for (const count of Object.values(observed)) { 162 // Should fall within 99% confidence interval for binomial with p=0.25. 163 expect(215 <= count && count <= 286).toBe(true); 164 } 165 }); 166 it('supports the random choice operator ("|") with nesting', () => { 167 const numCycles = 900; 168 const haps = mini('a | [b | c] | [d | e | f]').queryArc(0, numCycles); 169 // Should have about 1/3 a, 1/6 each of b | c, and 1/9 each of d | e | f. 170 // Evaluating this distribution with a chi-squared test. 171 // Note: this just evaluates the overall distribution, not things like correlation/runs of values 172 const observed = haps.reduce((acc, hap) => { 173 acc[hap.value] = (acc[hap.value] || 0) + 1; 174 return acc; 175 }, {}); 176 const expected = { 177 a: numCycles / 3, 178 b: numCycles / 6, 179 c: numCycles / 6, 180 d: numCycles / 9, 181 e: numCycles / 9, 182 f: numCycles / 9, 183 }; 184 let chisq = -numCycles; 185 for (let k in expected) { 186 chisq += (observed[k] * observed[k]) / expected[k]; 187 } 188 // 15.086 is the chisq for 5 degrees of freedom at 99%, so for 99% of uniformly-distributed 189 // PRNG, this test should succeed 190 expect(chisq <= 15.086).toBe(true); 191 }); 192 it('supports lists', () => { 193 expect(minV('a:b c:d:[e:f] g')).toEqual([['a', 'b'], ['c', 'd', ['e', 'f']], 'g']); 194 }); 195 it('supports ranges', () => { 196 expect(minV('0 .. 4')).toEqual([0, 1, 2, 3, 4]); 197 }); 198 it('supports patterned ranges', () => { 199 expect(minS('[<0 1> .. <2 4>]*2')).toEqual(minS('[0 1 2] [1 2 3 4]')); 200 }); 201 it('supports the . operator', () => { 202 expect(minS('a . b c')).toEqual(minS('a [b c]')); 203 expect(minS('a . b c . [d e f . g h]')).toEqual(minS('a [b c] [[d e f] [g h]]')); 204 }); 205 it('supports the _ operator', () => { 206 expect(minS('a _ b _ _')).toEqual(minS('a@2 b@3')); 207 }); 208 it('_ and @ are almost interchangeable', () => { 209 expect(minS('a @ b @ @')).toEqual(minS('a _2 b _3')); 210 }); 211 it('supports ^ step marking', () => { 212 expect(mini('a [^b c]')._steps).toEqual(Fraction(4)); 213 expect(mini('[^b c]!3')._steps).toEqual(Fraction(6)); 214 expect(mini('[a b c] [d [e f]]')._steps).toEqual(Fraction(2)); 215 expect(mini('^[a b c] [d [e f]]')._steps).toEqual(Fraction(2)); 216 expect(mini('[a b c] [d [^e f]]')._steps).toEqual(Fraction(8)); 217 expect(mini('[a b c] [^d [e f]]')._steps).toEqual(Fraction(4)); 218 expect(mini('[^a b c] [^d [e f]]')._steps).toEqual(Fraction(12)); 219 expect(mini('[^a b c] [d [^e f]]')._steps).toEqual(Fraction(24)); 220 expect(mini('[^a b c d e]')._steps).toEqual(Fraction(5)); 221 }); 222}); 223 224describe('getLeafLocation', () => { 225 it('gets location of leaf nodes', () => { 226 const code = '"bd sd"'; 227 const ast = mini2ast(code); 228 229 const bd = ast.source_[0].source_; 230 expect(getLeafLocation(code, bd)).toEqual([1, 3]); 231 232 const sd = ast.source_[1].source_; 233 expect(getLeafLocation(code, sd)).toEqual([4, 6]); 234 }); 235}); 236 237describe('getLeafLocations', () => { 238 it('gets locations of leaf nodes', () => { 239 expect(getLeafLocations('"bd sd"')).toEqual([ 240 [1, 3], // bd columns 241 [4, 6], // sd columns 242 ]); 243 expect(getLeafLocations('"bd*2 [sd cp]"')).toEqual([ 244 [1, 3], // bd columns 245 [7, 9], // sd columns 246 [10, 12], // cp columns 247 [4, 5], // "2" columns 248 ]); 249 expect(getLeafLocations('"bd*<2 3>"')).toEqual([ 250 [1, 3], // bd columns 251 [5, 6], // "2" columns 252 [7, 8], // "3" columns 253 ]); 254 }); 255});