| @@ -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(); | |||
| } | |||
| @@ -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(); | |||
| if (numCues > 0) | |||
| { | |||
| const size_t sizeNeeded = sizeof (CueChunk) + (numCues - 1) * sizeof (Cue); | |||
| data.setSize ((sizeNeeded + 3) & ~3, true); | |||
| 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()); | |||
| CueChunk* const c = static_cast <CueChunk*> (data.getData()); | |||
| c->numCues = ByteOrder::swapIfBigEndian ((uint32) numCues); | |||
| c->numCues = ByteOrder::swapIfBigEndian ((uint32) numCues); | |||
| const String dataChunkID (chunkName ("data")); | |||
| const String dataChunkID (chunkName ("data")); | |||
| int nextOrder = 0; | |||
| for (int i = 0; i < numCues; ++i) | |||
| { | |||
| 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()); | |||
| } | |||
| #if JUCE_DEBUG | |||
| Array<int> identifiers; | |||
| #endif | |||
| return data; | |||
| 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__ | |||
| @@ -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: | |||