Source: midi/scheduler.js

  1. /**
  2. * Schedules functions to occur at a given time in the future, relative to the start time.
  3. * Can be used to play music in realtime.
  4. */
  5. class Scheduler {
  6. constructor({ bpm=120 } = {}) {
  7. this.schedule = new Map();
  8. this.bpm = bpm;
  9. }
  10. /**
  11. * Delete all scheduled callbacks
  12. */
  13. clear() {
  14. this.schedule.clear();
  15. }
  16. /**
  17. * Schedule a callback to run.
  18. * Note, you may call this repeatedly with the same time parameter. It will append to any existing callbacks.
  19. * @param time {Number} - Time in milliseconds, relative to when {@link Scheduler#start scheduler.start()} is called.
  20. * @param callback {Function} - The code to execute at the scheduled time.
  21. */
  22. at(time, callback) {
  23. if (typeof time !== 'number') throw new TypeError('time must be a number');
  24. if (typeof callback !== 'function') throw new TypeError('callback must be a function');
  25. const timeInBeats = time * 60000/this.bpm;
  26. let callbacks = this.schedule.get(timeInBeats);
  27. if (!callbacks) {
  28. callbacks = [];
  29. this.schedule.set(timeInBeats, callbacks);
  30. }
  31. callbacks.push(callback);
  32. }
  33. /**
  34. * Run the scheduler and execute the callbacks as scheduled.
  35. */
  36. start() {
  37. this.times = [...this.schedule.keys()].sort((a,b) => a-b);
  38. this.startTime = new Date().getTime();
  39. this.tick();
  40. }
  41. /**
  42. * Stop the scheduler.
  43. */
  44. stop() {
  45. if (this.timeout) clearTimeout(this.timeout);
  46. }
  47. /**
  48. * @private
  49. */
  50. tick() {
  51. if (!this.nextTime) {
  52. this.nextSchedule = this.times.shift();
  53. if (this.nextSchedule == null) return;
  54. this.nextTime = this.startTime + this.nextSchedule;
  55. }
  56. if (this.nextTime <= new Date().getTime()) {
  57. const callbacks = this.schedule.get(this.nextSchedule) || [];
  58. this.nextSchedule = null;
  59. this.nextTime = null;
  60. callbacks.forEach(callback => callback.call(this.nextSchedule));
  61. }
  62. this.timeout = setTimeout(() => this.tick(), 1);
  63. }
  64. }
  65. module.exports = Scheduler;