old school music tracker
1use std::{io::Write, str::from_utf8};
2
3use font8x8::UnicodeFonts;
4use torque_tracker_engine::{
5 PlaybackSettings, audio_processing::playback::PlaybackPosition, project::pattern::Pattern,
6};
7
8use crate::{
9 coordinates::{CharPosition, CharRect},
10 draw_buffer::DrawBuffer,
11};
12
13#[derive(Debug, Clone)]
14pub enum HeaderEvent {
15 SetCursorRow(u16),
16 SetMaxCursorRow(u16),
17 SetPattern(u8),
18 SetMaxCursorPattern(u8),
19 SetOrder(u16),
20 SetOrderLen(u16),
21 SetSample(u8, Box<str>),
22 SetSpeed(usize),
23 SetTempo(usize),
24 SetPlayback(Option<PlaybackPosition>),
25}
26
27#[derive(Debug)]
28pub struct Header {
29 row: u16,
30 max_row: u16,
31 pattern: u8,
32 max_pattern: u8,
33 order: u16,
34 order_len: u16,
35 selected_sample: (u8, Box<str>),
36 playback: Option<PlaybackPosition>,
37}
38
39impl Default for Header {
40 fn default() -> Self {
41 Self {
42 row: 0,
43 max_row: Pattern::DEFAULT_ROWS,
44 pattern: 0,
45 max_pattern: 0,
46 order: 0,
47 order_len: 0,
48 selected_sample: (0, Box::from("")),
49 playback: None,
50 }
51 }
52}
53
54impl Header {
55 pub fn play_current_pattern(&self) -> PlaybackSettings {
56 PlaybackSettings::Pattern {
57 idx: self.pattern,
58 should_loop: true,
59 }
60 }
61
62 pub fn play_current_order(&self) -> PlaybackSettings {
63 PlaybackSettings::Order {
64 idx: self.order,
65 should_loop: true,
66 }
67 }
68
69 /// Header always needs a redraw after processing an event
70 pub fn process_event(&mut self, event: HeaderEvent) {
71 match event {
72 HeaderEvent::SetCursorRow(r) => self.row = r,
73 HeaderEvent::SetPattern(p) => self.pattern = p,
74 HeaderEvent::SetOrder(o) => self.order = o,
75 HeaderEvent::SetOrderLen(l) => self.order_len = l,
76 HeaderEvent::SetSample(i, n) => {
77 self.selected_sample.0 = i;
78 self.selected_sample.1 = n
79 }
80 HeaderEvent::SetSpeed(_) => todo!(),
81 HeaderEvent::SetTempo(_) => todo!(),
82 HeaderEvent::SetMaxCursorRow(r) => self.max_row = r,
83 HeaderEvent::SetMaxCursorPattern(p) => self.max_pattern = p,
84 HeaderEvent::SetPlayback(p) => self.playback = p,
85 }
86 }
87
88 pub fn draw(&self, draw_buffer: &mut DrawBuffer) {
89 let mut buf = [0; 3];
90 // row
91 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(&mut buf);
92 write!(&mut curse, "{:03}", self.row).unwrap();
93 draw_buffer.draw_string(from_utf8(&buf).unwrap(), CharPosition::new(12, 7), 5, 0);
94 // row max
95 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(&mut buf);
96 write!(&mut curse, "{:03}", self.max_row).unwrap();
97 draw_buffer.draw_string(from_utf8(&buf).unwrap(), CharPosition::new(16, 7), 5, 0);
98 // pattern
99 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(&mut buf);
100 write!(&mut curse, "{:03}", self.pattern).unwrap();
101 draw_buffer.draw_string(from_utf8(&buf).unwrap(), CharPosition::new(12, 6), 5, 0);
102 // max pattern
103 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(&mut buf);
104 write!(&mut curse, "{:03}", self.max_pattern).unwrap();
105 draw_buffer.draw_string(from_utf8(&buf).unwrap(), CharPosition::new(16, 6), 5, 0);
106
107 // order
108 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(&mut buf);
109 write!(&mut curse, "{:03}", self.order).unwrap();
110 draw_buffer.draw_string(from_utf8(&buf).unwrap(), CharPosition::new(12, 5), 5, 0);
111 // order len
112 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(&mut buf);
113 write!(&mut curse, "{:03}", self.order_len).unwrap();
114 draw_buffer.draw_string(from_utf8(&buf).unwrap(), CharPosition::new(16, 5), 5, 0);
115 // sample
116 draw_buffer.draw_string_length(&self.selected_sample.1, CharPosition::new(53, 3), 24, 5, 0);
117 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(&mut buf);
118 write!(&mut curse, "{:02}", self.selected_sample.0).unwrap();
119 draw_buffer.draw_string(
120 from_utf8(&buf[..2]).unwrap(),
121 CharPosition::new(50, 3),
122 5,
123 0,
124 );
125 // playback position
126 if let Some(position) = self.playback {
127 // Window width - 20
128 let mut buf = [b' '; 60];
129 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(&mut buf);
130 write!(&mut curse, "Playing, ").unwrap();
131 if let Some(order) = position.order {
132 write!(&mut curse, "Order: {}/{}, ", order, self.order_len).unwrap();
133 }
134 // TODO: figure out how to get the row count of the currently playing pattern in here
135 write!(
136 &mut curse,
137 "Pattern: {}, Row: {}",
138 position.pattern, position.row
139 )
140 .unwrap();
141 // TODO: add voice count
142 let string = from_utf8(&buf).unwrap();
143 for (index, char) in string.char_indices() {
144 let char_color = if char.is_ascii_digit() { 3 } else { 0 };
145 draw_buffer.draw_char(
146 font8x8::BASIC_FONTS.get(char).unwrap(),
147 CharPosition::new(2 + u8::try_from(index).unwrap(), 9),
148 char_color,
149 2,
150 );
151 }
152 } else {
153 draw_buffer.draw_rect(2, CharRect::new(9, 9, 2, 62));
154 }
155 }
156
157 pub fn draw_constant(&self, buffer: &mut DrawBuffer) {
158 buffer.draw_rect(2, CharRect::new(0, 11, 0, 79));
159 buffer.draw_string("Torque Tracker", CharPosition::new(34, 1), 0, 2);
160 buffer.draw_string("Song Name", CharPosition::new(2, 3), 0, 2);
161 buffer.draw_string("File Name", CharPosition::new(2, 4), 0, 2);
162 buffer.draw_string("Order", CharPosition::new(6, 5), 0, 2);
163 buffer.draw_string("Pattern", CharPosition::new(4, 6), 0, 2);
164 buffer.draw_string("Row", CharPosition::new(8, 7), 0, 2);
165 buffer.draw_string("Speed/Tempo", CharPosition::new(38, 4), 0, 2);
166 buffer.draw_string("Octave", CharPosition::new(43, 5), 0, 2);
167 buffer.draw_string(
168 "F1...Help F9.....Load",
169 CharPosition::new(21, 6),
170 0,
171 2,
172 );
173 buffer.draw_string(
174 "ESC..Main Menu F5/F8..Play / Stop",
175 CharPosition::new(21, 7),
176 0,
177 2,
178 );
179 buffer.draw_string("Time", CharPosition::new(63, 9), 0, 2);
180 buffer.draw_string("/", CharPosition::new(15, 5), 1, 0);
181 buffer.draw_string("/", CharPosition::new(15, 6), 1, 0);
182 buffer.draw_string("/", CharPosition::new(15, 7), 1, 0);
183 buffer.draw_string("/", CharPosition::new(53, 4), 1, 0);
184 buffer.draw_string(":", CharPosition::new(52, 3), 7, 0);
185 // TODO: Not actually constant as it changes between Sample and Instrument mode
186 buffer.draw_string("Sample", CharPosition::new(43, 3), 0, 2);
187 }
188
189 /// Returns the ID of the header root. This should be added as a child of the outer node
190 #[cfg(feature = "accesskit")]
191 pub fn build_tree(
192 &self,
193 tree: &mut Vec<(accesskit::NodeId, accesskit::Node)>,
194 ) -> accesskit::NodeId {
195 use accesskit::{Node, NodeId, Role};
196 const HEADER_ID: NodeId = NodeId(1);
197 let root = Node::new(Role::Header);
198
199 tree.push((HEADER_ID, root));
200 HEADER_ID
201 }
202}