package ddf.minim;
import java.util.ArrayList;
import java.util.HashMap;
import ddf.minim.ugens.Instrument;
/**
*
* @author ddf
* @invisible
*/
public class NoteManager
{
// we use this do our timing, basically
private float sampleRate;
private float tempo;
private float noteOffset;
private float durationFactor;
private int now;
// our events are stored in a map.
// the keys in this map are the "now" that the events should
// occur at and the values are a list of events that occur
// at that time.
private HashMap<Integer, ArrayList<NoteEvent>> events;
// are we paused?
// pausing is important because if we're going to queue up
// a large number of notes, we want to make sure their timestamps
// are accurate. this won't be possible if the note manager
// is sending events because of ticks from the audio output.
private boolean paused;
private interface NoteEvent
{
void send();
}
private class NoteOnEvent implements NoteEvent
{
private Instrument instrument;
private float duration;
public NoteOnEvent(Instrument i, float dur)
{
instrument = i;
duration = dur;
}
public void send()
{
instrument.noteOn(duration);
}
}
private class NoteOffEvent implements NoteEvent
{
private Instrument instrument;
public NoteOffEvent(Instrument i)
{
instrument = i;
}
public void send()
{
instrument.noteOff();
}
}
public NoteManager( float sampleRate )
{
this.sampleRate = sampleRate;
events = new HashMap<Integer, ArrayList<NoteEvent>>();
tempo = 60f;
noteOffset = 0.0f;
durationFactor = 1.0f;
now = 0;
paused = false;
}
// events are always specified as happening some period of time from now.
// but we store them as taking place at a specific time, rather than a relative time.
public synchronized void addEvent(float startTime, float duration, Instrument instrument)
{
int on = now + (int)(sampleRate * ( startTime + noteOffset ) * 60f/tempo);
Integer onAt = new Integer( on );
float actualDuration = duration * durationFactor * 60f/tempo;
if ( events.containsKey(onAt) )
{
ArrayList<NoteEvent> eventsAtOn = events.get(onAt);
eventsAtOn.add( new NoteOnEvent(instrument, actualDuration) );
}
else
{
ArrayList<NoteEvent> eventsAtOn = new ArrayList<NoteEvent>();
eventsAtOn.add( new NoteOnEvent(instrument, actualDuration) );
events.put(onAt, eventsAtOn);
}
Integer offAt = new Integer( on + (int)(sampleRate * actualDuration) );
if ( events.containsKey(offAt) )
{
ArrayList<NoteEvent> eventsAtOff = events.get(offAt);
eventsAtOff.add( new NoteOffEvent(instrument) );
}
else
{
ArrayList<NoteEvent> eventsAtOff = new ArrayList<NoteEvent>();
eventsAtOff.add( new NoteOffEvent(instrument) );
events.put(offAt, eventsAtOff);
}
}
public void setTempo(float tempo)
{
this.tempo = tempo;
}
public float getTempo()
{
return tempo;
}
public void setNoteOffset(float noteOffset)
{
this.noteOffset = noteOffset;
}
public float getNoteOffset()
{
return noteOffset;
}
public void setDurationFactor(float durationFactor)
{
this.durationFactor = durationFactor;
}
public float getDurationFactor()
{
return durationFactor;
}
public void pause()
{
paused = true;
}
public void resume()
{
paused = false;
}
synchronized public void tick()
{
if ( paused == false )
{
// find the events we should trigger now.
Integer Now = new Integer(now);
if ( events.containsKey(Now) )
{
ArrayList<NoteEvent> eventsToSend = events.get(Now);
// ddf: change this to a for loop from an iterator so that
// this list can be safely concurrently modified.
for( int i = 0; i < eventsToSend.size(); ++i )
{
eventsToSend.get(i).send();
}
// remove this list because we've sent all the events
events.remove(Now);
}
// increment our now
++now;
}
}
}