diff --git a/src/device/sequencer.rs b/src/device/sequencer.rs index b0270c89..7470ba74 100644 --- a/src/device/sequencer.rs +++ b/src/device/sequencer.rs @@ -12,9 +12,11 @@ pub struct Sequencer { time_cursor: u16, rate: Hz, tempo: Tempo, + steps: u64, + divisions: u64, transport: ::jack::Transport, sequence: Sequence, - ppq: u32, + ppq: u64, input_port: Port, input_connect: Vec, output_port: Port, @@ -55,6 +57,8 @@ impl Sequencer { recording: true, overdub: true, notes_on: vec![false;128], + steps: 64, + divisions: 16, }).activate(client) } } @@ -66,32 +70,29 @@ pub fn process (state: &mut Sequencer, client: &Client, scope: &ProcessScope) -> } fn process_in (state: &mut Sequencer, scope: &ProcessScope) { - let pos = state.transport.query().unwrap().pos; - let frame = pos.frame(); - let rate = pos.frame_rate().unwrap(); - let usecs = Frame(frame).to_usec(&Hz(rate)).0 as u64; - let usec_per_beat = state.tempo.usec_per_beat().0 as u64; - let usec_per_tick = state.tempo.usec_per_tick(state.ppq).0 as u64; - let beats = usecs / usec_per_beat; - let time = beats as u32 * state.ppq; - + let pos = state.transport.query().unwrap().pos; + let usecs = Frame(pos.frame()).to_usec(&state.rate).0; + let beat = usecs / state.tempo.usec_per_beat().0; + let step = usecs / state.tempo.usec_per_step(state.divisions as u64).0; + let tick_usec = state.tempo.usec_per_step(state.ppq).0; + let tick = usecs / tick_usec; for event in state.input_port.iter(scope) { match midly::live::LiveEvent::parse(event.bytes).unwrap() { midly::live::LiveEvent::Midi { channel, message } => match message { midly::MidiMessage::NoteOn { key, vel } => { state.notes_on[key.as_int() as usize] = true; - if state.sequence.contains_key(&time) { - state.sequence.get_mut(&time).unwrap().push(message.clone()); + if state.sequence.contains_key(&(tick as u32)) { + state.sequence.get_mut(&(tick as u32)).unwrap().push(message.clone()); } else { - state.sequence.insert(time, vec![message.clone()]); + state.sequence.insert(tick as u32, vec![message.clone()]); } }, midly::MidiMessage::NoteOff { key, vel } => { state.notes_on[key.as_int() as usize] = false; - if state.sequence.contains_key(&time) { - state.sequence.get_mut(&time).unwrap().push(message.clone()); + if state.sequence.contains_key(&(tick as u32)) { + state.sequence.get_mut(&(tick as u32)).unwrap().push(message.clone()); } else { - state.sequence.insert(time, vec![message.clone()]); + state.sequence.insert(tick as u32, vec![message.clone()]); } }, _ => {} @@ -107,8 +108,15 @@ fn process_out (state: &mut Sequencer, scope: &ProcessScope) { let start = scope.last_frame_time(); let end = start + size; let mut writer = state.output_port.writer(scope); + let pulse_usec = state.tempo.usec_per_step(state.ppq as u64).0; + let tick_start = Frame(start).to_usec(&state.rate).0; + let tick_start = tick_start / pulse_usec; + let tick_end = Frame(start).to_usec(&state.rate).0; + let tick_end = tick_start / pulse_usec; for time in 0..size { - // TODO + let usecs = Frame(start + time).to_usec(&state.rate).0; + let ticks = usecs / state.tempo.usec_per_step(state.ppq as u64).0; + //println!("{usecs} = {ticks}"); } } } @@ -134,6 +142,226 @@ impl NotificationHandler for Sequencer { } } +fn render (state: &Sequencer, buf: &mut Buffer, mut area: Rect) + -> Usually +{ + let Rect { x, y, width, height } = area; + let (time0, time1) = state.time_axis; + let (note0, note1) = state.note_axis; + let pos = state.transport.query().unwrap().pos; + let frame = pos.frame(); + let rate = pos.frame_rate().unwrap(); + let usecs = Frame(frame).to_usec(&Hz(rate)).0; + let usec_per_beat = state.tempo.usec_per_beat().0; + let usec_per_tick = state.tempo.usec_per_step(state.ppq).0; + let usec_per_step = state.tempo.usec_per_step(state.divisions as u64).0; + let steps = usecs / usec_per_step; + let header = draw_state_header(state, buf, area, steps)?; + let piano = match state.mode { + SequencerView::Tiny => + Rect { x, y, width, height: 0 }, + SequencerView::Compact => + Rect { x, y, width, height: 0 }, + SequencerView::Vertical => draw_state_vertical(state, buf, Rect { + x, + y: y + header.height, + width: 3 + note1 - note0, + height: 3 + time1 - time0, + }, steps)?, + SequencerView::Horizontal => draw_state_horizontal(state, buf, Rect { + x, + y: y + header.height, + width: 3 + time1 - time0, + height: 3 + note1 - note0, + })?, + }; + Ok(draw_box(buf, Rect { + x, + y, + width: header.width.max(piano.width), + height: header.height + piano.height + 1 + })) +} + +fn draw_state_header (state: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usually { + let rep = beat / state.steps; + let step = beat % state.steps; + let reps = state.steps / state.divisions; + let steps = state.steps % state.divisions; + let Rect { x, y, width, height } = area; + let style = Style::default().gray(); + buf.set_string(x + 1, y + 1, &format!(" │ {rep}.{step:2} / {reps}.{steps}"), style); + buf.set_string(x + 2, y + 1, &format!("{}", &state.name), style.white().bold()); + buf.set_string(x + 1, y + 2, &format!(" ▶ PLAY │ ⏹ STOP │ │"), style); + buf.set_string(x + 2, y + 2, &format!("▶ PLAY"), if state.playing { + Style::default().green() + } else { + Style::default().dim() + }); + buf.set_string(x + 24, y + 2, &format!("⏺ REC"), if state.recording { + Style::default().red() + } else { + Style::default().dim() + }); + buf.set_string(x + 32, y + 2, &format!("⏺ DUB"), if state.overdub { + Style::default().yellow() + } else { + Style::default().dim() + }); + Ok(Rect { x, y, width: 39, height: 4 }) +} + +const KEY_WHITE: Style = Style { + fg: Some(Color::Gray), + bg: None, + underline_color: None, + add_modifier: ::ratatui::style::Modifier::empty(), + sub_modifier: ::ratatui::style::Modifier::empty(), +}; + +const KEY_BLACK: Style = Style { + fg: Some(Color::Black), + bg: None, + underline_color: None, + add_modifier: ::ratatui::style::Modifier::empty(), + sub_modifier: ::ratatui::style::Modifier::empty(), +}; + +const KEY_HORIZONTAL_STYLE: [Style;12] = [ + KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, + KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, +]; + +fn draw_state_vertical (state: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usually { + let Rect { x, y, .. } = area; + let transport = state.transport.query().unwrap(); + let (time0, time1) = state.time_axis; + let (note0, note1) = state.note_axis; + let bw = Style::default().dim(); + let bg = Style::default().on_black(); + for key in note0..note1.max(note0+32) { + let x = x + 5 + key - note0; + if key % 12 == 0 { + let octave = format!("C{}", (key / 12) as i8 - 4); + buf.set_string(x, y, &octave, Style::default()); + } + let mut color = KEY_HORIZONTAL_STYLE[key as usize % 12]; + let mut is_on = state.notes_on[key as usize]; + let step = beat % state.steps; + let (a, b, c) = ( + (step + 0) as u32 * state.ppq as u32 / state.divisions as u32, + (step + 1) as u32 * state.ppq as u32 / state.divisions as u32, + (step + 2) as u32 * state.ppq as u32 / state.divisions as u32, + ); + let key = ::midly::num::u7::from(key as u8); + is_on = is_on || contains_note_on(&state.sequence, key, a, b); + is_on = is_on || contains_note_on(&state.sequence, key, b, c); + if is_on { + color = Style::default().red(); + } + buf.set_string(x, y - 1, &format!("▄"), color); + } + for step in time0..time1 { + let y = y - time0 + step / 2; + let step = step as u64; + //buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg); + if step % state.divisions == 0 { + buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default()); + } + for k in note0..note1.max(note0+32) { + let key = ::midly::num::u7::from_int_lossy(k as u8); + if step % 2 == 0 { + let (a, b, c) = ( + (step + 0) as u32 * state.ppq as u32 / state.divisions as u32, + (step + 1) as u32 * state.ppq as u32 / state.divisions as u32, + (step + 2) as u32 * state.ppq as u32 / state.divisions as u32, + ); + let (character, style) = match ( + contains_note_on(&state.sequence, key, a, b), + contains_note_on(&state.sequence, key, b, c), + ) { + (true, true) => ("█", bg), + (true, false) => ("▀", bg), + (false, true) => ("▄", bg), + (false, false) => (" ", bg), + }; + buf.set_string(x + 5 + k - note0, y, character, style); + } + } + if beat % state.steps == step as u64 { + buf.set_string(x + 1, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow()); + for key in note0..note1.max(note0+32) { + let color = if state.notes_on[key as usize] { + Style::default().red() + } else { + KEY_HORIZONTAL_STYLE[key as usize % 12] + }; + } + } + } + buf.set_string( + x + 5 + state.note_cursor, + y + state.time_cursor / 2, + if state.time_cursor % 2 == 0 { "▀" } else { "▄" }, + Style::default() + ); + Ok(Rect { x, y, width: area.width, height: (time1-time0)/2 }) +} + +fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, end: u32) -> bool { + for (i, (t, events)) in sequence.range(start..end).enumerate() { + for event in events.iter() { + match event { + ::midly::MidiMessage::NoteOn {key,..} => { + if *key == k { + return true + } + } + _ => {} + } + } + } + return false +} + +const KEYS_VERTICAL: [&'static str; 6] = [ + "▀", "▀", "▀", "█", "▄", "▄", +]; + +fn draw_state_horizontal (state: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { + let Rect { x, y, .. } = area; + let (time0, time1) = state.time_axis; + let (note0, note1) = state.note_axis; + let bw = Style::default().dim(); + let bg = Style::default().on_black(); + for i in 0..32.max(note1-note0)/2 { + let y = y + i; + buf.set_string(x + 1, y, KEYS_VERTICAL[(i % 6) as usize], bw); + buf.set_string(x + 2, y, "█", bw); + buf.set_string(x + 5, y, &" ".repeat((time1 - time0) as usize), bg); + if i % 6 == 0 { + let octave = format!("C{}", ((note1 - i) / 6) as i8 - 4); + buf.set_string(x+5, y, &octave, Style::default()); + } + } + for step in time0..time1 { + let time_start = step as u32 * state.ppq as u32; + let time_end = (step + 1) as u32 * state.ppq as u32; + for (i, (t, events)) in state.sequence.range(time_start..time_end).enumerate() { + if events.len() > 0 { + buf.set_string(x + 5 + step as u16, y, "█", bw); + } + } + } + buf.set_string( + x + 5 + state.time_cursor, + y + state.note_cursor / 2, + if state.note_cursor % 2 == 0 { "▀" } else { "▄" }, + Style::default() + ); + Ok(Rect { x, y, width: time1 - time0 + 6, height: 32.max(note1 - note0) / 2 }) +} + pub fn handle (state: &mut Sequencer, event: &AppEvent) -> Result<(), Box> { match event { AppEvent::Input(Event::Key(event)) => { @@ -174,8 +402,8 @@ fn nop (_: &mut Sequencer) { } fn note_add (s: &mut Sequencer) { let time = (s.time_axis.0 + s.time_cursor) as u32; - let time_start = time * s.ppq; - let time_end = (time + 1) * s.ppq; + let time_start = time * s.ppq as u32; + let time_end = (time + 1) * s.ppq as u32; let key = ::midly::num::u7::from_int_lossy((s.note_cursor + s.note_axis.0) as u8); let note_on = ::midly::MidiMessage::NoteOn { key, vel: 100.into() }; let note_off = ::midly::MidiMessage::NoteOff { key, vel: 100.into() }; @@ -249,373 +477,3 @@ fn toggle_record (s: &mut Sequencer) { fn toggle_overdub (s: &mut Sequencer) { s.overdub = !s.overdub } - -fn render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect) - -> Usually -{ - let Rect { x, y, width, height } = area; - let (time0, time1) = sequencer.time_axis; - let (note0, note1) = sequencer.note_axis; - let pos = sequencer.transport.query().unwrap().pos; - let frame = pos.frame(); - let rate = pos.frame_rate().unwrap(); - let usecs = Frame(frame).to_usec(&Hz(rate)).0 as u64; - let usec_per_beat = sequencer.tempo.usec_per_beat().0 as u64; - let usec_per_tick = sequencer.tempo.usec_per_tick(sequencer.ppq).0 as u64; - let beats = usecs / usec_per_beat; - let bar = beats / 4; - let beat = beats % 4; - let tick = (usecs % usec_per_beat) / usec_per_tick; - let header = draw_sequencer_header(sequencer, buf, area, bar, beat, tick)?; - let piano = match sequencer.mode { - SequencerView::Tiny => - Rect { x, y, width, height: 0 }, - SequencerView::Compact => - Rect { x, y, width, height: 0 }, - SequencerView::Vertical => draw_sequencer_vertical(sequencer, buf, Rect { - x, - y: y + header.height, - width: 3 + note1 - note0, - height: 3 + time1 - time0, - }, beats)?, - SequencerView::Horizontal => draw_sequencer_horizontal(sequencer, buf, Rect { - x, - y: y + header.height, - width: 3 + time1 - time0, - height: 3 + note1 - note0, - })?, - }; - Ok(draw_box(buf, Rect { - x, - y, - width: header.width.max(piano.width), - height: header.height + piano.height + 1 - })) -} - -fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect, bar: u64, beat: u64, tick: u64) -> Usually { - let Rect { x, y, width, height } = area; - let style = Style::default().gray(); - buf.set_string(x + 1, y + 1, &format!(" │ {bar}.{beat}.{tick:03}"), style); - buf.set_string(x + 2, y + 1, &format!("{}", &sequencer.name), style.white().bold()); - buf.set_string(x + 1, y + 2, &format!(" ▶ PLAY │ ⏹ STOP │ │"), style); - buf.set_string(x + 2, y + 2, &format!("▶ PLAY"), if sequencer.playing { - Style::default().green() - } else { - Style::default().dim() - }); - buf.set_string(x + 24, y + 2, &format!("⏺ REC"), if sequencer.recording { - Style::default().red() - } else { - Style::default().dim() - }); - buf.set_string(x + 32, y + 2, &format!("⏺ DUB"), if sequencer.overdub { - Style::default().yellow() - } else { - Style::default().dim() - }); - Ok(Rect { x, y, width: 39, height: 4 }) -} - -const KEY_WHITE: Style = Style { - fg: Some(Color::Gray), - bg: None, - underline_color: None, - add_modifier: ::ratatui::style::Modifier::empty(), - sub_modifier: ::ratatui::style::Modifier::empty(), -}; - -const KEY_BLACK: Style = Style { - fg: Some(Color::Black), - bg: None, - underline_color: None, - add_modifier: ::ratatui::style::Modifier::empty(), - sub_modifier: ::ratatui::style::Modifier::empty(), -}; - -const KEY_HORIZONTAL_STYLE: [Style;12] = [ - KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, - KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, -]; - -fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usually { - let Rect { x, y, .. } = area; - let transport = sequencer.transport.query().unwrap(); - let (time0, time1) = sequencer.time_axis; - let (note0, note1) = sequencer.note_axis; - let bw = Style::default().dim(); - let bg = Style::default().on_black(); - for key in note0..note1.max(note0+32) { - let x = x + 5 + key - note0; - let color = if sequencer.notes_on[key as usize] { - Style::default().red() - } else { - KEY_HORIZONTAL_STYLE[key as usize % 12] - }; - buf.set_string(x, y - 1, &format!("▄"), color); - if key % 12 == 0 { - let octave = format!("C{}", (key / 12) as i8 - 4); - buf.set_string(x, y, &octave, Style::default()); - } - } - for step in time0..time1 { - let y = y - time0 + step / 2; - //buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg); - if step % 8 == 0 { - buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default()); - } - for k in note0..note1.max(note0+32) { - let key = ::midly::num::u7::from_int_lossy(k as u8); - if step % 2 == 0 { - let (a, b, c) = ( - (step + 0) as u32 * sequencer.ppq, - (step + 1) as u32 * sequencer.ppq, - (step + 2) as u32 * sequencer.ppq, - ); - let (character, style) = match ( - contains_note_on(&sequencer.sequence, key, a, b), - contains_note_on(&sequencer.sequence, key, b, c), - ) { - (true, true) => ("█", bg), - (true, false) => ("▀", bg), - (false, true) => ("▄", bg), - (false, false) => (" ", bg), - }; - buf.set_string(x + 5 + k - note0, y, character, style); - } - } - if beat == step as u64 { - buf.set_string(x + 1, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow()); - } - } - buf.set_string( - x + 5 + sequencer.note_cursor, - y + sequencer.time_cursor / 2, - if sequencer.time_cursor % 2 == 0 { "▀" } else { "▄" }, - Style::default() - ); - Ok(Rect { x, y, width: area.width, height: (time1-time0)/2 }) -} - -fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, end: u32) -> bool { - for (i, (t, events)) in sequence.range(start..end).enumerate() { - for event in events.iter() { - match event { - ::midly::MidiMessage::NoteOn {key,..} => { - if *key == k { - return true - } - } - _ => {} - } - } - } - return false -} - -const KEYS_VERTICAL: [&'static str; 6] = [ - "▀", "▀", "▀", "█", "▄", "▄", -]; - -fn draw_sequencer_horizontal (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { - let Rect { x, y, .. } = area; - let (time0, time1) = sequencer.time_axis; - let (note0, note1) = sequencer.note_axis; - let bw = Style::default().dim(); - let bg = Style::default().on_black(); - for i in 0..32.max(note1-note0)/2 { - let y = y + i; - buf.set_string(x + 1, y, KEYS_VERTICAL[(i % 6) as usize], bw); - buf.set_string(x + 2, y, "█", bw); - buf.set_string(x + 5, y, &" ".repeat((time1 - time0) as usize), bg); - if i % 6 == 0 { - let octave = format!("C{}", ((note1 - i) / 6) as i8 - 4); - buf.set_string(x+5, y, &octave, Style::default()); - } - } - for step in time0..time1 { - let time_start = step as u32 * sequencer.ppq; - let time_end = (step + 1) as u32 * sequencer.ppq; - for (i, (t, events)) in sequencer.sequence.range(time_start..time_end).enumerate() { - if events.len() > 0 { - buf.set_string(x + 5 + step as u16, y, "█", bw); - } - } - } - buf.set_string( - x + 5 + sequencer.time_cursor, - y + sequencer.note_cursor / 2, - if sequencer.note_cursor % 2 == 0 { "▀" } else { "▄" }, - Style::default() - ); - Ok(Rect { x, y, width: time1 - time0 + 6, height: 32.max(note1 - note0) / 2 }) -} - - //buf.set_string(x + 3, y, "╭1.1.", Style::default().dim()); - //buf.set_string(x + 3 + 16, y, "╭1.2.", Style::default().dim()); - //buf.set_string(x + 3 + 32, y, "╭1.3.", Style::default().dim()); - //buf.set_string(x + 3 + 48, y, "╭1.4.", Style::default().dim()); - ////buf.set_string(x + 2, y, "╭", Style::default().dim()); - ////buf.set_string(x + 2, y + 13, "╰", Style::default().dim()); - //buf.set_string(x + 2 + 65, y, "╮", Style::default().dim()); - //buf.set_string(x + 2 + 65, y + 13, "╯", Style::default().dim()); - //let transport = sequencer.transport.query().unwrap(); - //let frame = transport.pos.frame(); - //let rate = transport.pos.frame_rate().unwrap(); - //let second = (frame as f64) / (rate as f64); - //let minute = second / 60f64; - //let bpm = 120f64; - //let div = 4; - //let beats = minute * bpm; - //let bars = beats as u32 / div as u32; - //let beat = beats as u32 % div as u32 + 1; - //let beat_sub = beats % 1.0; - //buf.set_string( - //x - 18, y + height, - //format!("BBT {bars:04}:{beat:02}.{:02}", (beat_sub * 16.0) as u32), - //Style::default() - //); - //let bg = if step as u32 == (beat - 1) * 16 + (beat_sub * 16.0) as u32 { - //ratatui::style::Color::Gray - //} else if step == sequencer.cursor.1 as usize || key == sequencer.cursor.0/2 { - //ratatui::style::Color::Black - //} else { - //ratatui::style::Color::Reset - //}; - //let top = sequence[(key * 2) as usize][step].is_some(); - //let bottom = sequence[(key * 2 + 1) as usize][step].is_some(); - //match (top, bottom) { - //(true, true) => { - //buf.set_string(x + 3 + step as u16, y + 1 + key, "█", - //Style::default().yellow().not_dim().bold().bg(bg)); - //}, - //(true, false) => { - //buf.set_string(x + 3 + step as u16, y + 1 + key, "▀", - //Style::default().yellow().not_dim().bold().bg(bg)); - //}, - //(false, true) => { - //buf.set_string(x + 3 + step as u16, y + 1 + key, "▄", - //Style::default().yellow().not_dim().bold().bg(bg)); - //}, - //(false, false) => if step % 16 == 0 { - //buf.set_string(x + 3 + step as u16, y + 1 + key, "┊", - //Style::default().dim().bg(bg)) - //} else { - //buf.set_string(x + 3 + step as u16, y + 1 + key, "·", - //Style::default().dim().bg(bg)) - //}, - //} - //for step in 0..64 { - //if step % 8 == 0 { - //buf.set_string(x + 3 + step as u16, y + 1 + 12, [ - //"|a", "|b", "|c", "|d", "|e", "|f", "|g", "|h" - //][step / 8 as usize], Style::default().dim()) - //} - //} - - //{ - //let mut area = area.clone(); - //area.y = area.y + 3; - //area.x = area.x + 1; - //area.height = area.height - 2; - //draw_sequence_keys(area, buf, - //&sequencer.jack_client.as_client().transport().query().unwrap(), - //&sequencer.sequence, - //&sequencer.cursor); - //draw_sequence_cursor(area, buf, - //&sequencer.cursor); - //} - //draw_box(buf, Rect { x, y, width: 18, height }); - - //draw_box(buf, Rect { x, y, width: 40, height: 8 }); - //buf.set_string(x + 1, y + 3, - //&format!(" │ INPUT │ OUTPUT │"), - //Style::default().gray()); - //buf.set_string(x + 1, y + 5, - //&format!(" {} │ IN │ ⏺ REC │ ⏺ DUB │ ▶ PLAY │ ⏹ STOP │ OUT │ 00:00.00 / 00:00.00", &sequencer.name), - //Style::default().gray()); - //if sequencer.inputs_open.fetch_and(true, Ordering::Relaxed) { - //buf.set_string(x + 15, y + 1, "IN", Style::default().green().bold()); - //} - //if sequencer.outputs_open.fetch_and(true, Ordering::Relaxed) { - //buf.set_string(x + 54, y + 1, "OUT", Style::default().green().bold()); - //} - - //let mut command = |y2: u16, c: &str, ommand: &str, value: &str| { - //buf.set_string(x + 1, y + y2, c, Style::default().bold()); - //buf.set_string(x + 2, y + y2, ommand, Style::default().dim()); - //buf.set_string(x + 4 + ommand.len() as u16, y + y2, value, Style::default().bold()); - //}; - //for (y, c, ommand, value) in [ - ////(5, "I", "nputs", "[+]"), - ////(6, "O", "utputs", "[+]"), - ////(7, "C", "hannel", "01"), - //(8, "G", "rid", "1/16"), - //(9, "Z", "oom", "1/64"), - //(10, "R", "ate", "1/1"), - //(11, "S", "ync", "1 bar"), - //(12, "A", "dd note", ""), - //(13, "D", "elete note", ""), - //] { - //command(y, c, ommand, value) - //} - //draw_box(buf, Rect { x, y: y + height - 1, width, height: 3 }); - //buf.set_string(x + 1, y + height, - //&format!(" Grid 1/16 │ Zoom 1/64 │ Rate 1/1 │ Sync 1 bar │ Add note │ Delete note "), - //Style::default().gray()); - //buf.set_string(x + 1, y + 1, &format!(" ▶ {} ", &sequencer.name), - //Style::default().white().bold().not_dim()); - //buf.set_string(x + 4, y + 0, "┬", Style::default().gray()); - //buf.set_string(x + 4, y + 1, "│", Style::default().gray().dim()); - //buf.set_string(x + 4, y + 2, "┴", Style::default().gray()); - //buf.set_string(x + 21, y + 1, " ⏺ REC DUB ", - //Style::default().white().bold().not_dim()); - //buf.set_string(x + 18, y + 0, "┬", Style::default().gray()); - //buf.set_string(x + 18, y + 1, "│", Style::default().gray().dim()); - //buf.set_string(x + 18, y + 2, "┴", Style::default().gray()); - - //draw_rec_dub_button(buf, x + 17, y); - //draw_sequence_button(buf, x, y, &sequencer.name); - - //draw_leaf(buf, Rect { x, y: y + height, width, height: 2 }, 0, 0, "Inputs "); - //draw_leaf(buf, Rect { x, y: y + height, width, height: 2 }, 0, 8, "Outputs"); - //for x2 in 0..4 { - //for y2 in 0..4 { - //buf.set_string( - //x + 2 + x2 * 3, - //y + 5 + y2, - //format!("{:>02} ", 1 + x2 + y2 * 4), - //Style::default().green().bold().not_dim() - //) - //} - //} - // -//fn draw_sequence_button ( - //buf: &mut Buffer, - //x: u16, - //y: u16, - //name: &str -//) { - //draw_box(buf, Rect { x, y, width: 18, height: 3 }); - //buf.set_string(x + 1, y + 1, &format!(" ▶ {} ", name), - //Style::default().white().bold().not_dim()); - //buf.set_string(x + 4, y + 0, "┬", Style::default().gray()); - //buf.set_string(x + 4, y + 1, "│", Style::default().gray().dim()); - //buf.set_string(x + 4, y + 2, "┴", Style::default().gray()); -//} - -//fn draw_rec_dub_button ( - //buf: &mut Buffer, - //x: u16, - //y: u16, -//) { - //draw_box(buf, Rect { x, y, width: 18, height: 3 }); - //buf.set_string(x + 1, y + 1, " ⏺ REC DUB ", - //Style::default().white().bold().not_dim()); - //buf.set_string(x + 5, y + 0, "┬", Style::default().gray()); - //buf.set_string(x + 5, y + 1, "│", Style::default().gray().dim()); - //buf.set_string(x + 5, y + 2, "┴", Style::default().gray()); - //buf.set_string(x + 11, y + 0, "┬", Style::default().gray()); - //buf.set_string(x + 11, y + 1, "│", Style::default().gray().dim()); - //buf.set_string(x + 11, y + 2, "┴", Style::default().gray()); -//} diff --git a/src/time.rs b/src/time.rs index c49939b4..2f7ce94d 100644 --- a/src/time.rs +++ b/src/time.rs @@ -8,7 +8,7 @@ pub struct Frame(pub u32); pub struct Usec(pub u64); /// Beats per minute as `120000` = 120.000BPM -pub struct Tempo(pub u32); +pub struct Tempo(pub u64); /// Time signature: N/Mths per bar. pub struct Signature(pub u32, pub u32); @@ -36,17 +36,17 @@ impl Usec { } impl Tempo { + #[inline] + pub fn usec_per_bar (&self, beats: u64) -> Usec { + Usec(beats * self.usec_per_beat().0) + } #[inline] pub fn usec_per_beat (&self) -> Usec { Usec(60_000_000_000 / self.0 as u64) } #[inline] - pub fn usec_per_quarter (&self) -> Usec { - Usec(self.usec_per_beat().0 / 4) - } - #[inline] - pub fn usec_per_tick (&self, ppq: u32) -> Usec { - Usec(self.usec_per_quarter().0 / ppq as u64) + pub fn usec_per_step (&self, divisor: u64) -> Usec { + Usec(self.usec_per_beat().0 / divisor as u64) } #[inline] pub fn quantize (&self, step: &NoteDuration, time: Usec) -> Usec {