/** * 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; }
/** * 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; }
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]); }
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; }
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); } } } }
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"); } }
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); } } } }