package jp.sourceforge.ocmml.android; import java.util.ArrayList; import android.util.Log; public class Engine { public static final int MULTIPLY = 32; public static final int MAX_PIPE = 3; public static Sequencer compile(String stringToParse) { try { Engine engine = new Engine(); engine.parse(stringToParse); return engine.mSequencer; } catch (Exception e) { StackTraceElement[] ste = e.getStackTrace(); for (StackTraceElement s : ste) { String className = s.getClassName(); if (className.startsWith("jp.sourceforge.ocmml.android")) Log.e("adMML", className + "." + s.getMethodName() + ": " + s.getLineNumber()); } return null; } } public void parse(String stringToParse) { init(stringToParse); mSequencer = new Sequencer(MULTIPLY); mWarnings = new ArrayList(); mTracks.add(createTrack()); mTracks.add(createTrack()); parseComment(); // TODO: parsing macro // parseMacro(); canonicalize(); parseRepeat(); mIndex = 0; int textLength = mText.length(); while (mIndex < textLength) firstLetterToken(); int trackCount = mTracks.size(); Track track = mTracks.get(trackCount - 1); if (track.getEventCount() == 0) { mTracks.remove(track); trackCount--; } track = mTracks.get(Track.TEMPO_TRACK); track.conductTracks(mTracks); for (int i = Track.TEMPO_TRACK; i < trackCount; i++) { track = mTracks.get(i); if (i > Track.TEMPO_TRACK) { track.recordRest((long) 2000); track.recordClose(); } mSequencer.addTrack(track); } mSequencer.createPipes(mMaxPipe + 1); } private void parseComment() { int start = -1, textLength = mText.length(); mIndex = 0; while (mIndex < textLength) { char c = characterTokenAndNext(); switch (c) { case '/': if (characterToken() == '*') { if (start < 0) start = mIndex - 1; mIndex++; } break; case '*': if (characterToken() == '/') { if (start >= 0) { mText.replace(start, mIndex + 1, ""); mIndex = start; textLength = mText.length(); start = -1; } else mWarnings.add(new Integer(0)); } break; default: break; } } if (start >= 0) mWarnings.add(new Integer(0)); } private void parseRepeat() { ArrayList repeats = new ArrayList(); ArrayList origins = new ArrayList(); ArrayList starts = new ArrayList(); ArrayList ends = new ArrayList(); int nest = -1, textLength = mText.length(); mIndex = 0; while (mIndex < textLength) { char c = characterTokenAndNext(); switch (c) { case '/': if (characterToken() == ':') { mIndex++; ++nest; origins.add(new Integer(mIndex - 2)); repeats.add(new Integer(uintToken(2))); starts.add(new Integer(mIndex)); ends.add(new Integer(-1)); } else if (nest >= 0) { ends.set(nest, new Integer(mIndex - 1)); mText.replace(mIndex, mIndex + 1, ""); textLength = mText.length(); --mIndex; } break; case ':': if (characterToken() == '/' && nest >= 0) { mIndex++; int start = starts.get(nest).intValue(); int end = ends.get(nest).intValue(); int repeat = repeats.get(nest).intValue(); String text = mText.toString(); String contents = text.substring(start, mIndex - 2); StringBuffer newString = new StringBuffer(text.substring(0, origins.get(nest).intValue())); for (int i = 0; i < repeat; i++) if (i < repeat - 1 || end < 0) newString.append(contents); else newString.append(mText.toString().substring(start, end)); int newStringLength = newString.length(); newString.append(mText.toString().substring(mIndex)); mText = newString; mIndex = newStringLength; textLength = newStringLength; origins.remove(nest); repeats.remove(nest); starts.remove(nest); ends.remove(nest); --nest; } break; default: break; } } if (nest >= 0) mWarnings.add(new Integer(0)); } private void firstLetterToken() { Track track; char c = characterTokenAndNext(); switch (c) { case 'c': noteToken(0); break; case 'd': noteToken(2); break; case 'e': noteToken(4); break; case 'f': noteToken(5); break; case 'g': noteToken(7); break; case 'a': noteToken(9); break; case 'b': noteToken(11); break; case 'r': restToken(); break; case 'o': mOctave = Math.min(Math.max(intToken(mOctave), -2), 8); break; case 'v': mVelocityDetail = false; mVelocity = Math.min(Math.max( intToken((mVelocity - 7) / 8) * 8 + 7, 0), 127); break; case 'l': mLength = tickFromDotToken(tickFromLength(uintToken(0))); break; case '(': mVelocity += mVelocityDetail ? 1 : 8; mVelocity = Math.min(mVelocity, 127); break; case ')': mVelocity -= mVelocityDetail ? 1 : 8; mVelocity = Math.min(mVelocity, 127); break; case 't': mTempo = Math.max(uintToken(mTempo), 1); Track tempoTrack = mTracks.get(Track.TEMPO_TRACK); tempoTrack.recordTempo(mTempo, mTracks.get(mTrackIndex) .getGlobalTick()); break; case 'q': mGate = uintToken(mGate); mTracks.get(mTrackIndex).recordGate((mGate * 1.0) / mMaxGate); break; case '<': mOctave += mRelativeDir ? 1 : -1; break; case '>': mOctave += mRelativeDir ? -1 : 1; break; case ';': track = mTracks.get(mTrackIndex); if (track.getEventCount() > 0) { track = createTrack(); mTracks.add(track); ++mTrackIndex; } break; case '@': atmarkToken(); break; case 'x': mTracks.get(mTrackIndex).recordVolumeMode(uintToken(1)); break; case 'n': char c0 = characterToken(); if (c0 == 's') { mIndex++; mNoteShift = intToken(mNoteShift); } else mWarnings.add(new Integer(0)); break; default: if (c < 128) mWarnings.add(new Integer(0)); break; } } private void atmarkToken() { char c = characterToken(); int tmp = 1, attack = 0, decay = 64, sustain = 32, release = 0; switch (c) { case 'v': mVelocityDetail = false; mIndex++; mVelocity = Math.min(uintToken(mVelocity), 127); break; case 'x': mIndex++; int expression = Math.min(uintToken(127), 127); mTracks.get(mTrackIndex).recordExpression(expression); break; case 'e': mIndex++; tmp = uintToken(tmp); if (characterToken() == ',') mIndex++; attack = uintToken(attack); if (characterToken() == ',') mIndex++; decay = uintToken(decay); if (characterToken() == ',') mIndex++; sustain = uintToken(sustain); if (characterToken() == ',') mIndex++; release = uintToken(release); mTracks.get(mTrackIndex).recordEnvelopeADSR(attack, decay, sustain, release, tmp == 1); break; case 'n': mIndex++; int noise = Math.min(uintToken(0), 127); mTracks.get(mTrackIndex).recordNoiseFrequency(noise); break; case 'w': mIndex++; int pwm = Math.max(Math.min(uintToken(50), 1), 99); mTracks.get(mTrackIndex).recordPWM(pwm); break; case 'p': mIndex++; int pan = Math.max(Math.min(uintToken(64), 1), 127); mTracks.get(mTrackIndex).recordPan(pan); break; case '\'': mIndex++; int vowel = Formant.UNKNOWN; while (true) { char v = characterToken(); if (v == '\'') { mIndex++; break; } else { switch (v) { case 'a': vowel = Formant.A; break; case 'e': vowel = Formant.E; break; case 'i': vowel = Formant.I; break; case 'o': vowel = Formant.O; break; case 'u': vowel = Formant.U; break; } mIndex++; } } mTracks.get(mTrackIndex).recordFormantVowel(vowel); break; case 'd': mIndex++; int detune = intToken(0); mTracks.get(mTrackIndex).recordDetune(detune); break; case 'l': mIndex++; Boolean reverse = false; int mainForm = 1, subForm = 0, delay = 0, time = 0, depth = uintToken(0); if (characterToken() == ',') mIndex++; int width = uintToken(0); if (characterToken() == ',') { mIndex++; if (characterToken() == '-') { reverse = true; mIndex++; } mainForm = uintToken(mainForm) + 1; if (characterToken() == '-') { mIndex++; subForm = uintToken(subForm); } if (characterToken() == ',') { mIndex++; delay = uintToken(delay); if (characterToken() == ',') { mIndex++; time = uintToken(time); } } } mTracks.get(mTrackIndex).recordLFO(mainForm, subForm, depth, width, delay, time, reverse ? 1 : 0); break; case 'f': mIndex++; int amount = 0, frequency = 0, resonance = 0, swt = intToken(0); if (characterToken() == ',') { mIndex++; amount = intToken(0); if (characterToken() == ',') { mIndex++; frequency = intToken(0); if (characterToken() == ',') { mIndex++; resonance = intToken(0); } } } mTracks.get(mTrackIndex).recordLPF(swt, amount, frequency, resonance); break; case 'q': mIndex++; int gate2 = uintToken(2); mTracks.get(mTrackIndex).recordGate(gate2); break; case 'i': mIndex++; int sens = uintToken(0); if (characterToken() == ',') { mIndex++; attack = Math.min(uintToken(attack), mMaxPipe); } mTracks.get(mTrackIndex).recordInput(sens, attack); break; case 'o': mIndex++; int mode = uintToken(0); if (characterToken() == ',') { mIndex++; attack = uintToken(attack); if (attack > mMaxPipe) { mMaxPipe = attack; if (mMaxPipe >= MAX_PIPE) mMaxPipe = attack = MAX_PIPE; } } mTracks.get(mTrackIndex).recordOutput(mode, attack); break; default: mForm = uintToken(mForm); int subForm2 = 0; if (characterToken() == '-') { mIndex++; subForm2 = uintToken(0); } mTracks.get(mTrackIndex).recordForm(mForm, subForm2); break; } } private void canonicalize() { StringBuffer ns = new StringBuffer(mText.length()); char[] chars = mText.toString().toCharArray(); for (char c : chars) if (!Character.isWhitespace(c)) ns.append(Character.toLowerCase(c)); mText = ns; } private void init(String stringToParse) { mTracks = new ArrayList(); mText = new StringBuffer(stringToParse); mTrackIndex = Track.FIRST_TRACK; mOctave = 4; mRelativeDir = true; mVelocity = 100; mVelocityDetail = true; mLength = tickFromLength(4); mTempo = 120; mKeyOff = true; mGate = 15; mMaxGate = 16; mForm = Oscillator.PULSE; mNoteShift = 0; mMaxPipe = 0; } private void noteToken(int noteIndex) { noteIndex += mNoteShift + keySignToken(); int length = uintToken(0); int tick = tickFromDotToken(tickFromLength(length)); Boolean keyOn = (mKeyOff == false) ? false : true; mKeyOff = true; if (characterToken() == '&') { mIndex++; mKeyOff = false; } mTracks.get(mTrackIndex).recordNote(noteIndex + mOctave * 12, tick, mVelocity, keyOn, mKeyOff); } private void restToken() { int length = uintToken(0); int tick = tickFromDotToken(tickFromLength(length)); mTracks.get(mTrackIndex).recordRest(tick); } private int keySignToken() { int key = 0; Boolean loop = true; while (loop) { char c = characterToken(); switch (c) { case '+': case '#': key++; mIndex++; break; case '-': key--; mIndex++; break; default: loop = false; break; } } return key; } private int intToken(int defaultValue) { char c = characterToken(); int sign = 1; switch (c) { case '+': mIndex++; break; case '-': sign = -1; mIndex++; default: break; } return uintToken(defaultValue) * sign; } private int uintToken(int defaultValue) { long ret = 0, sum = 0; int index = mIndex; while (true) { char c = characterToken(); if (Character.isDigit(c)) { sum = sum * 10 + (c - '0'); mIndex++; if (sum < Integer.MAX_VALUE) ret = sum; else break; } else break; } return index == mIndex ? defaultValue : (int) ret; } private int tickFromDotToken(int tick) { char c = characterToken(); int t = tick; while (c == '.') { mIndex++; t /= 2; tick += t; c = characterToken(); } return tick; } private int tickFromLength(int length) { return length == 0 ? mLength : 384 / length; } private char characterToken() { if (mIndex < mText.length()) return mText.charAt(mIndex); else return Character.MIN_VALUE; } private char characterTokenAndNext() { char c = characterToken(); mIndex++; return c; } private Track createTrack() { mOctave = 4; mVelocity = 100; mNoteShift = 0; return new Track(); } private ArrayList mTracks; private ArrayList mWarnings; private Sequencer mSequencer; private StringBuffer mText; private int mIndex; private int mForm; private Boolean mRelativeDir; private int mLength; private int mTrackIndex; private int mOctave; private int mVelocity; private Boolean mVelocityDetail; private int mTempo; private Boolean mKeyOff; private int mGate; private int mMaxGate; private int mNoteShift; private int mMaxPipe; }