Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
musical_typing_keyboard.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <functional>
7#include <span>
8#include <vector>
9
10namespace pulp::view {
11
12// Frame indices for the two mode frames (DesignFrameView::set_active_frame
13// targets). Frame 0 is the typing keyboard, frame 1 the piano keyboard.
14inline constexpr int kTypingFrame = 0;
15inline constexpr int kPianoFrame = 1;
16
17// ── MusicalTypingKeyboard ────────────────────────────────────────────────────
18// Ink & Signal "Musical Typing Keyboard" catalog component (Category::audio).
19//
20// THE playable computer-typing + piano keyboard primitive. Use THIS for a
21// "musical typing keyboard" — NOT MidiKeyboard (the plain piano strip). It is
22// fully wired:
23// • computer keyboard → notes (a w s e d f t g y h u j k o l p), z/x octave —
24// via an owned MusicalTypingController, while the view has keyboard focus;
25// • clicking either the typing row OR the lower piano row plays + lights keys;
26// • pressed keys light with the accent gradient (white + black), per design.
27// Wire on_note_on / on_note_off to a synth/sampler; everything else is internal.
28//
29// It is NOT a hand-painted widget: it renders TWO faithful, Figma-exported SVGs
30// 1:1 through DesignFrameView (SkSVGDOM) — one per Mode (typing = node 187:15,
31// piano = node 187:349), lowered via the faithful-vector lane
32// (tools/import-design/figma_rest_export.py). The toggle swaps which frame
33// renders (DesignFrameView::set_active_frame) and the view's intrinsic size.
34// Reskin via that lane (re-export → re-embed), not by hand.
36 public:
38
39 // The two keyboard modes. typing = the QWERTY/computer-typing keyboard
40 // (frame 0); piano = the full piano keyboard (frame 1). The 🎹/⌨ toggle
41 // buttons baked into each frame switch between them on click; set_mode does
42 // it programmatically. Switching REDRAWS the content and changes the
43 // reported intrinsic size (typing is taller than piano) and requests a
44 // re-layout — a host that sizes to intrinsic_width()/height() resizes to
45 // match; a fixed-bounds host fits the new frame into its current size.
48 Mode mode() const;
49
50 // Notes produced by typing (computer keyboard) OR clicking the keys.
51 // `velocity` is 0..1. Wire to a synth/sampler note sink.
52 std::function<void(int note, float velocity)> on_note_on;
53 std::function<void(int note)> on_note_off;
54
55 // The owned QWERTY→note controller (base note, octave, velocity). Exposed so
56 // a host can set the base note / velocity or feed keys from its own path.
58 return controller_;
59 }
60
61 // ── On-screen control callbacks (Logic-faithful) ─────────────────────────
62 // The on-screen octave −/+ and velocity −/+ buttons drive the controller
63 // directly (they change the next note's octave/velocity, no callback needed).
64 // These surface the controls that DON'T map to controller state. Keys 1–8 and
65 // tab mirror the on-screen buttons exactly:
66 // • on_pitch_bend(−1..+1) — keys 1 / 2 (and the −/+ pads) are MOMENTARY:
67 // hold → full bend down (−1) / up (+1), release → 0 (spring to center).
68 // Bipolar; the host maps it to its bend range. Readout shows −20 / 0 / +20.
69 // • on_sustain(bool) — sustain pad / tab key, MOMENTARY hold: true while
70 // held, false on release (lit while held).
71 // • on_modulation(0..1) — keys 3–8 (and the 6 mod pads) are a LATCHED
72 // selector: 3 = off (0, default) … 8 = max (1). The selection persists and
73 // highlights; 0..1 is mod_sel / 5.
74 std::function<void(float bend)> on_pitch_bend;
75 std::function<void(bool on)> on_sustain;
76 std::function<void(float amount)> on_modulation;
77
78 // Fired when the toggle (or set_mode) swaps frames and the intrinsic size
79 // changes — piano mode (732×176) is shorter than typing (732×266). A host
80 // that sizes itself to the keyboard wires this to resize its window/pane to
81 // (w, h) (e.g. WindowHost::request_content_size + set_design_viewport), so
82 // the piano frame shrinks the window top-aligned and toggling back grows it.
83 // Carries the NEW frame's panel width/height.
84 std::function<void(float w, float h)> on_intrinsic_size_changed;
85
86 // ── Host-driven integration (e.g. PulpTempoSampler) ──────────────────────
87 // Light keys from an EXTERNAL held-note set — host MIDI, an app-wide QWERTY
88 // monitor, clicks elsewhere. Absolute MIDI notes; a note lights every key
89 // that maps to it (the typing key at the current octave AND the piano key).
90 // This REPLACES the lit display, so the component never relies solely on its
91 // own key/mouse state. Pass an empty span to clear. Use when a host drives
92 // notes (typically with set_input_capture(false)).
93 void set_active_notes(std::span<const int> midi_notes);
94
95 // When false, the component does NOT capture computer-keyboard events, so a
96 // host that already feeds QWERTY (app-wide monitor → its own controller)
97 // won't double-trigger. It still plays + lights on CLICK and from
98 // set_active_notes. Default true (standalone playable). Also drops keyboard
99 // focusability so it won't steal first-responder key events.
100 void set_input_capture(bool capture);
101 bool input_capture() const {
102 return input_capture_;
103 }
104
105 // Set the controller's base note (e.g. a sampler's root) AND refresh the
106 // OCTAVE readout + overview highlight. Prefer this over controller().
107 // set_base_note(), which leaves the readout showing the previous octave.
108 void set_base_note(int note);
109
110 // Computer-keyboard playing while focused (when input_capture is on):
111 // a w s e d f t g y h u j k o l p play notes, z/x shift the octave. Returns
112 // true when consumed (false when capture is off, so the host keeps the key).
113 bool on_key_event(const KeyEvent& event) override;
114 void on_focus_changed(bool gained) override; // release held notes on blur
115
116 // ── Overview-strip octave control (Logic-style) ──────────────────────────
117 // The top overview strip's teal highlight is the canonical octave indicator:
118 // it tracks the octave (z/x, the < > arrows, AND dragging it), always snapping
119 // to a C boundary (each octave step is a C). Drag anywhere on the strip to set
120 // the octave; the cursor shows grab/grabbing over it. The highlight is drawn
121 // as a native overlay (the baked teal box is suppressed) so it can move.
122 void paint(canvas::Canvas& canvas) override;
123 void on_mouse_down(Point pos) override;
124 void on_mouse_drag(Point pos) override;
125 void on_mouse_up(Point pos) override;
126 void on_hover_move(Point pos) override;
127 void on_mouse_leave() override;
128 bool wants_mouse_input() const override {
129 return true;
130 }
131
132 // MIDI note an element maps to: in the typing frame, base+octave+semitone; in
133 // the piano frame, the windowed note (frame-low-note 48 offset onto the live
134 // octave window). Public so callers/tests can resolve a key's current pitch.
135 int midi_for_element(int index) const;
136
137 // Index of the (first) element whose action tag == `tag`, or -1. Public for
138 // the same reason as midi_for_element: it is a pure query, and pairing it
139 // with element_value() is how a caller or test reads a control's lit state
140 // (e.g. whether the sustain pad is still held).
141 int element_for_action(const std::string& tag) const;
142
143 protected:
144 // On a mode swap (toggle button or set_mode): release any QWERTY-held notes
145 // (so a held key can't sound forever after the typing frame goes away) and
146 // re-apply the external held-note set to the NEW frame's keys (so a host's
147 // sustained chord doesn't visually vanish across the swap).
148 void on_active_frame_changed() override;
149
150 private:
151 MusicalTypingController controller_;
152 bool input_capture_ = true; // false = host feeds QWERTY; we don't capture keys
153 bool sustain_ = false; // sustain-pad toggle state (surfaced via on_sustain)
154 int pb_value_ = 0; // live pitch-bend display value (−20 / 0 / +20)
155 int mod_sel_ = 0; // latched modulation selection (0 = "off" … 5 = "max")
156 bool dragging_strip_ = false; // mid drag-to-octave on the overview strip
157 static constexpr float kVelStep = 1.0f / 16.0f; // on-screen velocity −/+ increment
158 static constexpr int kPitchBendMax = 20; // Logic-style ±20 readout extent
159
160 // The overview strip is a full-range (C-2…G8 / MIDI 0–127) mini-piano ruler
161 // drawn procedurally in paint() over the design's strip background — white-key
162 // dividers, black-key marks, C-only labels, and the teal highlight that spans
163 // the current playable octave window. The baked partial ribbon is covered.
164 // Piano tab: lowest MIDI of the visible 3-octave (36-key) window. The window
165 // slides over the full C-2…G8 range as the octave moves and CLAMPS at the top
166 // so it ends on G8 (MIDI 127) — a partial top, like Logic (the keys shift, not
167 // just the labels). The typing tab is unaffected (it keeps its narrow playable
168 // window). Used to remap the piano keys' MIDI, the labels, and the highlight.
169 int piano_window_lo() const;
170 // The strip's horizontal extent (panel coords) for the active frame — the
171 // ribbon is centered in the toolbar and is a different width per frame (the
172 // typing/piano toolbars centered their own natural-width ribbons after #82).
173 void strip_bounds(float& x0, float& x1) const;
174 // Panel-x → octave shift, snapped to the nearest octave (each step is a C
175 // boundary — "always snaps to a C range"), clamped to the controller's ±4.
176 int octave_for_strip_x(float panel_x) const;
177 // If `pos` (view-local) is over the strip band, set `panel_x` to its panel-x
178 // and return true. Uses the shared panel_transform (panel origin is 0,0).
179 bool point_over_strip(Point pos, float& panel_x) const;
180 // Refresh the live OCTAVE / VEL / PITCH BEND value_label readouts of the
181 // active frame from current state. Called after any change + on frame swap.
182 void update_readouts();
183 // Press/release of a tagged control (pitch-bend pb_down/pb_up momentary,
184 // modulation mod_N latched, sustain hold) — shared by the mouse-gesture and
185 // keyboard paths so on-screen buttons and keys behave identically.
186 void control_press(const std::string& tag);
187 void control_release(const std::string& tag);
188 // Re-light the selected modulation button (mod_sel_) and clear the others;
189 // call after a press auto-clears the momentary light on release.
190 void refresh_mod_lights();
191 // Light (on) or clear (off) every momentary control with `tag` — the tap-flash
192 // for octave/velocity/arrow buttons (mouse press or the z/x·c/v keys).
193 void flash_action(const std::string& tag, bool on);
194 // Last external held set from set_active_notes; re-applied after a frame swap
195 // so the new frame reflects the host's still-held notes.
196 std::vector<int> held_notes_;
197 // Light every momentary key in the ACTIVE frame whose absolute MIDI is in
198 // `held_notes_`; clear the rest. Shared by set_active_notes + frame swaps.
199 void apply_held_notes();
200 // Typing element (note == relative semitone 0..17) for a QWERTY semitone, or -1.
201 int typing_element_for_semitone(int semitone) const;
202 void light_typing_semitone(int semitone, bool on);
203};
204
205} // namespace pulp::view
Definition canvas.hpp:240
DesignFrameView(std::string svg, std::vector< DesignFrameElement > elements, float panel_x=-1, float panel_y=-1, float panel_w=-1, float panel_h=-1)
Definition musical_typing.hpp:37
int midi_for_element(int index) const
void on_mouse_drag(Point pos) override
bool input_capture() const
Definition musical_typing_keyboard.hpp:101
void paint(canvas::Canvas &canvas) override
void on_hover_move(Point pos) override
Pointer moved over this view with no button held, reported in this view's LOCAL coordinates.
MusicalTypingController & controller()
Definition musical_typing_keyboard.hpp:57
void on_active_frame_changed() override
void on_focus_changed(bool gained) override
Called when this view gains or loses focus.
void set_input_capture(bool capture)
Mode
Definition musical_typing_keyboard.hpp:46
@ typing
Definition musical_typing_keyboard.hpp:46
@ piano
Definition musical_typing_keyboard.hpp:46
std::function< void(float bend)> on_pitch_bend
Definition musical_typing_keyboard.hpp:74
std::function< void(bool on)> on_sustain
Definition musical_typing_keyboard.hpp:75
void on_mouse_down(Point pos) override
std::function< void(float w, float h)> on_intrinsic_size_changed
Definition musical_typing_keyboard.hpp:84
void on_mouse_up(Point pos) override
std::function< void(float amount)> on_modulation
Definition musical_typing_keyboard.hpp:76
std::function< void(int note, float velocity)> on_note_on
Definition musical_typing_keyboard.hpp:52
bool on_key_event(const KeyEvent &event) override
Key event with modifiers and up/down state.
bool wants_mouse_input() const override
True if this widget handles mouse down/drag through virtual methods even when it does not expose JS p...
Definition musical_typing_keyboard.hpp:128
int element_for_action(const std::string &tag) const
void set_active_notes(std::span< const int > midi_notes)
std::function< void(int note)> on_note_off
Definition musical_typing_keyboard.hpp:53
Computer-keyboard "musical typing" — maps the QWERTY row to MIDI notes so a plugin/standalone editor ...
Definition affine_transform.hpp:24
Hover-cursor tracking for platform window hosts.
Definition fwd.hpp:27
@ h
Definition input_events.hpp:39
@ w
Definition input_events.hpp:41
constexpr int kPianoFrame
Definition musical_typing_keyboard.hpp:15
constexpr int kTypingFrame
Definition musical_typing_keyboard.hpp:14
@ on
Definition accessibility.hpp:132
Rich key event with key code, modifiers, and repeat state.
Definition input_events.hpp:193
Definition geometry.hpp:20