Skip to content

Commit

Permalink
Merge pull request #28 from vestor-dev/invert_notes
Browse files Browse the repository at this point in the history
feat: add invert_notes function
  • Loading branch information
caseywescott authored Dec 19, 2024
2 parents f48f7c8 + b432050 commit 4fea2fb
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 0 deletions.
78 changes: 78 additions & 0 deletions src/midi/core.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ trait MidiTrait {
fn arpeggiate_chords(self: @Midi, pattern: ArpPattern) -> Midi;
/// Add or modify dynamics (velocity) of notes based on a specified curve or pattern.
fn edit_dynamics(self: @Midi, curve: VelocityCurve) -> Midi;
/// Invert notes around a pivot note
fn invert_notes(self: @Midi, pivot_note: u8) -> Midi;
}

impl MidiImpl of MidiTrait {
Expand Down Expand Up @@ -937,6 +939,82 @@ impl MidiImpl of MidiTrait {
};
};

Midi { events: eventlist.span() }
}

fn invert_notes(self: @Midi, pivot_note: u8) -> Midi {
let mut ev = self.clone().events;
let mut eventlist = ArrayTrait::<Message>::new();

loop {
match ev.pop_front() {
Option::Some(currentevent) => {
match currentevent {
Message::NOTE_ON(NoteOn) => {
// Calculate inverted pitch
let original_note = *NoteOn.note;
let inverted_note = if original_note <= pivot_note {
pivot_note + (pivot_note - original_note)
} else {
// When original note is above pivot, we subtract in reverse
pivot_note - (original_note - pivot_note)
};

// Ensure note stays within MIDI range (0-127)
let final_note = if inverted_note > 127 {
127
} else {
inverted_note
};

let newnote = NoteOn {
channel: *NoteOn.channel,
note: final_note,
velocity: *NoteOn.velocity,
time: *NoteOn.time
};
let notemessage = Message::NOTE_ON((newnote));
eventlist.append(notemessage);
},
Message::NOTE_OFF(NoteOff) => {
let original_note = *NoteOff.note;
let inverted_note = if original_note <= pivot_note {
pivot_note + (pivot_note - original_note)
} else {
pivot_note - (original_note - pivot_note)
};

// Ensure note stays within MIDI range (0-127)
let final_note = if inverted_note > 127 {
127
} else {
inverted_note
};

let newnote = NoteOff {
channel: *NoteOff.channel,
note: final_note,
velocity: *NoteOff.velocity,
time: *NoteOff.time
};
let notemessage = Message::NOTE_OFF((newnote));
eventlist.append(notemessage);
},
// Pass through all other message types unchanged
Message::SET_TEMPO(_) => { eventlist.append(*currentevent); },
Message::TIME_SIGNATURE(_) => { eventlist.append(*currentevent); },
Message::CONTROL_CHANGE(_) => { eventlist.append(*currentevent); },
Message::PITCH_WHEEL(_) => { eventlist.append(*currentevent); },
Message::AFTER_TOUCH(_) => { eventlist.append(*currentevent); },
Message::POLY_TOUCH(_) => { eventlist.append(*currentevent); },
Message::PROGRAM_CHANGE(_) => { eventlist.append(*currentevent); },
Message::SYSTEM_EXCLUSIVE(_) => { eventlist.append(*currentevent); },
}
},
Option::None(_) => { break; }
};
};

Midi { events: eventlist.span() }
}
}
99 changes: 99 additions & 0 deletions src/tests/test_midi.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -547,4 +547,103 @@ mod tests {
};
};
}

#[test]
#[available_gas(100000000000)]
fn invert_notes_test() {
let mut eventlist = ArrayTrait::<Message>::new();

// Create test notes - using middle C (60) as pivot
let newnoteon1 = NoteOn {
channel: 0, note: 48, velocity: 100, time: FP32x32 { mag: 0, sign: false }
}; // C3 - should become C5 (72)

let newnoteon2 = NoteOn {
channel: 0, note: 67, velocity: 100, time: FP32x32 { mag: 1000, sign: false }
}; // G4 - should become Db4 (53)

let newnoteon3 = NoteOn {
channel: 0, note: 60, velocity: 100, time: FP32x32 { mag: 1500, sign: false }
}; // C4 (pivot) - should stay C4 (60)

let newnoteoff1 = NoteOff {
channel: 0, note: 48, velocity: 100, time: FP32x32 { mag: 2000, sign: false }
};

let newnoteoff2 = NoteOff {
channel: 0, note: 67, velocity: 100, time: FP32x32 { mag: 1500, sign: false }
};

let newnoteoff3 = NoteOff {
channel: 0, note: 60, velocity: 100, time: FP32x32 { mag: 5000, sign: false }
};

// Create messages
let notemessageon1 = Message::NOTE_ON((newnoteon1));
let notemessageon2 = Message::NOTE_ON((newnoteon2));
let notemessageon3 = Message::NOTE_ON((newnoteon3));

let notemessageoff1 = Message::NOTE_OFF((newnoteoff1));
let notemessageoff2 = Message::NOTE_OFF((newnoteoff2));
let notemessageoff3 = Message::NOTE_OFF((newnoteoff3));

// Add tempo message
let newtempo = SetTempo { tempo: 120, time: Option::Some(FP32x32 { mag: 0, sign: false }) };
let tempomessage = Message::SET_TEMPO((newtempo));

// Build event list
eventlist.append(tempomessage);
eventlist.append(notemessageon1);
eventlist.append(notemessageon2);
eventlist.append(notemessageon3);
eventlist.append(notemessageoff1);
eventlist.append(notemessageoff2);
eventlist.append(notemessageoff3);

let midiobj = Midi { events: eventlist.span() };

// Invert around middle C (60)
let midiobjnotes = midiobj.invert_notes(60);

// Test the inverted notes
let mut ev = midiobjnotes.clone().events;
loop {
match ev.pop_front() {
Option::Some(currentevent) => {
match currentevent {
Message::NOTE_ON(NoteOn) => {
if *NoteOn.time.mag == 0 {
// First note (C3 -> C5)
assert(*NoteOn.note == 72, 'C3 should invert to C5');
} else if *NoteOn.time.mag == 1000 {
// Second note (G4 -> Db4)
assert(*NoteOn.note == 53, 'G4 should invert to Db4');
} else if *NoteOn.time.mag == 1500 {
// Third note (pivot note)
assert(*NoteOn.note == 60, 'Pivot note should not change');
}
},
Message::NOTE_OFF(NoteOff) => {
if *NoteOff.time.mag == 2000 {
assert(*NoteOff.note == 72, 'C3 OFF should invert to C5');
} else if *NoteOff.time.mag == 1500 {
assert(*NoteOff.note == 53, 'G4 OFF should invert to Db4');
} else if *NoteOff.time.mag == 5000 {
assert(*NoteOff.note == 60, 'Pivot OFF should not change');
}
},
Message::SET_TEMPO(_) => {},
Message::TIME_SIGNATURE(_) => {},
Message::CONTROL_CHANGE(_) => {},
Message::PITCH_WHEEL(_) => {},
Message::AFTER_TOUCH(_) => {},
Message::POLY_TOUCH(_) => {},
Message::PROGRAM_CHANGE(_) => {},
Message::SYSTEM_EXCLUSIVE(_) => {},
}
},
Option::None(_) => { break; }
};
};
}
}

0 comments on commit 4fea2fb

Please sign in to comment.