/** * 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 Sequence process( final Sequence inputSequence, int track, long startTick, long endTick, int noteOverlap, int minimumRest, Handler loggingHandler) throws InvalidMidiDataException { Sequence outputSequence = new Sequence(inputSequence.getDivisionType(), inputSequence.getResolution()); Track[] inputTracks = inputSequence.getTracks(); SlurBinderA.noteOverlap = noteOverlap; SlurBinderA.minimumRest = minimumRest; slurEndNotes = new HashSet<>(); for (int trackI = 0; trackI < inputTracks.length; trackI++) { Track inputTrack = inputTracks[trackI]; Track outputTrack = outputSequence.createTrack(); if (trackI == track) { handleTrack(outputTrack, inputTrack, startTick, endTick); } else { for (int eventI = 0; eventI < inputTrack.size(); eventI++) { MidiEvent midEvent = inputTrack.get(eventI); outputTrack.add(midEvent); } } } return outputSequence; }
/** * Given a microsecond time, convert to tick. returns tempo at the given time in * cache.getCurrTempoMPQ */ public static long microsecond2tick(Sequence seq, long micros, TempoCache cache) { if (seq.getDivisionType() != Sequence.PPQ) { double dTick = (((double) micros) * ((double) seq.getDivisionType()) * ((double) seq.getResolution())) / ((double) 1000000); long tick = (long) dTick; if (cache != null) { cache.currTempo = (int) cache.getTempoMPQAt(tick); } return tick; } if (cache == null) { cache = new TempoCache(seq); } long[] ticks = cache.ticks; int[] tempos = cache.tempos; // in MPQ int cacheCount = tempos.length; int resolution = seq.getResolution(); long us = 0; long tick = 0; int newReadPos = 0; int i = 1; // walk through all tempo changes and add time for the respective blocks // to find the right tick if (micros > 0 && cacheCount > 0) { // this loop requires that the first tempo Event is at time 0 while (i < cacheCount) { long nextTime = us + ticks2microsec(ticks[i] - ticks[i - 1], tempos[i - 1], resolution); if (nextTime > micros) { break; } us = nextTime; i++; } tick = ticks[i - 1] + microsec2ticks(micros - us, tempos[i - 1], resolution); if (Printer.debug) Printer.debug("microsecond2tick(" + (micros / 1000) + ") = " + tick + " ticks."); // if (Printer.debug) Printer.debug(" -> convert back = " + (tick2microsecond(seq, tick, // null) / 1000)+" microseconds"); } cache.currTempo = tempos[i - 1]; return tick; }
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; }
/** * Given a tick, convert to microsecond * * @param cache tempo info and current tempo */ public static long tick2microsecond(Sequence seq, long tick, TempoCache cache) { if (seq.getDivisionType() != Sequence.PPQ) { double seconds = ((double) tick / (double) (seq.getDivisionType() * seq.getResolution())); return (long) (1000000 * seconds); } if (cache == null) { cache = new TempoCache(seq); } int resolution = seq.getResolution(); long[] ticks = cache.ticks; int[] tempos = cache.tempos; // in MPQ int cacheCount = tempos.length; // optimization to not always go through entire list of tempo events int snapshotIndex = cache.snapshotIndex; int snapshotMicro = cache.snapshotMicro; // walk through all tempo changes and add time for the respective blocks long us = 0; // microsecond if (snapshotIndex <= 0 || snapshotIndex >= cacheCount || ticks[snapshotIndex] > tick) { snapshotMicro = 0; snapshotIndex = 0; } if (cacheCount > 0) { // this implementation needs a tempo event at tick 0! int i = snapshotIndex + 1; while (i < cacheCount && ticks[i] <= tick) { snapshotMicro += ticks2microsec(ticks[i] - ticks[i - 1], tempos[i - 1], resolution); snapshotIndex = i; i++; } us = snapshotMicro + ticks2microsec(tick - ticks[snapshotIndex], tempos[snapshotIndex], resolution); } cache.snapshotIndex = snapshotIndex; cache.snapshotMicro = snapshotMicro; return us; }
/* Write a sequence to an output stream in standard midi format. * @see javax.sound.midi.spi.MidiFileWriter#write(javax.sound.midi.Sequence, int, java.io.OutputStream) */ public int write(Sequence in, int fileType, OutputStream out) throws IOException { MidiDataOutputStream dos = new MidiDataOutputStream(out); Track[] tracks = in.getTracks(); dos.writeInt(0x4d546864); // MThd dos.writeInt(6); dos.writeShort(fileType); dos.writeShort(tracks.length); float divisionType = in.getDivisionType(); int resolution = in.getResolution(); // FIXME: division computation is incomplete. int division = 0; if (divisionType == Sequence.PPQ) division = resolution & 0x7fff; dos.writeShort(division); int length = 14; for (Track track : tracks) length += writeTrack(track, dos); return length; }
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); } } } }