Merge pull request #407 from vbasky/fix/midi-editor-tool-insertion

Require the Pencil tool to insert MIDI notes
This commit is contained in:
Brandon Thomas authored and GitHub committed 2026-10-13 06:53:29 -04:00
commit ddb874e399
2 files changed
+35 -3

No files matched your search

+4 -3
View File
@@ -4,7 +4,7 @@ use crate::SoundApp;
use crate::theme::{Tokens, bold, regular};
use egui::{Align2, Color32, Rect, Sense, Stroke, StrokeKind, Ui, pos2, vec2};
use serde_json::json;
use soundcraft_model::{ClipContent, ClipId};
use soundcraft_model::{ClipContent, ClipId, Tool};
use soundcraft_time::TICKS_PER_QUARTER;
const KEY_W: f32 = 48.0;
@@ -93,7 +93,7 @@ pub fn show(app: &mut SoundApp, ui: &mut Ui) {
pos2(header.min.x + 110.0, header.center().y),
Align2::LEFT_CENTER,
format!(
"{} · {} notes · {} selected · click: add · drag: move/select · arrows: move · Delete: remove",
"{} · {} notes · {} selected · Pencil: add · drag: move/select · arrows: move · Delete: remove",
clip.name,
sequence.notes.len(),
app.midi.selected.len()
@@ -303,7 +303,7 @@ pub fn show(app: &mut SoundApp, ui: &mut Ui) {
}
// Audition on the instrument would go here (needs a live MIDI input path).
}
None => {
None if s.edit.tool == Tool::Pencil => {
let pitch = pitch_at(p.y).clamp(0, 127);
let start = (((p.x - roll.min.x) / px_per_tick) as i64 / grid_ticks) * grid_ticks;
let _ = app.run(
@@ -312,6 +312,7 @@ pub fn show(app: &mut SoundApp, ui: &mut Ui) {
);
app.midi.selected.clear();
}
None => app.midi.selected.clear(),
}
}
// Keyboard: Cmd+A select all; arrows transpose (Shift = octave) and move by the grid.
+31
View File
@@ -170,3 +170,34 @@ fn a_selected_video_clip_is_not_a_midi_target() {
let (_, clip) = app.engine.session().find_clip(midi).unwrap();
assert!(matches!(clip.content, ClipContent::Midi { .. }));
}
#[test]
fn only_pencil_inserts_notes_in_the_midi_roll() {
use soundcraft_model::{ClipContent, Tool};
let mut app = SoundApp::new(soundcraft_engine::demo::demo_engine(), None, Services::default());
app.ui.show_midi_editor = true;
let created = app.run("midi.new_clip", json!({"track": "Keys", "start": 0, "length": 96000})).unwrap();
let clip = created["clip"].as_u64().unwrap();
app.run("edit.select", json!({"clips": [clip]})).unwrap();
let ctx = egui::Context::default();
for _ in 0..6 {
frame(&ctx, &mut app, vec![]);
}
let body = panel(&ctx);
let point = pos2(body.left() + 200.0, body.top() + 60.0);
for tool in Tool::ALL.into_iter().filter(|tool| *tool != Tool::Pencil).chain([Tool::Pencil]) {
app.run("edit.tool", json!({"tool": tool.id()})).unwrap();
let before = app.engine.session().clone();
frame(&ctx, &mut app, vec![Event::PointerMoved(point)]);
frame(&ctx, &mut app, vec![button(point, true)]);
frame(&ctx, &mut app, vec![button(point, false)]);
let (_, edited) = app.engine.session().find_clip(soundcraft_model::ClipId(clip)).unwrap();
let ClipContent::Midi { sequence } = &edited.content else { panic!("MIDI clip required") };
if tool == Tool::Pencil {
assert_eq!(sequence.notes.len(), 1);
app.run("edit.undo", json!({})).unwrap();
}
assert!(*app.engine.session() == before, "{tool:?} click must not change the session");
}
}