Skip to content

Commit

Permalink
Fixes for global start beat handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Ameobea committed Jan 31, 2024
1 parent 82bcbfa commit b50f3e7
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 25 deletions.
68 changes: 60 additions & 8 deletions engine/note_container/src/exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,16 @@ pub fn iter_notes(
/// 1. an `is_attack` flag which is true if the note is starting and false if the note is ending
/// 2. line index
/// 3. beat
///
/// If `include_partial_notes` is true, then notes that intersect the start or end of the selection
/// will be included but truncated to the bounds of the selection.
#[wasm_bindgen]
pub fn iter_notes_with_cb(
lines: *const NoteLines,
start_beat_inclusive: f64,
end_beat_exclusive: f64,
cb: Function,
include_partial_notes: bool,
) {
let notes = unsafe { &*lines };

Expand All @@ -147,8 +151,14 @@ pub fn iter_notes_with_cb(
note: Note,
}

struct NoteEvent {
is_attack: bool,
line_ix: usize,
beat: f64,
}

let mut unreleased_notes: HashMap<u32, UnreleasedNote> = HashMap::default();
let mut events: Vec<(bool, usize, f64)> = Vec::default();
let mut events: Vec<NoteEvent> = Vec::default();
let iter = notes.lines.iter().enumerate().flat_map(|(line_ix, line)| {
line
.inner
Expand All @@ -165,7 +175,11 @@ pub fn iter_notes_with_cb(
for (line_ix, pos, entry) in iter {
match entry {
NoteEntry::NoteStart { note } => {
events.push((true, line_ix, pos));
events.push(NoteEvent {
is_attack: true,
line_ix,
beat: pos,
});
let existing = unreleased_notes.insert(note.id, UnreleasedNote {
line_ix,
start_point: pos,
Expand All @@ -178,8 +192,21 @@ pub fn iter_notes_with_cb(
},
NoteEntry::NoteEnd { note_id } => {
let existing = unreleased_notes.remove(&note_id);
if existing.is_some() {
events.push((false, line_ix, pos));

if existing.is_none() && include_partial_notes {
events.push(NoteEvent {
is_attack: true,
line_ix,
beat: start_beat_inclusive,
});
}

if existing.is_some() || include_partial_notes {
events.push(NoteEvent {
is_attack: false,
line_ix,
beat: pos,
});
}
},
NoteEntry::StartAndEnd {
Expand All @@ -189,10 +216,18 @@ pub fn iter_notes_with_cb(
// release before attack
let existing = unreleased_notes.remove(&end_note_id);
if existing.is_some() {
events.push((false, line_ix, pos));
events.push(NoteEvent {
is_attack: false,
line_ix,
beat: pos,
});
}

events.push((true, line_ix, pos));
events.push(NoteEvent {
is_attack: true,
line_ix,
beat: pos,
});
let existing = unreleased_notes.insert(start_note.id, UnreleasedNote {
line_ix,
start_point: pos,
Expand All @@ -212,10 +247,27 @@ pub fn iter_notes_with_cb(
}
for note in unreleased_notes.values() {
let release_time = note.start_point + note.note.length;
events.push((false, note.line_ix, release_time));
events.push(NoteEvent {
is_attack: false,
line_ix: note.line_ix,
beat: release_time,
});
}

for (is_attack, line_ix, beat) in events {
events.sort_unstable_by(|a, b| {
FloatOrd(a.beat)
.cmp(&FloatOrd(b.beat))
.then_with(|| a.line_ix.cmp(&b.line_ix))
// attacks before releases
.then_with(|| a.is_attack.cmp(&b.is_attack).reverse())
});

for NoteEvent {
is_attack,
line_ix,
beat,
} in events
{
let _ = cb.call3(
&JsValue::NULL,
&JsValue::from(is_attack),
Expand Down
6 changes: 3 additions & 3 deletions public/SequencerWorkletProcessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class SequencerWorkletProcessor extends AudioWorkletProcessor {
break;
}
case 'start': {
this.startBeat = globalThis.curBeat;
this.lastBeat = -1;
this.startBeat = evt.data.startBeat ?? globalThis.curBeat;
this.lastBeat = Math.trunc(evt.data.startBeat / this.config.beatRatio);
break;
}
case 'stop': {
Expand All @@ -44,7 +44,7 @@ class SequencerWorkletProcessor extends AudioWorkletProcessor {
return true;
}

const beatsSinceStart = globalThis.curBeat - this.startBeat;
const beatsSinceStart = globalThis.curBeat;
const curQuantizedBeat = Math.trunc(beatsSinceStart / this.config.beatRatio);

if (curQuantizedBeat !== this.lastBeat) {
Expand Down
2 changes: 1 addition & 1 deletion src/eventScheduler/eventScheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const useIsGlobalBeatCounterStarted = () => {
*
* Triggers all callbacks registered with `addStartCB` to be called.
*/
export const startAll = (startBeat = 0) => {
export const startAll = (startBeat = getCurBeat()) => {
if (isStarted) {
console.warn("Tried to start global beat counter, but it's already started");
return;
Expand Down
3 changes: 2 additions & 1 deletion src/midiEditor/MIDIEditorUIManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ export class ManagedMIDIEditorUIInstance {
noteLinesCtxPtr,
startBeatInclusive ?? 0,
endBeatExclusive ?? -1,
cb
cb,
true
);
};

Expand Down
14 changes: 8 additions & 6 deletions src/midiEditor/PlaybackHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ export default class MIDIEditorPlaybackHandler {
* to determine if a given playback session has ended or not.
*/
private playbackGeneration: number | null = null;
private lastPlaybackStartBeat = 0;
private cbs: {
start: (startBeat: number) => void;
stop: () => void;
Expand Down Expand Up @@ -343,28 +342,32 @@ export default class MIDIEditorPlaybackHandler {
return;
}

const firstLoopRemainder = loopLengthBeats - (startBeat % loopLengthBeats);
const curSegmentAbsoluteStartBeat =
startBeat - (loopLengthBeats - firstLoopRemainder) + loopIx * loopLengthBeats;
const curSegmentAbsoluteEndBeat = curSegmentAbsoluteStartBeat + loopLengthBeats;

const insts = get(this.inst.uiManager.instances);
for (const inst of insts) {
if (inst.type !== 'midiEditor') {
continue;
}
const instance = inst.instance;

const offset = loopLengthBeats * loopIx;
// If we're starting in the middle of a loop on the first loop iteration, filter out notes that
// start before the starting cursor position
const notesInRange = this.getNotesInRange(
instance,
loopIx === 0 ? startBeat : 0,
loopIx === 0 ? startBeat % loopLengthBeats : 0,
loopPoint,
offset
curSegmentAbsoluteStartBeat
);

this.scheduleNotes(instance, notesInRange);
}

// Schedule an event before the loop ends to recursively schedule another.
const curSegmentAbsoluteEndBeat = loopLengthBeats * (loopIx + 1);

scheduleEventBeats(curSegmentAbsoluteEndBeat - Math.min(1, loopLengthBeats / 2), () =>
scheduleAnother(loopIx + 1)
);
Expand Down Expand Up @@ -408,7 +411,6 @@ export default class MIDIEditorPlaybackHandler {
}
}

this.lastPlaybackStartBeat = startBeat;
this.playbackGeneration = Math.random();
if (this.loopPoint === null) {
this.scheduleOneshot(startBeat);
Expand Down
12 changes: 8 additions & 4 deletions src/sequencer/redux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const reschedule = (state: SequencerReduxState): SequencerReduxState => {
};

interface SequencerInst extends SequencerReduxInfra {
onGlobalStart: () => void;
onGlobalStart: (startBeat: number) => void;
onGlobalStop: () => void;
}

Expand Down Expand Up @@ -173,8 +173,12 @@ const actionGroups = {
}),
}),
TOGGLE_IS_PLAYING: buildActionGroup({
actionCreator: (vcId: string) => ({ type: 'TOGGLE_IS_PLAYING', vcId }),
subReducer: (state: SequencerReduxState, { vcId }) => {
actionCreator: (vcId: string, startBeat?: number) => ({
type: 'TOGGLE_IS_PLAYING',
vcId,
startBeat,
}),
subReducer: (state: SequencerReduxState, { vcId, startBeat }) => {
if (!state.awpHandle) {
return state;
}
Expand All @@ -185,7 +189,7 @@ const actionGroups = {
state.awpHandle.port.postMessage({ type: 'stop' });
return { ...state, isPlaying: false, curActiveMarkIx: null };
} else {
state.awpHandle.port.postMessage({ type: 'start' });
state.awpHandle.port.postMessage({ type: 'start', startBeat: startBeat ?? 0 });
return reschedule({
...state,
isPlaying: true,
Expand Down
4 changes: 2 additions & 2 deletions src/sequencer/sequencer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -329,12 +329,12 @@ export const init_sequencer = (stateKey: string) => {
console.error(`Existing entry in sequencer redux infra map for vcId ${vcId}; overwriting...`);
}

const onGlobalStart = () => {
const onGlobalStart = (startBeat: number) => {
const isPlaying = reduxInfra.getState().sequencer.isPlaying;
if (isPlaying) {
reduxInfra.dispatch(reduxInfra.actionCreators.sequencer.TOGGLE_IS_PLAYING(vcId));
}
reduxInfra.dispatch(reduxInfra.actionCreators.sequencer.TOGGLE_IS_PLAYING(vcId));
reduxInfra.dispatch(reduxInfra.actionCreators.sequencer.TOGGLE_IS_PLAYING(vcId, startBeat));
};
registerGlobalStartCB(onGlobalStart);
const onGlobalStop = () => {
Expand Down

0 comments on commit b50f3e7

Please sign in to comment.