blob: 7455f6a9ca19d9b25ea552a9a3bc0536aeb699d9 [file]
import { Layout, Context } from "rive/host";
import { Audio, AudioHandle, AudioProcessor } from "rive/audio";
import { Ring, Snapshot } from "rive/shared";
export const kLead: f64 = 4800;
export class Note {
frame: f64 = 0;
}
// The frames a clicker has rendered, and where it played the note.
export class Heard {
rendered: f64 = 0;
stamp: f64 = -1;
played: f64 = -1;
}
export class Link {
notes: Ring<Note> = Ring.create<Note>(256);
heard: Snapshot<Heard> = Snapshot.create<Heard>();
}
// Clicks once at the note's frame, or at the start of the block that finds
// it late.
export class Clicker extends AudioProcessor<Link> {
link: Link? = null;
channels: i32 = 2;
note: Note = new Note();
waiting: bool = false;
heard: Heard = new Heard();
override init(sampleRate: f64, maxFrames: i32, channels: i32, link: Link): void {
this.link = link;
this.channels = channels;
}
override process(out: StaticArray<f32>, frames: i32, startFrame: f64): void {
let link = this.link!;
if (!this.waiting) {
this.waiting = link.notes.receiveInto(this.note);
}
if (this.waiting && this.note.frame < startFrame + <f64>frames) {
this.waiting = false;
let at = <i32>Math.max(this.note.frame - startFrame, 0);
for (let c = 0; c < this.channels; c++) {
out[at * this.channels + c] = 1;
}
this.heard.stamp = this.note.frame;
this.heard.played = startFrame + <f64>at;
}
this.heard.rendered = startFrame + <f64>frames;
link.heard.publish(this.heard);
}
}
// Once the clicker has rendered past the lead, stamps one note that far
// ahead of the main lane's audio clock.
export class Main extends Layout {
context: Context? = null;
link: Link = new Link();
clicker: AudioHandle? = null;
last: f64 = -1;
sent: bool = false;
reported: bool = false;
override init(context: Context): bool {
this.context = context;
this.clicker = Audio.start(() => new Clicker(), this.link);
return true;
}
override advance(seconds: f64): bool {
let context = this.context!;
let now = Audio.timeFrame();
if (this.last < 0) {
context.log("clock starts at " + now.toString());
} else if (now < this.last) {
context.log("clock went back from " + this.last.toString() + " to " + now.toString());
}
this.last = now;
let heard = this.link.heard.latest();
if (heard == null) {
return true;
}
if (!this.sent && heard.value.rendered >= kLead) {
this.sent = true;
let note = new Note();
note.frame = now + kLead;
this.link.notes.send(note);
context.log("clock sent at " + now.toString() + " rate " +
Audio.sampleRate().toString() + " time " + Audio.time().toString());
context.log("clock offset " + (heard.value.rendered - now).toString());
}
if (!this.reported && heard.value.played >= 0) {
this.reported = true;
context.log("clock played " + (heard.value.played - heard.value.stamp).toString() +
" frames late");
}
return true;
}
}