/* * Copyright (C) 2014 たんらる */ package fourthline.mmlTools.parser; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Base64; import java.util.LinkedList; import java.util.List; import java.util.Base64.Decoder; import java.util.zip.CRC32; import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; import fourthline.mabiicco.midi.InstType; import fourthline.mabiicco.midi.MabiDLS; import fourthline.mmlTools.MMLEvent; import fourthline.mmlTools.MMLEventList; import fourthline.mmlTools.MMLScore; import fourthline.mmlTools.MMLTrack; import fourthline.mmlTools.Marker; /** * "*.mml" (3mleさん) のファイルを扱います. */ public final class MMLFile implements IMMLFileParser { private final MMLScore score = new MMLScore(); private String encoding = "Shift_JIS"; // channel sections private LinkedList<String> mmlParts = new LinkedList<>(); private List<Extension3mleTrack> trackList = null; @Override public MMLScore parse(InputStream istream) throws MMLParseException { List<SectionContents> contentsList = SectionContents.makeSectionContentsByInputStream(istream, encoding); if (contentsList.isEmpty()) { throw(new MMLParseException("no contents")); } parseSection(contentsList); if ( (trackList == null) || (trackList.size() == 0) ) { throw new MMLParseException("no track"); } createTrack(); setStartPosition(); return score; } private void parseSection(List<SectionContents> contentsList) throws MMLParseException { for (SectionContents contents : contentsList) { if (contents.getName().equals("[3MLE EXTENSION]")) { trackList = parse3mleExtension(contents.getContents()); } else if (contents.getName().matches("\\[Channel[0-9]*\\]")) { mmlParts.add( contents.getContents() .replaceAll("//.*\n", "\n") .replaceAll("/\\*/?([^/]|[^*]/)*\\*/", "") .replaceAll("[ \t\n]", "") ); } else if (contents.getName().equals("[Settings]")) { parseSettings(contents.getContents()); } } } private void createTrack() { for (Extension3mleTrack track : trackList) { int program = track.getInstrument() - 1; // 3MLEのInstruments番号は1がスタート. String text[] = new String[] { "", "", "" }; for (int i = 0; i < track.getTrackCount(); i++) { text[i] = mmlParts.pop(); } InstType instType = MabiDLS.getInstance().getInstByProgram(program).getType(); MMLTrack mmlTrack; if ( (instType == InstType.VOICE) || (instType == InstType.CHORUS) ) { // 歌パート mmlTrack = new MMLTrack().setMML("", "", "", text[0]); } else { mmlTrack = new MMLTrack().setMML(text[0], text[1], text[2], ""); } score.addTrack(mmlTrack); mmlTrack.setProgram(program); mmlTrack.setPanpot(track.getPanpot()); mmlTrack.setTrackName(track.getTrackName()); } } /** * 再生開始位置を設定します. */ private void setStartPosition() { if (score.getMarkerList().size() == 0) { return; } for (int i = 0; i < trackList.size(); i++) { Extension3mleTrack track = trackList.get(i); MMLTrack mmlTrack = score.getTrack(i); int markerId = track.getStartMarker(); if (markerId > 0) { Marker marker = score.getMarkerList().get(markerId-1); int tickOffset = marker.getTickOffset(); for (MMLEventList eventList : mmlTrack.getMMLEventList()) { MMLEvent.insertTick(eventList.getMMLNoteEventList(), 0, tickOffset); } } } } /** * parse [Settings] contents * @param contents */ private void parseSettings(String contents) { TextParser.text(contents) .pattern("Title=", t -> score.setTitle(t)) .pattern("Source=", t -> score.setAuthor(t)) .pattern("Encoding=", t -> this.encoding = t) .parse(); } /** * [3MLE EXTENSION] をパースし, トラック構成情報を取得します. * @param str [3MLE EXTENSION] セクションのコンテンツ * @return トラック構成情報 */ private List<Extension3mleTrack> parse3mleExtension(String str) throws MMLParseException { StringBuilder sb = new StringBuilder(); long c = 0; for (String s : str.split("\n")) { if (s.startsWith("d=")) { sb.append(s.substring(2)); } else if (s.startsWith("c=")) { c = Long.parseLong(s.substring(2)); } } byte data[] = decode(sb.toString(), c); return parseData(data); } private static byte[] decode(String dSection, long c) throws MMLParseException { CRC32 crc = new CRC32(); crc.update(dSection.getBytes()); if (c != crc.getValue()) { throw new MMLParseException("invalid c="+c+" <> "+crc.getValue()); } Decoder decoder = Base64.getDecoder(); byte b[] = decoder.decode(dSection); int dataLength = ByteBuffer.wrap(b, 0, 4).order(ByteOrder.LITTLE_ENDIAN).getInt(); byte data[] = new byte[dataLength]; try { BZip2CompressorInputStream bz2istream = new BZip2CompressorInputStream(new ByteArrayInputStream(b, 12, b.length-12)); bz2istream.read(data); bz2istream.close(); } catch (IOException e) { e.printStackTrace(); } for (int i = 0; i < dataLength; i++) { System.out.printf("%02x ", data[i]); } System.out.println(); return data; } /** * @param data decompress済みのバイト列 * @return トラック構成情報 */ private List<Extension3mleTrack> parseData(byte data[]) { LinkedList<Extension3mleTrack> trackList = new LinkedList<>(); trackList.add(new Extension3mleTrack(-1, -1, -1, null, 0)); // dummy ByteArrayInputStream istream = new ByteArrayInputStream(data); int b = 0; int hb = 0; while ( (b = istream.read()) != -1) { if ( (hb == 0x12) && (b == 0x10) ) { parseHeader(istream); } else if ( (hb == 0x02) && (b == 0x1c) ) { parseTrack(trackList, istream); } else if ( (hb == 0x09) && ( (b > 0x00) && (b < 0x20) )) { parseMarker(istream); } hb = b; } trackList.removeFirst(); return trackList; } private void parseHeader(ByteArrayInputStream istream) { istream.skip(37); int len = readLEIntValue(istream); istream.skip(len); } private void parseTrack(LinkedList<Extension3mleTrack> trackList, ByteArrayInputStream istream) { // parse Track istream.skip(3); int trackNo = istream.read(); istream.skip(1); // volumn int panpot = istream.read(); istream.skip(5); int startMarker = istream.read(); istream.skip(7); int instrument = istream.read(); istream.skip(3); int group = istream.read(); istream.skip(13); String trackName = readString(istream); System.out.println(trackNo+" "+instrument+" "+trackName); Extension3mleTrack lastTrack = trackList.getLast(); if ( (lastTrack.getGroup() != group) || (lastTrack.getInstrument() != instrument) || (lastTrack.getPanpot() != panpot) || (lastTrack.isLimit())) { // new Track trackList.add(new Extension3mleTrack(instrument, group, panpot, trackName, startMarker)); } else { lastTrack.addTrack(); } } private int readLEIntValue(InputStream istream) { byte b[] = new byte[4]; try { istream.read(b); } catch (IOException e) { e.printStackTrace(); } return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getInt(); } private void parseMarker(ByteArrayInputStream istream) { List<Marker> markerList = score.getMarkerList(); // parse Marker istream.skip(7); int tickOffset = readLEIntValue(istream); istream.skip(4); String name = readString(istream); System.out.println("Marker " + name + "=" + tickOffset); if (markerList != null) { markerList.add(new Marker(name, tickOffset)); } } private String readString(InputStream istream) { ByteArrayOutputStream ostream = new ByteArrayOutputStream(); int b; try { while ( (b = istream.read()) != 0 ) { ostream.write(b); } return new String(ostream.toByteArray(), encoding); } catch (IOException e) { e.printStackTrace(); } return ""; } public static void main(String[] args) { try { String str = "c=3902331007\nd=4wAAAJvYl0oBAAAAQlpoOTFBWSZTWReDTXYAAEH/i/7U0AQCAHgAQAAEAGwIEABAAECAAAoABKAAcivUCaZGmRiAyNqDEgnqRpkPTUZGh5S6QfOGHRg+AfSJE3ebNDxInstECT3owI1yYiuIY5IwTCLAQz1oZyAogJFOhVYmv39cWsLxsbh0MkELhClECHm5wCBjLYz8XckU4UJAXg012A=="; MMLFile mmlFile = new MMLFile(); List<Extension3mleTrack> trackList = mmlFile.parse3mleExtension(str); for (Extension3mleTrack track : trackList) { System.out.println(track); } } catch (MMLParseException e) { e.printStackTrace(); } } }