museaikit
Version:
A powerful music-focused AI toolkit
201 lines • 6.72 kB
JavaScript
import * as Tone from 'tone';
import { performance } from '../core/compat/global';
import { NoteSequence } from '../protobuf/index';
import { DEFAULT_QUARTERS_PER_MINUTE } from './constants';
import * as logging from './logging';
export class BaseRecorderCallback {
}
export class Recorder {
callbackObject;
config;
recording;
firstNoteTimestamp;
notes = [];
onNotes;
midiInputs = [];
startRecordingAtFirstNote;
loClick = new Tone
.MembraneSynth({
pitchDecay: 0.008,
envelope: { attack: 0.001, decay: 0.3, sustain: 0 },
})
.toDestination();
hiClick = new Tone
.MembraneSynth({
pitchDecay: 0.008,
envelope: { attack: 0.001, decay: 0.3, sustain: 0 },
})
.toDestination();
clickLoop;
constructor(config = {}, callbackObject) {
this.config = {
playClick: config.playClick,
qpm: config.qpm || DEFAULT_QUARTERS_PER_MINUTE,
playCountIn: config.playCountIn,
startRecordingAtFirstNote: config.startRecordingAtFirstNote || false,
};
this.callbackObject = callbackObject;
this.recording = false;
this.onNotes = new Map();
}
async initialize() {
await navigator
.requestMIDIAccess()
.then((midi) => this.midiReady(midi), (err) => console.log('Something went wrong', err));
}
midiReady(midi) {
logging.log('Initialized Recorder', 'Recorder');
const inputs = midi.inputs.values();
for (let input = inputs.next(); input && !input.done; input = inputs.next()) {
this.midiInputs.push(input.value);
}
}
isRecording() {
return this.recording;
}
setTempo(qpm) {
this.config.qpm = qpm;
if (Tone.Transport.state === 'started') {
Tone.Transport.bpm.value = qpm;
}
}
enablePlayClick(playClick) {
this.config.playClick = playClick;
}
enablePlayCountIn(countIn) {
this.config.playCountIn = countIn;
}
initClickLoop() {
let clickStep = 0;
this.clickLoop = new Tone.Loop((time) => {
if (clickStep % 4 === 0) {
this.loClick.triggerAttack('G5', time);
}
else {
this.hiClick.triggerAttack('C6', time);
}
clickStep++;
if (this.config.playCountIn && clickStep === 4) {
Tone.Transport.stop();
this.clickLoop.stop();
}
}, '4n');
}
getMIDIInputs() {
return this.midiInputs;
}
start(midiInputs) {
const list = midiInputs ? midiInputs : this.midiInputs;
for (const input of list) {
input.onmidimessage = (event) => {
this.midiMessageReceived(event);
};
}
if (this.config.playClick || this.config.playCountIn) {
this.initClickLoop();
Tone.Transport.bpm.value = this.config.qpm;
Tone.Transport.start();
this.clickLoop.start();
}
else {
this.clickLoop = null;
}
this.recording = true;
this.firstNoteTimestamp = undefined;
this.notes = [];
this.onNotes = new Map();
if (!this.startRecordingAtFirstNote) {
const timeStamp = Date.now();
this.firstNoteTimestamp = timeStamp;
}
}
stop() {
this.recording = false;
const timeStamp = Date.now();
this.onNotes.forEach((pitch, note) => {
this.noteOff(note, timeStamp);
});
for (const input of this.midiInputs) {
input.onmidimessage = null;
}
if (this.clickLoop) {
Tone.Transport.stop();
this.clickLoop.stop();
}
if (this.notes.length === 0) {
return null;
}
return this.getNoteSequence();
}
getNoteSequence() {
if (this.notes.length === 0) {
return null;
}
return NoteSequence.create({
notes: this.notes,
totalTime: this.notes[this.notes.length - 1].endTime,
});
}
reset() {
const noteSequence = this.stop();
this.firstNoteTimestamp = undefined;
this.notes = [];
this.onNotes = new Map();
return noteSequence;
}
midiMessageReceived(event) {
if (!this.recording) {
return;
}
let timeStampOffset;
if (event.timeStamp !== undefined && event.timeStamp !== 0) {
timeStampOffset = event.timeStamp;
}
else {
timeStampOffset = performance.now();
}
const timeStamp = timeStampOffset + performance.timing.navigationStart;
if (this.firstNoteTimestamp === undefined) {
this.firstNoteTimestamp = timeStamp;
}
const NOTE_ON = 9;
const NOTE_OFF = 8;
const cmd = event.data[0] >> 4;
const pitch = event.data[1];
const velocity = event.data.length > 2 ? event.data[2] : 1;
const device = event.srcElement;
if (cmd === NOTE_OFF || (cmd === NOTE_ON && velocity === 0)) {
if (this.callbackObject && this.callbackObject.noteOff) {
this.callbackObject.noteOff(pitch, velocity, device);
}
this.noteOff(pitch, timeStamp);
if (this.callbackObject && this.callbackObject.run) {
this.callbackObject.run(this.getNoteSequence());
}
}
else if (cmd === NOTE_ON) {
if (this.callbackObject && this.callbackObject.noteOn) {
this.callbackObject.noteOn(pitch, velocity, device);
}
this.noteOn(pitch, velocity, timeStamp);
}
}
noteOn(pitch, velocity, timeStamp) {
const MILLIS_PER_SECOND = 1000;
const note = new NoteSequence.Note();
note.pitch = pitch;
note.startTime = (timeStamp - this.firstNoteTimestamp) / MILLIS_PER_SECOND;
note.velocity = velocity;
this.onNotes.set(pitch, note);
}
noteOff(pitch, timeStamp) {
const MILLIS_PER_SECOND = 1000;
const note = this.onNotes.get(pitch);
if (note) {
note.endTime = (timeStamp - this.firstNoteTimestamp) / MILLIS_PER_SECOND;
this.notes.push(note);
}
this.onNotes.delete(pitch);
}
}
//# sourceMappingURL=recorder.js.map