/** * Shows an edit midi event dialog. * * @param event The events to be edited. * @return <code>true</code> if the dialog has been shown. */ public static boolean showEditShortMessageEventDialog( MidiEvent event, TrackProxy track, MidiDescriptor midiDescriptor) { MidiEditPanel mep = new MidiEditPanel( track, event, SgEngine.getInstance().getResourceBundle().getString("midi.event.edit.event")); Object[] message = new Object[] {mep}; int option = JOptionPane.showConfirmDialog( getMainFrame(), message, SgEngine.getInstance().getResourceBundle().getString("midi.event.edit.title"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null); if (option == JOptionPane.OK_OPTION && mep.hasChanged()) { try { // create change edit and perform changes Object changeObj = new Object(); ChangeEventsEdit edit = new ChangeEventsEdit(track, new MidiEvent[] {event}, midiDescriptor, null, changeObj); mep.applyChanges(changeObj); edit.perform(); midiDescriptor.getUndoManager().addEdit(edit); } catch (InvalidMidiDataException imdex) { JOptionPane.showMessageDialog( getMainFrame(), imdex.getMessage(), SgEngine.getInstance().getResourceBundle().getString("error.invalidMidiData"), JOptionPane.ERROR_MESSAGE); } } return true; }
public void PlayMidiFile(String name, String filename) { Sequencer seq = null; Transmitter seqTrans = null; Synthesizer synth; Receiver synthRcvr = null; File midiFile = null; try { seq = MidiSystem.getSequencer(); seqTrans = seq.getTransmitter(); synth = MidiSystem.getSynthesizer(); synthRcvr = synth.getReceiver(); midiFile = new File(filename); if (seq == null) { Debug.showMessage("MidiCSD::PlayMidiFile: Sequencer nicht gefunden!"); } else { seq.open(); seqTrans.setReceiver(synthRcvr); Sequence mySeq; mySeq = MidiSystem.getSequence(midiFile); new Player(name, seq, mySeq, synth, m_playing).start(); } } catch (MidiUnavailableException e) { Debug.showException(e, "MidiCSD::PlayMidiFile: MidiUnavailable" + e.getMessage()); } catch (InvalidMidiDataException e) { Debug.showException(e, "MidiCSD::PlayMidiFile: InvalidMidiDataException" + e.getMessage()); } catch (IOException e) { Debug.showException(e, "MidiCSD::PlayMidiFile:IOException (fn:" + filename + ")"); } }
private void tune(final Receiver recv) { try { if (rebasedTuning != null) { for (int i = 0; i < 16; i++) { MidiUtils.sendTunings(recv, i, 0, "african", rebasedTuning); MidiUtils.sendTuningChange(recv, i, 0); } } } catch (final IOException e) { LOG.log(Level.SEVERE, e.getMessage(), e); } catch (final InvalidMidiDataException e) { LOG.log(Level.SEVERE, e.getMessage(), e); } }
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; }