1+ const { getClosestStandardNoteValue, transcribeMidi } = require ( '../midi' ) ;
2+
3+ const mockMidi = {
4+ header : {
5+ ppq : 480 ,
6+ tempos : [ { bpm : 120 } ] ,
7+ timeSignatures : [ { timeSignature : [ 4 , 4 ] , ticks : 0 } ]
8+ } ,
9+ tracks : [
10+ {
11+ instrument : { name : 'acoustic grand piano' , family : 'piano' , number : 0 , percussion : false } ,
12+ channel : 1 ,
13+ notes : [
14+ { name : 'C4' , midi : 60 , time : 0 , duration : 0.5 , velocity : 0.8 } ,
15+ { name : 'E4' , midi : 64 , time : 0.5 , duration : 0.75 , velocity : 0.9 } ,
16+ { name : 'G4' , midi : 67 , time : 1.25 , duration : 0.5 , velocity : 0.85 }
17+ ]
18+ } ,
19+ {
20+ instrument : { name : 'acoustic guitar (nylon)' , family : 'guitar' , number : 24 , percussion : false } ,
21+ channel : 2 ,
22+ notes : [
23+ { name : 'G3' , midi : 55 , time : 0 , duration : 0.6 , velocity : 0.7 } ,
24+ { name : 'C4' , midi : 60 , time : 0.6 , duration : 0.8 , velocity : 0.75 }
25+ ]
26+ } ,
27+ {
28+ instrument : { name : 'drums' , family : 'percussion' , number : 128 , percussion : true } ,
29+ channel : 9 ,
30+ notes : [
31+ { name : 'Snare Drum' , midi : 38 , time : 0 , duration : 0.3 , velocity : 0.9 } ,
32+ { name : 'Kick Drum' , midi : 36 , time : 0.5 , duration : 0.3 , velocity : 0.8 }
33+ ]
34+ }
35+ ]
36+ } ;
37+
38+ describe ( 'getClosestStandardNoteValue' , ( ) => {
39+ it ( 'should return the closest standard note duration for a given input' , ( ) => {
40+ expect ( getClosestStandardNoteValue ( 1 ) ) . toEqual ( [ 1 , 1 ] ) ;
41+ expect ( getClosestStandardNoteValue ( 0.0078125 ) ) . toEqual ( [ 1 , 128 ] ) ;
42+ } ) ;
43+ } ) ;
44+
45+ describe ( 'transcribeMidi' , ( ) => {
46+ let loadNewBlocksSpy ;
47+
48+ beforeEach ( ( ) => {
49+ // Mock dependencies
50+ global . getReverseDrumMidi = jest . fn ( ( ) => ( {
51+ 38 : [ "snare drum" ] ,
52+ 36 : [ "kick drum" ] ,
53+ 41 : [ "tom tom" ]
54+ } ) ) ;
55+
56+ global . VOICENAMES = [
57+ [ "piano" , "acoustic grand piano" ] ,
58+ [ "guitar" , "acoustic guitar (nylon)" ] ,
59+ ] ;
60+
61+ global . activity = {
62+ textMsg : jest . fn ( ) ,
63+ blocks : {
64+ loadNewBlocks : jest . fn ( ) ,
65+ palettes : { _hideMenus : jest . fn ( ) } ,
66+ trashStacks : [ ]
67+ }
68+ } ;
69+
70+ // Spy on loadNewBlocks
71+ loadNewBlocksSpy = jest . spyOn ( activity . blocks , 'loadNewBlocks' ) ;
72+ } ) ;
73+
74+ afterEach ( ( ) => {
75+ jest . clearAllMocks ( ) ;
76+ } ) ;
77+
78+ it ( 'should process all tracks and generate blocks' , async ( ) => {
79+ await transcribeMidi ( mockMidi ) ;
80+ expect ( loadNewBlocksSpy ) . toHaveBeenCalled ( ) ;
81+ const loadedBlocks = loadNewBlocksSpy . mock . calls [ 0 ] [ 0 ] ;
82+ expect ( Array . isArray ( loadedBlocks ) ) . toBe ( true ) ;
83+ expect ( loadedBlocks . length ) . toBeGreaterThan ( 0 ) ;
84+ } ) ;
85+
86+ it ( 'should handle default tempo correctly' , async ( ) => {
87+ const midiWithoutTempo = {
88+ ...mockMidi ,
89+ header : {
90+ ...mockMidi . header ,
91+ tempos : [ ]
92+ }
93+ } ;
94+
95+ await transcribeMidi ( midiWithoutTempo ) ;
96+ expect ( loadNewBlocksSpy ) . toHaveBeenCalled ( ) ;
97+ const loadedBlocks = loadNewBlocksSpy . mock . calls [ 0 ] [ 0 ] ;
98+ const bpmBlock = loadedBlocks . find ( block =>
99+ Array . isArray ( block [ 1 ] ) && block [ 1 ] [ 0 ] === 'setbpm3'
100+ ) ;
101+ expect ( bpmBlock ) . toBeDefined ( ) ;
102+ const tempoValueBlock = loadedBlocks . find ( block =>
103+ block [ 0 ] === bpmBlock [ 4 ] [ 1 ]
104+ ) ;
105+ expect ( tempoValueBlock ) . toBeDefined ( ) ;
106+ expect ( tempoValueBlock [ 1 ] [ 1 ] . value ) . toBe ( 90 ) ;
107+ } ) ;
108+
109+ it ( 'should skip tracks with no notes' , async ( ) => {
110+ const emptyTrackMidi = {
111+ ...mockMidi ,
112+ tracks : [ { ...mockMidi . tracks [ 0 ] , notes : [ ] } ]
113+ } ;
114+
115+ await transcribeMidi ( emptyTrackMidi ) ;
116+ expect ( loadNewBlocksSpy ) . toHaveBeenCalled ( ) ;
117+ const loadedBlocks = loadNewBlocksSpy . mock . calls [ 0 ] [ 0 ] ;
118+ const trackBlocks = loadedBlocks . filter ( block =>
119+ Array . isArray ( block [ 1 ] ) && block [ 1 ] [ 0 ] === 'setturtlename2'
120+ ) ;
121+ expect ( trackBlocks . length ) . toBe ( 0 ) ;
122+ } ) ;
123+
124+ it ( 'should handle percussion instruments correctly' , async ( ) => {
125+ await transcribeMidi ( mockMidi ) ;
126+ expect ( loadNewBlocksSpy ) . toHaveBeenCalled ( ) ;
127+ const loadedBlocks = loadNewBlocksSpy . mock . calls [ 0 ] [ 0 ] ;
128+ const drumBlocks = loadedBlocks . filter ( block =>
129+ block [ 1 ] === 'playdrum'
130+ ) ;
131+ expect ( drumBlocks . length ) . toBeGreaterThan ( 0 ) ;
132+ } ) ;
133+
134+ it ( 'should assign correct instruments to tracks' , async ( ) => {
135+ await transcribeMidi ( mockMidi ) ;
136+ expect ( loadNewBlocksSpy ) . toHaveBeenCalled ( ) ;
137+
138+ const loadedBlocks = loadNewBlocksSpy . mock . calls [ 0 ] [ 0 ] ;
139+ const instrumentBlocks = loadedBlocks . filter ( block =>
140+ Array . isArray ( block [ 1 ] ) && block [ 1 ] [ 0 ] === 'settimbre'
141+ ) ;
142+ const nonPercussionTracks = mockMidi . tracks . filter ( track => ! track . instrument . percussion ) ;
143+ instrumentBlocks . forEach ( ( block , index ) => {
144+ const instrumentName = nonPercussionTracks [ index ] . instrument . name ;
145+ expect ( block [ 1 ] [ 1 ] . value ) . toBe ( instrumentName ) ;
146+ } ) ;
147+ } ) ;
148+
149+ it ( 'should generate correct note durations' , async ( ) => {
150+ await transcribeMidi ( mockMidi ) ;
151+ expect ( loadNewBlocksSpy ) . toHaveBeenCalled ( ) ;
152+
153+ const loadedBlocks = loadNewBlocksSpy . mock . calls [ 0 ] [ 0 ] ;
154+ const noteBlocks = loadedBlocks . filter ( block =>
155+ Array . isArray ( block [ 1 ] ) && block [ 1 ] [ 0 ] === 'newnote'
156+ ) ;
157+
158+ noteBlocks . forEach ( block => {
159+ const divideBlock = loadedBlocks . find ( b => b [ 0 ] === block [ 4 ] [ 1 ] ) ;
160+ expect ( divideBlock ) . toBeDefined ( ) ;
161+
162+ const numeratorBlock = loadedBlocks . find ( b => b [ 0 ] === divideBlock [ 4 ] [ 1 ] ) ;
163+ const denominatorBlock = loadedBlocks . find ( b => b [ 0 ] === divideBlock [ 4 ] [ 2 ] ) ;
164+
165+ expect ( numeratorBlock ) . toBeDefined ( ) ;
166+ expect ( denominatorBlock ) . toBeDefined ( ) ;
167+ expect ( numeratorBlock [ 1 ] [ 1 ] . value ) . toBeGreaterThan ( 0 ) ;
168+ expect ( denominatorBlock [ 1 ] [ 1 ] . value ) . toBeGreaterThan ( 0 ) ;
169+ } ) ;
170+ } ) ;
171+
172+ it ( 'should generate rest notes for gaps between notes' , async ( ) => {
173+ await transcribeMidi ( mockMidi ) ;
174+ expect ( loadNewBlocksSpy ) . toHaveBeenCalled ( ) ;
175+ const loadedBlocks = loadNewBlocksSpy . mock . calls [ 0 ] [ 0 ] ;
176+ const restBlocks = loadedBlocks . filter ( block =>
177+ block [ 1 ] === 'rest2'
178+ ) ;
179+ expect ( restBlocks . length ) . toBeGreaterThan ( 0 ) ;
180+ } ) ;
181+ } ) ;
0 commit comments