/* * Copyright (C) 2017 たんらる */ package fourthline.mmlTools.parser; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import javax.sound.midi.*; import fourthline.mmlTools.MMLEventList; import fourthline.mmlTools.MMLNoteEvent; import fourthline.mmlTools.MMLScore; import fourthline.mmlTools.MMLTempoEvent; import fourthline.mmlTools.MMLTrack; import fourthline.mmlTools.core.MMLTickTable; import fourthline.mmlTools.core.MMLTicks; import fourthline.mmlTools.core.UndefinedTickException; import fourthline.mmlTools.optimizer.MMLStringOptimizer; /** * "*.mid" MIDIファイルの読み込み. */ public final class MidiFile implements IMMLFileParser { private final MMLScore score = new MMLScore(); private int resolution; @Override public MMLScore parse(InputStream istream) throws MMLParseException { try { Sequence seq = MidiSystem.getSequence(istream); resolution = seq.getResolution(); System.out.println(seq.getTracks().length); System.out.println(resolution); System.out.println(seq.getDivisionType()); System.out.println(seq.getMicrosecondLength()); System.out.println(seq.getTickLength()); int count = 1; for (Track track : seq.getTracks()) { parseTrack(track, count++); } } catch (InvalidMidiDataException | IOException e) { e.printStackTrace(); } score.getTempoEventList().addAll(tempoList); try { return score.generateAll(); } catch (UndefinedTickException e) { return score; } } private HashMap<Integer, MMLNoteEvent> activeNoteMap = new HashMap<>(); private ArrayList<MMLNoteEvent> curNoteList = new ArrayList<>(); private ArrayList<MMLTempoEvent> tempoList = new ArrayList<>(); class TrackInfo { String name; int panpot = 64; TrackInfo(int count) { name = "Track"+count; } MMLTrack createMMLTrack() { MMLTrack track = new MMLTrack(); track.setTrackName(name); track.setPanpot(panpot); return track; } } /** * トラックデータパース * @param track * @param count * @throws MMLParseException */ private void parseTrack(Track track, int count) throws MMLParseException { TrackInfo trackInfo = new TrackInfo(count); activeNoteMap.clear(); curNoteList.clear(); System.out.println(" - track -"); System.out.println(track.size()); ArrayList<MidiEvent> midiEventList = new ArrayList<>(track.size()); for (int i = 0; i < track.size(); i++) { midiEventList.add(track.get(i)); } for (MidiEvent event : midiEventList) { MidiMessage msg = event.getMessage(); long tick = convTick( event.getTick() ); if (msg instanceof MetaMessage) { parseMetaMessage((MetaMessage)msg, tick, trackInfo); } else if (msg instanceof ShortMessage) { parseShortMessage((ShortMessage)msg, tick, trackInfo); } else if (msg instanceof SysexMessage) { System.out.println("Sysex"); } else { throw new MMLParseException("Unknown MIDI message."); } } // ノートイベントから重複しないMMLEventListへ. ArrayList<MMLEventList> mmlEventList = createMMLEventList(); // MMLEventListのリストを使ってトラックを生成. System.out.printf(" ###### track tick: %d %d => %d\n", activeNoteMap.size(), curNoteList.size(), mmlEventList.size()); createMMLTrack(mmlEventList, trackInfo); } /** * 整列済みノートイベントからMMLTrackをつくる * @param eventList * @param trackInfo */ private void createMMLTrack(ArrayList<MMLEventList> eventList, TrackInfo trackInfo) { try { while (eventList.size() > 0) { String mml[] = new String[3]; for (int i = 0; i < mml.length; i++) { if (!eventList.isEmpty()) { mml[i] = new MMLStringOptimizer(eventList.get(0).toMMLString(false, false)).toString(); eventList.remove(0); } else { mml[i] = ""; } } MMLTrack track = trackInfo.createMMLTrack(); track.setMML(mml[0], mml[1], mml[2], ""); score.addTrack(track); } } catch (UndefinedTickException e) { e.printStackTrace(); } } /** * 取り込んだノートイベントから重複しないMMLEventListを生成する. * @return */ private ArrayList<MMLEventList> createMMLEventList() { ArrayList<MMLEventList> eventList = new ArrayList<>(); for (MMLNoteEvent noteEvent : curNoteList) { for (MMLEventList e : eventList) { if (!e.isOverlapNote(noteEvent)) { e.addMMLNoteEvent(noteEvent); noteEvent = null; break; } } if (noteEvent != null) { MMLEventList e = new MMLEventList(""); e.addMMLNoteEvent(noteEvent); eventList.add(e); } } return eventList; } /** * メタメッセージ * @param msg * @param tick * @param trackInfo */ private void parseMetaMessage(MetaMessage msg, long tick, TrackInfo trackInfo) { System.out.print(tick+" > "); int type = msg.getType(); byte[] data = msg.getData(); switch (type) { case MMLTempoEvent.META: // テンポ ByteBuffer buf = ByteBuffer.allocate(4); buf.put((byte)0); buf.put(data); int tempo = 60000000/buf.getInt(0); System.out.println("Tempo: "+tempo); new MMLTempoEvent(tempo, (int)tick).appendToListElement(tempoList); break; case 3: // シーケンス名/トラック名 String name = new String(data); System.out.println("Name: "+name); break; case 1: // テキストイベント case 2: // 著作権表示 case 4: // 楽器名 case 5: // 歌詞 case 6: // マーカー case 7: // キューポイント System.out.println("Text: "+new String(data)); break; case 0x58: // 拍子/メトロノーム設定 System.out.printf("met: %d %d %d %d\n", data[0], 1<<data[1], data[2], data[3]); break; default: System.out.printf("Meta: [%x] [%d]\n", type, data.length); break; } } /** * ショートメッセージ * @param msg * @param tick * @param trackInfo * @throws MMLParseException */ private void parseShortMessage(ShortMessage msg, long tick, TrackInfo trackInfo) throws MMLParseException { int command = msg.getCommand(); int channel = msg.getChannel(); int data1 = msg.getData1(); int data2 = msg.getData2(); switch (command) { case ShortMessage.CONTROL_CHANGE: if (data1 == 10) { // panpot trackInfo.panpot = data2; } System.out.printf("control change: [%d] [%d]\n", data1, data2); break; case ShortMessage.NOTE_ON: if (data2 > 0) { int note = data1 - 12; int velocity = data2 / 8; if (!activeNoteMap.containsKey(note)) { MMLNoteEvent noteEvent = new MMLNoteEvent(note, 0, (int)tick, velocity); activeNoteMap.put(note, noteEvent); curNoteList.add(noteEvent); } break; } // data2 == 0 は Note Off. case ShortMessage.NOTE_OFF: int note = data1 - 12; MMLNoteEvent noteEvent = activeNoteMap.get(note); if (noteEvent != null) { tick -= noteEvent.getTickOffset(); if (tick < MMLTicks.minimumTick()) { tick = MMLTicks.minimumTick(); } noteEvent.setTick( (int)tick ); activeNoteMap.remove(note); } break; case ShortMessage.PROGRAM_CHANGE: System.out.printf("program change: [%d] [%d]\n", data1, data2); break; default: System.out.printf("short: [%x] [%d] [%d] [%d]\n", command, channel, data1, data2); } } /** * Tick変換 * @param tick * @return */ private long convTick(long tick) { int min = MMLTicks.minimumTick(); long value = (tick * MMLTickTable.TPQN / resolution) + (min/2); value -= value % min; return value; } public static void main(String[] args) { try { MMLScore score = new MidiFile().parse(new FileInputStream("sample2.mid")); score.generateAll(); } catch (FileNotFoundException | MMLParseException | UndefinedTickException e) { e.printStackTrace(); } } }