char-slop/chordpad.vibed.software
git clone https://git.t4t.associates/char-slop/chordpad.vibed.software
ee3b50a
main
1import { assertEquals } from "@std/assert" ; 2import { 3canonBeat , 4type ChordEvent , 5copyChords , 6insertLeadIn , 7moveTrack , 8newChordTrack , 9newProject , 10pasteChords , 11placeChord , 12} from "./model.ts" ; 13 14const layout = ( chords :ChordEvent []) => chords . map ( c => [ c . symbol , c . start , c . start + c . dur ]); 15 16Deno . test ( 17"a placed chord cuts away what it overlaps, splitting a chord it lands inside" , 18() => { 19const track = newChordTrack (); 20placeChord ( track , { id :"a" , start :0 , dur :4 , symbol :"C" }); 21placeChord ( track , { id :"b" , start :1 , dur :0.25 , symbol :"F" }); 22assertEquals ( layout ( track . chords ), [ 23[ "C" , 0 , 1 ], 24[ "F" , 1 , 1.25 ], 25[ "C" , 1.25 , 4 ], 26]); 27placeChord ( track , { id :"c" , start :0.75 , dur :1 , symbol :"G" }); 28assertEquals ( layout ( track . chords ), [ 29[ "C" , 0 , 0.75 ], 30[ "G" , 0.75 , 1.75 ], 31[ "C" , 1.75 , 4 ], 32]); 33}, 34); 35 36Deno . test ( "chords on a triplet grid meet exactly, without slivers left over" , () => { 37const third = 1 / 3 ; 38const track = newChordTrack (); 39// a chord placed by clicking: snapped straight onto the grid, at exactly 2 40placeChord ( track , { id :"g" , start :6 * third , dur :third , symbol :"G" }); 41// then six typed from the start, the cursor adding up thirds as it goes (to 1.9999999999999998) 42let at = 0 ; 43for ( const symbol of [ "C" , "F" , "G" , "C" , "Am" , "Dm" , "E" ]) { 44placeChord ( track , { id :symbol + at , start :at , dur :third , symbol}); 45at += third ; 46} 47 48assertEquals ( 49track . chords . map ( c => c . symbol ), 50[ "C" , "F" , "G" , "C" , "Am" , "Dm" , "E" ], 51); 52for ( const [ a , b ] of track . chords . slice ( 1 ). map (( c , i ) => [ track . chords [ i ], c ])) { 53assertEquals ( canonBeat ( a . start + a . dur ), b . start ); 54} 55}); 56 57Deno . test ( "a lead-in takes the back half of the chord before, so the target stays put" , () => { 58const track = newChordTrack (); 59placeChord ( track , { id :"a" , start :0 , dur :4 , symbol :"C" }); 60placeChord ( track , { id :"b" , start :4 , dur :4 , symbol :"Am" }); 61assertEquals ( insertLeadIn ( track , "b" , [ "E7" ], 0.5 ), true ); 62assertEquals ( layout ( track . chords ), [ 63[ "C" , 0 , 2 ], 64[ "E7" , 2 , 4 ], 65[ "Am" , 4 , 8 ], 66]); 67// a ii–V shares the space 68assertEquals ( insertLeadIn ( track , "a" , [ "Dm7" , "G7" ], 0.5 ), true ); 69assertEquals ( layout ( track . chords ). slice ( 0 , 3 ), [ 70[ "Dm7" , 0 , 0.5 ], 71[ "G7" , 0.5 , 1 ], 72[ "C" , 1 , 2 ], 73]); 74}); 75 76Deno . test ( "with nothing right before it, a lead-in takes the target's front half" , () => { 77const track = newChordTrack (); 78placeChord ( track , { id :"a" , start :4 , dur :4 , symbol :"Am" }); 79assertEquals ( insertLeadIn ( track , "a" , [ "E7" ], 1 ), true ); 80assertEquals ( layout ( track . chords ), [ 81[ "E7" , 4 , 6 ], 82[ "Am" , 6 , 8 ], 83]); 84// no room left on a whole-bar grid 85assertEquals ( insertLeadIn ( track , "a" , [ "E7" ], 4 ), false ); 86assertEquals ( layout ( track . chords ), [ 87[ "E7" , 4 , 6 ], 88[ "Am" , 6 , 8 ], 89]); 90}); 91 92Deno . test ( "copied chords paste with their spacing, over what's there" , () => { 93const track = newChordTrack (); 94for ( const [ start , dur , symbol ] of [ 95[ 0 , 4 , "C" ], 96[ 4 , 2 , "F" ], 97[ 6 , 2 , "G" ], 98[ 8 , 4 , "Am" ], 99] as const ) { 100placeChord ( track , { id :symbol , start, dur, symbol}); 101} 102// F and Am: not next to each other 103const clip = copyChords ( track , [ "F" , "Am" ]) ! ; 104assertEquals ( clip . span , 8 ); 105const ids = pasteChords ( track , clip , 1 ); 106// the pasted Am (beats 5 to 9) writes over the end of F, all of G, and the start of Am 107assertEquals ( layout ( track . chords ), [ 108[ "C" , 0 , 1 ], 109[ "F" , 1 , 3 ], 110[ "C" , 3 , 4 ], 111[ "F" , 4 , 5 ], 112[ "Am" , 5 , 9 ], 113[ "Am" , 9 , 12 ], 114]); 115assertEquals ( ids . length , 2 ); 116// a hand voicing comes along, and isn't shared with the original 117const other = newChordTrack (); 118placeChord ( other , { id :"G" , start :0 , dur :2 , symbol :"G" , notes :[ 43 , 59 , 62 , 67 ] }); 119const [ g ] = pasteChords ( track , copyChords ( other , [ "G" ]) ! , 12 ); 120const pasted = track . chords . find ( c => c . id === g ) ! ; 121assertEquals ( pasted . notes , [ 43 , 59 , 62 , 67 ]); 122pasted . notes ! . push ( 72 ); 123assertEquals ( other . chords [ 0 ]. notes , [ 43 , 59 , 62 , 67 ]); 124}); 125 126Deno . test ( "tracks move to before the track they're dropped on, or to the end" , () => { 127const p = newProject (); 128p . tracks = [ "a" , "b" , "c" , "d" ]. map ( name => ({ ...newChordTrack ( name ), id :name })); 129const order = () => p . tracks . map ( t => t . id ). join ( "" ); 130moveTrack ( p , "a" , 2 ); 131assertEquals ( order (), "bacd" ); 132moveTrack ( p , "d" , 0 ); 133assertEquals ( order (), "dbac" ); 134moveTrack ( p , "b" , 4 ); 135assertEquals ( order (), "dacb" ); 136});