)>,
15 | // x/y/w/h, op_i, in_idx
16 | active_zones: Vec<([f32; 4], usize, usize)>,
17 | highlight: Option<(usize, usize)>,
18 | selection: Option<(usize, usize)>,
19 | orig_val: f32,
20 | pub scroll_offs: (usize, usize),
21 | }
22 |
23 | fn draw_op(p: &mut P, op: &(OpIOSpec, OpInfo), highlight: &Option<(usize, usize)>, selection: &Option<(usize, usize)>) -> (f32, f32, Vec<([f32; 4], usize, usize)>)
24 | where P: GUIPainter {
25 | let inp_col_w : f32 = 180.0;
26 | let inp_col_wr: f32 = 70.0;
27 | let padding : f32 = 2.0;
28 | let text_h : f32 = 12.0;
29 |
30 | let mut io_lens = op.0.input_values.len();
31 | if op.0.output_regs.len() > io_lens {
32 | io_lens = op.0.output_regs.len();
33 | }
34 |
35 | let op_h = (1 + io_lens) as f32 * text_h + padding * 2.0;
36 | let op_w = padding + inp_col_w + padding + inp_col_wr;
37 |
38 | p.draw_rect(
39 | [0.2, 0.2, 0.2, 1.0], [0.0, 0.0], [op_w, op_h], true, 0.1);
40 | p.draw_rect(
41 | [1.0, 0.0, 1.0, 1.0], [0.0, 0.0], [op_w, op_h], false, 0.5);
42 |
43 | p.add_offs(padding, padding);
44 |
45 | p.draw_text(
46 | [0.3, 1.0, 0.8, 1.0], [0.0, 0.0], text_h, format!("{}", op.1.name));
47 | p.draw_text(
48 | [1.0, 0.3, 0.3, 1.0], [inp_col_w - (text_h + padding), 0.0], text_h, "IN".to_string());
49 | p.draw_text(
50 | [0.3, 1.0, 0.3, 1.0], [inp_col_w + padding, 0.0], text_h, "OUT".to_string());
51 |
52 | let mut y = text_h;
53 |
54 | let mut active_zones : Vec<([f32; 4], usize, usize)> = Vec::new();
55 |
56 | for (idx, (i, is)) in op.0.input_values.iter().zip(op.0.inputs.iter()).enumerate() {
57 | let text = match i {
58 | OpIn::Constant(v) => {
59 | format!("{:>8.3}", *v)
60 | },
61 | OpIn::Reg(u) =>
62 | format!("r{}", *u),
63 | OpIn::RegMix2(u, u2, f) =>
64 | format!("r{}x{:0.2}[{:0.2}]", *u, *u2, *f),
65 | OpIn::RegAdd(u, f) =>
66 | format!("r{}+[{:0.2}]", *u, *f),
67 | OpIn::RegMul(u, f) =>
68 | format!("r{}*[{:0.2}]", *u, *f),
69 | OpIn::RegAddMul(u, f, f2) =>
70 | format!("(r{}+[{:0.2}])*[{:0.2}]", *u, *f, *f2),
71 | OpIn::RegMulAdd(u, f, f2) =>
72 | format!("(r{}*[{:0.2}])+[{:0.2}]", *u, *f, *f2),
73 | OpIn::RegLerp(u, f, f2) =>
74 | format!("r{}/[{:0.2}][{:0.2}]", *u, *f, *f2),
75 | OpIn::RegSStep(u, f, f2) =>
76 | format!("r{}~[{:0.2}][{:0.2}]", *u, *f, *f2),
77 | OpIn::RegMap(u, f, f2, g, g2) =>
78 | format!("r{}[{:0.2}-{:0.2}]->[{:0.2}-{:0.2}]", *u, *f, *f2, *g, *g2),
79 | };
80 |
81 | let o = p.get_offs();
82 | active_zones.push(([o.0, o.1 + y, inp_col_w, text_h], op.0.index, idx));
83 |
84 | let mut highlighted = if let Some((op_idx, i_idx)) = highlight {
85 | *op_idx == op.0.index && idx == *i_idx
86 | } else {
87 | false
88 | };
89 |
90 | // XXX: Because the mouse cursor is repositioned, we would
91 | // get flickering on neighbour elements.
92 | if selection.is_some() {
93 | highlighted = false;
94 | }
95 |
96 | let selected = if let Some((op_idx, i_idx)) = selection {
97 | *op_idx == op.0.index && idx == *i_idx
98 | } else {
99 | false
100 | };
101 |
102 | p.draw_rect(
103 | if selected { [1.0, 0.2, 0.2, 1.0] }
104 | else { [0.4, 0.4, 0.4, 1.0] },
105 | [0.0, y],
106 | [inp_col_w - 1.0, text_h - 1.0],
107 | !selected && highlighted,
108 | 0.5);
109 |
110 | p.draw_text(
111 | [1.0, 0.3, 0.8, 1.0], [0.0, y], text_h,
112 | format!("{:<7} {}", is.name, text));
113 |
114 | y += text_h;
115 | }
116 |
117 | y = text_h;
118 | for (o, os) in op.0.output_regs.iter().zip(op.0.outputs.iter()) {
119 | p.draw_text(
120 | [1.0, 0.3, 0.8, 1.0], [inp_col_w + padding, y], text_h,
121 | format!("{:<7} r{}", os.name, o));
122 | y += text_h;
123 | }
124 |
125 | p.add_offs(0.0, -padding);
126 | p.draw_lines(
127 | [1.0, 0.0, 1.0, 1.0],
128 | [inp_col_w, 0.0],
129 | &vec![[0.0, 0.0], [0.0, op_h]],
130 | false,
131 | 0.5);
132 |
133 | (op_w, op_h, active_zones)
134 | }
135 |
136 | impl OperatorInputSettings {
137 | pub fn new(simcom: SimulatorCommunicator) -> Self {
138 | OperatorInputSettings {
139 | simcom: simcom,
140 | specs: Vec::new(),
141 | groups: Vec::new(),
142 | active_zones: Vec::new(),
143 | highlight: None,
144 | selection: None,
145 | orig_val: 0.0,
146 | scroll_offs: (0, 0),
147 | }
148 | }
149 |
150 | pub fn save_input_values(&mut self) -> Vec<(String, Vec<(String, OpIn)>)> {
151 | self.simcom.save_input_values()
152 | }
153 |
154 | pub fn load_input_values(&mut self, inputs: &Vec<(String, Vec<(String, OpIn)>)>) {
155 | self.simcom.load_input_values(inputs);
156 | }
157 |
158 | pub fn hit_zone(&mut self, x: f32, y: f32) -> Option<(usize, usize)> {
159 | for az in self.active_zones.iter() {
160 | if x >= (az.0)[0]
161 | && y >= (az.0)[1]
162 | && x <= ((az.0)[0] + (az.0)[2])
163 | && y <= ((az.0)[1] + (az.0)[3]) {
164 |
165 | return Some((az.1, az.2));
166 | }
167 | }
168 |
169 | None
170 | }
171 |
172 | pub fn handle_mouse_move(&mut self, x: f32, y: f32, xr: f32, yr: f32, button_is_down: bool) -> bool {
173 | if !button_is_down { self.selection = None; }
174 |
175 | let old_highlight = self.highlight;
176 |
177 | self.highlight = None;
178 |
179 | for az in self.active_zones.iter() {
180 | if x >= (az.0)[0]
181 | && y >= (az.0)[1]
182 | && x <= ((az.0)[0] + (az.0)[2])
183 | && y <= ((az.0)[1] + (az.0)[3]) {
184 |
185 | self.highlight = Some((az.1, az.2));
186 |
187 | if button_is_down
188 | && self.selection.is_none()
189 | && old_highlight == self.highlight {
190 |
191 | self.selection = self.highlight;
192 | self.orig_val = self.get_selection_val();
193 | return true;
194 | }
195 | break;
196 | }
197 | }
198 |
199 | if self.selection.is_some() && button_is_down {
200 | let exp = (10.0 as f64).powf((xr / 200.0).abs() as f64);
201 | let ampli = -((yr as f64 * exp) / 200.0) as f32;
202 | let s = self.selection.unwrap();
203 | self.set_input_val(s.0, s.1, self.orig_val + ampli);
204 | return true;
205 | }
206 |
207 | false
208 | }
209 |
210 | pub fn set_input_default(&mut self, op_idx: usize, i_idx: usize) {
211 | let iname = self.specs[op_idx].0.inputs[i_idx].name.clone();
212 | let default = self.specs[op_idx].0.input_defaults[i_idx];
213 | self.specs[op_idx].0.input_values[i_idx] = default;
214 | self.simcom.set_op_input(op_idx, &iname, default, false);
215 | }
216 |
217 | pub fn set_input_val(&mut self, op_idx: usize, i_idx: usize, val: f32) {
218 | let iname = self.specs[op_idx].0.inputs[i_idx].name.clone();
219 | self.specs[op_idx].0.input_values[i_idx] = OpIn::Constant(val);
220 | self.simcom.set_op_input(op_idx, &iname, OpIn::Constant(val), false);
221 | }
222 |
223 | pub fn get_selection_val(&self) -> f32 {
224 | if self.selection.is_some() {
225 | let s = self.selection.unwrap();
226 | self.get_input_val(s.0, s.1)
227 | } else {
228 | 0.0
229 | }
230 | }
231 |
232 | pub fn get_input_val(&self, op_idx: usize, i_idx: usize) -> f32 {
233 | if let OpIn::Constant(v) = self.specs[op_idx].0.input_values[i_idx] {
234 | v
235 | } else {
236 | 0.0
237 | }
238 | }
239 |
240 | pub fn update(&mut self) {
241 | let r = self.simcom.update(|ev| {
242 | if let SimulatorUIEvent::OpSpecUpdate(up) = ev {
243 | Some(up)
244 | } else { None }
245 | });
246 |
247 | if r.is_some() {
248 | self.update_from_spec(r.unwrap().unwrap());
249 | }
250 | }
251 |
252 | fn update_from_spec(&mut self, specs: Vec<(OpIOSpec, OpInfo)>) {
253 | //d// println!("Updated: {:?}", specs);
254 | self.specs = specs;
255 | self.groups = Vec::new();
256 |
257 | for iv in self.specs.iter() {
258 | let group = &iv.1.group;
259 | if group.index <= self.groups.len() {
260 | self.groups.resize(group.index + 1, ("".to_string(), Vec::new()));
261 | }
262 | }
263 |
264 | for i in 0..self.groups.len() {
265 | let ops : Vec =
266 | self.specs
267 | .iter()
268 | .filter(|o| o.1.group.index == i)
269 | .map(|o| o.0.index)
270 | .collect();
271 |
272 | if ops.is_empty() { continue; }
273 |
274 | let group = self.specs[ops[0]].1.group.clone();
275 | println!("OP: {:?} => {}", ops, group.name);
276 |
277 | self.groups[i] = (group.name.clone(), ops);
278 | }
279 | }
280 |
281 | pub fn draw(&mut self, p: &mut P) where P: GUIPainter {
282 | let text_h = 10.0;
283 |
284 | self.active_zones = Vec::new();
285 |
286 | let oo = p.get_offs();
287 |
288 | let mut skip_groups_count = self.scroll_offs.1;
289 | for grp in self.groups.iter() {
290 | if skip_groups_count > 0 {
291 | skip_groups_count -= 1;
292 | continue;
293 | }
294 |
295 | p.draw_text([1.0, 1.0, 1.0, 1.0], [0.0, 0.0], text_h, grp.0.clone());
296 |
297 | let ooo = p.get_offs();
298 |
299 | let mut skip_ops_count = self.scroll_offs.0;
300 |
301 | let mut max_op_h = 0.0;
302 | for op_i in grp.1.iter() {
303 | if skip_ops_count > 0 {
304 | skip_ops_count -= 1;
305 | continue;
306 | }
307 |
308 | let op = &self.specs[*op_i];
309 | let o = p.get_offs();
310 | p.set_offs((o.0, o.1 + text_h));
311 |
312 | let (w, h, zones) = draw_op(p, op, &self.highlight, &self.selection);
313 | self.active_zones.extend_from_slice(&zones);
314 | if h > max_op_h { max_op_h = h; }
315 | p.set_offs((o.0 + w + 3.0, o.1));
316 |
317 | if (p.get_offs().0 - oo.0) > p.get_area_size().0 {
318 | break;
319 | }
320 |
321 | }
322 |
323 | p.set_offs(ooo);
324 |
325 | p.add_offs(0.0, max_op_h + text_h);
326 | }
327 |
328 | p.set_offs(oo);
329 | }
330 | }
331 |
332 |
--------------------------------------------------------------------------------
/src/scopes.rs:
--------------------------------------------------------------------------------
1 | use wctr_signal_ops::sample_row::SampleRow;
2 | use crate::gui_painter::*;
3 |
4 | pub const SCOPE_SAMPLES : usize = 128;
5 | pub const SCOPE_WIDTH : f32 = 128.0;
6 | pub const SCOPE_HEIGHT : f32 = 48.0;
7 | const SCOPE_FONT_HEIGHT : f32 = 13.0;
8 |
9 | #[derive(Debug, PartialEq, Clone)]
10 | pub struct Scope {
11 | pub samples: Vec,
12 | recent_value: f32,
13 | pub points: Vec<[f32; 2]>,
14 | min: f32,
15 | max: f32,
16 | }
17 |
18 | impl Scope {
19 | fn new(sample_count: usize) -> Self {
20 | let mut v = Vec::new();
21 | v.resize(sample_count, 0.0);
22 | let mut p = Vec::new();
23 | p.resize(sample_count, [0.0; 2]);
24 | Scope {
25 | samples: v,
26 | points: p,
27 | min: 99999.0,
28 | max: -99999.0,
29 | recent_value: 0.0,
30 | }
31 | }
32 |
33 | fn draw(&mut self, painter: &mut P, idx: usize, pos: [f32; 2], size: [f32; 2]) where P: GUIPainter {
34 | let x_offs : f32 = size[0] / self.samples.len() as f32;
35 |
36 | let mut diff = self.max - self.min;
37 | if diff <= std::f32::EPSILON {
38 | diff = 1.0;
39 | }
40 |
41 | for (v, (i, p)) in self.samples.iter().zip(self.points.iter_mut().enumerate()) {
42 | p[0] = x_offs * (i as f32);
43 | p[1] = size[1] - (((v - self.min) / diff) * size[1]);
44 |
45 | if self.min > *v { self.min = *v; }
46 | if self.max < *v { self.max = *v; }
47 | }
48 |
49 | painter.draw_lines(
50 | [0.0, 1.0, 0.0, 1.0],
51 | pos,
52 | &[[0.0, 0.0],[size[0], 0.0]],
53 | false,
54 | 0.5);
55 | painter.draw_lines(
56 | [0.0, 1.0, 0.0, 1.0],
57 | pos,
58 | &[[0.0, size[1]],[size[0], size[1]]],
59 | false,
60 | 0.5);
61 | painter.draw_lines(
62 | [0.0, 1.0, 0.0, 1.0],
63 | pos,
64 | &[[size[0], 0.0],[size[0], size[1]]],
65 | false,
66 | 0.5);
67 | if !self.points.is_empty() {
68 | painter.draw_lines(
69 | [1.0, 1.0, 1.0, 1.0],
70 | pos,
71 | &self.points,
72 | false,
73 | 0.5);
74 | }
75 | painter.draw_text(
76 | [1.0, 0.0, 1.0, 1.0],
77 | [pos[0], pos[1] + size[1]],
78 | SCOPE_FONT_HEIGHT,
79 | format!("r{} {:0.2}", idx, self.recent_value));
80 | }
81 | }
82 |
83 | pub struct Scopes {
84 | pub sample_count: usize,
85 | pub scopes: Vec,
86 | pub sample_row: std::sync::Arc>,
87 | my_sample_row: SampleRow,
88 | }
89 |
90 | impl Scopes {
91 | pub fn new(sample_count: usize) -> Self {
92 | use std::sync::Arc;
93 | use std::sync::Mutex;
94 |
95 | Scopes {
96 | sample_count,
97 | scopes: Vec::new(),
98 | sample_row: Arc::new(Mutex::new(SampleRow::new())),
99 | my_sample_row: SampleRow::new(),
100 | }
101 | }
102 |
103 | pub fn update_from_audio_bufs(&mut self, bufs: &Vec>) {
104 | if bufs.len() != self.scopes.len() {
105 | self.scopes.resize(
106 | bufs.len() * 2,
107 | Scope::new(self.sample_count));
108 | }
109 |
110 | for (i, ab) in bufs.iter().enumerate() {
111 | for channel in 0..2 {
112 | let s : &mut Scope = &mut self.scopes[(i * 2) + channel];
113 | if s.samples.len() != ab.len() {
114 | self.sample_count = ab.len();
115 | s.samples.resize(ab.len() / 2, 0.0);
116 | s.points.resize(ab.len() / 2, [0.0; 2]);
117 | }
118 |
119 | for (j, s) in s.samples.iter_mut().enumerate() {
120 | *s = ab[(j * 2) + channel];
121 | }
122 | }
123 | }
124 | }
125 |
126 | pub fn update_from_sample_row(&mut self) {
127 | // use std::ops::DerefMut;
128 |
129 | if !self.sample_row.lock().unwrap().updated {
130 | return;
131 | }
132 |
133 | let old_pos = self.my_sample_row.pos;
134 |
135 | std::mem::swap(
136 | &mut *self.sample_row.lock().unwrap(),
137 | &mut self.my_sample_row);
138 |
139 | self.my_sample_row.updated = false;
140 |
141 | let len = self.my_sample_row.sample_row.len();
142 |
143 | let pos = self.my_sample_row.pos;
144 | if self.scopes.len() < len {
145 | self.scopes.resize(len, Scope::new(self.sample_count));
146 | }
147 |
148 | for (i, s) in self.my_sample_row.sample_row.iter().enumerate() {
149 | self.scopes[i].recent_value = *s;
150 | // println!("RECENT {}", *s);
151 |
152 | let mut j = old_pos;
153 | while j != pos {
154 | j = (j + 1) % self.sample_count;
155 | self.scopes[i].samples[j] = *s;
156 | }
157 | }
158 | }
159 |
160 | pub fn draw_scopes(&mut self, p: &mut P) where P: GUIPainter {
161 | let scope_width = SCOPE_WIDTH;
162 | let scope_height = SCOPE_HEIGHT;
163 | let font_height = SCOPE_FONT_HEIGHT;
164 | let elem_height = scope_height + SCOPE_FONT_HEIGHT;
165 | let per_row = (p.get_area_size().0 / scope_width).ceil() as usize;
166 | let max_rows = (p.get_area_size().1 / elem_height).ceil() as usize;
167 |
168 | if per_row <= 0 { return; }
169 |
170 | let s = p.get_area_size();
171 |
172 | p.draw_rect(
173 | [0.3, 0.1, 0.1, 1.0],
174 | [0.0, 0.0],
175 | [s.0, s.1],
176 | true, 1.0);
177 |
178 | for (i, s) in self.scopes.iter_mut().enumerate() {
179 | let row_idx = i % per_row;
180 | if (1 + (i / per_row)) >= max_rows { break; }
181 | let y = (scope_height + font_height) * ((i / per_row) as f32);
182 | s.draw(
183 | p,
184 | i,
185 | [(row_idx as f32) * scope_width, y],
186 | [scope_width, scope_height]);
187 | }
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/src/track.rs:
--------------------------------------------------------------------------------
1 | use serde::Serialize;
2 | use serde::Deserialize;
3 | use crate::gui_painter::GUIPainter;
4 |
5 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
6 | pub enum Interpolation {
7 | Empty,
8 | Step,
9 | Lerp,
10 | SStep,
11 | Exp,
12 | }
13 |
14 | impl std::default::Default for Interpolation {
15 | fn default() -> Self { Interpolation::Empty }
16 | }
17 |
18 | #[derive(Debug, Copy, Clone, PartialEq)]
19 | struct InterpolationState {
20 | line_a: usize,
21 | line_b: usize,
22 | val_a: f32,
23 | val_b: f32,
24 | int: Interpolation,
25 | desync: bool,
26 | }
27 |
28 | impl std::default::Default for InterpolationState {
29 | fn default() -> Self { InterpolationState::new() }
30 | }
31 |
32 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33 | pub struct Row {
34 | pub value: Option<(f32, Interpolation)>,
35 | pub a: u8,
36 | pub b: u8,
37 | pub note: u8,
38 | }
39 |
40 | impl Row {
41 | fn new() -> Self {
42 | Row {
43 | value: None,
44 | a: 0,
45 | b: 0,
46 | note: 0,
47 | }
48 | }
49 |
50 | pub fn draw
(&self, p: &mut P, state: &mut GUIState, line: usize) where P: GUIPainter {
51 | let val_s =
52 | if let Some((val, int)) = self.value {
53 | format!("{:>6.2}{}",
54 | val,
55 | match int {
56 | Interpolation::Empty => "e",
57 | Interpolation::Step => "_",
58 | Interpolation::Lerp => "/",
59 | Interpolation::SStep => "~",
60 | Interpolation::Exp => "^",
61 | })
62 | } else {
63 | String::from("------ ")
64 | };
65 |
66 | let note_s = match self.note {
67 | 0 => String::from("---"),
68 | 1 => String::from("off"),
69 | n => format!("{:<4}", note2name(n)),
70 | };
71 |
72 | let s =
73 | if state.track_index == 0 {
74 | format!("{:<05} |{:<02}|{:<4}{:>7}|{:02X} {:02X}|",
75 | line,
76 | state.pattern_index,
77 | note_s, val_s, self.a, self.b)
78 | } else {
79 | format!("|{:<02}|{:<4}{:>7}|{:02X} {:02X}|",
80 | state.pattern_index,
81 | note_s, val_s, self.a, self.b)
82 | };
83 |
84 | let color =
85 | if state.cursor_on_line && state.play_on_line {
86 | [0.8, 0.8, 0.4, 1.0]
87 | } else if state.play_on_line {
88 | [0.8, 0.4, 0.4, 1.0]
89 | } else if state.cursor_on_line {
90 | [0.4, 0.8, 0.4, 1.0]
91 | } else {
92 | [0.0, 0.0, 0.0, 1.0]
93 | };
94 |
95 | let txt_color =
96 | if state.cursor_on_line || state.play_on_line {
97 | if state.on_beat { [0.0, 0.4, 0.0, 1.0] }
98 | else { [0.0, 0.0, 0.0, 1.0] }
99 | } else {
100 | if state.on_beat { [0.6, 1.0, 0.6, 1.0] }
101 | else { [0.8, 0.8, 0.8, 1.0] }
102 | };
103 |
104 | let width =
105 | if state.track_index == 0 {
106 | FIRST_TRACK_WIDTH
107 | } else {
108 | TRACK_WIDTH
109 | };
110 | p.draw_rect(
111 | color,
112 | [0.0, 0.0],
113 | [width, ROW_HEIGHT], true, 0.5);
114 | p.draw_text(txt_color, [0.0, 0.0], ROW_HEIGHT * 0.9, s);
115 | }
116 | }
117 |
118 | impl InterpolationState {
119 | fn new() -> Self {
120 | InterpolationState {
121 | line_a: 0,
122 | line_b: 0,
123 | val_a: 0.0,
124 | val_b: 0.0,
125 | int: Interpolation::Empty,
126 | desync: true,
127 | }
128 | }
129 |
130 | fn clear(&mut self) {
131 | self.int = Interpolation::Empty;
132 | }
133 |
134 | fn to_end(&mut self, l: usize, d: &Row, end_line: usize) {
135 | self.line_a = l;
136 | self.val_a = d.value.unwrap_or((0.0, Interpolation::Step)).0;
137 | self.int = d.value.unwrap_or((0.0, Interpolation::Step)).1;
138 | self.line_b = end_line;
139 | self.val_b = 0.0;
140 | }
141 |
142 | fn to_next(&mut self, l: usize, d: &Row, lb: usize, db: &Row) {
143 | self.line_a = l;
144 | self.val_a = d.value.unwrap_or((0.0, Interpolation::Step)).0;
145 | self.int = d.value.unwrap_or((0.0, Interpolation::Step)).1;
146 | self.line_b = lb;
147 | self.val_b = db.value.unwrap_or((0.0, Interpolation::Step)).0;
148 | }
149 |
150 | fn desync(&mut self) {
151 | self.clear();
152 | self.desync = true;
153 | }
154 | }
155 |
156 | const NOTE_NAMES : &'static [&str] = &["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
157 |
158 | fn note2name(note: u8) -> String {
159 | if note == 0 { return String::from(""); }
160 |
161 | let octave : i32 = (note / 12) as i32 - 1;
162 | let name_idx : usize = (note % 12) as usize;
163 | format!("{}{}", NOTE_NAMES[name_idx], octave)
164 | }
165 |
166 | pub const TPOS_PAD : f32 = 50.0;
167 | pub const TRACK_PAD : f32 = 0.0;
168 | pub const TRACK_WIDTH : f32 = 160.0;
169 | pub const FIRST_TRACK_WIDTH : f32 = TRACK_WIDTH + 40.0;
170 | pub const ROW_HEIGHT : f32 = 15.0;
171 | pub const ROW_COMPR_FACT : f32 = 0.8;
172 | pub const CONTEXT_LINES : usize = 6;
173 |
174 | pub struct GUIState {
175 | pub cursor_track_idx: usize,
176 | pub cursor_line: usize,
177 | pub play_line: i32,
178 | pub cursor_on_track: bool,
179 | pub track_index: usize,
180 | pub pattern_index: usize,
181 | pub play_on_line: bool,
182 | pub on_beat: bool,
183 | pub lpb: usize,
184 | pub cursor_on_line: bool,
185 | pub scroll_offs: usize,
186 | }
187 |
188 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
189 | pub struct Track {
190 | pub name: String,
191 | #[serde(skip)]
192 | interpol: InterpolationState,
193 | // if index is at or above desired key, interpolate
194 | // else set index = 0 and restart search for right key
195 | pub lpp: usize,
196 | pub patterns: Vec>,
197 | pub arrangement: Vec, // arrangement of the patterns
198 | }
199 |
200 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
201 | pub struct TrackSerialized {
202 | pub name: String,
203 | pub patterns: Vec>,
204 | pub arrangement: Vec, // arrangement of the patterns
205 | }
206 |
207 | impl Track {
208 | pub fn new(name: &str, lpp: usize) -> Self {
209 | let mut fp = Vec::new();
210 | fp.resize(lpp, Row::new());
211 |
212 | Track {
213 | name: String::from(name),
214 | interpol: InterpolationState::new(),
215 | patterns: vec![fp],
216 | arrangement: vec![0],
217 | lpp,
218 | }
219 | }
220 |
221 | pub fn draw(&self, p: &mut P, state: &mut GUIState) where P: GUIPainter {
222 | let rows = (p.get_area_size().1 / (ROW_HEIGHT * ROW_COMPR_FACT)) as usize;
223 |
224 | let lines = self.line_count();
225 | let offs =
226 | if rows > 2 * CONTEXT_LINES {
227 | if state.cursor_line < (state.scroll_offs + CONTEXT_LINES) {
228 | let mut cl : i32 = state.cursor_line as i32;
229 | cl -= CONTEXT_LINES as i32;
230 | if cl < 0 { cl = 0; }
231 | cl as usize
232 | } else if state.cursor_line > (state.scroll_offs + (rows - CONTEXT_LINES)) {
233 | let mut cl = state.cursor_line as i32;
234 | cl -= (rows - CONTEXT_LINES) as i32;
235 | if cl < 0 { cl = 0; }
236 | cl as usize
237 | } else {
238 | state.scroll_offs
239 | }
240 | } else {
241 | if state.cursor_line >= (rows / 2) {
242 | state.cursor_line - (rows / 2)
243 | } else {
244 | 0
245 | }
246 | };
247 |
248 | state.scroll_offs = offs;
249 |
250 | let from = offs;
251 | let to = if from + rows > lines { lines } else { from + rows };
252 |
253 | // rows is the nums of displayable lines
254 | // the window of viewed rows should be stable and only scroll if
255 | // the cursor is close to the edge of an window.
256 |
257 | let o = p.get_offs();
258 |
259 | p.draw_text(
260 | [1.0, 1.0, 1.0, 1.0],
261 | [0.0, 0.2 * ROW_HEIGHT],
262 | 0.8 * ROW_HEIGHT,
263 | self.name.clone());
264 | p.add_offs(0.0, ROW_HEIGHT);
265 |
266 | for l in from..to {
267 | state.play_on_line = state.play_line == l as i32;
268 | state.cursor_on_line = state.cursor_on_track && state.cursor_line == l;
269 | state.on_beat = (l % state.lpb) == 0;
270 |
271 | if let Some((pat_idx, row)) = self.row_checked(l) {
272 | state.pattern_index = pat_idx;
273 | row.draw(p, state, l);
274 | }
275 |
276 | p.add_offs(0.0, ROW_HEIGHT * ROW_COMPR_FACT);
277 | }
278 |
279 | p.set_offs(o);
280 | }
281 |
282 | pub fn deserialize_contents(&mut self, ts: &TrackSerialized) {
283 | self.patterns = ts.patterns.clone();
284 | self.arrangement = ts.arrangement.clone();
285 | self.desync();
286 | }
287 |
288 | pub fn serialize_contents(&self) -> TrackSerialized {
289 | TrackSerialized {
290 | name: self.name.clone(),
291 | patterns: self.patterns.clone(),
292 | arrangement: self.arrangement.clone(),
293 | }
294 | }
295 |
296 | pub fn set_arrangement_pattern(&mut self, line: usize, pat_idx: usize) {
297 | if pat_idx < self.patterns.len() {
298 | let arr_idx = line / self.lpp;
299 | while arr_idx >= self.arrangement.len() {
300 | self.arrangement.push(pat_idx)
301 | }
302 | }
303 | }
304 |
305 | pub fn touch_pattern_idx(&mut self, pat_idx: usize) {
306 | if pat_idx >= self.patterns.len() {
307 | let mut fp = Vec::new();
308 | fp.resize(self.lpp, Row::new());
309 | self.patterns.resize(pat_idx + 1, fp);
310 | }
311 | }
312 |
313 | pub fn line_count(&self) -> usize {
314 | self.arrangement.len() * self.lpp
315 | }
316 |
317 | pub fn row_checked(&self, line: usize) -> Option<(usize, Row)> {
318 | if line >= self.line_count() { return None; }
319 | Some((
320 | self.arrangement[line / self.lpp],
321 | self.patterns[self.arrangement[line / self.lpp]][line % self.lpp].clone()
322 | ))
323 | }
324 |
325 | pub fn row(&mut self, line: usize) -> &mut Row {
326 | &mut self.patterns[self.arrangement[line / self.lpp]][line % self.lpp]
327 | }
328 |
329 | pub fn prev_row_with_value(&mut self, line: usize) -> Option<(usize, Row)> {
330 | let mut ll = line;
331 | while ll > 0 {
332 | let row = &self.patterns[self.arrangement[(ll - 1) / self.lpp]][(ll - 1) % self.lpp];
333 | if (*row).value.is_some() {
334 | return Some(((ll - 1), row.clone()));
335 | }
336 | ll -= 1;
337 | }
338 |
339 | None
340 | }
341 |
342 | pub fn next_row_with_value(&mut self, line: usize) -> Option<(usize, Row)> {
343 | let mut ll = line;
344 | let lc = self.line_count();
345 | while ll < lc {
346 | let pat_idx = self.arrangement[ll / self.lpp];
347 | let row = &self.patterns[pat_idx][ll % self.lpp];
348 | if (*row).value.is_some() {
349 | return Some((ll, row.clone()));
350 | }
351 | ll += 1;
352 | }
353 |
354 | None
355 | }
356 |
357 | pub fn touch_row(&mut self, line: usize) -> &mut Row {
358 | let a = line / self.lpp;
359 | while a >= self.arrangement.len() {
360 | self.patterns.push(Vec::new());
361 | let last_idx = self.patterns.len() - 1;
362 | self.patterns[last_idx].resize(self.lpp, Row::new());
363 | self.arrangement.push(self.patterns.len() - 1);
364 | }
365 |
366 | &mut self.patterns[self.arrangement[a]][line % self.lpp]
367 | }
368 |
369 | pub fn desync(&mut self) {
370 | self.interpol.desync();
371 | }
372 |
373 | pub fn remove_value(&mut self, line: usize) {
374 | *self.touch_row(line) = Row::new();
375 | self.desync();
376 | }
377 |
378 | pub fn set_int(&mut self, line: usize, int: Interpolation) {
379 | if let Some((v, _i)) = (*self.touch_row(line)).value {
380 | (*self.touch_row(line)).value = Some((v, int));
381 | } else {
382 | (*self.touch_row(line)).value = Some((0.0, int));
383 | }
384 | self.desync();
385 | }
386 |
387 | pub fn set_note(&mut self, line: usize, value: u8) {
388 | (*self.touch_row(line)).note = value;
389 | self.desync();
390 | }
391 |
392 | pub fn set_a(&mut self, line: usize, value: u8) {
393 | (*self.touch_row(line)).a = value;
394 | self.desync();
395 | }
396 |
397 | pub fn set_b(&mut self, line: usize, value: u8) {
398 | (*self.touch_row(line)).b = value;
399 | self.desync();
400 | }
401 |
402 | pub fn set_value(&mut self, line: usize, value: f32) {
403 | if let Some((_v, i)) = (*self.touch_row(line)).value {
404 | (*self.touch_row(line)).value = Some((value, i));
405 | } else {
406 | (*self.touch_row(line)).value = Some((value, Interpolation::Step));
407 | }
408 | self.desync();
409 | }
410 |
411 | fn sync_interpol_to_play_line(&mut self, line: usize) -> Option {
412 | if let Some((l_a, row_a)) = self.next_row_with_value(line) {
413 | if let Some((l_b, row_b)) = self.prev_row_with_value(line) {
414 | self.interpol.to_next(l_b, &row_b, l_a, &row_a);
415 | } else {
416 | self.interpol.to_end(l_a, &row_a, self.line_count() - 1);
417 | }
418 |
419 | if l_a == line { Some(row_a) }
420 | else { None }
421 | } else {
422 | if let Some((l_b, row_b)) = self.prev_row_with_value(line) {
423 | self.interpol.to_end(l_b, &row_b, self.line_count() - 1);
424 | } else {
425 | self.interpol.clear();
426 | }
427 |
428 | None
429 | }
430 | }
431 |
432 | /// Advances the play head to the line. The last line has to be
433 | /// specified for setting up the interpolations.
434 | /// Should be called in order of the track events, othewise
435 | /// desync() should be called first.
436 | pub fn play_line(&mut self, line: usize) -> Option {
437 | self.sync_interpol_to_play_line(line);
438 |
439 | let r = self.row(line);
440 | if r.note > 0 { Some(r.clone()) } else { None }
441 | }
442 |
443 | /// Returns the interpolated value of this track at the specified line.
444 | /// Only works if the interpolation was
445 | /// initialized with self.sync_interpol_to_play_line() in self.play_line()!
446 | pub fn get_value(&mut self, line: usize, fract_next_line: f64) -> f32 {
447 | let i = &mut self.interpol;
448 |
449 | if line < i.line_a {
450 | i.clear();
451 | }
452 |
453 | let mut diff = i.line_b - i.line_a;
454 | if diff == 0 { diff = 1; }
455 | let diff = diff as f64;
456 | let line_f = line as f64 + fract_next_line;
457 |
458 | match i.int {
459 | Interpolation::Empty => 0.0,
460 | Interpolation::Step => {
461 | if line == i.line_b {
462 | i.val_b
463 | } else {
464 | i.val_a
465 | }
466 | },
467 | Interpolation::Lerp => {
468 | let x = (line_f - (i.line_a as f64)) / diff;
469 | ( i.val_a as f64 * (1.0 - x)
470 | + i.val_b as f64 * x)
471 | as f32
472 | },
473 | Interpolation::SStep => {
474 | let x = (line_f - (i.line_a as f64)) / diff;
475 | let x = if x < 0.0 { 0.0 } else { x };
476 | let x = if x > 1.0 { 1.0 } else { x };
477 | let x = x * x * (3.0 - 2.0 * x);
478 |
479 | ( i.val_a as f64 * (1.0 - x)
480 | + i.val_b as f64 * x)
481 | as f32
482 | },
483 | Interpolation::Exp => {
484 | let x = (line_f - (i.line_a as f64)) / diff;
485 | let x = x * x;
486 |
487 | ( i.val_a as f64 * (1.0 - x)
488 | + i.val_b as f64 * x)
489 | as f32
490 | },
491 | }
492 | }
493 | }
494 |
--------------------------------------------------------------------------------
/src/tracker.rs:
--------------------------------------------------------------------------------
1 | use crate::track::*;
2 | use crate::gui_painter::GUIPainter;
3 |
4 | /// This trait handles the output of a Tracker when being driven
5 | /// by the tick() method. It generates events for starting notes
6 | /// on a synthesizer and returns a vector of the interpolated values
7 | /// on all tracks. The emit_play_line() function gives feedback of the
8 | /// current song position in terms of the track line index.
9 | pub trait OutputHandler {
10 | /// Called by Tracker::tick() when a new line started and
11 | /// a track has a new value defined. Useful for driving note on/off
12 | /// events on a synthesizer.
13 | fn emit_event(&mut self, track_idx: usize, row: &Row);
14 | /// Called when the Tracker::tick() function advanced to a new line.
15 | fn emit_play_line(&mut self, play_line: i32);
16 | /// Is used to output the song position in seconds.
17 | fn song_pos(&mut self) -> &mut f32;
18 | }
19 |
20 | #[derive(Debug, Copy, Clone, PartialEq)]
21 | pub enum PlayHeadAction {
22 | TogglePause,
23 | Pause,
24 | Play,
25 | Restart,
26 | NextLine,
27 | PrevLine,
28 | }
29 |
30 | /// This trait provides an interface to synchronize the track data
31 | /// between two Tracker instances. The main purpose is to connect a
32 | /// frontend Tracker with an audio thread tracker in such a way, that
33 | /// changes to the track data is transmitted to the backend thread.
34 | /// How threading is done is up to the implementor of this trait.
35 | /// You may even not use threads at all and use some network protocol
36 | /// for synchronization.
37 | pub trait TrackerSync {
38 | /// Called by Tracker when a new Track is added.
39 | fn add_track(&mut self, t: Track);
40 | /// Called by Tracker when the a note in a specific track and line is added.
41 | fn set_note(&mut self, track_idx: usize, line: usize, value: u8);
42 | /// Called by Tracker when a value in a specific track and line
43 | /// is added.
44 | fn set_value(&mut self, track_idx: usize, line: usize, value: f32);
45 | /// Called by Tracker when the a flag value in a specific track and line
46 | /// is added.
47 | fn set_a(&mut self, track_idx: usize, line: usize, value: u8);
48 | /// Called by Tracker when the b flag value in a specific track and line
49 | /// is added.
50 | fn set_b(&mut self, track_idx: usize, line: usize, value: u8);
51 | /// Called by Tracker when an interpolation for a value should be set.
52 | /// Does nothing if no value at that position exists.
53 | fn set_int(&mut self, track_idx: usize, line: usize, int: Interpolation);
54 | /// Called by Tracker when a value is removed from a track.
55 | fn remove_value(&mut self, track_idx: usize, line: usize);
56 | /// Called when the tracker should change the play head state:
57 | fn play_head(&mut self, _act: PlayHeadAction) { }
58 | /// Called when track data is loaded
59 | fn deserialize_contents(&mut self, track_idx: usize, contents: TrackSerialized);
60 | }
61 |
62 | /// This is a Tracker synchronizer that does nothing.
63 | /// Use it if you don't want to or not need to sync.
64 | pub struct TrackerNopSync { }
65 |
66 | impl TrackerSync for TrackerNopSync {
67 | fn add_track(&mut self, _t: Track) { }
68 | fn set_value(&mut self, _track_idx: usize, _line: usize, _value: f32) { }
69 | fn set_note(&mut self, _track_idx: usize, _line: usize, _value: u8) { }
70 | fn set_a(&mut self, _track_idx: usize, _line: usize, _value: u8) { }
71 | fn set_b(&mut self, _track_idx: usize, _line: usize, _value: u8) { }
72 | fn set_int(&mut self, _track_idx: usize, _line: usize, _int: Interpolation) { }
73 | fn remove_value(&mut self, _track_idx: usize, _line: usize) { }
74 | fn deserialize_contents(&mut self, _track_idx: usize, _contents: TrackSerialized) { }
75 | fn play_head(&mut self, _act: PlayHeadAction) { }
76 | }
77 |
78 | /// This structure stores the state of a tracker.
79 | /// It stores the play state aswell as the actual track data.
80 | /// The SYNC type must implement the TrackerSync trait.
81 | /// It is responsible for connecting the tracker frontend
82 | /// in a graphics thread with a tracker in the audio thread.
83 | pub struct Tracker where SYNC: TrackerSync {
84 | /// lines per beat
85 | pub lpb: usize,
86 | /// ticks per row/line
87 | pub tpl: usize,
88 | /// number of lines per pattern
89 | pub lpp: usize,
90 | /// current play head, if -1 it will start with line 0
91 | pub play_line: i32,
92 | /// The actual track data.
93 | pub tracks: Vec