////////////////////////////////////////////////////////////////////////////// // 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 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<Class<? extends MidiEvent>, ArrayList<MidiEventListener>> mEventsToListeners; private HashMap<MidiEventListener, ArrayList<Class<? extends MidiEvent>>> 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<Class<? extends MidiEvent>, ArrayList<MidiEventListener>>(); mListenersToEvents = new HashMap<MidiEventListener, ArrayList<Class<? extends MidiEvent>>>(); mMetronome = new MetronomeTick(new TimeSignature(), mPPQ); this.reset(); } public synchronized void start() { if(mRunning) return; mRunning = true; new Thread(this::process).start(); } public void stop() { mRunning = false; } public void reset() { mRunning = false; mTicksElapsed = 0; mMsElapsed = 0; mMetronome.setTimeSignature(new TimeSignature()); ArrayList<MidiTrack> 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<MidiEventListener> it = mListenersToEvents.keySet().iterator(); while(it.hasNext()) { MidiEventListener mel = it.next(); mel.onStart(fromBeginning); } } protected void onStop(boolean finished) { Iterator<MidiEventListener> it = mListenersToEvents.keySet().iterator(); while(it.hasNext()) { MidiEventListener mel = it.next(); mel.onStop(finished); } } public void registerEventListener(MidiEventListener mel, Class<? extends MidiEvent> event) { ArrayList<MidiEventListener> listeners = mEventsToListeners.get(event); if(listeners == null) { listeners = new ArrayList<MidiEventListener>(); listeners.add(mel); mEventsToListeners.put(event, listeners); } else { listeners.add(mel); } ArrayList<Class<? extends MidiEvent>> events = mListenersToEvents.get(mel); if(events == null) { events = new ArrayList<Class<? extends MidiEvent>>(); events.add(event); mListenersToEvents.put(mel, events); } else { events.add(event); } } public void unregisterEventListener(MidiEventListener mel) { ArrayList<Class<? extends MidiEvent>> events = mListenersToEvents.get(mel); if(events == null) { return; } for(Class<? extends MidiEvent> event : events) { ArrayList<MidiEventListener> listeners = mEventsToListeners.get(event); listeners.remove(mel); } mListenersToEvents.remove(mel); } public void unregisterEventListener(MidiEventListener mel, Class<? extends MidiEvent> event) { ArrayList<MidiEventListener> listeners = mEventsToListeners.get(event); if(listeners != null) { listeners.remove(mel); } ArrayList<Class<? extends MidiEvent>> 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<? extends MidiEvent> eventClass) { ArrayList<MidiEventListener> 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<MidiEvent> 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<MidiEvent> mIterator; private ArrayList<MidiEvent> mEventsToDispatch; private MidiEvent mNext; public MidiTrackEventQueue(MidiTrack track) { mTrack = track; mIterator = mTrack.getEvents().iterator(); mEventsToDispatch = new ArrayList<MidiEvent>(); if(mIterator.hasNext()) { mNext = mIterator.next(); } } public ArrayList<MidiEvent> 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; } } }