char-slop/chordpad.vibed.software
git clone https://git.t4t.associates/char-slop/chordpad.vibed.software
a5e999d
main
1import { chordSymbol , parseChord } from "../theory/chord.ts" ; 2import { midiName } from "../theory/pitch.ts" ; 3import type { Key } from "../theory/key.ts" ; 4import { respell , voiceTrack } from "./harmony.ts" ; 5import { 6type ChordClip , 7type ChordTrack , 8copyChords , 9copyNotes , 10keyOf , 11type NoteClip , 12type NoteTrack , 13type Project , 14} from "./model.ts" ; 15 16/** how chords go on the system clipboard between tabs of this app */ 17export const CHORDS_MIME = "application/x-chordpad-chords+json" ; 18type ClipboardChords = { key :Project [ "key" ]; clip :ChordClip }; 19 20/** chords to put on the clipboard: exactly, for this app, and as plain text for anything else */ 21export function chordsToClipboard ( 22track :ChordTrack , 23ids :readonly string [], 24key :Project [ "key" ], 25) :{ data :string ; text :string } | undefined { 26const clip = copyChords ( track , ids ); 27if ( ! clip ) return undefined ; 28// letter names: a roman numeral means nothing outside the key it's in 29const text = voiceTrack ( track , keyOf ( key )) 30. filter ( v => ids . includes ( v . event . id )) 31. map ( v => ( v . chord ?chordSymbol ( v . chord ) :v . label )) 32. join ( " " ); 33return { data :JSON . stringify ({ key, clip} satisfies ClipboardChords ), text}; 34} 35 36/** copied chords, written to name the same chords in the key they're pasted into */ 37export function chordsFromClipboard ( data :string , into :Key ) :ChordClip | undefined { 38let parsed :ClipboardChords ; 39try { 40parsed = JSON . parse ( data ); 41} catch { 42return undefined ; 43} 44if ( ! Array . isArray ( parsed ?. clip ?. chords ) || ! parsed . key ) return undefined ; 45const from = keyOf ( parsed . key ); 46return { 47 ...parsed . clip , 48chords :parsed . clip . chords . map ( c => 49c . symbol ?{ ...c , symbol :respell ( c . symbol , from , into ) } :c , 50), 51}; 52} 53 54/** chord symbols pasted as text ("Dm7 G7 | Cmaj7"), each the given length; what won't parse is left out */ 55export function chordsFromText ( text :string , key :Key , length :number ) :ChordClip | undefined { 56const symbols = text . split ( / [\s|,]+ / ). filter ( s => s && parseChord ( s , key )); 57if ( ! symbols . length ) return undefined ; 58return { 59chords :symbols . map (( symbol , i ) => ({ start :i * length , dur :length , symbol})), 60span :symbols . length * length , 61}; 62} 63 64export const NOTES_MIME = "application/x-chordpad-notes+json" ; 65 66/** notes to put on the clipboard: exactly, and as their names for anything else */ 67export function notesToClipboard ( 68track :NoteTrack , 69ids :readonly string [], 70key :Key , 71) :{ data :string ; text :string } | undefined { 72const clip = copyNotes ( track , ids ); 73if ( ! clip ) return undefined ; 74const text = clip . notes . map ( n => midiName ( n . pitch , key . tonic )). join ( " " ); 75return { data :JSON . stringify ( clip ), text}; 76} 77 78export function notesFromClipboard ( data :string ) :NoteClip | undefined { 79try { 80const clip = JSON . parse ( data ); 81return Array . isArray ( clip ?. notes ) ?clip :undefined ; 82} catch { 83return undefined ; 84} 85}