1212 hint =" Play some notes on your input device to test the connection"
1313 ></v-select >
1414 <v-select v-model =" selectedOutput" :items =" outputs" label =" Select Output" ></v-select >
15+ <v-divider class =" my-4" ></v-divider >
16+ <h3 class =" text-subtitle-1 mb-2" >Keyboard Range</h3 >
17+
18+ <v-select
19+ v-model =" selectedKeyboardRange"
20+ :items =" keyboardRangeOptions"
21+ label =" Select Keyboard Range"
22+ @update:model-value =" updateCustomRangeFromPreset"
23+ ></v-select >
24+
25+ <div v-if =" selectedKeyboardRange === 'custom'" class =" custom-range-inputs d-flex gap-4" >
26+ <v-select
27+ v-model =" lowestNote"
28+ :items =" noteOptions"
29+ label =" Lowest Note"
30+ @update:model-value =" updateRangeAndCheckChanges"
31+ ></v-select >
32+
33+ <v-select
34+ v-model =" highestNote"
35+ :items =" noteOptions"
36+ label =" Highest Note"
37+ @update:model-value =" updateRangeAndCheckChanges"
38+ ></v-select >
39+ </div >
40+
41+ <piano-range-visualizer :lowest-note =" lowestNote" :highest-note =" highestNote" />
42+
1543 <div class =" d-flex justify-space-between mt-3" >
1644 <v-btn color =" primary" @click =" playSound" >Test midi output</v-btn >
1745 <v-btn
@@ -56,6 +84,7 @@ import { Status } from '@vue-skuilder/common';
5684import { User } from ' @/db/userDB' ;
5785import { InputEventNoteon } from ' webmidi' ;
5886import { getCurrentUser } from ' @/stores/useAuthStore' ;
87+ import PianoRangeVisualizer from ' ./PianoRangeVisualizer.vue' ;
5988
6089interface MidiDevice {
6190 text: string ;
@@ -64,6 +93,10 @@ interface MidiDevice {
6493export default defineComponent ({
6594 name: ' MidiConfig' ,
6695
96+ components: {
97+ PianoRangeVisualizer ,
98+ },
99+
67100 props: {
68101 _id: {
69102 type: String ,
@@ -86,8 +119,90 @@ export default defineComponent({
86119 const savedOutputId = ref <string >(' ' );
87120 const configChanged = ref (false );
88121
122+ // Keyboard range
123+ const selectedKeyboardRange = ref (' full-88' );
124+ const lowestNote = ref (21 ); // A0
125+ const highestNote = ref (108 ); // C8
126+ const savedKeyboardRange = ref (' ' );
127+ const savedLowestNote = ref (0 );
128+ const savedHighestNote = ref (0 );
129+
130+ const noteOptions = ref <Array <{ title: string ; value: number }>>([]);
131+
132+ // Initialize noteOptions with all MIDI notes (0-127) with proper naming
133+ const initNoteOptions = () => {
134+ const noteNames = [' C' , ' C#' , ' D' , ' D#' , ' E' , ' F' , ' F#' , ' G' , ' G#' , ' A' , ' A#' , ' B' ];
135+ const options = [];
136+
137+ // Generate all 128 MIDI notes with proper labeling
138+ for (let i = 0 ; i <= 127 ; i ++ ) {
139+ const octave = Math .floor (i / 12 ) - 1 ;
140+ const noteName = noteNames [i % 12 ];
141+
142+ options .push ({
143+ title: ` ${noteName }${octave } (${i }) ` , // Format: "C4 (60)"
144+ value: i ,
145+ });
146+ }
147+
148+ noteOptions .value = options ;
149+ };
150+
151+ const keyboardRangeOptions = ref ([
152+ { title: ' Full 88-key Piano (A0-C8)' , value: ' full-88' },
153+ { title: ' 76-key Keyboard (E1-G7)' , value: ' 76-key' },
154+ { title: ' 61-key Keyboard (C2-C7)' , value: ' 61-key' },
155+ { title: ' 49-key Keyboard (C3-C7)' , value: ' 49-key' },
156+ { title: ' 37-key Keyboard (C3-C6)' , value: ' 37-key' },
157+ { title: ' 25-key Keyboard (C4-C6)' , value: ' 25-key' },
158+ { title: ' Custom Range' , value: ' custom' },
159+ ]);
160+
89161 const checkConfigChanged = () => {
90- configChanged .value = selectedInput .value !== savedInputId .value || selectedOutput .value !== savedOutputId .value ;
162+ configChanged .value =
163+ selectedInput .value !== savedInputId .value ||
164+ selectedOutput .value !== savedOutputId .value ||
165+ selectedKeyboardRange .value !== savedKeyboardRange .value ||
166+ (selectedKeyboardRange .value === ' custom' &&
167+ (lowestNote .value !== savedLowestNote .value || highestNote .value !== savedHighestNote .value ));
168+ };
169+
170+ const updateCustomRangeFromPreset = () => {
171+ switch (selectedKeyboardRange .value ) {
172+ case ' full-88' :
173+ lowestNote .value = 21 ; // A0
174+ highestNote .value = 108 ; // C8
175+ break ;
176+ case ' 76-key' :
177+ lowestNote .value = 28 ; // E1
178+ highestNote .value = 103 ; // G7
179+ break ;
180+ case ' 61-key' :
181+ lowestNote .value = 36 ; // C2
182+ highestNote .value = 96 ; // C7
183+ break ;
184+ case ' 49-key' :
185+ lowestNote .value = 48 ; // C3
186+ highestNote .value = 96 ; // C7
187+ break ;
188+ case ' 37-key' :
189+ lowestNote .value = 48 ; // C3
190+ highestNote .value = 84 ; // C6
191+ break ;
192+ case ' 25-key' :
193+ lowestNote .value = 60 ; // C4
194+ highestNote .value = 84 ; // C6
195+ break ;
196+ }
197+ checkConfigChanged ();
198+ };
199+
200+ const updateRangeAndCheckChanges = () => {
201+ // Ensure lowest is always below highest
202+ if (lowestNote .value >= highestNote .value ) {
203+ highestNote .value = lowestNote .value + 12 ; // At least an octave higher
204+ }
205+ checkConfigChanged ();
91206 };
92207
93208 const playSound = () => {
@@ -293,6 +408,27 @@ export default defineComponent({
293408 }
294409 }
295410
411+ // Load keyboard range settings
412+ if (s ?.keyboardRange ) {
413+ savedKeyboardRange .value = s .keyboardRange .toString ();
414+ selectedKeyboardRange .value = savedKeyboardRange .value ;
415+ }
416+
417+ if (s ?.lowestNote ) {
418+ savedLowestNote .value = parseInt (s .lowestNote .toString ());
419+ lowestNote .value = savedLowestNote .value ;
420+ }
421+
422+ if (s ?.highestNote ) {
423+ savedHighestNote .value = parseInt (s .highestNote .toString ());
424+ highestNote .value = savedHighestNote .value ;
425+ }
426+
427+ // If we have custom values but not the 'custom' range type, set it
428+ if (s ?.lowestNote && s ?.highestNote && ! s ?.keyboardRange ) {
429+ selectedKeyboardRange .value = ' custom' ;
430+ }
431+
296432 // Initialize with no pending changes after loading
297433 configChanged .value = false ;
298434 };
@@ -308,11 +444,27 @@ export default defineComponent({
308444 key: ' midioutput' ,
309445 value: selectedOutput .value ,
310446 },
447+ {
448+ key: ' keyboardRange' ,
449+ value: selectedKeyboardRange .value ,
450+ },
451+ {
452+ key: ' lowestNote' ,
453+ value: lowestNote .value ,
454+ },
455+ {
456+ key: ' highestNote' ,
457+ value: highestNote .value ,
458+ },
311459 ]);
312460
313461 // Update saved state references
314462 savedInputId .value = selectedInput .value ;
315463 savedOutputId .value = selectedOutput .value ;
464+ savedKeyboardRange .value = selectedKeyboardRange .value ;
465+ savedLowestNote .value = lowestNote .value ;
466+ savedHighestNote .value = highestNote .value ;
467+
316468 configChanged .value = false ;
317469 updatePending .value = false ;
318470
@@ -324,6 +476,7 @@ export default defineComponent({
324476
325477 onMounted (async () => {
326478 user .value = await getCurrentUser ();
479+ initNoteOptions ();
327480 try {
328481 midi .value = await SkMidi .instance ();
329482 midiSupported .value = midi .value .state === ' ready' || midi .value .state === ' nodevice' ;
@@ -375,6 +528,13 @@ export default defineComponent({
375528 playSound ,
376529 saveSettings ,
377530 configChanged ,
531+ lowestNote ,
532+ highestNote ,
533+ keyboardRangeOptions ,
534+ selectedKeyboardRange ,
535+ noteOptions ,
536+ updateCustomRangeFromPreset ,
537+ updateRangeAndCheckChanges ,
378538 };
379539 },
380540});
0 commit comments