package de.maramuse.soundcomp.process;
/*
* Copyright 2010 Jan Schmidt-Reinisch
*
* SoundComp - a sound processing library
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; in version 2.1
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* This class keeps track of global timing. It is advanced by the sample advancer and
* allows any process element or set of these to register themselves for certain events at certain time stamps.
* When the time stamp is reached, the event is sent back to each affected process element.
* TODO This class lacks its C++ pendant.
*/
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeMap;
import de.maramuse.soundcomp.util.NotImplementedException;
public class TimerMap implements Stateful {
/**
* An interface for events with an intrinsic timestamp.
* Note that this is for simplifying test case purposes only. In "real life"
* scenarios, precalculated timing rarely makes sense due to the possibility
* to change tempo at any point in time as well as to react on external events.
* Therefore marked as @deprecated
*/
public static interface TimedEvent extends Event {
/**
* Returns the timestamp of the event
*
* @return the timestamp of the event
*/
public double getTimestamp();
}
/**
* An interface to mark all Events that may be sent from the global timer Typical Events may be "note on", "note off",
* etc. Typical use would be constructing a state machine of multiple events, for providing input streams to envelope
* generators etc.
*/
public static interface Event {
/**
* Each Implementation has the knowledge how its Element wants to get notified
*
* @param processElement
* The process element that has to get notified
*/
public void notifyElement(ProcessElement processElement);
};
/**
* The container for the data stored for one event/element-set
*/
private class EventObject {
private Set<ProcessElement> receivers;
private Event event;
/** constructor for a 1-element-receiver set */
private EventObject(Event event, ProcessElement receiver) {
this.event=event;
this.receivers=new HashSet<ProcessElement>();
this.receivers.add(receiver);
}
/** constructor for a real receiver set */
private EventObject(Event event, Set<ProcessElement> receivers) {
this.event=event;
this.receivers=receivers;
}
/**
* notifies the process element that the event has occurred The event has the knowledge how to accomplish that
*/
public void notifyReceiver() {
if(event!=null){
for(ProcessElement receiver:receivers)
event.notifyElement(receiver);
}
}
}
Time time=new Time();
Double nextTimeStamp;
public TreeMap<Double, HashSet<EventObject>> eventsByTime=new TreeMap<Double, HashSet<EventObject>>();
public HashMap<Event, Double> eventsByEvent=new HashMap<Event, Double>();
/**
* add a future event and its receiver at a certain timestamp; past events will silently be ignored.
*
* @param timestamp
* the timestamp at which the event shall get triggered
* @param event
* the event that shall get triggered
* @param element
* the element that is to notify of the event
*/
public void addEvent(Double timestamp, Event event, ProcessElement element) {
if(timestamp<time.getValue(StandardParameters.OUT.i))
return; // past elements will not get added
eventsByEvent.put(event, timestamp);
HashSet<EventObject> events=eventsByTime.get(timestamp);
if(events==null){
events=new HashSet<EventObject>();
eventsByTime.put(timestamp, events);
}
events.add(new EventObject(event, element));
if(nextTimeStamp==null||timestamp<nextTimeStamp)
nextTimeStamp=timestamp;
}
/**
* add a future event and its receiver at a certain timestamp; past events will silently be ignored.
*
* @param timestamp
* the timestamp at which the event shall get triggered
* @param event
* the event that shall get triggered
* @param elements
* the set of elements that are to notify of the event
*/
public void addEvent(Double timestamp, Event event, Set<ProcessElement> elements) {
if(timestamp<time.getValue(StandardParameters.OUT.i))
return; // past elements will not get added
eventsByEvent.put(event, timestamp);
HashSet<EventObject> events=eventsByTime.get(timestamp);
if(events==null){
events=new HashSet<EventObject>();
eventsByTime.put(timestamp, events);
}
events.add(new EventObject(event, elements));
if(nextTimeStamp==null||timestamp<nextTimeStamp)
nextTimeStamp=timestamp;
}
/**
* remove a single event that has earlier been added, independent of its timestamp (for the sake of completeness.
* might rarely be needed)
*
* @param event
* the event to remove. None of its elements will be notified of reaching the timestamp
*/
public void removeEvent(Event event) {
Double timestamp=eventsByEvent.get(event);
if(timestamp!=null){
eventsByEvent.remove(event);
HashSet<EventObject> events=eventsByTime.get(timestamp);
if(events!=null)
for(EventObject o:events)
if(o.event==event){
events.remove(o);
break; // only one hit possible, so leave iterator, else would get ConcurrentModificationException
}
}
}
/**
* remove all contained information, so you get a "virgin" TimerMap also clears the internal timer, so future events
* have no relation to the past
*/
public void clear() {
eventsByEvent.clear();
eventsByTime.clear();
nextTimeStamp=null;
time.reset();
}
@Override
public void advanceOutput() {
time.advanceOutput();
}
@Override
public void advanceState() {
boolean repeat=false;
Double now=time.getValue(StandardParameters.OUT.i);
// notify and remove all past events
do{
if(nextTimeStamp==null){
if(!eventsByTime.isEmpty())
nextTimeStamp=eventsByTime.firstKey();
}
repeat=false;
if(nextTimeStamp!=null&&nextTimeStamp<now){
Set<EventObject> eventObjects=eventsByTime.get(nextTimeStamp);
for(EventObject obj:eventObjects){
obj.notifyReceiver();
eventsByEvent.remove(obj.event);
}
eventsByTime.remove(nextTimeStamp);
nextTimeStamp=null;
repeat=true;
}
}while(repeat);
time.advanceState();
}
@Override
public String getAbstractName() {
return "$$$GlobalTimer$$$";
}
@Override
public String getInstanceName() {
return "$$$GlobalTimer$$$Instance";
}
@Override
public void setAbstractName(String abstractName) {
throw new NotImplementedException();
}
@Override
public void setInstanceName(String instanceName) {
throw new NotImplementedException();
}
@Override
public long getNativeSpace() {
// TODO This class has no native pendant yet, and does not implement ProcessElement.
// When implemented, it must just return the current time at the only output
return 0;
}
public double currentTime() {
return time.getValue(StandardParameters.OUT.i);
}
}