Skip to content

Commit 2407eab

Browse files
harukasanclaude
andcommitted
Extend the audio_demo keyboard by half an octave
- Continue the note rows past the octave like a DAW keyboard: K L ; play C D E of the next octave with O and P as their black keys - Switch the octave from the cursor keys as well as the pad, and retune held notes right away when the octave or waveform changes - Narrow the key cells on the 53-column zoomed grid so the ten white keys still fit Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 19ab99c commit 2407eab

1 file changed

Lines changed: 42 additions & 15 deletions

File tree

rootfs/app/audio_demo.rb

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
BAR_ATTR = 0x0F
2020
PLAYING_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.
2325
NOTE_KEYCODES = {
2426
0x04 => 0, # A -> C
2527
0x1A => 1, # W -> C#
@@ -33,6 +35,11 @@
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)
@@ -62,13 +69,14 @@
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.
6471
WHITE_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]]
6674
BLACK_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]]
6877
DRUM_KEYS = [["Z", 0x1D], ["X", 0x1B], ["C", 0x06], ["V", 0x19],
6978
["B", 0x05], ["N", 0x11], ["M", 0x10], [",", 0x36]]
7079

71-
KEY_WIDTH = 6
7280
BLACK_CAP_ROW = 2
7381
BLACK_LABEL_ROW = 3
7482
WHITE_CAP_ROW = 5
@@ -80,32 +88,35 @@
8088
cols = DVI::Text.cols
8189
rows = DVI::Text.rows
8290
command_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
8495
left = 1 if left < 1
8596

8697
# keycode -> [column, row] of its cap on screen
8798
key_cells = {}
8899
i = 0
89100
while 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
92103
end
93104
i = 0
94105
while 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
98109
end
99110
i = 0
100111
while 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
103114
end
104115

105116
def 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
115126
end
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)
183198
DVI::Text.clear(TEXT_ATTR)
184199
draw_title.call
185200
draw_keyboard.call
186-
help = " 1-4 waveform pad up/down octave Esc quit"
201+
help = " 1-4 waveform arrows/pad octave Esc quit"
187202
DVI::Text.put_string(0, command_row, help.ljust(cols)[0, cols], BAR_ATTR)
188203
DVI::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

Comments
 (0)