1919BAR_ATTR = 0x0F
2020PLAYING_ATTR = 0xF4
2121
22- # HID keycode -> semitone (0-11)
22+ # HID keycode -> semitone above the base octave's C. The row keeps
23+ # going past the octave (12-16) like a DAW keyboard, so K L ; play
24+ # C D E of the next octave with O and P as their black keys.
2325NOTE_KEYCODES = {
2426 0x04 => 0 , # A -> C
2527 0x1A => 1 , # W -> C#
3335 0x0B => 9 , # H -> A
3436 0x18 => 10 , # U -> A#
3537 0x0D => 11 , # J -> B
38+ 0x0E => 12 , # K -> C (next octave)
39+ 0x12 => 13 , # O -> C#
40+ 0x0F => 14 , # L -> D
41+ 0x13 => 15 , # P -> D#
42+ 0x33 => 16 , # ; -> E
3643}
3744
3845# HID keycode -> drum name (bottom row)
6269# the home row, black keys in the gaps above them, drums half a key to
6370# the right below. One cell per key: "[A]" plus a label underneath.
6471WHITE_KEYS = [ [ "A" , 0x04 ] , [ "S" , 0x16 ] , [ "D" , 0x07 ] , [ "F" , 0x09 ] ,
65- [ "G" , 0x0A ] , [ "H" , 0x0B ] , [ "J" , 0x0D ] ]
72+ [ "G" , 0x0A ] , [ "H" , 0x0B ] , [ "J" , 0x0D ] , [ "K" , 0x0E ] ,
73+ [ "L" , 0x0F ] , [ ";" , 0x33 ] ]
6674BLACK_KEYS = [ [ "W" , 0x1A , 0 ] , [ "E" , 0x08 , 1 ] , [ "T" , 0x17 , 3 ] ,
67- [ "Y" , 0x1C , 4 ] , [ "U" , 0x18 , 5 ] ]
75+ [ "Y" , 0x1C , 4 ] , [ "U" , 0x18 , 5 ] , [ "O" , 0x12 , 7 ] ,
76+ [ "P" , 0x13 , 8 ] ]
6877DRUM_KEYS = [ [ "Z" , 0x1D ] , [ "X" , 0x1B ] , [ "C" , 0x06 ] , [ "V" , 0x19 ] ,
6978 [ "B" , 0x05 ] , [ "N" , 0x11 ] , [ "M" , 0x10 ] , [ "," , 0x36 ] ]
7079
71- KEY_WIDTH = 6
7280BLACK_CAP_ROW = 2
7381BLACK_LABEL_ROW = 3
7482WHITE_CAP_ROW = 5
8088cols = DVI ::Text . cols
8189rows = DVI ::Text . rows
8290command_row = rows - 1
83- left = ( cols - ( DRUM_KEYS . length * KEY_WIDTH + 2 ) ) / 2
91+ # Ten white keys at width 6 need 60 columns; the zoomed 53-column grid
92+ # gets width 5.
93+ key_width = cols >= WHITE_KEYS . length * 6 + 4 ? 6 : 5
94+ left = ( cols - WHITE_KEYS . length * key_width ) / 2
8495left = 1 if left < 1
8596
8697# keycode -> [column, row] of its cap on screen
8798key_cells = { }
8899i = 0
89100while i < WHITE_KEYS . length
90- key_cells [ WHITE_KEYS [ i ] [ 1 ] ] = [ left + i * KEY_WIDTH , WHITE_CAP_ROW ]
101+ key_cells [ WHITE_KEYS [ i ] [ 1 ] ] = [ left + i * key_width , WHITE_CAP_ROW ]
91102 i += 1
92103end
93104i = 0
94105while i < BLACK_KEYS . length
95106 gap = BLACK_KEYS [ i ] [ 2 ]
96- key_cells [ BLACK_KEYS [ i ] [ 1 ] ] = [ left + gap * KEY_WIDTH + 3 , BLACK_CAP_ROW ]
107+ key_cells [ BLACK_KEYS [ i ] [ 1 ] ] = [ left + gap * key_width + key_width / 2 , BLACK_CAP_ROW ]
97108 i += 1
98109end
99110i = 0
100111while i < DRUM_KEYS . length
101- key_cells [ DRUM_KEYS [ i ] [ 1 ] ] = [ left + 2 + i * KEY_WIDTH , DRUM_CAP_ROW ]
112+ key_cells [ DRUM_KEYS [ i ] [ 1 ] ] = [ left + 2 + i * key_width , DRUM_CAP_ROW ]
102113 i += 1
103114end
104115
105116def note_frequency ( semitone , octave )
106117 base = [ 262 , 277 , 294 , 311 , 330 , 349 , 370 , 392 , 415 , 440 , 466 , 494 ]
107- freq = base [ semitone ]
108- shift = octave - 4
118+ freq = base [ semitone % 12 ]
119+ shift = octave + semitone / 12 - 4
109120 if shift > 0
110121 shift . times { freq = freq * 2 }
111122 elsif shift < 0
@@ -114,6 +125,10 @@ def note_frequency(semitone, octave)
114125 freq
115126end
116127
128+ def note_name ( semitone , octave )
129+ "#{ NOTE_NAMES [ semitone % 12 ] } #{ octave + semitone / 12 } "
130+ end
131+
117132# Load the kit from /data/drums; a missing or unreadable file falls
118133# back to rendering the same definition on the board. Note that
119134# File.open of a missing file returns nil instead of raising.
@@ -158,15 +173,15 @@ def note_frequency(semitone, octave)
158173 keycode = BLACK_KEYS [ i2 ] [ 1 ]
159174 x = key_cells [ keycode ] [ 0 ]
160175 draw_cap . call ( keycode , false )
161- DVI ::Text . put_string ( x , BLACK_LABEL_ROW , NOTE_NAMES [ NOTE_KEYCODES [ keycode ] ] . rjust ( 2 ) , TEXT_ATTR )
176+ DVI ::Text . put_string ( x , BLACK_LABEL_ROW , NOTE_NAMES [ NOTE_KEYCODES [ keycode ] % 12 ] . rjust ( 2 ) , TEXT_ATTR )
162177 i2 += 1
163178 end
164179 i2 = 0
165180 while i2 < WHITE_KEYS . length
166181 keycode = WHITE_KEYS [ i2 ] [ 1 ]
167182 x = key_cells [ keycode ] [ 0 ]
168183 draw_cap . call ( keycode , false )
169- DVI ::Text . put_string ( x + 1 , WHITE_LABEL_ROW , NOTE_NAMES [ NOTE_KEYCODES [ keycode ] ] , TEXT_ATTR )
184+ DVI ::Text . put_string ( x + 1 , WHITE_LABEL_ROW , NOTE_NAMES [ NOTE_KEYCODES [ keycode ] % 12 ] , TEXT_ATTR )
170185 i2 += 1
171186 end
172187 i2 = 0
@@ -183,7 +198,7 @@ def note_frequency(semitone, octave)
183198DVI ::Text . clear ( TEXT_ATTR )
184199draw_title . call
185200draw_keyboard . call
186- help = " 1-4 waveform pad up/down octave Esc quit"
201+ help = " 1-4 waveform arrows/pad octave Esc quit"
187202DVI ::Text . put_string ( 0 , command_row , help . ljust ( cols ) [ 0 , cols ] , BAR_ATTR )
188203DVI ::Text . commit
189204
@@ -202,7 +217,9 @@ def note_frequency(semitone, octave)
202217 key = keyboard . read_char
203218 break if key == Keyboard ::CTRL_C || key == Keyboard ::ESCAPE
204219
205- # Octave shift via pad (edge detection)
220+ previous_setting = [ octave , waveform ]
221+
222+ # Octave shift via pad (edge detection) or cursor keys
206223 left_pad . read
207224 if left_pad . up? && !prev_octave_up
208225 octave = octave + 1 if octave < 7
@@ -212,6 +229,13 @@ def note_frequency(semitone, octave)
212229 end
213230 prev_octave_up = left_pad . up?
214231 prev_octave_down = left_pad . down?
232+ if key
233+ if key . match? ( :up )
234+ octave = octave + 1 if octave < 7
235+ elsif key . match? ( :down )
236+ octave = octave - 1 if octave > 1
237+ end
238+ end
215239
216240 # Read currently held keys directly from HID report
217241 keycodes = USB ::Host . keyboard_keycodes
@@ -234,6 +258,9 @@ def note_frequency(semitone, octave)
234258 end
235259 end
236260
261+ # Retune held notes right away when the octave or waveform changes.
262+ prev_note_keycodes = nil if [ octave , waveform ] != previous_setting
263+
237264 if keycodes != prev_keycodes
238265 # Trigger drums on newly pressed keys (edge detection on the raw
239266 # report, so holding a key does not machine-gun the drum)
@@ -288,7 +315,7 @@ def note_frequency(semitone, octave)
288315 end
289316
290317 if note_keycodes . length > 0
291- names = note_keycodes . map { |kc | " #{ NOTE_NAMES [ NOTE_KEYCODES [ kc ] ] } #{ octave } " }
318+ names = note_keycodes . map { |kc | note_name ( NOTE_KEYCODES [ kc ] , octave ) }
292319 DVI ::Text . put_string ( 0 , PLAYING_ROW , " playing: #{ names . join ( " + " ) } " . ljust ( cols ) [ 0 , cols ] , PLAYING_ATTR )
293320 else
294321 DVI ::Text . clear_line ( PLAYING_ROW , TEXT_ATTR )
0 commit comments