diff options
Diffstat (limited to 'src/main/java/com/jsyn/unitgen/UnitGenerator.java')
-rw-r--r-- | src/main/java/com/jsyn/unitgen/UnitGenerator.java | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/src/main/java/com/jsyn/unitgen/UnitGenerator.java b/src/main/java/com/jsyn/unitgen/UnitGenerator.java new file mode 100644 index 0000000..7aacaf1 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/UnitGenerator.java @@ -0,0 +1,357 @@ +/* + * Copyright 2009 Phil Burk, Mobileer Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.jsyn.unitgen; + +import java.io.PrintStream; +import java.util.Collection; +import java.util.LinkedHashMap; + +import com.jsyn.Synthesizer; +import com.jsyn.engine.SynthesisEngine; +import com.jsyn.ports.ConnectableInput; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.ports.UnitPort; +import com.softsynth.shared.time.TimeStamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Base class for all unit generators. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public abstract class UnitGenerator { + + private static final Logger LOGGER = LoggerFactory.getLogger(UnitGenerator.class); + + protected static final double VERY_SMALL_FLOAT = 1.0e-26; + + // Some common port names. + public static final String PORT_NAME_INPUT = "Input"; + public static final String PORT_NAME_OUTPUT = "Output"; + public static final String PORT_NAME_PHASE = "Phase"; + public static final String PORT_NAME_FREQUENCY = "Frequency"; + public static final String PORT_NAME_FREQUENCY_SCALER = "FreqScaler"; + public static final String PORT_NAME_AMPLITUDE = "Amplitude"; + public static final String PORT_NAME_PAN = "Pan"; + public static final String PORT_NAME_TIME = "Time"; + public static final String PORT_NAME_CUTOFF = "Cutoff"; + public static final String PORT_NAME_PRESSURE = "Pressure"; + public static final String PORT_NAME_TIMBRE = "Timbre"; + + public static final double FALSE = 0.0; + public static final double TRUE = 1.0; + protected SynthesisEngine synthesisEngine; + private final LinkedHashMap<String, UnitPort> ports = new LinkedHashMap<String, UnitPort>(); + private Circuit circuit; + private long lastFrameCount; + private boolean enabled = true; + private static int nextId; + private final int id = nextId++; + + public int getId() { + return id; + } + + public int getFrameRate() { + // return frameRate; + return synthesisEngine.getFrameRate(); + } + + public double getFramePeriod() { + // return framePeriod; // TODO - Why does OldJSynTestSuite fail if I use this! + return synthesisEngine.getFramePeriod(); + } + + public void addPort(UnitPort port) { + port.setUnitGenerator(this); + // Store in a hash table by name. + ports.put(port.getName().toLowerCase(), port); + } + + public void addPort(UnitPort port, String name) { + port.setName(name); + addPort(port); + } + + /** + * Case-insensitive search for a port by name. + * @param portName + * @return matching port or null + */ + public UnitPort getPortByName(String portName) { + return ports.get(portName.toLowerCase()); + } + + public Collection<UnitPort> getPorts() { + return ports.values(); + } + + /** + * Perform essential synthesis function. + * + * @param start offset into port buffers + * @param limit limit offset into port buffers for loop + */ + public abstract void generate(int start, int limit); + + /** + * Generate a full block. + */ + public void generate() { + generate(0, Synthesizer.FRAMES_PER_BLOCK); + } + + /** + * @return the synthesisEngine + */ + public SynthesisEngine getSynthesisEngine() { + return synthesisEngine; + } + + /** + * @return the Synthesizer + */ + public Synthesizer getSynthesizer() { + return synthesisEngine; + } + + /** + * @param synthesisEngine the synthesisEngine to set + */ + public void setSynthesisEngine(SynthesisEngine synthesisEngine) { + if ((this.synthesisEngine != null) && (this.synthesisEngine != synthesisEngine)) { + throw new RuntimeException("Unit synthesisEngine already set."); + } + this.synthesisEngine = synthesisEngine; + } + + public UnitGenerator getTopUnit() { + UnitGenerator unit = this; + // Climb to top of circuit hierarchy. + while (unit.circuit != null) { + unit = unit.circuit; + } + LOGGER.debug("getTopUnit " + this + " => " + unit); + return unit; + } + + protected void autoStop() { + synthesisEngine.autoStopUnit(getTopUnit()); + } + + /** Calculate signal based on halflife of an exponential decay. */ + public double convertHalfLifeToMultiplier(double halfLife) { + if (halfLife < (2.0 * getFramePeriod())) { + return 1.0; + } else { + // Oddly enough, this code is valid for both PeakFollower and AsymptoticRamp. + return 1.0 - Math.pow(0.5, 1.0 / (halfLife * getSynthesisEngine().getFrameRate())); + } + } + + protected double incrementWrapPhase(double currentPhase, double phaseIncrement) { + currentPhase += phaseIncrement; + + if (currentPhase >= 1.0) { + currentPhase -= 2.0; + } else if (currentPhase < -1.0) { + currentPhase += 2.0; + } + return currentPhase; + } + + /** Calculate rate based on phase going from 0.0 to 1.0 in time. */ + protected double convertTimeToRate(double time) { + double period2X = synthesisEngine.getInverseNyquist(); + if (time < period2X) { + return 1.0; + } else { + return getFramePeriod() / time; + } + } + + /** Flatten output ports so we don't output a changing signal when stopped. */ + public void flattenOutputs() { + for (UnitPort port : ports.values()) { + if (port instanceof UnitOutputPort) { + ((UnitOutputPort) port).flatten(); + } + } + } + + public void setCircuit(Circuit circuit) { + if ((this.circuit != null) && (circuit != null)) { + throw new RuntimeException("Unit is already in a circuit."); + } + // LOGGER.info( "setCircuit in unit " + this + " with circuit " + circuit ); + this.circuit = circuit; + } + + public Circuit getCircuit() { + return circuit; + } + + public void pullData(long frameCount, int start, int limit) { + // Don't generate twice in case the paths merge. + if (enabled && (frameCount > lastFrameCount)) { + // Do this first to block recursion when there is a feedback loop. + lastFrameCount = frameCount; + // Then pull from all the units that are upstream. + for (UnitPort port : ports.values()) { + if (port instanceof ConnectableInput) { + ((ConnectableInput) port).pullData(frameCount, start, limit); + } + } + // Finally generate using outputs of the upstream units. + generate(start, limit); + } + } + + public boolean isEnabled() { + return enabled; + } + + /** + * If enabled, then a unit will execute if its output is connected to another unit that is + * executed. If not enabled then it will not execute and will not pull data from units that are + * connected to its inputs. Disabling a unit at the output of a tree of units can be used to + * turn off the entire tree, thus saving CPU cycles. + * + * @param enabled + * @see UnitGate#setupAutoDisable(UnitGenerator) + * @see #start() + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + if (!enabled) { + flattenOutputs(); + } + } + + /** + * Some units, for example LineOut and FixedRateMonoWriter, will only work if + * started explicitly. Other units will run when downstream units are started. + * + * @return true if you should call start() for this unit + */ + public boolean isStartRequired() { + return false; + } + + /** + * Start executing this unit directly by adding it to a "run list" of units in the synthesis + * engine. This method is normally only called for the final unit in a chain, for example a + * LineOut. When that final unit executes it will "pull" data from any units connected to its + * inputs. Those units will then pull data their inputs until the entire chain is executed. If + * units are connected in a circle then this will be detected and the infinite recursion will be + * blocked. + * + * @see #setEnabled(boolean) + */ + public void start() { + if (getSynthesisEngine() == null) { + throw new RuntimeException("This " + this.getClass().getName() + + " was not add()ed to a Synthesizer."); + } + getSynthesisEngine().startUnit(this); + } + + /** + * Start a unit at the specified time. + * + * @param time + * @see #start() + */ + public void start(double time) { + start(new TimeStamp(time)); + } + + /** + * Start a unit at the specified time. + * + * @param timeStamp + * @see #start() + */ + public void start(TimeStamp timeStamp) { + if (getSynthesisEngine() == null) { + throw new RuntimeException("This " + this.getClass().getName() + + " was not add()ed to a Synthesizer."); + } + getSynthesisEngine().startUnit(this, timeStamp); + } + + /** + * Stop a unit at the specified time. + * + * @param time + * @see #start() + */ + public void stop(double time) { + stop(new TimeStamp(time)); + } + + public void stop() { + getSynthesisEngine().stopUnit(this); + } + + public void stop(TimeStamp timeStamp) { + getSynthesisEngine().stopUnit(this, timeStamp); + } + + /** + * @deprecated ignored, frameRate comes from the SynthesisEngine + * @param rate + */ + @Deprecated + public void setFrameRate(int rate) { + } + + /** Needed by UnitSink */ + public UnitGenerator getUnitGenerator() { + return this; + } + + /** Needed by UnitVoice */ + public void setPort(String portName, double value, TimeStamp timeStamp) { + UnitInputPort port = (UnitInputPort) getPortByName(portName); + // LOGGER.debug("setPort " + port ); + if (port == null) { + LOGGER.warn("port was null for name " + portName + ", " + this.getClass().getName()); + } else { + port.set(value, timeStamp); + } + } + + public void printConnections() { + printConnections(System.out); + } + + public void printConnections(PrintStream out) { + printConnections(out, 0); + } + + public void printConnections(PrintStream out, int level) { + for (UnitPort port : getPorts()) { + if (port instanceof UnitInputPort) { + ((UnitInputPort) port).printConnections(out, level); + } + } + } + +} |