mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-27 12:18:55 +01:00
ad86c297fb
Closes #1626
449 lines
13 KiB
C++
Vendored
449 lines
13 KiB
C++
Vendored
/*
|
|
* Copyright 2015 The Android Open Source Project
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* FlowGraph.h
|
|
*
|
|
* Processing node and ports that can be used in a simple data flow graph.
|
|
* This was designed to work with audio but could be used for other
|
|
* types of data.
|
|
*/
|
|
|
|
#ifndef FLOWGRAPH_FLOW_GRAPH_NODE_H
|
|
#define FLOWGRAPH_FLOW_GRAPH_NODE_H
|
|
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <math.h>
|
|
#include <memory>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <vector>
|
|
|
|
// TODO Move these classes into separate files.
|
|
// TODO Review use of raw pointers for connect(). Maybe use smart pointers but need to avoid
|
|
// run-time deallocation in audio thread.
|
|
|
|
// Set this to 1 if using it inside the Android framework.
|
|
// This code is kept here so that it can be moved easily between Oboe and AAudio.
|
|
#ifndef FLOWGRAPH_ANDROID_INTERNAL
|
|
#define FLOWGRAPH_ANDROID_INTERNAL 0
|
|
#endif
|
|
|
|
// Set this to a name that will prevent AAudio from calling into Oboe.
|
|
// AAudio and Oboe both use a version of this flowgraph package.
|
|
// There was a problem in the unit tests where AAudio would call a constructor
|
|
// in AAudio and then call a destructor in Oboe! That caused memory corruption.
|
|
// For more details, see Issue #930.
|
|
#ifndef FLOWGRAPH_OUTER_NAMESPACE
|
|
#define FLOWGRAPH_OUTER_NAMESPACE oboe
|
|
#endif
|
|
|
|
namespace FLOWGRAPH_OUTER_NAMESPACE {
|
|
namespace flowgraph {
|
|
|
|
// Default block size that can be overridden when the FlowGraphPortFloat is created.
|
|
// If it is too small then we will have too much overhead from switching between nodes.
|
|
// If it is too high then we will thrash the caches.
|
|
constexpr int kDefaultBufferSize = 8; // arbitrary
|
|
|
|
class FlowGraphPort;
|
|
class FlowGraphPortFloatInput;
|
|
|
|
/***************************************************************************/
|
|
/**
|
|
* Base class for all nodes in the flowgraph.
|
|
*/
|
|
class FlowGraphNode {
|
|
public:
|
|
FlowGraphNode() {}
|
|
virtual ~FlowGraphNode() = default;
|
|
|
|
/**
|
|
* Read from the input ports,
|
|
* generate multiple frames of data then write the results to the output ports.
|
|
*
|
|
* @param numFrames maximum number of frames requested for processing
|
|
* @return number of frames actually processed
|
|
*/
|
|
virtual int32_t onProcess(int32_t numFrames) = 0;
|
|
|
|
/**
|
|
* If the callCount is at or after the previous callCount then call
|
|
* pullData on all of the upstreamNodes.
|
|
* Then call onProcess().
|
|
* This prevents infinite recursion in case of cyclic graphs.
|
|
* It also prevents nodes upstream from a branch from being executed twice.
|
|
*
|
|
* @param callCount
|
|
* @param numFrames
|
|
* @return number of frames valid
|
|
*/
|
|
int32_t pullData(int32_t numFrames, int64_t callCount);
|
|
|
|
/**
|
|
* Recursively reset all the nodes in the graph, starting from a Sink.
|
|
*
|
|
* This must not be called at the same time as pullData!
|
|
*/
|
|
void pullReset();
|
|
|
|
/**
|
|
* Reset framePosition counters.
|
|
*/
|
|
virtual void reset();
|
|
|
|
void addInputPort(FlowGraphPort &port) {
|
|
mInputPorts.push_back(port);
|
|
}
|
|
|
|
bool isDataPulledAutomatically() const {
|
|
return mDataPulledAutomatically;
|
|
}
|
|
|
|
/**
|
|
* Set true if you want the data pulled through the graph automatically.
|
|
* This is the default.
|
|
*
|
|
* Set false if you want to pull the data from the input ports in the onProcess() method.
|
|
* You might do this, for example, in a sample rate converting node.
|
|
*
|
|
* @param automatic
|
|
*/
|
|
void setDataPulledAutomatically(bool automatic) {
|
|
mDataPulledAutomatically = automatic;
|
|
}
|
|
|
|
virtual const char *getName() {
|
|
return "FlowGraph";
|
|
}
|
|
|
|
int64_t getLastCallCount() {
|
|
return mLastCallCount;
|
|
}
|
|
|
|
protected:
|
|
|
|
static constexpr int64_t kInitialCallCount = -1;
|
|
int64_t mLastCallCount = kInitialCallCount;
|
|
|
|
std::vector<std::reference_wrapper<FlowGraphPort>> mInputPorts;
|
|
|
|
private:
|
|
bool mDataPulledAutomatically = true;
|
|
bool mBlockRecursion = false;
|
|
int32_t mLastFrameCount = 0;
|
|
|
|
};
|
|
|
|
/***************************************************************************/
|
|
/**
|
|
* This is a connector that allows data to flow between modules.
|
|
*
|
|
* The ports are the primary means of interacting with a module.
|
|
* So they are generally declared as public.
|
|
*
|
|
*/
|
|
class FlowGraphPort {
|
|
public:
|
|
FlowGraphPort(FlowGraphNode &parent, int32_t samplesPerFrame)
|
|
: mContainingNode(parent)
|
|
, mSamplesPerFrame(samplesPerFrame) {
|
|
}
|
|
|
|
virtual ~FlowGraphPort() = default;
|
|
|
|
// Ports are often declared public. So let's make them non-copyable.
|
|
FlowGraphPort(const FlowGraphPort&) = delete;
|
|
FlowGraphPort& operator=(const FlowGraphPort&) = delete;
|
|
|
|
int32_t getSamplesPerFrame() const {
|
|
return mSamplesPerFrame;
|
|
}
|
|
|
|
virtual int32_t pullData(int64_t framePosition, int32_t numFrames) = 0;
|
|
|
|
virtual void pullReset() {}
|
|
|
|
protected:
|
|
FlowGraphNode &mContainingNode;
|
|
|
|
private:
|
|
const int32_t mSamplesPerFrame = 1;
|
|
};
|
|
|
|
/***************************************************************************/
|
|
/**
|
|
* This port contains a 32-bit float buffer that can contain several frames of data.
|
|
* Processing the data in a block improves performance.
|
|
*
|
|
* The size is framesPerBuffer * samplesPerFrame).
|
|
*/
|
|
class FlowGraphPortFloat : public FlowGraphPort {
|
|
public:
|
|
FlowGraphPortFloat(FlowGraphNode &parent,
|
|
int32_t samplesPerFrame,
|
|
int32_t framesPerBuffer = kDefaultBufferSize
|
|
);
|
|
|
|
virtual ~FlowGraphPortFloat() = default;
|
|
|
|
int32_t getFramesPerBuffer() const {
|
|
return mFramesPerBuffer;
|
|
}
|
|
|
|
protected:
|
|
|
|
/**
|
|
* @return buffer internal to the port or from a connected port
|
|
*/
|
|
virtual float *getBuffer() {
|
|
return mBuffer.get();
|
|
}
|
|
|
|
private:
|
|
const int32_t mFramesPerBuffer = 1;
|
|
std::unique_ptr<float[]> mBuffer; // allocated in constructor
|
|
};
|
|
|
|
/***************************************************************************/
|
|
/**
|
|
* The results of a node's processing are stored in the buffers of the output ports.
|
|
*/
|
|
class FlowGraphPortFloatOutput : public FlowGraphPortFloat {
|
|
public:
|
|
FlowGraphPortFloatOutput(FlowGraphNode &parent, int32_t samplesPerFrame)
|
|
: FlowGraphPortFloat(parent, samplesPerFrame) {
|
|
}
|
|
|
|
virtual ~FlowGraphPortFloatOutput() = default;
|
|
|
|
using FlowGraphPortFloat::getBuffer;
|
|
|
|
/**
|
|
* Connect to the input of another module.
|
|
* An input port can only have one connection.
|
|
* An output port can have multiple connections.
|
|
* If you connect a second output port to an input port
|
|
* then it overwrites the previous connection.
|
|
*
|
|
* This not thread safe. Do not modify the graph topology from another thread while running.
|
|
* Also do not delete a module while it is connected to another port if the graph is running.
|
|
*/
|
|
void connect(FlowGraphPortFloatInput *port);
|
|
|
|
/**
|
|
* Disconnect from the input of another module.
|
|
* This not thread safe.
|
|
*/
|
|
void disconnect(FlowGraphPortFloatInput *port);
|
|
|
|
/**
|
|
* Call the parent module's onProcess() method.
|
|
* That may pull data from its inputs and recursively
|
|
* process the entire graph.
|
|
* @return number of frames actually pulled
|
|
*/
|
|
int32_t pullData(int64_t framePosition, int32_t numFrames) override;
|
|
|
|
|
|
void pullReset() override;
|
|
|
|
};
|
|
|
|
/***************************************************************************/
|
|
|
|
/**
|
|
* An input port for streaming audio data.
|
|
* You can set a value that will be used for processing.
|
|
* If you connect an output port to this port then its value will be used instead.
|
|
*/
|
|
class FlowGraphPortFloatInput : public FlowGraphPortFloat {
|
|
public:
|
|
FlowGraphPortFloatInput(FlowGraphNode &parent, int32_t samplesPerFrame)
|
|
: FlowGraphPortFloat(parent, samplesPerFrame) {
|
|
// Add to parent so it can pull data from each input.
|
|
parent.addInputPort(*this);
|
|
}
|
|
|
|
virtual ~FlowGraphPortFloatInput() = default;
|
|
|
|
/**
|
|
* If connected to an output port then this will return
|
|
* that output ports buffers.
|
|
* If not connected then it returns the input ports own buffer
|
|
* which can be loaded using setValue().
|
|
*/
|
|
float *getBuffer() override;
|
|
|
|
/**
|
|
* Write every value of the float buffer.
|
|
* This value will be ignored if an output port is connected
|
|
* to this port.
|
|
*/
|
|
void setValue(float value) {
|
|
int numFloats = kDefaultBufferSize * getSamplesPerFrame();
|
|
float *buffer = getBuffer();
|
|
for (int i = 0; i < numFloats; i++) {
|
|
*buffer++ = value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Connect to the output of another module.
|
|
* An input port can only have one connection.
|
|
* An output port can have multiple connections.
|
|
* This not thread safe.
|
|
*/
|
|
void connect(FlowGraphPortFloatOutput *port) {
|
|
assert(getSamplesPerFrame() == port->getSamplesPerFrame());
|
|
mConnected = port;
|
|
}
|
|
|
|
void disconnect(FlowGraphPortFloatOutput *port) {
|
|
assert(mConnected == port);
|
|
(void) port;
|
|
mConnected = nullptr;
|
|
}
|
|
|
|
void disconnect() {
|
|
mConnected = nullptr;
|
|
}
|
|
|
|
/**
|
|
* Pull data from any output port that is connected.
|
|
*/
|
|
int32_t pullData(int64_t framePosition, int32_t numFrames) override;
|
|
|
|
void pullReset() override;
|
|
|
|
private:
|
|
FlowGraphPortFloatOutput *mConnected = nullptr;
|
|
};
|
|
|
|
/***************************************************************************/
|
|
|
|
/**
|
|
* Base class for an edge node in a graph that has no upstream nodes.
|
|
* It outputs data but does not consume data.
|
|
* By default, it will read its data from an external buffer.
|
|
*/
|
|
class FlowGraphSource : public FlowGraphNode {
|
|
public:
|
|
explicit FlowGraphSource(int32_t channelCount)
|
|
: output(*this, channelCount) {
|
|
}
|
|
|
|
virtual ~FlowGraphSource() = default;
|
|
|
|
FlowGraphPortFloatOutput output;
|
|
};
|
|
|
|
/***************************************************************************/
|
|
|
|
/**
|
|
* Base class for an edge node in a graph that has no upstream nodes.
|
|
* It outputs data but does not consume data.
|
|
* By default, it will read its data from an external buffer.
|
|
*/
|
|
class FlowGraphSourceBuffered : public FlowGraphSource {
|
|
public:
|
|
explicit FlowGraphSourceBuffered(int32_t channelCount)
|
|
: FlowGraphSource(channelCount) {}
|
|
|
|
virtual ~FlowGraphSourceBuffered() = default;
|
|
|
|
/**
|
|
* Specify buffer that the node will read from.
|
|
*
|
|
* @param data TODO Consider using std::shared_ptr.
|
|
* @param numFrames
|
|
*/
|
|
void setData(const void *data, int32_t numFrames) {
|
|
mData = data;
|
|
mSizeInFrames = numFrames;
|
|
mFrameIndex = 0;
|
|
}
|
|
|
|
protected:
|
|
const void *mData = nullptr;
|
|
int32_t mSizeInFrames = 0; // number of frames in mData
|
|
int32_t mFrameIndex = 0; // index of next frame to be processed
|
|
};
|
|
|
|
/***************************************************************************/
|
|
/**
|
|
* Base class for an edge node in a graph that has no downstream nodes.
|
|
* It consumes data but does not output data.
|
|
* This graph will be executed when data is read() from this node
|
|
* by pulling data from upstream nodes.
|
|
*/
|
|
class FlowGraphSink : public FlowGraphNode {
|
|
public:
|
|
explicit FlowGraphSink(int32_t channelCount)
|
|
: input(*this, channelCount) {
|
|
}
|
|
|
|
virtual ~FlowGraphSink() = default;
|
|
|
|
FlowGraphPortFloatInput input;
|
|
|
|
/**
|
|
* Dummy processor. The work happens in the read() method.
|
|
*
|
|
* @param numFrames
|
|
* @return number of frames actually processed
|
|
*/
|
|
int32_t onProcess(int32_t numFrames) override {
|
|
return numFrames;
|
|
}
|
|
|
|
virtual int32_t read(void *data, int32_t numFrames) = 0;
|
|
|
|
protected:
|
|
/**
|
|
* Pull data through the graph using this nodes last callCount.
|
|
* @param numFrames
|
|
* @return
|
|
*/
|
|
int32_t pullData(int32_t numFrames);
|
|
};
|
|
|
|
/***************************************************************************/
|
|
/**
|
|
* Base class for a node that has an input and an output with the same number of channels.
|
|
* This may include traditional filters, eg. FIR, but also include
|
|
* any processing node that converts input to output.
|
|
*/
|
|
class FlowGraphFilter : public FlowGraphNode {
|
|
public:
|
|
explicit FlowGraphFilter(int32_t channelCount)
|
|
: input(*this, channelCount)
|
|
, output(*this, channelCount) {
|
|
}
|
|
|
|
virtual ~FlowGraphFilter() = default;
|
|
|
|
FlowGraphPortFloatInput input;
|
|
FlowGraphPortFloatOutput output;
|
|
};
|
|
|
|
} /* namespace flowgraph */
|
|
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
|
|
|
|
#endif /* FLOWGRAPH_FLOW_GRAPH_NODE_H */
|