// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>
//
// Klipspringer 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 3 of the
// License, or (at your option) any later version.
//
// Klipspringer 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 Klipspringer. If
// not, see <https://www.gnu.org/licenses/>.
package net.avadeaux.klipspringer;
import org.json.*;
import java.util.regex.*;
import java.util.HashMap;
/** Object representation of mix specification given for a track, created from its JSON
* specification. Times in seconds. */
public class MixSpec {
/** Crossfade shape function class. */
public static abstract class Fade {
public interface Factory {
Fade get(double[] params);
}
/** Crossfade sample value that combines one to fade out with one to fade in, given a point
* in time between 0 (crossfade starting) and 1 (crossfade ending). Clips if out of
* range. */
public abstract int mix(int out, int in, double t);
}
private final static double SQRTHALF = Math.sqrt(0.5);
public final static Fade JUMP_START = new Fade() {
public int mix(int out, int in, double t) { return in; }
public String toString() { return "jump_start"; }
};
public final static Fade JUMP_END = new Fade() {
public int mix(int out, int in, double t) { return out; }
public String toString() { return "jump_end"; }
};
public final static Fade LIN_AMP = new Fade() {
public int mix(int out, int in, double t) {
return (int) Math.max(Math.min(in*t + out*(1-t), Integer.MAX_VALUE), Integer.MIN_VALUE);
}
public String toString() { return "lin_amp"; }
};
public final static Fade LIN_POW = new Fade() {
public int mix(int out, int in, double t) {
return (int) Math.max(Math.min(in*Math.sqrt(t) + out*Math.sqrt(1-t), Integer.MAX_VALUE), Integer.MIN_VALUE);
}
public String toString() { return "lin_pow"; }
};
public final static Fade COMBINE = new Fade() {
public int mix(int out, int in, double t) {
return (int) Math.max(Math.min(in*SQRTHALF + out*SQRTHALF, Integer.MAX_VALUE), Integer.MIN_VALUE);
}
public String toString() { return "combine"; }
};
private final static HashMap<String, Fade.Factory> facts = new HashMap<String, Fade.Factory>();
static {
facts.put(JUMP_START.toString(), new Fade.Factory() { public Fade get(double[] params) { return JUMP_START; } });
facts.put(JUMP_END.toString(), new Fade.Factory() { public Fade get(double[] params) { return JUMP_END; } });
facts.put(LIN_AMP.toString(), new Fade.Factory() { public Fade get(double[] params) { return LIN_AMP; } });
facts.put(LIN_POW.toString(), new Fade.Factory() { public Fade get(double[] params) { return LIN_POW; } });
facts.put(COMBINE.toString(), new Fade.Factory() { public Fade get(double[] params) { return COMBINE; } });
facts.put("exp", new Fade.Factory() {
public Fade get(double[] params) {
final double a = params[0];
return new Fade() {
public int mix(int out, int in, double t) {
double g = 1 - Math.pow(2, -a * t);
double f = Math.sqrt(1-g*g);
return (int) Math.max(Math.min(out*f + in*g, Integer.MAX_VALUE), Integer.MIN_VALUE);
}
public String toString() { return a == (long) a ? "exp,"+(long) a : "exp,"+a; }
};
}
});
}
private final static Pattern timeP = Pattern.compile("(?:(?:([0-9]+):)?(?:([0-9]+):))?([0-9]+(?:\\.[0-9]*)?)");
private static double num(String s) {
if (s == null || s.length() == 0) { return 0; }
return Double.parseDouble(s);
}
private static double time(JSONObject o, String key, double dflt) {
if (!o.has(key)) { return dflt; }
return time(o.optString(key));
}
private static double time(String s) {
Matcher m = timeP.matcher(s);
if (!m.matches()) { throw new IllegalArgumentException("Invalid time value: "+s); }
return num(m.group(1))*3600 + num(m.group(2))*60 + num(m.group(3));
}
private static MixSpec defaultInstance = new MixSpec(0, 0, Double.NaN, JUMP_START);
public static MixSpec getInstance() { return defaultInstance; }
public static MixSpec getInstance(JSONObject json) {
double skip = time(json, "skip", 0);
double end = time(json, "end", Double.NaN);
if (!json.has("fadein")) {
return new MixSpec(skip, 0, end, JUMP_START);
} else {
JSONArray fadeInSpec = json.getJSONArray("fadein");
double fadeTime = time(fadeInSpec.optString(0));
String fadeType = fadeInSpec.optString(1);
double[] params = new double[fadeInSpec.length()-2];
for (int i = 0; i < params.length; i++) {
params[i] = fadeInSpec.getDouble(i+2);
}
return new MixSpec(skip, fadeTime, end, fade(fadeType, params));
}
}
public static Fade fade(String fadeType, double[] params) {
Fade.Factory fact = facts.get(fadeType);
if (fact == null) { throw new IllegalArgumentException("Unrecognized fade type: "+fadeType); }
return fact.get(params);
}
public final double skip, fadeIn, end;
public final Fade fade;
public MixSpec(double skip, double fadeIn, double end, Fade fade) {
this.skip = skip;
this.fadeIn = fadeIn;
this.end = end;
this.fade = fade;
}
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home