Exemple #1
0
  public void go() {
    setUpGui();

    try {

      Sequencer sequencer = MidiSystem.getSequencer();
      sequencer.open();
      // make a sequencer and open
      sequencer.addControllerEventListener(m1, new int[] {127});
      Sequence seq = new Sequence(Sequence.PPQ, 4);
      Track track = seq.createTrack();

      int r = 0;
      for (int i = 0; i < 300; i += 4) {

        r = (int) ((Math.random() * 50) + 1);
        track.add(makeEvent(144, 1, r, 100, i));
        track.add(makeEvent(176, 1, 127, 0, i));
        track.add(makeEvent(128, 1, r, 100, i + 2));
      } // end loop

      sequencer.setSequence(seq);
      sequencer.start();
      sequencer.setTempoInBPM(120);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  } // close method
  public void buildTrackAndStart() {
    int[] trackList = null;

    sequence.deleteTrack(track);
    track = sequence.createTrack();

    for (int i = 0; i < 16; i++) {
      trackList = new int[16];

      int key = instruments[i];

      for (int j = 0; j < 16; j++) {
        JCheckBox jc = (JCheckBox) checkboxList.get(j + (16 * i));
        if (jc.isSelected()) {
          trackList[j] = key;
        } else {
          trackList[j] = 0;
        }
      } // close inner loop

      makeTracks(trackList);
      track.add(makeEvent(176, 1, 127, 0, 16));
    } // close outer

    track.add(makeEvent(192, 9, 1, 0, 15));
    try {
      sequencer.setSequence(sequence);
      sequencer.setLoopCount(sequencer.LOOP_CONTINUOUSLY);
      sequencer.start();
      sequencer.setTempoInBPM(120);
    } catch (Exception e) {
      e.printStackTrace();
    }
  } // close buildTrackAndStart method
Exemple #3
0
  public Sequencer(int instrument, int tempo) {
    // Set up initial settings for the sequencer
    this.instrument = instrument;
    this.tempo = tempo;
    Synthesizer synth;
    ticks = 0;
    velocity = 64; // Mid volume

    try {
      // Setup values to create sequencer
      sequence = new Sequence(Sequence.PPQ, 16);
      sequencer = MidiSystem.getSequencer();
      sequencer.open();
      synth = MidiSystem.getSynthesizer();
      synth.open();
      sequencer.getTransmitter().setReceiver(synth.getReceiver());
      sequencer.setTempoInBPM(tempo);
      track = sequence.createTrack();

    } catch (InvalidMidiDataException e) {
      e.printStackTrace();
    } catch (MidiUnavailableException e) {
      e.printStackTrace();
    }
  }
  public static Sequence process(
      final Sequence inputSequence,
      int track,
      long startTick,
      long endTick,
      int noteOverlap,
      int minimumRest,
      Handler loggingHandler)
      throws InvalidMidiDataException {
    Sequence outputSequence =
        new Sequence(inputSequence.getDivisionType(), inputSequence.getResolution());
    Track[] inputTracks = inputSequence.getTracks();
    SlurBinderA.noteOverlap = noteOverlap;
    SlurBinderA.minimumRest = minimumRest;
    slurEndNotes = new HashSet<>();

    for (int trackI = 0; trackI < inputTracks.length; trackI++) {
      Track inputTrack = inputTracks[trackI];
      Track outputTrack = outputSequence.createTrack();

      if (trackI == track) {
        handleTrack(outputTrack, inputTrack, startTick, endTick);
      } else {
        for (int eventI = 0; eventI < inputTrack.size(); eventI++) {
          MidiEvent midEvent = inputTrack.get(eventI);
          outputTrack.add(midEvent);
        }
      }
    }

    return outputSequence;
  }
  public void play(int instrument, int note) {
    try {

      Sequencer player = MidiSystem.getSequencer();
      player.open();
      Sequence seq = new Sequence(Sequence.PPQ, 4);
      Track track = seq.createTrack();

      MidiEvent event = null;
      ShortMessage first = new ShortMessage();
      first.setMessage(192, 1, instrument, 0);
      MidiEvent changeInstrument = new MidiEvent(first, 1);
      track.add(changeInstrument);
      ShortMessage a = new ShortMessage();
      a.setMessage(144, 1, note, 100);
      MidiEvent noteOn = new MidiEvent(a, 1);
      track.add(noteOn);
      ShortMessage b = new ShortMessage();
      b.setMessage(128, 1, note, 100);
      MidiEvent noteOff = new MidiEvent(b, 16);
      track.add(noteOff);
      player.setSequence(seq);
      player.start();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  } // close play
  public void los() {
    guiErstellen();

    try {
      Sequencer sequencer = MidiSystem.getSequencer();
      sequencer.open();
      sequencer.addControllerEventListener(ml, new int[] {127});
      Sequence seq = new Sequence(Sequence.PPQ, 4);
      Track track = seq.createTrack();

      int r = 0;

      for (int i = 0; i < 60; i += 4) {

        r = (int) ((Math.random() * 50) + 1);

        track.add(eventErzeugen(144, 1, r, 100, i));

        track.add(eventErzeugen(176, 1, 127, 0, i));

        track.add(eventErzeugen(128, 1, r, 100, i + 2));
      }

      sequencer.setSequence(seq);
      sequencer.setTempoInBPM(120);
      sequencer.start();
      Thread.sleep(5000);
      sequencer.close();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
 /**
  * @param key is the note that this starts with. 60 is middle C.
  * @param tempo is measured in beats per second
  */
 public MidiFileGenerator(int key, int tempo, int resolution)
     throws MidiUnavailableException, InvalidMidiDataException {
   this.resolution = resolution;
   Sequence sequence = new Sequence(Sequence.PPQ, resolution);
   track = sequence.createTrack();
   // makeSong(key);
   sequencer = MidiSystem.getSequencer();
   sequencer.open();
   sequencer.setSequence(sequence);
   sequencer.setTempoInBPM(tempo);
 }
  public void setUpMidi() {
    try {
      sequencer = MidiSystem.getSequencer();
      sequencer.open();
      sequence = new Sequence(Sequence.PPQ, 4);
      track = sequence.createTrack();
      sequencer.setTempoInBPM(120);

    } catch (Exception e) {
      e.printStackTrace();
    }
  } // close method
 public Sequence getSequence(InputStream inputStream)
     throws InvalidMidiDataException, IOException {
   MidiFileFormat midiFileFormat = getMidiFileFormat(inputStream);
   Sequence sequence =
       new Sequence(midiFileFormat.getDivisionType(), midiFileFormat.getResolution());
   DataInputStream dataInputStream = new DataInputStream(inputStream);
   int nNumTracks = ((TMidiFileFormat) midiFileFormat).getTrackCount();
   for (int nTrack = 0; nTrack < nNumTracks; nTrack++) {
     Track track = sequence.createTrack();
     readTrack(dataInputStream, track);
   }
   return sequence;
 }
Exemple #10
0
 public void go() {
   try {
     sq.open();
     Sequence seq = new Sequence(Sequence.PPQ, 4);
     Track track = seq.createTrack();
     track.add(addNote(144, 9, 56, 100, 1));
     track.add(addNote(128, 9, 56, 100, 4));
     sq.setSequence(seq);
     sq.setTempoInBPM(tempo);
     sq.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
     sq.start();
   } catch (Exception ex) {
     ex.printStackTrace();
   }
 }
Exemple #11
0
 public boolean createSequence() {
   try {
     // Create a sequence with microsecond timing resolution
     // (Although this doesn't seem to have any effect on tick resolution - ticks in MidiEvents
     // always microsecond
     fSequence = new Sequence(Sequence.SMPTE_25, 40000);
     // Create track and add program change to bell sound
     Track track = fSequence.createTrack();
     // track.add(createBankChange(1, 0));
     track.add(createProgramChange(14, 0));
   } catch (InvalidMidiDataException e) {
     System.out.println("Error creating sequence: " + e);
     return false;
   }
   return true;
 }
    public void actionPerformed(ActionEvent ev) {

      try {

        // make (and open) a sequencer, make a sequence and track

        Sequencer sequencer = MidiSystem.getSequencer();
        sequencer.open();

        sequencer.addControllerEventListener(myPanel, new int[] {127});
        Sequence seq = new Sequence(Sequence.PPQ, 4);
        Track track = seq.createTrack();

        // now make two midi events (containing a midi message)

        for (int i = 0; i < 100; i += 4) {

          int rNum = (int) ((Math.random() * 50) + 1);
          if (rNum < 38) { // so now only do it if num <38 (75% of the time)

            track.add(makeEvent(144, 1, rNum, 100, i));

            track.add(makeEvent(176, 1, 127, 0, i));

            track.add(makeEvent(128, 1, rNum, 100, i + 2));
          }
        } // end loop

        // add the events to the track
        // add the sequence to the sequencer, set timing, and start

        sequencer.setSequence(seq);

        sequencer.start();
        sequencer.setTempoInBPM(220);
      } catch (Exception ex) {
        ex.printStackTrace();
      }
    } // close actionperformed
Exemple #13
0
  /**
   * Import a song into the MidiExporter to play or export.
   *
   * @param song The Song to convert to MIDI
   */
  public void importSong(Song song) {
    // Fetch important data from the song.
    // Meaning, metadata and the song itself.
    Beat[] beats = song.getBeatArray();
    this.song = song;

    // Create a track for each voice.
    int numVoices = song.getNumVoices();
    for (int voiceCount = 0; voiceCount < numVoices; voiceCount++) {
      sequence.createTrack();
    }

    // Iterate through each beat, adding each note to the corresponding
    // track.
    Track[] tracks = sequence.getTracks();
    for (int beat = 0; beat < beats.length; beat++) {
      Note[] firstHalf = beats[beat].getNotesFirstHalf();
      Note[] secondHalf = beats[beat].getNotesSecondHalf();
      // Iterate through each note in the beat, adding it to the
      // corresponding track.
      for (int note = 0; note < firstHalf.length; note++) {
        if (firstHalf[note] == secondHalf[note]) {
          createNote(firstHalf[note], 2 * beat, 2, tracks[note]);
        } else {
          createNote(firstHalf[note], 2 * beat, 1, tracks[note]);
          createNote(secondHalf[note], 2 * beat + 1, 1, tracks[note]);
        } // if/else
      } // for
    } // for

    try {
      setUpSequencer();
    } catch (MidiUnavailableException ex) {
      System.out.println("Unable to set up sequencer");
      // do nothing
    }
  }
Exemple #14
0
  public boolean writeSequence(NoteList noteList) {

    this.noteList = noteList;

    toneMap = toneMapFrame.getToneMap();
    timeSet = toneMap.getTimeSet();
    pitchSet = toneMap.getPitchSet();
    timeRange = timeSet.getRange();
    pitchRange = pitchSet.getRange();

    if (!buildNoteSequence()) return false;

    try {
      sequence = new Sequence(Sequence.PPQ, 10);
    } catch (Exception ex) {
      ex.printStackTrace();
      return false;
    }

    track = sequence.createTrack();
    startTime = System.currentTimeMillis();

    // add a program change right at the beginning of
    // the track for the current instrument

    createEvent(PROGRAM, cc.program + 1, 1);

    for (int i = 0; i < noteSequence.size(); i++) {
      noteSequenceElement = noteSequence.get(i);
      if (noteSequenceElement.state == ON)
        if (!createEvent(NOTEON, noteSequenceElement.note, noteSequenceElement.tick)) return false;
      if (noteSequenceElement.state == OFF)
        if (!createEvent(NOTEOFF, noteSequenceElement.note, noteSequenceElement.tick)) return false;
    }
    return true;
  }
  public void los() {
    guiErstellen();

    try {

      // einen Sequencer erzeugen (und öffnen),
      // eine Sequence und einen Track erzeugen

      Sequencer sequencer = MidiSystem.getSequencer();
      sequencer.open();
      sequencer.addControllerEventListener(ml, new int[] {127});
      Sequence seq = new Sequence(Sequence.PPQ, 4);
      Track track = seq.createTrack();

      // jetzt werden MidiEvents (die eine
      // MidiMessage enthalten) erzeugt

      int r = 0;
      for (int i = 0; i < 60; i += 4) {
        r = (int) ((Math.random() * 50) + 1);
        track.add(eventErzeugen(144, 1, r, 100, i));
        track.add(eventErzeugen(176, 1, 127, 0, i));
        track.add(eventErzeugen(128, 1, r, 100, i + 2));
      } // Ende der for-Schleife

      // Hinzufügen der Events zum Track und der Sequence
      // zum Sequencer, Setzen der Zeiten und Starten

      sequencer.setSequence(seq);
      sequencer.start();
      sequencer.setTempoInBPM(120);

    } catch (Exception ex) {
      ex.printStackTrace();
    }
  } // Methode los schließen
  /**
   * Converts the given tune to a midi sequence.
   *
   * @param tune The tune to be converted.
   * @return The midi sequence of the tune.
   */
  public Sequence toMidiSequence(Tune tune) {
    Sequence sequence = null;
    try {
      if (instrument == null) {
        Synthesizer synth = MidiSystem.getSynthesizer();
        synth.open();
        try {
          setInstrument(synth.getAvailableInstruments()[0]);
        } finally {
          synth.close();
        }
      }
      // Sequence in ticks per quarter note : PPQ = Pulse Per Quarter Note
      // Resolution is expressed in ticks per beat.
      // Last parameter "1" is the number of tracks.
      sequence = new Sequence(Sequence.PPQ, SEQUENCE_RESOLUTION, 1);
      // Set the instrument on channel 0
      ShortMessage sm = new ShortMessage();
      sm.setMessage(ShortMessage.PROGRAM_CHANGE, 0, instrument.getPatch().getProgram(), 0);

      Track track = sequence.createTrack();
      track.add(new MidiEvent(sm, 0));
      // long trackLengthInTicks = track.ticks();
      int lastRepeatOpen = -1;
      int repeatNumber = 1;
      boolean inWrongEnding = false;
      KeySignature tuneKey = null;
      KeySignature currentKey = null;
      Hashtable partsKey = new Hashtable();

      long elapsedTime = 0;
      NoteAbstract[] graceNotes = null;
      Music staff = tune.getMusicForAudioRendition();
      Iterator it = staff.getVoices().iterator();
      while (it.hasNext()) {
        Voice voice = (Voice) it.next();
        int i = 0; // StaffItem iterator
        while (i < voice.size()) {
          if (!inWrongEnding) {
            // ==================================================================== TEMPO
            if (voice.elementAt(i) instanceof abc.notation.Tempo) {
              addTempoEventsFor(
                  track,
                  elapsedTime,
                  getMidiMessagesFor((Tempo) voice.elementAt(i))); // , trackLengthInTicks));
            } else
            /*if (voice.elementAt(i) instanceof abc.notation.PartLabel) {
            	//Imagine... part A in Gmaj, B in Amin
            	//in tune you have K:G, P:A, ... P:B, K:Am
            	//if you have part order ABA, when you return to A
            	//you stay in Amin. This stores the tuneKey when a
            	//new part appear, and restitute it when part is played again
            	abc.notation.PartLabel pl = (abc.notation.PartLabel) voice.elementAt(i);
            	if (partsKey.get(pl.getLabel()+"") == null) {
            		partsKey.put(pl.getLabel()+"", tuneKey);
            	} else {
            		tuneKey = (KeySignature) partsKey.get(pl.getLabel()+"");
            	}
            }
            else*/
            // ==================================================================== KEY SIGNATURE
            if (voice.elementAt(i) instanceof abc.notation.KeySignature) {
              tuneKey = (KeySignature) (voice.elementAt(i));
              currentKey = new KeySignature(tuneKey.getAccidentals());
            } else
            // ==================================================================== NOTE
            // Notes ending ties should be ignored. Already taken into
            // account in getNoteLengthInTicks(Note)
            if (voice.elementAt(i) instanceof abc.notation.Note
                && !((abc.notation.Note) voice.elementAt(i)).isEndingTie()) {

              Note note = (Note) voice.elementAt(i);
              long noteDuration;
              boolean fermata = false;
              Vector decorationNotes = new Vector();
              if (note.hasGeneralGracing() || note.hasDecorations()) {
                Decoration[] d = note.getDecorations();
                for (int j = 0; j < d.length; j++) {
                  switch (d[j].getType()) {
                    case Decoration.FERMATA:
                    case Decoration.FERMATA_INVERTED:
                      fermata = true;
                      break;
                    case Decoration.LOWERMORDENT:
                    case Decoration.UPPERMORDENT:
                    case Decoration.DOUBLE_LOWER_MORDANT:
                    case Decoration.DOUBLE_UPPER_MORDANT:
                    case Decoration.TRILL:
                    case Decoration.TURN: // GRUPETTO_UP
                    case Decoration.TURN_INVERTED: // GRUPETTO_DOWN
                    case Decoration.TURNX:
                    case Decoration.TURNX_INVERTED:
                      Note n = new Note(note.getHeight());
                      n.setAccidental(note.getAccidental(currentKey));
                      Note o =
                          new Interval(Interval.SECOND, Interval.MAJOR, Interval.UPWARD)
                              .calculateSecondNote(n);
                      Note m =
                          new Interval(Interval.SECOND, Interval.MAJOR, Interval.DOWNWARD)
                              .calculateSecondNote(n);
                      // TODO ornament templates: regular, musette, balkan...
                      // n.setStrictDuration(Note.SIXTEENTH);
                      // o.setDuration((short)(Note.EIGHTH+Note.SIXTEENTH));
                      o.setAccidental(Accidental.NONE);
                      m.setAccidental(Accidental.NONE);
                      n.setStrictDuration(Note.THIRTY_SECOND);
                      m.setStrictDuration(Note.THIRTY_SECOND);
                      o.setStrictDuration(Note.THIRTY_SECOND);
                      switch (d[j].getType()) {
                        case Decoration.DOUBLE_LOWER_MORDANT:
                          decorationNotes.add(n);
                          decorationNotes.add(m);
                        case Decoration.LOWERMORDENT:
                          decorationNotes.add(n);
                          decorationNotes.add(m);
                          break;
                        case Decoration.DOUBLE_UPPER_MORDANT:
                        case Decoration.TRILL:
                          decorationNotes.add(n);
                          decorationNotes.add(o);
                        case Decoration.UPPERMORDENT:
                          decorationNotes.add(n);
                          decorationNotes.add(o);
                          break;
                        case Decoration.TURNX_INVERTED:
                        case Decoration.TURN:
                          decorationNotes.add(o);
                          decorationNotes.add(n);
                          decorationNotes.add(m);
                          break;
                        case Decoration.TURNX:
                        case Decoration.TURN_INVERTED:
                          decorationNotes.add(m);
                          decorationNotes.add(n);
                          decorationNotes.add(o);
                      }
                      break;
                  }
                }
                // currently not used
                // future use: playing rolls, slides, etc.
              }
              long graceNotesDuration = 0;
              if (note.hasGracingNotes() || (decorationNotes.size() > 0)) {
                graceNotes = note.getGracingNotes();
                // gracing are eighth note for graphical rendition
                // and because that's it in the parser
                // adapt duration to note length
                int divisor = 1;
                if (note.getStrictDuration() >= Note.HALF) divisor = 1; // grace is an eighth
                else if (note.getStrictDuration() >= Note.QUARTER) divisor = 2; // 16th
                else if (note.getStrictDuration() >= Note.EIGHTH) divisor = 4; // 32nd
                else divisor = 8; // 64th
                if (note.hasGracingNotes()) {
                  for (int j = 0; j < graceNotes.length; j++) {
                    noteDuration = getNoteLengthInTicks(graceNotes[j], staff) / divisor;
                    graceNotesDuration += noteDuration;
                    if (graceNotes[j] instanceof Note)
                      playNote(
                          (Note) graceNotes[j], i, currentKey, elapsedTime, noteDuration, track);
                    else
                      playMultiNote(
                          (MultiNote) graceNotes[j],
                          i,
                          currentKey, /*elapsedTime,*/
                          noteDuration,
                          track,
                          staff);
                    elapsedTime += noteDuration;
                  }
                }
                for (int j = 0; j < decorationNotes.size(); j++) {
                  noteDuration = getNoteLengthInTicks((Note) decorationNotes.elementAt(j), staff);
                  graceNotesDuration += noteDuration;
                  playNote(
                      (Note) decorationNotes.elementAt(j),
                      i,
                      currentKey,
                      elapsedTime,
                      noteDuration,
                      track);
                  elapsedTime += noteDuration;
                }
              }
              // The note duration if the note isn't part of a tuplet.
              noteDuration = getNoteLengthInTicks(note, staff) - graceNotesDuration;
              if (noteDuration <= 0) // in case of too much grace notes
              noteDuration = getNoteLengthInTicks(note, staff);
              if (fermata) noteDuration *= 2;
              playNote(note, i, currentKey, elapsedTime, noteDuration, track);
              elapsedTime += noteDuration;
            } else
            // ==================================================================== MULTI NOTE
            if ((voice.elementAt(i) instanceof abc.notation.MultiNote)) {
              MultiNote multiNote = (MultiNote) voice.elementAt(i);
              playMultiNote(multiNote, i, currentKey, elapsedTime, track, staff);
              elapsedTime += getNoteLengthInTicks(multiNote, staff);
            }
          } // endif (!inWrongEnding)
          // ====================================================================== REPEAT BAR LINE
          if (voice.elementAt(i) instanceof abc.notation.RepeatBarLine) {
            RepeatBarLine bar = (RepeatBarLine) voice.elementAt(i);
            if (repeatNumber < bar.getRepeatNumbers()[0] && lastRepeatOpen != -1) {
              repeatNumber++;
              i = lastRepeatOpen;
            } else if (repeatNumber > bar.getRepeatNumbers()[0]) inWrongEnding = true;
            else inWrongEnding = false;
          } else
          // ====================================================================== BAR LINE OPEN /
          // CLOSE
          if (voice.elementAt(i) instanceof abc.notation.BarLine) {
            // currentKey = new KeySignature(tuneKey.getAccidentals());
            switch (((BarLine) (voice.elementAt(i))).getType()) {
              case BarLine.SIMPLE:
                break;
              case BarLine.REPEAT_OPEN:
                lastRepeatOpen = i;
                repeatNumber = 1;
                break;
              case BarLine.REPEAT_CLOSE:
                if (repeatNumber < 2 && lastRepeatOpen != -1) {
                  repeatNumber++;
                  i = lastRepeatOpen;
                } else {
                  repeatNumber = 1;
                  lastRepeatOpen = -1;
                }
                break;
                // TODO case BarLine.BEGIN_AND_END_REPEAT
            }
          }
          // Whatever kind of bar line it is
          if (voice.elementAt(i) instanceof abc.notation.BarLine) {
            currentKey = new KeySignature(tuneKey.getAccidentals());
          }
          i++;
        } // end while each element in voice
      } // end while each voice in music
    } catch (InvalidMidiDataException e) {
      e.printStackTrace();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return sequence;
  }
Exemple #17
0
  private static Sequence convert(
      List<FileAndData> filesData,
      boolean useLotroInstruments,
      Map<Integer, LotroInstrument> instrumentOverrideMap,
      AbcInfo abcInfo,
      final boolean enableLotroErrors,
      final boolean stereo)
      throws ParseException {
    if (abcInfo == null) abcInfo = new AbcInfo();
    else abcInfo.reset();

    TuneInfo info = new TuneInfo();
    Sequence seq = null;
    Track track = null;

    int channel = 0;
    int trackNumber = 0;
    int noteDivisorChangeLine = 0;

    long chordStartTick = 0;
    long chordEndTick = 0;
    long PPQN = 0;
    Map<Integer, Integer> tiedNotes =
        new HashMap<Integer, Integer>(); // noteId => (line << 16) | column
    Map<Integer, Integer> accidentals = new HashMap<Integer, Integer>(); // noteId => deltaNoteId

    List<MidiEvent> noteOffEvents = new ArrayList<MidiEvent>();
    for (FileAndData fileAndData : filesData) {
      String fileName = fileAndData.file.getName();
      int lineNumber = 0;
      int partStartLine = 0;
      for (String line : fileAndData.lines) {
        lineNumber++;

        // Handle extended info
        Matcher xInfoMatcher = XINFO_PATTERN.matcher(line);
        if (xInfoMatcher.matches()) {
          AbcField field =
              AbcField.fromString(
                  xInfoMatcher.group(XINFO_FIELD) + xInfoMatcher.group(XINFO_COLON));

          if (field == AbcField.TEMPO) {
            try {
              info.addTempoEvent(chordStartTick, xInfoMatcher.group(XINFO_VALUE).trim());
            } catch (IllegalArgumentException e) {
              // Apparently that wasn't actually a tempo change
            }
          } else if (field != null) {
            String value = xInfoMatcher.group(XINFO_VALUE).trim();

            abcInfo.setExtendedMetadata(field, value);

            if (field == AbcField.PART_NAME) {
              info.setTitle(value, true);
              abcInfo.setPartName(trackNumber, value, true);
            }
          }

          continue;
        }

        int comment = line.indexOf('%');
        if (comment >= 0) line = line.substring(0, comment);
        if (line.trim().length() == 0) continue;

        int chordSize = 0;

        Matcher infoMatcher = INFO_PATTERN.matcher(line);
        if (infoMatcher.matches()) {
          char type = Character.toUpperCase(infoMatcher.group(INFO_TYPE).charAt(0));
          String value = infoMatcher.group(INFO_VALUE).trim();

          abcInfo.setMetadata(type, value);

          try {
            switch (type) {
              case 'X':
                for (int lineAndColumn : tiedNotes.values()) {
                  throw new ParseException(
                      "Tied note does not connect to another note",
                      fileName,
                      lineAndColumn >>> 16,
                      lineAndColumn & 0xFFFF);
                }

                accidentals.clear();
                noteOffEvents.clear();

                info.newPart(Integer.parseInt(value));
                trackNumber++;
                partStartLine = lineNumber;
                chordStartTick = 0;
                abcInfo.setPartNumber(trackNumber, info.getPartNumber());
                track = null; // Will create a new track after the header is done
                if (instrumentOverrideMap != null
                    && instrumentOverrideMap.containsKey(trackNumber)) {
                  info.setInstrument(instrumentOverrideMap.get(trackNumber));
                }
                break;
              case 'T':
                if (track != null)
                  throw new ParseException(
                      "Can't specify the title in the middle of a part", fileName, lineNumber, 0);

                info.setTitle(value, false);
                abcInfo.setPartName(trackNumber, value, false);
                if (instrumentOverrideMap == null
                    || !instrumentOverrideMap.containsKey(trackNumber)) {
                  info.setInstrument(TuneInfo.findInstrumentName(value, info.getInstrument()));
                }
                break;
              case 'K':
                info.setKey(value);
                break;
              case 'L':
                info.setNoteDivisor(value);
                noteDivisorChangeLine = lineNumber;
                break;
              case 'M':
                info.setMeter(value);
                noteDivisorChangeLine = lineNumber;
                break;
              case 'Q':
                {
                  int tempo = info.getPrimaryTempoBPM();
                  info.setPrimaryTempoBPM(value);
                  if (seq != null && (info.getPrimaryTempoBPM() != tempo)) {
                    throw new ParseException(
                        "The tempo must be the same for all parts of the song",
                        fileName,
                        lineNumber);
                  }
                  break;
                }
            }
          } catch (IllegalArgumentException e) {
            throw new ParseException(
                e.getMessage(), fileName, lineNumber, infoMatcher.start(INFO_VALUE));
          }
        } else {
          // The line contains notes

          if (trackNumber == 0) {
            // This ABC file doesn't have an "X:" line before notes. Tsk tsk.
            trackNumber = 1;
            if (instrumentOverrideMap != null && instrumentOverrideMap.containsKey(trackNumber)) {
              info.setInstrument(instrumentOverrideMap.get(trackNumber));
            }
          }

          if (seq == null) {
            try {
              PPQN = info.getPpqn();
              seq = new Sequence(Sequence.PPQ, (int) PPQN);

              abcInfo.setPrimaryTempoBPM(info.getPrimaryTempoBPM());

              // Create track 0, which will later be filled with the
              // tempo events and song metadata (title, etc.)
              seq.createTrack();

              abcInfo.setPartNumber(0, 0);
              abcInfo.setPartName(0, info.getTitle(), false);

              track = null;
            } catch (InvalidMidiDataException mde) {
              throw new ParseException("Midi Error: " + mde.getMessage(), fileName);
            }
          }

          if (track == null) {
            channel = getTrackChannel(seq.getTracks().length);
            if (channel > MidiConstants.CHANNEL_COUNT - 1)
              throw new ParseException(
                  "Too many parts (max = " + (MidiConstants.CHANNEL_COUNT - 1) + ")",
                  fileName,
                  partStartLine);
            track = seq.createTrack();
            track.add(
                MidiFactory.createProgramChangeEvent(
                    info.getInstrument().midiProgramId, channel, 0));
            if (useLotroInstruments)
              track.add(MidiFactory.createChannelVolumeEvent(MidiConstants.MAX_VOLUME, channel, 1));
            abcInfo.setPartInstrument(trackNumber, info.getInstrument());
          }

          Matcher m = NOTE_PATTERN.matcher(line);
          int i = 0;
          boolean inChord = false;
          Tuplet tuplet = null;
          int brokenRhythmNumerator = 1; // The numerator of the note after the broken rhythm sign
          int brokenRhythmDenominator =
              1; // The denominator of the note after the broken rhythm sign
          while (true) {
            boolean found = m.find(i);
            int parseEnd = found ? m.start() : line.length();
            // Parse anything that's not a note
            for (; i < parseEnd; i++) {
              char ch = line.charAt(i);
              if (Character.isWhitespace(ch)) {
                if (inChord)
                  throw new ParseException(
                      "Unexpected whitespace inside a chord", fileName, lineNumber, i);
                continue;
              }

              switch (ch) {
                case '[': // Chord start
                  if (inChord) {
                    throw new ParseException(
                        "Unexpected '" + ch + "' inside a chord", fileName, lineNumber, i);
                  }

                  if (brokenRhythmDenominator != 1 || brokenRhythmNumerator != 1) {
                    throw new ParseException(
                        "Can't have broken rhythm (< or >) within a chord",
                        fileName,
                        lineNumber,
                        i);
                  }

                  chordSize = 0;
                  inChord = true;
                  break;

                case ']': // Chord end
                  if (!inChord) {
                    throw new ParseException("Unexpected '" + ch + "'", fileName, lineNumber, i);
                  }
                  inChord = false;
                  chordStartTick = chordEndTick;
                  break;

                case '|': // Bar line
                  if (inChord) {
                    throw new ParseException(
                        "Unexpected '" + ch + "' inside a chord", fileName, lineNumber, i);
                  }

                  if (trackNumber == 1) abcInfo.addBar(chordStartTick);

                  accidentals.clear();
                  if (i + 1 < line.length() && line.charAt(i + 1) == ']') {
                    i++; // Skip |]
                  } else if (trackNumber == 1) {
                    abcInfo.addBar(chordStartTick);
                  }
                  break;

                case '+':
                  {
                    int j = line.indexOf('+', i + 1);
                    if (j < 0) {
                      throw new ParseException("There is no matching '+'", fileName, lineNumber, i);
                    }
                    try {
                      info.setDynamics(line.substring(i + 1, j));
                    } catch (IllegalArgumentException iae) {
                      throw new ParseException("Unsupported +decoration+", fileName, lineNumber, i);
                    }

                    if (enableLotroErrors && inChord) {
                      throw new LotroParseException(
                          "Can't include a +decoration+ inside a chord", fileName, lineNumber, i);
                    }

                    i = j;
                    break;
                  }

                case '(':
                  // Tuplet or slur start
                  if (i + 1 < line.length() && Character.isDigit(line.charAt(i + 1))) {
                    // If it has a digit following it, it's a tuplet
                    if (tuplet != null)
                      throw new ParseException(
                          "Unexpected '" + ch + "' before end of tuplet", fileName, lineNumber, i);

                    try {
                      for (int j = i + 1; j < line.length(); j++) {
                        if (line.charAt(i) != ':' && !Character.isDigit(line.charAt(i))) {
                          tuplet = new Tuplet(line.substring(i + 1, j + 1), info.isCompoundMeter());
                          i = j;
                          break;
                        }
                      }
                    } catch (IllegalArgumentException e) {
                      throw new ParseException("Invalid tuplet", fileName, lineNumber, i);
                    }
                  } else {
                    // Otherwise it's a slur, which LotRO conveniently ignores
                    if (inChord) {
                      throw new ParseException(
                          "Unexpected '" + ch + "' inside a chord", fileName, lineNumber, i);
                    }
                  }
                  break;

                case ')':
                  // End of a slur, ignore
                  if (inChord) {
                    throw new ParseException(
                        "Unexpected '" + ch + "' inside a chord", fileName, lineNumber, i);
                  }
                  break;

                case '\\':
                  // Ignore backslashes
                  break;

                default:
                  throw new ParseException(
                      "Unknown/unexpected character '" + ch + "'", fileName, lineNumber, i);
              }
            }

            if (i >= line.length()) break;

            // The matcher might find +f+, +ff+, or +fff+ and think it's a note
            if (i > m.start()) continue;

            if (inChord) chordSize++;

            if (enableLotroErrors && inChord && chordSize > AbcConstants.MAX_CHORD_NOTES) {
              throw new LotroParseException(
                  "Too many notes in a chord", fileName, lineNumber, m.start());
            }

            // Parse the note
            int numerator;
            int denominator;

            numerator =
                (m.group(NOTE_LEN_NUMER) == null) ? 1 : Integer.parseInt(m.group(NOTE_LEN_NUMER));
            String denom = m.group(NOTE_LEN_DENOM);
            if (denom == null) denominator = 1;
            else if (denom.equals("/")) denominator = 2;
            else if (denom.equals("//")) denominator = 4;
            else denominator = Integer.parseInt(denom.substring(1));

            String brokenRhythm = m.group(NOTE_BROKEN_RHYTHM);
            if (brokenRhythm != null) {
              if (brokenRhythmDenominator != 1 || brokenRhythmNumerator != 1) {
                throw new ParseException(
                    "Invalid broken rhythm: " + brokenRhythm,
                    fileName,
                    lineNumber,
                    m.start(NOTE_BROKEN_RHYTHM));
              }
              if (inChord) {
                throw new ParseException(
                    "Can't have broken rhythm (< or >) within a chord",
                    fileName,
                    lineNumber,
                    m.start(NOTE_BROKEN_RHYTHM));
              }
              if (m.group(NOTE_TIE) != null) {
                throw new ParseException(
                    "Tied notes can't have broken rhythms (< or >)",
                    fileName,
                    lineNumber,
                    m.start(NOTE_BROKEN_RHYTHM));
              }

              int factor = 1 << brokenRhythm.length();

              if (brokenRhythm.charAt(0) == '>') {
                numerator *= 2 * factor - 1;
                denominator *= factor;
                brokenRhythmDenominator = factor;
              } else {
                brokenRhythmNumerator = 2 * factor - 1;
                brokenRhythmDenominator = factor;
                denominator *= factor;
              }
            } else {
              numerator *= brokenRhythmNumerator;
              denominator *= brokenRhythmDenominator;
              brokenRhythmNumerator = 1;
              brokenRhythmDenominator = 1;
            }

            if (tuplet != null) {
              if (!inChord || chordSize == 1) tuplet.r--;
              numerator *= tuplet.q;
              denominator *= tuplet.p;
              if (tuplet.r == 0) tuplet = null;
            }

            // Convert back to the original tempo
            int curTempoBPM = info.getCurrentTempoBPM(chordStartTick);
            int primaryTempoBPM = info.getPrimaryTempoBPM();
            numerator *= curTempoBPM;
            denominator *= primaryTempoBPM;

            // Try to guess if this note is using triplet timing
            if ((denominator % 3 == 0) && (numerator % 3 != 0)) {
              abcInfo.setHasTriplets(true);
            }

            long noteEndTick = chordStartTick + DEFAULT_NOTE_TICKS * numerator / denominator;

            // A chord is as long as its shortest note
            if (chordEndTick == chordStartTick || noteEndTick < chordEndTick)
              chordEndTick = noteEndTick;

            char noteLetter = m.group(NOTE_LETTER).charAt(0);
            String octaveStr = m.group(NOTE_OCTAVE);
            if (octaveStr == null) octaveStr = "";
            if (noteLetter == 'z' || noteLetter == 'x') {
              if (m.group(NOTE_ACCIDENTAL) != null && m.group(NOTE_ACCIDENTAL).length() > 0) {
                throw new ParseException(
                    "Unexpected accidental on a rest",
                    fileName,
                    lineNumber,
                    m.start(NOTE_ACCIDENTAL));
              }
              if (octaveStr.length() > 0) {
                throw new ParseException(
                    "Unexpected octave indicator on a rest",
                    fileName,
                    lineNumber,
                    m.start(NOTE_OCTAVE));
              }
            } else {
              int octave = Character.isUpperCase(noteLetter) ? 3 : 4;
              if (octaveStr.indexOf('\'') >= 0) octave += octaveStr.length();
              else if (octaveStr.indexOf(',') >= 0) octave -= octaveStr.length();

              int noteId;
              int lotroNoteId;

              lotroNoteId =
                  noteId =
                      (octave + 1) * 12 + CHR_NOTE_DELTA[Character.toLowerCase(noteLetter) - 'a'];
              if (!useLotroInstruments) noteId += 12 * info.getInstrument().octaveDelta;

              if (m.group(NOTE_ACCIDENTAL) != null) {
                if (m.group(NOTE_ACCIDENTAL).startsWith("_"))
                  accidentals.put(noteId, -m.group(NOTE_ACCIDENTAL).length());
                else if (m.group(NOTE_ACCIDENTAL).startsWith("^"))
                  accidentals.put(noteId, m.group(NOTE_ACCIDENTAL).length());
                else if (m.group(NOTE_ACCIDENTAL).equals("=")) accidentals.put(noteId, 0);
              }

              int noteDelta;
              if (accidentals.containsKey(noteId)) {
                noteDelta = accidentals.get(noteId);
              } else {
                // Use the key signature to determine the accidental
                noteDelta = info.getKey().getDefaultAccidental(noteId).deltaNoteId;
              }
              lotroNoteId += noteDelta;
              noteId += noteDelta;

              if (enableLotroErrors && lotroNoteId < Note.MIN_PLAYABLE.id)
                throw new LotroParseException("Note is too low", fileName, lineNumber, m.start());
              else if (enableLotroErrors && lotroNoteId > Note.MAX_PLAYABLE.id)
                throw new LotroParseException("Note is too high", fileName, lineNumber, m.start());

              if (info.getInstrument() == LotroInstrument.COWBELL
                  || info.getInstrument() == LotroInstrument.MOOR_COWBELL) {
                if (useLotroInstruments) {
                  // Randomize the noteId unless it's part of a note tie
                  if (m.group(NOTE_TIE) == null && !tiedNotes.containsKey(noteId)) {
                    int min = info.getInstrument().lowestPlayable.id;
                    int max = info.getInstrument().highestPlayable.id;
                    lotroNoteId = noteId = min + (int) (Math.random() * (max - min));
                  }
                } else {
                  noteId = (info.getInstrument() == LotroInstrument.COWBELL) ? 76 : 71;
                  lotroNoteId = 71;
                }
              }

              // Check for overlapping notes, and remove extra note off events
              Iterator<MidiEvent> noteOffIter = noteOffEvents.iterator();
              while (noteOffIter.hasNext()) {
                MidiEvent evt = noteOffIter.next();
                if (evt.getTick() <= chordStartTick) {
                  noteOffIter.remove();
                  continue;
                }

                int noteOffId = ((ShortMessage) evt.getMessage()).getData1();
                if (noteOffId == noteId) {
                  track.remove(evt);
                  evt.setTick(chordStartTick);
                  track.add(evt);
                  noteOffIter.remove();
                  break;
                }
              }

              if (!tiedNotes.containsKey(noteId)) {
                if (info.getPpqn() != PPQN) {
                  throw new ParseException(
                      "The default note length must be the same for all parts of the song",
                      fileName,
                      noteDivisorChangeLine);
                }
                track.add(
                    MidiFactory.createNoteOnEventEx(
                        noteId,
                        channel,
                        info.getDynamics().getVol(useLotroInstruments),
                        chordStartTick));
              }

              if (m.group(NOTE_TIE) != null) {
                int lineAndColumn = (lineNumber << 16) | m.start();
                tiedNotes.put(noteId, lineAndColumn);
              } else {
                double MPQN = MidiUtils.convertTempo(curTempoBPM);
                double lengthMicros = (noteEndTick - chordStartTick) * MPQN / PPQN;

                if (enableLotroErrors && lengthMicros < AbcConstants.SHORTEST_NOTE_MICROS) {
                  throw new LotroParseException(
                      "Note's duration is too short", fileName, lineNumber, m.start());
                } else if (enableLotroErrors && lengthMicros > AbcConstants.LONGEST_NOTE_MICROS) {
                  throw new LotroParseException(
                      "Note's duration is too long", fileName, lineNumber, m.start());
                }

                // Stringed instruments, drums, and woodwind breath sounds always play the
                // sound sample in its entirety. Since Gervill doesn't support the SoundFont
                // extension that specifies this, we have to increase the note length.
                // One second should do the trick.
                long noteEndTickTmp = noteEndTick;
                if (useLotroInstruments && !info.getInstrument().isSustainable(lotroNoteId)) {
                  noteEndTickTmp =
                      Math.max(
                          noteEndTick,
                          chordStartTick
                              + Math.round(AbcConstants.ONE_SECOND_MICROS * PPQN / MPQN));
                }
                MidiEvent noteOff =
                    MidiFactory.createNoteOffEventEx(
                        noteId,
                        channel,
                        info.getDynamics().getVol(useLotroInstruments),
                        noteEndTickTmp);
                track.add(noteOff);
                noteOffEvents.add(noteOff);

                tiedNotes.remove(noteId);
              }
            }

            if (!inChord) chordStartTick = noteEndTick;
            i = m.end();
          }

          if (tuplet != null)
            throw new ParseException("Tuplet not finished by end of line", fileName, lineNumber, i);

          if (inChord)
            throw new ParseException("Chord not closed at end of line", fileName, lineNumber, i);

          if (brokenRhythmDenominator != 1 || brokenRhythmNumerator != 1)
            throw new ParseException(
                "Broken rhythm unfinished at end of line", fileName, lineNumber, i);
        }
      }

      if (seq == null) throw new ParseException("The file contains no notes", fileName, lineNumber);

      for (int lineAndColumn : tiedNotes.values()) {
        throw new ParseException(
            "Tied note does not connect to another note",
            fileName,
            lineAndColumn >>> 16,
            lineAndColumn & 0xFFFF);
      }
    }

    PanGenerator pan = null;
    if (stereo && trackNumber > 1) pan = new PanGenerator();

    Track[] tracks = seq.getTracks();

    // Add tempo events
    for (Map.Entry<Long, Integer> tempoEvent : info.getAllPartsTempoMap().entrySet()) {
      long tick = tempoEvent.getKey();
      int mpq = (int) MidiUtils.convertTempo(tempoEvent.getValue());
      tracks[0].add(MidiFactory.createTempoEvent(mpq, tick));
    }

    // Add name and pan events
    tracks[0].add(MidiFactory.createTrackNameEvent(abcInfo.getTitle()));
    for (int i = 1; i <= trackNumber; i++) {
      tracks[i].add(MidiFactory.createTrackNameEvent(abcInfo.getPartName(i)));

      int panAmount = PanGenerator.CENTER;
      if (pan != null) panAmount = pan.get(abcInfo.getPartInstrument(i), abcInfo.getPartName(i));
      tracks[i].add(MidiFactory.createPanEvent(panAmount, getTrackChannel(i)));
    }

    return seq;
  }