old school music tracker
1use std::{io::Write, str::from_utf8};
2
3use torque_tracker_engine::project::{
4 event_command::NoteCommand,
5 note_event::{Note, NoteEvent, VolumeEffect},
6 pattern::{InPatternPosition, Pattern, PatternOperation},
7 song::{Song, SongOperation},
8};
9use winit::{
10 event_loop::EventLoopProxy,
11 keyboard::{Key, NamedKey, SmolStr},
12};
13
14use crate::{
15 app::{EXECUTOR, EventQueue, GlobalEvent, SONG_MANAGER, send_song_op},
16 coordinates::{CharPosition, CharRect},
17 ui::header::HeaderEvent,
18};
19
20use super::{Page, PageResponse};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23enum InEventPosition {
24 Note,
25 Octave,
26 Sample1,
27 Sample2,
28 VolPan1,
29 VolPan2,
30 Effect1,
31 Effect2,
32 Effect3,
33}
34
35impl InEventPosition {
36 fn to_right(self) -> Option<Self> {
37 match self {
38 InEventPosition::Note => Some(Self::Octave),
39 InEventPosition::Octave => Some(Self::Sample1),
40 InEventPosition::Sample1 => Some(Self::Sample2),
41 InEventPosition::Sample2 => Some(Self::VolPan1),
42 InEventPosition::VolPan1 => Some(Self::VolPan2),
43 InEventPosition::VolPan2 => Some(Self::Effect1),
44 InEventPosition::Effect1 => Some(Self::Effect2),
45 InEventPosition::Effect2 => Some(Self::Effect3),
46 InEventPosition::Effect3 => None,
47 }
48 }
49
50 fn to_left(self) -> Option<Self> {
51 match self {
52 InEventPosition::Note => None,
53 InEventPosition::Octave => Some(Self::Note),
54 InEventPosition::Sample1 => Some(Self::Octave),
55 InEventPosition::Sample2 => Some(Self::Sample1),
56 InEventPosition::VolPan1 => Some(Self::Sample2),
57 InEventPosition::VolPan2 => Some(Self::VolPan1),
58 InEventPosition::Effect1 => Some(Self::VolPan2),
59 InEventPosition::Effect2 => Some(Self::Effect1),
60 InEventPosition::Effect3 => Some(Self::Effect2),
61 }
62 }
63}
64
65fn get_note_from_key(key: &winit::keyboard::SmolStr, octave: u8) -> Option<Note> {
66 // TODO: keyboard layouts fuckk me.
67 #[allow(clippy::identity_op, clippy::zero_prefixed_literal)]
68 let note = match key.as_str() {
69 // lower octave
70 "z" => Some(Note::new(00 + 12 * (octave - 1))), // C-1
71 "s" => Some(Note::new(01 + 12 * (octave - 1))),
72 "x" => Some(Note::new(02 + 12 * (octave - 1))),
73 "d" => Some(Note::new(03 + 12 * (octave - 1))),
74 "c" => Some(Note::new(04 + 12 * (octave - 1))),
75 "v" => Some(Note::new(05 + 12 * (octave - 1))),
76 "g" => Some(Note::new(06 + 12 * (octave - 1))),
77 "b" => Some(Note::new(07 + 12 * (octave - 1))),
78 "h" => Some(Note::new(08 + 12 * (octave - 1))),
79 "n" => Some(Note::new(09 + 12 * (octave - 1))),
80 "j" => Some(Note::new(10 + 12 * (octave - 1))),
81 "m" => Some(Note::new(11 + 12 * (octave - 1))),
82 // base octave
83 "q" => Some(Note::new(00 + 12 * octave)), // C
84 "2" => Some(Note::new(01 + 12 * octave)), // Db / C#
85 "w" => Some(Note::new(02 + 12 * octave)), // D
86 "3" => Some(Note::new(03 + 12 * octave)), // Eb / D#
87 "e" => Some(Note::new(04 + 12 * octave)), // E
88 "r" => Some(Note::new(05 + 12 * octave)), // F
89 "5" => Some(Note::new(06 + 12 * octave)), // Gb / F#
90 "t" => Some(Note::new(07 + 12 * octave)), // G
91 "6" => Some(Note::new(08 + 12 * octave)), // Ab / G#
92 "y" => Some(Note::new(09 + 12 * octave)), // A
93 "7" => Some(Note::new(10 + 12 * octave)), // Bb / A#
94 "u" => Some(Note::new(11 + 12 * octave)), // B
95 // higher octave
96 "i" => Some(Note::new(0 + 12 * (octave + 1))), // C+1
97 "9" => Some(Note::new(1 + 12 * (octave + 1))),
98 "o" => Some(Note::new(2 + 12 * (octave + 1))),
99 "0" => Some(Note::new(3 + 12 * (octave + 1))),
100 "p" => Some(Note::new(4 + 12 * (octave + 1))),
101 _ => None,
102 };
103 note.and_then(Result::ok)
104}
105
106#[derive(Debug, Clone)]
107pub enum PatternPageEvent {
108 Loaded(Pattern, u8),
109 SetSampleInstr(u8),
110 /// pattern, row
111 PlaybackPosition(Option<(u8, u16)>),
112}
113
114#[derive(Debug)]
115pub struct PatternPage {
116 pattern_index: u8,
117 pattern: Pattern,
118 cursor_position: (InPatternPosition, InEventPosition),
119 draw_position: InPatternPosition,
120 event_proxy: EventLoopProxy<GlobalEvent>,
121 /// Last used or last selected in the sample menu
122 selected_sample_instr: u8,
123 /// pattern, row
124 // storest the pattern index, because if i switch page i want to show the current position before i
125 // get the next event
126 playback: Option<(u8, u16)>,
127}
128
129impl PatternPage {
130 const MAX_PATTERN: usize = 199;
131 const DRAWN_ROWS: u16 = 32;
132 const DRAWN_CHANNELS: u8 = 5;
133 const MAX_CHANNELS: u8 = 64;
134 /// how many rows the cursor is moved when pressing pageup/down
135 // TODO: make configurable
136 const PAGE_AS_ROWS: u16 = 16;
137 const CHANNEL_WIDTH: usize = 14;
138
139 // TODO: make configurable
140 const ROW_HIGHTLIGHT_MINOR: u16 = 4;
141 const ROW_HIGHTLIGHT_MAJOR: u16 = 16;
142
143 pub fn process_event(
144 &mut self,
145 event: PatternPageEvent,
146 events: &mut EventQueue<'_>,
147 ) -> PageResponse {
148 match event {
149 PatternPageEvent::Loaded(pattern, idx) => {
150 self.pattern = pattern;
151 self.pattern_index = idx;
152 events.push(GlobalEvent::Header(HeaderEvent::SetPattern(idx)));
153 events.push(GlobalEvent::Header(HeaderEvent::SetMaxCursorRow(
154 self.pattern.row_count(),
155 )));
156 PageResponse::RequestRedraw
157 }
158 PatternPageEvent::SetSampleInstr(i) => {
159 self.selected_sample_instr = i;
160 PageResponse::None
161 }
162 PatternPageEvent::PlaybackPosition(p) => {
163 self.playback = p;
164 // TODO: only return this if the change is actually visible
165 PageResponse::RequestRedraw
166 }
167 }
168 }
169
170 pub fn new(proxy: EventLoopProxy<GlobalEvent>) -> Self {
171 Self {
172 pattern_index: 0,
173 pattern: Pattern::default(),
174 cursor_position: (
175 InPatternPosition { row: 0, channel: 0 },
176 InEventPosition::Note,
177 ),
178 draw_position: InPatternPosition { row: 0, channel: 0 },
179 event_proxy: proxy,
180 selected_sample_instr: 0,
181 playback: None,
182 }
183 }
184
185 /// returns true if the position was changed
186 fn set_cursor(&mut self, mut pos: InPatternPosition, events: &mut EventQueue<'_>) -> bool {
187 if pos.row >= self.pattern.row_count() {
188 pos.row = self.pattern.row_count() - 1;
189 }
190 if pos.channel >= Self::MAX_CHANNELS {
191 pos.channel = Self::MAX_CHANNELS - 1;
192 }
193
194 if pos == self.cursor_position.0 {
195 return false;
196 }
197
198 if pos.row != self.cursor_position.0.row {
199 events.push(GlobalEvent::Header(HeaderEvent::SetCursorRow(pos.row)));
200 }
201
202 self.cursor_position.0 = pos;
203
204 // update draw position
205 if pos.channel >= self.draw_position.channel + Self::DRAWN_CHANNELS {
206 self.draw_position.channel = pos.channel - Self::DRAWN_CHANNELS + 1;
207 } else if pos.channel < self.draw_position.channel {
208 self.draw_position.channel = pos.channel
209 }
210
211 if pos.row <= (Self::DRAWN_ROWS / 2) {
212 self.draw_position.row = 0;
213 } else if pos.row >= self.pattern.row_count() - (Self::DRAWN_ROWS / 2) {
214 self.draw_position.row = self.pattern.row_count() - Self::DRAWN_ROWS
215 } else {
216 self.draw_position.row = pos.row - (Self::DRAWN_ROWS / 2);
217 }
218
219 true
220 }
221
222 /// returns true if the cursor was changed
223 fn cursor_next_row(&mut self, events: &mut EventQueue<'_>) -> bool {
224 let mut pos = self.cursor_position.0;
225 pos.row = pos.row.saturating_add(1);
226 self.set_cursor(pos, events)
227 }
228
229 fn load_pattern(&mut self, idx: u8) {
230 let proxy = self.event_proxy.clone();
231 EXECUTOR
232 .spawn(async move {
233 let lock = SONG_MANAGER.lock().await;
234 let pattern = lock.get_song().patterns[usize::from(idx)].clone();
235 drop(lock);
236 proxy
237 .send_event(GlobalEvent::Page(super::PageEvent::Pattern(
238 PatternPageEvent::Loaded(pattern, idx),
239 )))
240 .unwrap();
241 })
242 .detach();
243 }
244
245 fn set_event(&mut self, position: InPatternPosition, event: NoteEvent) {
246 self.pattern.set_event(position, event);
247 let op = SongOperation::PatternOperation(
248 self.pattern_index,
249 PatternOperation::SetEvent { position, event },
250 );
251 send_song_op(op);
252 }
253
254 fn remove_event(&mut self, position: InPatternPosition) {
255 self.pattern.remove_event(position);
256 let op = SongOperation::PatternOperation(
257 self.pattern_index,
258 PatternOperation::RemoveEvent { position },
259 );
260 send_song_op(op);
261 }
262
263 pub fn set_sample(&mut self, sample: u8, events: &mut EventQueue<'_>) {
264 self.selected_sample_instr = sample;
265 events.push(GlobalEvent::Page(super::PageEvent::SampleList(
266 super::sample_list::SampleListEvent::SelectSample(sample),
267 )));
268 }
269}
270
271impl Page for PatternPage {
272 fn draw(&mut self, draw_buffer: &mut super::DrawBuffer) {
273 // helper fns
274 fn visible_channels(page: &PatternPage) -> impl Iterator<Item = (usize, u8)> {
275 (page.draw_position.channel..page.draw_position.channel + PatternPage::DRAWN_CHANNELS)
276 .enumerate()
277 }
278
279 fn visible_rows(page: &PatternPage) -> impl Iterator<Item = (usize, u16)> {
280 (page.draw_position.row..page.draw_position.row + PatternPage::DRAWN_ROWS).enumerate()
281 }
282
283 // draw row numbers
284 assert!(self.draw_position.row + Self::DRAWN_ROWS <= 999);
285 let mut buf = [0; 3];
286 for (index, value) in visible_rows(self) {
287 const BASE_POS: CharPosition = CharPosition::new(1, 15);
288 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(&mut buf);
289 write!(&mut curse, "{:03}", value).unwrap();
290 let text_color = if self.playback == Some((self.pattern_index, value)) {
291 3
292 } else {
293 0
294 };
295 draw_buffer.draw_string(
296 from_utf8(&buf).unwrap(),
297 BASE_POS + CharPosition::new(0, index),
298 text_color,
299 2,
300 );
301 }
302
303 // draw channel headings
304 assert!(self.draw_position.channel + Self::DRAWN_CHANNELS <= 99);
305 let mut buf: [u8; 2] = [0; 2];
306 for (index, value) in visible_channels(self) {
307 const BASE_POS: CharPosition = CharPosition::new(14, 14);
308
309 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(&mut buf);
310 write!(&mut curse, "{:02}", value).unwrap();
311
312 draw_buffer.draw_string(
313 from_utf8(&buf).unwrap(),
314 BASE_POS + (index * Self::CHANNEL_WIDTH, 0),
315 3,
316 1,
317 );
318 }
319
320 // draw events
321 const BLOCK_CODE: [u8; 8] = [0b0, 0b0, 0b0, 0b11000, 0b11000, 0b0, 0b0, 0b0];
322 struct EventView {
323 note1: [u8; 8],
324 note2: [u8; 8],
325 octave: [u8; 8],
326 sample1: [u8; 8],
327 sample2: [u8; 8],
328 vol_pan1: [u8; 8],
329 vol_pan2: [u8; 8],
330 effect1: [u8; 8],
331 effect2: [u8; 8],
332 effect3: [u8; 8],
333 }
334
335 impl Default for EventView {
336 fn default() -> Self {
337 Self {
338 note1: BLOCK_CODE,
339 note2: BLOCK_CODE,
340 octave: BLOCK_CODE,
341 sample1: BLOCK_CODE,
342 sample2: BLOCK_CODE,
343 vol_pan1: BLOCK_CODE,
344 vol_pan2: BLOCK_CODE,
345 effect1: font8x8::UnicodeFonts::get(&font8x8::BASIC_FONTS, '.').unwrap(),
346 effect2: font8x8::UnicodeFonts::get(&font8x8::BASIC_FONTS, '0').unwrap(),
347 effect3: font8x8::UnicodeFonts::get(&font8x8::BASIC_FONTS, '0').unwrap(),
348 }
349 }
350 }
351
352 impl From<NoteEvent> for EventView {
353 fn from(value: NoteEvent) -> Self {
354 fn get_bitmap_digit(value: u8) -> [u8; 8] {
355 let char = match value {
356 0 => '0',
357 1 => '1',
358 2 => '2',
359 3 => '3',
360 4 => '4',
361 5 => '5',
362 6 => '6',
363 7 => '7',
364 8 => '8',
365 9 => '9',
366 _ => panic!("not a digit. number too large"),
367 };
368 font8x8::UnicodeFonts::get(&font8x8::BASIC_FONTS, char).unwrap()
369 }
370
371 let mut view = Self::default();
372 // note
373 let mut note_chars = value.note.get_note_name().chars();
374 let first = note_chars.next().unwrap();
375 let second = note_chars.next().unwrap_or('-');
376 view.note1 = font8x8::UnicodeFonts::get(&font8x8::BASIC_FONTS, first).unwrap();
377 view.note2 = font8x8::UnicodeFonts::get(&font8x8::BASIC_FONTS, second).unwrap();
378
379 view.octave = get_bitmap_digit(value.note.get_octave());
380 // sample_instr number
381 view.sample1 = get_bitmap_digit(value.sample_instr / 10);
382 view.sample2 = get_bitmap_digit(value.sample_instr % 10);
383 // TODO: rest noch
384 view
385 }
386 }
387
388 const EVENT_BASE_POS: CharPosition = CharPosition::new(5, 15);
389 const BACKGROUND: u8 = 0;
390 const FOREGROUND: u8 = 6;
391 for (c_idx, c_val) in visible_channels(self) {
392 for (r_idx, r_val) in visible_rows(self) {
393 let background_color = match r_val {
394 val if val == self.cursor_position.0.row => 1,
395 val if val % Self::ROW_HIGHTLIGHT_MAJOR == 0 => 14,
396 val if val % Self::ROW_HIGHTLIGHT_MINOR == 0 => 15,
397 _ => BACKGROUND,
398 };
399 let view: EventView = self
400 .pattern
401 .get_event(InPatternPosition {
402 row: r_val,
403 channel: c_val,
404 })
405 .map(|e| (*e).into())
406 .unwrap_or_default();
407 let pos = EVENT_BASE_POS + (c_idx * Self::CHANNEL_WIDTH, r_idx);
408 draw_buffer.draw_char(view.note1, pos, 6, background_color);
409 draw_buffer.draw_char(view.note2, pos + (1, 0), FOREGROUND, background_color);
410 draw_buffer.draw_char(view.octave, pos + (2, 0), FOREGROUND, background_color);
411 draw_buffer.draw_rect(background_color, (pos + (3, 0)).into());
412 draw_buffer.draw_char(view.sample1, pos + (4, 0), FOREGROUND, background_color);
413 draw_buffer.draw_char(view.sample2, pos + (5, 0), FOREGROUND, background_color);
414 draw_buffer.draw_rect(background_color, (pos + (6, 0)).into());
415 draw_buffer.draw_char(view.vol_pan1, pos + (7, 0), FOREGROUND, background_color);
416 draw_buffer.draw_char(view.vol_pan2, pos + (8, 0), FOREGROUND, background_color);
417 draw_buffer.draw_rect(background_color, (pos + (9, 0)).into());
418 draw_buffer.draw_char(view.effect1, pos + (10, 0), FOREGROUND, background_color);
419 draw_buffer.draw_char(view.effect2, pos + (11, 0), FOREGROUND, background_color);
420 draw_buffer.draw_char(view.effect3, pos + (12, 0), FOREGROUND, background_color);
421 }
422 }
423
424 // draw cursor
425 let view: EventView = self
426 .pattern
427 .get_event(self.cursor_position.0)
428 .map(|e| (*e).into())
429 .unwrap_or_default();
430 assert!(self.cursor_position.0.channel >= self.draw_position.channel);
431 assert!(self.cursor_position.0.row >= self.draw_position.row);
432 let c_idx = self.cursor_position.0.channel - self.draw_position.channel;
433 let r_idx = self.cursor_position.0.row - self.draw_position.row;
434 let pos = EVENT_BASE_POS + (c_idx as usize * Self::CHANNEL_WIDTH, r_idx as usize);
435 match self.cursor_position.1 {
436 InEventPosition::Note => draw_buffer.draw_char(view.note1, pos, 0, 3),
437 InEventPosition::Octave => draw_buffer.draw_char(view.octave, pos + (2, 0), 0, 3),
438 InEventPosition::Sample1 => draw_buffer.draw_char(view.sample1, pos + (4, 0), 0, 3),
439 InEventPosition::Sample2 => draw_buffer.draw_char(view.sample2, pos + (5, 0), 0, 3),
440 InEventPosition::VolPan1 => draw_buffer.draw_char(view.vol_pan1, pos + (7, 0), 0, 3),
441 InEventPosition::VolPan2 => draw_buffer.draw_char(view.vol_pan2, pos + (8, 0), 0, 3),
442 InEventPosition::Effect1 => draw_buffer.draw_char(view.effect1, pos + (10, 0), 0, 3),
443 InEventPosition::Effect2 => draw_buffer.draw_char(view.effect2, pos + (11, 0), 0, 3),
444 InEventPosition::Effect3 => draw_buffer.draw_char(view.effect3, pos + (12, 0), 0, 3),
445 }
446 }
447
448 fn draw_constant(&mut self, draw_buffer: &mut super::DrawBuffer) {
449 draw_buffer.draw_rect(2, CharRect::PAGE_AREA);
450
451 // draw channel headers const parts
452 for index in 0..Self::DRAWN_CHANNELS as usize {
453 const BASE_POS: CharPosition = CharPosition::new(5, 14);
454 let pos = BASE_POS + (index * Self::CHANNEL_WIDTH, 0);
455 draw_buffer.draw_rect(1, (pos + (11, 0)).into());
456
457 draw_buffer.draw_string(" Channel ", pos, 3, 1);
458 }
459 }
460
461 fn process_key_event(
462 &mut self,
463 modifiers: &winit::event::Modifiers,
464 key_event: &winit::event::KeyEvent,
465 events: &mut EventQueue<'_>,
466 ) -> PageResponse {
467 if !key_event.state.is_pressed() {
468 return PageResponse::None;
469 }
470
471 if key_event.logical_key == Key::Character(SmolStr::new_static("+")) {
472 if usize::from(self.pattern_index) != Self::MAX_PATTERN {
473 self.load_pattern(self.pattern_index + 1);
474 return PageResponse::RequestRedraw;
475 }
476 } else if key_event.logical_key == Key::Character(SmolStr::new_static("-")) {
477 if self.pattern_index != 0 {
478 self.load_pattern(self.pattern_index - 1);
479 return PageResponse::RequestRedraw;
480 }
481 } else if key_event.logical_key == Key::Named(NamedKey::ArrowDown) {
482 if self.cursor_next_row(events) {
483 return PageResponse::RequestRedraw;
484 }
485 } else if key_event.logical_key == Key::Named(NamedKey::ArrowUp) {
486 let mut pos = self.cursor_position.0;
487 pos.row = pos.row.saturating_sub(1);
488 if self.set_cursor(pos, events) {
489 return PageResponse::RequestRedraw;
490 }
491 } else if key_event.logical_key == Key::Named(NamedKey::ArrowRight) {
492 match self.cursor_position.1.to_right() {
493 Some(p) => {
494 self.cursor_position.1 = p;
495 return PageResponse::RequestRedraw;
496 }
497 None => {
498 let mut pos = self.cursor_position.0;
499 pos.channel = pos.channel.saturating_add(1);
500 if self.set_cursor(pos, events) {
501 self.cursor_position.1 = InEventPosition::Note;
502 return PageResponse::RequestRedraw;
503 }
504 }
505 }
506 } else if key_event.logical_key == Key::Named(NamedKey::ArrowLeft) {
507 match self.cursor_position.1.to_left() {
508 Some(p) => {
509 self.cursor_position.1 = p;
510 return PageResponse::RequestRedraw;
511 }
512 None => {
513 let mut pos = self.cursor_position.0;
514 pos.channel = pos.channel.saturating_sub(1);
515 if self.set_cursor(pos, events) {
516 self.cursor_position.1 = InEventPosition::Effect3;
517 return PageResponse::RequestRedraw;
518 }
519 }
520 }
521 } else if key_event.logical_key == Key::Named(NamedKey::Tab) {
522 // shift => move left
523 // not shift => move right
524 if modifiers.state().shift_key() {
525 if self.cursor_position.1 == InEventPosition::Note {
526 let mut pos = self.cursor_position.0;
527 pos.channel = pos.channel.saturating_sub(1);
528 if self.set_cursor(pos, events) {
529 return PageResponse::RequestRedraw;
530 }
531 } else {
532 self.cursor_position.1 = InEventPosition::Note;
533 return PageResponse::RequestRedraw;
534 }
535 } else {
536 let mut pos = self.cursor_position.0;
537 pos.channel = pos.channel.saturating_add(1);
538 self.set_cursor(pos, events);
539 self.cursor_position.1 = InEventPosition::Note;
540 return PageResponse::RequestRedraw;
541 }
542 } else if key_event.logical_key == Key::Named(NamedKey::PageDown) {
543 let mut pos = self.cursor_position.0;
544 pos.row = pos.row.saturating_add(Self::PAGE_AS_ROWS);
545 if self.set_cursor(pos, events) {
546 return PageResponse::RequestRedraw;
547 }
548 } else if key_event.logical_key == Key::Named(NamedKey::PageUp) {
549 // original has special behaviour on page up:
550 // when on last row it only goes up 15 rows
551 let mut pos = self.cursor_position.0;
552 pos.row = pos.row.saturating_sub(Self::PAGE_AS_ROWS);
553 if self.set_cursor(pos, events) {
554 return PageResponse::RequestRedraw;
555 }
556 } else if Key::Character(SmolStr::new_static(".")) == key_event.logical_key {
557 self.remove_event(self.cursor_position.0);
558 self.cursor_next_row(events);
559 return PageResponse::RequestRedraw;
560 } else if let Key::Character(char) = &key_event.logical_key {
561 // should be copied from the header, where this can already be set
562 const DEFAULT_OCTAVE: u8 = 5;
563 match self.cursor_position.1 {
564 InEventPosition::Note => {
565 // TODO: make octave configurable
566 if let Some(note) = get_note_from_key(char, DEFAULT_OCTAVE) {
567 let old_event = self.pattern.get_event(self.cursor_position.0);
568 let event = match old_event {
569 None => NoteEvent {
570 note,
571 sample_instr: self.selected_sample_instr,
572 vol: VolumeEffect::None,
573 command: NoteCommand::None,
574 },
575 Some(&event) => NoteEvent { note, ..event },
576 };
577 self.set_event(self.cursor_position.0, event);
578 }
579 // move to next row even if
580 self.cursor_next_row(events);
581 // always redraw is incorrect. I only need to redraw if either the cursor moved, or the event changed
582 return PageResponse::RequestRedraw;
583 }
584 InEventPosition::Octave => {
585 if let Some(event) = self.pattern.get_event(self.cursor_position.0) {
586 let mut new_event = *event;
587 // set octave fn needed
588 let octave: Result<u8, _> = char.as_str().parse();
589 if let Ok(octave) = octave {
590 new_event.note =
591 Note::new(event.note.get() % 12 + 12 * octave).unwrap();
592 self.set_event(self.cursor_position.0, new_event);
593 }
594 }
595 self.cursor_next_row(events);
596 // always redraw is incorrect.
597 return PageResponse::RequestRedraw;
598 }
599 InEventPosition::Sample1 => {
600 let num: Result<u8, _> = char.as_str().parse();
601 if let Ok(num) = num
602 && let Some(event) = self.pattern.get_event(self.cursor_position.0).copied()
603 {
604 let zeros = event.sample_instr % 10;
605 let sample_instr = zeros + num * 10;
606 if usize::from(sample_instr) < Song::MAX_SAMPLES_INSTR {
607 self.set_event(
608 self.cursor_position.0,
609 NoteEvent {
610 sample_instr,
611 ..event
612 },
613 );
614 self.set_sample(sample_instr, events);
615 }
616 }
617 // move cursor one step right
618 self.cursor_position.1 = InEventPosition::Sample2;
619 return PageResponse::RequestRedraw;
620 }
621 InEventPosition::Sample2 => {
622 let num: Result<u8, _> = char.as_str().parse();
623 if let Ok(num) = num
624 && let Some(event) = self.pattern.get_event(self.cursor_position.0).copied()
625 {
626 let tens = event.sample_instr / 10;
627 let sample_instr = tens * 10 + num;
628 if usize::from(sample_instr) < Song::MAX_SAMPLES_INSTR {
629 self.set_event(
630 self.cursor_position.0,
631 NoteEvent {
632 sample_instr,
633 ..event
634 },
635 );
636 self.set_sample(sample_instr, events);
637 }
638 }
639 self.cursor_next_row(events);
640 return PageResponse::RequestRedraw;
641 }
642 InEventPosition::VolPan1 => eprintln!("not yet implemented"),
643 InEventPosition::VolPan2 => eprintln!("not yet implemented"),
644 InEventPosition::Effect1 => eprintln!("not yet implemented"),
645 InEventPosition::Effect2 => eprintln!("not yet implemented"),
646 InEventPosition::Effect3 => eprintln!("not yet implemented"),
647 }
648 }
649
650 PageResponse::None
651 }
652}