public void go() { setUpGui(); try { Sequencer sequencer = MidiSystem.getSequencer(); sequencer.open(); // make a sequencer and open sequencer.addControllerEventListener(m1, new int[] {127}); Sequence seq = new Sequence(Sequence.PPQ, 4); Track track = seq.createTrack(); int r = 0; for (int i = 0; i < 300; i += 4) { r = (int) ((Math.random() * 50) + 1); track.add(makeEvent(144, 1, r, 100, i)); track.add(makeEvent(176, 1, 127, 0, i)); track.add(makeEvent(128, 1, r, 100, i + 2)); } // end loop sequencer.setSequence(seq); sequencer.start(); sequencer.setTempoInBPM(120); } catch (Exception ex) { ex.printStackTrace(); } } // close method
public void buildTrackAndStart() { int[] trackList = null; sequence.deleteTrack(track); track = sequence.createTrack(); for (int i = 0; i < 16; i++) { trackList = new int[16]; int key = instruments[i]; for (int j = 0; j < 16; j++) { JCheckBox jc = (JCheckBox) checkboxList.get(j + (16 * i)); if (jc.isSelected()) { trackList[j] = key; } else { trackList[j] = 0; } } // close inner loop makeTracks(trackList); track.add(makeEvent(176, 1, 127, 0, 16)); } // close outer track.add(makeEvent(192, 9, 1, 0, 15)); try { sequencer.setSequence(sequence); sequencer.setLoopCount(sequencer.LOOP_CONTINUOUSLY); sequencer.start(); sequencer.setTempoInBPM(120); } catch (Exception e) { e.printStackTrace(); } } // close buildTrackAndStart method
public Sequencer(int instrument, int tempo) { // Set up initial settings for the sequencer this.instrument = instrument; this.tempo = tempo; Synthesizer synth; ticks = 0; velocity = 64; // Mid volume try { // Setup values to create sequencer sequence = new Sequence(Sequence.PPQ, 16); sequencer = MidiSystem.getSequencer(); sequencer.open(); synth = MidiSystem.getSynthesizer(); synth.open(); sequencer.getTransmitter().setReceiver(synth.getReceiver()); sequencer.setTempoInBPM(tempo); track = sequence.createTrack(); } catch (InvalidMidiDataException e) { e.printStackTrace(); } catch (MidiUnavailableException e) { e.printStackTrace(); } }
public static Sequence process( final Sequence inputSequence, int track, long startTick, long endTick, int noteOverlap, int minimumRest, Handler loggingHandler) throws InvalidMidiDataException { Sequence outputSequence = new Sequence(inputSequence.getDivisionType(), inputSequence.getResolution()); Track[] inputTracks = inputSequence.getTracks(); SlurBinderA.noteOverlap = noteOverlap; SlurBinderA.minimumRest = minimumRest; slurEndNotes = new HashSet<>(); for (int trackI = 0; trackI < inputTracks.length; trackI++) { Track inputTrack = inputTracks[trackI]; Track outputTrack = outputSequence.createTrack(); if (trackI == track) { handleTrack(outputTrack, inputTrack, startTick, endTick); } else { for (int eventI = 0; eventI < inputTrack.size(); eventI++) { MidiEvent midEvent = inputTrack.get(eventI); outputTrack.add(midEvent); } } } return outputSequence; }
public void play(int instrument, int note) { try { Sequencer player = MidiSystem.getSequencer(); player.open(); Sequence seq = new Sequence(Sequence.PPQ, 4); Track track = seq.createTrack(); MidiEvent event = null; ShortMessage first = new ShortMessage(); first.setMessage(192, 1, instrument, 0); MidiEvent changeInstrument = new MidiEvent(first, 1); track.add(changeInstrument); ShortMessage a = new ShortMessage(); a.setMessage(144, 1, note, 100); MidiEvent noteOn = new MidiEvent(a, 1); track.add(noteOn); ShortMessage b = new ShortMessage(); b.setMessage(128, 1, note, 100); MidiEvent noteOff = new MidiEvent(b, 16); track.add(noteOff); player.setSequence(seq); player.start(); } catch (Exception ex) { ex.printStackTrace(); } } // close play
public void los() { guiErstellen(); try { Sequencer sequencer = MidiSystem.getSequencer(); sequencer.open(); sequencer.addControllerEventListener(ml, new int[] {127}); Sequence seq = new Sequence(Sequence.PPQ, 4); Track track = seq.createTrack(); int r = 0; for (int i = 0; i < 60; i += 4) { r = (int) ((Math.random() * 50) + 1); track.add(eventErzeugen(144, 1, r, 100, i)); track.add(eventErzeugen(176, 1, 127, 0, i)); track.add(eventErzeugen(128, 1, r, 100, i + 2)); } sequencer.setSequence(seq); sequencer.setTempoInBPM(120); sequencer.start(); Thread.sleep(5000); sequencer.close(); } catch (Exception ex) { ex.printStackTrace(); } }
/** * @param key is the note that this starts with. 60 is middle C. * @param tempo is measured in beats per second */ public MidiFileGenerator(int key, int tempo, int resolution) throws MidiUnavailableException, InvalidMidiDataException { this.resolution = resolution; Sequence sequence = new Sequence(Sequence.PPQ, resolution); track = sequence.createTrack(); // makeSong(key); sequencer = MidiSystem.getSequencer(); sequencer.open(); sequencer.setSequence(sequence); sequencer.setTempoInBPM(tempo); }
public void setUpMidi() { try { sequencer = MidiSystem.getSequencer(); sequencer.open(); sequence = new Sequence(Sequence.PPQ, 4); track = sequence.createTrack(); sequencer.setTempoInBPM(120); } catch (Exception e) { e.printStackTrace(); } } // close method
public Sequence getSequence(InputStream inputStream) throws InvalidMidiDataException, IOException { MidiFileFormat midiFileFormat = getMidiFileFormat(inputStream); Sequence sequence = new Sequence(midiFileFormat.getDivisionType(), midiFileFormat.getResolution()); DataInputStream dataInputStream = new DataInputStream(inputStream); int nNumTracks = ((TMidiFileFormat) midiFileFormat).getTrackCount(); for (int nTrack = 0; nTrack < nNumTracks; nTrack++) { Track track = sequence.createTrack(); readTrack(dataInputStream, track); } return sequence; }
public void go() { try { sq.open(); Sequence seq = new Sequence(Sequence.PPQ, 4); Track track = seq.createTrack(); track.add(addNote(144, 9, 56, 100, 1)); track.add(addNote(128, 9, 56, 100, 4)); sq.setSequence(seq); sq.setTempoInBPM(tempo); sq.setLoopCount(Sequencer.LOOP_CONTINUOUSLY); sq.start(); } catch (Exception ex) { ex.printStackTrace(); } }
public boolean createSequence() { try { // Create a sequence with microsecond timing resolution // (Although this doesn't seem to have any effect on tick resolution - ticks in MidiEvents // always microsecond fSequence = new Sequence(Sequence.SMPTE_25, 40000); // Create track and add program change to bell sound Track track = fSequence.createTrack(); // track.add(createBankChange(1, 0)); track.add(createProgramChange(14, 0)); } catch (InvalidMidiDataException e) { System.out.println("Error creating sequence: " + e); return false; } return true; }
public void actionPerformed(ActionEvent ev) { try { // make (and open) a sequencer, make a sequence and track Sequencer sequencer = MidiSystem.getSequencer(); sequencer.open(); sequencer.addControllerEventListener(myPanel, new int[] {127}); Sequence seq = new Sequence(Sequence.PPQ, 4); Track track = seq.createTrack(); // now make two midi events (containing a midi message) for (int i = 0; i < 100; i += 4) { int rNum = (int) ((Math.random() * 50) + 1); if (rNum < 38) { // so now only do it if num <38 (75% of the time) track.add(makeEvent(144, 1, rNum, 100, i)); track.add(makeEvent(176, 1, 127, 0, i)); track.add(makeEvent(128, 1, rNum, 100, i + 2)); } } // end loop // add the events to the track // add the sequence to the sequencer, set timing, and start sequencer.setSequence(seq); sequencer.start(); sequencer.setTempoInBPM(220); } catch (Exception ex) { ex.printStackTrace(); } } // close actionperformed
/** * Import a song into the MidiExporter to play or export. * * @param song The Song to convert to MIDI */ public void importSong(Song song) { // Fetch important data from the song. // Meaning, metadata and the song itself. Beat[] beats = song.getBeatArray(); this.song = song; // Create a track for each voice. int numVoices = song.getNumVoices(); for (int voiceCount = 0; voiceCount < numVoices; voiceCount++) { sequence.createTrack(); } // Iterate through each beat, adding each note to the corresponding // track. Track[] tracks = sequence.getTracks(); for (int beat = 0; beat < beats.length; beat++) { Note[] firstHalf = beats[beat].getNotesFirstHalf(); Note[] secondHalf = beats[beat].getNotesSecondHalf(); // Iterate through each note in the beat, adding it to the // corresponding track. for (int note = 0; note < firstHalf.length; note++) { if (firstHalf[note] == secondHalf[note]) { createNote(firstHalf[note], 2 * beat, 2, tracks[note]); } else { createNote(firstHalf[note], 2 * beat, 1, tracks[note]); createNote(secondHalf[note], 2 * beat + 1, 1, tracks[note]); } // if/else } // for } // for try { setUpSequencer(); } catch (MidiUnavailableException ex) { System.out.println("Unable to set up sequencer"); // do nothing } }
public boolean writeSequence(NoteList noteList) { this.noteList = noteList; toneMap = toneMapFrame.getToneMap(); timeSet = toneMap.getTimeSet(); pitchSet = toneMap.getPitchSet(); timeRange = timeSet.getRange(); pitchRange = pitchSet.getRange(); if (!buildNoteSequence()) return false; try { sequence = new Sequence(Sequence.PPQ, 10); } catch (Exception ex) { ex.printStackTrace(); return false; } track = sequence.createTrack(); startTime = System.currentTimeMillis(); // add a program change right at the beginning of // the track for the current instrument createEvent(PROGRAM, cc.program + 1, 1); for (int i = 0; i < noteSequence.size(); i++) { noteSequenceElement = noteSequence.get(i); if (noteSequenceElement.state == ON) if (!createEvent(NOTEON, noteSequenceElement.note, noteSequenceElement.tick)) return false; if (noteSequenceElement.state == OFF) if (!createEvent(NOTEOFF, noteSequenceElement.note, noteSequenceElement.tick)) return false; } return true; }
public void los() { guiErstellen(); try { // einen Sequencer erzeugen (und öffnen), // eine Sequence und einen Track erzeugen Sequencer sequencer = MidiSystem.getSequencer(); sequencer.open(); sequencer.addControllerEventListener(ml, new int[] {127}); Sequence seq = new Sequence(Sequence.PPQ, 4); Track track = seq.createTrack(); // jetzt werden MidiEvents (die eine // MidiMessage enthalten) erzeugt int r = 0; for (int i = 0; i < 60; i += 4) { r = (int) ((Math.random() * 50) + 1); track.add(eventErzeugen(144, 1, r, 100, i)); track.add(eventErzeugen(176, 1, 127, 0, i)); track.add(eventErzeugen(128, 1, r, 100, i + 2)); } // Ende der for-Schleife // Hinzufügen der Events zum Track und der Sequence // zum Sequencer, Setzen der Zeiten und Starten sequencer.setSequence(seq); sequencer.start(); sequencer.setTempoInBPM(120); } catch (Exception ex) { ex.printStackTrace(); } } // Methode los schließen
/** * Converts the given tune to a midi sequence. * * @param tune The tune to be converted. * @return The midi sequence of the tune. */ public Sequence toMidiSequence(Tune tune) { Sequence sequence = null; try { if (instrument == null) { Synthesizer synth = MidiSystem.getSynthesizer(); synth.open(); try { setInstrument(synth.getAvailableInstruments()[0]); } finally { synth.close(); } } // Sequence in ticks per quarter note : PPQ = Pulse Per Quarter Note // Resolution is expressed in ticks per beat. // Last parameter "1" is the number of tracks. sequence = new Sequence(Sequence.PPQ, SEQUENCE_RESOLUTION, 1); // Set the instrument on channel 0 ShortMessage sm = new ShortMessage(); sm.setMessage(ShortMessage.PROGRAM_CHANGE, 0, instrument.getPatch().getProgram(), 0); Track track = sequence.createTrack(); track.add(new MidiEvent(sm, 0)); // long trackLengthInTicks = track.ticks(); int lastRepeatOpen = -1; int repeatNumber = 1; boolean inWrongEnding = false; KeySignature tuneKey = null; KeySignature currentKey = null; Hashtable partsKey = new Hashtable(); long elapsedTime = 0; NoteAbstract[] graceNotes = null; Music staff = tune.getMusicForAudioRendition(); Iterator it = staff.getVoices().iterator(); while (it.hasNext()) { Voice voice = (Voice) it.next(); int i = 0; // StaffItem iterator while (i < voice.size()) { if (!inWrongEnding) { // ==================================================================== TEMPO if (voice.elementAt(i) instanceof abc.notation.Tempo) { addTempoEventsFor( track, elapsedTime, getMidiMessagesFor((Tempo) voice.elementAt(i))); // , trackLengthInTicks)); } else /*if (voice.elementAt(i) instanceof abc.notation.PartLabel) { //Imagine... part A in Gmaj, B in Amin //in tune you have K:G, P:A, ... P:B, K:Am //if you have part order ABA, when you return to A //you stay in Amin. This stores the tuneKey when a //new part appear, and restitute it when part is played again abc.notation.PartLabel pl = (abc.notation.PartLabel) voice.elementAt(i); if (partsKey.get(pl.getLabel()+"") == null) { partsKey.put(pl.getLabel()+"", tuneKey); } else { tuneKey = (KeySignature) partsKey.get(pl.getLabel()+""); } } else*/ // ==================================================================== KEY SIGNATURE if (voice.elementAt(i) instanceof abc.notation.KeySignature) { tuneKey = (KeySignature) (voice.elementAt(i)); currentKey = new KeySignature(tuneKey.getAccidentals()); } else // ==================================================================== NOTE // Notes ending ties should be ignored. Already taken into // account in getNoteLengthInTicks(Note) if (voice.elementAt(i) instanceof abc.notation.Note && !((abc.notation.Note) voice.elementAt(i)).isEndingTie()) { Note note = (Note) voice.elementAt(i); long noteDuration; boolean fermata = false; Vector decorationNotes = new Vector(); if (note.hasGeneralGracing() || note.hasDecorations()) { Decoration[] d = note.getDecorations(); for (int j = 0; j < d.length; j++) { switch (d[j].getType()) { case Decoration.FERMATA: case Decoration.FERMATA_INVERTED: fermata = true; break; case Decoration.LOWERMORDENT: case Decoration.UPPERMORDENT: case Decoration.DOUBLE_LOWER_MORDANT: case Decoration.DOUBLE_UPPER_MORDANT: case Decoration.TRILL: case Decoration.TURN: // GRUPETTO_UP case Decoration.TURN_INVERTED: // GRUPETTO_DOWN case Decoration.TURNX: case Decoration.TURNX_INVERTED: Note n = new Note(note.getHeight()); n.setAccidental(note.getAccidental(currentKey)); Note o = new Interval(Interval.SECOND, Interval.MAJOR, Interval.UPWARD) .calculateSecondNote(n); Note m = new Interval(Interval.SECOND, Interval.MAJOR, Interval.DOWNWARD) .calculateSecondNote(n); // TODO ornament templates: regular, musette, balkan... // n.setStrictDuration(Note.SIXTEENTH); // o.setDuration((short)(Note.EIGHTH+Note.SIXTEENTH)); o.setAccidental(Accidental.NONE); m.setAccidental(Accidental.NONE); n.setStrictDuration(Note.THIRTY_SECOND); m.setStrictDuration(Note.THIRTY_SECOND); o.setStrictDuration(Note.THIRTY_SECOND); switch (d[j].getType()) { case Decoration.DOUBLE_LOWER_MORDANT: decorationNotes.add(n); decorationNotes.add(m); case Decoration.LOWERMORDENT: decorationNotes.add(n); decorationNotes.add(m); break; case Decoration.DOUBLE_UPPER_MORDANT: case Decoration.TRILL: decorationNotes.add(n); decorationNotes.add(o); case Decoration.UPPERMORDENT: decorationNotes.add(n); decorationNotes.add(o); break; case Decoration.TURNX_INVERTED: case Decoration.TURN: decorationNotes.add(o); decorationNotes.add(n); decorationNotes.add(m); break; case Decoration.TURNX: case Decoration.TURN_INVERTED: decorationNotes.add(m); decorationNotes.add(n); decorationNotes.add(o); } break; } } // currently not used // future use: playing rolls, slides, etc. } long graceNotesDuration = 0; if (note.hasGracingNotes() || (decorationNotes.size() > 0)) { graceNotes = note.getGracingNotes(); // gracing are eighth note for graphical rendition // and because that's it in the parser // adapt duration to note length int divisor = 1; if (note.getStrictDuration() >= Note.HALF) divisor = 1; // grace is an eighth else if (note.getStrictDuration() >= Note.QUARTER) divisor = 2; // 16th else if (note.getStrictDuration() >= Note.EIGHTH) divisor = 4; // 32nd else divisor = 8; // 64th if (note.hasGracingNotes()) { for (int j = 0; j < graceNotes.length; j++) { noteDuration = getNoteLengthInTicks(graceNotes[j], staff) / divisor; graceNotesDuration += noteDuration; if (graceNotes[j] instanceof Note) playNote( (Note) graceNotes[j], i, currentKey, elapsedTime, noteDuration, track); else playMultiNote( (MultiNote) graceNotes[j], i, currentKey, /*elapsedTime,*/ noteDuration, track, staff); elapsedTime += noteDuration; } } for (int j = 0; j < decorationNotes.size(); j++) { noteDuration = getNoteLengthInTicks((Note) decorationNotes.elementAt(j), staff); graceNotesDuration += noteDuration; playNote( (Note) decorationNotes.elementAt(j), i, currentKey, elapsedTime, noteDuration, track); elapsedTime += noteDuration; } } // The note duration if the note isn't part of a tuplet. noteDuration = getNoteLengthInTicks(note, staff) - graceNotesDuration; if (noteDuration <= 0) // in case of too much grace notes noteDuration = getNoteLengthInTicks(note, staff); if (fermata) noteDuration *= 2; playNote(note, i, currentKey, elapsedTime, noteDuration, track); elapsedTime += noteDuration; } else // ==================================================================== MULTI NOTE if ((voice.elementAt(i) instanceof abc.notation.MultiNote)) { MultiNote multiNote = (MultiNote) voice.elementAt(i); playMultiNote(multiNote, i, currentKey, elapsedTime, track, staff); elapsedTime += getNoteLengthInTicks(multiNote, staff); } } // endif (!inWrongEnding) // ====================================================================== REPEAT BAR LINE if (voice.elementAt(i) instanceof abc.notation.RepeatBarLine) { RepeatBarLine bar = (RepeatBarLine) voice.elementAt(i); if (repeatNumber < bar.getRepeatNumbers()[0] && lastRepeatOpen != -1) { repeatNumber++; i = lastRepeatOpen; } else if (repeatNumber > bar.getRepeatNumbers()[0]) inWrongEnding = true; else inWrongEnding = false; } else // ====================================================================== BAR LINE OPEN / // CLOSE if (voice.elementAt(i) instanceof abc.notation.BarLine) { // currentKey = new KeySignature(tuneKey.getAccidentals()); switch (((BarLine) (voice.elementAt(i))).getType()) { case BarLine.SIMPLE: break; case BarLine.REPEAT_OPEN: lastRepeatOpen = i; repeatNumber = 1; break; case BarLine.REPEAT_CLOSE: if (repeatNumber < 2 && lastRepeatOpen != -1) { repeatNumber++; i = lastRepeatOpen; } else { repeatNumber = 1; lastRepeatOpen = -1; } break; // TODO case BarLine.BEGIN_AND_END_REPEAT } } // Whatever kind of bar line it is if (voice.elementAt(i) instanceof abc.notation.BarLine) { currentKey = new KeySignature(tuneKey.getAccidentals()); } i++; } // end while each element in voice } // end while each voice in music } catch (InvalidMidiDataException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return sequence; }
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; }