<commit_before>import * as mm from '@magenta/music';
import { observable } from 'mobx';
import { Note } from './note';
import editor from './editor';
import { range } from 'lodash';
import * as Tone from 'tone';

import {
  DEFAULT_BPM,
  MIN_PITCH,
  MAX_PITCH,
  NOTE_VELOCITY,
  SOUNDFONT_URL,
  MODEL_URL,
  TOTAL_SIXTEENTHS,
} from './constants';
import { Voice } from './note';

interface InfillMask {
  step: number;
  voice: number;
}

export class CallbackObject extends mm.BasePlayerCallback {
  constructor(private engine: Engine) {
    super();
  }

  run(note: mm.NoteSequence.INote, t: number) {
    const { pitch, quantizedStartStep } = note;
    editor.setNotePlaying(pitch, quantizedStartStep);
  }
  stop() {
    if (this.engine.shouldLoop) {
      this.engine.loop();
    } else {
      this.engine.stop();
    }
  }
}

function delay(ms = 1) {
  return new Promise(resolve => setTimeout(() => resolve(), ms));
}

class Engine {
  playerCallbackObject = new CallbackObject(this);
  @observable isPlayerLoaded = false;
  @observable isModelLoaded = false;
  @observable isPlaying = false;
  @observable isWorking = false;

  @observable shouldLoop = true;

  @observable loopStart = 0;
  @observable loopEnd = TOTAL_SIXTEENTHS;

  player = new mm.SoundFontPlayer(
    SOUNDFONT_URL,
    Tone.master,
    undefined,
    undefined,
    this.playerCallbackObject
  );
  model = new mm.Coconet(MODEL_URL);

  @observable bpm = DEFAULT_BPM;

  constructor() {
    this.loadPlayer();
    this.loadModel();
  }

  async loadPlayer() {
    const allNotes: mm.NoteSequence.INote[] = range(
      MIN_PITCH,
      MAX_PITCH + 1
    ).map(pitch => ({
      pitch,
      velocity: NOTE_VELOCITY,
    }));
    const allNotesSeq = { notes: allNotes };
    await this.player.loadSamples(allNotesSeq);
    this.isPlayerLoaded = true;
  }

  async loadModel() {
    await this.model.initialize();
    this.isModelLoaded = true;
  }

  togglePlay() {
    if (this.isPlaying) {
      this.stop();
    } else {
      this.start();
    }
  }

  playNote(note: Note) {
    if (this.isPlayerLoaded) {
      this.playNoteDown(note);
      const timePerSixteenth = (((1 / this.bpm) * 60) / 4) * 1000;
      const delay = note.duration * timePerSixteenth;
      setTimeout(() => this.playNoteUp(note), delay);
    }
  }

  playNoteDown(note: Note) {
    if (this.isPlayerLoaded) {
      editor.activeNoteValue = note.pitch;
      this.player.playNoteDown(note.magentaNote);
    }
  }

  playNoteUp(note: Note) {
    if (this.isPlayerLoaded) {
      editor.activeNoteValue = null;
      this.player.playNoteUp(note.magentaNote);
    }
  }

  getMagentaNoteSequence(notes: Note[], merge = false) {
    const noteSequence = {
      notes: notes.map(note => note.magentaNote),
      tempos: [{ time: 0, qpm: this.bpm }],
      totalQuantizedSteps: editor.totalSixteenths,
      quantizationInfo: { stepsPerQuarter: 4 },
    };
    return merge
      ? mm.sequences.mergeConsecutiveNotes(noteSequence)
      : mm.sequences.clone(noteSequence);
  }

  start() {
    if (this.isPlayerLoaded) {
      if (editor.allNotes.length === 0) {
        return;
      }

      const sequence = this.getMagentaNoteSequence(editor.allNotes);
      const loopSequence = mm.sequences.trim(
        sequence,
        this.loopStart,
        // FIXME(rlouie): using loopEnd still includes one too many notes for playback
        this.loopEnd,
        true
      );

      // this offsets the start step and end step, so that the notes correspond to the notes in the notesMap
      // which allows the notes to be highlighted red for playing (in editor.setNotePlaying)
      // FIXME(rlouie): still pauses quantizedStartStep seconds before the beginning of loopStart plays
      loopSequence.notes = loopSequence.notes.map(note => {
        note.quantizedStartStep = note.quantizedStartStep + this.loopStart;
        note.quantizedEndStep = note.quantizedEndStep + this.loopStart;
        return note;
      })

      // trim can give a note that has quantizedStartStep == quantizedEndStep
      // when on boarder of trim region.
      loopSequence.notes = loopSequence.notes.filter(note => {
        return note.quantizedStartStep != note.quantizedEndStep;
      });

      this.player.start(loopSequence);
      this.isPlaying = true;
    }
  }

  loop() {
    editor.clearPlayingNotes();
    this.start();
  }
<commit_msg>player now starts playing notes with no delay, but they remained visually highlighted<commit_after>import * as mm from '@magenta/music';
import { observable } from 'mobx';
import { Note } from './note';
import editor from './editor';
import { range } from 'lodash';
import * as Tone from 'tone';

import {
  DEFAULT_BPM,
  MIN_PITCH,
  MAX_PITCH,
  NOTE_VELOCITY,
  SOUNDFONT_URL,
  MODEL_URL,
  TOTAL_SIXTEENTHS,
} from './constants';
import { Voice } from './note';

interface InfillMask {
  step: number;
  voice: number;
}

export class CallbackObject extends mm.BasePlayerCallback {
  constructor(private engine: Engine) {
    super();
  }

  run(note: mm.NoteSequence.INote, t: number) {
    const { pitch, quantizedStartStep } = note;

    // this offsets the position by loopStart, so that the notes correspond to the notes in the notesMap
    // which allows the notes to be highlighted red for playing
    editor.setNotePlaying(pitch, quantizedStartStep + this.engine.loopStart);
  }
  stop() {
    if (this.engine.shouldLoop) {
      this.engine.loop();
    } else {
      this.engine.stop();
    }
  }
}

function delay(ms = 1) {
  return new Promise(resolve => setTimeout(() => resolve(), ms));
}

class Engine {
  playerCallbackObject = new CallbackObject(this);
  @observable isPlayerLoaded = false;
  @observable isModelLoaded = false;
  @observable isPlaying = false;
  @observable isWorking = false;

  @observable shouldLoop = true;

  @observable loopStart = 0;
  @observable loopEnd = TOTAL_SIXTEENTHS;

  player = new mm.SoundFontPlayer(
    SOUNDFONT_URL,
    Tone.master,
    undefined,
    undefined,
    this.playerCallbackObject
  );
  model = new mm.Coconet(MODEL_URL);

  @observable bpm = DEFAULT_BPM;

  constructor() {
    this.loadPlayer();
    this.loadModel();
  }

  async loadPlayer() {
    const allNotes: mm.NoteSequence.INote[] = range(
      MIN_PITCH,
      MAX_PITCH + 1
    ).map(pitch => ({
      pitch,
      velocity: NOTE_VELOCITY,
    }));
    const allNotesSeq = { notes: allNotes };
    await this.player.loadSamples(allNotesSeq);
    this.isPlayerLoaded = true;
  }

  async loadModel() {
    await this.model.initialize();
    this.isModelLoaded = true;
  }

  togglePlay() {
    if (this.isPlaying) {
      this.stop();
    } else {
      this.start();
    }
  }

  playNote(note: Note) {
    if (this.isPlayerLoaded) {
      this.playNoteDown(note);
      const timePerSixteenth = (((1 / this.bpm) * 60) / 4) * 1000;
      const delay = note.duration * timePerSixteenth;
      setTimeout(() => this.playNoteUp(note), delay);
    }
  }

  playNoteDown(note: Note) {
    if (this.isPlayerLoaded) {
      editor.activeNoteValue = note.pitch;
      this.player.playNoteDown(note.magentaNote);
    }
  }

  playNoteUp(note: Note) {
    if (this.isPlayerLoaded) {
      editor.activeNoteValue = null;
      this.player.playNoteUp(note.magentaNote);
    }
  }

  getMagentaNoteSequence(notes: Note[], merge = false) {
    const noteSequence = {
      notes: notes.map(note => note.magentaNote),
      tempos: [{ time: 0, qpm: this.bpm }],
      totalQuantizedSteps: editor.totalSixteenths,
      quantizationInfo: { stepsPerQuarter: 4 },
    };
    return merge
      ? mm.sequences.mergeConsecutiveNotes(noteSequence)
      : mm.sequences.clone(noteSequence);
  }

  start() {
    if (this.isPlayerLoaded) {
      if (editor.allNotes.length === 0) {
        return;
      }

      const sequence = this.getMagentaNoteSequence(editor.allNotes);
      const loopSequence = mm.sequences.trim(
        sequence,
        this.loopStart,
        // FIXME(rlouie): using loopEnd still includes one too many notes for playback
        this.loopEnd,
        true
      );

      // trim can give a note that has quantizedStartStep == quantizedEndStep
      // when on boarder of trim region.
      loopSequence.notes = loopSequence.notes.filter(note => {
        return note.quantizedStartStep != note.quantizedEndStep;
      });

      this.player.start(loopSequence);
      this.isPlaying = true;
    }
  }

  loop() {
    editor.clearPlayingNotes();
    this.start();
  }

{'fraction_non_alphanumeric': 0.11717005523563286, 'fraction_numerical': 0.0032906334469385355, 'mean_word_length': 2.873463814292217, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5130427', 'n_tokens_mistral': 2805, 'n_tokens_neox': 2691, 'n_words': 624}