mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-23 01:44:22 +00:00
Fix for audio plugin builds on win32. AudioProcessorGraph optimisations. Access to min/max values in audio thumbnails. More metadata support for wav and aiff formats.
This commit is contained in:
parent
345c7aa23c
commit
d97ce5f9ee
20 changed files with 2256 additions and 926 deletions
|
|
@ -295,7 +295,7 @@ static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcep
|
|||
}
|
||||
|
||||
XmlElement* e = new XmlElement ("FILTER");
|
||||
e->setAttribute ("uid", (int) node->id);
|
||||
e->setAttribute ("uid", (int) node->nodeId);
|
||||
e->setAttribute ("x", node->properties ["x"].toString());
|
||||
e->setAttribute ("y", node->properties ["y"].toString());
|
||||
e->setAttribute ("uiLastX", node->properties ["uiLastX"].toString());
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ PluginWindow::PluginWindow (Component* const uiComp,
|
|||
void PluginWindow::closeCurrentlyOpenWindowsFor (const uint32 nodeId)
|
||||
{
|
||||
for (int i = activePluginWindows.size(); --i >= 0;)
|
||||
if (activePluginWindows.getUnchecked(i)->owner->id == nodeId)
|
||||
if (activePluginWindows.getUnchecked(i)->owner->nodeId == nodeId)
|
||||
delete activePluginWindows.getUnchecked(i);
|
||||
}
|
||||
|
||||
|
|
@ -839,9 +839,9 @@ void GraphEditorPanel::updateComponents()
|
|||
{
|
||||
const AudioProcessorGraph::Node::Ptr f (graph.getNode (i));
|
||||
|
||||
if (getComponentForFilter (f->id) == 0)
|
||||
if (getComponentForFilter (f->nodeId) == 0)
|
||||
{
|
||||
FilterComponent* const comp = new FilterComponent (graph, f->id);
|
||||
FilterComponent* const comp = new FilterComponent (graph, f->nodeId);
|
||||
addAndMakeVisible (comp);
|
||||
comp->update();
|
||||
}
|
||||
|
|
|
|||
1492
juce_amalgamated.cpp
1492
juce_amalgamated.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -73,7 +73,7 @@ namespace JuceDummyNamespace {}
|
|||
*/
|
||||
#define JUCE_MAJOR_VERSION 1
|
||||
#define JUCE_MINOR_VERSION 53
|
||||
#define JUCE_BUILDNUMBER 73
|
||||
#define JUCE_BUILDNUMBER 74
|
||||
|
||||
/** Current Juce version number.
|
||||
|
||||
|
|
@ -23112,8 +23112,9 @@ public:
|
|||
/** Returns one of the registered clients. */
|
||||
TimeSliceClient* getClient (int index) const;
|
||||
|
||||
/** @internal */
|
||||
#ifndef DOXYGEN
|
||||
void run();
|
||||
#endif
|
||||
|
||||
private:
|
||||
CriticalSection callbackLock, listLock;
|
||||
|
|
@ -33344,8 +33345,7 @@ public:
|
|||
|
||||
@see MessageManager, DeletedAtShutdown
|
||||
*/
|
||||
class JUCE_API JUCEApplication : public ApplicationCommandTarget,
|
||||
private ActionListener
|
||||
class JUCE_API JUCEApplication : public ApplicationCommandTarget
|
||||
{
|
||||
protected:
|
||||
|
||||
|
|
@ -33497,18 +33497,9 @@ public:
|
|||
*/
|
||||
const String getCommandLineParameters() const noexcept { return commandLineParameters; }
|
||||
|
||||
// These are used by the START_JUCE_APPLICATION() macro and aren't for public use.
|
||||
|
||||
/** @internal */
|
||||
static int main (const String& commandLine);
|
||||
/** @internal */
|
||||
static int main (int argc, const char* argv[]);
|
||||
/** @internal */
|
||||
static void sendUnhandledException (const std::exception* e, const char* sourceFile, int lineNumber);
|
||||
|
||||
/** Returns true if this executable is running as an app (as opposed to being a plugin
|
||||
or other kind of shared library. */
|
||||
static inline bool isStandaloneApp() noexcept { return createInstance != 0; }
|
||||
static inline bool isStandaloneApp() noexcept { return createInstance != 0; }
|
||||
|
||||
/** @internal */
|
||||
ApplicationCommandTarget* getNextCommandTarget();
|
||||
|
|
@ -33518,26 +33509,28 @@ public:
|
|||
void getAllCommands (Array <CommandID>& commands);
|
||||
/** @internal */
|
||||
bool perform (const InvocationInfo& info);
|
||||
/** @internal */
|
||||
void actionListenerCallback (const String& message);
|
||||
/** @internal */
|
||||
|
||||
#ifndef DOXYGEN
|
||||
// The following methods are internal calls - not for public use.
|
||||
static int main (const String& commandLine);
|
||||
static int main (int argc, const char* argv[]);
|
||||
static void sendUnhandledException (const std::exception* e, const char* sourceFile, int lineNumber);
|
||||
bool initialiseApp (const String& commandLine);
|
||||
/** @internal */
|
||||
int shutdownApp();
|
||||
/** @internal */
|
||||
static void appWillTerminateByForce();
|
||||
/** @internal */
|
||||
typedef JUCEApplication* (*CreateInstanceFunction)();
|
||||
/** @internal */
|
||||
static CreateInstanceFunction createInstance;
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
||||
static JUCEApplication* appInstance;
|
||||
|
||||
String commandLineParameters;
|
||||
ScopedPointer<InterProcessLock> appLock;
|
||||
ScopedPointer<ActionListener> broadcastCallback;
|
||||
int appReturnValue;
|
||||
bool stillInitialising;
|
||||
ScopedPointer<InterProcessLock> appLock;
|
||||
static JUCEApplication* appInstance;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (JUCEApplication);
|
||||
};
|
||||
|
|
@ -36550,9 +36543,10 @@ public:
|
|||
const Array <int> getPossibleBitDepths();
|
||||
bool canDoStereo();
|
||||
bool canDoMono();
|
||||
#if JUCE_MAC
|
||||
|
||||
#if JUCE_MAC
|
||||
bool canHandleFile (const File& fileToTest);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
AudioFormatReader* createReaderFor (InputStream* sourceStream,
|
||||
bool deleteStreamIfOpeningFails);
|
||||
|
|
@ -37224,12 +37218,19 @@ public:
|
|||
*/
|
||||
float getApproximatePeak() const;
|
||||
|
||||
/** Reads the approximate min and max levels from a section of the thumbnail.
|
||||
The lowest and highest samples are returned in minValue and maxValue, but obviously
|
||||
because the thumb only stores low-resolution data, these numbers will only be a rough
|
||||
approximation of the true values.
|
||||
*/
|
||||
void getApproximateMinMax (double startTime, double endTime, int channelIndex,
|
||||
float& minValue, float& maxValue) const noexcept;
|
||||
|
||||
/** Returns the hash code that was set by setSource() or setReader(). */
|
||||
int64 getHashCode() const;
|
||||
|
||||
#ifndef DOXYGEN
|
||||
// (this is only public to avoid a VC6 bug)
|
||||
class LevelDataSource;
|
||||
class LevelDataSource; // (this is only public to avoid a VC6 bug)
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
|
@ -47098,10 +47099,9 @@ public:
|
|||
public:
|
||||
|
||||
/** The ID number assigned to this node.
|
||||
|
||||
This is assigned by the graph that owns it, and can't be changed.
|
||||
*/
|
||||
const uint32 id;
|
||||
const uint32 nodeId;
|
||||
|
||||
/** The actual processor object that this node represents. */
|
||||
AudioProcessor* getProcessor() const noexcept { return processor; }
|
||||
|
|
@ -47125,7 +47125,7 @@ public:
|
|||
const ScopedPointer<AudioProcessor> processor;
|
||||
bool isPrepared;
|
||||
|
||||
Node (uint32 id, AudioProcessor* processor);
|
||||
Node (uint32 nodeId, AudioProcessor* processor) noexcept;
|
||||
|
||||
void prepare (double sampleRate, int blockSize, AudioProcessorGraph* graph);
|
||||
void unprepare();
|
||||
|
|
@ -47141,6 +47141,9 @@ public:
|
|||
{
|
||||
public:
|
||||
|
||||
Connection (uint32 sourceNodeId, int sourceChannelIndex,
|
||||
uint32 destNodeId, int destChannelIndex) noexcept;
|
||||
|
||||
/** The ID number of the node which is the input source for this connection.
|
||||
@see AudioProcessorGraph::getNodeForId
|
||||
*/
|
||||
|
|
@ -48766,8 +48769,7 @@ public:
|
|||
@returns the value that the callback function returns.
|
||||
@see MessageManagerLock
|
||||
*/
|
||||
void* callFunctionOnMessageThread (MessageCallbackFunction* callback,
|
||||
void* userData);
|
||||
void* callFunctionOnMessageThread (MessageCallbackFunction* callback, void* userData);
|
||||
|
||||
/** Returns true if the caller-thread is the message thread. */
|
||||
bool isThisTheMessageThread() const noexcept;
|
||||
|
|
@ -48815,12 +48817,12 @@ public:
|
|||
/** Deregisters a broadcast listener. */
|
||||
void deregisterBroadcastListener (ActionListener* listener);
|
||||
|
||||
/** @internal */
|
||||
#ifndef DOXYGEN
|
||||
// Internal methods - do not use!
|
||||
void deliverMessage (Message*);
|
||||
/** @internal */
|
||||
void deliverBroadcastMessage (const String&);
|
||||
/** @internal */
|
||||
~MessageManager() noexcept;
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
||||
|
|
@ -48946,7 +48948,6 @@ public:
|
|||
~MessageManagerLock() noexcept;
|
||||
|
||||
/** Returns true if the lock was successfully acquired.
|
||||
|
||||
(See the constructor that takes a Thread for more info).
|
||||
*/
|
||||
bool lockWasGained() const noexcept { return locked; }
|
||||
|
|
@ -56566,7 +56567,7 @@ protected:
|
|||
/** @internal */
|
||||
int getDesktopWindowStyleFlags() const;
|
||||
|
||||
#if JUCE_DEBUG
|
||||
#if JUCE_DEBUG
|
||||
/** Overridden to warn people about adding components directly to this component
|
||||
instead of using setContentOwned().
|
||||
|
||||
|
|
@ -56581,7 +56582,7 @@ protected:
|
|||
a base-class method call to Component::addAndMakeVisible(), to side-step this warning.
|
||||
*/
|
||||
void addAndMakeVisible (Component* child, int zOrder = -1);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
ScopedPointer <ResizableCornerComponent> resizableCorner;
|
||||
ScopedPointer <ResizableBorderComponent> resizableBorder;
|
||||
|
|
@ -59438,6 +59439,7 @@ public:
|
|||
and feel class how this is used. */
|
||||
};
|
||||
|
||||
#ifndef DOXYGEN
|
||||
/** @internal */
|
||||
void paint (Graphics& g);
|
||||
/** @internal */
|
||||
|
|
@ -59460,6 +59462,7 @@ public:
|
|||
void parentHierarchyChanged();
|
||||
/** @internal */
|
||||
const Rectangle<int> getTitleBarArea();
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
||||
|
|
@ -66942,8 +66945,9 @@ public:
|
|||
void removeListener (Listener* listenerToRemove);
|
||||
|
||||
protected:
|
||||
/** @internal */
|
||||
#ifndef DOXYGEN
|
||||
CameraDevice (const String& name, int index);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void* internal;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,22 @@ BEGIN_JUCE_NAMESPACE
|
|||
extern void juce_initialiseMacMainMenu();
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
class AppBroadcastCallback : public ActionListener
|
||||
{
|
||||
public:
|
||||
AppBroadcastCallback() { MessageManager::getInstance()->registerBroadcastListener (this); }
|
||||
~AppBroadcastCallback() { MessageManager::getInstance()->deregisterBroadcastListener (this); }
|
||||
|
||||
void actionListenerCallback (const String& message)
|
||||
{
|
||||
JUCEApplication* const app = JUCEApplication::getInstance();
|
||||
|
||||
if (app != 0 && message.startsWith (app->getApplicationName() + "/"))
|
||||
app->anotherInstanceStarted (message.substring (app->getApplicationName().length() + 1));
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
JUCEApplication::JUCEApplication()
|
||||
: appReturnValue (0),
|
||||
|
|
@ -87,12 +103,6 @@ void JUCEApplication::setApplicationReturnValue (const int newReturnValue) noexc
|
|||
appReturnValue = newReturnValue;
|
||||
}
|
||||
|
||||
void JUCEApplication::actionListenerCallback (const String& message)
|
||||
{
|
||||
if (message.startsWith (getApplicationName() + "/"))
|
||||
anotherInstanceStarted (message.substring (getApplicationName().length() + 1));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void JUCEApplication::unhandledException (const std::exception*,
|
||||
const String&,
|
||||
|
|
@ -149,7 +159,7 @@ bool JUCEApplication::initialiseApp (const String& commandLine)
|
|||
{
|
||||
commandLineParameters = commandLine.trim();
|
||||
|
||||
#if ! JUCE_IOS
|
||||
#if ! JUCE_IOS
|
||||
jassert (appLock == nullptr); // initialiseApp must only be called once!
|
||||
|
||||
if (! moreThanOneInstanceAllowed())
|
||||
|
|
@ -165,17 +175,18 @@ bool JUCEApplication::initialiseApp (const String& commandLine)
|
|||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// let the app do its setting-up..
|
||||
initialise (commandLineParameters);
|
||||
|
||||
#if JUCE_MAC
|
||||
#if JUCE_MAC
|
||||
juce_initialiseMacMainMenu(); // needs to be called after the app object has created, to get its name
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// register for broadcast new app messages
|
||||
MessageManager::getInstance()->registerBroadcastListener (this);
|
||||
#if ! JUCE_IOS
|
||||
broadcastCallback = new AppBroadcastCallback();
|
||||
#endif
|
||||
|
||||
stillInitialising = false;
|
||||
return true;
|
||||
|
|
@ -185,7 +196,7 @@ int JUCEApplication::shutdownApp()
|
|||
{
|
||||
jassert (appInstance == this);
|
||||
|
||||
MessageManager::getInstance()->deregisterBroadcastListener (this);
|
||||
broadcastCallback = nullptr;
|
||||
|
||||
JUCE_TRY
|
||||
{
|
||||
|
|
@ -249,20 +260,20 @@ int JUCEApplication::main (int argc, const char* argv[])
|
|||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
|
||||
#if ! JUCE_WINDOWS
|
||||
#if ! JUCE_WINDOWS
|
||||
jassert (createInstance != nullptr);
|
||||
juce_Argv0 = argv[0];
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if JUCE_IOS
|
||||
#if JUCE_IOS
|
||||
return juce_iOSMain (argc, argv);
|
||||
#else
|
||||
#else
|
||||
String cmd;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
cmd << argv[i] << ' ';
|
||||
|
||||
return JUCEApplication::main (cmd);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -87,8 +87,7 @@
|
|||
|
||||
@see MessageManager, DeletedAtShutdown
|
||||
*/
|
||||
class JUCE_API JUCEApplication : public ApplicationCommandTarget,
|
||||
private ActionListener
|
||||
class JUCE_API JUCEApplication : public ApplicationCommandTarget
|
||||
{
|
||||
protected:
|
||||
//==============================================================================
|
||||
|
|
@ -244,20 +243,11 @@ public:
|
|||
*/
|
||||
const String getCommandLineParameters() const noexcept { return commandLineParameters; }
|
||||
|
||||
//==============================================================================
|
||||
// These are used by the START_JUCE_APPLICATION() macro and aren't for public use.
|
||||
|
||||
/** @internal */
|
||||
static int main (const String& commandLine);
|
||||
/** @internal */
|
||||
static int main (int argc, const char* argv[]);
|
||||
/** @internal */
|
||||
static void sendUnhandledException (const std::exception* e, const char* sourceFile, int lineNumber);
|
||||
|
||||
/** Returns true if this executable is running as an app (as opposed to being a plugin
|
||||
or other kind of shared library. */
|
||||
static inline bool isStandaloneApp() noexcept { return createInstance != 0; }
|
||||
static inline bool isStandaloneApp() noexcept { return createInstance != 0; }
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
ApplicationCommandTarget* getNextCommandTarget();
|
||||
/** @internal */
|
||||
|
|
@ -266,26 +256,29 @@ public:
|
|||
void getAllCommands (Array <CommandID>& commands);
|
||||
/** @internal */
|
||||
bool perform (const InvocationInfo& info);
|
||||
/** @internal */
|
||||
void actionListenerCallback (const String& message);
|
||||
/** @internal */
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
// The following methods are internal calls - not for public use.
|
||||
static int main (const String& commandLine);
|
||||
static int main (int argc, const char* argv[]);
|
||||
static void sendUnhandledException (const std::exception* e, const char* sourceFile, int lineNumber);
|
||||
bool initialiseApp (const String& commandLine);
|
||||
/** @internal */
|
||||
int shutdownApp();
|
||||
/** @internal */
|
||||
static void appWillTerminateByForce();
|
||||
/** @internal */
|
||||
typedef JUCEApplication* (*CreateInstanceFunction)();
|
||||
/** @internal */
|
||||
static CreateInstanceFunction createInstance;
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
static JUCEApplication* appInstance;
|
||||
|
||||
String commandLineParameters;
|
||||
ScopedPointer<InterProcessLock> appLock;
|
||||
ScopedPointer<ActionListener> broadcastCallback;
|
||||
int appReturnValue;
|
||||
bool stillInitialising;
|
||||
ScopedPointer<InterProcessLock> appLock;
|
||||
static JUCEApplication* appInstance;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (JUCEApplication);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ BEGIN_JUCE_NAMESPACE
|
|||
|
||||
#include "juce_AiffAudioFormat.h"
|
||||
#include "../../io/streams/juce_BufferedInputStream.h"
|
||||
#include "../../io/streams/juce_MemoryOutputStream.h"
|
||||
#include "../../core/juce_PlatformUtilities.h"
|
||||
#include "../../text/juce_LocalisedStrings.h"
|
||||
|
||||
|
|
@ -37,6 +38,215 @@ BEGIN_JUCE_NAMESPACE
|
|||
static const char* const aiffFormatName = "AIFF file";
|
||||
static const char* const aiffExtensions[] = { ".aiff", ".aif", 0 };
|
||||
|
||||
//==============================================================================
|
||||
namespace AiffFileHelpers
|
||||
{
|
||||
inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); }
|
||||
|
||||
#if JUCE_MSVC
|
||||
#pragma pack (push, 1)
|
||||
#define PACKED
|
||||
#elif JUCE_GCC
|
||||
#define PACKED __attribute__((packed))
|
||||
#else
|
||||
#define PACKED
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
struct InstChunk
|
||||
{
|
||||
struct Loop
|
||||
{
|
||||
uint16 type; // these are different in AIFF and WAV
|
||||
uint16 startIdentifier;
|
||||
uint16 endIdentifier;
|
||||
} PACKED;
|
||||
|
||||
int8 baseNote;
|
||||
int8 detune;
|
||||
int8 lowNote;
|
||||
int8 highNote;
|
||||
int8 lowVelocity;
|
||||
int8 highVelocity;
|
||||
int16 gain;
|
||||
Loop sustainLoop;
|
||||
Loop releaseLoop;
|
||||
|
||||
void copyTo (StringPairArray& values) const
|
||||
{
|
||||
values.set ("MidiUnityNote", String (baseNote));
|
||||
values.set ("Detune", String (detune));
|
||||
|
||||
values.set ("LowNote", String (lowNote));
|
||||
values.set ("HighNote", String (highNote));
|
||||
values.set ("LowVelocity", String (lowVelocity));
|
||||
values.set ("HighVelocity", String (highVelocity));
|
||||
|
||||
values.set ("Gain", String ((int16) ByteOrder::swapIfLittleEndian ((uint16) gain)));
|
||||
|
||||
values.set ("NumSampleLoops", String (2)); // always 2 with AIFF, WAV can have more
|
||||
values.set ("Loop0Type", String (ByteOrder::swapIfLittleEndian (sustainLoop.type)));
|
||||
values.set ("Loop0StartIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.startIdentifier)));
|
||||
values.set ("Loop0EndIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.endIdentifier)));
|
||||
values.set ("Loop1Type", String (ByteOrder::swapIfLittleEndian (releaseLoop.type)));
|
||||
values.set ("Loop1StartIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.startIdentifier)));
|
||||
values.set ("Loop1EndIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.endIdentifier)));
|
||||
}
|
||||
|
||||
static void create (MemoryBlock& block, const StringPairArray& values)
|
||||
{
|
||||
if (values.getAllKeys().contains ("MidiUnityNote", true))
|
||||
{
|
||||
block.setSize ((sizeof (InstChunk) + 3) & ~3, true);
|
||||
InstChunk* const inst = static_cast <InstChunk*> (block.getData());
|
||||
|
||||
inst->baseNote = (int8) values.getValue ("MidiUnityNote", "60").getIntValue();
|
||||
inst->detune = (int8) values.getValue ("Detune", "0").getIntValue();
|
||||
inst->lowNote = (int8) values.getValue ("LowNote", "0").getIntValue();
|
||||
inst->highNote = (int8) values.getValue ("HighNote", "127").getIntValue();
|
||||
inst->lowVelocity = (int8) values.getValue ("LowVelocity", "1").getIntValue();
|
||||
inst->highVelocity = (int8) values.getValue ("HighVelocity", "127").getIntValue();
|
||||
inst->gain = (int16) ByteOrder::swapIfLittleEndian ((uint16) values.getValue ("Gain", "0").getIntValue());
|
||||
|
||||
inst->sustainLoop.type = ByteOrder::swapIfLittleEndian ((uint16) values.getValue ("Loop0Type", "0").getIntValue());
|
||||
inst->sustainLoop.startIdentifier = ByteOrder::swapIfLittleEndian ((uint16) values.getValue ("Loop0StartIdentifier", "0").getIntValue());
|
||||
inst->sustainLoop.endIdentifier = ByteOrder::swapIfLittleEndian ((uint16) values.getValue ("Loop0EndIdentifier", "0").getIntValue());
|
||||
inst->releaseLoop.type = ByteOrder::swapIfLittleEndian ((uint16) values.getValue ("Loop1Type", "0").getIntValue());
|
||||
inst->releaseLoop.startIdentifier = ByteOrder::swapIfLittleEndian ((uint16) values.getValue ("Loop1StartIdentifier", "0").getIntValue());
|
||||
inst->releaseLoop.endIdentifier = ByteOrder::swapIfLittleEndian ((uint16) values.getValue ("Loop1EndIdentifier", "0").getIntValue());
|
||||
}
|
||||
}
|
||||
|
||||
} PACKED;
|
||||
|
||||
#if JUCE_MSVC
|
||||
#pragma pack (pop)
|
||||
#endif
|
||||
|
||||
#undef PACKED
|
||||
|
||||
//==============================================================================
|
||||
namespace MarkChunk
|
||||
{
|
||||
bool metaDataContainsZeroIdentifiers (const StringPairArray& values)
|
||||
{
|
||||
// (zero cue identifiers are valid for WAV but not for AIFF)
|
||||
const String cueString ("Cue");
|
||||
const String noteString ("CueNote");
|
||||
const String identifierString ("Identifier");
|
||||
|
||||
const StringArray& keys = values.getAllKeys();
|
||||
|
||||
for (int i = 0; i < keys.size(); ++i)
|
||||
{
|
||||
const String key (keys[i]);
|
||||
|
||||
if (key.startsWith (noteString))
|
||||
continue; // zero identifier IS valid in a COMT chunk
|
||||
|
||||
if (key.startsWith (cueString) && key.contains (identifierString))
|
||||
{
|
||||
const int value = values.getValue (key, "-1").getIntValue();
|
||||
|
||||
if (value == 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void create (MemoryBlock& block, const StringPairArray& values)
|
||||
{
|
||||
const int numCues = values.getValue ("NumCuePoints", "0").getIntValue();
|
||||
|
||||
if (numCues > 0)
|
||||
{
|
||||
MemoryOutputStream out (block, false);
|
||||
|
||||
out.writeShortBigEndian ((short) numCues);
|
||||
|
||||
const int numCueLabels = values.getValue ("NumCueLabels", "0").getIntValue();
|
||||
const int idOffset = metaDataContainsZeroIdentifiers (values) ? 1 : 0; // can't have zero IDs in AIFF
|
||||
|
||||
#if JUCE_DEBUG
|
||||
Array<int> identifiers;
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < numCues; ++i)
|
||||
{
|
||||
const String prefixCue ("Cue" + String (i));
|
||||
const String prefixLabel ("CueLabel" + String (i));
|
||||
|
||||
const int identifier = idOffset + values.getValue (prefixCue + "Identifier", "1").getIntValue();
|
||||
|
||||
#if JUCE_DEBUG
|
||||
jassert (! identifiers.contains (identifier));
|
||||
identifiers.add (identifier);
|
||||
#endif
|
||||
|
||||
const int offset = values.getValue (prefixCue + "Offset", "0").getIntValue();
|
||||
|
||||
String label (prefixLabel);
|
||||
|
||||
for (int labelIndex = 0; labelIndex < numCueLabels; ++labelIndex)
|
||||
{
|
||||
const String prefixLabel ("CueLabel" + String (labelIndex));
|
||||
const int labelIdentifier = idOffset + values.getValue (prefixLabel + "Identifier", "1").getIntValue();
|
||||
|
||||
if (labelIdentifier == identifier)
|
||||
{
|
||||
label = values.getValue (prefixLabel + "Text", label);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out.writeShortBigEndian ((short) identifier);
|
||||
out.writeIntBigEndian (offset);
|
||||
|
||||
const int labelLength = jmin (254, label.getNumBytesAsUTF8()); // seems to need null terminator even though it's a pstring
|
||||
out.writeByte ((char) labelLength + 1);
|
||||
out.write (label.toUTF8(), labelLength);
|
||||
out.writeByte (0);
|
||||
}
|
||||
|
||||
if ((out.getDataSize() & 1) != 0)
|
||||
out.writeByte (0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace COMTChunk
|
||||
{
|
||||
void create (MemoryBlock& block, const StringPairArray& values)
|
||||
{
|
||||
const int numNotes = values.getValue ("NumCueNotes", "0").getIntValue();
|
||||
|
||||
if (numNotes > 0)
|
||||
{
|
||||
MemoryOutputStream out (block, false);
|
||||
out.writeShortBigEndian ((short) numNotes);
|
||||
|
||||
for (int i = 0; i < numNotes; ++i)
|
||||
{
|
||||
const String prefix ("CueNote" + String (i));
|
||||
|
||||
out.writeIntBigEndian (values.getValue (prefix + "TimeStamp", "0").getIntValue());
|
||||
out.writeShortBigEndian ((short) values.getValue (prefix + "Identifier", "0").getIntValue());
|
||||
|
||||
const String comment (values.getValue (prefix + "Text", String::empty));
|
||||
out.write (comment.toUTF8(), jmin (comment.getNumBytesAsUTF8(), 65534));
|
||||
out.writeByte (0);
|
||||
|
||||
if ((out.getDataSize() & 1) != 0)
|
||||
out.writeByte (0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class AiffAudioFormatReader : public AudioFormatReader
|
||||
|
|
@ -50,6 +260,8 @@ public:
|
|||
AiffAudioFormatReader (InputStream* in)
|
||||
: AudioFormatReader (in, TRANS (aiffFormatName))
|
||||
{
|
||||
using namespace AiffFileHelpers;
|
||||
|
||||
if (input->readInt() == chunkName ("FORM"))
|
||||
{
|
||||
const int len = input->readIntBigEndian();
|
||||
|
|
@ -131,6 +343,67 @@ public:
|
|||
dataChunkStart = input->getPosition() + 4 + offset;
|
||||
lengthInSamples = (bytesPerFrame > 0) ? jmin (lengthInSamples, (int64) (length / bytesPerFrame)) : 0;
|
||||
}
|
||||
else if (type == chunkName ("MARK"))
|
||||
{
|
||||
const uint16 numCues = (uint16) input->readShortBigEndian();
|
||||
|
||||
// these two are always the same for AIFF-read files
|
||||
metadataValues.set ("NumCuePoints", String (numCues));
|
||||
metadataValues.set ("NumCueLabels", String (numCues));
|
||||
|
||||
for (uint16 i = 0; i < numCues; ++i)
|
||||
{
|
||||
uint16 identifier = (uint16) input->readShortBigEndian();
|
||||
uint32 offset = (uint32) input->readIntBigEndian();
|
||||
uint8 stringLength = (uint8) input->readByte();
|
||||
MemoryBlock textBlock;
|
||||
input->readIntoMemoryBlock (textBlock, stringLength);
|
||||
|
||||
// if the stringLength is even then read one more byte as the
|
||||
// string needs to be an even number of bytes INCLUDING the
|
||||
// leading length character in the pascal string
|
||||
if ((stringLength & 1) == 0)
|
||||
input->readByte();
|
||||
|
||||
const String text = String::fromUTF8 ((const char*)textBlock.getData(), stringLength);
|
||||
|
||||
const String prefixCue ("Cue" + String (i));
|
||||
metadataValues.set (prefixCue + "Identifier", String (identifier));
|
||||
metadataValues.set (prefixCue + "Offset", String (offset));
|
||||
|
||||
const String prefixLabel ("CueLabel" + String (i));
|
||||
metadataValues.set (prefixLabel + "Identifier", String (identifier));
|
||||
metadataValues.set (prefixLabel + "Text", text);
|
||||
}
|
||||
}
|
||||
else if (type == chunkName ("COMT"))
|
||||
{
|
||||
const uint16 numNotes = (uint16) input->readShortBigEndian();
|
||||
metadataValues.set ("NumCueNotes", String (numNotes));
|
||||
|
||||
for (uint16 i = 0; i < numNotes; ++i)
|
||||
{
|
||||
uint32 timestamp = (uint32) input->readIntBigEndian();
|
||||
uint16 identifier = (uint16) input->readShortBigEndian(); // may be zero in this case
|
||||
uint16 stringLength = (uint16) input->readShortBigEndian();
|
||||
|
||||
MemoryBlock textBlock;
|
||||
input->readIntoMemoryBlock (textBlock, stringLength + (stringLength & 1));
|
||||
const String text = String::fromUTF8 ((const char*)textBlock.getData(), stringLength);
|
||||
|
||||
const String prefix ("CueNote" + String (i));
|
||||
metadataValues.set (prefix + "TimeStamp", String (timestamp));
|
||||
metadataValues.set (prefix + "Identifier", String (identifier));
|
||||
metadataValues.set (prefix + "Text", text);
|
||||
}
|
||||
}
|
||||
else if (type == chunkName ("INST"))
|
||||
{
|
||||
HeapBlock <InstChunk> inst;
|
||||
inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1);
|
||||
input->read (inst, length);
|
||||
inst->copyTo (metadataValues);
|
||||
}
|
||||
else if ((hasGotVer && hasGotData && hasGotType)
|
||||
|| chunkEnd < input->getPosition()
|
||||
|| input->isExhausted())
|
||||
|
|
@ -142,6 +415,9 @@ public:
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (metadataValues.size() > 0)
|
||||
metadataValues.set ("MetaDataSource", "AIFF");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -211,8 +487,6 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
static inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); }
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatReader);
|
||||
};
|
||||
|
||||
|
|
@ -221,12 +495,28 @@ class AiffAudioFormatWriter : public AudioFormatWriter
|
|||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
AiffAudioFormatWriter (OutputStream* out, double sampleRate_, unsigned int numChans, int bits)
|
||||
AiffAudioFormatWriter (OutputStream* out, double sampleRate_,
|
||||
unsigned int numChans, int bits,
|
||||
const StringPairArray& metadataValues)
|
||||
: AudioFormatWriter (out, TRANS (aiffFormatName), sampleRate_, numChans, bits),
|
||||
lengthInSamples (0),
|
||||
bytesWritten (0),
|
||||
writeFailed (false)
|
||||
{
|
||||
using namespace AiffFileHelpers;
|
||||
|
||||
if (metadataValues.size() > 0)
|
||||
{
|
||||
// The meta data should have been santised for the AIFF format.
|
||||
// If it was originally sourced from a WAV file the MetaDataSource
|
||||
// key should be removed (or set to "AIFF") once this has been done
|
||||
jassert (metadataValues.getValue ("MetaDataSource", "None") != "WAV");
|
||||
|
||||
MarkChunk::create (markChunk, metadataValues);
|
||||
COMTChunk::create (comtChunk, metadataValues);
|
||||
InstChunk::create (instChunk, metadataValues);
|
||||
}
|
||||
|
||||
headerPosition = out->getPosition();
|
||||
writeHeader();
|
||||
}
|
||||
|
|
@ -279,15 +569,15 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
MemoryBlock tempBlock;
|
||||
MemoryBlock tempBlock, markChunk, comtChunk, instChunk;
|
||||
uint32 lengthInSamples, bytesWritten;
|
||||
int64 headerPosition;
|
||||
bool writeFailed;
|
||||
|
||||
static inline int chunkName (const char* const name) { return (int) ByteOrder::littleEndianInt (name); }
|
||||
|
||||
void writeHeader()
|
||||
{
|
||||
using namespace AiffFileHelpers;
|
||||
|
||||
const bool couldSeekOk = output->setPosition (headerPosition);
|
||||
(void) couldSeekOk;
|
||||
|
||||
|
|
@ -295,7 +585,9 @@ private:
|
|||
// to be able to seek back to write the header
|
||||
jassert (couldSeekOk);
|
||||
|
||||
const int headerLen = 54;
|
||||
const int headerLen = 54 + (markChunk.getSize() > 0 ? markChunk.getSize() + 8 : 0)
|
||||
+ (comtChunk.getSize() > 0 ? comtChunk.getSize() + 8 : 0)
|
||||
+ (instChunk.getSize() > 0 ? instChunk.getSize() + 8 : 0);
|
||||
int audioBytes = lengthInSamples * ((bitsPerSample * numChannels) / 8);
|
||||
audioBytes += (audioBytes & 1);
|
||||
|
||||
|
|
@ -351,6 +643,27 @@ private:
|
|||
|
||||
output->write (sampleRateBytes, 10);
|
||||
|
||||
if (markChunk.getSize() > 0)
|
||||
{
|
||||
output->writeInt (chunkName ("MARK"));
|
||||
output->writeIntBigEndian ((int) markChunk.getSize());
|
||||
output->write (markChunk.getData(), (int) markChunk.getSize());
|
||||
}
|
||||
|
||||
if (comtChunk.getSize() > 0)
|
||||
{
|
||||
output->writeInt (chunkName ("COMT"));
|
||||
output->writeIntBigEndian ((int) comtChunk.getSize());
|
||||
output->write (comtChunk.getData(), (int) comtChunk.getSize());
|
||||
}
|
||||
|
||||
if (instChunk.getSize() > 0)
|
||||
{
|
||||
output->writeInt (chunkName ("INST"));
|
||||
output->writeIntBigEndian ((int) instChunk.getSize());
|
||||
output->write (instChunk.getData(), (int) instChunk.getSize());
|
||||
}
|
||||
|
||||
output->writeInt (chunkName ("SSND"));
|
||||
output->writeIntBigEndian (audioBytes + 8);
|
||||
output->writeInt (0);
|
||||
|
|
@ -416,11 +729,11 @@ AudioFormatWriter* AiffAudioFormat::createWriterFor (OutputStream* out,
|
|||
double sampleRate,
|
||||
unsigned int numberOfChannels,
|
||||
int bitsPerSample,
|
||||
const StringPairArray& /*metadataValues*/,
|
||||
const StringPairArray& metadataValues,
|
||||
int /*qualityOptionIndex*/)
|
||||
{
|
||||
if (getPossibleBitDepths().contains (bitsPerSample))
|
||||
return new AiffAudioFormatWriter (out, sampleRate, numberOfChannels, bitsPerSample);
|
||||
return new AiffAudioFormatWriter (out, sampleRate, numberOfChannels, bitsPerSample, metadataValues);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,9 +50,10 @@ public:
|
|||
const Array <int> getPossibleBitDepths();
|
||||
bool canDoStereo();
|
||||
bool canDoMono();
|
||||
#if JUCE_MAC
|
||||
|
||||
#if JUCE_MAC
|
||||
bool canHandleFile (const File& fileToTest);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
AudioFormatReader* createReaderFor (InputStream* sourceStream,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ struct AudioThumbnail::MinMaxValue
|
|||
char minValue;
|
||||
char maxValue;
|
||||
|
||||
MinMaxValue() : minValue (0), maxValue (0)
|
||||
MinMaxValue() noexcept : minValue (0), maxValue (0)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +281,7 @@ public:
|
|||
return data.size();
|
||||
}
|
||||
|
||||
void getMinMax (int startSample, int endSample, MinMaxValue& result) noexcept
|
||||
void getMinMax (int startSample, int endSample, MinMaxValue& result) const noexcept
|
||||
{
|
||||
if (startSample >= 0)
|
||||
{
|
||||
|
|
@ -323,12 +323,12 @@ public:
|
|||
dest[i] = source[i];
|
||||
}
|
||||
|
||||
void resetPeak()
|
||||
void resetPeak() noexcept
|
||||
{
|
||||
peakLevel = -1;
|
||||
}
|
||||
|
||||
int getPeak()
|
||||
int getPeak() noexcept
|
||||
{
|
||||
if (peakLevel < 0)
|
||||
{
|
||||
|
|
@ -743,6 +743,24 @@ float AudioThumbnail::getApproximatePeak() const
|
|||
return jlimit (0, 127, peak) / 127.0f;
|
||||
}
|
||||
|
||||
void AudioThumbnail::getApproximateMinMax (const double startTime, const double endTime, const int channelIndex,
|
||||
float& minValue, float& maxValue) const noexcept
|
||||
{
|
||||
MinMaxValue result;
|
||||
const ThumbData* const data = channels [channelIndex];
|
||||
|
||||
if (data != nullptr && sampleRate > 0)
|
||||
{
|
||||
const int firstThumbIndex = (int) ((startTime * sampleRate) / samplesPerThumbSample);
|
||||
const int lastThumbIndex = (int) (((endTime * sampleRate) + samplesPerThumbSample - 1) / samplesPerThumbSample);
|
||||
|
||||
data->getMinMax (jmax (0, firstThumbIndex), lastThumbIndex, result);
|
||||
}
|
||||
|
||||
minValue = result.minValue / 128.0f;
|
||||
maxValue = result.maxValue / 128.0f;
|
||||
}
|
||||
|
||||
void AudioThumbnail::drawChannel (Graphics& g, const Rectangle<int>& area, double startTime,
|
||||
double endTime, int channelNum, float verticalZoomFactor)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -186,12 +186,19 @@ public:
|
|||
*/
|
||||
float getApproximatePeak() const;
|
||||
|
||||
/** Reads the approximate min and max levels from a section of the thumbnail.
|
||||
The lowest and highest samples are returned in minValue and maxValue, but obviously
|
||||
because the thumb only stores low-resolution data, these numbers will only be a rough
|
||||
approximation of the true values.
|
||||
*/
|
||||
void getApproximateMinMax (double startTime, double endTime, int channelIndex,
|
||||
float& minValue, float& maxValue) const noexcept;
|
||||
|
||||
/** Returns the hash code that was set by setSource() or setReader(). */
|
||||
int64 getHashCode() const;
|
||||
|
||||
#ifndef DOXYGEN
|
||||
// (this is only public to avoid a VC6 bug)
|
||||
class LevelDataSource;
|
||||
class LevelDataSource; // (this is only public to avoid a VC6 bug)
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ BEGIN_JUCE_NAMESPACE
|
|||
|
||||
#include "juce_WavAudioFormat.h"
|
||||
#include "../../io/streams/juce_BufferedInputStream.h"
|
||||
#include "../../io/streams/juce_MemoryOutputStream.h"
|
||||
#include "../../text/juce_LocalisedStrings.h"
|
||||
#include "../../io/files/juce_FileInputStream.h"
|
||||
#include "../../io/files/juce_TemporaryFile.h"
|
||||
|
|
@ -156,7 +157,7 @@ struct SMPLChunk
|
|||
struct SampleLoop
|
||||
{
|
||||
uint32 identifier;
|
||||
uint32 type;
|
||||
uint32 type; // these are different in AIFF and WAV
|
||||
uint32 start;
|
||||
uint32 end;
|
||||
uint32 fraction;
|
||||
|
|
@ -214,8 +215,6 @@ struct SMPLChunk
|
|||
|
||||
SMPLChunk* const s = static_cast <SMPLChunk*> (data.getData());
|
||||
|
||||
// Allow these calls to overwrite an extra byte at the end, which is fine as long
|
||||
// as they get called in the right order..
|
||||
s->manufacturer = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("Manufacturer", "0").getIntValue());
|
||||
s->product = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("Product", "0").getIntValue());
|
||||
s->samplePeriod = ByteOrder::swapIfBigEndian ((uint32) values.getValue ("SamplePeriod", "0").getIntValue());
|
||||
|
|
@ -241,6 +240,52 @@ struct SMPLChunk
|
|||
}
|
||||
} PACKED;
|
||||
|
||||
//==============================================================================
|
||||
struct InstChunk
|
||||
{
|
||||
int8 baseNote;
|
||||
int8 detune;
|
||||
int8 gain;
|
||||
int8 lowNote;
|
||||
int8 highNote;
|
||||
int8 lowVelocity;
|
||||
int8 highVelocity;
|
||||
|
||||
void copyTo (StringPairArray& values) const
|
||||
{
|
||||
values.set ("MidiUnityNote", String (baseNote));
|
||||
values.set ("Detune", String (detune));
|
||||
values.set ("Gain", String (gain));
|
||||
values.set ("LowNote", String (lowNote));
|
||||
values.set ("HighNote", String (highNote));
|
||||
values.set ("LowVelocity", String (lowVelocity));
|
||||
values.set ("HighVelocity", String (highVelocity));
|
||||
}
|
||||
|
||||
static MemoryBlock createFrom (const StringPairArray& values)
|
||||
{
|
||||
const StringArray& keys = values.getAllKeys();
|
||||
|
||||
if (! (keys.contains ("LowNote", true) && keys.contains ("HighNote", true)))
|
||||
return MemoryBlock();
|
||||
|
||||
MemoryBlock data (8);
|
||||
data.fillWith (0);
|
||||
|
||||
InstChunk* const inst = static_cast <InstChunk*> (data.getData());
|
||||
|
||||
inst->baseNote = (int8) values.getValue ("MidiUnityNote", "60").getIntValue();
|
||||
inst->detune = (int8) values.getValue ("Detune", "0").getIntValue();
|
||||
inst->gain = (int8) values.getValue ("Gain", "0").getIntValue();
|
||||
inst->lowNote = (int8) values.getValue ("LowNote", "0").getIntValue();
|
||||
inst->highNote = (int8) values.getValue ("HighNote", "127").getIntValue();
|
||||
inst->lowVelocity = (int8) values.getValue ("LowVelocity", "1").getIntValue();
|
||||
inst->highVelocity = (int8) values.getValue ("HighVelocity", "127").getIntValue();
|
||||
|
||||
return data;
|
||||
}
|
||||
} PACKED;
|
||||
|
||||
//==============================================================================
|
||||
struct CueChunk
|
||||
{
|
||||
|
|
@ -276,39 +321,118 @@ struct CueChunk
|
|||
}
|
||||
}
|
||||
|
||||
static MemoryBlock createFrom (const StringPairArray& values)
|
||||
static void create (MemoryBlock& data, const StringPairArray& values)
|
||||
{
|
||||
const int numCues = values.getValue ("NumCuePoints", "0").getIntValue();
|
||||
|
||||
if (numCues <= 0)
|
||||
return MemoryBlock();
|
||||
|
||||
const size_t sizeNeeded = sizeof (CueChunk) + (numCues - 1) * sizeof (Cue);
|
||||
MemoryBlock data ((sizeNeeded + 3) & ~3);
|
||||
data.fillWith (0);
|
||||
|
||||
CueChunk* const c = static_cast <CueChunk*> (data.getData());
|
||||
|
||||
c->numCues = ByteOrder::swapIfBigEndian ((uint32) numCues);
|
||||
|
||||
const String dataChunkID (chunkName ("data"));
|
||||
|
||||
for (int i = 0; i < numCues; ++i)
|
||||
if (numCues > 0)
|
||||
{
|
||||
const String prefix ("Cue" + String(i));
|
||||
c->cues[i].identifier = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Identifier", "0").getIntValue());
|
||||
c->cues[i].order = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Order", "0").getIntValue());
|
||||
c->cues[i].chunkID = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "ChunkID", dataChunkID).getIntValue());
|
||||
c->cues[i].chunkStart = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "ChunkStart", "0").getIntValue());
|
||||
c->cues[i].blockStart = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "BlockStart", "0").getIntValue());
|
||||
c->cues[i].offset = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Offset", "0").getIntValue());
|
||||
}
|
||||
const size_t sizeNeeded = sizeof (CueChunk) + (numCues - 1) * sizeof (Cue);
|
||||
data.setSize ((sizeNeeded + 3) & ~3, true);
|
||||
|
||||
return data;
|
||||
CueChunk* const c = static_cast <CueChunk*> (data.getData());
|
||||
|
||||
c->numCues = ByteOrder::swapIfBigEndian ((uint32) numCues);
|
||||
|
||||
const String dataChunkID (chunkName ("data"));
|
||||
|
||||
int nextOrder = 0;
|
||||
|
||||
#if JUCE_DEBUG
|
||||
Array<int> identifiers;
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < numCues; ++i)
|
||||
{
|
||||
const String prefix ("Cue" + String (i));
|
||||
|
||||
uint32 identifier = values.getValue (prefix + "Identifier", "0").getIntValue();
|
||||
|
||||
#if JUCE_DEBUG
|
||||
jassert (! identifiers.contains (identifier));
|
||||
identifiers.add (identifier);
|
||||
#endif
|
||||
|
||||
c->cues[i].identifier = ByteOrder::swapIfBigEndian ((uint32) identifier);
|
||||
|
||||
const int order = values.getValue (prefix + "Order", String (nextOrder)).getIntValue();
|
||||
nextOrder = jmax (nextOrder, order) + 1;
|
||||
|
||||
c->cues[i].order = ByteOrder::swapIfBigEndian ((uint32) order);
|
||||
c->cues[i].chunkID = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "ChunkID", dataChunkID).getIntValue());
|
||||
c->cues[i].chunkStart = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "ChunkStart", "0").getIntValue());
|
||||
c->cues[i].blockStart = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "BlockStart", "0").getIntValue());
|
||||
c->cues[i].offset = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Offset", "0").getIntValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} PACKED;
|
||||
|
||||
//==============================================================================
|
||||
namespace ListChunk
|
||||
{
|
||||
void appendLabelOrNoteChunk (const StringPairArray& values, const String& prefix,
|
||||
const int chunkType, MemoryOutputStream& out)
|
||||
{
|
||||
const String label (values.getValue (prefix + "Text", prefix));
|
||||
const int labelLength = label.getNumBytesAsUTF8() + 1;
|
||||
const int chunkLength = 4 + labelLength + (labelLength & 1);
|
||||
|
||||
out.writeInt (chunkType);
|
||||
out.writeInt (chunkLength);
|
||||
out.writeInt (values.getValue (prefix + "Identifier", "0").getIntValue());
|
||||
out.write (label.toUTF8(), labelLength);
|
||||
|
||||
if ((out.getDataSize() & 1) != 0)
|
||||
out.writeByte (0);
|
||||
}
|
||||
|
||||
void appendExtraChunk (const StringPairArray& values, const String& prefix, MemoryOutputStream& out)
|
||||
{
|
||||
const String text (values.getValue (prefix + "Text", prefix));
|
||||
|
||||
const int textLength = text.getNumBytesAsUTF8() + 1; // include null terminator
|
||||
uint32 chunkLength = textLength + 20 + (textLength & 1);
|
||||
|
||||
out.writeInt (chunkName ("ltxt"));
|
||||
out.writeInt (chunkLength);
|
||||
out.writeInt (values.getValue (prefix + "Identifier", "0").getIntValue());
|
||||
out.writeInt (values.getValue (prefix + "SampleLength", "0").getIntValue());
|
||||
out.writeInt (values.getValue (prefix + "Purpose", "0").getIntValue());
|
||||
out.writeShort ((short) values.getValue (prefix + "Country", "0").getIntValue());
|
||||
out.writeShort ((short) values.getValue (prefix + "Language", "0").getIntValue());
|
||||
out.writeShort ((short) values.getValue (prefix + "Dialect", "0").getIntValue());
|
||||
out.writeShort ((short) values.getValue (prefix + "CodePage", "0").getIntValue());
|
||||
out.write (text.toUTF8(), textLength);
|
||||
|
||||
if ((out.getDataSize() & 1) != 0)
|
||||
out.writeByte (0);
|
||||
}
|
||||
|
||||
void create (MemoryBlock& block, const StringPairArray& values)
|
||||
{
|
||||
const int numCueLabels = values.getValue ("NumCueLabels", "0").getIntValue();
|
||||
const int numCueNotes = values.getValue ("NumCueNotes", "0").getIntValue();
|
||||
const int numCueRegions = values.getValue ("NumCueRegions", "0").getIntValue();
|
||||
|
||||
if (numCueLabels > 0 || numCueNotes > 0 || numCueRegions > 0)
|
||||
{
|
||||
MemoryOutputStream out (block, false);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < numCueLabels; ++i)
|
||||
appendLabelOrNoteChunk (values, "CueLabel" + String (i), chunkName ("labl"), out);
|
||||
|
||||
for (i = 0; i < numCueNotes; ++i)
|
||||
appendLabelOrNoteChunk (values, "CueNote" + String (i), chunkName ("note"), out);
|
||||
|
||||
for (i = 0; i < numCueRegions; ++i)
|
||||
appendExtraChunk (values, "CueRegion" + String (i), out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ExtensibleWavSubFormat
|
||||
{
|
||||
|
|
@ -355,6 +479,9 @@ public:
|
|||
int64 end = 0;
|
||||
bool hasGotType = false;
|
||||
bool hasGotData = false;
|
||||
int cueNoteIndex = 0;
|
||||
int cueLabelIndex = 0;
|
||||
int cueRegionIndex = 0;
|
||||
|
||||
const int firstChunkType = input->readInt();
|
||||
|
||||
|
|
@ -479,6 +606,13 @@ public:
|
|||
input->read (smpl, length);
|
||||
smpl->copyTo (metadataValues, length);
|
||||
}
|
||||
else if (chunkType == chunkName ("inst") || chunkType == chunkName ("INST")) // need to check which...
|
||||
{
|
||||
HeapBlock <InstChunk> inst;
|
||||
inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1);
|
||||
input->read (inst, length);
|
||||
inst->copyTo (metadataValues);
|
||||
}
|
||||
else if (chunkType == chunkName ("cue "))
|
||||
{
|
||||
HeapBlock <CueChunk> cue;
|
||||
|
|
@ -486,6 +620,65 @@ public:
|
|||
input->read (cue, length);
|
||||
cue->copyTo (metadataValues, length);
|
||||
}
|
||||
else if (chunkType == chunkName ("LIST"))
|
||||
{
|
||||
if (input->readInt() == chunkName ("adtl"))
|
||||
{
|
||||
while (input->getPosition() < chunkEnd)
|
||||
{
|
||||
const int adtlChunkType = input->readInt();
|
||||
const uint32 adtlLength = (uint32) input->readInt();
|
||||
const int64 adtlChunkEnd = input->getPosition() + (adtlLength + (adtlLength & 1));
|
||||
|
||||
if (adtlChunkType == chunkName ("labl") || adtlChunkType == chunkName ("note"))
|
||||
{
|
||||
String prefix;
|
||||
|
||||
if (adtlChunkType == chunkName ("labl"))
|
||||
prefix << "CueLabel" << cueLabelIndex++;
|
||||
else if (adtlChunkType == chunkName ("note"))
|
||||
prefix << "CueNote" << cueNoteIndex++;
|
||||
|
||||
const uint32 identifier = (uint32) input->readInt();
|
||||
const uint32 stringLength = adtlLength - 4;
|
||||
|
||||
MemoryBlock textBlock;
|
||||
input->readIntoMemoryBlock (textBlock, stringLength);
|
||||
const String text (String::fromUTF8 (static_cast <const char*> (textBlock.getData()), textBlock.getSize()));
|
||||
|
||||
metadataValues.set (prefix + "Identifier", String (identifier));
|
||||
metadataValues.set (prefix + "Text", text);
|
||||
}
|
||||
else if (adtlChunkType == chunkName ("ltxt"))
|
||||
{
|
||||
const String prefix ("CueRegion" + String (cueRegionIndex++));
|
||||
const uint32 identifier = (uint32) input->readInt();
|
||||
const uint32 sampleLength = (uint32) input->readInt();
|
||||
const uint32 purpose = (uint32) input->readInt();
|
||||
const uint16 country = (uint16) input->readInt();
|
||||
const uint16 language = (uint16) input->readInt();
|
||||
const uint16 dialect = (uint16) input->readInt();
|
||||
const uint16 codePage = (uint16) input->readInt();
|
||||
const uint32 stringLength = adtlLength - 20;
|
||||
|
||||
MemoryBlock textBlock;
|
||||
input->readIntoMemoryBlock (textBlock, stringLength);
|
||||
const String text = String::fromUTF8 ((const char*)textBlock.getData(), textBlock.getSize());
|
||||
|
||||
metadataValues.set (prefix + "Identifier", String (identifier));
|
||||
metadataValues.set (prefix + "SampleLength", String (sampleLength));
|
||||
metadataValues.set (prefix + "Purpose", String (purpose));
|
||||
metadataValues.set (prefix + "Country", String (country));
|
||||
metadataValues.set (prefix + "Language", String (language));
|
||||
metadataValues.set (prefix + "Dialect", String (dialect));
|
||||
metadataValues.set (prefix + "CodePage", String (codePage));
|
||||
metadataValues.set (prefix + "Text", text);
|
||||
}
|
||||
|
||||
input->setPosition (adtlChunkEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (chunkEnd <= input->getPosition())
|
||||
{
|
||||
break;
|
||||
|
|
@ -494,6 +687,11 @@ public:
|
|||
input->setPosition (chunkEnd);
|
||||
}
|
||||
}
|
||||
|
||||
if (cueLabelIndex > 0) metadataValues.set ("NumCueLabels", String (cueLabelIndex));
|
||||
if (cueNoteIndex > 0) metadataValues.set ("NumCueNotes", String (cueNoteIndex));
|
||||
if (cueRegionIndex > 0) metadataValues.set ("NumCueRegions", String (cueRegionIndex));
|
||||
if (metadataValues.size() > 0) metadataValues.set ("MetaDataSource", "WAV");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -576,9 +774,16 @@ public:
|
|||
|
||||
if (metadataValues.size() > 0)
|
||||
{
|
||||
// The meta data should have been santised for the WAV format.
|
||||
// If it was originally sourced from an AIFF file the MetaDataSource
|
||||
// key should be removed (or set to "WAV") once this has been done
|
||||
jassert (metadataValues.getValue ("MetaDataSource", "None") != "AIFF");
|
||||
|
||||
bwavChunk = BWAVChunk::createFrom (metadataValues);
|
||||
smplChunk = SMPLChunk::createFrom (metadataValues);
|
||||
cueChunk = CueChunk ::createFrom (metadataValues);
|
||||
instChunk = InstChunk::createFrom (metadataValues);
|
||||
CueChunk ::create (cueChunk, metadataValues);
|
||||
ListChunk::create (listChunk, metadataValues);
|
||||
}
|
||||
|
||||
headerPosition = out->getPosition();
|
||||
|
|
@ -636,7 +841,7 @@ public:
|
|||
|
||||
private:
|
||||
ScopedPointer<AudioData::Converter> converter;
|
||||
MemoryBlock tempBlock, bwavChunk, smplChunk, cueChunk;
|
||||
MemoryBlock tempBlock, bwavChunk, smplChunk, instChunk, cueChunk, listChunk;
|
||||
uint64 lengthInSamples, bytesWritten;
|
||||
int64 headerPosition;
|
||||
bool writeFailed;
|
||||
|
|
@ -675,7 +880,9 @@ private:
|
|||
+ 8 + audioDataSize + (audioDataSize & 1)
|
||||
+ (bwavChunk.getSize() > 0 ? (8 + bwavChunk.getSize()) : 0)
|
||||
+ (smplChunk.getSize() > 0 ? (8 + smplChunk.getSize()) : 0)
|
||||
+ (instChunk.getSize() > 0 ? (8 + instChunk.getSize()) : 0)
|
||||
+ (cueChunk .getSize() > 0 ? (8 + cueChunk .getSize()) : 0)
|
||||
+ (listChunk.getSize() > 0 ? (12 + listChunk.getSize()) : 0)
|
||||
+ (8 + 28); // (ds64 chunk)
|
||||
|
||||
riffChunkSize += (riffChunkSize & 0x1);
|
||||
|
|
@ -754,6 +961,13 @@ private:
|
|||
output->write (smplChunk.getData(), (int) smplChunk.getSize());
|
||||
}
|
||||
|
||||
if (instChunk.getSize() > 0)
|
||||
{
|
||||
output->writeInt (chunkName ("inst"));
|
||||
output->writeInt (7);
|
||||
output->write (instChunk.getData(), (int) instChunk.getSize());
|
||||
}
|
||||
|
||||
if (cueChunk.getSize() > 0)
|
||||
{
|
||||
output->writeInt (chunkName ("cue "));
|
||||
|
|
@ -761,6 +975,14 @@ private:
|
|||
output->write (cueChunk.getData(), (int) cueChunk.getSize());
|
||||
}
|
||||
|
||||
if (listChunk.getSize() > 0)
|
||||
{
|
||||
output->writeInt (chunkName ("LIST"));
|
||||
output->writeInt ((int) listChunk.getSize() + 4);
|
||||
output->writeInt (chunkName ("adtl"));
|
||||
output->write (listChunk.getData(), (int) listChunk.getSize());
|
||||
}
|
||||
|
||||
output->writeInt (chunkName ("data"));
|
||||
output->writeInt (isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame));
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@
|
|||
*/
|
||||
|
||||
#include "juce_IncludeCharacteristics.h"
|
||||
|
||||
#define DONT_AUTOLINK_TO_JUCE_LIBRARY 1
|
||||
|
||||
#include "../../../juce.h"
|
||||
|
||||
#ifndef __JUCE_PLUGINHEADERS_JUCEHEADER__
|
||||
|
|
|
|||
|
|
@ -30,309 +30,8 @@ BEGIN_JUCE_NAMESPACE
|
|||
#include "juce_AudioProcessorGraph.h"
|
||||
#include "../../events/juce_MessageManager.h"
|
||||
|
||||
|
||||
const int AudioProcessorGraph::midiChannelIndex = 0x1000;
|
||||
|
||||
//==============================================================================
|
||||
AudioProcessorGraph::Node::Node (const uint32 id_, AudioProcessor* const processor_)
|
||||
: id (id_),
|
||||
processor (processor_),
|
||||
isPrepared (false)
|
||||
{
|
||||
jassert (processor_ != nullptr);
|
||||
}
|
||||
|
||||
void AudioProcessorGraph::Node::prepare (const double sampleRate, const int blockSize,
|
||||
AudioProcessorGraph* const graph)
|
||||
{
|
||||
if (! isPrepared)
|
||||
{
|
||||
isPrepared = true;
|
||||
|
||||
AudioProcessorGraph::AudioGraphIOProcessor* const ioProc
|
||||
= dynamic_cast <AudioProcessorGraph::AudioGraphIOProcessor*> (static_cast<AudioProcessor*> (processor));
|
||||
|
||||
if (ioProc != nullptr)
|
||||
ioProc->setParentGraph (graph);
|
||||
|
||||
processor->setPlayConfigDetails (processor->getNumInputChannels(),
|
||||
processor->getNumOutputChannels(),
|
||||
sampleRate, blockSize);
|
||||
|
||||
processor->prepareToPlay (sampleRate, blockSize);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioProcessorGraph::Node::unprepare()
|
||||
{
|
||||
if (isPrepared)
|
||||
{
|
||||
isPrepared = false;
|
||||
processor->releaseResources();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AudioProcessorGraph::AudioProcessorGraph()
|
||||
: lastNodeId (0),
|
||||
renderingBuffers (1, 1),
|
||||
currentAudioOutputBuffer (1, 1)
|
||||
{
|
||||
}
|
||||
|
||||
AudioProcessorGraph::~AudioProcessorGraph()
|
||||
{
|
||||
clearRenderingSequence();
|
||||
clear();
|
||||
}
|
||||
|
||||
const String AudioProcessorGraph::getName() const
|
||||
{
|
||||
return "Audio Graph";
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void AudioProcessorGraph::clear()
|
||||
{
|
||||
nodes.clear();
|
||||
connections.clear();
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (const uint32 nodeId) const
|
||||
{
|
||||
for (int i = nodes.size(); --i >= 0;)
|
||||
if (nodes.getUnchecked(i)->id == nodeId)
|
||||
return nodes.getUnchecked(i);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const newProcessor,
|
||||
uint32 nodeId)
|
||||
{
|
||||
if (newProcessor == nullptr)
|
||||
{
|
||||
jassertfalse;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (nodeId == 0)
|
||||
{
|
||||
nodeId = ++lastNodeId;
|
||||
}
|
||||
else
|
||||
{
|
||||
// you can't add a node with an id that already exists in the graph..
|
||||
jassert (getNodeForId (nodeId) == nullptr);
|
||||
removeNode (nodeId);
|
||||
}
|
||||
|
||||
lastNodeId = nodeId;
|
||||
|
||||
Node* const n = new Node (nodeId, newProcessor);
|
||||
nodes.add (n);
|
||||
triggerAsyncUpdate();
|
||||
|
||||
AudioProcessorGraph::AudioGraphIOProcessor* const ioProc
|
||||
= dynamic_cast <AudioProcessorGraph::AudioGraphIOProcessor*> (static_cast<AudioProcessor*> (n->processor));
|
||||
|
||||
if (ioProc != nullptr)
|
||||
ioProc->setParentGraph (this);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::removeNode (const uint32 nodeId)
|
||||
{
|
||||
disconnectNode (nodeId);
|
||||
|
||||
for (int i = nodes.size(); --i >= 0;)
|
||||
{
|
||||
if (nodes.getUnchecked(i)->id == nodeId)
|
||||
{
|
||||
AudioProcessorGraph::AudioGraphIOProcessor* const ioProc
|
||||
= dynamic_cast <AudioProcessorGraph::AudioGraphIOProcessor*> (static_cast<AudioProcessor*> (nodes.getUnchecked(i)->processor));
|
||||
|
||||
if (ioProc != nullptr)
|
||||
ioProc->setParentGraph (nullptr);
|
||||
|
||||
nodes.remove (i);
|
||||
triggerAsyncUpdate();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const uint32 sourceNodeId,
|
||||
const int sourceChannelIndex,
|
||||
const uint32 destNodeId,
|
||||
const int destChannelIndex) const
|
||||
{
|
||||
for (int i = connections.size(); --i >= 0;)
|
||||
{
|
||||
const Connection* const c = connections.getUnchecked(i);
|
||||
|
||||
if (c->sourceNodeId == sourceNodeId
|
||||
&& c->destNodeId == destNodeId
|
||||
&& c->sourceChannelIndex == sourceChannelIndex
|
||||
&& c->destChannelIndex == destChannelIndex)
|
||||
{
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::isConnected (const uint32 possibleSourceNodeId,
|
||||
const uint32 possibleDestNodeId) const
|
||||
{
|
||||
for (int i = connections.size(); --i >= 0;)
|
||||
{
|
||||
const Connection* const c = connections.getUnchecked(i);
|
||||
|
||||
if (c->sourceNodeId == possibleSourceNodeId
|
||||
&& c->destNodeId == possibleDestNodeId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::canConnect (const uint32 sourceNodeId,
|
||||
const int sourceChannelIndex,
|
||||
const uint32 destNodeId,
|
||||
const int destChannelIndex) const
|
||||
{
|
||||
if (sourceChannelIndex < 0
|
||||
|| destChannelIndex < 0
|
||||
|| sourceNodeId == destNodeId
|
||||
|| (destChannelIndex == midiChannelIndex) != (sourceChannelIndex == midiChannelIndex))
|
||||
return false;
|
||||
|
||||
const Node* const source = getNodeForId (sourceNodeId);
|
||||
|
||||
if (source == nullptr
|
||||
|| (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getNumOutputChannels())
|
||||
|| (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi()))
|
||||
return false;
|
||||
|
||||
const Node* const dest = getNodeForId (destNodeId);
|
||||
|
||||
if (dest == nullptr
|
||||
|| (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getNumInputChannels())
|
||||
|| (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi()))
|
||||
return false;
|
||||
|
||||
return getConnectionBetween (sourceNodeId, sourceChannelIndex,
|
||||
destNodeId, destChannelIndex) == nullptr;
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::addConnection (const uint32 sourceNodeId,
|
||||
const int sourceChannelIndex,
|
||||
const uint32 destNodeId,
|
||||
const int destChannelIndex)
|
||||
{
|
||||
if (! canConnect (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex))
|
||||
return false;
|
||||
|
||||
Connection* const c = new Connection();
|
||||
c->sourceNodeId = sourceNodeId;
|
||||
c->sourceChannelIndex = sourceChannelIndex;
|
||||
c->destNodeId = destNodeId;
|
||||
c->destChannelIndex = destChannelIndex;
|
||||
|
||||
connections.add (c);
|
||||
triggerAsyncUpdate();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioProcessorGraph::removeConnection (const int index)
|
||||
{
|
||||
connections.remove (index);
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::removeConnection (const uint32 sourceNodeId, const int sourceChannelIndex,
|
||||
const uint32 destNodeId, const int destChannelIndex)
|
||||
{
|
||||
bool doneAnything = false;
|
||||
|
||||
for (int i = connections.size(); --i >= 0;)
|
||||
{
|
||||
const Connection* const c = connections.getUnchecked(i);
|
||||
|
||||
if (c->sourceNodeId == sourceNodeId
|
||||
&& c->destNodeId == destNodeId
|
||||
&& c->sourceChannelIndex == sourceChannelIndex
|
||||
&& c->destChannelIndex == destChannelIndex)
|
||||
{
|
||||
removeConnection (i);
|
||||
doneAnything = true;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
return doneAnything;
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::disconnectNode (const uint32 nodeId)
|
||||
{
|
||||
bool doneAnything = false;
|
||||
|
||||
for (int i = connections.size(); --i >= 0;)
|
||||
{
|
||||
const Connection* const c = connections.getUnchecked(i);
|
||||
|
||||
if (c->sourceNodeId == nodeId || c->destNodeId == nodeId)
|
||||
{
|
||||
removeConnection (i);
|
||||
doneAnything = true;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
return doneAnything;
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::removeIllegalConnections()
|
||||
{
|
||||
bool doneAnything = false;
|
||||
|
||||
for (int i = connections.size(); --i >= 0;)
|
||||
{
|
||||
const Connection* const c = connections.getUnchecked(i);
|
||||
|
||||
const Node* const source = getNodeForId (c->sourceNodeId);
|
||||
const Node* const dest = getNodeForId (c->destNodeId);
|
||||
|
||||
if (source == nullptr || dest == nullptr
|
||||
|| (c->sourceChannelIndex != midiChannelIndex
|
||||
&& ! isPositiveAndBelow (c->sourceChannelIndex, source->processor->getNumOutputChannels()))
|
||||
|| (c->sourceChannelIndex == midiChannelIndex
|
||||
&& ! source->processor->producesMidi())
|
||||
|| (c->destChannelIndex != midiChannelIndex
|
||||
&& ! isPositiveAndBelow (c->destChannelIndex, dest->processor->getNumInputChannels()))
|
||||
|| (c->destChannelIndex == midiChannelIndex
|
||||
&& ! dest->processor->acceptsMidi()))
|
||||
{
|
||||
removeConnection (i);
|
||||
doneAnything = true;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
return doneAnything;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace GraphRenderingOps
|
||||
{
|
||||
|
|
@ -575,7 +274,7 @@ private:
|
|||
{
|
||||
const AudioProcessorGraph::Connection* const c = graph.getConnection (i);
|
||||
|
||||
if (c->destNodeId == node->id && c->destChannelIndex == inputChan)
|
||||
if (c->destNodeId == node->nodeId && c->destChannelIndex == inputChan)
|
||||
{
|
||||
sourceNodes.add (c->sourceNodeId);
|
||||
sourceOutputChans.add (c->sourceChannelIndex);
|
||||
|
|
@ -690,7 +389,7 @@ private:
|
|||
audioChannelsToUse.add (bufIndex);
|
||||
|
||||
if (inputChan < numOuts)
|
||||
markBufferAsContaining (bufIndex, node->id, inputChan);
|
||||
markBufferAsContaining (bufIndex, node->nodeId, inputChan);
|
||||
}
|
||||
|
||||
for (int outputChan = numIns; outputChan < numOuts; ++outputChan)
|
||||
|
|
@ -699,7 +398,7 @@ private:
|
|||
jassert (bufIndex != 0);
|
||||
audioChannelsToUse.add (bufIndex);
|
||||
|
||||
markBufferAsContaining (bufIndex, node->id, outputChan);
|
||||
markBufferAsContaining (bufIndex, node->nodeId, outputChan);
|
||||
}
|
||||
|
||||
// Now the same thing for midi..
|
||||
|
|
@ -709,7 +408,7 @@ private:
|
|||
{
|
||||
const AudioProcessorGraph::Connection* const c = graph.getConnection (i);
|
||||
|
||||
if (c->destNodeId == node->id && c->destChannelIndex == AudioProcessorGraph::midiChannelIndex)
|
||||
if (c->destNodeId == node->nodeId && c->destChannelIndex == AudioProcessorGraph::midiChannelIndex)
|
||||
midiSourceNodes.add (c->sourceNodeId);
|
||||
}
|
||||
|
||||
|
|
@ -799,7 +498,7 @@ private:
|
|||
}
|
||||
|
||||
if (node->getProcessor()->producesMidi())
|
||||
markBufferAsContaining (midiBufferToUse, node->id,
|
||||
markBufferAsContaining (midiBufferToUse, node->nodeId,
|
||||
AudioProcessorGraph::midiChannelIndex);
|
||||
|
||||
renderingOps.add (new ProcessBufferOp (node, audioChannelsToUse,
|
||||
|
|
@ -830,12 +529,12 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
int getReadOnlyEmptyBuffer() const
|
||||
int getReadOnlyEmptyBuffer() const noexcept
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getBufferContaining (const uint32 nodeId, const int outputChannel) const
|
||||
int getBufferContaining (const uint32 nodeId, const int outputChannel) const noexcept
|
||||
{
|
||||
if (outputChannel == AudioProcessorGraph::midiChannelIndex)
|
||||
{
|
||||
|
|
@ -893,7 +592,7 @@ private:
|
|||
{
|
||||
if (inputChannelOfIndexToIgnore != AudioProcessorGraph::midiChannelIndex
|
||||
&& graph.getConnectionBetween (nodeId, AudioProcessorGraph::midiChannelIndex,
|
||||
node->id, AudioProcessorGraph::midiChannelIndex) != nullptr)
|
||||
node->nodeId, AudioProcessorGraph::midiChannelIndex) != nullptr)
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
|
@ -901,7 +600,7 @@ private:
|
|||
for (int i = 0; i < node->getProcessor()->getNumInputChannels(); ++i)
|
||||
if (i != inputChannelOfIndexToIgnore
|
||||
&& graph.getConnectionBetween (nodeId, outputChanIndex,
|
||||
node->id, i) != nullptr)
|
||||
node->nodeId, i) != nullptr)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -932,6 +631,419 @@ private:
|
|||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RenderingOpSequenceCalculator);
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Holds a fast lookup table for checking which nodes are inputs to others.
|
||||
class ConnectionLookupTable
|
||||
{
|
||||
public:
|
||||
explicit ConnectionLookupTable (const OwnedArray<AudioProcessorGraph::Connection>& connections)
|
||||
{
|
||||
for (int i = 0; i < connections.size(); ++i)
|
||||
{
|
||||
const AudioProcessorGraph::Connection* const c = connections.getUnchecked(i);
|
||||
|
||||
int index;
|
||||
Entry* entry = findEntry (c->destNodeId, index);
|
||||
|
||||
if (entry == nullptr)
|
||||
{
|
||||
entry = new Entry (c->destNodeId);
|
||||
entries.insert (index, entry);
|
||||
}
|
||||
|
||||
entry->srcNodes.add (c->sourceNodeId);
|
||||
}
|
||||
}
|
||||
|
||||
bool isAnInputTo (const uint32 possibleInputId,
|
||||
const uint32 possibleDestinationId) const noexcept
|
||||
{
|
||||
return isAnInputToRecursive (possibleInputId, possibleDestinationId, entries.size());
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct Entry
|
||||
{
|
||||
explicit Entry (const uint32 destNodeId_) noexcept : destNodeId (destNodeId_) {}
|
||||
|
||||
const uint32 destNodeId;
|
||||
SortedSet<uint32> srcNodes;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (Entry);
|
||||
};
|
||||
|
||||
OwnedArray<Entry> entries;
|
||||
|
||||
bool isAnInputToRecursive (const uint32 possibleInputId,
|
||||
const uint32 possibleDestinationId,
|
||||
int recursionCheck) const noexcept
|
||||
{
|
||||
int index;
|
||||
const Entry* const entry = findEntry (possibleDestinationId, index);
|
||||
|
||||
if (entry != nullptr)
|
||||
{
|
||||
const SortedSet<uint32>& srcNodes = entry->srcNodes;
|
||||
|
||||
if (srcNodes.contains (possibleInputId))
|
||||
return true;
|
||||
|
||||
if (--recursionCheck >= 0)
|
||||
{
|
||||
for (int i = 0; i < srcNodes.size(); ++i)
|
||||
if (isAnInputToRecursive (possibleInputId, srcNodes.getUnchecked(i), recursionCheck))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Entry* findEntry (const uint32 destNodeId, int& insertIndex) const noexcept
|
||||
{
|
||||
Entry* result = nullptr;
|
||||
int firstElement = 0, lastElement = entries.size();
|
||||
|
||||
while (firstElement < lastElement)
|
||||
{
|
||||
Entry* const firstEntry = entries.getUnchecked (firstElement);
|
||||
if (destNodeId == firstEntry->destNodeId)
|
||||
{
|
||||
result = firstEntry;
|
||||
break;
|
||||
}
|
||||
|
||||
const int halfway = (firstElement + lastElement) / 2;
|
||||
|
||||
if (halfway <= firstElement)
|
||||
break;
|
||||
|
||||
if (destNodeId >= entries.getUnchecked (halfway)->destNodeId)
|
||||
firstElement = halfway;
|
||||
else
|
||||
lastElement = halfway;
|
||||
}
|
||||
|
||||
insertIndex = firstElement;
|
||||
return result;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (ConnectionLookupTable);
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct ConnectionSorter
|
||||
{
|
||||
static int compareElements (const AudioProcessorGraph::Connection* const first,
|
||||
const AudioProcessorGraph::Connection* const second) noexcept
|
||||
{
|
||||
if (first->sourceNodeId < second->sourceNodeId) return -1;
|
||||
else if (first->sourceNodeId > second->sourceNodeId) return 1;
|
||||
else if (first->destNodeId < second->destNodeId) return -1;
|
||||
else if (first->destNodeId > second->destNodeId) return 1;
|
||||
else if (first->sourceChannelIndex < second->sourceChannelIndex) return -1;
|
||||
else if (first->sourceChannelIndex > second->sourceChannelIndex) return 1;
|
||||
else if (first->destChannelIndex < second->destChannelIndex) return -1;
|
||||
else if (first->destChannelIndex > second->destChannelIndex) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AudioProcessorGraph::Connection::Connection (const uint32 sourceNodeId_, const int sourceChannelIndex_,
|
||||
const uint32 destNodeId_, const int destChannelIndex_) noexcept
|
||||
: sourceNodeId (sourceNodeId_), sourceChannelIndex (sourceChannelIndex_),
|
||||
destNodeId (destNodeId_), destChannelIndex (destChannelIndex_)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AudioProcessorGraph::Node::Node (const uint32 nodeId_, AudioProcessor* const processor_) noexcept
|
||||
: nodeId (nodeId_),
|
||||
processor (processor_),
|
||||
isPrepared (false)
|
||||
{
|
||||
jassert (processor_ != nullptr);
|
||||
}
|
||||
|
||||
void AudioProcessorGraph::Node::prepare (const double sampleRate, const int blockSize,
|
||||
AudioProcessorGraph* const graph)
|
||||
{
|
||||
if (! isPrepared)
|
||||
{
|
||||
isPrepared = true;
|
||||
|
||||
AudioProcessorGraph::AudioGraphIOProcessor* const ioProc
|
||||
= dynamic_cast <AudioProcessorGraph::AudioGraphIOProcessor*> (static_cast<AudioProcessor*> (processor));
|
||||
|
||||
if (ioProc != nullptr)
|
||||
ioProc->setParentGraph (graph);
|
||||
|
||||
processor->setPlayConfigDetails (processor->getNumInputChannels(),
|
||||
processor->getNumOutputChannels(),
|
||||
sampleRate, blockSize);
|
||||
|
||||
processor->prepareToPlay (sampleRate, blockSize);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioProcessorGraph::Node::unprepare()
|
||||
{
|
||||
if (isPrepared)
|
||||
{
|
||||
isPrepared = false;
|
||||
processor->releaseResources();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AudioProcessorGraph::AudioProcessorGraph()
|
||||
: lastNodeId (0),
|
||||
renderingBuffers (1, 1),
|
||||
currentAudioOutputBuffer (1, 1)
|
||||
{
|
||||
}
|
||||
|
||||
AudioProcessorGraph::~AudioProcessorGraph()
|
||||
{
|
||||
clearRenderingSequence();
|
||||
clear();
|
||||
}
|
||||
|
||||
const String AudioProcessorGraph::getName() const
|
||||
{
|
||||
return "Audio Graph";
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void AudioProcessorGraph::clear()
|
||||
{
|
||||
nodes.clear();
|
||||
connections.clear();
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (const uint32 nodeId) const
|
||||
{
|
||||
for (int i = nodes.size(); --i >= 0;)
|
||||
if (nodes.getUnchecked(i)->nodeId == nodeId)
|
||||
return nodes.getUnchecked(i);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const newProcessor,
|
||||
uint32 nodeId)
|
||||
{
|
||||
if (newProcessor == nullptr)
|
||||
{
|
||||
jassertfalse;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (nodeId == 0)
|
||||
{
|
||||
nodeId = ++lastNodeId;
|
||||
}
|
||||
else
|
||||
{
|
||||
// you can't add a node with an id that already exists in the graph..
|
||||
jassert (getNodeForId (nodeId) == nullptr);
|
||||
removeNode (nodeId);
|
||||
}
|
||||
|
||||
lastNodeId = nodeId;
|
||||
|
||||
Node* const n = new Node (nodeId, newProcessor);
|
||||
nodes.add (n);
|
||||
triggerAsyncUpdate();
|
||||
|
||||
AudioProcessorGraph::AudioGraphIOProcessor* const ioProc
|
||||
= dynamic_cast <AudioProcessorGraph::AudioGraphIOProcessor*> (static_cast<AudioProcessor*> (n->processor));
|
||||
|
||||
if (ioProc != nullptr)
|
||||
ioProc->setParentGraph (this);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::removeNode (const uint32 nodeId)
|
||||
{
|
||||
disconnectNode (nodeId);
|
||||
|
||||
for (int i = nodes.size(); --i >= 0;)
|
||||
{
|
||||
if (nodes.getUnchecked(i)->nodeId == nodeId)
|
||||
{
|
||||
AudioProcessorGraph::AudioGraphIOProcessor* const ioProc
|
||||
= dynamic_cast <AudioProcessorGraph::AudioGraphIOProcessor*> (static_cast<AudioProcessor*> (nodes.getUnchecked(i)->processor));
|
||||
|
||||
if (ioProc != nullptr)
|
||||
ioProc->setParentGraph (nullptr);
|
||||
|
||||
nodes.remove (i);
|
||||
triggerAsyncUpdate();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const uint32 sourceNodeId,
|
||||
const int sourceChannelIndex,
|
||||
const uint32 destNodeId,
|
||||
const int destChannelIndex) const
|
||||
{
|
||||
const Connection c (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex);
|
||||
GraphRenderingOps::ConnectionSorter sorter;
|
||||
return connections [connections.indexOfSorted (sorter, &c)];
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::isConnected (const uint32 possibleSourceNodeId,
|
||||
const uint32 possibleDestNodeId) const
|
||||
{
|
||||
for (int i = connections.size(); --i >= 0;)
|
||||
{
|
||||
const Connection* const c = connections.getUnchecked(i);
|
||||
|
||||
if (c->sourceNodeId == possibleSourceNodeId
|
||||
&& c->destNodeId == possibleDestNodeId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::canConnect (const uint32 sourceNodeId,
|
||||
const int sourceChannelIndex,
|
||||
const uint32 destNodeId,
|
||||
const int destChannelIndex) const
|
||||
{
|
||||
if (sourceChannelIndex < 0
|
||||
|| destChannelIndex < 0
|
||||
|| sourceNodeId == destNodeId
|
||||
|| (destChannelIndex == midiChannelIndex) != (sourceChannelIndex == midiChannelIndex))
|
||||
return false;
|
||||
|
||||
const Node* const source = getNodeForId (sourceNodeId);
|
||||
|
||||
if (source == nullptr
|
||||
|| (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getNumOutputChannels())
|
||||
|| (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi()))
|
||||
return false;
|
||||
|
||||
const Node* const dest = getNodeForId (destNodeId);
|
||||
|
||||
if (dest == nullptr
|
||||
|| (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getNumInputChannels())
|
||||
|| (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi()))
|
||||
return false;
|
||||
|
||||
return getConnectionBetween (sourceNodeId, sourceChannelIndex,
|
||||
destNodeId, destChannelIndex) == nullptr;
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::addConnection (const uint32 sourceNodeId,
|
||||
const int sourceChannelIndex,
|
||||
const uint32 destNodeId,
|
||||
const int destChannelIndex)
|
||||
{
|
||||
if (! canConnect (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex))
|
||||
return false;
|
||||
|
||||
GraphRenderingOps::ConnectionSorter sorter;
|
||||
connections.addSorted (sorter, new Connection (sourceNodeId, sourceChannelIndex,
|
||||
destNodeId, destChannelIndex));
|
||||
triggerAsyncUpdate();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioProcessorGraph::removeConnection (const int index)
|
||||
{
|
||||
connections.remove (index);
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::removeConnection (const uint32 sourceNodeId, const int sourceChannelIndex,
|
||||
const uint32 destNodeId, const int destChannelIndex)
|
||||
{
|
||||
bool doneAnything = false;
|
||||
|
||||
for (int i = connections.size(); --i >= 0;)
|
||||
{
|
||||
const Connection* const c = connections.getUnchecked(i);
|
||||
|
||||
if (c->sourceNodeId == sourceNodeId
|
||||
&& c->destNodeId == destNodeId
|
||||
&& c->sourceChannelIndex == sourceChannelIndex
|
||||
&& c->destChannelIndex == destChannelIndex)
|
||||
{
|
||||
removeConnection (i);
|
||||
doneAnything = true;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
return doneAnything;
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::disconnectNode (const uint32 nodeId)
|
||||
{
|
||||
bool doneAnything = false;
|
||||
|
||||
for (int i = connections.size(); --i >= 0;)
|
||||
{
|
||||
const Connection* const c = connections.getUnchecked(i);
|
||||
|
||||
if (c->sourceNodeId == nodeId || c->destNodeId == nodeId)
|
||||
{
|
||||
removeConnection (i);
|
||||
doneAnything = true;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
return doneAnything;
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::removeIllegalConnections()
|
||||
{
|
||||
bool doneAnything = false;
|
||||
|
||||
for (int i = connections.size(); --i >= 0;)
|
||||
{
|
||||
const Connection* const c = connections.getUnchecked(i);
|
||||
|
||||
const Node* const source = getNodeForId (c->sourceNodeId);
|
||||
const Node* const dest = getNodeForId (c->destNodeId);
|
||||
|
||||
if (source == nullptr || dest == nullptr
|
||||
|| (c->sourceChannelIndex != midiChannelIndex
|
||||
&& ! isPositiveAndBelow (c->sourceChannelIndex, source->processor->getNumOutputChannels()))
|
||||
|| (c->sourceChannelIndex == midiChannelIndex
|
||||
&& ! source->processor->producesMidi())
|
||||
|| (c->destChannelIndex != midiChannelIndex
|
||||
&& ! isPositiveAndBelow (c->destChannelIndex, dest->processor->getNumInputChannels()))
|
||||
|| (c->destChannelIndex == midiChannelIndex
|
||||
&& ! dest->processor->acceptsMidi()))
|
||||
{
|
||||
removeConnection (i);
|
||||
doneAnything = true;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
return doneAnything;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -980,21 +1092,22 @@ void AudioProcessorGraph::buildRenderingSequence()
|
|||
|
||||
Array<void*> orderedNodes;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < nodes.size(); ++i)
|
||||
{
|
||||
Node* const node = nodes.getUnchecked(i);
|
||||
const GraphRenderingOps::ConnectionLookupTable table (connections);
|
||||
|
||||
node->prepare (getSampleRate(), getBlockSize(), this);
|
||||
for (int i = 0; i < nodes.size(); ++i)
|
||||
{
|
||||
Node* const node = nodes.getUnchecked(i);
|
||||
|
||||
int j = 0;
|
||||
for (; j < orderedNodes.size(); ++j)
|
||||
if (isAnInputTo (node->id,
|
||||
((Node*) orderedNodes.getUnchecked (j))->id,
|
||||
nodes.size() + 1))
|
||||
break;
|
||||
node->prepare (getSampleRate(), getBlockSize(), this);
|
||||
|
||||
orderedNodes.insert (j, node);
|
||||
int j = 0;
|
||||
for (; j < orderedNodes.size(); ++j)
|
||||
if (table.isAnInputTo (node->nodeId, ((Node*) orderedNodes.getUnchecked(j))->nodeId))
|
||||
break;
|
||||
|
||||
orderedNodes.insert (j, node);
|
||||
}
|
||||
}
|
||||
|
||||
GraphRenderingOps::RenderingOpSequenceCalculator calculator (*this, orderedNodes, newRenderingOps);
|
||||
|
|
@ -1162,38 +1275,38 @@ void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer
|
|||
|
||||
switch (type)
|
||||
{
|
||||
case audioOutputNode:
|
||||
{
|
||||
for (int i = jmin (graph->currentAudioOutputBuffer.getNumChannels(),
|
||||
buffer.getNumChannels()); --i >= 0;)
|
||||
case audioOutputNode:
|
||||
{
|
||||
graph->currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples());
|
||||
for (int i = jmin (graph->currentAudioOutputBuffer.getNumChannels(),
|
||||
buffer.getNumChannels()); --i >= 0;)
|
||||
{
|
||||
graph->currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case audioInputNode:
|
||||
{
|
||||
for (int i = jmin (graph->currentAudioInputBuffer->getNumChannels(),
|
||||
buffer.getNumChannels()); --i >= 0;)
|
||||
case audioInputNode:
|
||||
{
|
||||
buffer.copyFrom (i, 0, *graph->currentAudioInputBuffer, i, 0, buffer.getNumSamples());
|
||||
for (int i = jmin (graph->currentAudioInputBuffer->getNumChannels(),
|
||||
buffer.getNumChannels()); --i >= 0;)
|
||||
{
|
||||
buffer.copyFrom (i, 0, *graph->currentAudioInputBuffer, i, 0, buffer.getNumSamples());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case midiOutputNode:
|
||||
graph->currentMidiOutputBuffer.addEvents (midiMessages, 0, buffer.getNumSamples(), 0);
|
||||
break;
|
||||
|
||||
case midiOutputNode:
|
||||
graph->currentMidiOutputBuffer.addEvents (midiMessages, 0, buffer.getNumSamples(), 0);
|
||||
break;
|
||||
case midiInputNode:
|
||||
midiMessages.addEvents (*graph->currentMidiInputBuffer, 0, buffer.getNumSamples(), 0);
|
||||
break;
|
||||
|
||||
case midiInputNode:
|
||||
midiMessages.addEvents (*graph->currentMidiInputBuffer, 0, buffer.getNumSamples(), 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1241,15 +1354,8 @@ bool AudioProcessorGraph::AudioGraphIOProcessor::isOutputChannelStereoPair (int
|
|||
return isInputChannelStereoPair (index);
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::AudioGraphIOProcessor::isInput() const
|
||||
{
|
||||
return type == audioInputNode || type == midiInputNode;
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::AudioGraphIOProcessor::isOutput() const
|
||||
{
|
||||
return type == audioOutputNode || type == midiOutputNode;
|
||||
}
|
||||
bool AudioProcessorGraph::AudioGraphIOProcessor::isInput() const { return type == audioInputNode || type == midiInputNode; }
|
||||
bool AudioProcessorGraph::AudioGraphIOProcessor::isOutput() const { return type == audioOutputNode || type == midiOutputNode; }
|
||||
|
||||
bool AudioProcessorGraph::AudioGraphIOProcessor::hasEditor() const { return false; }
|
||||
AudioProcessorEditor* AudioProcessorGraph::AudioGraphIOProcessor::createEditor() { return nullptr; }
|
||||
|
|
@ -1266,15 +1372,10 @@ int AudioProcessorGraph::AudioGraphIOProcessor::getCurrentProgram()
|
|||
void AudioProcessorGraph::AudioGraphIOProcessor::setCurrentProgram (int) { }
|
||||
|
||||
const String AudioProcessorGraph::AudioGraphIOProcessor::getProgramName (int) { return String::empty; }
|
||||
void AudioProcessorGraph::AudioGraphIOProcessor::changeProgramName (int, const String&) { }
|
||||
void AudioProcessorGraph::AudioGraphIOProcessor::changeProgramName (int, const String&) {}
|
||||
|
||||
void AudioProcessorGraph::AudioGraphIOProcessor::getStateInformation (JUCE_NAMESPACE::MemoryBlock&)
|
||||
{
|
||||
}
|
||||
|
||||
void AudioProcessorGraph::AudioGraphIOProcessor::setStateInformation (const void*, int)
|
||||
{
|
||||
}
|
||||
void AudioProcessorGraph::AudioGraphIOProcessor::getStateInformation (JUCE_NAMESPACE::MemoryBlock&) {}
|
||||
void AudioProcessorGraph::AudioGraphIOProcessor::setStateInformation (const void*, int) {}
|
||||
|
||||
void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorGraph* const newGraph)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -71,10 +71,9 @@ public:
|
|||
public:
|
||||
//==============================================================================
|
||||
/** The ID number assigned to this node.
|
||||
|
||||
This is assigned by the graph that owns it, and can't be changed.
|
||||
*/
|
||||
const uint32 id;
|
||||
const uint32 nodeId;
|
||||
|
||||
/** The actual processor object that this node represents. */
|
||||
AudioProcessor* getProcessor() const noexcept { return processor; }
|
||||
|
|
@ -99,7 +98,7 @@ public:
|
|||
const ScopedPointer<AudioProcessor> processor;
|
||||
bool isPrepared;
|
||||
|
||||
Node (uint32 id, AudioProcessor* processor);
|
||||
Node (uint32 nodeId, AudioProcessor* processor) noexcept;
|
||||
|
||||
void prepare (double sampleRate, int blockSize, AudioProcessorGraph* graph);
|
||||
void unprepare();
|
||||
|
|
@ -115,6 +114,10 @@ public:
|
|||
struct JUCE_API Connection
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
Connection (uint32 sourceNodeId, int sourceChannelIndex,
|
||||
uint32 destNodeId, int destChannelIndex) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** The ID number of the node which is the input source for this connection.
|
||||
@see AudioProcessorGraph::getNodeForId
|
||||
|
|
|
|||
|
|
@ -42,10 +42,9 @@ static const int quitMessageId = 0xfffff321;
|
|||
MessageManager::MessageManager() noexcept
|
||||
: quitMessagePosted (false),
|
||||
quitMessageReceived (false),
|
||||
messageThreadId (Thread::getCurrentThreadId()),
|
||||
threadWithLock (0)
|
||||
{
|
||||
messageThreadId = Thread::getCurrentThreadId();
|
||||
|
||||
if (JUCEApplication::isStandaloneApp())
|
||||
Thread::setCurrentThreadName ("Juce Message Thread");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,8 +106,7 @@ public:
|
|||
@returns the value that the callback function returns.
|
||||
@see MessageManagerLock
|
||||
*/
|
||||
void* callFunctionOnMessageThread (MessageCallbackFunction* callback,
|
||||
void* userData);
|
||||
void* callFunctionOnMessageThread (MessageCallbackFunction* callback, void* userData);
|
||||
|
||||
/** Returns true if the caller-thread is the message thread. */
|
||||
bool isThisTheMessageThread() const noexcept;
|
||||
|
|
@ -157,12 +156,12 @@ public:
|
|||
void deregisterBroadcastListener (ActionListener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
#ifndef DOXYGEN
|
||||
// Internal methods - do not use!
|
||||
void deliverMessage (Message*);
|
||||
/** @internal */
|
||||
void deliverBroadcastMessage (const String&);
|
||||
/** @internal */
|
||||
~MessageManager() noexcept;
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
@ -294,12 +293,10 @@ public:
|
|||
|
||||
//==============================================================================
|
||||
/** Returns true if the lock was successfully acquired.
|
||||
|
||||
(See the constructor that takes a Thread for more info).
|
||||
*/
|
||||
bool lockWasGained() const noexcept { return locked; }
|
||||
|
||||
|
||||
private:
|
||||
class BlockingMessage;
|
||||
friend class ReferenceCountedObjectPtr<BlockingMessage>;
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@ public:
|
|||
};
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
/** @internal */
|
||||
void paint (Graphics& g);
|
||||
/** @internal */
|
||||
|
|
@ -251,6 +252,7 @@ public:
|
|||
void parentHierarchyChanged();
|
||||
/** @internal */
|
||||
const Rectangle<int> getTitleBarArea();
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ protected:
|
|||
/** @internal */
|
||||
int getDesktopWindowStyleFlags() const;
|
||||
|
||||
#if JUCE_DEBUG
|
||||
#if JUCE_DEBUG
|
||||
/** Overridden to warn people about adding components directly to this component
|
||||
instead of using setContentOwned().
|
||||
|
||||
|
|
@ -355,7 +355,7 @@ protected:
|
|||
a base-class method call to Component::addAndMakeVisible(), to side-step this warning.
|
||||
*/
|
||||
void addAndMakeVisible (Component* child, int zOrder = -1);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
ScopedPointer <ResizableCornerComponent> resizableCorner;
|
||||
ScopedPointer <ResizableBorderComponent> resizableBorder;
|
||||
|
|
|
|||
|
|
@ -140,8 +140,9 @@ public:
|
|||
|
||||
|
||||
protected:
|
||||
/** @internal */
|
||||
#ifndef DOXYGEN
|
||||
CameraDevice (const String& name, int index);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void* internal;
|
||||
|
|
|
|||
|
|
@ -123,8 +123,9 @@ public:
|
|||
TimeSliceClient* getClient (int index) const;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
#ifndef DOXYGEN
|
||||
void run();
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
private:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue