/**
   * Write a track to an output stream.
   *
   * @param track the track to write
   * @param dos a MidiDataOutputStream to write to
   * @return the number of bytes written
   */
  private int writeTrack(Track track, MidiDataOutputStream dos) throws IOException {
    int i = 0, elength = track.size(), trackLength;
    MidiEvent pme = null;
    dos.writeInt(0x4d54726b); // "MTrk"
    trackLength = computeTrackLength(track, dos);
    dos.writeInt(trackLength);
    while (i < elength) {
      MidiEvent me = track.get(i);
      int dtime = 0;
      if (pme != null) dtime = (int) (me.getTick() - pme.getTick());
      dos.writeVariableLengthInt(dtime);
      // FIXME: use running status byte
      byte msg[] = me.getMessage().getMessage();
      dos.write(msg);
      pme = me;
      i++;
    }

    // We're done if the last event was an End of Track meta message.
    if (pme != null && (pme.getMessage() instanceof MetaMessage)) {
      MetaMessage mm = (MetaMessage) pme.getMessage();
      if (mm.getType() == 0x2f) // End of Track message
      return trackLength + 8;
    }

    // Write End of Track meta message
    dos.writeVariableLengthInt(0); // Delta time of 0
    dos.writeByte(0xff); // Meta Message
    dos.writeByte(0x2f); // End of Track message
    dos.writeVariableLengthInt(0); // Length of 0

    return trackLength + 8 + 4;
  }
Esempio n. 2
0
  /**
   * Send entry MIDI Sequence into Receiver using time stamps.
   *
   * @return The total length of the sequence.
   */
  private double send(final Sequence seq, final Receiver recv) {
    assert seq.getDivisionType() == Sequence.PPQ;

    final float divtype = seq.getDivisionType();
    final Track[] tracks = seq.getTracks();

    tune(recv);

    final int[] trackspos = new int[tracks.length];
    int mpq = 500000;
    final int seqres = seq.getResolution();
    long lasttick = 0;
    long curtime = 0;
    while (true) {
      MidiEvent selevent = null;
      int seltrack = -1;
      for (int i = 0; i < tracks.length; i++) {
        final int trackpos = trackspos[i];
        final Track track = tracks[i];
        if (trackpos < track.size()) {
          final MidiEvent event = track.get(trackpos);
          if (selevent == null || event.getTick() < selevent.getTick()) {
            selevent = event;
            seltrack = i;
          }
        }
      }
      if (seltrack == -1) {
        break;
      }
      trackspos[seltrack]++;
      final long tick = selevent.getTick();
      if (divtype == Sequence.PPQ) {
        curtime += (tick - lasttick) * mpq / seqres;
      } else {
        curtime = (long) (tick * 1000000.0 * divtype / seqres);
      }
      lasttick = tick;
      final MidiMessage msg = selevent.getMessage();
      if (msg instanceof MetaMessage) {
        if (divtype == Sequence.PPQ && ((MetaMessage) msg).getType() == 0x51) {
          final byte[] data = ((MetaMessage) msg).getData();
          mpq = (data[0] & 0xff) << 16 | (data[1] & 0xff) << 8 | data[2] & 0xff;
        }
      } else if (recv != null) {
        recv.send(msg, curtime);
      }
    }
    return curtime / 1000000.0;
  }
Esempio n. 3
0
  public static void convertMidi2RealTime(Sequence seqIn) {
    seq = seqIn;
    double currentTempo = 500000;
    int tickOfTempoChange = 0;
    double msb4 = 0;
    double division = seq.getResolution();
    int lastTick = 0;
    int count = 0;
    for (int track = 0; track < seq.getTracks().length; track++) nextMessageOf.add(0);
    System.out.println();

    MidiEvent nextEvent;
    while ((nextEvent = getNextEvent()) != null) {
      int tick = (int) nextEvent.getTick();
      if (noteIsOff(nextEvent)) {
        double time =
            (msb4 + (((currentTempo / seq.getResolution()) / 1000) * (tick - tickOfTempoChange)));
        System.out.println(
            "track="
                + currentTrack
                + " tick="
                + tick
                + " time="
                + (int) (time + 0.5)
                + "ms "
                + " note "
                + ((int) nextEvent.getMessage().getMessage()[1] & 0xFF)
                + " off");
      } else if (noteIsOn(nextEvent)) {
        double time =
            (msb4 + (((currentTempo / seq.getResolution()) / 1000) * (tick - tickOfTempoChange)));
        System.out.println(
            "track="
                + currentTrack
                + " tick="
                + tick
                + " time="
                + (int) (time + 0.5)
                + "ms "
                + " note "
                + ((int) nextEvent.getMessage().getMessage()[1] & 0xFF)
                + " on");
      } else if (changeTemp(nextEvent)) {
        String a = (Integer.toHexString((int) nextEvent.getMessage().getMessage()[3] & 0xFF));
        String b = (Integer.toHexString((int) nextEvent.getMessage().getMessage()[4] & 0xFF));
        String c = (Integer.toHexString((int) nextEvent.getMessage().getMessage()[5] & 0xFF));
        if (a.length() == 1) a = ("0" + a);
        if (b.length() == 1) b = ("0" + b);
        if (c.length() == 1) c = ("0" + c);
        String whole = a + b + c;
        int newTempo = Integer.parseInt(whole, 16);
        double newTime = (currentTempo / seq.getResolution()) * (tick - tickOfTempoChange);
        msb4 += (newTime / 1000);
        tickOfTempoChange = tick;
        currentTempo = newTempo;
      }
    }
  }
  public static double send(Sequence seq, Receiver recv) {
    float divtype = seq.getDivisionType();
    assert (seq.getDivisionType() == Sequence.PPQ);
    Track[] tracks = seq.getTracks();
    int[] trackspos = new int[tracks.length];
    int mpq = 60000000 / 100;
    int seqres = seq.getResolution();
    long lasttick = 0;
    long curtime = 0;
    while (true) {
      MidiEvent selevent = null;
      int seltrack = -1;
      for (int i = 0; i < tracks.length; i++) {
        int trackpos = trackspos[i];
        Track track = tracks[i];
        if (trackpos < track.size()) {
          MidiEvent event = track.get(trackpos);
          if (selevent == null || event.getTick() < selevent.getTick()) {
            selevent = event;
            seltrack = i;
          }
        }
      }
      if (seltrack == -1) break;
      trackspos[seltrack]++;
      long tick = selevent.getTick();
      if (divtype == Sequence.PPQ) curtime += ((tick - lasttick) * mpq) / seqres;
      else curtime = (long) ((tick * 1000000.0 * divtype) / seqres);
      lasttick = tick;
      MidiMessage msg = selevent.getMessage();
      if (msg instanceof MetaMessage) {
        if (divtype == Sequence.PPQ)
          if (((MetaMessage) msg).getType() == 0x51) {
            byte[] data = ((MetaMessage) msg).getData();
            mpq = ((data[0] & 0xff) << 16) | ((data[1] & 0xff) << 8) | (data[2] & 0xff);
          }
      } else {
        if (recv != null) recv.send(msg, curtime);
      }
    }

    return curtime / 1000000.0;
  }
 /**
  * Compute the length of a track as it will be written to the output stream.
  *
  * @param track the track to measure
  * @param dos a MidiDataOutputStream used for helper method
  * @return the length of the track
  */
 private int computeTrackLength(Track track, MidiDataOutputStream dos) {
   int count = 0, length = 0, i = 0, eventCount = track.size();
   long ptick = 0;
   while (i < eventCount) {
     MidiEvent me = track.get(i);
     long tick = me.getTick();
     length += dos.variableLengthIntLength((int) (tick - ptick));
     ptick = tick;
     length += me.getMessage().getLength();
     i++;
   }
   return length;
 }
  public static void assertNoteEventEqual(
      MidiEvent noteEvent,
      long tick,
      byte byte1,
      byte byte2,
      byte byte3,
      boolean ignoreTickOffset) {
    int tickOffset = (ignoreTickOffset ? 0 : MidiNote.MIDI_SEQUENCE_START_SILENCE_TICK_OFFSET);

    assertEquals(tick, noteEvent.getTick() - tickOffset);
    javax.sound.midi.MidiMessage msg = noteEvent.getMessage();
    assertEquals(byte1, msg.getMessage()[0]);
    assertEquals(byte2, msg.getMessage()[1]);
    assertEquals(byte3, msg.getMessage()[2]);
  }
Esempio n. 7
0
  public MyPatch getProgram() {

    MyPatch patch = programEvent.getPatch();
    if (patch != null) {
      return patch;
    }

    FrinikaTrackWrapper track = ftw;
    int count = track.size();
    for (int i = 0; i < count; i++) {
      MidiEvent event = track.get(i);
      if (event.getTick() != 0) {
        return patch;
      }
      MidiMessage msg = event.getMessage();
      if (msg instanceof ShortMessage) {
        ShortMessage sms = (ShortMessage) msg;
        if (sms.getCommand() == ShortMessage.PROGRAM_CHANGE) {
          if (patch == null) {
            patch = new MyPatch(0, 0, 0);
          }
          patch.prog = sms.getData1();
        }
        if (sms.getCommand() == ShortMessage.CONTROL_CHANGE) {
          if (sms.getData1() == 0) {
            if (patch == null) {
              patch = new MyPatch(0, 0, 0);
            }
            patch.msb = sms.getData2();
          }
          if (sms.getData1() == 0x20) {
            if (patch == null) {
              patch = new MyPatch(0, 0, 0);
            }
            patch.lsb = sms.getData2();
          }
        }
      }
    }
    return patch;
  }
Esempio n. 8
0
 public synchronized void refresh(Sequence seq) {
   ArrayList<MidiEvent> list = new ArrayList<>();
   Track[] tracks = seq.getTracks();
   if (tracks.length > 0) {
     // tempo events only occur in track 0
     Track track = tracks[0];
     int c = track.size();
     for (int i = 0; i < c; i++) {
       MidiEvent ev = track.get(i);
       MidiMessage msg = ev.getMessage();
       if (isMetaTempo(msg)) {
         // found a tempo event. Add it to the list
         list.add(ev);
       }
     }
   }
   int size = list.size() + 1;
   firstTempoIsFake = true;
   if ((size > 1) && (list.get(0).getTick() == 0)) {
     // do not need to add an initial tempo event at the beginning
     size--;
     firstTempoIsFake = false;
   }
   ticks = new long[size];
   tempos = new int[size];
   int e = 0;
   if (firstTempoIsFake) {
     // add tempo 120 at beginning
     ticks[0] = 0;
     tempos[0] = DEFAULT_TEMPO_MPQ;
     e++;
   }
   for (int i = 0; i < list.size(); i++, e++) {
     MidiEvent evt = list.get(i);
     ticks[e] = evt.getTick();
     tempos[e] = getTempoMPQ(evt.getMessage());
   }
   snapshotIndex = 0;
   snapshotMicro = 0;
 }
 public static final void addNotesToTrack(Track From, Track To) throws InvalidMidiDataException {
   for (int i = 0; i < From.size(); i++) {
     MidiEvent Me = From.get(i);
     MidiMessage Mm = Me.getMessage();
     if (Mm instanceof ShortMessage) {
       ShortMessage Sm = (ShortMessage) Mm;
       int Command = Sm.getCommand();
       int Com = -1;
       if (Command == ShortMessage.NOTE_ON) {
         Com = MetaEventOffset;
       } else if (Command == ShortMessage.NOTE_OFF) {
         Com = MetaEventOffset + 1;
       }
       if (Com > 0) {
         byte[] b = Sm.getMessage();
         int l = (b == null ? 0 : b.length);
         MetaMessage MetaMessage = new MetaMessage(Com, b, l);
         MidiEvent Me2 = new MidiEvent(MetaMessage, Me.getTick());
         To.add(Me2);
       }
     }
   }
 }
Esempio n. 10
0
  public void _testGPWithPlayer(MidiSongDefinition sd, int usq) throws Exception {
    MidiSongDefinition testFile = SongArchive.testFileSongDefinition();
    try {
      GPInputStream gpis = new GPInputStream(sd.getGpFileName());
      GPSong gpsong = (GPSong) gpis.readObject();
      gpis.close();

      int tempoGPSong = gpsong.getTempo();
      // OLD assertEquals((int)(60*1000*1000/usq),tempoGPSong);
      assertEquals((60 * 1000 * 1000 / usq), tempoGPSong);

      Song song = GPAdaptor.makeSong(gpsong);

      Tempo tempoSong = song.getTempo();
      assertEquals(usq, (int) tempoSong.getUSQ());

      MasterPlayer player = new MasterPlayer();
      player.setSoundPlayer(new MidiFiler(testFile.getMidiFileName()));
      Performance performance = player.arrange(song, null);

      Tempo tempoPerformance = performance.getTempo();
      assertEquals(usq, (int) tempoPerformance.getUSQ());

      // a performance is really a sequence. So make sure there
      // is a tempo event (meta 0x51) on track 0.

      // make sure as well there is exactly ONE tempo event at timestamp 0
      Sequence sequence = (Sequence) performance;
      Track[] midiTracks = sequence.getTracks();
      Track tempoMap = midiTracks[0];

      Tempo tempoInMIDI = new Tempo();
      for (int i = 0; i < tempoMap.size(); i++) {
        MidiEvent me = tempoMap.get(i);
        long tick = me.getTick();
        if (tick > 0) break;
        MidiMessage mm = me.getMessage();
        if (mm.getStatus() == MetaMessage.META) {
          MetaMessage meta = (MetaMessage) mm;
          if (meta.getType() == 0x51) {
            byte[] data = meta.getData();
            tempoInMIDI.setUSQ(
                ((data[0] & 0x00FF) << 16) | ((data[1] & 0x00FF) << 8) | ((data[2] & 0x00FF)));
            break;
          }
        }
      }
      assertEquals(usq, (int) tempoInMIDI.getUSQ());

      MidiOutputStream mos = new MidiOutputStream(new FileOutputStream(testFile.getMidiFileName()));
      mos.write(performance);
      mos.close();

      compareMIDIFiles(
          sd.getMidiFileName(), testFile.getMidiFileName(), sd.getChannels(), sd.getEventRemap());
    } catch (FileNotFoundException e) {
      fail("file not found exception");
    } catch (GPFormatException e) {
      fail("gp format exception");
    } catch (IOException e) {
      fail("ioexception");
    } catch (CodecFormatException e) {
      fail("codec format exception");
    } catch (InvalidMidiDataException e) {
      fail("invalid midi data exception");
    }
  }
Esempio n. 11
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;
  }
  public static void main(String[] args) {
    /*
     *	We check that there is exactly 3 command-line
     *	argument. If not, we display the usage message and
     *	exit.
     */
    if (args.length != 3) {
      System.out.println("DumpSequence: usage:");
      System.out.println(
          "\tjava DumpSequence <midifile> <\"guitar\" | \"bass\" | \"drums\" | \"vocals\"> <\"easy\" | \"medium\" | \"hard\" | \"expert\">");
      System.exit(1);
    }

    /*
     *	Now, that we're sure there is an argument, we take it as
     *	the filename of the soundfile we want to play.
     */
    String strFilename = args[0];
    String selectInstru = args[1];
    if (!selectInstru.equals("guitar")
        && !selectInstru.equals("bass")
        && !selectInstru.equals("drums")
        && !selectInstru.equals("vocals")) {
      System.out.println("invalid instrument");
      System.exit(1);
    }
    String level = args[2];
    int lvl = 0;
    if (level.equals("easy")) {
      lvl = 4;
    } else if (level.equals("medium")) {
      lvl = 5;
    } else if (level.equals("hard")) {
      lvl = 6;
    } else if (level.equals("expert")) {
      lvl = 7;
    } else {
      System.out.println("invalid level");
      System.exit(1);
    }

    File midiFile = new File(strFilename);

    /*
     *	We try to get a Sequence object, which the content
     *	of the MIDI file.
     */
    Sequence sequence = null;
    try {
      sequence = MidiSystem.getSequence(midiFile);
    } catch (Exception e) {
      e.printStackTrace();
      System.exit(1);
    }
    //		catch (InvalidMidiDataException e)
    //		{
    //			e.printStackTrace();
    //			System.exit(1);
    //		}
    //		catch (IOException e)
    //		{
    //			e.printStackTrace();
    //			System.exit(1);
    //		}

    /*
     *	And now, we output the data.
     */
    if (sequence == null) {
      System.out.println("Cannot retrieve Sequence.");
    } else {
      System.out.println("File: " + strFilename);
      System.out.println("Instrument: " + selectInstru);
      System.out.println("Level: " + level);
      long dur = sequence.getMicrosecondLength();

      String strResolutionType = null;
      if (sequence.getDivisionType() == Sequence.PPQ) {
        strResolutionType = " ticks per beat";
      } else {
        strResolutionType = " ticks per frame";
      }
      long ticks_per_beat = sequence.getResolution();
      //			System.out.println(ticks_per_beat);
      Track[] tracks = sequence.getTracks();

      // we want only track with track name "midi_export", "EVENTS" and  "PART DRUMS"
      // create an arrayList with only the index of the tracks we want
      String timeSig = ""; // , timeSigOther = "", tempoBPM = "";
      int useTrack = 0;

      ArrayList<String[]> events = new ArrayList<String[]>();
      for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
        Track track = tracks[nTrack];

        MidiEvent event = track.get(0);
        MidiMessage m = event.getMessage();
        if (m instanceof MetaMessage) {
          MetaMessage meta = (MetaMessage) m;
          String trackName = DumpReceiver.myDecodeMessage(meta);
          trackName = trackName.toLowerCase();
          if (trackName.contains(selectInstru))
          // get indexes of the tracks which contain the songs sections, and drum notes
          {
            useTrack = nTrack;
          } else if (trackName.contains("midi_export"))
          // get information about the song
          // time signature and tempo are entries 2 and 3, where tick ==0
          {
            for (int nEvent = 1; nEvent < track.size(); nEvent++) {
              event = track.get(nEvent);
              m = event.getMessage();
              long lTicks = event.getTick();
              if (lTicks == 0) {
                String line = DumpReceiver.mySend(m, lTicks).toLowerCase();
                //								System.out.println(line);
                if (line.contains("time signature")) {
                  timeSig =
                      line.substring(
                          line.indexOf("time signature: ") + ("time signature: ").length());
                  timeSig = timeSig.substring(0, timeSig.indexOf(','));
                }
              }
            }
          } else if (trackName.contains("events"))
          // store the song sections, and the tick values where they start
          {
            for (int nEvent = 1; nEvent < track.size(); nEvent++) {
              String[] tick_and_event = new String[2];
              event = track.get(nEvent);
              m = event.getMessage();
              long lTicks = event.getTick();
              String line = DumpReceiver.mySend(m, lTicks).toLowerCase();
              if (line.contains("text event: [section")) {
                tick_and_event[0] = "" + lTicks;
                line =
                    line.substring(
                        line.indexOf("text event: [section") + "text event: [section".length(),
                        line.length() - 1);
                tick_and_event[1] = line;
                events.add(tick_and_event);
              }
            }
          }
        }
      }

      if (timeSig.equals("")) {
        // no time signature found. Assume 4/4
        timeSig = "4/4";
      }

      // create an ArrayList of all tick indexes we want in our tab
      ArrayList<Long> allTicks = new ArrayList<Long>();
      Track track = tracks[useTrack];

      long lastTick = 0;
      for (int nEvent = 0; nEvent < track.size(); nEvent++) {
        String line = "";
        MidiEvent event = track.get(nEvent);
        MidiMessage message = event.getMessage();
        long lTicks = event.getTick();

        if (message instanceof ShortMessage) {
          line = DumpReceiver.myDecodeMessage((ShortMessage) message).toLowerCase();
          if (line.contains("note on") && line.endsWith("" + lvl) && !allTicks.contains(lTicks)) {
            allTicks.add(lTicks);
          }
        }
      }
      // allTicks are now all the unique time indexes of notes

      // create a 2d array, containging the timeTick and all notes played for that timeTick, for the
      // whole song
      String[][] masterList =
          new String[allTicks.size() + 1][0]; // plus one, to take into account for the drum part
      masterList[0] = new String[] {"0", "B |", "FT|", "T2|", "S |", "HH|", "C |"};

      for (int i = 0; i < allTicks.size(); i++) // loop through all saved tick times
      {
        String[] oneTick = new String[] {"", "", "", "", "", "", ""};
        oneTick[0] = "" + allTicks.get(i);

        for (int nEvent = 0; nEvent < track.size(); nEvent++) // loop through all events in track
        {
          String line = "";
          MidiEvent event = track.get(nEvent);
          MidiMessage message = event.getMessage();
          long lTicks = event.getTick();

          if (message instanceof ShortMessage
              && lTicks
                  == allTicks.get(i)) // if it's a short message, and is the tick we're looking for
          {
            line = DumpReceiver.myDecodeMessage((ShortMessage) message).toLowerCase();
            if (line.contains("note on") && line.endsWith("" + lvl)) {
              insert(oneTick, line);
            }
          } else if (lTicks > allTicks.get(i)) // we've gone past that point in the song
          {
            nEvent += track.size();
          }
        }

        // if there are any notes that are not played, use "-"
        for (int j = 0; j < oneTick.length; j++) {
          if (oneTick[j].equals("")) {
            oneTick[j] = "-";
          }
        }
        masterList[i + 1] = oneTick; // i+1, since [0] is the names of the drums
      }

      // work with time sig

      long note_amount = Long.valueOf(timeSig.substring(0, timeSig.indexOf("/")));
      System.out.println(
          "timeSig " + timeSig + " ticks_per_beat/note_amount " + ticks_per_beat / note_amount);
      long note_type = Long.valueOf(timeSig.substring(timeSig.indexOf("/") + 1));

      // GENERATE FINAL CONTENT TO BE PRINTED
      // the amount of --- should be printed in reverse, ie 1---3, is determined by 3.
      // if time 1 is 0, 3 is ticks_per_beat, and the ticks per beat is 480, --- is printed, then 3
      // if time 1 is 0, 3 is ticks_per_beat/2, ""							, - is printed, then 3
      // if time 1 is 0, 3 is ticks_per_beat/4, ""							, nothing is printed, then 3

      // every ticks_per_beat*note_amount there should be a bar

      int noteAmount = 6; // amount of notes defined. 1 is the tick time, anything more is a drum
      int amountOfBarsPerLine = 4;

      String[][] complete;
      ArrayList<String[]> completeList = new ArrayList<String[]>();
      ArrayList<String> tickTimesUsed = new ArrayList<String>();

      // TODO: fix structure of code. seperate into smaller functions.
      for (int j = 1; j <= noteAmount; j++) // crash at the top, bass at the bottom
      {
        int listIndex = 0;
        // TODO fix error where events are in margin

        long bar_index = 0;
        int barCount = 0;

        String[] lineArray;
        String[] eventLineArray;
        ArrayList<String> line = new ArrayList<String>();
        ArrayList<String> eventLine = new ArrayList<String>();

        String start = "";
        for (int i = 0;
            i < masterList.length;
            i++) // loop through all saved tick times, for this drum
        {
          if (i
              > 1) // the symbols for the drum kit, and the very first note should be printed
                   // without anything else in front of them
          {
            long currentNoteTick =
                Long.valueOf(masterList[i][0]); // the tick belonging to the current note
            long previousNoteTick =
                Long.valueOf(masterList[i - 1][0]); // the tick belonging to the previous note
            long diff = currentNoteTick - previousNoteTick;

            while (diff > (ticks_per_beat / note_amount) + 5) // to allow for some time differences
            {
              // NOTE
              line.add("-");
              bar_index++; // update bar_index to reflect adding the "-"
              diff -=
                  (ticks_per_beat / note_amount); // seems to be 17 for first bar, 16 for the rest

              if (j == 1) // EVENT
              {
                eventLine.add(
                    " "); // have to add an additional gap to eventLine, to keep it the same length
                          // as line
              }

              if (bar_index
                  == (note_amount
                      * note_type)) // every (note_amount*note_type)+1 character should be a bar
                                    // line
              {
                line.add("|");
                if (j == 1) // EVENT
                {
                  eventLine.add(
                      " "); // have to add an additional gap to eventLine, to keep it the same
                            // length as line
                }

                bar_index = 0; // reset bar_index, as we are now in a new bar
                barCount++;

                if (barCount == amountOfBarsPerLine) // a new line
                {
                  // int num = 1;
                  if (j == 1) // EVENT
                  {
                    // NOTE
                    // we want to start new line
                    lineArray = new String[line.size()];
                    lineArray = line.toArray(lineArray); // cast ArrayList to Array
                    completeList.add(listIndex, lineArray);
                    listIndex++;

                    line = new ArrayList<String>();
                    line.add(start); // always have which drum it is, at the start of the line
                    barCount = 0; // reset barCount for the current line

                    // we want to start new line
                    eventLineArray = new String[eventLine.size()];
                    eventLineArray = eventLine.toArray(eventLineArray); // cast ArrayList to Array
                    completeList.add(listIndex, eventLineArray);
                    listIndex++;

                    eventLine = new ArrayList<String>();
                    eventLine.add("  "); // 2 gaps
                  } else {
                    // NOTE
                    // we want to start new line
                    lineArray = new String[line.size()];
                    lineArray = line.toArray(lineArray); // cast ArrayList to Array
                    completeList.add(listIndex, lineArray);
                    listIndex += j + 1; // + num; //this orders the notes

                    line = new ArrayList<String>();
                    line.add(start); // always have which drum it is, at the start of the line
                    barCount = 0; // reset barCount for the current line
                  }
                }
              }
            }
            if (j == 1) // && !tickTimesUsed.contains(""+currentNoteTick)) //EVENT
            {
              String s = getEventAtTick(events, "" + currentNoteTick);
              eventLine.add(s);
              tickTimesUsed.add("" + currentNoteTick);
            }
          } else if (i == 1) // check to see where abouts in the bar the first note should be
          {
            long currentNoteTick = Long.valueOf(masterList[i][0]);
            long gapBeforeFirst = currentNoteTick % (ticks_per_beat * note_amount);
            while (gapBeforeFirst > 0) {
              if (j == 1) // && !tickTimesUsed.contains(""+currentNoteTick)) //EVENT
              {
                String s = getEventAtTick(events, "" + currentNoteTick);
                eventLine.add(s);
                tickTimesUsed.add("" + currentNoteTick);
              }

              // NOTE
              line.add("-");
              bar_index++; // update bar_index to reflect adding the "-"
              gapBeforeFirst -= (ticks_per_beat / note_amount);
            }
          } else if (i == 0) // the very first index of an array for a note, ie "B |", "HH|", etc
          {
            start += masterList[i][j]; // "B |", "HH|", etc
            bar_index--; // printing out the first "|" will make bar_index = 1, when we want it to
                         // be 0
          }

          long currentNoteTick =
              Long.valueOf(masterList[i][0]); // the tick belonging to the current note
          if (j == 1 && !tickTimesUsed.contains("" + currentNoteTick)) // EVENT
          {
            String s = getEventAtTick(events, "" + currentNoteTick);
            eventLine.add(s);
            tickTimesUsed.add("" + currentNoteTick);
          }

          // NOTE
          line.add(masterList[i][j]);
          bar_index++; // update bar_index to reflect adding the note

          // if adding the note has ended the bar
          if (bar_index
              == (note_amount
                  * note_type)) // every (note_amount*note_type)+1 character should be a bar line
          {
            line.add("|");
            if (j == 1) // EVENT
            {
              eventLine.add(
                  " "); // have to add an additional gap to eventLine, to keep it the same length as
                        // line
            }

            bar_index = 0; // reset bar_index, as we are now in a new bar
            barCount++;

            if (barCount == amountOfBarsPerLine) // a new line
            {
              // int num = 1;
              if (j == 1) // EVENT
              {
                // NOTE
                // we want to start new line
                lineArray = new String[line.size()];
                lineArray = line.toArray(lineArray); // cast ArrayList to Array
                completeList.add(listIndex, lineArray);
                listIndex++;

                line = new ArrayList<String>();
                line.add(start); // always have which drum it is, at the start of the line
                barCount = 0; // reset barCount for the current line

                // we want to start new line
                eventLineArray = new String[eventLine.size()];
                eventLineArray = eventLine.toArray(eventLineArray); // cast ArrayList to Array
                completeList.add(listIndex, eventLineArray);
                listIndex++;

                eventLine = new ArrayList<String>();
                eventLine.add("  "); // 2 gaps
              } else {
                // NOTE
                // we want to start new line
                lineArray = new String[line.size()];
                lineArray = line.toArray(lineArray); // cast ArrayList to Array
                completeList.add(listIndex, lineArray);
                listIndex += j + 1; // + num; //this orders the notes

                line = new ArrayList<String>();
                line.add(start); // always have which drum it is, at the start of the line
                barCount = 0; // reset barCount for the current line
              }
            }
          }

          if (i
              == masterList.length
                  - 1) // the very last index of an array for a note. Could be a note, or a "-"
          {
            // we want to add this bar to the arrayList, because it is the end, regardless if it's a
            // full bar
            lineArray = new String[line.size()];
            lineArray = line.toArray(lineArray); // cast ArrayList to Array
            completeList.add(listIndex, lineArray);
            listIndex += j; // this orders the notes

            line = new ArrayList<String>();
            line.add(start); // always have which drum it is, at the start of the line
            barCount = 0; // reset barCount for the current line
          }
        }
      }

      complete = new String[completeList.size()][];
      complete = completeList.toArray(complete); // cast ArrayList to Array
      // complete is the tab with bar lines

      // PRINT
      for (int i = 0; i < complete.length; ++i) {
        if (i % (noteAmount + 1)
            == 0) // a new section. Add a gap to make it easier to read. Plus 1, for event line
        {
          System.out.println();
        }
        String line = ""; // reset line
        for (int j = 0; j < complete[i].length; j++) // create a whole line to print
        {
          line += complete[i][j]; // a single character
        }
        if (line.contains("O")
            || line.contains("X")
            || line.substring(3).matches(".*[a-z]+.*")) // the line contains a note
        // substring, so the [a-z]  isn't the drum part, of a blank line
        {
          System.out.println(line);
        }
      }
    }
  }