package com.frinika.sequencer.model.util;
/*
* Created on May 9, 2006
*
* Copyright (c) 2006 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
*/
import com.frinika.project.ProjectContainer;
import com.frinika.sequencer.model.timesignature.TimeSignatureList;
import com.frinika.sequencer.model.timesignature.TimeSignatureList.TimeSignatureEvent;
/**
* Helper class to convert ticks into the display format and visa versa
*
* tick <---> bar.beat:tick
*
* Imcomplete strings are allowed
*
* bar. beat: :tick bar.beat beat:tick .beat:tick bar.beat:
*
* You can select NORMAL mode 0 ---> 1.1:000 OR CMODE 0 ---> 0.0.000
*
* @author pjl
*
*/
public class TimeUtils {
// ProjectContainer project;
final static int NORMAL = 0;
final static int CMODE = 1;
static int defaultMode = CMODE;
int ticksPerBeat; // = 420;
TimeSignatureList timeSig;
// TODO hash map allowing sig changes.
// int beatsPerBar = 4;
static int barOff = 0;
static int beatOff = 0;
/**
*
* @param proj
*/
public TimeUtils(ProjectContainer proj) { // could get rid of this ?
this(proj.getSequence().getResolution(), proj.getTimeSignatureList());
}
public TimeUtils(int ticksPerBeat, TimeSignatureList timeSig) {
this.ticksPerBeat = ticksPerBeat;
;
this.timeSig = timeSig;
setMode(defaultMode);
// TODO keep list of TImeUtils and set all with a static setMode
}
public static void setMode(int key) {
switch (key) {
case NORMAL:
barOff = 1;
beatOff = 1;
break;
case CMODE:
barOff = 0;
beatOff = 0;
break;
default:
try {
throw new Exception("Unknown display mode");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
*
*
* @param tick
* @return formated bar.beat:tick
*/
public String tickToBarBeatTick(long tick) {
boolean minus = (tick < 0);
if (minus) {
tick = -tick;
}
// this one is easy
long beats = tick / ticksPerBeat;
long tickBit = tick % ticksPerBeat;
// long bar = beats / beatsPerBar;
// beats = beats % beatsPerBar;
TimeSignatureEvent ev = timeSig.getEventAtBeat((int) beats);
int bar = (int) (ev.bar + (beats - ev.beat) / ev.beatsPerBar);
beats = (beats - ev.beat) % ev.beatsPerBar;
String s = (bar + barOff) + "." + (beats + beatOff) + ":"
+ String.format("%03d", tickBit);
if (minus) {
s = "-" + s;
}
return s;
}
/**
*
*
* @param tick
* must be an multiple of ticksPerBeat
* @return formated bar.beat
*/
public String tickToBarBeat(long tick) {
boolean minus = false;
if (tick < 0) {
minus = true;
tick = -tick;
}
// this one is easy
long beats = tick / ticksPerBeat;
long tickBit = tick % ticksPerBeat;
assert (tickBit == 0);
// long bar = beats / beatsPerBar;
// beats = beats % beatsPerBar;
TimeSignatureEvent ev = timeSig.getEventAtBeat((int) beats);
String s = ((ev.bar + barOff) + "." + (beats - ev.beat + beatOff));
if (minus) {
return "-" + s;
} else {
return s;
}
}
/**
* Do not attempt to convert beats into bars.
*
* @param tick
* @return beat:tick
*/
public String tickToBeatTick(long tick) {
boolean minus = false;
if (tick < 0) {
minus = true;
tick = -tick;
}
long beats = tick / ticksPerBeat;
long tickBit = tick % ticksPerBeat;
String s = (beats + beatOff) + ":" + String.format("%03d", tickBit);
if (minus) {
return "-" + s;
} else {
return s;
}
}
/**
*
* @param tick
* @return bar at or just before tick
*/
public int barAtTick(long tick) {
long beats = tick / ticksPerBeat;
long tickBit = tick % ticksPerBeat;
if (tickBit != 0)
return -1;
TimeSignatureEvent ev = timeSig.getEventAtBeat((int) beats);
if (ev == null) return -1;
int bb = (int) ((beats - ev.beat) % ev.beatsPerBar);
if (bb != 0)
return -1;
return (int) (ev.bar + barOff + (beats - ev.beat) / ev.beatsPerBar);
}
/**
*
* @param str
* bar.beat:tick string
* @return ticks
*/
public long barBeatTickToTick(String str) {
// This is a lot of if statements
try {
str = str.trim();
if (str.length() == 0)
return 0;
long sign = (str.charAt(0) == '-') ? -1 : 1;
if (sign == -1) {
str = str.substring(1).trim();
if (str.length() == 0)
return 0;
}
String toks[] = str.split("[:|.]");
switch (toks.length) {
case 1:
if (str.charAt(0) == ':')
return sign * Long.parseLong(toks[0]);
else if (str.indexOf('.') > 0) { // bar to tick
if (sign < 0) {
throw new Exception(" negative times are broken ");
}
int beat = timeSig.getBeatAtBar(Integer.parseInt(toks[0])
- barOff);
return sign * beat * ticksPerBeat;
// return 0; // sign * (Long.parseLong(toks[0]) - barOff) *
// ticksPerBeat * beatsPerBar;
} else
return sign * (Long.parseLong(toks[0]) - beatOff)
* ticksPerBeat;
case 2:
if (str.charAt(0) == '.')
return sign * (Long.parseLong(toks[1]) - beatOff)
* ticksPerBeat;
else if (str.charAt(0) == ':')
return sign * Long.parseLong(toks[1]);
else if (str.indexOf('.') > 0) {
if (sign < 0) {
throw new Exception(" negative times are broken ");
}
int beat = timeSig.getBeatAtBar(Integer.parseInt(toks[0])
- barOff);
return sign
* (beat + (Integer.parseInt(toks[1]) - beatOff))
* ticksPerBeat;
// ticksPerBeat * beatsPerBar + (Long.parseLong(toks[1]) -
// beatOff) * ticksPerBeat);
// return sign * ((Long.parseLong(toks[0]) - barOff) *
// ticksPerBeat * beatsPerBar + (Long.parseLong(toks[1]) -
// beatOff) * ticksPerBeat);
} else {
return sign
* ((Long.parseLong(toks[0]) - beatOff)
* ticksPerBeat + Long.parseLong(toks[1]));
}
case 3:
if (!toks[0].equals("")) {
if (sign < 0) {
throw new Exception(" negative times are broken ");
}
int beat = timeSig.getBeatAtBar(Integer.parseInt(toks[0])
- barOff);
return sign
* (beat + (Integer.parseInt(toks[1]) - beatOff))
* ticksPerBeat + +Long.parseLong(toks[2]);
// return sign * (((Long.parseLong(toks[0]) - barOff) *
// beatsPerBar + Long.parseLong(toks[1]) - beatOff) *
// ticksPerBeat + Long.parseLong(toks[2]));
} else
return sign
* ((Long.parseLong(toks[1]) - beatOff)
* ticksPerBeat + Long.parseLong(toks[2]));
default:
throw new Exception("error in time format >" + str + "<");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return 0;
}
public long beatTickToTick(String s) {
int sgn = 1;
s = s.trim();
if (s.length() == 0) return 0;
char sgnch = s.charAt(0);
if (sgnch=='-') {
sgn = -1;
s = s.substring(1);
} else if (sgnch=='+') {
s = s.substring(1);
}
int pos = s.indexOf(':');
if (pos == -1) { // assume ticks only
try {
return sgn * Long.parseLong(s);
} catch (NumberFormatException nfe) {
return 0;
}
}
String beatsStr = s.substring(0, pos);
String ticksStr = s.substring(pos+1);
int beats;
try {
beats = Integer.parseInt(beatsStr);
} catch (NumberFormatException nfe1) {
beats = 0;
}
int ticks;
try {
ticks = Integer.parseInt(ticksStr);
} catch (NumberFormatException nfe1) {
ticks = 0;
}
return sgn * ((beats * (long)ticksPerBeat) + ticks);
}
// public BarIterator barIterator(long tick1, long tick2) {
// return new BarIterator(tick1, tick2);
// }
public double tickToFloatBeat(long tick) {
return tick / ticksPerBeat; // project.getSequence().getResolution();
}
// public class BarTick {
// int bar;
//
// int tick;
//
// BarTick(int bar) {
// this.bar = bar;
// this.tick = (bar - barOff) * ticksPerBeat * beatsPerBar;
// }
//
// public long getTick() {
// return tick;
// }
//
// public long getBar() {
// return bar;
// }
// }
//
// class BarIterator implements Iterator {
//
// int barNext = 0;
//
// int barLast = 0;
//
// BarIterator(long tick1, long tick2) {
// barNext = barOff + (int) (tick1 / ticksPerBeat / beatsPerBar);
// barLast = barOff + (int) (tick2 / ticksPerBeat / beatsPerBar);
// }
//
// public boolean hasNext() {
// return barNext <= barLast;
// }
//
// public Object next() {
// // TODO Auto-generated method stub
// return new BarTick(barNext++);
// }
//
// public void remove() {
// // TODO Auto-generated method stub
//
// }
//
// }
public long ticksPerBeat() { // Jens
// return project.getSequence().getResolution();
return ticksPerBeat;
}
public int beatsPerBar(long tick) { // Jens
long beats = tick / ticksPerBeat;
TimeSignatureEvent ev = timeSig.getEventAtBeat((int) beats);
//int bar = (int) (ev.bar + (beats - ev.beat) / ev.beatsPerBar);
//beats = (beats - ev.beat) % ev.beatsPerBar;
return ev.beatsPerBar;
}
public long startTickOfBar(int bar) { // PJL
return timeSig.getBeatAtBar(bar)*ticksPerBeat;
}
public long beatToTick(double beat) {
return (long)Math.round(ticksPerBeat*beat);
}
static public void main(String args[]) {
TimeSignatureList list = new TimeSignatureList();
list.add(0, 4);
TimeUtils time = new TimeUtils(60, list);
//
String tests[] = { "1.", "234", // beats
"2:234", // beat.ticks
":234", // ticks
"17.3:031", // bar.beat.ticks
"14.", // bar.
".3", // beat
".3:004", // .beat:tick
"2:", // beat:
"15.1:", // bar.beat
};
setMode(CMODE);
for (String str : tests)
System.out.println(str + " = "
+ time.tickToBarBeatTick(time.barBeatTickToTick(str)));
setMode(NORMAL);
for (String str : tests)
System.out.println(str + " = "
+ time.tickToBarBeatTick(time.barBeatTickToTick(str)));
/*
* for (int i = 0; i < 10000; i += 5) {
* System.out.println(time.tickToBarBeatTick(i)); }
*/
}
}