Beispiel #1
0
 /**
  * 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;
 }
Beispiel #2
0
  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 + ")");
    }
  }
Beispiel #3
0
 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);
   }
 }
Beispiel #4
0
  private static Sequence convert(
      List<FileAndData> filesData,
      boolean useLotroInstruments,
      Map<Integer, LotroInstrument> instrumentOverrideMap,
      AbcInfo abcInfo,
      final boolean enableLotroErrors,
      final boolean stereo)
      throws ParseException {
    if (abcInfo == null) abcInfo = new AbcInfo();
    else abcInfo.reset();

    TuneInfo info = new TuneInfo();
    Sequence seq = null;
    Track track = null;

    int channel = 0;
    int trackNumber = 0;
    int noteDivisorChangeLine = 0;

    long chordStartTick = 0;
    long chordEndTick = 0;
    long PPQN = 0;
    Map<Integer, Integer> tiedNotes =
        new HashMap<Integer, Integer>(); // noteId => (line << 16) | column
    Map<Integer, Integer> accidentals = new HashMap<Integer, Integer>(); // noteId => deltaNoteId

    List<MidiEvent> noteOffEvents = new ArrayList<MidiEvent>();
    for (FileAndData fileAndData : filesData) {
      String fileName = fileAndData.file.getName();
      int lineNumber = 0;
      int partStartLine = 0;
      for (String line : fileAndData.lines) {
        lineNumber++;

        // Handle extended info
        Matcher xInfoMatcher = XINFO_PATTERN.matcher(line);
        if (xInfoMatcher.matches()) {
          AbcField field =
              AbcField.fromString(
                  xInfoMatcher.group(XINFO_FIELD) + xInfoMatcher.group(XINFO_COLON));

          if (field == AbcField.TEMPO) {
            try {
              info.addTempoEvent(chordStartTick, xInfoMatcher.group(XINFO_VALUE).trim());
            } catch (IllegalArgumentException e) {
              // Apparently that wasn't actually a tempo change
            }
          } else if (field != null) {
            String value = xInfoMatcher.group(XINFO_VALUE).trim();

            abcInfo.setExtendedMetadata(field, value);

            if (field == AbcField.PART_NAME) {
              info.setTitle(value, true);
              abcInfo.setPartName(trackNumber, value, true);
            }
          }

          continue;
        }

        int comment = line.indexOf('%');
        if (comment >= 0) line = line.substring(0, comment);
        if (line.trim().length() == 0) continue;

        int chordSize = 0;

        Matcher infoMatcher = INFO_PATTERN.matcher(line);
        if (infoMatcher.matches()) {
          char type = Character.toUpperCase(infoMatcher.group(INFO_TYPE).charAt(0));
          String value = infoMatcher.group(INFO_VALUE).trim();

          abcInfo.setMetadata(type, value);

          try {
            switch (type) {
              case 'X':
                for (int lineAndColumn : tiedNotes.values()) {
                  throw new ParseException(
                      "Tied note does not connect to another note",
                      fileName,
                      lineAndColumn >>> 16,
                      lineAndColumn & 0xFFFF);
                }

                accidentals.clear();
                noteOffEvents.clear();

                info.newPart(Integer.parseInt(value));
                trackNumber++;
                partStartLine = lineNumber;
                chordStartTick = 0;
                abcInfo.setPartNumber(trackNumber, info.getPartNumber());
                track = null; // Will create a new track after the header is done
                if (instrumentOverrideMap != null
                    && instrumentOverrideMap.containsKey(trackNumber)) {
                  info.setInstrument(instrumentOverrideMap.get(trackNumber));
                }
                break;
              case 'T':
                if (track != null)
                  throw new ParseException(
                      "Can't specify the title in the middle of a part", fileName, lineNumber, 0);

                info.setTitle(value, false);
                abcInfo.setPartName(trackNumber, value, false);
                if (instrumentOverrideMap == null
                    || !instrumentOverrideMap.containsKey(trackNumber)) {
                  info.setInstrument(TuneInfo.findInstrumentName(value, info.getInstrument()));
                }
                break;
              case 'K':
                info.setKey(value);
                break;
              case 'L':
                info.setNoteDivisor(value);
                noteDivisorChangeLine = lineNumber;
                break;
              case 'M':
                info.setMeter(value);
                noteDivisorChangeLine = lineNumber;
                break;
              case 'Q':
                {
                  int tempo = info.getPrimaryTempoBPM();
                  info.setPrimaryTempoBPM(value);
                  if (seq != null && (info.getPrimaryTempoBPM() != tempo)) {
                    throw new ParseException(
                        "The tempo must be the same for all parts of the song",
                        fileName,
                        lineNumber);
                  }
                  break;
                }
            }
          } catch (IllegalArgumentException e) {
            throw new ParseException(
                e.getMessage(), fileName, lineNumber, infoMatcher.start(INFO_VALUE));
          }
        } else {
          // The line contains notes

          if (trackNumber == 0) {
            // This ABC file doesn't have an "X:" line before notes. Tsk tsk.
            trackNumber = 1;
            if (instrumentOverrideMap != null && instrumentOverrideMap.containsKey(trackNumber)) {
              info.setInstrument(instrumentOverrideMap.get(trackNumber));
            }
          }

          if (seq == null) {
            try {
              PPQN = info.getPpqn();
              seq = new Sequence(Sequence.PPQ, (int) PPQN);

              abcInfo.setPrimaryTempoBPM(info.getPrimaryTempoBPM());

              // Create track 0, which will later be filled with the
              // tempo events and song metadata (title, etc.)
              seq.createTrack();

              abcInfo.setPartNumber(0, 0);
              abcInfo.setPartName(0, info.getTitle(), false);

              track = null;
            } catch (InvalidMidiDataException mde) {
              throw new ParseException("Midi Error: " + mde.getMessage(), fileName);
            }
          }

          if (track == null) {
            channel = getTrackChannel(seq.getTracks().length);
            if (channel > MidiConstants.CHANNEL_COUNT - 1)
              throw new ParseException(
                  "Too many parts (max = " + (MidiConstants.CHANNEL_COUNT - 1) + ")",
                  fileName,
                  partStartLine);
            track = seq.createTrack();
            track.add(
                MidiFactory.createProgramChangeEvent(
                    info.getInstrument().midiProgramId, channel, 0));
            if (useLotroInstruments)
              track.add(MidiFactory.createChannelVolumeEvent(MidiConstants.MAX_VOLUME, channel, 1));
            abcInfo.setPartInstrument(trackNumber, info.getInstrument());
          }

          Matcher m = NOTE_PATTERN.matcher(line);
          int i = 0;
          boolean inChord = false;
          Tuplet tuplet = null;
          int brokenRhythmNumerator = 1; // The numerator of the note after the broken rhythm sign
          int brokenRhythmDenominator =
              1; // The denominator of the note after the broken rhythm sign
          while (true) {
            boolean found = m.find(i);
            int parseEnd = found ? m.start() : line.length();
            // Parse anything that's not a note
            for (; i < parseEnd; i++) {
              char ch = line.charAt(i);
              if (Character.isWhitespace(ch)) {
                if (inChord)
                  throw new ParseException(
                      "Unexpected whitespace inside a chord", fileName, lineNumber, i);
                continue;
              }

              switch (ch) {
                case '[': // Chord start
                  if (inChord) {
                    throw new ParseException(
                        "Unexpected '" + ch + "' inside a chord", fileName, lineNumber, i);
                  }

                  if (brokenRhythmDenominator != 1 || brokenRhythmNumerator != 1) {
                    throw new ParseException(
                        "Can't have broken rhythm (< or >) within a chord",
                        fileName,
                        lineNumber,
                        i);
                  }

                  chordSize = 0;
                  inChord = true;
                  break;

                case ']': // Chord end
                  if (!inChord) {
                    throw new ParseException("Unexpected '" + ch + "'", fileName, lineNumber, i);
                  }
                  inChord = false;
                  chordStartTick = chordEndTick;
                  break;

                case '|': // Bar line
                  if (inChord) {
                    throw new ParseException(
                        "Unexpected '" + ch + "' inside a chord", fileName, lineNumber, i);
                  }

                  if (trackNumber == 1) abcInfo.addBar(chordStartTick);

                  accidentals.clear();
                  if (i + 1 < line.length() && line.charAt(i + 1) == ']') {
                    i++; // Skip |]
                  } else if (trackNumber == 1) {
                    abcInfo.addBar(chordStartTick);
                  }
                  break;

                case '+':
                  {
                    int j = line.indexOf('+', i + 1);
                    if (j < 0) {
                      throw new ParseException("There is no matching '+'", fileName, lineNumber, i);
                    }
                    try {
                      info.setDynamics(line.substring(i + 1, j));
                    } catch (IllegalArgumentException iae) {
                      throw new ParseException("Unsupported +decoration+", fileName, lineNumber, i);
                    }

                    if (enableLotroErrors && inChord) {
                      throw new LotroParseException(
                          "Can't include a +decoration+ inside a chord", fileName, lineNumber, i);
                    }

                    i = j;
                    break;
                  }

                case '(':
                  // Tuplet or slur start
                  if (i + 1 < line.length() && Character.isDigit(line.charAt(i + 1))) {
                    // If it has a digit following it, it's a tuplet
                    if (tuplet != null)
                      throw new ParseException(
                          "Unexpected '" + ch + "' before end of tuplet", fileName, lineNumber, i);

                    try {
                      for (int j = i + 1; j < line.length(); j++) {
                        if (line.charAt(i) != ':' && !Character.isDigit(line.charAt(i))) {
                          tuplet = new Tuplet(line.substring(i + 1, j + 1), info.isCompoundMeter());
                          i = j;
                          break;
                        }
                      }
                    } catch (IllegalArgumentException e) {
                      throw new ParseException("Invalid tuplet", fileName, lineNumber, i);
                    }
                  } else {
                    // Otherwise it's a slur, which LotRO conveniently ignores
                    if (inChord) {
                      throw new ParseException(
                          "Unexpected '" + ch + "' inside a chord", fileName, lineNumber, i);
                    }
                  }
                  break;

                case ')':
                  // End of a slur, ignore
                  if (inChord) {
                    throw new ParseException(
                        "Unexpected '" + ch + "' inside a chord", fileName, lineNumber, i);
                  }
                  break;

                case '\\':
                  // Ignore backslashes
                  break;

                default:
                  throw new ParseException(
                      "Unknown/unexpected character '" + ch + "'", fileName, lineNumber, i);
              }
            }

            if (i >= line.length()) break;

            // The matcher might find +f+, +ff+, or +fff+ and think it's a note
            if (i > m.start()) continue;

            if (inChord) chordSize++;

            if (enableLotroErrors && inChord && chordSize > AbcConstants.MAX_CHORD_NOTES) {
              throw new LotroParseException(
                  "Too many notes in a chord", fileName, lineNumber, m.start());
            }

            // Parse the note
            int numerator;
            int denominator;

            numerator =
                (m.group(NOTE_LEN_NUMER) == null) ? 1 : Integer.parseInt(m.group(NOTE_LEN_NUMER));
            String denom = m.group(NOTE_LEN_DENOM);
            if (denom == null) denominator = 1;
            else if (denom.equals("/")) denominator = 2;
            else if (denom.equals("//")) denominator = 4;
            else denominator = Integer.parseInt(denom.substring(1));

            String brokenRhythm = m.group(NOTE_BROKEN_RHYTHM);
            if (brokenRhythm != null) {
              if (brokenRhythmDenominator != 1 || brokenRhythmNumerator != 1) {
                throw new ParseException(
                    "Invalid broken rhythm: " + brokenRhythm,
                    fileName,
                    lineNumber,
                    m.start(NOTE_BROKEN_RHYTHM));
              }
              if (inChord) {
                throw new ParseException(
                    "Can't have broken rhythm (< or >) within a chord",
                    fileName,
                    lineNumber,
                    m.start(NOTE_BROKEN_RHYTHM));
              }
              if (m.group(NOTE_TIE) != null) {
                throw new ParseException(
                    "Tied notes can't have broken rhythms (< or >)",
                    fileName,
                    lineNumber,
                    m.start(NOTE_BROKEN_RHYTHM));
              }

              int factor = 1 << brokenRhythm.length();

              if (brokenRhythm.charAt(0) == '>') {
                numerator *= 2 * factor - 1;
                denominator *= factor;
                brokenRhythmDenominator = factor;
              } else {
                brokenRhythmNumerator = 2 * factor - 1;
                brokenRhythmDenominator = factor;
                denominator *= factor;
              }
            } else {
              numerator *= brokenRhythmNumerator;
              denominator *= brokenRhythmDenominator;
              brokenRhythmNumerator = 1;
              brokenRhythmDenominator = 1;
            }

            if (tuplet != null) {
              if (!inChord || chordSize == 1) tuplet.r--;
              numerator *= tuplet.q;
              denominator *= tuplet.p;
              if (tuplet.r == 0) tuplet = null;
            }

            // Convert back to the original tempo
            int curTempoBPM = info.getCurrentTempoBPM(chordStartTick);
            int primaryTempoBPM = info.getPrimaryTempoBPM();
            numerator *= curTempoBPM;
            denominator *= primaryTempoBPM;

            // Try to guess if this note is using triplet timing
            if ((denominator % 3 == 0) && (numerator % 3 != 0)) {
              abcInfo.setHasTriplets(true);
            }

            long noteEndTick = chordStartTick + DEFAULT_NOTE_TICKS * numerator / denominator;

            // A chord is as long as its shortest note
            if (chordEndTick == chordStartTick || noteEndTick < chordEndTick)
              chordEndTick = noteEndTick;

            char noteLetter = m.group(NOTE_LETTER).charAt(0);
            String octaveStr = m.group(NOTE_OCTAVE);
            if (octaveStr == null) octaveStr = "";
            if (noteLetter == 'z' || noteLetter == 'x') {
              if (m.group(NOTE_ACCIDENTAL) != null && m.group(NOTE_ACCIDENTAL).length() > 0) {
                throw new ParseException(
                    "Unexpected accidental on a rest",
                    fileName,
                    lineNumber,
                    m.start(NOTE_ACCIDENTAL));
              }
              if (octaveStr.length() > 0) {
                throw new ParseException(
                    "Unexpected octave indicator on a rest",
                    fileName,
                    lineNumber,
                    m.start(NOTE_OCTAVE));
              }
            } else {
              int octave = Character.isUpperCase(noteLetter) ? 3 : 4;
              if (octaveStr.indexOf('\'') >= 0) octave += octaveStr.length();
              else if (octaveStr.indexOf(',') >= 0) octave -= octaveStr.length();

              int noteId;
              int lotroNoteId;

              lotroNoteId =
                  noteId =
                      (octave + 1) * 12 + CHR_NOTE_DELTA[Character.toLowerCase(noteLetter) - 'a'];
              if (!useLotroInstruments) noteId += 12 * info.getInstrument().octaveDelta;

              if (m.group(NOTE_ACCIDENTAL) != null) {
                if (m.group(NOTE_ACCIDENTAL).startsWith("_"))
                  accidentals.put(noteId, -m.group(NOTE_ACCIDENTAL).length());
                else if (m.group(NOTE_ACCIDENTAL).startsWith("^"))
                  accidentals.put(noteId, m.group(NOTE_ACCIDENTAL).length());
                else if (m.group(NOTE_ACCIDENTAL).equals("=")) accidentals.put(noteId, 0);
              }

              int noteDelta;
              if (accidentals.containsKey(noteId)) {
                noteDelta = accidentals.get(noteId);
              } else {
                // Use the key signature to determine the accidental
                noteDelta = info.getKey().getDefaultAccidental(noteId).deltaNoteId;
              }
              lotroNoteId += noteDelta;
              noteId += noteDelta;

              if (enableLotroErrors && lotroNoteId < Note.MIN_PLAYABLE.id)
                throw new LotroParseException("Note is too low", fileName, lineNumber, m.start());
              else if (enableLotroErrors && lotroNoteId > Note.MAX_PLAYABLE.id)
                throw new LotroParseException("Note is too high", fileName, lineNumber, m.start());

              if (info.getInstrument() == LotroInstrument.COWBELL
                  || info.getInstrument() == LotroInstrument.MOOR_COWBELL) {
                if (useLotroInstruments) {
                  // Randomize the noteId unless it's part of a note tie
                  if (m.group(NOTE_TIE) == null && !tiedNotes.containsKey(noteId)) {
                    int min = info.getInstrument().lowestPlayable.id;
                    int max = info.getInstrument().highestPlayable.id;
                    lotroNoteId = noteId = min + (int) (Math.random() * (max - min));
                  }
                } else {
                  noteId = (info.getInstrument() == LotroInstrument.COWBELL) ? 76 : 71;
                  lotroNoteId = 71;
                }
              }

              // Check for overlapping notes, and remove extra note off events
              Iterator<MidiEvent> noteOffIter = noteOffEvents.iterator();
              while (noteOffIter.hasNext()) {
                MidiEvent evt = noteOffIter.next();
                if (evt.getTick() <= chordStartTick) {
                  noteOffIter.remove();
                  continue;
                }

                int noteOffId = ((ShortMessage) evt.getMessage()).getData1();
                if (noteOffId == noteId) {
                  track.remove(evt);
                  evt.setTick(chordStartTick);
                  track.add(evt);
                  noteOffIter.remove();
                  break;
                }
              }

              if (!tiedNotes.containsKey(noteId)) {
                if (info.getPpqn() != PPQN) {
                  throw new ParseException(
                      "The default note length must be the same for all parts of the song",
                      fileName,
                      noteDivisorChangeLine);
                }
                track.add(
                    MidiFactory.createNoteOnEventEx(
                        noteId,
                        channel,
                        info.getDynamics().getVol(useLotroInstruments),
                        chordStartTick));
              }

              if (m.group(NOTE_TIE) != null) {
                int lineAndColumn = (lineNumber << 16) | m.start();
                tiedNotes.put(noteId, lineAndColumn);
              } else {
                double MPQN = MidiUtils.convertTempo(curTempoBPM);
                double lengthMicros = (noteEndTick - chordStartTick) * MPQN / PPQN;

                if (enableLotroErrors && lengthMicros < AbcConstants.SHORTEST_NOTE_MICROS) {
                  throw new LotroParseException(
                      "Note's duration is too short", fileName, lineNumber, m.start());
                } else if (enableLotroErrors && lengthMicros > AbcConstants.LONGEST_NOTE_MICROS) {
                  throw new LotroParseException(
                      "Note's duration is too long", fileName, lineNumber, m.start());
                }

                // Stringed instruments, drums, and woodwind breath sounds always play the
                // sound sample in its entirety. Since Gervill doesn't support the SoundFont
                // extension that specifies this, we have to increase the note length.
                // One second should do the trick.
                long noteEndTickTmp = noteEndTick;
                if (useLotroInstruments && !info.getInstrument().isSustainable(lotroNoteId)) {
                  noteEndTickTmp =
                      Math.max(
                          noteEndTick,
                          chordStartTick
                              + Math.round(AbcConstants.ONE_SECOND_MICROS * PPQN / MPQN));
                }
                MidiEvent noteOff =
                    MidiFactory.createNoteOffEventEx(
                        noteId,
                        channel,
                        info.getDynamics().getVol(useLotroInstruments),
                        noteEndTickTmp);
                track.add(noteOff);
                noteOffEvents.add(noteOff);

                tiedNotes.remove(noteId);
              }
            }

            if (!inChord) chordStartTick = noteEndTick;
            i = m.end();
          }

          if (tuplet != null)
            throw new ParseException("Tuplet not finished by end of line", fileName, lineNumber, i);

          if (inChord)
            throw new ParseException("Chord not closed at end of line", fileName, lineNumber, i);

          if (brokenRhythmDenominator != 1 || brokenRhythmNumerator != 1)
            throw new ParseException(
                "Broken rhythm unfinished at end of line", fileName, lineNumber, i);
        }
      }

      if (seq == null) throw new ParseException("The file contains no notes", fileName, lineNumber);

      for (int lineAndColumn : tiedNotes.values()) {
        throw new ParseException(
            "Tied note does not connect to another note",
            fileName,
            lineAndColumn >>> 16,
            lineAndColumn & 0xFFFF);
      }
    }

    PanGenerator pan = null;
    if (stereo && trackNumber > 1) pan = new PanGenerator();

    Track[] tracks = seq.getTracks();

    // Add tempo events
    for (Map.Entry<Long, Integer> tempoEvent : info.getAllPartsTempoMap().entrySet()) {
      long tick = tempoEvent.getKey();
      int mpq = (int) MidiUtils.convertTempo(tempoEvent.getValue());
      tracks[0].add(MidiFactory.createTempoEvent(mpq, tick));
    }

    // Add name and pan events
    tracks[0].add(MidiFactory.createTrackNameEvent(abcInfo.getTitle()));
    for (int i = 1; i <= trackNumber; i++) {
      tracks[i].add(MidiFactory.createTrackNameEvent(abcInfo.getPartName(i)));

      int panAmount = PanGenerator.CENTER;
      if (pan != null) panAmount = pan.get(abcInfo.getPartInstrument(i), abcInfo.getPartName(i));
      tracks[i].add(MidiFactory.createPanEvent(panAmount, getTrackChannel(i)));
    }

    return seq;
  }