All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jaudiolibs.pipes.Pipe Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2019 Neil C Smith.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3 only, as
 * published by the Free Software Foundation.
 *
 * This code 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 Lesser General Public License
 * version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License version 3
 * along with this work; if not, see http://www.gnu.org/licenses/
 *
 */
package org.jaudiolibs.pipes;

import java.util.ArrayList;
import java.util.List;

/**
 * The base type of the Pipes library. A Pipe is a unit generator, providing the
 * basic building blocks for synthesis and signal-processing. Pipes are joined
 * to each other in a graph-like structure of sources and sinks.
 */
public abstract class Pipe {

    private final List sources;
    private final List sinks;
    private final List buffers;
    private final int sourceCapacity;
    private final int sinkCapacity;

    private long time;
    private long renderReqTime;
    private boolean renderReqCache;
    private int renderIdx = 0;

    /**
     * Base constructor. The capacity, the maximum number of source and sink
     * pipes that can be connected, must be specified.
     *
     * @param sourceCapacity maximum number of sources
     * @param sinkCapacity maximum numbers of sinks
     */
    protected Pipe(int sourceCapacity, int sinkCapacity) {
        sources = sourceCapacity < 8 ? new ArrayList<>(sourceCapacity) : new ArrayList<>();
        sinks = sinkCapacity < 8 ? new ArrayList<>(sinkCapacity) : new ArrayList<>();
        buffers = sourceCapacity < 8 ? new ArrayList<>(sourceCapacity) : new ArrayList<>();
        this.sourceCapacity = sourceCapacity;
        this.sinkCapacity = sinkCapacity;
    }

    /**
     * Add another Pipe as a source.
     *
     * @param source pipe to add
     */
    public final void addSource(Pipe source) {
        source.registerSink(this);
        try {
            registerSource(source);
        } catch (RuntimeException ex) {
            source.unregisterSink(this);
            throw ex;
        }
    }

    /**
     * Remove a source Pipe.
     *
     * @param source pipe to remove
     */
    public final void removeSource(Pipe source) {
        source.unregisterSink(this);
        unregisterSource(source);
    }

    /**
     * Query the actual number of connected sources.
     *
     * @return number of sources
     */
    public int getSourceCount() {
        return sources.size();
    }

    /**
     * Query the maximum number of sources supported.
     *
     * @return maximum number of sources
     */
    public int getSourceCapacity() {
        return sourceCapacity;
    }

    /**
     * Get the source Pipe at the specified index, between 0 and
     * getSourceCount() - 1.
     *
     * @param idx index
     * @return source pipe at index
     */
    public Pipe getSource(int idx) {
        return sources.get(idx);
    }

    /**
     * Query the actual number of connected sinks.
     *
     * @return number of sinks
     */
    public int getSinkCount() {
        return sinks.size();
    }

    /**
     * Query the maximum number of sinks supported.
     *
     * @return maximum number of sinks
     */
    public int getSinkCapacity() {
        return sinkCapacity;
    }

    /**
     * Get the sink Pipe at the specified index, between 0 and getSinkCount() -
     * 1
     *
     * @param idx index
     * @return sink pipe at index
     */
    public Pipe getSink(int idx) {
        return sinks.get(idx);
    }

    /**
     * Reset the Pipe to its default state. This is an empty hook that may be
     * optionally implemented by sub-classes. The Pipe should set all
     * non-transient state to its default values - eg. the frequency of an
     * oscillator, but not its phase.
     */
    public void reset() {
    }

    /**
     * Process output for the requested sink and time.
     * 

* Sub-classes should normally just implement * {@link #process(java.util.List)} * * @param sink requesting sink * @param outputBuffer buffer to write in to * @param time nanoseconds time of the requested buffer */ protected void process(Pipe sink, Buffer outputBuffer, long time) { int sinkIndex = sinks.indexOf(sink); if (sinkIndex < 0) { // throw exception? return; } boolean inPlace = sinks.size() == 1 && sources.size() < 2; if (this.time != time) { boolean processRequired = isProcessRequired(time); this.time = time; if (inPlace) { processInPlace(outputBuffer, processRequired, time); } else { processCached(outputBuffer, processRequired, time); } } if (!inPlace && sink.isOutputRequired(this, time)) { writeOutput(buffers, outputBuffer, sinkIndex); } } private void processInPlace(Buffer outputBuffer, boolean processRequired, long time) { if (!buffers.isEmpty()) { buffers.forEach(Buffer::dispose); buffers.clear(); } if (sources.isEmpty()) { outputBuffer.clear(); } else { sources.get(0).process(this, outputBuffer, time); } if (processRequired) { buffers.add(outputBuffer); process(buffers); buffers.clear(); } else { skip(outputBuffer.getSize()); } } private void processCached(Buffer outputBuffer, boolean processRequired, long time) { int bufferCount = Math.max(sources.size(), sinks.size()); while (buffers.size() > bufferCount) { buffers.remove(buffers.size() - 1); } for (int i = 0; i < bufferCount; i++) { Buffer inputBuffer; if (i < buffers.size()) { inputBuffer = buffers.get(i); if (!outputBuffer.isCompatible(inputBuffer)) { inputBuffer.dispose(); inputBuffer = outputBuffer.createBuffer(); buffers.set(i, inputBuffer); } } else { inputBuffer = outputBuffer.createBuffer(); buffers.add(inputBuffer); } if (i < sources.size()) { sources.get(i).process(this, inputBuffer, time); } else { inputBuffer.clear(); } } if (processRequired) { process(buffers); } else { skip(outputBuffer.getSize()); } } /** * Process buffers. Buffer data should be processed in-place. This method * will be called once per processing cycle (time). The buffer list will be * the maximum of source count or sink count. For buffers without a matching * input Pipe the buffer will be clear. * * @param buffers list of buffers to process */ protected abstract void process(List buffers); /** * A hook called instead of {@link #process(java.util.List)} if no sink * requires output during processing. The number of samples being skipped * (equivalent to the non-processed buffer's size) is given. * * @param samples number of samples skipped */ protected void skip(int samples) { // default no op hook } /** * Write data to the output buffer. This method is only called if there is * more than one sink Pipe and so the output buffer cannot be processed in * place. By default this method will write the data from the processed * input buffer at the same index if there is one, or otherwise clear the * buffer. * * @param inputBuffers list of processed input buffers * @param outputBuffer output buffer to write to * @param sinkIndex index of the sink owning the buffer */ protected void writeOutput(List inputBuffers, Buffer outputBuffer, int sinkIndex) { if (sinkIndex < inputBuffers.size()) { outputBuffer.copy(inputBuffers.get(sinkIndex)); } else { outputBuffer.clear(); } } /** * Query whether this Pipe requires the output of the provided source Pipe. * This method is generally called by the source Pipe during processing. By * default, this method will return true if any sink of this Pipe requires * the output of this Pipe. Sub-classes can override this to switch off * processing eg. a mixer for silent channels * * @param source input source Pipe * @param time processing time in nanoseconds * @return true if this Pipe requires the output of the provided source */ protected boolean isOutputRequired(Pipe source, long time) { return isProcessRequired(time); } /** * Query whether this Pipe requires processing for the processing cycle at * time. By default this method will check if any sink requires output. * * @param time processing time in nanoseconds * @return true if this Pipe should process buffers */ protected boolean isProcessRequired(long time) { if (sinks.size() == 1) { return simpleOutputCheck(time); } else { return multipleOutputCheck(time); } } /** * Register the provided source with this Pipe. Called as part of * {@link #addSource(org.jaudiolibs.pipes.Pipe)}. * * @param source pipe being added as source */ protected void registerSource(Pipe source) { if (source == null) { throw new NullPointerException(); } if (sources.size() == sourceCapacity) { throw new SinkIsFullException(); } if (sources.contains(source)) { throw new IllegalArgumentException(); } sources.add(source); } /** * Unregister the provided source with this Pipe. Called as part of * {@link #removeSource(org.jaudiolibs.pipes.Pipe)} * * @param source pipe being removed as source */ protected void unregisterSource(Pipe source) { sources.remove(source); } /** * Register the provided sink with this Pipe. Called as part of * {@link #addSource(org.jaudiolibs.pipes.Pipe)} being called on the sink * Pipe. * * @param sink pipe being added as sink */ protected void registerSink(Pipe sink) { if (sink == null) { throw new NullPointerException(); } if (sinks.size() == sinkCapacity) { throw new SourceIsFullException(); } if (sinks.contains(sink)) { throw new IllegalArgumentException(); } sinks.add(sink); } /** * Unregister the provided sink with this Pipe. Called as part of * {@link #removeSource(org.jaudiolibs.pipes.Pipe)} being called on the sink * Pipe. * * @param sink pipe being removed as sink */ protected void unregisterSink(Pipe sink) { sinks.remove(sink); } private boolean simpleOutputCheck(long time) { if (time != renderReqTime) { renderReqTime = time; renderReqCache = sinks.get(0).isOutputRequired(this, time); } return renderReqCache; } private boolean multipleOutputCheck(long time) { if (renderIdx > 0) { while (renderIdx < sinks.size()) { if (sinks.get(renderIdx++).isOutputRequired(this, time)) { renderIdx = 0; return true; } } return false; } else { if (renderReqTime != time) { renderReqTime = time; renderReqCache = false; while (renderIdx < sinks.size()) { if (sinks.get(renderIdx++).isOutputRequired(this, time)) { renderReqCache = true; break; } } renderIdx = 0; } return renderReqCache; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy