diff --git a/.gitignore b/.gitignore index 4ce1cad..287e67f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,46 @@ -.idea/ +# built application files +*.apk +*.ap_ + +# files for the dex VM +*.dex + +# Java class files +*.class + +# generated files +bin/ +gen/ + +# Local configuration file (sdk path, etc) +local.properties + +# Eclipse project files +.classpath +.project + +# Proguard folder generated by Eclipse +proguard/ + +# Intellij project files *.iml -target/ +*.ipr +*.iws +.idea/ + +*.class + +# gwt caches and compiled units # +war/gwt_bree/ +gwt-unitCache/ + +# boilerplate generated classes # +.apt_generated/ + +# more caches and things from deploy # +war/WEB-INF/deploy/ +war/WEB-INF/classes/ +target +build +.gradle diff --git a/src/main/java/com/leff/midi/util/MidiProcessor.java b/src/main/java/com/leff/midi/util/MidiProcessor.java index 862bc7f..425544e 100644 --- a/src/main/java/com/leff/midi/util/MidiProcessor.java +++ b/src/main/java/com/leff/midi/util/MidiProcessor.java @@ -1,386 +1,411 @@ -////////////////////////////////////////////////////////////////////////////// -// Copyright 2011 Alex Leffelman -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -////////////////////////////////////////////////////////////////////////////// - -package com.leff.midi.util; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; - -import com.leff.midi.MidiFile; -import com.leff.midi.MidiTrack; -import com.leff.midi.event.MidiEvent; -import com.leff.midi.event.meta.Tempo; -import com.leff.midi.event.meta.TimeSignature; - -public class MidiProcessor -{ - private static final int PROCESS_RATE_MS = 8; - - private HashMap, List> mEventsToListeners; - private HashMap>> mListenersToEvents; - - private MidiFile mMidiFile; - private boolean mRunning; - private double mTicksElapsed; - private long mMsElapsed; - - private int mMPQN; - private int mPPQ; - - private MetronomeTick mMetronome; - private MidiTrackEventQueue[] mEventQueues; - - public MidiProcessor(MidiFile input) - { - - mMidiFile = input; - - mMPQN = Tempo.DEFAULT_MPQN; - mPPQ = mMidiFile.getResolution(); - - mEventsToListeners = new HashMap, List>(); - mListenersToEvents = new HashMap>>(); - - mMetronome = new MetronomeTick(new TimeSignature(), mPPQ); - - this.reset(); - } - - public synchronized void start() - { - if(mRunning) - return; - - mRunning = true; - new Thread(new Runnable() - { - public void run() - { - process(); - } - }).start(); - } - - public void stop() - { - mRunning = false; - } - - public void reset() - { - - mRunning = false; - mTicksElapsed = 0; - mMsElapsed = 0; - - mMetronome.setTimeSignature(new TimeSignature()); - - List tracks = mMidiFile.getTracks(); - - if(mEventQueues == null) - { - mEventQueues = new MidiTrackEventQueue[tracks.size()]; - } - - for(int i = 0; i < tracks.size(); i++) - { - mEventQueues[i] = new MidiTrackEventQueue(tracks.get(i)); - } - } - - public boolean isStarted() - { - return mTicksElapsed > 0; - } - - public boolean isRunning() - { - return mRunning; - } - - protected void onStart(boolean fromBeginning) - { - - Iterator it = mListenersToEvents.keySet().iterator(); - - while(it.hasNext()) - { - - MidiEventListener mel = it.next(); - mel.onStart(fromBeginning); - } - } - - protected void onStop(boolean finished) - { - - Iterator it = mListenersToEvents.keySet().iterator(); - - while(it.hasNext()) - { - - MidiEventListener mel = it.next(); - mel.onStop(finished); - } - } - - public void registerEventListener(MidiEventListener mel, Class event) - { - - List listeners = mEventsToListeners.get(event); - if(listeners == null) - { - - listeners = new ArrayList(); - listeners.add(mel); - mEventsToListeners.put(event, listeners); - } - else - { - listeners.add(mel); - } - - List> events = mListenersToEvents.get(mel); - if(events == null) - { - - events = new ArrayList>(); - events.add(event); - mListenersToEvents.put(mel, events); - } - else - { - events.add(event); - } - } - - public void unregisterEventListener(MidiEventListener mel) - { - - List> events = mListenersToEvents.get(mel); - if(events == null) - { - return; - } - - for(Class event : events) - { - - List listeners = mEventsToListeners.get(event); - listeners.remove(mel); - } - - mListenersToEvents.remove(mel); - } - - public void unregisterEventListener(MidiEventListener mel, Class event) - { - - List listeners = mEventsToListeners.get(event); - if(listeners != null) - { - listeners.remove(mel); - } - - List> events = mListenersToEvents.get(mel); - if(events != null) - { - events.remove(event); - } - } - - public void unregisterAllEventListeners() - { - mEventsToListeners.clear(); - mListenersToEvents.clear(); - } - - protected void dispatch(MidiEvent event) - { - - // Tempo and Time Signature events are always needed by the processor - if(event.getClass().equals(Tempo.class)) - { - mMPQN = ((Tempo) event).getMpqn(); - } - else if(event.getClass().equals(TimeSignature.class)) - { - - boolean shouldDispatch = mMetronome.getBeatNumber() != 1; - mMetronome.setTimeSignature((TimeSignature) event); - - if(shouldDispatch) - { - dispatch(mMetronome); - } - } - - this.sendOnEventForClass(event, event.getClass()); - this.sendOnEventForClass(event, MidiEvent.class); - } - - private void sendOnEventForClass(MidiEvent event, Class eventClass) - { - - List listeners = mEventsToListeners.get(eventClass); - - if(listeners == null) - { - return; - } - - for(MidiEventListener mel : listeners) - { - mel.onEvent(event, mMsElapsed); - } - } - - private void process() - { - - onStart(mTicksElapsed < 1); - - long lastMs = System.currentTimeMillis(); - - boolean finished = false; - - while(mRunning) - { - - long now = System.currentTimeMillis(); - long msElapsed = now - lastMs; - - if(msElapsed < PROCESS_RATE_MS) - { - try - { - Thread.sleep(PROCESS_RATE_MS - msElapsed); - } - catch(Exception e) - { - } - continue; - } - - double ticksElapsed = MidiUtil.msToTicks(msElapsed, mMPQN, mPPQ); - - if(ticksElapsed < 1) - { - continue; - } - - if(mMetronome.update(ticksElapsed)) - { - dispatch(mMetronome); - } - - lastMs = now; - mMsElapsed += msElapsed; - mTicksElapsed += ticksElapsed; - - boolean more = false; - for(int i = 0; i < mEventQueues.length; i++) - { - - MidiTrackEventQueue queue = mEventQueues[i]; - if(!queue.hasMoreEvents()) - { - continue; - } - - ArrayList events = queue.getNextEventsUpToTick(mTicksElapsed); - for(MidiEvent event : events) - { - this.dispatch(event); - } - - if(queue.hasMoreEvents()) - { - more = true; - } - } - - if(!more) - { - finished = true; - break; - } - } - - mRunning = false; - onStop(finished); - } - - private class MidiTrackEventQueue - { - - private MidiTrack mTrack; - private Iterator mIterator; - private ArrayList mEventsToDispatch; - private MidiEvent mNext; - - public MidiTrackEventQueue(MidiTrack track) - { - - mTrack = track; - - mIterator = mTrack.getEvents().iterator(); - mEventsToDispatch = new ArrayList(); - - if(mIterator.hasNext()) - { - mNext = mIterator.next(); - } - } - - public ArrayList getNextEventsUpToTick(double tick) - { - - mEventsToDispatch.clear(); - - while(mNext != null) - { - - if(mNext.getTick() <= tick) - { - mEventsToDispatch.add(mNext); - - if(mIterator.hasNext()) - { - mNext = mIterator.next(); - } - else - { - mNext = null; - } - } - else - { - break; - } - } - - return mEventsToDispatch; - } - - public boolean hasMoreEvents() - { - return mNext != null; - } - } -} +////////////////////////////////////////////////////////////////////////////// +// Copyright 2011 Alex Leffelman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +////////////////////////////////////////////////////////////////////////////// + +package com.leff.midi.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import com.leff.midi.MidiFile; +import com.leff.midi.MidiTrack; +import com.leff.midi.event.MidiEvent; +import com.leff.midi.event.meta.Tempo; +import com.leff.midi.event.meta.TimeSignature; + +public class MidiProcessor +{ + private static final int PROCESS_RATE_MS = 8; + + private HashMap, List> mEventsToListeners; + private HashMap>> mListenersToEvents; + + private MidiFile mMidiFile; + private boolean mRunning; + private double mTicksElapsed; + private long mMsElapsed; + private long mSkipUntilMs; + + private int mMPQN; + private int mPPQ; + + private MetronomeTick mMetronome; + private MidiTrackEventQueue[] mEventQueues; + + public MidiProcessor(MidiFile input) + { + + mMidiFile = input; + + mMPQN = Tempo.DEFAULT_MPQN; + mPPQ = mMidiFile.getResolution(); + + mEventsToListeners = new HashMap, List>(); + mListenersToEvents = new HashMap>>(); + + mMetronome = new MetronomeTick(new TimeSignature(), mPPQ); + + this.reset(); + } + + public synchronized void start() + { + if(mRunning) + return; + + mRunning = true; + new Thread(new Runnable() + { + public void run() + { + process(); + } + }).start(); + } + + public void stop() + { + mRunning = false; + } + + public void reset() + { + + mRunning = false; + mTicksElapsed = 0; + mMsElapsed = 0; + + mMetronome.setTimeSignature(new TimeSignature()); + + List tracks = mMidiFile.getTracks(); + + if(mEventQueues == null) + { + mEventQueues = new MidiTrackEventQueue[tracks.size()]; + } + + for(int i = 0; i < tracks.size(); i++) + { + mEventQueues[i] = new MidiTrackEventQueue(tracks.get(i)); + } + } + + public void setElapsed(long ms) + { + if(ms < mMsElapsed) + { + reset(); + } + mSkipUntilMs = ms; + } + + public boolean isStarted() + { + return mTicksElapsed > 0; + } + + public boolean isRunning() + { + return mRunning; + } + + protected void onStart(boolean fromBeginning) + { + + Iterator it = mListenersToEvents.keySet().iterator(); + + while(it.hasNext()) + { + + MidiEventListener mel = it.next(); + mel.onStart(fromBeginning); + } + } + + protected void onStop(boolean finished) + { + + Iterator it = mListenersToEvents.keySet().iterator(); + + while(it.hasNext()) + { + + MidiEventListener mel = it.next(); + mel.onStop(finished); + } + } + + public void registerEventListener(MidiEventListener mel, Class event) + { + + List listeners = mEventsToListeners.get(event); + if(listeners == null) + { + + listeners = new ArrayList(); + listeners.add(mel); + mEventsToListeners.put(event, listeners); + } + else + { + listeners.add(mel); + } + + List> events = mListenersToEvents.get(mel); + if(events == null) + { + + events = new ArrayList>(); + events.add(event); + mListenersToEvents.put(mel, events); + } + else + { + events.add(event); + } + } + + public void unregisterEventListener(MidiEventListener mel) + { + + List> events = mListenersToEvents.get(mel); + if(events == null) + { + return; + } + + for(Class event : events) + { + + List listeners = mEventsToListeners.get(event); + listeners.remove(mel); + } + + mListenersToEvents.remove(mel); + } + + public void unregisterEventListener(MidiEventListener mel, Class event) + { + + List listeners = mEventsToListeners.get(event); + if(listeners != null) + { + listeners.remove(mel); + } + + List> events = mListenersToEvents.get(mel); + if(events != null) + { + events.remove(event); + } + } + + public void unregisterAllEventListeners() + { + mEventsToListeners.clear(); + mListenersToEvents.clear(); + } + + protected void dispatch(MidiEvent event) + { + + // Tempo and Time Signature events are always needed by the processor + if(event.getClass().equals(Tempo.class)) + { + mMPQN = ((Tempo) event).getMpqn(); + } + else if(event.getClass().equals(TimeSignature.class)) + { + + boolean shouldDispatch = mMetronome.getBeatNumber() != 1; + mMetronome.setTimeSignature((TimeSignature) event); + + if(shouldDispatch) + { + dispatch(mMetronome); + } + } + + if(mSkipUntilMs == 0) + { + this.sendOnEventForClass(event, event.getClass()); + this.sendOnEventForClass(event, MidiEvent.class); + } + } + + private void sendOnEventForClass(MidiEvent event, Class eventClass) + { + + List listeners = mEventsToListeners.get(eventClass); + + if(listeners == null) + { + return; + } + + for(MidiEventListener mel : listeners) + { + mel.onEvent(event, mMsElapsed); + } + } + + private void process() + { + + onStart(mTicksElapsed < 1); + + long lastMs = System.currentTimeMillis(); + + boolean finished = false; + + while(mRunning) + { + + if(mSkipUntilMs == 0) + { + long now = System.currentTimeMillis(); + long msElapsed = now - lastMs; + + if(msElapsed < PROCESS_RATE_MS) + { + try + { + Thread.sleep(PROCESS_RATE_MS - msElapsed); + } + catch(Exception e) + { + } + continue; + } + + double ticksElapsed = MidiUtil.msToTicks(msElapsed, mMPQN, mPPQ); + + if(ticksElapsed < 1) + { + continue; + } + + if(mMetronome.update(ticksElapsed)) + { + dispatch(mMetronome); + } + + lastMs = now; + mMsElapsed += msElapsed; + mTicksElapsed += ticksElapsed; + } + else + { + mMsElapsed += PROCESS_RATE_MS; + mTicksElapsed += MidiUtil.msToTicks(PROCESS_RATE_MS, mMPQN, mPPQ); + if(mMsElapsed > mSkipUntilMs) + { + mSkipUntilMs = 0; + } + } + + boolean more = false; + for(int i = 0; i < mEventQueues.length; i++) + { + + MidiTrackEventQueue queue = mEventQueues[i]; + if(!queue.hasMoreEvents()) + { + continue; + } + + ArrayList events = queue.getNextEventsUpToTick(mTicksElapsed); + for(MidiEvent event : events) + { + this.dispatch(event); + } + + if(queue.hasMoreEvents()) + { + more = true; + } + } + + if(!more) + { + finished = true; + break; + } + } + + mRunning = false; + onStop(finished); + } + + private class MidiTrackEventQueue + { + + private MidiTrack mTrack; + private Iterator mIterator; + private ArrayList mEventsToDispatch; + private MidiEvent mNext; + + public MidiTrackEventQueue(MidiTrack track) + { + + mTrack = track; + + mIterator = mTrack.getEvents().iterator(); + mEventsToDispatch = new ArrayList(); + + if(mIterator.hasNext()) + { + mNext = mIterator.next(); + } + } + + public ArrayList getNextEventsUpToTick(double tick) + { + + mEventsToDispatch.clear(); + + while(mNext != null) + { + + if(mNext.getTick() <= tick) + { + mEventsToDispatch.add(mNext); + + if(mIterator.hasNext()) + { + mNext = mIterator.next(); + } + else + { + mNext = null; + } + } + else + { + break; + } + } + + return mEventsToDispatch; + } + + public boolean hasMoreEvents() + { + return mNext != null; + } + } +}