Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cycle chords & chord mapping #22

Merged
merged 4 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion examples/play.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// generate a few phrases
let cycle =
new_cycle_event("bd [~ bd] ~ ~ bd [~ bd] _ ~ bd [~ bd] ~ ~ bd [~ bd] [_ bd2] [~ bd _ ~]")?
.with_mappings(&[("bd", new_note("c4")), ("bd2", new_note(("c4", None, 0.5)))]);
.with_mappings(&[
("bd", vec![new_note("c4")]),
("bd2", vec![new_note(("c4", None, 0.5))]),
]);

let kick_pattern = beat_time
.every_nth_beat(16.0)
Expand Down
3 changes: 1 addition & 2 deletions src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ pub use callback::{
pub(crate) use callback::LuaCallback;
pub(crate) use timeout::LuaTimeoutHook;
pub(crate) use unwrap::{
gate_trigger_from_value, note_event_from_value, note_events_from_value,
pattern_pulse_from_value,
gate_trigger_from_value, note_events_from_value, pattern_pulse_from_value,
};

// ---------------------------------------------------------------------------------------------
Expand Down
12 changes: 6 additions & 6 deletions src/bindings/cycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ use mlua::prelude::*;

use crate::{event::NoteEvent, tidal::Cycle};

use super::unwrap::{bad_argument_error, note_event_from_value};
use super::unwrap::{bad_argument_error, note_events_from_value};

// ---------------------------------------------------------------------------------------------

/// Cycle Userdata in bindings
#[derive(Clone, Debug)]
pub struct CycleUserData {
pub cycle: Cycle,
pub mappings: Vec<(String, Option<NoteEvent>)>,
pub mappings: Vec<(String, Vec<Option<NoteEvent>>)>,
pub mapping_function: Option<LuaOwnedFunction>,
}

Expand Down Expand Up @@ -47,7 +47,7 @@ impl LuaUserData for CycleUserData {
let cycle = this.cycle.clone();
let mut mappings = Vec::new();
for (k, v) in table.pairs::<LuaValue, LuaValue>().flatten() {
mappings.push((k.to_string()?, note_event_from_value(&v, None)?));
mappings.push((k.to_string()?, note_events_from_value(&v, None)?));
}
let mapping_function = None;
Ok(CycleUserData {
Expand Down Expand Up @@ -136,9 +136,9 @@ mod test {
.into_iter()
.collect::<HashMap<_, _>>(),
HashMap::from([
("a".to_string(), new_note(Note::C0)),
("b".to_string(), new_note(Note::C4)),
("c".to_string(), new_note(Note::C6)),
("a".to_string(), vec![new_note(Note::C0)]),
("b".to_string(), vec![new_note(Note::C4)]),
("c".to_string(), vec![new_note(Note::C6)]),
])
);

Expand Down
184 changes: 121 additions & 63 deletions src/event/cycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,11 @@ use fraction::Fraction;
use crate::{
event::{new_note, Event, EventIter, EventIterItem, InstrumentId, NoteEvent},
tidal::{Cycle, Event as CycleEvent, Target as CycleTarget, Value as CycleValue},
BeatTimeBase, Note, PulseIterItem,
BeatTimeBase, Chord, Note, PulseIterItem,
};

// -------------------------------------------------------------------------------------------------

/// Default conversion of a cycle event value to an optional NoteEvent, as used by [`EventIter`].
impl From<&CycleValue> for Option<NoteEvent> {
fn from(value: &CycleValue) -> Self {
match value {
CycleValue::Hold => None,
CycleValue::Rest => new_note(Note::OFF),
CycleValue::Float(_f) => None,
CycleValue::Integer(i) => new_note(Note::from((*i).clamp(0, 0x7f) as u8)),
CycleValue::Pitch(p) => new_note(Note::from(p.midi_note())),
CycleValue::Name(s) => {
if s.eq_ignore_ascii_case("off") {
new_note(Note::OFF)
} else {
None
}
}
}
}
}

/// Default conversion of a cycle target to an optional instrument id, as used by [`EventIter`].
impl From<&CycleTarget> for Option<InstrumentId> {
fn from(value: &CycleTarget) -> Self {
Expand All @@ -41,57 +21,125 @@ impl From<&CycleTarget> for Option<InstrumentId> {
}
}

/// Default conversion of a CycleValue into a note stack.
///
/// Returns an error when resolving chord modes failed.
impl TryFrom<&CycleValue> for Vec<Option<NoteEvent>> {
type Error = String;

fn try_from(value: &CycleValue) -> Result<Self, String> {
match value {
CycleValue::Hold => Ok(vec![None]),
CycleValue::Rest => Ok(vec![new_note(Note::OFF)]),
CycleValue::Float(_f) => Ok(vec![None]),
CycleValue::Integer(i) => Ok(vec![new_note(Note::from((*i).clamp(0, 0x7f) as u8))]),
CycleValue::Pitch(p) => Ok(vec![new_note(Note::from(p.midi_note()))]),
CycleValue::Chord(p, m) => {
let chord = Chord::try_from((p.midi_note(), m.as_str()))?;
Ok(chord
.intervals()
.iter()
.map(|i| new_note(chord.note().transposed(*i as i32)))
.collect())
}
CycleValue::Name(s) => {
if s.eq_ignore_ascii_case("off") {
Ok(vec![new_note(Note::OFF)])
} else {
Ok(vec![None])
}
}
}
}
}

// -------------------------------------------------------------------------------------------------

/// Helper struct to convert time tagged events from Cycle into a `Vec<EventIterItem>`
pub(crate) struct CycleNoteEvents {
events: Vec<(Fraction, Fraction, Vec<Option<NoteEvent>>)>,
// collected events for a given time span per channels
events: Vec<(Fraction, Fraction, Vec<Option<Event>>)>,
// max note event count per channel
event_counts: Vec<usize>,
}

impl CycleNoteEvents {
/// Create a new, empty list of events.
pub fn new() -> Self {
Self { events: vec![] }
let events = Vec::with_capacity(16);
let event_counts = Vec::with_capacity(3);
Self {
events,
event_counts,
}
}

/// Add a new cycle channel item.
/// Add a single note event stack from a cycle channel event.
pub fn add(
&mut self,
channel: usize,
start: Fraction,
length: Fraction,
note_event: NoteEvent,
note_events: Vec<Option<NoteEvent>>,
) {
// memorize max event count per channel
if self.event_counts.len() <= channel {
self.event_counts.resize(channel + 1, 0);
}
self.event_counts[channel] = self.event_counts[channel].max(note_events.len());
// insert events into existing time slot or a new one
match self
.events
.binary_search_by(|(time, _, _)| time.cmp(&start))
{
Ok(pos) => {
// use max length of all notes in stack
let note_length = &mut self.events[pos].1;
*note_length = (*note_length).max(length);
// add note to existing time stack
let note_events = &mut self.events[pos].2;
note_events.resize(channel + 1, None);
note_events[channel] = Some(note_event);
// use min length of all notes in stack
let event_length = &mut self.events[pos].1;
*event_length = (*event_length).min(length);
// add new notes to existing events
let timed_event = &mut self.events[pos].2;
timed_event.resize(channel + 1, None);
timed_event[channel] = Some(Event::NoteEvents(note_events));
}
Err(pos) => {
// insert a new time event
let mut timed_event = Vec::with_capacity(channel + 1);
timed_event.resize(channel + 1, None);
timed_event[channel] = Some(Event::NoteEvents(note_events));
self.events.insert(pos, (start, length, timed_event))
}
Err(pos) => self
.events
.insert(pos, (start, length, vec![Some(note_event)])),
}
}

/// Convert to a list of NoteEvents.
/// Convert to a list of EventIterItems.
pub fn into_event_iter_items(self) -> Vec<EventIterItem> {
let mut events: Vec<EventIterItem> = Vec::with_capacity(self.events.len());
for (start_time, length, note_events) in self.events.into_iter() {
events.push(EventIterItem::new_with_fraction(
Event::NoteEvents(note_events),
start_time,
length,
));
// max number of note events in a single merged down Event
let max_event_count = self.event_counts.iter().sum::<usize>();
// apply padding per channel, merge down and convert to EventIterItem
let mut event_iter_items: Vec<EventIterItem> = Vec::with_capacity(self.events.len());
for (start_time, length, mut events) in self.events.into_iter() {
// ensure that each event in the channel, contains the same number of note events
for (channel, mut event) in events.iter_mut().enumerate() {
if let Some(Event::NoteEvents(note_events)) = &mut event {
// pad existing note events with OFFs
note_events.resize_with(self.event_counts[channel], || new_note(Note::OFF));
} else if self.event_counts[channel] > 0 {
// pad missing note events with 'None'
*event = Some(Event::NoteEvents(vec![None; self.event_counts[channel]]))
}
}
// merge all events that happen at the same time together
let mut merged_note_events = Vec::with_capacity(max_event_count);
for mut event in events.into_iter().flatten() {
if let Event::NoteEvents(note_events) = &mut event {
merged_note_events.append(note_events);
}
}
// convert padded, merged note events to a timed 'Event'
let event = Event::NoteEvents(merged_note_events);
event_iter_items.push(EventIterItem::new_with_fraction(event, start_time, length));
}
events
event_iter_items
}
}

Expand All @@ -106,7 +154,7 @@ impl CycleNoteEvents {
#[derive(Clone, Debug)]
pub struct CycleEventIter {
cycle: Cycle,
mappings: HashMap<String, Option<NoteEvent>>,
mappings: HashMap<String, Vec<Option<NoteEvent>>>,
}

impl CycleEventIter {
Expand All @@ -132,7 +180,10 @@ impl CycleEventIter {
}

/// Return a new cycle with the given value mappings applied.
pub fn with_mappings<S: Into<String> + Clone>(self, map: &[(S, Option<NoteEvent>)]) -> Self {
pub fn with_mappings<S: Into<String> + Clone>(
self,
map: &[(S, Vec<Option<NoteEvent>>)],
) -> Self {
let mut mappings = HashMap::new();
for (k, v) in map.iter().cloned() {
mappings.insert(k.into(), v);
Expand All @@ -141,23 +192,25 @@ impl CycleEventIter {
}

/// Generate a note event from a single cycle event, applying mappings if necessary
fn note_event(&mut self, event: CycleEvent) -> Option<NoteEvent> {
let mut note_event = {
if let Some(mapped_note_event) = self.mappings.get(event.string()) {
// apply custom note mapping
mapped_note_event.clone()
fn note_events(&mut self, event: CycleEvent) -> Result<Vec<Option<NoteEvent>>, String> {
let mut note_events = {
if let Some(note_events) = self.mappings.get(event.string()) {
// apply custom note mappings
note_events.clone()
} else {
// else try to convert value to a note
event.value().into()
// try converting the cycle value to a single note
event.value().try_into()?
}
};
// inject target instrument, if present
if let Some(instrument) = event.target().into() {
if let Some(note_event) = &mut note_event {
note_event.instrument = Some(instrument);
for mut note_event in &mut note_events {
if let Some(note_event) = &mut note_event {
note_event.instrument = Some(instrument);
}
}
}
note_event
Ok(note_events)
}

/// Generate next batch of events from the next cycle run.
Expand All @@ -168,7 +221,7 @@ impl CycleEventIter {
match self.cycle.generate() {
Ok(events) => events,
Err(err) => {
// NB: only expected error here is exceeding the event limit
// NB: only expected error here is exceeding the event limit
panic!("Cycle runtime error: {err}");
}
}
Expand All @@ -179,8 +232,16 @@ impl CycleEventIter {
for event in channel_events.into_iter() {
let start = event.span().start();
let length = event.span().length();
if let Some(note_event) = self.note_event(event) {
timed_note_events.add(channel_index, start, length, note_event);
match self.note_events(event) {
Ok(note_events) => {
if !note_events.is_empty() {
timed_note_events.add(channel_index, start, length, note_events);
}
}
Err(err) => {
// NB: only expected error here is a chord parser error
panic!("Cycle runtime error: {err}");
}
}
}
}
Expand Down Expand Up @@ -221,9 +282,6 @@ pub fn new_cycle_event(input: &str) -> Result<CycleEventIter, String> {
CycleEventIter::from_mini(input)
}

pub fn new_cycle_event_with_seed(
input: &str,
seed: [u8; 32],
) -> Result<CycleEventIter, String> {
pub fn new_cycle_event_with_seed(input: &str, seed: [u8; 32]) -> Result<CycleEventIter, String> {
CycleEventIter::from_mini_with_seed(input, seed)
}
Loading