/* * Param.java * (FScape) * * Copyright (c) 2001-2016 Hanns Holger Rutz. All rights reserved. * * This software is published under the GNU General Public License v3+ * * * For further information, please contact Hanns Holger Rutz at * contact@sciss.de */ package de.sciss.fscape.util; import de.sciss.fscape.spect.SpectStream; public class Param implements Cloneable { // -------- Unit constants -------- /** * Magnitudes (Dimensions) */ public static final int NONE = 0x0000; public static final int AMP = 0x0001; // has no units public static final int TIME = 0x0002; // default: ms public static final int FREQ = 0x0003; // default: Hz public static final int PHASE = 0x0004; // default: degrees public static final int DIM_MASK = 0x000F; /** * Display (Form) */ public static final int ABS_UNIT = 0x0000; // ms, Hz, ... public static final int ABS_PERCENT = 0x0010; // % public static final int REL_UNIT = 0x0020; // +/- ms, +/- Hz, ... public static final int REL_PERCENT = 0x0030; // +/- % public static final int FORM_MASK = 0x00F0; /** * Special forms */ public static final int BEATS = 0x0100; public static final int SEMITONES = 0x0200; public static final int DECIBEL = 0x0300; public static final int SPECIAL_MASK= 0x0F00; /** * Common units */ public static final int FACTOR = NONE | ABS_PERCENT; public static final int ABS_AMP = AMP | ABS_UNIT; public static final int FACTOR_AMP = AMP | ABS_PERCENT; public static final int DECIBEL_AMP = AMP | ABS_PERCENT | DECIBEL; public static final int OFFSET_AMP = AMP | REL_PERCENT; public static final int ABS_MS = TIME | ABS_UNIT; public static final int ABS_BEATS = TIME | ABS_UNIT | BEATS; public static final int FACTOR_TIME = TIME | ABS_PERCENT; public static final int OFFSET_MS = TIME | REL_UNIT; public static final int OFFSET_BEATS = TIME | REL_UNIT | BEATS; public static final int OFFSET_TIME = TIME | REL_PERCENT; public static final int ABS_HZ = FREQ | ABS_UNIT; public static final int FACTOR_FREQ = FREQ | ABS_PERCENT; public static final int OFFSET_HZ = FREQ | REL_UNIT; public static final int OFFSET_SEMITONES= FREQ | REL_UNIT | SEMITONES; public static final int OFFSET_FREQ = FREQ | REL_PERCENT; // -------- public variables -------- public double value; public int unit; // -------- private variables -------- private final static int chain[] = { 1, 0, 2, 3 }; // mapping; vgl. transform() private static double freqScale; private static double timeScale; static { updateScales(); } // -------- public methods -------- public Param() { value = 0.0; unit = NONE; } /** * @param value value * @param unit unit of measure */ public Param(double value, int unit) { this.value = value; this.unit = unit; } public Param(Param src) { this.value = src.value; this.unit = src.unit; } public Object clone() { return new Param( this ); } /** * Veraenderung der Scalen mitteilen */ public synchronized static void updateScales() { // String value; // // value = Application.userPrefs.get( MainPrefs.KEY_FREQSCALE, null ); // if( value != null ) { // freqScale = Param.valueOf( value ).value; // } else { freqScale = 12.0; // } // value = Application.userPrefs.get( MainPrefs.KEY_TIMESCALE, null ); // if( value != null ) { // timeScale = Param.valueOf( value ).value; // } else { timeScale = 120.0; // } } /** * Transformiert einen Parameter von einer Groesse/Masseinheit (eindimensional) * in eine andere (eindimensional) * * - bei gleichen Einheiten (ohne Umrechnung) wird das Original zurueckgeliefert! * - ein ParamSpace.fitValue() wird nicht durchgefuehrt! * * @param reference optionaler Referrer, der ggf. fuer Umrechnungen von/in * relative Werte benoetigt wird; kann null sein; * WENN ER BENOETIGT WIRD, MUSS ER ENTWEDER IN DER FORM * ABS_UNIT VORLIEGEN ODER (OHNE REFERENZ) IN DIESE UMWANDELBAR SEIN! * * reference wird synchronisiert! * * @param stream optionaler Stream-Referrer, der ggf. fuer Umrechnungen * von/in Beats oder Semitones benoetigt wird; darf null sein * (ggf. wird dann auf die globale Geschwindigkeit/Scala * zurueckgegriffen) * * @return null, wenn wg. fehlendem Referrer die Transformation nicht durchgefuehrt * werden kann oder bei Inkompatiblitaet (Zeit kann nicht in Frequenzen * umgerechnet werden etc.) */ public static Param transform(Param src, int destUnit, Param reference, SpectStream stream) { if (src.unit == destUnit) return src; double destVal = src.value; int srcChain, destChain; // -------- take out special forms -------- switch (src.unit & SPECIAL_MASK) { case BEATS: destVal *= 60000.0 / timeScale; break; case SEMITONES: if (reference == null) return null; destVal = reference.value * (Math.pow(2, destVal / freqScale) - 1); break; case DECIBEL: destVal = Math.exp(destVal / 20 * Constants.ln10) * 100; break; default: break; } // chain: Abs% <==> AbsUnit <==> RelUnit <==> Rel% srcChain = chain[(src.unit & FORM_MASK) >> 4]; destChain = chain[(destUnit & FORM_MASK) >> 4]; if (srcChain != destChain) { if (reference == null) return null; // we need reference here // i.e. in the form of an absolute unit: reference = Param.transform(reference, (reference.unit & ~(FORM_MASK | SPECIAL_MASK)) | ABS_UNIT, null, stream); if (reference == null) return null; // transformation did not work out try { synchronized (reference) { if (srcChain < destChain) { // -------- chain from left to right -------- for (int i = srcChain; i < destChain; i++) { switch (i) { case 0: destVal *= reference.value / 100; // Abs% ==> AbsUnit break; case 1: destVal -= reference.value; // AbsUnit ==> RelUnit break; case 2: destVal *= 100 / reference.value; // RelUnit ==> Rel% break; default: break; } } } else { // -------- chain from right to left -------- for (int i = srcChain; i > destChain; i--) { switch (i) { case 3: destVal *= reference.value / 100; // Rel% ==> RelUnit break; case 2: destVal += reference.value; // RelUnit ==> AbsUnit break; case 1: destVal *= 100 / reference.value; // AbsUnit ==> Abs% break; default: break; } } } } } catch (ArithmeticException e) { // division by zero return null; } } // -------- include special forms -------- switch (destUnit & SPECIAL_MASK) { case BEATS: destVal *= timeScale / 60000; break; case SEMITONES: if (reference == null) return null; destVal = Math.log(1 + destVal / reference.value) / Constants.ln2 * freqScale; break; case DECIBEL: if (destVal > Constants.minPercent) { destVal = Math.log(destVal / 100) * 20 / Constants.ln10; } else { // value too small; minus infinite dB assumed to be -144 dB destVal = Constants.minDecibel; } break; default: break; } return new Param(destVal, destUnit); } /** * Ermittelt, wie "gut" eine Einheiten-Umwandlung waere, * das Ergebnis kann mit anderen verglichen werden; je hoeher * der Wert, desto eher ist die entsprechende Umwandlung * anderen vorzuziehen */ public static int getPriority(int srcUnit, int destUnit) { if (srcUnit == destUnit) return 99; // best one if ((srcUnit & DIM_MASK) != (destUnit & DIM_MASK)) return -99; // worst int srcChain, destChain; srcChain = chain[(srcUnit & FORM_MASK) >> 4]; destChain = chain[(destUnit & FORM_MASK) >> 4]; if (srcChain <= destChain) { return (destChain - srcChain); } else { return (srcChain - destChain); } } // -------- StringComm methods -------- public String toString() { return( "" + value + ',' + unit ); } /** * @param s MUST BE in the format as returned by Param.toString() */ public static Param valueOf(String s) { int i = s.indexOf(','); return new Param(Double.valueOf(s.substring(0, i)).doubleValue(), // value Integer.parseInt(s.substring(i + 1))); // unit } }