/*
* Created on May 16, 2007
*
* Copyright (c) 2006-2007 P.J.Leonard
*
* http://www.frinika.com
*
* This file is part of Frinika.
*
* Frinika is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* Frinika 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Frinika; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.frinika.sequencer.model.timesignature;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
import java.util.Map.Entry;
/**
* Maintains a list of time signature changes.
*
* TODO the iterators need to implement a locking mechanism to avoid concurrent
* modification
*
* Please use iterators if posible. Faster than repeated querries.
*
* @author pjl
*
*/
public class TimeSignatureList implements Serializable {
transient private TreeMap<Integer, TimeSignatureEvent> eventByBar;
private Vector<TimeSignatureEvent> list;
transient private boolean dirty = true;
static double tol = 1e-6;
public TimeSignatureList() {
eventByBar = new TreeMap<Integer, TimeSignatureEvent>();
list = new Vector<TimeSignatureEvent>();
// eventByBeat = new TreeMap<Integer, TimeSignitureEvent>(new
// Comparator<TimeSignitureEvent>() {
// });
}
public void remove(int bar) {
eventByBar.remove(bar);
dirty = true;
}
public synchronized void add(int bar, int nBeatPerBar) {
if (eventByBar.remove(bar) != null) {
System.out.println(" TIME SIG AT" + bar + " REMOVED ");
}
eventByBar.put(bar, new TimeSignatureEvent(bar, nBeatPerBar));
dirty = true;
}
public synchronized void remove(int tick1, int tick2) {
eventByBar.subMap(tick1, tick2).clear();
dirty = true;
}
public synchronized void reco() {
if (!dirty) {
return;
}
TimeSignatureEvent prev = null;
list.clear();
for (Map.Entry<Integer, TimeSignatureEvent> e : eventByBar.entrySet()) {
TimeSignatureEvent ptr = e.getValue();
if (prev != null) {
ptr.beat = prev.beat + (ptr.bar - prev.bar) * prev.beatsPerBar;
} else {
ptr.beat = 0;
}
prev = ptr;
list.add(ptr);
}
dirty = false;
}
public synchronized Vector<TimeSignatureEvent> getList() {
reco();
return list;
}
public class TimeSignatureEvent implements Serializable {
public final int bar; // bar at which the Time Signiture Happens
public int beat; // Beat at which it happens (
public final int beatsPerBar;
TimeSignatureEvent(int bar, int nBeatPerbar) {
this.beatsPerBar = nBeatPerbar;
this.bar = bar;
beat = -1;
}
void display() {
System.out.print(beatsPerBar);
}
}
public int getBeatAtBar(int bar) {
TimeSignatureEvent ev = getTimeSignutureEventAtBar(bar);
return ev.beat + ev.beatsPerBar * (bar - ev.bar);
}
public TimeSignatureEvent getTimeSignutureEventAtBar(int bar) {
if (dirty) {
reco();
}
int itick = bar + 1; //
SortedMap<Integer, TimeSignatureEvent> head = eventByBar.headMap(itick);
if (head.isEmpty()) {
return null;
}
Integer lastKey = head.lastKey();
TimeSignatureEvent ev = head.get(lastKey);
return ev;
}
public int getBarContaining(int beat) {
TimeSignatureEvent ev = getEventAtBeat(beat);
return ev.bar + (beat - ev.beat) / ev.beatsPerBar;
}
//
// public int getNearestBarTo(int beat) {
// TimeSignatureEvent ev=getEventAtBeat(beat);
// int barBefore= ev.bar+(beat-ev.beat)/ev.beatsPerBar;
//
//
// }
public TimeSignatureEvent getEventAtBeat(int beat) {
if (dirty) {
reco();
}
assert (beat >= 0);
// TimeSignatureEvent ret = null;
// TimeSignatureEvent next= null;
Iterator<Map.Entry<Integer, TimeSignatureEvent>> iter = eventByBar.entrySet().iterator();
TimeSignatureEvent ts1 = iter.next().getValue(); // Should always be one event
if (!iter.hasNext()) {
return ts1;
}
do {
TimeSignatureEvent ts2 = iter.next().getValue();
if (ts1.beat <= beat && ts2.beat > beat) {
return ts1;
}
ts1 = ts2;
} while (iter.hasNext());
return ts1;
}
private void writeObject(ObjectOutputStream out) throws IOException {
reco();
out.defaultWriteObject();
}
private void readObject(ObjectInputStream in)
throws ClassNotFoundException, IOException {
in.defaultReadObject();
eventByBar = new TreeMap<Integer, TimeSignatureEvent>();
for (TimeSignatureEvent ev:list) {
eventByBar.put(ev.bar,ev);
}
dirty=false;
}
void display() {
reco();
for (Map.Entry<Integer, TimeSignatureEvent> me : eventByBar.entrySet()) {
System.out.println(me.getKey() + " @" + me.getValue().bar + " * " + me.getValue().beat + " : " + " : " + me.getValue().beatsPerBar);
// System.out.println(getTimeAt(me.getKey()+0.5));
}
}
/**
* Interface for an iterator
*
* @author pjl
*
*/
public interface QStepIterator {
boolean hasNext(); // more
void next(); // advance to next beat
/**
*
* @return absolute number of beats
*/
double getBeat();
/**
*
* @return true if we are at a bar line
*/
boolean isBar();
/**
*
* @return bar which beat belongs
*/
int getBar();
};
/**
*
* Iterates on bar between beat1 and beat2
*
* @author pjl
*
*/
public class QStepIteratorBar implements QStepIterator {
public Iterator<Entry<Integer, TimeSignatureEvent>> tsIter;
TimeSignatureEvent ts = null;
TimeSignatureEvent tsNext = null;
int beatNext = Integer.MAX_VALUE;
int beatNow = Integer.MIN_VALUE;
int beat2;
int barNow;
QStepIteratorBar(int beat1, int beat2) {
if (dirty) {
reco();
}
this.beat2 = beat2;
tsIter = eventByBar.entrySet().iterator();
ts = tsIter.next().getValue();
while (tsIter.hasNext()) {
tsNext = tsIter.next().getValue();
if (tsNext.beat > beat1 + tol) { // case when there is a event after beat1
beatNext = ts.beat + ((beat1 - ts.beat) / ts.beatsPerBar) * ts.beatsPerBar;
barNow = (ts.bar + ((beat1 - ts.beat) / ts.beatsPerBar)) - 1;
return;
}
ts = tsNext;
}
// Case when there is no event after beat1
beatNext = ts.beat + ((beat1 - ts.beat) / ts.beatsPerBar) * ts.beatsPerBar;
barNow = (ts.bar + ((beat1 - ts.beat) / ts.beatsPerBar)) - 1;
tsNext = null;
}
public boolean hasNext() {
return beatNext <= beat2;
}
public void next() {
barNow++;
beatNow = beatNext;
beatNext = beatNow + ts.beatsPerBar;
if (tsNext != null && beatNext == tsNext.beat) {
ts = tsNext;
if (tsIter.hasNext()) {
tsNext = tsIter.next().getValue();
} else {
tsNext = null;
}
}
}
public double getBeat() {
return beatNow;
}
public boolean isBar() {
return true;
}
public int getBar() {
return barNow;
}
}
class QStepIteratorDef implements QStepIterator {
public double beat;
public boolean isBar;
double step;
int count;
public Iterator<Entry<Integer, TimeSignatureEvent>> tsIter;
TimeSignatureEvent ts;
private TimeSignatureEvent tsNext;
QStepIteratorDef(double beat1, double beat2, double step) {
if (dirty) {
reco();
}
tsIter = eventByBar.entrySet().iterator();
beat = beat1 - step;
this.step = step;
count = (int) ((beat2 + tol - beat1) / step);
while (tsIter.hasNext()) {
ts = tsIter.next().getValue();
if (ts.beat >= beat - tol) {
if (tsIter.hasNext()) {
tsNext = tsIter.next().getValue();
} else {
tsNext = null;
}
break;
}
}
}
public boolean hasNext() {
return count >= 0;
}
public void next() {
beat += step;
isBar = Math.abs((beat + tol - ts.beat) % ts.beatsPerBar) < 2 * tol;
count--;
if (isBar && tsNext != null) {
if (Math.abs(beat - tsNext.beat) < tol) {
ts = tsNext;
if (tsIter.hasNext()) {
tsNext = tsIter.next().getValue();
} else {
tsNext = null;
}
}
}
}
public double getBeat() {
return beat;
}
public boolean isBar() {
return isBar;
}
public int getBar() {
return ts.bar + ((int) (beat - ts.beat)) / ts.beatsPerBar;
}
}
/**
*
* Create an iterator between beat1 and beat2
*
* @param beat1 should be a multiple of step
* @param beat2
* @param step should be a divisor of 1 OR negtive will step by whole bars
*
* @return
*/
public QStepIterator createQStepIterator(double beat1, double beat2,
double step) {
if (step > 0) {
return new QStepIteratorDef(beat1, beat2, step);
} else {
return new QStepIteratorBar((int) Math.ceil(beat1), (int) Math.floor(beat2));
}
}
public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException {
TimeSignatureList list = new TimeSignatureList();
list.add(0, 4);
list.add(2, 3);
list.add(3, 5);
//
// int N = 1000;
//
// int bar = 0;
//
// for (int i = 0; i < N; i += 10) {
// int nBeatPerBar = 2 + i % 3;
// list.add(bar, nBeatPerBar);
// bar += 3;
// }
// // list.display();
//
double beat1 = 0;
double beat2 = 20;
// double step = 0.5;
QStepIterator iter = list.createQStepIterator(beat1, beat2, 1);
while (iter.hasNext()) {
iter.next();
System.out.print("@" + iter.getBeat());
TimeSignatureEvent ev = list.getEventAtBeat((int) iter.getBeat());
System.out.println(" Event =" + ev.beat + " " + ev.bar + " " + ev.beatsPerBar);
}
File tt = new File("/tmp/TS");
OutputStream fout = new FileOutputStream(tt);
ObjectOutputStream out = new ObjectOutputStream(fout);
out.writeObject(list);
out.close();
InputStream fin = new FileInputStream(tt);
ObjectInputStream in = new ObjectInputStream(fin);
Object x=in.readObject();
iter = ((TimeSignatureList)x).createQStepIterator(beat1, beat2, 1);
while (iter.hasNext()) {
iter.next();
System.out.print("@" + iter.getBeat());
TimeSignatureEvent ev = list.getEventAtBeat((int) iter.getBeat());
System.out.println(" Event =" + ev.beat + " " + ev.bar + " " + ev.beatsPerBar);
}
}
}