webdaw-modules
Version:
a set of modules for building a web-based DAW
330 lines (267 loc) • 9.41 kB
JavaScript
function metronome() {
'use strict';
var
//import
context, //defined in open_module.js
findItem, //defined in asset_manager.js
getPosition, //defined in position.js
objectForEach, //defined in util.js
createMidiNote, //defined in midi_note.js
parseEvents, //defined in parse_events.js
parseMetronomeEvents, //defined in song_update.js
methodMap = {
volume: 'setVolume',
instrument: 'setInstrument',
noteNumberAccentedTick: 'setNoteNumberAccentedTick',
noteNumberNonAccentedTick: 'setNoteNumberNonAccentedTick',
velocityAccentedTick: 'setVelocityAccentedTick',
velocityNonAccentedTick: 'setVelocityNonAccentedTick',
noteLengthAccentedTick: 'setNoteLengthAccentedTick',
noteLengthNonAccentedTick: 'setNoteLengthNonAccentedTick'
},
Metronome;
function checkNumber(value) {
//console.log(value);
if (isNaN(value)) {
if (sequencer.debug) {
console.log('please provide a number');
}
return false;
}
if (value < 0 || value > 127) {
if (sequencer.debug) {
console.log('please provide a number between 0 and 127');
}
return false;
}
return value;
}
Metronome = function (song) {
this.song = song;
this.track = sequencer.createTrack(this.song.id + '_metronome', 'metronome');
this.part = sequencer.createPart();
this.track.addPart(this.part);
this.track.connect(this.song.gainNode);
this.events = [];
this.precountEvents = [];
this.noteNumberAccented = 61;
this.noteNumberNonAccented = 60;
this.volume = 1;
this.velocityNonAccented = 100;
this.velocityAccented = 100;
this.noteLengthNonAccented = song.ppq / 4; // sixteenth notes -> don't make this too short if your sample has a long attack!
this.noteLengthAccented = song.ppq / 4;
this.track.setInstrument('heartbeat/metronome');
this.precountDurationInMillis = 0;
this.bars = 0;
//this.reset();
};
function createEvents(metronome, startBar, endBar, id) {
var i, j,
data,
velocity,
noteLength,
noteNumber,
beatsPerBar,
ticksPerBeat,
ticks = 0,
events = [],
song = metronome.song,
noteOn, noteOff, note;
//console.log(startBar, endBar);
for (i = startBar; i <= endBar; i++) {
data = getPosition(song, ['barsbeats', i]);
beatsPerBar = data.nominator;
ticksPerBeat = data.ticksPerBeat;
for (j = 0; j < beatsPerBar; j++) {
noteNumber = j === 0 ? metronome.noteNumberAccented : metronome.noteNumberNonAccented;
noteLength = j === 0 ? metronome.noteLengthAccented : metronome.noteLengthNonAccented;
velocity = j === 0 ? metronome.velocityAccented : metronome.velocityNonAccented;
noteOn = sequencer.createMidiEvent(ticks, 144, noteNumber, velocity);
noteOff = sequencer.createMidiEvent(ticks + noteLength, 128, noteNumber, 0);
if (id === 'precount') {
noteOn.part = { id: 'precount' };
noteOn.track = metronome.track;
noteOff.part = { id: 'precount' };
noteOff.track = metronome.track;
}
note = createMidiNote(noteOn, noteOff);
events.push(noteOn, noteOff);
ticks += ticksPerBeat;
}
}
return events;
}
Metronome.prototype.init = function (id, startBar, endBar) {
id = id === undefined ? 'init' : id;
//console.log('metronome', id, this.song.bars, startBar, endBar);
if (this.part.numEvents > 0) {
this.part.removeEvents(this.part.events);
}
this.events = createEvents(this, startBar, endBar, id);
this.numEvents = this.events.length;
this.part.addEvents(this.events);
this.bars = this.song.bars;
parseMetronomeEvents(this.song, this.events);
};
Metronome.prototype.update = function (startBar, endBar) {
//console.time('metronome update')
if (startBar === 0) {
startBar = 1;
}
//console.log('metronome', this.song.bars, startBar, endBar);
// for now, just re-init the metronome
if (startBar !== undefined && endBar !== undefined) {
this.init('update', startBar, endBar);
} else {
this.init('update', 1, this.song.bars);
}
//console.timeEnd('metronome update')
//this.allNotesOff();
//this.song.scheduler.updateSong();
// var events = createEvents(this, startBar, endBar, 'update');
// this.events = this.events.concat(events);
// parseMetronomeEvents(this.song, this.events);
};
Metronome.prototype.updateConfig = function () {
this.init('configure', 1, this.bars);
this.allNotesOff();
this.song.scheduler.updateSong();
};
Metronome.prototype.configure = function (config) {
var me = this;
objectForEach(config, function (value, key) {
me[methodMap[key]](value);
//console.log(key, me[methodMap[key]]);
});
this.updateConfig();
};
Metronome.prototype.setInstrument = function (instrument) {
if (instrument.className !== 'Instrument') {
instrument = sequencer.createInstrument(instrument);
}
if (instrument !== false) {
this.track.setInstrument(instrument);
} else {
this.track.setInstrument('heartbeat/metronome');
}
this.updateConfig();
};
Metronome.prototype.setNoteLengthAccentedTick = function (value) {
if (isNaN(value)) {
if (sequencer.debug >= 2) {
console.warn('please provide a number');
}
}
this.noteLengthAccented = value;
this.updateConfig();
};
Metronome.prototype.setNoteLengthNonAccentedTick = function (value) {
if (isNaN(value)) {
if (sequencer.debug >= 2) {
console.warn('please provide a number');
}
}
this.noteLengthNonAccented = value;
this.updateConfig();
};
Metronome.prototype.setVelocityAccentedTick = function (value) {
value = checkNumber(value);
if (value !== false) {
this.velocityAccented = value;
} else if (sequencer.debug >= 2) {
console.warn('please provide a number');
}
this.updateConfig();
};
Metronome.prototype.setVelocityNonAccentedTick = function (value) {
value = checkNumber(value);
if (value !== false) {
this.velocityNonAccented = value;
} else if (sequencer.debug >= 2) {
console.warn('please provide a number');
}
this.updateConfig();
};
Metronome.prototype.setNoteNumberAccentedTick = function (value) {
value = checkNumber(value);
if (value !== false) {
this.noteNumberAccented = value;
} else if (sequencer.debug >= 2) {
console.warn('please provide a number');
}
this.updateConfig();
};
Metronome.prototype.setNoteNumberNonAccentedTick = function (value) {
value = checkNumber(value);
if (value !== false) {
this.noteNumberNonAccented = value;
} else if (sequencer.debug >= 2) {
console.warn('please provide a number');
}
this.updateConfig();
};
Metronome.prototype.reset = function () {
this.volume = 1;
this.track.setInstrument('heartbeat/metronome');
this.noteNumberAccented = 61;
this.noteNumberNonAccented = 60;
this.velocityAccented = 100;
this.velocityNonAccented = 100;
this.noteLengthAccented = this.song.ppq / 4;
this.noteLengthNonAccented = this.song.ppq / 4;
};
Metronome.prototype.allNotesOff = function () {
if (this.track.instrument) {
this.track.instrument.allNotesOff();
}
};
Metronome.prototype.createPrecountEvents = function (precount) {
if (precount <= 0) {
return;
}
var endPos = this.song.getPosition('barsbeats', this.song.bar + precount);
this.index = 0;
this.millis = 0;
this.startMillis = this.song.millis;
this.precountDurationInMillis = endPos.millis - this.startMillis;
this.precountEvents = createEvents(this, this.song.bar, endPos.bar - 1, 'precount');
parseEvents(this.song, this.precountEvents);
//console.log(this.song.bar, endPos.bar, precount, this.precountEvents.length);
//console.log(this.precountEvents, this.precountDurationInMillis, startTicks, endTicks);
};
// called by scheduler.js
Metronome.prototype.getPrecountEvents = function (maxtime) {
var events = this.precountEvents,
maxi = events.length, i, event,
result = [];
//console.log(maxtime, maxi, this.index, this.millis);
for (i = this.index; i < maxi; i++) {
event = events[i];
//console.log(event.millis, maxtime, this.millis);
if (event.millis < maxtime) {
event.time = this.startTime + event.millis;
result.push(event);
this.index++;
} else {
break;
}
}
return result;
};
Metronome.prototype.setVolume = function (value) {
this.track.setVolume(value);
};
sequencer.protectedScope.createMetronome = function (song) {
return new Metronome(song);
};
sequencer.protectedScope.addInitMethod(function initMetronome() {
context = sequencer.protectedScope.context;
findItem = sequencer.protectedScope.findItem;
getPosition = sequencer.protectedScope.getPosition;
createMidiNote = sequencer.createMidiNote;
objectForEach = sequencer.util.objectForEach;
parseEvents = sequencer.protectedScope.parseEvents;
parseMetronomeEvents = sequencer.protectedScope.parseMetronomeEvents;
});
}