From f9387f9a34f44e6e32c79f3677c9fc9d84a35b98 Mon Sep 17 00:00:00 2001 From: falkTX Date: Mon, 2 Jan 2017 16:55:20 +0000 Subject: [PATCH] Update to latest juce --- data/copy-juce-carla | 4 +- source/modules/AppConfig.h | 1 + .../buffers/juce_AudioChannelSet.cpp | 336 +++++ .../buffers/juce_AudioChannelSet.h | 361 +++++ .../buffers/juce_AudioSampleBuffer.h | 4 +- .../buffers/juce_FloatVectorOperations.cpp | 38 +- .../buffers/juce_FloatVectorOperations.h | 4 +- .../effects/juce_CatmullRomInterpolator.cpp | 1 - .../effects/juce_CatmullRomInterpolator.h | 1 - .../effects/juce_IIRFilter.cpp | 133 +- .../effects/juce_IIRFilter.h | 39 + .../effects/juce_LagrangeInterpolator.h | 1 - .../effects/juce_LinearSmoothedValue.h | 18 +- .../juce_audio_basics/juce_audio_basics.cpp | 10 + .../juce_audio_basics/juce_audio_basics.h | 30 +- .../juce_audio_basics/midi/juce_MidiFile.cpp | 2 +- .../midi/juce_MidiMessage.cpp | 166 ++- .../juce_audio_basics/midi/juce_MidiMessage.h | 40 +- .../midi/juce_MidiMessageSequence.cpp | 2 +- .../midi/juce_MidiMessageSequence.h | 14 +- .../juce_audio_basics/midi/juce_MidiRPN.cpp | 3 +- .../mpe/juce_MPEInstrument.cpp | 358 ++--- .../mpe/juce_MPEInstrument.h | 31 +- .../mpe/juce_MPEMessages.cpp | 1 - .../mpe/juce_MPESynthesiserBase.cpp | 15 +- .../mpe/juce_MPESynthesiserBase.h | 12 +- .../juce_audio_basics/mpe/juce_MPEZone.h | 20 +- .../mpe/juce_MPEZoneLayout.cpp | 1 + .../mpe/juce_MPEZoneLayout.h | 5 +- .../sources/juce_BufferingAudioSource.cpp | 55 +- .../sources/juce_BufferingAudioSource.h | 32 +- .../sources/juce_ResamplingAudioSource.h | 2 +- .../synthesisers/juce_Synthesiser.cpp | 22 +- .../synthesisers/juce_Synthesiser.h | 9 +- .../audio_io/juce_AudioDeviceManager.cpp | 298 +---- .../audio_io/juce_AudioDeviceManager.h | 98 +- .../juce_audio_devices/juce_audio_devices.cpp | 39 +- .../juce_audio_devices/juce_audio_devices.h | 50 +- .../native/juce_MidiDataConcatenator.h | 13 +- .../native/juce_android_Midi.cpp | 2 +- .../native/juce_android_OpenSL.cpp | 4 +- .../native/juce_ios_Audio.cpp | 53 +- .../native/juce_linux_JackAudio.cpp | 4 +- .../native/juce_linux_Midi.cpp | 170 +-- .../native/juce_mac_CoreAudio.cpp | 61 +- .../native/juce_mac_CoreMidi.cpp | 12 + .../native/juce_win32_ASIO.cpp | 7 +- .../native/juce_win32_WASAPI.cpp | 7 + .../sources/juce_AudioTransportSource.h | 2 +- .../juce_audio_formats/codecs/flac/alloc.h | 13 +- .../juce_audio_formats/codecs/flac/assert.h | 5 +- .../juce_audio_formats/codecs/flac/callback.h | 5 +- .../juce_audio_formats/codecs/flac/compat.h | 33 - .../juce_audio_formats/codecs/flac/endswap.h | 4 +- .../juce_audio_formats/codecs/flac/metadata.h | 1 - .../codecs/flac/stream_decoder.h | 1 - .../codecs/flac/stream_encoder.h | 1 - .../codecs/flac/win_utf8_io.h | 5 - .../codecs/juce_AiffAudioFormat.cpp | 14 +- .../codecs/juce_FlacAudioFormat.cpp | 71 +- .../codecs/juce_QuickTimeAudioFormat.cpp | 2 +- .../codecs/juce_WavAudioFormat.cpp | 10 +- .../codecs/juce_WindowsMediaAudioFormat.cpp | 5 +- .../juce_audio_formats/juce_audio_formats.cpp | 2 + .../juce_audio_formats/juce_audio_formats.h | 28 +- .../format/juce_AudioPluginFormat.cpp | 183 +++ .../format/juce_AudioPluginFormat.h | 72 +- .../format/juce_AudioPluginFormatManager.cpp | 96 +- .../format/juce_AudioPluginFormatManager.h | 42 + .../format_types/juce_AU_Shared.h | 778 +++++++++++ .../format_types/juce_AudioUnitPluginFormat.h | 18 +- .../juce_AudioUnitPluginFormat.mm | 1103 ++++++++++++---- .../format_types/juce_LADSPAPluginFormat.cpp | 21 +- .../format_types/juce_LADSPAPluginFormat.h | 12 +- .../format_types/juce_VST3Common.h | 106 +- .../format_types/juce_VST3Headers.h | 10 +- .../format_types/juce_VST3PluginFormat.cpp | 909 +++++++++---- .../format_types/juce_VST3PluginFormat.h | 36 +- .../format_types/juce_VSTCommon.h | 238 ++++ .../format_types/juce_VSTInterface.h | 458 +++++++ .../format_types/juce_VSTMidiEventList.h | 77 +- .../format_types/juce_VSTPluginFormat.cpp | 1161 +++++++++-------- .../format_types/juce_VSTPluginFormat.h | 35 +- .../juce_audio_processors.cpp | 40 +- .../juce_audio_processors.h | 41 +- .../processors/juce_AudioPluginInstance.h | 3 + .../processors/juce_AudioProcessor.cpp | 930 ++++++++++--- .../processors/juce_AudioProcessor.h | 711 ++++++++-- .../processors/juce_AudioProcessorEditor.cpp | 131 +- .../processors/juce_AudioProcessorEditor.h | 86 +- .../processors/juce_AudioProcessorGraph.cpp | 72 +- .../processors/juce_AudioProcessorGraph.h | 8 + .../juce_GenericAudioProcessorEditor.cpp | 10 + .../processors/juce_PluginDescription.h | 2 +- .../scanning/juce_KnownPluginList.cpp | 96 +- .../scanning/juce_KnownPluginList.h | 5 +- .../scanning/juce_PluginDirectoryScanner.cpp | 8 +- .../scanning/juce_PluginDirectoryScanner.h | 18 +- .../scanning/juce_PluginListComponent.cpp | 27 +- .../scanning/juce_PluginListComponent.h | 10 +- .../utilities/juce_AudioParameterBool.h | 3 +- .../utilities/juce_AudioParameterChoice.h | 3 +- .../utilities/juce_AudioParameterFloat.h | 3 +- .../utilities/juce_AudioParameterInt.h | 3 +- .../juce_AudioProcessorParameterWithID.h | 8 +- .../juce_AudioProcessorParameters.cpp | 2 +- .../juce_AudioProcessorValueTreeState.cpp | 49 +- .../juce_AudioProcessorValueTreeState.h | 11 +- .../modules/juce_core/containers/juce_Array.h | 93 +- .../juce_core/containers/juce_DynamicObject.h | 2 +- .../juce_core/containers/juce_ListenerList.h | 5 +- .../containers/juce_NamedValueSet.cpp | 53 +- .../juce_core/containers/juce_NamedValueSet.h | 43 +- .../juce_core/containers/juce_OwnedArray.h | 17 +- .../juce_core/containers/juce_PropertySet.h | 4 +- .../containers/juce_ReferenceCountedArray.h | 17 +- .../juce_core/containers/juce_SortedSet.h | 8 +- .../juce_core/containers/juce_Variant.cpp | 5 +- .../juce_core/containers/juce_Variant.h | 44 +- source/modules/juce_core/files/juce_File.cpp | 80 +- source/modules/juce_core/files/juce_File.h | 23 +- .../juce_core/files/juce_FileOutputStream.h | 4 + .../juce_core/files/juce_FileSearchPath.cpp | 5 +- .../juce_core/files/juce_FileSearchPath.h | 7 +- .../juce_core/files/juce_MemoryMappedFile.h | 12 +- .../juce_core/files/juce_TemporaryFile.cpp | 2 +- .../juce_core/javascript/juce_JSON.cpp | 17 +- .../modules/juce_core/javascript/juce_JSON.h | 8 +- .../juce_core/javascript/juce_Javascript.cpp | 104 +- source/modules/juce_core/juce_core.cpp | 6 +- source/modules/juce_core/juce_core.h | 50 +- .../juce_core/maths/juce_BigInteger.cpp | 708 +++++++--- .../modules/juce_core/maths/juce_BigInteger.h | 53 +- .../juce_core/maths/juce_MathsFunctions.h | 21 +- .../juce_core/maths/juce_NormalisableRange.h | 60 +- source/modules/juce_core/maths/juce_Random.h | 4 +- source/modules/juce_core/maths/juce_Range.h | 9 + source/modules/juce_core/memory/juce_Atomic.h | 44 +- .../modules/juce_core/memory/juce_ByteOrder.h | 17 +- .../memory/juce_LeakedObjectDetector.h | 9 +- .../juce_core/memory/juce_MemoryBlock.h | 20 +- .../memory/juce_ReferenceCountedObject.h | 36 +- .../juce_core/memory/juce_ScopedPointer.h | 10 +- .../memory/juce_SharedResourcePointer.h | 2 +- .../juce_core/misc/juce_RuntimePermissions.h | 6 +- .../java/AndroidRuntimePermissions.java | 2 - .../native/java/JuceAppActivity.java | 15 +- .../native/juce_BasicNativeHeaders.h | 4 + .../juce_android_RuntimePermissions.cpp | 2 +- .../juce_core/native/juce_curl_Network.cpp | 25 +- .../juce_core/native/juce_linux_Network.cpp | 15 +- .../juce_core/native/juce_mac_Files.mm | 18 +- .../juce_core/native/juce_mac_Network.mm | 308 ++++- .../juce_core/native/juce_mac_SystemStats.mm | 4 +- .../juce_core/native/juce_mac_Threads.mm | 9 +- .../juce_core/native/juce_osx_ObjCHelpers.h | 52 +- .../juce_core/native/juce_posix_SharedCode.h | 43 +- .../juce_core/native/juce_win32_ComSmartPtr.h | 18 +- .../juce_core/native/juce_win32_Files.cpp | 20 +- .../juce_core/native/juce_win32_Network.cpp | 2 +- .../native/juce_win32_SystemStats.cpp | 74 +- .../juce_core/native/juce_win32_Threads.cpp | 27 - .../modules/juce_core/network/juce_Socket.cpp | 71 +- .../modules/juce_core/network/juce_Socket.h | 2 +- source/modules/juce_core/network/juce_URL.cpp | 20 +- source/modules/juce_core/network/juce_URL.h | 27 +- .../juce_core/system/juce_CompilerSupport.h | 2 +- .../juce_core/system/juce_PlatformDefs.h | 4 +- .../juce_core/system/juce_StandardHeader.h | 31 +- .../juce_core/system/juce_SystemStats.cpp | 26 + .../juce_core/system/juce_SystemStats.h | 7 + .../juce_core/system/juce_TargetPlatform.h | 36 +- .../juce_core/text/juce_CharPointer_ASCII.h | 6 - .../text/juce_CharacterFunctions.cpp | 13 + .../juce_core/text/juce_CharacterFunctions.h | 3 + .../modules/juce_core/text/juce_Identifier.h | 12 + source/modules/juce_core/text/juce_String.cpp | 71 +- source/modules/juce_core/text/juce_String.h | 13 +- .../juce_core/text/juce_StringArray.cpp | 14 +- .../modules/juce_core/text/juce_StringArray.h | 15 +- .../modules/juce_core/text/juce_TextDiff.cpp | 6 +- .../modules/juce_core/threads/juce_Thread.cpp | 8 + .../modules/juce_core/threads/juce_Thread.h | 12 +- .../juce_core/threads/juce_ThreadPool.cpp | 17 +- .../juce_core/threads/juce_ThreadPool.h | 18 +- .../juce_core/time/juce_PerformanceCounter.h | 2 +- source/modules/juce_core/time/juce_Time.cpp | 49 +- .../juce_core/unit_tests/juce_UnitTest.h | 93 +- .../juce_core/xml/juce_XmlDocument.cpp | 13 +- .../modules/juce_core/xml/juce_XmlElement.cpp | 21 +- .../modules/juce_core/xml/juce_XmlElement.h | 4 +- source/modules/juce_core/zip/juce_ZipFile.cpp | 2 +- source/modules/juce_core/zip/juce_ZipFile.h | 6 +- .../juce_data_structures.cpp | 3 + .../juce_data_structures.h | 27 +- .../undomanager/juce_UndoManager.cpp | 44 +- .../undomanager/juce_UndoManager.h | 6 +- .../undomanager/juce_UndoableAction.h | 2 +- .../values/juce_CachedValue.cpp | 152 +++ .../values/juce_CachedValue.h | 311 +++++ .../values/juce_ValueTree.cpp | 47 +- .../values/juce_ValueTree.h | 38 +- .../juce_InterprocessConnectionServer.cpp | 4 +- .../juce_InterprocessConnectionServer.h | 9 +- source/modules/juce_events/juce_events.cpp | 2 + source/modules/juce_events/juce_events.h | 27 +- .../messages/juce_ApplicationBase.cpp | 14 + .../native/juce_ios_MessageManager.mm | 4 +- .../native/juce_linux_Messaging.cpp | 29 +- .../native/juce_mac_MessageManager.mm | 4 +- .../modules/juce_events/timers/juce_Timer.h | 10 +- .../colour/juce_ColourGradient.cpp | 8 +- .../juce_graphics/colour/juce_FillType.cpp | 4 +- .../contexts/juce_GraphicsContext.cpp | 78 +- .../contexts/juce_GraphicsContext.h | 50 +- .../juce_graphics/fonts/juce_CustomTypeface.h | 2 +- .../fonts/juce_GlyphArrangement.h | 2 +- .../juce_graphics/fonts/juce_TextLayout.cpp | 8 +- .../juce_graphics/fonts/juce_TextLayout.h | 2 +- .../geometry/juce_AffineTransform.cpp | 2 + .../geometry/juce_AffineTransform.h | 12 +- .../juce_graphics/geometry/juce_Line.h | 53 +- .../juce_graphics/geometry/juce_Path.cpp | 22 +- .../juce_graphics/geometry/juce_Path.h | 31 +- .../geometry/juce_PathIterator.cpp | 2 - .../geometry/juce_PathIterator.h | 5 +- .../geometry/juce_PathStrokeType.cpp | 14 +- .../juce_graphics/geometry/juce_Rectangle.h | 67 +- .../image_formats/juce_JPEGLoader.cpp | 2 +- .../juce_graphics/images/juce_Image.cpp | 21 +- .../modules/juce_graphics/images/juce_Image.h | 11 +- .../juce_graphics/images/juce_ImageCache.cpp | 4 +- .../modules/juce_graphics/juce_graphics.cpp | 12 + source/modules/juce_graphics/juce_graphics.h | 31 +- .../native/juce_RenderingHelpers.h | 4 +- .../native/juce_mac_CoreGraphicsContext.mm | 20 +- .../juce_graphics/native/juce_mac_Fonts.mm | 8 +- .../juce_win32_DirectWriteTypeLayout.cpp | 8 +- .../juce_gui_basics/buttons/juce_Button.cpp | 25 +- .../juce_gui_basics/buttons/juce_Button.h | 4 +- .../buttons/juce_DrawableButton.h | 14 +- .../buttons/juce_HyperlinkButton.cpp | 4 +- .../buttons/juce_ImageButton.h | 2 +- .../buttons/juce_ToggleButton.cpp | 2 +- .../buttons/juce_ToggleButton.h | 4 +- .../commands/juce_ApplicationCommandInfo.h | 2 +- .../commands/juce_ApplicationCommandTarget.h | 2 +- .../components/juce_Component.cpp | 57 +- .../components/juce_Component.h | 30 +- .../components/juce_Desktop.cpp | 1 + .../juce_gui_basics/components/juce_Desktop.h | 2 +- .../components/juce_ModalComponentManager.cpp | 21 +- .../components/juce_ModalComponentManager.h | 39 +- .../drawables/juce_DrawableRectangle.cpp | 1 + .../drawables/juce_DrawableShape.cpp | 2 +- .../drawables/juce_SVGParser.cpp | 108 +- .../filebrowser/juce_DirectoryContentsList.h | 2 +- .../filebrowser/juce_FileBrowserComponent.cpp | 14 +- .../filebrowser/juce_FileBrowserComponent.h | 4 +- .../filebrowser/juce_FileChooser.cpp | 10 +- .../filebrowser/juce_FileChooser.h | 8 +- .../filebrowser/juce_FileChooserDialogBox.cpp | 2 +- .../filebrowser/juce_FileChooserDialogBox.h | 4 +- .../filebrowser/juce_FileListComponent.cpp | 6 +- .../filebrowser/juce_FilePreviewComponent.h | 2 +- .../juce_FileSearchPathListComponent.cpp | 8 +- .../filebrowser/juce_FileTreeComponent.cpp | 4 +- .../filebrowser/juce_FileTreeComponent.h | 10 +- .../filebrowser/juce_FilenameComponent.cpp | 4 +- .../juce_ImagePreviewComponent.cpp | 2 +- .../juce_gui_basics/juce_gui_basics.cpp | 11 +- .../modules/juce_gui_basics/juce_gui_basics.h | 40 +- .../keyboard/juce_KeyPress.cpp | 2 +- .../juce_gui_basics/layout/juce_FlexBox.cpp | 836 ++++++++++++ .../juce_gui_basics/layout/juce_FlexBox.h | 102 ++ .../juce_gui_basics/layout/juce_FlexItem.h | 142 ++ .../layout/juce_GroupComponent.h | 4 +- .../layout/juce_MultiDocumentPanel.cpp | 2 +- .../layout/juce_ResizableBorderComponent.h | 6 +- .../layout/juce_ResizableEdgeComponent.h | 6 +- .../layout/juce_TabbedButtonBar.cpp | 2 +- .../juce_gui_basics/layout/juce_Viewport.cpp | 108 +- .../juce_gui_basics/layout/juce_Viewport.h | 18 +- .../lookandfeel/juce_LookAndFeel.h | 2 +- .../lookandfeel/juce_LookAndFeel_V2.cpp | 36 +- .../lookandfeel/juce_LookAndFeel_V2.h | 3 + .../lookandfeel/juce_LookAndFeel_V3.cpp | 1 + .../menus/juce_MenuBarComponent.cpp | 5 + .../menus/juce_MenuBarModel.cpp | 9 + .../juce_gui_basics/menus/juce_MenuBarModel.h | 15 +- .../juce_gui_basics/menus/juce_PopupMenu.cpp | 683 +++++----- .../juce_gui_basics/menus/juce_PopupMenu.h | 143 +- .../misc/juce_DropShadower.cpp | 2 +- .../mouse/juce_DragAndDropContainer.h | 2 +- .../mouse/juce_LassoComponent.h | 2 +- .../mouse/juce_MouseInputSource.cpp | 2 +- .../mouse/juce_SelectedItemSet.h | 6 +- .../native/juce_android_Windowing.cpp | 5 +- .../native/juce_ios_UIViewComponentPeer.mm | 65 +- .../native/juce_ios_Windowing.mm | 17 +- .../native/juce_linux_FileChooser.cpp | 2 - .../native/juce_linux_Windowing.cpp | 32 +- .../native/juce_mac_FileChooser.mm | 2 +- .../native/juce_mac_MainMenu.mm | 78 +- .../native/juce_mac_MouseCursor.mm | 23 +- .../native/juce_mac_NSViewComponentPeer.mm | 139 +- .../native/juce_mac_Windowing.mm | 16 +- .../native/juce_win32_FileChooser.cpp | 2 +- .../native/juce_win32_Windowing.cpp | 107 +- .../properties/juce_PropertyPanel.cpp | 2 +- .../properties/juce_PropertyPanel.h | 4 + .../juce_SliderPropertyComponent.cpp | 10 +- .../properties/juce_SliderPropertyComponent.h | 6 +- .../properties/juce_TextPropertyComponent.cpp | 2 +- .../properties/juce_TextPropertyComponent.h | 15 +- .../juce_gui_basics/widgets/juce_ComboBox.cpp | 24 +- .../juce_gui_basics/widgets/juce_ComboBox.h | 10 +- .../widgets/juce_ImageComponent.cpp | 2 +- .../widgets/juce_ImageComponent.h | 2 +- .../juce_gui_basics/widgets/juce_Label.h | 6 +- .../juce_gui_basics/widgets/juce_ListBox.cpp | 8 +- .../juce_gui_basics/widgets/juce_ListBox.h | 12 +- .../juce_gui_basics/widgets/juce_Slider.cpp | 60 +- .../juce_gui_basics/widgets/juce_Slider.h | 17 +- .../widgets/juce_TableHeaderComponent.cpp | 2 +- .../widgets/juce_TableHeaderComponent.h | 4 +- .../widgets/juce_TextEditor.cpp | 13 +- .../juce_gui_basics/widgets/juce_TextEditor.h | 3 + .../juce_gui_basics/widgets/juce_Toolbar.cpp | 11 +- .../widgets/juce_ToolbarItemComponent.cpp | 2 +- .../widgets/juce_ToolbarItemComponent.h | 2 +- .../juce_gui_basics/widgets/juce_TreeView.cpp | 16 +- .../juce_gui_basics/widgets/juce_TreeView.h | 6 +- .../windows/juce_AlertWindow.cpp | 2 + .../windows/juce_CallOutBox.cpp | 21 +- .../juce_gui_basics/windows/juce_CallOutBox.h | 14 +- .../windows/juce_ComponentPeer.cpp | 2 +- .../windows/juce_DialogWindow.h | 4 +- .../windows/juce_DocumentWindow.h | 6 +- .../windows/juce_ResizableWindow.h | 4 +- .../windows/juce_TooltipWindow.cpp | 3 +- .../windows/juce_TopLevelWindow.h | 2 +- .../juce_CPlusPlusCodeTokeniserFunctions.h | 34 +- .../code_editor/juce_CodeDocument.h | 2 +- .../documents/juce_FileBasedDocument.cpp | 2 +- .../documents/juce_FileBasedDocument.h | 6 +- .../modules/juce_gui_extra/juce_gui_extra.cpp | 2 + .../modules/juce_gui_extra/juce_gui_extra.h | 28 +- .../misc/juce_ColourSelector.cpp | 30 +- .../juce_gui_extra/misc/juce_ColourSelector.h | 4 +- .../misc/juce_KeyMappingEditorComponent.cpp | 19 +- .../misc/juce_LiveConstantEditor.cpp | 39 +- .../misc/juce_LiveConstantEditor.h | 4 + .../misc/juce_RecentlyOpenedFilesList.h | 8 +- .../juce_gui_extra/misc/juce_SplashScreen.cpp | 28 +- .../juce_gui_extra/misc/juce_SplashScreen.h | 2 +- .../juce_android_WebBrowserComponent.cpp | 4 + .../native/juce_linux_SystemTrayIcon.cpp | 4 +- .../native/juce_linux_WebBrowserComponent.cpp | 4 + .../native/juce_mac_NSViewComponent.mm | 5 + .../native/juce_mac_SystemTrayIcon.cpp | 2 +- .../native/juce_win32_ActiveXComponent.cpp | 24 +- .../native/juce_win32_SystemTrayIcon.cpp | 2 +- .../native/juce_win32_WebBrowserComponent.cpp | 25 +- source/utils/CarlaJuceEvents.cpp | 2 +- source/utils/CarlaUtils.hpp | 1 + 366 files changed, 13853 insertions(+), 4315 deletions(-) create mode 100644 source/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp create mode 100644 source/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h create mode 100644 source/modules/juce_audio_processors/format_types/juce_AU_Shared.h create mode 100644 source/modules/juce_audio_processors/format_types/juce_VSTCommon.h create mode 100644 source/modules/juce_audio_processors/format_types/juce_VSTInterface.h create mode 100644 source/modules/juce_data_structures/values/juce_CachedValue.cpp create mode 100644 source/modules/juce_data_structures/values/juce_CachedValue.h create mode 100644 source/modules/juce_gui_basics/layout/juce_FlexBox.cpp create mode 100644 source/modules/juce_gui_basics/layout/juce_FlexBox.h create mode 100644 source/modules/juce_gui_basics/layout/juce_FlexItem.h diff --git a/data/copy-juce-carla b/data/copy-juce-carla index 8fff89738..4a1517543 100755 --- a/data/copy-juce-carla +++ b/data/copy-juce-carla @@ -2,8 +2,8 @@ set -e -JUCE_MODULES_DIR="/home/falktx/Projects/FOSS/GIT-mine/DISTRHO-Ports/libs/juce/source/modules/" -CARLA_MODULES_DIR="/home/falktx/Projects/FOSS/GIT-mine/Carla/source/modules/" +JUCE_MODULES_DIR="/Shared/Personal/FOSS/GIT/DISTRHO/DISTRHO-Ports/libs/juce/source/modules/" +CARLA_MODULES_DIR="/home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/" MODULES=("juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics juce_gui_extra") diff --git a/source/modules/AppConfig.h b/source/modules/AppConfig.h index 17a46b331..27e04c75d 100644 --- a/source/modules/AppConfig.h +++ b/source/modules/AppConfig.h @@ -60,6 +60,7 @@ // misc #define JUCE_DISABLE_JUCE_VERSION_PRINTING 1 +#define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1 #define JUCE_STANDALONE_APPLICATION 0 #define JUCE_STRING_UTF_TYPE 8 #define JUCE_USE_VFORK 1 diff --git a/source/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp b/source/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp new file mode 100644 index 000000000..8cfaa300b --- /dev/null +++ b/source/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp @@ -0,0 +1,336 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +AudioChannelSet::AudioChannelSet (uint32 c) : channels (c) {} + +bool AudioChannelSet::operator== (const AudioChannelSet& other) const noexcept { return channels == other.channels; } +bool AudioChannelSet::operator!= (const AudioChannelSet& other) const noexcept { return channels != other.channels; } +bool AudioChannelSet::operator< (const AudioChannelSet& other) const noexcept { return channels < other.channels; } + +String AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type) +{ + if (type >= discreteChannel0) + return String ("Discrete ") + String (type - discreteChannel0 + 1); + + switch (type) + { + case left: return NEEDS_TRANS("Left"); + case right: return NEEDS_TRANS("Right"); + case centre: return NEEDS_TRANS("Centre"); + case LFE: return NEEDS_TRANS("LFE"); + case leftSurround: return NEEDS_TRANS("Left Surround"); + case rightSurround: return NEEDS_TRANS("Right Surround"); + case leftCentre: return NEEDS_TRANS("Left Centre"); + case rightCentre: return NEEDS_TRANS("Right Centre"); + case centreSurround: return NEEDS_TRANS("Centre Surround"); + case leftSurroundRear: return NEEDS_TRANS("Left Surround Rear"); + case rightSurroundRear: return NEEDS_TRANS("Right Surround Rear"); + case topMiddle: return NEEDS_TRANS("Top Middle"); + case topFrontLeft: return NEEDS_TRANS("Top Front Left"); + case topFrontCentre: return NEEDS_TRANS("Top Front Centre"); + case topFrontRight: return NEEDS_TRANS("Top Front Right"); + case topRearLeft: return NEEDS_TRANS("Top Rear Left"); + case topRearCentre: return NEEDS_TRANS("Top Rear Centre"); + case topRearRight: return NEEDS_TRANS("Top Rear Right"); + case wideLeft: return NEEDS_TRANS("Wide Left"); + case wideRight: return NEEDS_TRANS("Wide Right"); + case LFE2: return NEEDS_TRANS("LFE 2"); + case leftSurroundSide: return NEEDS_TRANS ("Left Surround Side"); + case rightSurroundSide: return NEEDS_TRANS ("Right Surround Side"); + case ambisonicW: return NEEDS_TRANS("Ambisonic W"); + case ambisonicX: return NEEDS_TRANS("Ambisonic X"); + case ambisonicY: return NEEDS_TRANS("Ambisonic Y"); + case ambisonicZ: return NEEDS_TRANS("Ambisonic Z"); + default: break; + } + + return "Unknown"; +} + +String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelType type) +{ + if (type >= discreteChannel0) + return String (type - discreteChannel0 + 1); + + switch (type) + { + case left: return "L"; + case right: return "R"; + case centre: return "C"; + case LFE: return "Lfe"; + case leftSurround: return "Ls"; + case rightSurround: return "Rs"; + case leftCentre: return "Lc"; + case rightCentre: return "Rc"; + case centreSurround: return "Cs"; + case leftSurroundRear: return "Lrs"; + case rightSurroundRear: return "Rrs"; + case topMiddle: return "Tm"; + case topFrontLeft: return "Tfl"; + case topFrontCentre: return "Tfc"; + case topFrontRight: return "Tfr"; + case topRearLeft: return "Trl"; + case topRearCentre: return "Trc"; + case topRearRight: return "Trr"; + case wideLeft: return "Wl"; + case wideRight: return "Wr"; + case LFE2: return "Lfe2"; + case leftSurroundSide: return "Lss"; + case rightSurroundSide: return "Rss"; + case ambisonicW: return "W"; + case ambisonicX: return "X"; + case ambisonicY: return "Y"; + case ambisonicZ: return "Z"; + default: break; + } + + return ""; +} + +String AudioChannelSet::getSpeakerArrangementAsString() const +{ + StringArray speakerTypes; + Array speakers = getChannelTypes(); + + for (int i = 0; i < speakers.size(); ++i) + { + String name = getAbbreviatedChannelTypeName (speakers.getReference (i)); + + if (name.isNotEmpty()) + speakerTypes.add (name); + } + + return speakerTypes.joinIntoString (" "); +} + +String AudioChannelSet::getDescription() const +{ + if (isDiscreteLayout()) return String ("Discrete #") + String (size()); + if (*this == disabled()) return "Disabled"; + if (*this == mono()) return "Mono"; + if (*this == stereo()) return "Stereo"; + + if (*this == createLCR()) return "LCR"; + if (*this == createLRS()) return "LRS"; + if (*this == createLCRS()) return "LCRS"; + + if (*this == create5point0()) return "5.1 Surround"; + if (*this == create5point1()) return "5.1 Surround (+Lfe)"; + if (*this == create6point0()) return "6.1 Surround"; + if (*this == create6point1()) return "6.1 Surround (+Lfe)"; + if (*this == create6point0Music()) return "6.1 (Music) Surround"; + if (*this == create6point1Music()) return "6.1 (Music) Surround (+Lfe)"; + if (*this == create7point0()) return "7.1 Surround"; + if (*this == create7point1()) return "7.1 Surround (Lfe)"; + if (*this == create7point0SDDS()) return "7.1 Surround SDDS"; + if (*this == create7point1SDDS()) return "7.1 Surround SDDS (+Lfe)"; + + if (*this == quadraphonic()) return "Quadraphonic"; + if (*this == pentagonal()) return "Pentagonal"; + if (*this == hexagonal()) return "Hexagonal"; + if (*this == octagonal()) return "Octagonal"; + if (*this == ambisonic()) return "Ambisonic"; + + + + return "Unknown"; +} + +bool AudioChannelSet::isDiscreteLayout() const noexcept +{ + Array speakers = getChannelTypes(); + for (int i = 0; i < speakers.size(); ++i) + if (speakers.getReference (i) > ambisonicZ) + return true; + + return false; +} + +int AudioChannelSet::size() const noexcept +{ + return channels.countNumberOfSetBits(); +} + +AudioChannelSet::ChannelType AudioChannelSet::getTypeOfChannel (int index) const noexcept +{ + int bit = channels.findNextSetBit(0); + + for (int i = 0; i < index && bit >= 0; ++i) + bit = channels.findNextSetBit (bit + 1); + + return static_cast (bit); +} + +int AudioChannelSet::getChannelIndexForType (AudioChannelSet::ChannelType type) const noexcept +{ + int idx = 0; + for (int bit = channels.findNextSetBit (0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) + { + if (static_cast (bit) == type) + return idx; + + idx++; + } + + return -1; +} + +Array AudioChannelSet::getChannelTypes() const +{ + Array result; + + for (int bit = channels.findNextSetBit(0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) + result.add (static_cast (bit)); + + return result; +} + +void AudioChannelSet::addChannel (ChannelType newChannel) +{ + const int bit = static_cast (newChannel); + jassert (bit >= 0 && bit < 1024); + channels.setBit (bit); +} + +void AudioChannelSet::removeChannel (ChannelType newChannel) +{ + const int bit = static_cast (newChannel); + jassert (bit >= 0 && bit < 1024); + channels.clearBit (bit); +} + +AudioChannelSet AudioChannelSet::disabled() { return AudioChannelSet(); } +AudioChannelSet AudioChannelSet::mono() { return AudioChannelSet (1u << centre); } +AudioChannelSet AudioChannelSet::stereo() { return AudioChannelSet ((1u << left) | (1u << right)); } +AudioChannelSet AudioChannelSet::createLCR() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre)); } +AudioChannelSet AudioChannelSet::createLRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surround)); } +AudioChannelSet AudioChannelSet::createLCRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surround)); } +AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround)); } +AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << LFE)); } +AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround)); } +AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround) | (1u << LFE)); } +AudioChannelSet AudioChannelSet::create6point0Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide)); } +AudioChannelSet AudioChannelSet::create6point1Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << LFE)); } +AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } +AudioChannelSet AudioChannelSet::create7point0SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre)); } +AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << LFE)); } +AudioChannelSet AudioChannelSet::create7point1SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre) | (1u << LFE)); } +AudioChannelSet AudioChannelSet::ambisonic() { return AudioChannelSet ((1u << ambisonicW) | (1u << ambisonicX) | (1u << ambisonicY) | (1u << ambisonicZ)); } +AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround)); } +AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } +AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << centre) | (1u << centreSurround)); } +AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << centre) | (1u << centreSurround) | (1u << wideLeft) | (1u << wideRight)); } + + +AudioChannelSet AudioChannelSet::discreteChannels (int numChannels) +{ + AudioChannelSet s; + s.channels.setRange (discreteChannel0, numChannels, true); + return s; +} + +AudioChannelSet AudioChannelSet::canonicalChannelSet (int numChannels) +{ + if (numChannels == 1) return AudioChannelSet::mono(); + if (numChannels == 2) return AudioChannelSet::stereo(); + if (numChannels == 3) return AudioChannelSet::createLCR(); + if (numChannels == 4) return AudioChannelSet::quadraphonic(); + if (numChannels == 5) return AudioChannelSet::create5point0(); + if (numChannels == 6) return AudioChannelSet::create5point1(); + if (numChannels == 7) return AudioChannelSet::create7point0(); + if (numChannels == 8) return AudioChannelSet::create7point1(); + + return discreteChannels (numChannels); +} + +AudioChannelSet AudioChannelSet::namedChannelSet (int numChannels) +{ + if (numChannels == 1) return AudioChannelSet::mono(); + if (numChannels == 2) return AudioChannelSet::stereo(); + if (numChannels == 3) return AudioChannelSet::createLCR(); + if (numChannels == 4) return AudioChannelSet::quadraphonic(); + if (numChannels == 5) return AudioChannelSet::create5point0(); + if (numChannels == 6) return AudioChannelSet::create5point1(); + if (numChannels == 7) return AudioChannelSet::create7point0(); + if (numChannels == 8) return AudioChannelSet::create7point1(); + + return AudioChannelSet(); +} + +Array AudioChannelSet::channelSetsWithNumberOfChannels (int numChannels) +{ + Array retval; + + if (numChannels != 0) + { + retval.add (AudioChannelSet::discreteChannels (numChannels)); + + if (numChannels == 1) + { + retval.add (AudioChannelSet::mono()); + } + else if (numChannels == 2) + { + retval.add (AudioChannelSet::stereo()); + } + else if (numChannels == 3) + { + retval.add (AudioChannelSet::createLCR()); + retval.add (AudioChannelSet::createLRS()); + } + else if (numChannels == 4) + { + retval.add (AudioChannelSet::quadraphonic()); + retval.add (AudioChannelSet::createLCRS()); + retval.add (AudioChannelSet::ambisonic()); + } + else if (numChannels == 5) + { + retval.add (AudioChannelSet::create5point0()); + retval.add (AudioChannelSet::pentagonal()); + } + else if (numChannels == 6) + { + retval.add (AudioChannelSet::create5point1()); + retval.add (AudioChannelSet::create6point0()); + retval.add (AudioChannelSet::create6point0Music()); + retval.add (AudioChannelSet::hexagonal()); + } + else if (numChannels == 7) + { + retval.add (AudioChannelSet::create7point0()); + retval.add (AudioChannelSet::create7point0SDDS()); + retval.add (AudioChannelSet::create6point1()); + retval.add (AudioChannelSet::create6point1Music()); + } + else if (numChannels == 8) + { + retval.add (AudioChannelSet::create7point1()); + retval.add (AudioChannelSet::create7point1SDDS()); + retval.add (AudioChannelSet::octagonal()); + } + } + + return retval; +} diff --git a/source/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h b/source/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h new file mode 100644 index 000000000..c4c9274cc --- /dev/null +++ b/source/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h @@ -0,0 +1,361 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_AUDIOCHANNELSET_H_INCLUDED +#define JUCE_AUDIOCHANNELSET_H_INCLUDED + + +//============================================================================== +/** + Represents a set of audio channel types. + + For example, you might have a set of left + right channels, which is a stereo + channel set. It is a collection of values from the AudioChannelSet::ChannelType + enum, where each type may only occur once within the set. + + The documentation below lists which AudioChannelSet corresponds to which native + layouts used by AAX, VST2/VST3 and CoreAudio/AU. The layout tags in CoreAudio + are particularly confusing. For example, the layout which is labeled as "7.1 SDDS" + in Logic Pro, corresponds to CoreAudio/AU's kAudioChannelLayoutTag_DTS_7_0 tag, whereas + AAX's DTS 7.1 Layout corresponds to CoreAudio/AU's + kAudioChannelLayoutTag_MPEG_7_1_A format, etc. Please do not use the CoreAudio tag + as an indication to the actual layout of the speakers. + + @see Bus +*/ +class JUCE_API AudioChannelSet +{ +public: + /** Creates an empty channel set. + You can call addChannel to add channels to the set. + */ + AudioChannelSet() noexcept {} + + /** Creates a zero-channel set which can be used to indicate that a + bus is disabled. */ + static AudioChannelSet disabled(); + + //============================================================================== + /** Creates a one-channel mono set (centre). + + Is equivalent to: kMonoAAX (VST), AAX_eStemFormat_Mono (AAX), kAudioChannelLayoutTag_Mono (CoreAudio) + */ + static AudioChannelSet mono(); + + + /** Creates a set containing a stereo set (left, right). + + Is equivalent to: kStereo (VST), AAX_eStemFormat_Stereo (AAX), kAudioChannelLayoutTag_Stereo (CoreAudio) + */ + static AudioChannelSet stereo(); + + + //============================================================================== + /** Creates a set containing an LCR set (left, right, centre). + + Is equivalent to: k30Cine (VST), AAX_eStemFormat_LCR (AAX), kAudioChannelLayoutTag_MPEG_3_0_A (CoreAudio) + + This format is referred to as "LRC" in Cubase. + This format is referred to as "LCR" in Pro Tools. + */ + static AudioChannelSet createLCR(); + + + /** Creates a set containing an LRS set (left, right, surround). + + Is equivalent to: k30Music (VST), n/a (AAX), kAudioChannelLayoutTag_ITU_2_1 (CoreAudio) + + This format is referred to as "LRS" in Cubase. + */ + static AudioChannelSet createLRS(); + + + /** Creates a set containing an LCRS set (left, right, centre, surround). + + Is equivalent to: k40Cine (VST), AAX_eStemFormat_LCRS (AAX), kAudioChannelLayoutTag_MPEG_4_0_A (CoreAudio) + + This format is referred to as "LCRS (Pro Logic)" in Logic Pro. + This format is referred to as "LRCS" in Cubase. + This format is referred to as "LCRS" in Pro Tools. + */ + static AudioChannelSet createLCRS(); + + + //============================================================================== + /** Creates a set for a 5.0 surround setup (left, right, centre, leftSurround, rightSurround). + + Is equivalent to: k50 (VST), AAX_eStemFormat_5_0 (AAX), kAudioChannelLayoutTag_MPEG_5_0_A (CoreAudio) + + This format is referred to as "5.0" in Cubase. + This format is referred to as "5.0" in Pro Tools. + */ + static AudioChannelSet create5point0(); + + + /** Creates a set for a 5.1 surround setup (left, right, centre, leftSurround, rightSurround, LFE). + + Is equivalent to: k51 (VST), AAX_eStemFormat_5_1 (AAX), kAudioChannelLayoutTag_MPEG_5_1_A (CoreAudio) + + This format is referred to as "5.1 (ITU 775)" in Logic Pro. + This format is referred to as "5.1" in Cubase. + This format is referred to as "5.1" in Pro Tools. + */ + static AudioChannelSet create5point1(); + + + /** Creates a set for a 6.0 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround). + + Is equivalent to: k60Cine (VST), AAX_eStemFormat_6_0 (AAX), kAudioChannelLayoutTag_AudioUnit_6_0 (CoreAudio) + + Logic Pro incorrectly uses this for the surround format labeled "6.1 (ES/EX)". + This format is referred to as "6.0 Cine" in Cubase. + This format is referred to as "6.0" in Pro Tools. + */ + static AudioChannelSet create6point0(); + + + /** Creates a set for a 6.1 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround, LFE). + + Is equivalent to: k61Cine (VST), AAX_eStemFormat_6_1 (AAX), kAudioChannelLayoutTag_MPEG_6_1_A (CoreAudio) + + This format is referred to as "6.1" in Pro Tools. + */ + static AudioChannelSet create6point1(); + + + /** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide). + + Is equivalent to: k60Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_0_A (CoreAudio) + + This format is referred to as "6.0 Music" in Cubase. + */ + static AudioChannelSet create6point0Music(); + + + /** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide, LFE). + + Is equivalent to: k61Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_1_A (CoreAudio) + */ + static AudioChannelSet create6point1Music(); + + + /** Creates a set for a DTS 7.0 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear). + + Is equivalent to: k70Music (VST), AAX_eStemFormat_7_0_DTS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0 (CoreAudio) + + This format is referred to as "7.0" in Pro Tools. + */ + static AudioChannelSet create7point0(); + + + /** Creates a set for a SDDS 7.0 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre). + + Is equivalent to: k70Cine (VST), AAX_eStemFormat_7_0_SDDS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0_Front (CoreAudio) + + This format is referred to as "7.0 SDDS" in Pro Tools. + */ + static AudioChannelSet create7point0SDDS(); + + + /** Creates a set for a DTS 7.1 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE). + + Is equivalent to: k71CineSideFill (VST), AAX_eStemFormat_7_1_DTS (AAX), kAudioChannelLayoutTag_MPEG_7_1_C/kAudioChannelLayoutTag_ITU_3_4_1 (CoreAudio) + + This format is referred to as "7.1 (3/4.1)" in Logic Pro. + This format is referred to as "7.1" in Pro Tools. + */ + static AudioChannelSet create7point1(); + + + /** Creates a set for a 7.1 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre, LFE). + + Is equivalent to: k71Cine (VST), AAX_eStemFormat_7_1_SDDS (AAX), kAudioChannelLayoutTag_MPEG_7_1_A (CoreAudio) + + This format is referred to as "7.1 (SDDS)" in Logic Pro. + This format is referred to as "7.1 SDDS" in Pro Tools. + */ + static AudioChannelSet create7point1SDDS(); + + + //============================================================================== + /** Creates a set for ambisonic surround setups (ambisonicW, ambisonicX, ambisonicY, ambisonicZ). + + Is equivalent to: kBFormat (VST), n/a (AAX), kAudioChannelLayoutTag_Ambisonic_B_Format (CoreAudio) + */ + static AudioChannelSet ambisonic(); + + + /** Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround) + + Is equivalent to: k40Music (VST), AAX_eStemFormat_Quad (AAX), kAudioChannelLayoutTag_Quadraphonic (CoreAudio) + + This format is referred to as "Quadraphonic" in Logic Pro. + This format is referred to as "Quadro" in Cubase. + This format is referred to as "Quad" in Pro Tools. + */ + static AudioChannelSet quadraphonic(); + + + /** Creates a set for pentagonal surround setup (left, right, centre, leftSurroundRear, rightSurroundRear). + + Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Pentagonal (CoreAudio) + */ + static AudioChannelSet pentagonal(); + + + /** Creates a set for hexagonal surround setup (left, right, leftSurroundRear, rightSurroundRear, centre, surroundCentre). + + Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Hexagonal (CoreAudio) + */ + static AudioChannelSet hexagonal(); + + + /** Creates a set for octagonal surround setup (left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight). + + Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Octagonal (CoreAudio) + */ + static AudioChannelSet octagonal(); + + //============================================================================== + /** Creates a set of untyped discrete channels. */ + static AudioChannelSet discreteChannels (int numChannels); + + /** Create a canonical channel set for a given number of channels. + For example, numChannels = 1 will return mono, numChannels = 2 will return stereo, etc. */ + static AudioChannelSet canonicalChannelSet (int numChannels); + + /** Create a channel set for a given number of channels which is non-discrete. + If numChannels is larger than the number of channels of the surround format + with the maximum amount of channels (currently 7.1 Surround), then this + function returns an empty set.*/ + static AudioChannelSet namedChannelSet (int numChannels); + + /** Return an array of channel sets which have a given number of channels */ + static Array channelSetsWithNumberOfChannels (int numChannels); + + //============================================================================== + /** Represents different audio channel types. */ + enum ChannelType + { + unknown = 0, + + left = 1, // L + right = 2, // R + centre = 3, // C (sometimes M for mono) + + LFE = 4, + leftSurround = 5, // Ls + rightSurround = 6, // Rs + leftCentre = 7, // Lc (AAX/VST), Lc used as Lss in AU for most layouts + rightCentre = 8, // Rc (AAX/VST), Rc used as Rss in AU for most layouts + centreSurround = 9, // Cs/S + surround = centreSurround, // Cs/S + leftSurroundSide = 10, // Lss (AXX), Side Left "Sl" (VST), Left Centre "LC" (AU) + rightSurroundSide = 11, // Rss (AXX), Side right "Sr" (VST), Right Centre "Rc" (AU) + topMiddle = 12, + topFrontLeft = 13, + topFrontCentre = 14, + topFrontRight = 15, + topRearLeft = 16, + topRearCentre = 17, + topRearRight = 18, + LFE2 = 19, + leftSurroundRear = 20, // Lsr (AAX), Lcs (VST), Rls (AU) + rightSurroundRear = 21, // Rsr (AAX), Rcs (VST), Rrs (AU) + wideLeft = 22, + wideRight = 23, + + + ambisonicW = 24, + ambisonicX = 25, + ambisonicY = 26, + ambisonicZ = 27, + + + discreteChannel0 = 64 /**< Non-typed individual channels are indexed upwards from this value. */ + }; + + /** Returns the name of a given channel type. For example, this method may return "Surround Left". */ + static String getChannelTypeName (ChannelType); + + /** Returns the abbreviated name of a channel type. For example, this method may return "Ls". */ + static String getAbbreviatedChannelTypeName (ChannelType); + + //============================================================================== + enum + { + maxChannelsOfNamedLayout = 8 + }; + + /** Adds a channel to the set. */ + void addChannel (ChannelType newChannelType); + + /** Removes a channel from the set. */ + void removeChannel (ChannelType newChannelType); + + /** Returns the number of channels in the set. */ + int size() const noexcept; + + /** Returns true if there are no channels in the set. */ + bool isDisabled() const noexcept { return size() == 0; } + + /** Returns an array of all the types in this channel set. */ + Array getChannelTypes() const; + + /** Returns the type of one of the channels in the set, by index. */ + ChannelType getTypeOfChannel (int channelIndex) const noexcept; + + /** Returns the index for a particular channel-type. + Will return -1 if the this set does not contain a channel of this type. */ + int getChannelIndexForType (ChannelType type) const noexcept; + + /** Returns a string containing a whitespace-separated list of speaker types + corresponding to each channel. For example in a 5.1 arrangement, + the string may be "L R C Lfe Ls Rs". If the speaker arrangement is unknown, + the returned string will be empty.*/ + String getSpeakerArrangementAsString() const; + + /** Returns the description of the current layout. For example, this method may return + "Quadraphonic". Note that the returned string may not be unique. */ + String getDescription() const; + + /** Returns if this is a channel layout made-up of discrete channels. */ + bool isDiscreteLayout() const noexcept; + + /** Intersect two channel layouts. */ + void intersect (const AudioChannelSet& other) { channels &= other.channels; } + + //============================================================================== + bool operator== (const AudioChannelSet&) const noexcept; + bool operator!= (const AudioChannelSet&) const noexcept; + bool operator< (const AudioChannelSet&) const noexcept; +private: + BigInteger channels; + + explicit AudioChannelSet (uint32); +}; + + + +#endif // JUCE_AUDIOCHANNELSET_H_INCLUDED diff --git a/source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h b/source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h index 19e91bb88..05c47832b 100644 --- a/source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h +++ b/source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h @@ -397,9 +397,9 @@ public: convert between 32 and 64 bit float buffer types. */ template - void makeCopyOf (const AudioBuffer& other) + void makeCopyOf (const AudioBuffer& other, bool avoidReallocating = false) { - setSize (other.getNumChannels(), other.getNumSamples()); + setSize (other.getNumChannels(), other.getNumSamples(), false, false, avoidReallocating); if (other.hasBeenCleared()) { diff --git a/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp b/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp index 856055948..cfd778cdc 100644 --- a/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp +++ b/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp @@ -204,10 +204,11 @@ namespace FloatVectorHelpers typedef float Type; typedef float32x4_t ParallelType; typedef uint32x4_t IntegerType; + union signMaskUnion { ParallelType f; IntegerType i; }; enum { numParallel = 4 }; - static forcedinline IntegerType toint (ParallelType v) noexcept { union { ParallelType f; IntegerType i; } u; u.f = v; return u.i; } - static forcedinline ParallelType toflt (IntegerType v) noexcept { union { ParallelType f; IntegerType i; } u; u.i = v; return u.f; } + static forcedinline IntegerType toint (ParallelType v) noexcept { signMaskUnion u; u.f = v; return u.i; } + static forcedinline ParallelType toflt (IntegerType v) noexcept { signMaskUnion u; u.i = v; return u.f; } static forcedinline ParallelType load1 (Type v) noexcept { return vld1q_dup_f32 (&v); } static forcedinline ParallelType loadA (const Type* v) noexcept { return vld1q_f32 (v); } @@ -235,10 +236,11 @@ namespace FloatVectorHelpers typedef double Type; typedef double ParallelType; typedef uint64 IntegerType; + union signMaskUnion { ParallelType f; IntegerType i; }; enum { numParallel = 1 }; - static forcedinline IntegerType toint (ParallelType v) noexcept { union { ParallelType f; IntegerType i; } u; u.f = v; return u.i; } - static forcedinline ParallelType toflt (IntegerType v) noexcept { union { ParallelType f; IntegerType i; } u; u.i = v; return u.f; } + static forcedinline IntegerType toint (ParallelType v) noexcept { signMaskUnion u; u.f = v; return u.i; } + static forcedinline ParallelType toflt (IntegerType v) noexcept { signMaskUnion u; u.i = v; return u.f; } static forcedinline ParallelType load1 (Type v) noexcept { return v; } static forcedinline ParallelType loadA (const Type* v) noexcept { return *v; } @@ -346,6 +348,9 @@ namespace FloatVectorHelpers #define JUCE_LOAD_SRC1_SRC2_DEST(src1Load, src2Load, dstLoad) const Mode::ParallelType d = dstLoad (dest), s1 = src1Load (src1), s2 = src2Load (src2); #define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest), s = srcLoad (src); + union signMask32 { float f; uint32 i; }; + union signMask64 { double d; uint64 i; }; + #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON template struct ModeType { typedef BasicOps32 Mode; }; template<> struct ModeType<8> { typedef BasicOps64 Mode; }; @@ -481,6 +486,17 @@ namespace FloatVectorHelpers #endif } +//============================================================================== +namespace +{ + #if JUCE_USE_VDSP_FRAMEWORK + // This casts away constness to account for slightly different vDSP function signatures + // in OSX 10.8 SDK and below. Can be safely removed once those SDKs are obsolete. + template + ValueType* osx108sdkCompatibilityCast (const ValueType* arg) noexcept { return const_cast (arg); } + #endif +} + //============================================================================== void JUCE_CALLTYPE FloatVectorOperations::clear (float* dest, int num) noexcept { @@ -568,10 +584,10 @@ void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double amount, int const Mode::ParallelType amountToAdd = Mode::load1 (amount);) } -void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float* src, float amount, int num) noexcept +void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src, float amount, int num) noexcept { #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsadd (src, 1, &amount, dest, 1, (vDSP_Length) num); + vDSP_vsadd (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); #else JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, @@ -579,10 +595,10 @@ void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float* src, float am #endif } -void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double* src, double amount, int num) noexcept +void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src, double amount, int num) noexcept { #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsaddD (src, 1, &amount, dest, 1, (vDSP_Length) num); + vDSP_vsaddD (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); #else JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, @@ -795,7 +811,7 @@ void FloatVectorOperations::abs (float* dest, const float* src, int num) noexcep #if JUCE_USE_VDSP_FRAMEWORK vDSP_vabs ((float*) src, 1, dest, 1, (vDSP_Length) num); #else - union { float f; uint32 i; } signMask; + FloatVectorHelpers::signMask32 signMask; signMask.i = 0x7fffffffUL; JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabsf (src[i]), Mode::bit_and (s, mask), JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, @@ -810,7 +826,7 @@ void FloatVectorOperations::abs (double* dest, const double* src, int num) noexc #if JUCE_USE_VDSP_FRAMEWORK vDSP_vabsD ((double*) src, 1, dest, 1, (vDSP_Length) num); #else - union {double d; uint64 i;} signMask; + FloatVectorHelpers::signMask64 signMask; signMask.i = 0x7fffffffffffffffULL; JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabs (src[i]), Mode::bit_and (s, mask), @@ -991,7 +1007,7 @@ void JUCE_CALLTYPE FloatVectorOperations::enableFlushToZeroMode (bool shouldEnab void JUCE_CALLTYPE FloatVectorOperations::disableDenormalisedNumberSupport() noexcept { #if JUCE_USE_SSE_INTRINSICS - const int mxcsr = _mm_getcsr(); + const unsigned int mxcsr = _mm_getcsr(); _mm_setcsr (mxcsr | 0x8040); // add the DAZ and FZ bits #endif } diff --git a/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h b/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h index f1a8d13b4..b22e3a187 100644 --- a/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h +++ b/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h @@ -66,10 +66,10 @@ public: static void JUCE_CALLTYPE add (double* dest, double amountToAdd, int numValues) noexcept; /** Adds a fixed value to each source value and stores it in the destination array. */ - static void JUCE_CALLTYPE add (float* dest, float* src, float amount, int numValues) noexcept; + static void JUCE_CALLTYPE add (float* dest, const float* src, float amount, int numValues) noexcept; /** Adds a fixed value to each source value and stores it in the destination array. */ - static void JUCE_CALLTYPE add (double* dest, double* src, double amount, int numValues) noexcept; + static void JUCE_CALLTYPE add (double* dest, const double* src, double amount, int numValues) noexcept; /** Adds the source values to the destination values. */ static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; diff --git a/source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.cpp b/source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.cpp index ec91ac15f..8823b426f 100644 --- a/source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.cpp +++ b/source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.cpp @@ -22,7 +22,6 @@ ============================================================================== */ - struct CatmullRomAlgorithm { static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept diff --git a/source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h b/source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h index 466c2278d..1e2ca396e 100644 --- a/source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h +++ b/source/modules/juce_audio_basics/effects/juce_CatmullRomInterpolator.h @@ -22,7 +22,6 @@ ============================================================================== */ - /** Interpolator for resampling a stream of floats using Catmull-Rom interpolation. diff --git a/source/modules/juce_audio_basics/effects/juce_IIRFilter.cpp b/source/modules/juce_audio_basics/effects/juce_IIRFilter.cpp index 836c0f6f9..15c644c21 100644 --- a/source/modules/juce_audio_basics/effects/juce_IIRFilter.cpp +++ b/source/modules/juce_audio_basics/effects/juce_IIRFilter.cpp @@ -62,33 +62,131 @@ IIRCoefficients::IIRCoefficients (double c1, double c2, double c3, IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate, const double frequency) noexcept { - jassert (sampleRate > 0); + return makeLowPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); +} + +IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate, + const double frequency, + const double Q) noexcept +{ + jassert (sampleRate > 0.0); + jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); + jassert (Q > 0.0); const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); const double nSquared = n * n; - const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); + const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); return IIRCoefficients (c1, c1 * 2.0, c1, 1.0, c1 * 2.0 * (1.0 - nSquared), - c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); + c1 * (1.0 - 1.0 / Q * n + nSquared)); } IIRCoefficients IIRCoefficients::makeHighPass (const double sampleRate, const double frequency) noexcept { + return makeHighPass (sampleRate, frequency, 1.0 / std::sqrt(2.0)); +} + +IIRCoefficients IIRCoefficients::makeHighPass (const double sampleRate, + const double frequency, + const double Q) noexcept +{ + jassert (sampleRate > 0.0); + jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); + jassert (Q > 0.0); + const double n = std::tan (double_Pi * frequency / sampleRate); const double nSquared = n * n; - const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); + const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); return IIRCoefficients (c1, c1 * -2.0, c1, 1.0, c1 * 2.0 * (nSquared - 1.0), - c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); + c1 * (1.0 - 1.0 / Q * n + nSquared)); +} + +IIRCoefficients IIRCoefficients::makeBandPass (const double sampleRate, + const double frequency) noexcept +{ + return makeBandPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); +} + +IIRCoefficients IIRCoefficients::makeBandPass (const double sampleRate, + const double frequency, + const double Q) noexcept +{ + jassert (sampleRate > 0.0); + jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); + jassert (Q > 0.0); + + const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); + const double nSquared = n * n; + const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); + + return IIRCoefficients (c1 * n / Q, + 0.0, + -c1 * n / Q, + 1.0, + c1 * 2.0 * (1.0 - nSquared), + c1 * (1.0 - 1.0 / Q * n + nSquared)); +} + +IIRCoefficients IIRCoefficients::makeNotchFilter (const double sampleRate, + const double frequency) noexcept +{ + return makeNotchFilter (sampleRate, frequency, 1.0 / std::sqrt (2.0)); +} + +IIRCoefficients IIRCoefficients::makeNotchFilter (const double sampleRate, + const double frequency, + const double Q) noexcept +{ + jassert (sampleRate > 0.0); + jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); + jassert (Q > 0.0); + + const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); + const double nSquared = n * n; + const double c1 = 1.0 / (1.0 + n / Q + nSquared); + + return IIRCoefficients (c1 * (1.0 + nSquared), + 2.0 * c1 * (1.0 - nSquared), + c1 * (1.0 + nSquared), + 1.0, + c1 * 2.0 * (1.0 - nSquared), + c1 * (1.0 - n / Q + nSquared)); +} + +IIRCoefficients IIRCoefficients::makeAllPass (const double sampleRate, + const double frequency) noexcept +{ + return makeAllPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); +} + +IIRCoefficients IIRCoefficients::makeAllPass (const double sampleRate, + const double frequency, + const double Q) noexcept +{ + jassert (sampleRate > 0.0); + jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); + jassert (Q > 0.0); + + const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); + const double nSquared = n * n; + const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); + + return IIRCoefficients (c1 * (1.0 - n / Q + nSquared), + c1 * 2.0 * (1.0 - nSquared), + 1.0, + 1.0, + c1 * 2.0 * (1.0 - nSquared), + c1 * (1.0 - n / Q + nSquared)); } IIRCoefficients IIRCoefficients::makeLowShelf (const double sampleRate, @@ -96,8 +194,9 @@ IIRCoefficients IIRCoefficients::makeLowShelf (const double sampleRate, const double Q, const float gainFactor) noexcept { - jassert (sampleRate > 0); - jassert (Q > 0); + jassert (sampleRate > 0.0); + jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); + jassert (Q > 0.0); const double A = jmax (0.0f, std::sqrt (gainFactor)); const double aminus1 = A - 1.0; @@ -120,8 +219,9 @@ IIRCoefficients IIRCoefficients::makeHighShelf (const double sampleRate, const double Q, const float gainFactor) noexcept { - jassert (sampleRate > 0); - jassert (Q > 0); + jassert (sampleRate > 0.0); + jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); + jassert (Q > 0.0); const double A = jmax (0.0f, std::sqrt (gainFactor)); const double aminus1 = A - 1.0; @@ -140,15 +240,16 @@ IIRCoefficients IIRCoefficients::makeHighShelf (const double sampleRate, } IIRCoefficients IIRCoefficients::makePeakFilter (const double sampleRate, - const double centreFrequency, + const double frequency, const double Q, const float gainFactor) noexcept { - jassert (sampleRate > 0); - jassert (Q > 0); + jassert (sampleRate > 0.0); + jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); + jassert (Q > 0.0); const double A = jmax (0.0f, std::sqrt (gainFactor)); - const double omega = (double_Pi * 2.0 * jmax (centreFrequency, 2.0)) / sampleRate; + const double omega = (double_Pi * 2.0 * jmax (frequency, 2.0)) / sampleRate; const double alpha = 0.5 * std::sin (omega) / Q; const double c2 = -2.0 * std::cos (omega); const double alphaTimesA = alpha * A; @@ -164,12 +265,12 @@ IIRCoefficients IIRCoefficients::makePeakFilter (const double sampleRate, //============================================================================== IIRFilter::IIRFilter() noexcept - : v1 (0), v2 (0), active (false) + : v1 (0.0), v2 (0.0), active (false) { } IIRFilter::IIRFilter (const IIRFilter& other) noexcept - : v1 (0), v2 (0), active (other.active) + : v1 (0.0), v2 (0.0), active (other.active) { const SpinLock::ScopedLockType sl (other.processLock); coefficients = other.coefficients; @@ -198,7 +299,7 @@ void IIRFilter::setCoefficients (const IIRCoefficients& newCoefficients) noexcep void IIRFilter::reset() noexcept { const SpinLock::ScopedLockType sl (processLock); - v1 = v2 = 0; + v1 = v2 = 0.0; } float IIRFilter::processSingleSampleRaw (const float in) noexcept diff --git a/source/modules/juce_audio_basics/effects/juce_IIRFilter.h b/source/modules/juce_audio_basics/effects/juce_IIRFilter.h index 03deee723..a71506673 100644 --- a/source/modules/juce_audio_basics/effects/juce_IIRFilter.h +++ b/source/modules/juce_audio_basics/effects/juce_IIRFilter.h @@ -55,14 +55,53 @@ public: /** Destructor. */ ~IIRCoefficients() noexcept; + //============================================================================== /** Returns the coefficients for a low-pass filter. */ static IIRCoefficients makeLowPass (double sampleRate, double frequency) noexcept; + /** Returns the coefficients for a low-pass filter with variable Q. */ + static IIRCoefficients makeLowPass (double sampleRate, + double frequency, + double Q) noexcept; + + //============================================================================== /** Returns the coefficients for a high-pass filter. */ static IIRCoefficients makeHighPass (double sampleRate, double frequency) noexcept; + /** Returns the coefficients for a high-pass filter with variable Q. */ + static IIRCoefficients makeHighPass (double sampleRate, + double frequency, + double Q) noexcept; + + //============================================================================== + /** Returns the coefficients for a band-pass filter. */ + static IIRCoefficients makeBandPass (double sampleRate, double frequency) noexcept; + + /** Returns the coefficients for a band-pass filter with variable Q. */ + static IIRCoefficients makeBandPass (double sampleRate, + double frequency, + double Q) noexcept; + + //============================================================================== + /** Returns the coefficients for a notch filter. */ + static IIRCoefficients makeNotchFilter (double sampleRate, double frequency) noexcept; + + /** Returns the coefficients for a notch filter with variable Q. */ + static IIRCoefficients makeNotchFilter (double sampleRate, + double frequency, + double Q) noexcept; + + //============================================================================== + /** Returns the coefficients for an all-pass filter. */ + static IIRCoefficients makeAllPass (double sampleRate, double frequency) noexcept; + + /** Returns the coefficients for an all-pass filter with variable Q. */ + static IIRCoefficients makeAllPass (double sampleRate, + double frequency, + double Q) noexcept; + //============================================================================== /** Returns the coefficients for a low-pass shelf filter with variable Q and gain. diff --git a/source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h b/source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h index d3231556e..c33294a1e 100644 --- a/source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h +++ b/source/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.h @@ -22,7 +22,6 @@ ============================================================================== */ - /** Interpolator for resampling a stream of floats using 4-point lagrange interpolation. diff --git a/source/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h b/source/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h index 818ddb8e5..fc7490e9e 100644 --- a/source/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h +++ b/source/modules/juce_audio_basics/effects/juce_LinearSmoothedValue.h @@ -33,8 +33,8 @@ */ //============================================================================== -template -class JUCE_API LinearSmoothedValue +template +class LinearSmoothedValue { public: /** Constructor. */ @@ -59,7 +59,6 @@ public: countdown = 0; } - //============================================================================== /** Set a new target value. */ void setValue (FloatType newValue) noexcept { @@ -75,7 +74,6 @@ public: } } - //============================================================================== /** Compute the next value. */ FloatType getNextValue() noexcept { @@ -87,6 +85,18 @@ public: return currentValue; } + /** Returns true if the current value is currently being interpolated. */ + bool isSmoothing() const noexcept + { + return countdown > 0; + } + + /** Returns the target value towards which the smoothed value is currently moving. */ + FloatType getTargetValue() const noexcept + { + return target; + } + private: //============================================================================== FloatType currentValue, target, step; diff --git a/source/modules/juce_audio_basics/juce_audio_basics.cpp b/source/modules/juce_audio_basics/juce_audio_basics.cpp index 6f37f2dfb..b5f1fb4df 100644 --- a/source/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/source/modules/juce_audio_basics/juce_audio_basics.cpp @@ -31,6 +31,8 @@ #error "Incorrect use of JUCE cpp file" #endif +#include "AppConfig.h" + #include "juce_audio_basics.h" #if JUCE_MINGW && ! defined (__SSE2__) @@ -67,6 +69,13 @@ #define JUCE_USE_ARM_NEON 1 #endif +#if TARGET_IPHONE_SIMULATOR + #ifdef JUCE_USE_ARM_NEON + #undef JUCE_USE_ARM_NEON + #endif + #define JUCE_USE_ARM_NEON 0 +#endif + #if JUCE_USE_ARM_NEON #include #endif @@ -76,6 +85,7 @@ namespace juce #include "buffers/juce_AudioDataConverters.cpp" #include "buffers/juce_FloatVectorOperations.cpp" +#include "buffers/juce_AudioChannelSet.cpp" #include "effects/juce_IIRFilter.cpp" #include "effects/juce_IIRFilterOld.cpp" #include "effects/juce_LagrangeInterpolator.cpp" diff --git a/source/modules/juce_audio_basics/juce_audio_basics.h b/source/modules/juce_audio_basics/juce_audio_basics.h index 09b4760fb..0d491c8dd 100644 --- a/source/modules/juce_audio_basics/juce_audio_basics.h +++ b/source/modules/juce_audio_basics/juce_audio_basics.h @@ -22,12 +22,37 @@ ============================================================================== */ +/******************************************************************************* + The block below describes the properties of this module, and is read by + the Projucer to automatically generate project code that uses it. + For details about the syntax and how to create or use a module, see the + JUCE Module Format.txt file. + + + BEGIN_JUCE_MODULE_DECLARATION + + ID: juce_audio_basics + vendor: juce + version: 4.3.0 + name: JUCE audio and MIDI data classes + description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. + website: http://www.juce.com/juce + license: GPL/Commercial + + dependencies: juce_core + OSXFrameworks: Accelerate + iOSFrameworks: Accelerate + + END_JUCE_MODULE_DECLARATION + +*******************************************************************************/ + + #ifndef JUCE_AUDIO_BASICS_H_INCLUDED #define JUCE_AUDIO_BASICS_H_INCLUDED -#include "../juce_core/juce_core.h" +#include "juce_core/juce_core.h" -//============================================================================== namespace juce { @@ -37,6 +62,7 @@ namespace juce #include "buffers/juce_AudioDataConverters.h" #include "buffers/juce_FloatVectorOperations.h" #include "buffers/juce_AudioSampleBuffer.h" +#include "buffers/juce_AudioChannelSet.h" #include "effects/juce_Decibels.h" #include "effects/juce_IIRFilter.h" #include "effects/juce_IIRFilterOld.h" diff --git a/source/modules/juce_audio_basics/midi/juce_MidiFile.cpp b/source/modules/juce_audio_basics/midi/juce_MidiFile.cpp index c9abe8636..fe5937b2c 100644 --- a/source/modules/juce_audio_basics/midi/juce_MidiFile.cpp +++ b/source/modules/juce_audio_basics/midi/juce_MidiFile.cpp @@ -248,7 +248,7 @@ bool MidiFile::readFrom (InputStream& sourceStream) clear(); MemoryBlock data; - const int maxSensibleMidiFileSize = 2 * 1024 * 1024; + const int maxSensibleMidiFileSize = 200 * 1024 * 1024; // (put a sanity-check on the file size, as midi files are generally small) if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) diff --git a/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp b/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp index a3867a289..556c2d84b 100644 --- a/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp +++ b/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp @@ -33,11 +33,23 @@ namespace MidiHelpers { return (uint8) jlimit (0, 127, v); } +} - inline uint8 floatVelocityToByte (const float v) noexcept - { - return validVelocity (roundToInt (v * 127.0f)); - } +//============================================================================== +uint8 MidiMessage::floatValueToMidiByte (const float v) noexcept +{ + return MidiHelpers::validVelocity (roundToInt (v * 127.0f)); +} + +uint16 MidiMessage::pitchbendToPitchwheelPos (const float pitchbend, + const float pitchbendRange) noexcept +{ + // can't translate a pitchbend value that is outside of the given range! + jassert (std::abs (pitchbend) <= pitchbendRange); + + return static_cast (pitchbend > 0.0f + ? jmap (pitchbend, 0.0f, pitchbendRange, 8192.0f, 16383.0f) + : jmap (pitchbend, -pitchbendRange, 0.0f, 0.0f, 8192.0f)); } //============================================================================== @@ -84,25 +96,24 @@ int MidiMessage::getMessageLengthFromFirstByte (const uint8 firstByte) noexcept MidiMessage::MidiMessage() noexcept : timeStamp (0), size (2) { - preallocatedData.asBytes[0] = 0xf0; - preallocatedData.asBytes[1] = 0xf7; + packedData.asBytes[0] = 0xf0; + packedData.asBytes[1] = 0xf7; } MidiMessage::MidiMessage (const void* const d, const int dataSize, const double t) - : timeStamp (t), - size (dataSize) + : timeStamp (t), size (dataSize) { jassert (dataSize > 0); - memcpy (allocateSpace (dataSize), d, (size_t) dataSize); + // this checks that the length matches the data.. + jassert (dataSize > 3 || *(uint8*)d >= 0xf0 || getMessageLengthFromFirstByte (*(uint8*)d) == size); - // check that the length matches the data.. - jassert (size > 3 || *(uint8*)d >= 0xf0 || getMessageLengthFromFirstByte (*(uint8*)d) == size); + memcpy (allocateSpace (dataSize), d, (size_t) dataSize); } MidiMessage::MidiMessage (const int byte1, const double t) noexcept : timeStamp (t), size (1) { - preallocatedData.asBytes[0] = (uint8) byte1; + packedData.asBytes[0] = (uint8) byte1; // check that the length matches the data.. jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 1); @@ -111,8 +122,8 @@ MidiMessage::MidiMessage (const int byte1, const double t) noexcept MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) noexcept : timeStamp (t), size (2) { - preallocatedData.asBytes[0] = (uint8) byte1; - preallocatedData.asBytes[1] = (uint8) byte2; + packedData.asBytes[0] = (uint8) byte1; + packedData.asBytes[1] = (uint8) byte2; // check that the length matches the data.. jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 2); @@ -121,9 +132,9 @@ MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) noex MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, const double t) noexcept : timeStamp (t), size (3) { - preallocatedData.asBytes[0] = (uint8) byte1; - preallocatedData.asBytes[1] = (uint8) byte2; - preallocatedData.asBytes[2] = (uint8) byte3; + packedData.asBytes[0] = (uint8) byte1; + packedData.asBytes[1] = (uint8) byte2; + packedData.asBytes[2] = (uint8) byte3; // check that the length matches the data.. jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 3); @@ -132,29 +143,19 @@ MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, con MidiMessage::MidiMessage (const MidiMessage& other) : timeStamp (other.timeStamp), size (other.size) { - if (other.allocatedData != nullptr) - { - allocatedData.malloc ((size_t) size); - memcpy (allocatedData, other.allocatedData, (size_t) size); - } + if (isHeapAllocated()) + memcpy (allocateSpace (size), other.getData(), (size_t) size); else - { - preallocatedData.asInt32 = other.preallocatedData.asInt32; - } + packedData.allocatedData = other.packedData.allocatedData; } MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) : timeStamp (newTimeStamp), size (other.size) { - if (other.allocatedData != nullptr) - { - allocatedData.malloc ((size_t) size); - memcpy (allocatedData, other.allocatedData, (size_t) size); - } + if (isHeapAllocated()) + memcpy (allocateSpace (size), other.getData(), (size_t) size); else - { - preallocatedData.asInt32 = other.preallocatedData.asInt32; - } + packedData.allocatedData = other.packedData.allocatedData; } MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const uint8 lastStatusByte, @@ -229,16 +230,15 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const } else { - preallocatedData.asInt32 = 0; size = getMessageLengthFromFirstByte ((uint8) byte); - preallocatedData.asBytes[0] = (uint8) byte; + packedData.asBytes[0] = (uint8) byte; if (size > 1) { - preallocatedData.asBytes[1] = src[0]; + packedData.asBytes[1] = src[0]; if (size > 2) - preallocatedData.asBytes[2] = src[1]; + packedData.asBytes[2] = src[1]; } } @@ -246,7 +246,7 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const } else { - preallocatedData.asInt32 = 0; + packedData.allocatedData = nullptr; size = 0; } } @@ -255,19 +255,25 @@ MidiMessage& MidiMessage::operator= (const MidiMessage& other) { if (this != &other) { - timeStamp = other.timeStamp; - size = other.size; - - if (other.allocatedData != nullptr) + if (other.isHeapAllocated()) { - allocatedData.malloc ((size_t) size); - memcpy (allocatedData, other.allocatedData, (size_t) size); + if (isHeapAllocated()) + packedData.allocatedData = static_cast (std::realloc (packedData.allocatedData, (size_t) other.size)); + else + packedData.allocatedData = static_cast (std::malloc ((size_t) other.size)); + + memcpy (packedData.allocatedData, other.packedData.allocatedData, (size_t) other.size); } else { - allocatedData.free(); - preallocatedData.asInt32 = other.preallocatedData.asInt32; + if (isHeapAllocated()) + std::free (packedData.allocatedData); + + packedData.allocatedData = other.packedData.allocatedData; } + + timeStamp = other.timeStamp; + size = other.size; } return *this; @@ -277,36 +283,61 @@ MidiMessage& MidiMessage::operator= (const MidiMessage& other) MidiMessage::MidiMessage (MidiMessage&& other) noexcept : timeStamp (other.timeStamp), size (other.size) { - if (other.allocatedData != nullptr) - allocatedData.swapWith (other.allocatedData); - else - preallocatedData.asInt32 = other.preallocatedData.asInt32; + packedData.allocatedData = other.packedData.allocatedData; + other.size = 0; } MidiMessage& MidiMessage::operator= (MidiMessage&& other) noexcept { - jassert (this != &other); // shouldn't be possible - + packedData.allocatedData = other.packedData.allocatedData; timeStamp = other.timeStamp; size = other.size; - allocatedData.swapWith (other.allocatedData); - preallocatedData.asInt32 = other.preallocatedData.asInt32; - + other.size = 0; return *this; } #endif -MidiMessage::~MidiMessage() {} +MidiMessage::~MidiMessage() noexcept +{ + if (isHeapAllocated()) + std::free (packedData.allocatedData); +} uint8* MidiMessage::allocateSpace (int bytes) { - if (bytes > 4) + if (bytes > (int) sizeof (packedData)) + { + uint8* d = static_cast (std::malloc ((size_t) bytes)); + packedData.allocatedData = d; + return d; + } + + return packedData.asBytes; +} + +String MidiMessage::getDescription() const +{ + if (isNoteOn()) return "Note on " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); + if (isNoteOff()) return "Note off " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); + if (isProgramChange()) return "Program change " + String (getProgramChangeNumber()) + " Channel " + String (getChannel()); + if (isPitchWheel()) return "Pitch wheel " + String (getPitchWheelValue()) + " Channel " + String (getChannel()); + if (isAftertouch()) return "Aftertouch " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + ": " + String (getAfterTouchValue()) + " Channel " + String (getChannel()); + if (isChannelPressure()) return "Channel pressure " + String (getChannelPressureValue()) + " Channel " + String (getChannel()); + if (isAllNotesOff()) return "All notes off Channel " + String (getChannel()); + if (isAllSoundOff()) return "All sound off Channel " + String (getChannel()); + if (isMetaEvent()) return "Meta event"; + + if (isController()) { - allocatedData.malloc ((size_t) bytes); - return allocatedData; + String name (MidiMessage::getControllerName (getControllerNumber())); + + if (name.isEmpty()) + name = String (getControllerNumber()); + + return "Controller " + name + ": " + String (getControllerValue()) + " Channel " + String (getChannel()); } - return preallocatedData.asBytes; + return String::toHexString (getRawData(), getRawDataSize()); } int MidiMessage::getChannel() const noexcept @@ -391,7 +422,7 @@ float MidiMessage::getFloatVelocity() const noexcept void MidiMessage::setVelocity (const float newVelocity) noexcept { if (isNoteOnOrOff()) - getData()[2] = MidiHelpers::floatVelocityToByte (newVelocity); + getData()[2] = floatValueToMidiByte (newVelocity); } void MidiMessage::multiplyVelocity (const float scaleFactor) noexcept @@ -538,7 +569,7 @@ MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const float velocity) noexcept { - return noteOn (channel, noteNumber, MidiHelpers::floatVelocityToByte (velocity)); + return noteOn (channel, noteNumber, floatValueToMidiByte (velocity)); } MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, uint8 velocity) noexcept @@ -552,7 +583,7 @@ MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, uint8 MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, float velocity) noexcept { - return noteOff (channel, noteNumber, MidiHelpers::floatVelocityToByte (velocity)); + return noteOff (channel, noteNumber, floatValueToMidiByte (velocity)); } MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber) noexcept @@ -697,9 +728,10 @@ MidiMessage MidiMessage::textMetaEvent (int type, StringRef text) header[--n] = 0xff; const size_t headerLen = sizeof (header) - n; + const int totalSize = (int) (headerLen + textSize); - uint8* const dest = result.allocateSpace ((int) (headerLen + textSize)); - result.size = (int) (headerLen + textSize); + uint8* const dest = result.allocateSpace (totalSize); + result.size = totalSize; memcpy (dest, header + n, headerLen); memcpy (dest + headerLen, text.text.getAddress(), textSize); @@ -816,7 +848,7 @@ bool MidiMessage::isKeySignatureMetaEvent() const noexcept int MidiMessage::getKeySignatureNumberOfSharpsOrFlats() const noexcept { - return (int) getMetaEventData()[0]; + return (int) (int8) getMetaEventData()[0]; } bool MidiMessage::isKeySignatureMajorKey() const noexcept @@ -985,7 +1017,7 @@ String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includeOctav return String(); } -double MidiMessage::getMidiNoteInHertz (int noteNumber, const double frequencyOfA) noexcept +double MidiMessage::getMidiNoteInHertz (const int noteNumber, const double frequencyOfA) noexcept { return frequencyOfA * pow (2.0, (noteNumber - 69) / 12.0); } diff --git a/source/modules/juce_audio_basics/midi/juce_MidiMessage.h b/source/modules/juce_audio_basics/midi/juce_MidiMessage.h index 61e084b73..bfaaa0261 100644 --- a/source/modules/juce_audio_basics/midi/juce_MidiMessage.h +++ b/source/modules/juce_audio_basics/midi/juce_MidiMessage.h @@ -104,7 +104,7 @@ public: MidiMessage (const MidiMessage&, double newTimeStamp); /** Destructor. */ - ~MidiMessage(); + ~MidiMessage() noexcept; /** Copies this message from another one. */ MidiMessage& operator= (const MidiMessage& other); @@ -118,13 +118,19 @@ public: /** Returns a pointer to the raw midi data. @see getRawDataSize */ - const uint8* getRawData() const noexcept { return allocatedData != nullptr ? allocatedData.getData() : preallocatedData.asBytes; } + const uint8* getRawData() const noexcept { return getData(); } /** Returns the number of bytes of data in the message. @see getRawData */ int getRawDataSize() const noexcept { return size; } + //============================================================================== + /** Returns a human-readable description of the midi message as a string, + for example "Note On C#3 Velocity 120 Channel 1". + */ + String getDescription() const; + //============================================================================== /** Returns the timestamp associated with this message. @@ -845,7 +851,7 @@ public: The value passed in must be 0x80 or higher. */ - static int getMessageLengthFromFirstByte (const uint8 firstByte) noexcept; + static int getMessageLengthFromFirstByte (uint8 firstByte) noexcept; //============================================================================== /** Returns the name of a midi note number. @@ -872,7 +878,7 @@ public: The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch. @see getMidiNoteName */ - static double getMidiNoteInHertz (int noteNumber, const double frequencyOfA = 440.0) noexcept; + static double getMidiNoteInHertz (int noteNumber, double frequencyOfA = 440.0) noexcept; /** Returns true if the given midi note number is a black key. */ static bool isMidiNoteBlack (int noteNumber) noexcept; @@ -899,21 +905,29 @@ public: */ static const char* getControllerName (int controllerNumber); + /** Converts a floating-point value between 0 and 1 to a MIDI 7-bit value between 0 and 127. */ + static uint8 floatValueToMidiByte (float valueBetween0and1) noexcept; + + /** Converts a pitchbend value in semitones to a MIDI 14-bit pitchwheel position value. */ + static uint16 pitchbendToPitchwheelPos (float pitchbendInSemitones, + float pitchbendRangeInSemitones) noexcept; + private: //============================================================================== - double timeStamp; - HeapBlock allocatedData; - int size; - #ifndef DOXYGEN - union + union PackedData { - uint8 asBytes[4]; - uint32 asInt32; - } preallocatedData; + uint8* allocatedData; + uint8 asBytes[sizeof (uint8*)]; + }; + + PackedData packedData; + double timeStamp; + int size; #endif - inline uint8* getData() noexcept { return allocatedData != nullptr ? allocatedData.getData() : preallocatedData.asBytes; } + inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); } + inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : (uint8*) packedData.asBytes; } uint8* allocateSpace (int); }; diff --git a/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp b/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp index 7b2ab85ca..4d6e436fa 100644 --- a/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp +++ b/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp @@ -80,7 +80,7 @@ int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const noexcep return -1; } -int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const noexcept +int MidiMessageSequence::getIndexOf (const MidiEventHolder* const event) const noexcept { return list.indexOf (event); } diff --git a/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h b/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h index 2f55e2dab..dd6b01517 100644 --- a/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h +++ b/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h @@ -48,6 +48,18 @@ public: /** Replaces this sequence with another one. */ MidiMessageSequence& operator= (const MidiMessageSequence&); + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + MidiMessageSequence (MidiMessageSequence&& other) noexcept + : list (static_cast&&> (other.list)) + {} + + MidiMessageSequence& operator= (MidiMessageSequence&& other) noexcept + { + list = static_cast&&> (other.list); + return *this; + } + #endif + /** Destructor. */ ~MidiMessageSequence(); @@ -109,7 +121,7 @@ public: int getIndexOfMatchingKeyUp (int index) const noexcept; /** Returns the index of an event. */ - int getIndexOf (MidiEventHolder* event) const noexcept; + int getIndexOf (const MidiEventHolder* event) const noexcept; /** Returns the index of the first event on or after the given timestamp. If the time is beyond the end of the sequence, this will return the diff --git a/source/modules/juce_audio_basics/midi/juce_MidiRPN.cpp b/source/modules/juce_audio_basics/midi/juce_MidiRPN.cpp index 1819624e3..fe3c51ea0 100644 --- a/source/modules/juce_audio_basics/midi/juce_MidiRPN.cpp +++ b/source/modules/juce_audio_basics/midi/juce_MidiRPN.cpp @@ -22,7 +22,6 @@ ============================================================================== */ - MidiRPNDetector::MidiRPNDetector() noexcept { } @@ -55,7 +54,7 @@ void MidiRPNDetector::reset() noexcept } //============================================================================== -MidiRPNDetector::ChannelState::ChannelState () noexcept +MidiRPNDetector::ChannelState::ChannelState() noexcept : parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false) { } diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp b/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp index 9c88fc7c1..df7ff8047 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp +++ b/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp @@ -39,6 +39,9 @@ MPEInstrument::MPEInstrument() noexcept pressureDimension.value = &MPENote::pressure; timbreDimension.value = &MPENote::timbre; + // the default value for pressure is 0, for all other dimension it is centre (= default MPEValue) + std::fill_n (pressureDimension.lastValueReceivedOnChannel, 16, MPEValue::minValue()); + legacyMode.isEnabled = false; legacyMode.pitchbendRange = 2; legacyMode.channelRange = Range (1, 17); @@ -271,22 +274,6 @@ void MPEInstrument::handleTimbreLSB (int midiChannel, int value) noexcept lastTimbreLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value); } -//============================================================================== -MPEValue MPEInstrument::getInitialPitchbendForNoteOn (int midiChannel, int /*midiNoteNumber*/, MPEValue /*midiNoteOnVelocity*/) const -{ - return pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1]; -} - -MPEValue MPEInstrument::getInitialPressureForNoteOn (int /*midiChannel*/, int /*midiNoteNumber*/, MPEValue midiNoteOnVelocity) const -{ - return midiNoteOnVelocity; -} - -MPEValue MPEInstrument::getInitialTimbreForNoteOn (int midiChannel, int /*midiNoteNumber*/, MPEValue /*midiNoteOnVelocity*/) const -{ - return timbreDimension.lastValueReceivedOnChannel[midiChannel - 1]; -} - //============================================================================== void MPEInstrument::noteOn (int midiChannel, int midiNoteNumber, @@ -298,9 +285,9 @@ void MPEInstrument::noteOn (int midiChannel, MPENote newNote (midiChannel, midiNoteNumber, midiNoteOnVelocity, - getInitialPitchbendForNoteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity), - getInitialPressureForNoteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity), - getInitialTimbreForNoteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity), + getInitialValueForNewNote (midiChannel, pitchbendDimension), + getInitialValueForNewNote (midiChannel, pressureDimension), + getInitialValueForNewNote (midiChannel, timbreDimension), isNoteChannelSustained[midiChannel - 1] ? MPENote::keyDownAndSustained : MPENote::keyDown); const ScopedLock sl (lock); @@ -324,7 +311,7 @@ void MPEInstrument::noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity) { - if (notes.empty() || ! isNoteChannel (midiChannel)) + if (notes.isEmpty() || ! isNoteChannel (midiChannel)) return; const ScopedLock sl (lock); @@ -334,10 +321,11 @@ void MPEInstrument::noteOff (int midiChannel, note->keyState = (note->keyState == MPENote::keyDownAndSustained) ? MPENote::sustained : MPENote::off; note->noteOffVelocity = midiNoteOffVelocity; - // last pitchbend and timbre values received for this note should not be re-used for + // last dimension values received for this note should not be re-used for // any new notes, so reset them: - pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue(); - timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue(); + pressureDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::minValue(); + pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); + timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); if (note->keyState == MPENote::off) { @@ -370,12 +358,20 @@ void MPEInstrument::timbre (int midiChannel, MPEValue value) updateDimension (midiChannel, timbreDimension, value); } +MPEValue MPEInstrument::getInitialValueForNewNote (int midiChannel, MPEDimension& dimension) const +{ + if (getLastNotePlayedPtr (midiChannel) != nullptr) + return &dimension == &pressureDimension ? MPEValue::minValue() : MPEValue::centreValue(); + + return dimension.lastValueReceivedOnChannel[midiChannel - 1]; +} + //============================================================================== void MPEInstrument::updateDimension (int midiChannel, MPEDimension& dimension, MPEValue value) { dimension.lastValueReceivedOnChannel[midiChannel - 1] = value; - if (notes.empty()) + if (notes.isEmpty()) return; if (MPEZone* zone = zoneLayout.getZoneByMasterChannel (midiChannel)) @@ -758,7 +754,7 @@ public: test.noteOn (3, 60, MPEValue::from7BitInt (100)); expectEquals (test.getNumPlayingNotes(), 1); expectEquals (test.noteAddedCallCounter, 1); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); // note-off test.noteOff (3, 60, MPEValue::from7BitInt (33)); @@ -774,13 +770,13 @@ public: // note off with non-matching note number shouldn't do anything test.noteOff (3, 61, MPEValue::from7BitInt (33)); expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteReleasedCallCounter, 0); // note off with non-matching midi channel shouldn't do anything test.noteOff (2, 60, MPEValue::from7BitInt (33)); expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteReleasedCallCounter, 0); } { @@ -791,9 +787,9 @@ public: test.noteOn (3, 1, MPEValue::from7BitInt (100)); test.noteOn (3, 2, MPEValue::from7BitInt (100)); expectEquals (test.getNumPlayingNotes(), 3); - expectNote (test.getNote (3, 0), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 1), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 2), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 0), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 1), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 2), 100, 0, 8192, 64, MPENote::keyDown); } { // pathological case: second note-on for same note should retrigger it. @@ -802,7 +798,7 @@ public: test.noteOn (3, 0, MPEValue::from7BitInt (100)); test.noteOn (3, 0, MPEValue::from7BitInt (60)); expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 0), 60, 60, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 0), 60, 0, 8192, 64, MPENote::keyDown); } } @@ -844,42 +840,42 @@ public: // sustain pedal on per-note channel shouldn't do anything. test.sustainPedal (3, true); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteKeyStateChangedCallCounter, 0); // sustain pedal on non-zone channel shouldn't do anything either. test.sustainPedal (1, true); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteKeyStateChangedCallCounter, 0); // sustain pedal on master channel should sustain notes on *that* zone. test.sustainPedal (2, true); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDownAndSustained); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteKeyStateChangedCallCounter, 1); // release test.sustainPedal (2, false); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteKeyStateChangedCallCounter, 2); // should also sustain new notes added after the press test.sustainPedal (2, true); expectEquals (test.noteKeyStateChangedCallCounter, 3); test.noteOn (4, 51, MPEValue::from7BitInt (100)); - expectNote (test.getNote (4, 51), 100, 100, 8192, 64, MPENote::keyDownAndSustained); + expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::keyDownAndSustained); expectEquals (test.noteKeyStateChangedCallCounter, 3); // ...but only if that sustain came on the master channel of that zone! test.sustainPedal (11, true); test.noteOn (11, 52, MPEValue::from7BitInt (100)); - expectNote (test.getNote (11, 52), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (11, 52), 100, 0, 8192, 64, MPENote::keyDown); test.noteOff (11, 52, MPEValue::from7BitInt (100)); expectEquals (test.noteReleasedCallCounter, 1); @@ -890,8 +886,8 @@ public: expectEquals (test.getNumPlayingNotes(), 2); expectEquals (test.noteReleasedCallCounter, 2); expectEquals (test.noteKeyStateChangedCallCounter, 5); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); - expectNote (test.getNote (4, 51), 100, 100, 8192, 64, MPENote::sustained); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); + expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::sustained); // notes should be turned off when pedal is released test.sustainPedal (2, false); @@ -908,26 +904,26 @@ public: // sostenuto pedal on per-note channel shouldn't do anything. test.sostenutoPedal (3, true); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteKeyStateChangedCallCounter, 0); // sostenuto pedal on non-zone channel shouldn't do anything either. test.sostenutoPedal (1, true); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteKeyStateChangedCallCounter, 0); // sostenuto pedal on master channel should sustain notes on *that* zone. test.sostenutoPedal (2, true); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDownAndSustained); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteKeyStateChangedCallCounter, 1); // release test.sostenutoPedal (2, false); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteKeyStateChangedCallCounter, 2); // should only sustain notes turned on *before* the press (difference to sustain pedal) @@ -935,9 +931,9 @@ public: expectEquals (test.noteKeyStateChangedCallCounter, 3); test.noteOn (4, 51, MPEValue::from7BitInt (100)); expectEquals (test.getNumPlayingNotes(), 3); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDownAndSustained); - expectNote (test.getNote (4, 51), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); + expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteKeyStateChangedCallCounter, 3); // note-off should not turn off sustained notes inside the same zone, @@ -946,7 +942,7 @@ public: test.noteOff (4, 51, MPEValue::from7BitInt (100)); test.noteOff (10, 60, MPEValue::from7BitInt (100)); // not affected! expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); expectEquals (test.noteReleasedCallCounter, 2); expectEquals (test.noteKeyStateChangedCallCounter, 4); @@ -1047,22 +1043,22 @@ public: // applying pressure on a per-note channel should modulate one note test.pressure (3, MPEValue::from7BitInt (33)); expectNote (test.getNote (3, 60), 100, 33, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.notePressureChangedCallCounter, 1); // applying pressure on a master channel should modulate all notes in this zone test.pressure (2, MPEValue::from7BitInt (44)); expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.notePressureChangedCallCounter, 3); // applying pressure on an unrelated channel should be ignored test.pressure (1, MPEValue::from7BitInt (55)); expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.notePressureChangedCallCounter, 3); } { @@ -1073,7 +1069,7 @@ public: test.noteOn (3, 60, MPEValue::from7BitInt (100)); test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.pressure (3, MPEValue::from7BitInt (66)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); expectNote (test.getNote (3, 61), 100, 66, 8192, 64, MPENote::keyDown); expectEquals (test.notePressureChangedCallCounter, 1); } @@ -1091,6 +1087,49 @@ public: expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); expectEquals (test.notePressureChangedCallCounter, 1); } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // if no pressure is sent before note-on, default = 0 should be used + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // if pressure is sent before note-on, use that + test.pressure (3, MPEValue::from7BitInt (77)); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // if pressure is sent before note-on, but it belonged to another note + // on the same channel that has since been turned off, use default = 0 + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pressure (3, MPEValue::from7BitInt (77)); + test.noteOff (3, 61, MPEValue::from7BitInt (100)); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // edge case: two notes on the same channel simultaneously. the second one should use + // pressure = 0 initially but then react to additional pressure messages + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pressure (3, MPEValue::from7BitInt (77)); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + test.pressure (3, MPEValue::from7BitInt (78)); + expectNote (test.getNote (3, 60), 100, 78, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 77, 8192, 64, MPENote::keyDown); + } } beginTest ("pitchbend"); @@ -1105,9 +1144,9 @@ public: // applying pitchbend on a per-note channel should modulate one note test.pitchbend (3, MPEValue::from14BitInt (1111)); - expectNote (test.getNote (3, 60), 100, 100, 1111, 64, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.notePitchbendChangedCallCounter, 1); // applying pitchbend on a master channel should be ignored for the @@ -1115,16 +1154,16 @@ public: // Note: noteChanged will be called anyway for notes in that zone // because the total pitchbend for those notes has changed test.pitchbend (2, MPEValue::from14BitInt (2222)); - expectNote (test.getNote (3, 60), 100, 100, 1111, 64, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.notePitchbendChangedCallCounter, 3); // applying pitchbend on an unrelated channel should do nothing. test.pitchbend (1, MPEValue::from14BitInt (3333)); - expectNote (test.getNote (3, 60), 100, 100, 1111, 64, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.notePitchbendChangedCallCounter, 3); } { @@ -1135,8 +1174,8 @@ public: test.noteOn (3, 60, MPEValue::from7BitInt (100)); test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.pitchbend (3, MPEValue::from14BitInt (4444)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 100, 4444, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 4444, 64, MPENote::keyDown); expectEquals (test.notePitchbendChangedCallCounter, 1); } { @@ -1150,7 +1189,7 @@ public: test.noteOff (3, 61, MPEValue::from7BitInt (100)); test.pitchbend (3, MPEValue::from14BitInt (5555)); expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 60), 100, 100, 5555, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 5555, 64, MPENote::keyDown); expectEquals (test.notePitchbendChangedCallCounter, 1); } { @@ -1169,14 +1208,14 @@ public: test.sustainPedal (2, true); test.noteOff (3, 60, MPEValue::from7BitInt (64)); expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); expectEquals (test.noteKeyStateChangedCallCounter, 2); test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.pitchbend (3, MPEValue::from14BitInt (6666)); expectEquals (test.getNumPlayingNotes(), 2); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); - expectNote (test.getNote (3, 61), 100, 100, 6666, 64, MPENote::keyDownAndSustained); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); + expectNote (test.getNote (3, 61), 100, 0, 6666, 64, MPENote::keyDownAndSustained); expectEquals (test.notePitchbendChangedCallCounter, 1); } { @@ -1193,11 +1232,11 @@ public: test.noteOn (3, 60, MPEValue::from7BitInt (100)); test.pitchbend (3, MPEValue::from14BitInt (5555)); - expectNote (test.getNote (3, 60), 100, 100, 5555, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 5555, 64, MPENote::keyDown); test.noteOff (3, 60, MPEValue::from7BitInt (100)); test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); } { // applying per-note pitchbend should set the note's totalPitchbendInSemitones @@ -1289,23 +1328,23 @@ public: // modulating timbre on a per-note channel should modulate one note test.timbre (3, MPEValue::from7BitInt (33)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 33, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 33, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteTimbreChangedCallCounter, 1); // modulating timbre on a master channel should modulate all notes in this zone test.timbre (2, MPEValue::from7BitInt (44)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 44, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 100, 8192, 44, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 44, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 44, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteTimbreChangedCallCounter, 3); // modulating timbre on an unrelated channel should be ignored test.timbre (1, MPEValue::from7BitInt (55)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 44, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 100, 8192, 44, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 44, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 44, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteTimbreChangedCallCounter, 3); } { @@ -1316,8 +1355,8 @@ public: test.noteOn (3, 60, MPEValue::from7BitInt (100)); test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.timbre (3, MPEValue::from7BitInt (66)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 100, 8192, 66, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 66, MPENote::keyDown); expectEquals (test.noteTimbreChangedCallCounter, 1); } { @@ -1331,7 +1370,7 @@ public: test.noteOff (3, 61, MPEValue::from7BitInt (100)); test.timbre (3, MPEValue::from7BitInt (77)); expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 60), 100, 100, 8192, 77, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 77, MPENote::keyDown); expectEquals (test.noteTimbreChangedCallCounter, 1); } { @@ -1341,11 +1380,11 @@ public: // Zsolt's edge case for timbre test.noteOn (3, 60, MPEValue::from7BitInt (100)); test.timbre (3, MPEValue::from7BitInt (42)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 42, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 42, MPENote::keyDown); test.noteOff (3, 60, MPEValue::from7BitInt (100)); test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); } } @@ -1361,8 +1400,8 @@ public: test.noteOn (3, 62, MPEValue::from7BitInt (100)); test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.pressure (3, MPEValue::from7BitInt (99)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); expectNote (test.getNote (3, 61), 100, 99, 8192, 64, MPENote::keyDown); expectEquals (test.notePressureChangedCallCounter, 1); } @@ -1377,8 +1416,8 @@ public: test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.pressure (3, MPEValue::from7BitInt (99)); expectNote (test.getNote (3, 60), 100, 99, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.notePressureChangedCallCounter, 1); } { @@ -1391,9 +1430,9 @@ public: test.noteOn (3, 62, MPEValue::from7BitInt (100)); test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.pressure (3, MPEValue::from7BitInt (99)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); expectNote (test.getNote (3, 62), 100, 99, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.notePressureChangedCallCounter, 1); } { @@ -1425,9 +1464,9 @@ public: test.noteOn (3, 62, MPEValue::from7BitInt (100)); test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.pitchbend (3, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 100, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 9999, 64, MPENote::keyDown); expectEquals (test.notePitchbendChangedCallCounter, 1); } { @@ -1440,9 +1479,9 @@ public: test.noteOn (3, 62, MPEValue::from7BitInt (100)); test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.pitchbend (3, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (3, 60), 100, 100, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.notePitchbendChangedCallCounter, 1); } { @@ -1455,9 +1494,9 @@ public: test.noteOn (3, 62, MPEValue::from7BitInt (100)); test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.pitchbend (3, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 100, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.notePitchbendChangedCallCounter, 1); } { @@ -1470,9 +1509,9 @@ public: test.noteOn (3, 62, MPEValue::from7BitInt (100)); test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.pitchbend (3, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (3, 60), 100, 100, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 100, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 100, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 9999, 64, MPENote::keyDown); expectEquals (test.notePitchbendChangedCallCounter, 3); } } @@ -1489,9 +1528,9 @@ public: test.noteOn (3, 62, MPEValue::from7BitInt (100)); test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.timbre (3, MPEValue::from7BitInt (99)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 100, 8192, 99, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 99, MPENote::keyDown); expectEquals (test.noteTimbreChangedCallCounter, 1); } { @@ -1504,9 +1543,9 @@ public: test.noteOn (3, 62, MPEValue::from7BitInt (100)); test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.timbre (3, MPEValue::from7BitInt (99)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 99, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 99, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteTimbreChangedCallCounter, 1); } { @@ -1519,9 +1558,9 @@ public: test.noteOn (3, 62, MPEValue::from7BitInt (100)); test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.timbre (3, MPEValue::from7BitInt (99)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 100, 8192, 99, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 99, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); expectEquals (test.noteTimbreChangedCallCounter, 1); } { @@ -1534,9 +1573,9 @@ public: test.noteOn (3, 62, MPEValue::from7BitInt (100)); test.noteOn (3, 61, MPEValue::from7BitInt (100)); test.timbre (3, MPEValue::from7BitInt (99)); - expectNote (test.getNote (3, 60), 100, 100, 8192, 99, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 100, 8192, 99, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 100, 8192, 99, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 99, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 99, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 99, MPENote::keyDown); expectEquals (test.noteTimbreChangedCallCounter, 3); } } @@ -1724,7 +1763,7 @@ public: expectEquals (test.getNumPlayingNotes(), 0); } - beginTest ("default getInitial...ForNoteOn"); + beginTest ("default initial values for pitchbend and timbre"); { MPEInstrument test; test.setZoneLayout (testLayout); @@ -1739,16 +1778,7 @@ public: test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expectNote (test.getMostRecentNote (3), 100, 100, 3333, 66, MPENote::keyDown); - } - - beginTest ("overriding getInitial...ForNoteOn"); - { - CustomInitialValuesTest<33, 4444, 55> test; - test.setZoneLayout (testLayout); - - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - expectNote (test.getMostRecentNote (3), 100, 33, 4444, 55, MPENote::keyDown); + expectNote (test.getMostRecentNote (3), 100, 0, 3333, 66, MPENote::keyDown); } beginTest ("Legacy mode"); @@ -1806,10 +1836,10 @@ public: test.pressure (2, MPEValue::from7BitInt (88)); test.timbre (15, MPEValue::from7BitInt (77)); - expectNote (test.getNote (1, 60), 100, 100, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); expectNote (test.getNote (2, 60), 100, 88, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (15, 60), 100, 100, 8192, 77, MPENote::keyDown); - expectNote (test.getNote (16, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (15, 60), 100, 0, 8192, 77, MPENote::keyDown); + expectNote (test.getNote (16, 60), 100, 0, 8192, 64, MPENote::keyDown); // note off should work in legacy mode @@ -1835,10 +1865,10 @@ public: test.noteOn (16, 60, MPEValue::from7BitInt (100)); expectEquals (test.getNumPlayingNotes(), 4); - expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (6, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (7, 60), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (6, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (7, 60), 100, 0, 8192, 64, MPENote::keyDown); } { // tracking mode in legacy mode @@ -1851,9 +1881,9 @@ public: test.noteOn (1, 62, MPEValue::from7BitInt (100)); test.noteOn (1, 61, MPEValue::from7BitInt (100)); test.pitchbend (1, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (1, 61), 100, 100, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (1, 62), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (1, 61), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); } { UnitTestInstrument test; @@ -1864,9 +1894,9 @@ public: test.noteOn (1, 62, MPEValue::from7BitInt (100)); test.noteOn (1, 61, MPEValue::from7BitInt (100)); test.pitchbend (1, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (1, 60), 100, 100, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (1, 61), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (1, 62), 100, 100, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (1, 61), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); } { UnitTestInstrument test; @@ -1877,9 +1907,9 @@ public: test.noteOn (1, 62, MPEValue::from7BitInt (100)); test.noteOn (1, 61, MPEValue::from7BitInt (100)); test.pitchbend (1, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (1, 61), 100, 100, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (1, 62), 100, 100, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (1, 61), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (1, 62), 100, 0, 9999, 64, MPENote::keyDown); } { UnitTestInstrument test; @@ -1890,9 +1920,9 @@ public: test.noteOn (1, 62, MPEValue::from7BitInt (100)); test.noteOn (1, 61, MPEValue::from7BitInt (100)); test.pitchbend (1, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (1, 60), 100, 100, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (1, 61), 100, 100, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (1, 62), 100, 100, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (1, 61), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (1, 62), 100, 0, 9999, 64, MPENote::keyDown); } } { @@ -1916,7 +1946,7 @@ public: test.noteOff (1, 60, MPEValue::from7BitInt (100)); expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::sustained); + expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::sustained); test.sustainPedal (1, false); expectEquals (test.getNumPlayingNotes(), 0); @@ -1939,7 +1969,7 @@ public: test.noteOff (2, 61, MPEValue::from7BitInt (100)); expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::sustained); + expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::sustained); test.sostenutoPedal (1, false); expectEquals (test.getNumPlayingNotes(), 0); @@ -2081,26 +2111,6 @@ private: } }; - //============================================================================== - template - class CustomInitialValuesTest : public MPEInstrument - { - MPEValue getInitialPitchbendForNoteOn (int, int, MPEValue) const override - { - return MPEValue::from14BitInt (initial14BitPitchbend); - } - - MPEValue getInitialPressureForNoteOn (int, int, MPEValue) const override - { - return MPEValue::from7BitInt (initial7BitPressure); - } - - MPEValue getInitialTimbreForNoteOn (int, int, MPEValue) const override - { - return MPEValue::from7BitInt (initial7BitTimbre); - } - }; - //============================================================================== void expectNote (MPENote noteToTest, int noteOnVelocity7Bit, diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.h b/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.h index ea895baab..6303f22c9 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.h +++ b/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.h @@ -320,36 +320,6 @@ public: /** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ void setLegacyModePitchbendRange (int pitchbendRange); -protected: - //============================================================================== - /** This method defines what initial pitchbend value should be used for newly - triggered notes. The default is to use the last pitchbend value - that has been received on the same MIDI channel (or no pitchbend - if no pitchbend messages have been received so far). - Override this method if you need different behaviour. - */ - virtual MPEValue getInitialPitchbendForNoteOn (int midiChannel, - int midiNoteNumber, - MPEValue midiNoteOnVelocity) const; - - /** This method defines what initial pressure value should be used for newly - triggered notes. The default is to re-use the note-on velocity value. - Override this method if you need different behaviour. - */ - virtual MPEValue getInitialPressureForNoteOn (int midiChannel, - int midiNoteNumber, - MPEValue midiNoteOnVelocity) const; - - /** This method defines what initial timbre value should be used for newly - triggered notes. The default is to use the last timbre value that has - that has been received on the same MIDI channel (or a neutral centred value - if no pitchbend messages have been received so far). - Override this method if you need different behaviour. - */ - virtual MPEValue getInitialTimbreForNoteOn (int midiChannel, - int midiNoteNumber, - MPEValue midiNoteOnVelocity) const; - private: //============================================================================== CriticalSection lock; @@ -384,6 +354,7 @@ private: void updateDimensionMaster (MPEZone&, MPEDimension&, MPEValue); void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); void callListenersDimensionChanged (MPENote&, MPEDimension&); + MPEValue getInitialValueForNewNote (int midiChannel, MPEDimension&) const; void processMidiNoteOnMessage (const MidiMessage&); void processMidiNoteOffMessage (const MidiMessage&); diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp b/source/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp index fcfe2f580..9759678df 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp +++ b/source/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp @@ -22,7 +22,6 @@ ============================================================================== */ - MidiBuffer MPEMessages::addZone (MPEZone zone) { MidiBuffer buffer (MidiRPNGenerator::generate (zone.getFirstNoteChannel(), diff --git a/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp b/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp index 44c679b83..30e2411a2 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp +++ b/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp @@ -25,7 +25,8 @@ MPESynthesiserBase::MPESynthesiserBase() : instrument (new MPEInstrument), sampleRate (0), - minimumSubBlockSize (32) + minimumSubBlockSize (32), + subBlockSubdivisionIsStrict (false) { instrument->addListener (this); } @@ -100,10 +101,11 @@ void MPESynthesiserBase::renderNextBlock (AudioBuffer& outputAudio, MidiBuffer::Iterator midiIterator (inputMidi); midiIterator.setNextSamplePosition (startSample); + bool firstEvent = true; int midiEventPos; MidiMessage m; - const ScopedLock sl (renderAudioLock); + const ScopedLock sl (noteStateLock); while (numSamples > 0) { @@ -122,12 +124,14 @@ void MPESynthesiserBase::renderNextBlock (AudioBuffer& outputAudio, break; } - if (samplesToNextMidiMessage < minimumSubBlockSize) + if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) { handleMidiEvent (m); continue; } + firstEvent = false; + renderNextSubBlock (outputAudio, startSample, samplesToNextMidiMessage); handleMidiEvent (m); startSample += samplesToNextMidiMessage; @@ -147,15 +151,16 @@ void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate) { if (sampleRate != newRate) { - const ScopedLock sl (renderAudioLock); + const ScopedLock sl (noteStateLock); instrument->releaseAllNotes(); sampleRate = newRate; } } //============================================================================== -void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples) noexcept +void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept { jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 minimumSubBlockSize = numSamples; + subBlockSubdivisionIsStrict = shouldBeStrict; } diff --git a/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h b/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h index 898b28143..d85a9a432 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h +++ b/source/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h @@ -127,8 +127,14 @@ public: The default setting is 32, which means that midi messages are accurate to about < 1ms accuracy, which is probably fine for most purposes, but you may want to increase or decrease this value for your synth. + + If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. + + If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed + to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate + (this can sometimes help to avoid quantisation or phasing issues). */ - void setMinimumRenderingSubdivisionSize (int numSamples) noexcept; + void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; //============================================================================== /** Puts the synthesiser into legacy mode. @@ -179,13 +185,13 @@ protected: //============================================================================== /** @internal */ ScopedPointer instrument; - /** @internal */ - CriticalSection renderAudioLock; private: //============================================================================== + CriticalSection noteStateLock; double sampleRate; int minimumSubBlockSize; + bool subBlockSubdivisionIsStrict; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase) }; diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEZone.h b/source/modules/juce_audio_basics/mpe/juce_MPEZone.h index d9d6c3ec8..c677d3c43 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEZone.h +++ b/source/modules/juce_audio_basics/mpe/juce_MPEZone.h @@ -69,27 +69,35 @@ struct JUCE_API MPEZone int perNotePitchbendRange = 48, int masterPitchbendRange = 2) noexcept; - /* Returns the MIDI master channel of this zone. */ + /* Returns the MIDI master channel number (in the range 1-16) of this zone. */ int getMasterChannel() const noexcept; /** Returns the number of note channels occupied by this zone. */ int getNumNoteChannels() const noexcept; - /* Returns the MIDI channel number of the lowest-numbered note channel of this zone. */ + /* Returns the MIDI channel number (in the range 1-16) of the + lowest-numbered note channel of this zone. + */ int getFirstNoteChannel() const noexcept; - /* Returns the MIDI channel number of the highest-numbered note channel of this zone. */ + /* Returns the MIDI channel number (in the range 1-16) of the + highest-numbered note channel of this zone. + */ int getLastNoteChannel() const noexcept; - /** Returns the MIDI channel numbers of the note channels of this zone as a Range. */ + /** Returns the MIDI channel numbers (in the range 1-16) of the + note channels of this zone as a Range. + */ Range getNoteChannelRange() const noexcept; /** Returns true if the MIDI channel (in the range 1-16) is used by this zone - either as a note channel or as the master channel; false otherwise. */ + either as a note channel or as the master channel; false otherwise. + */ bool isUsingChannel (int channel) const noexcept; /** Returns true if the MIDI channel (in the range 1-16) is used by this zone - as a note channel; false otherwise. */ + as a note channel; false otherwise. + */ bool isUsingChannelAsNoteChannel (int channel) const noexcept; /** Returns the per-note pitchbend range in semitones set for this zone. */ diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp b/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp index f105da975..7228a1e55 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp +++ b/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp @@ -34,6 +34,7 @@ MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other) MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other) { zones = other.zones; + listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); return *this; } diff --git a/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h b/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h index f4a1cf2a1..3a331d315 100644 --- a/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h +++ b/source/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h @@ -100,8 +100,9 @@ public: /** Returns the current number of MPE zones. */ int getNumZones() const noexcept; - /** Returns a pointer to the MPE zone at the given index, - or nullptr if there is no such zone. + /** Returns a pointer to the MPE zone at the given index, or nullptr if there + is no such zone. Zones are sorted by insertion order (most recently added + zone last). */ MPEZone* getZoneByIndex (int index) const noexcept; diff --git a/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp b/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp index c47cc5e55..3443e149d 100644 --- a/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp +++ b/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp @@ -26,7 +26,8 @@ BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s, TimeSliceThread& thread, const bool deleteSourceWhenDeleted, const int bufferSizeSamples, - const int numChannels) + const int numChannels, + bool prefillBufferOnPrepareToPlay) : source (s, deleteSourceWhenDeleted), backgroundThread (thread), numberOfSamplesToBuffer (jmax (1024, bufferSizeSamples)), @@ -36,7 +37,8 @@ BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s, nextPlayPos (0), sampleRate (0), wasSourceLooping (false), - isPrepared (false) + isPrepared (false), + prefillBuffer (prefillBufferOnPrepareToPlay) { jassert (source != nullptr); @@ -73,12 +75,13 @@ void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double ne backgroundThread.addTimeSliceClient (this); - while (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, - buffer.getNumSamples() / 2)) + do { backgroundThread.moveToFrontOfQueue (this); Thread::sleep (5); } + while (prefillBuffer + && (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, buffer.getNumSamples() / 2))); } } @@ -88,7 +91,12 @@ void BufferingAudioSource::releaseResources() backgroundThread.removeTimeSliceClient (this); buffer.setSize (numberOfChannels, 0); - source->releaseResources(); + + // MSVC2015 seems to need this if statement to not generate a warning during linking. + // As source is set in the constructor, there is no way that source could + // ever equal this, but it seems to make MSVC2015 happy. + if (source != this) + source->releaseResources(); } void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) @@ -148,6 +156,41 @@ void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info } } +bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout) +{ + if (!source || source->getTotalLength() <= 0) + return false; + + if (nextPlayPos + info.numSamples < 0) + return true; + + if (! isLooping() && nextPlayPos > getTotalLength()) + return true; + + const uint32 endTime = Time::getMillisecondCounter() + timeout; + uint32 now = Time::getMillisecondCounter(); + + while (now < endTime) + { + { + const ScopedLock sl (bufferStartPosLock); + + const int validStart = static_cast (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos); + const int validEnd = static_cast (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos + info.numSamples) - nextPlayPos); + + if (validStart <= 0 && validStart < validEnd && validEnd >= info.numSamples) + return true; + } + + if (! bufferReadyEvent.wait (static_cast (endTime - now))) + return false; + + now = Time::getMillisecondCounter(); + } + + return false; +} + int64 BufferingAudioSource::getNextReadPosition() const { jassert (source->getTotalLength() > 0); @@ -241,6 +284,8 @@ bool BufferingAudioSource::readNextBufferChunk() bufferValidEnd = newBVE; } + bufferReadyEvent.signal(); + return true; } diff --git a/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h b/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h index 88116207c..717e35d5d 100644 --- a/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h +++ b/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h @@ -43,21 +43,24 @@ public: //============================================================================== /** Creates a BufferingAudioSource. - @param source the input source to read from - @param backgroundThread a background thread that will be used for the - background read-ahead. This object must not be deleted - until after any BufferingAudioSources that are using it - have been deleted! - @param deleteSourceWhenDeleted if true, then the input source object will - be deleted when this object is deleted - @param numberOfSamplesToBuffer the size of buffer to use for reading ahead - @param numberOfChannels the number of channels that will be played + @param source the input source to read from + @param backgroundThread a background thread that will be used for the + background read-ahead. This object must not be deleted + until after any BufferingAudioSources that are using it + have been deleted! + @param deleteSourceWhenDeleted if true, then the input source object will + be deleted when this object is deleted + @param numberOfSamplesToBuffer the size of buffer to use for reading ahead + @param numberOfChannels the number of channels that will be played + @param prefillBufferOnPrepareToPlay if true, then calling prepareToPlay on this object will + block until the buffer has been filled */ BufferingAudioSource (PositionableAudioSource* source, TimeSliceThread& backgroundThread, bool deleteSourceWhenDeleted, int numberOfSamplesToBuffer, - int numberOfChannels = 2); + int numberOfChannels = 2, + bool prefillBufferOnPrepareToPlay = true); /** Destructor. @@ -89,6 +92,12 @@ public: /** Implements the PositionableAudioSource method. */ bool isLooping() const override { return source->isLooping(); } + /** A useful function to block until the next the buffer info can be filled. + + This is useful for offline rendering. + */ + bool waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout); + private: //============================================================================== OptionalScopedPointer source; @@ -96,9 +105,10 @@ private: int numberOfSamplesToBuffer, numberOfChannels; AudioSampleBuffer buffer; CriticalSection bufferStartPosLock; + WaitableEvent bufferReadyEvent; int64 volatile bufferValidStart, bufferValidEnd, nextPlayPos; double volatile sampleRate; - bool wasSourceLooping, isPrepared; + bool wasSourceLooping, isPrepared, prefillBuffer; bool readNextBufferChunk(); void readBufferSection (int64 start, int length, int bufferOffset); diff --git a/source/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h b/source/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h index 09491cb1f..0f38358e7 100644 --- a/source/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h +++ b/source/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h @@ -30,7 +30,7 @@ /** A type of AudioSource that takes an input source and changes its sample rate. - @see AudioSource + @see AudioSource, LagrangeInterpolator, CatmullRomInterpolator */ class JUCE_API ResamplingAudioSource : public AudioSource { diff --git a/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp b/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp index 7f27b33d7..38c51e1a6 100644 --- a/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp +++ b/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp @@ -88,6 +88,7 @@ Synthesiser::Synthesiser() : sampleRate (0), lastNoteOnCounter (0), minimumSubBlockSize (32), + subBlockSubdivisionIsStrict (false), shouldStealNotes (true) { for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) @@ -147,10 +148,11 @@ void Synthesiser::setNoteStealingEnabled (const bool shouldSteal) shouldStealNotes = shouldSteal; } -void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples) noexcept +void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept { jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 minimumSubBlockSize = numSamples; + subBlockSubdivisionIsStrict = shouldBeStrict; } //============================================================================== @@ -177,10 +179,12 @@ void Synthesiser::processNextBlock (AudioBuffer& outputAudio, { // must set the sample rate before using this! jassert (sampleRate != 0); + const int targetChannels = outputAudio.getNumChannels(); MidiBuffer::Iterator midiIterator (midiData); midiIterator.setNextSamplePosition (startSample); + bool firstEvent = true; int midiEventPos; MidiMessage m; @@ -190,7 +194,9 @@ void Synthesiser::processNextBlock (AudioBuffer& outputAudio, { if (! midiIterator.getNextEvent (m, midiEventPos)) { - renderVoices (outputAudio, startSample, numSamples); + if (targetChannels > 0) + renderVoices (outputAudio, startSample, numSamples); + return; } @@ -198,18 +204,24 @@ void Synthesiser::processNextBlock (AudioBuffer& outputAudio, if (samplesToNextMidiMessage >= numSamples) { - renderVoices (outputAudio, startSample, numSamples); + if (targetChannels > 0) + renderVoices (outputAudio, startSample, numSamples); + handleMidiEvent (m); break; } - if (samplesToNextMidiMessage < minimumSubBlockSize) + if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) { handleMidiEvent (m); continue; } - renderVoices (outputAudio, startSample, samplesToNextMidiMessage); + firstEvent = false; + + if (targetChannels > 0) + renderVoices (outputAudio, startSample, samplesToNextMidiMessage); + handleMidiEvent (m); startSample += samplesToNextMidiMessage; numSamples -= samplesToNextMidiMessage; diff --git a/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h b/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h index 0cf92dc7d..9cb2f053d 100644 --- a/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h +++ b/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h @@ -539,8 +539,14 @@ public: The default setting is 32, which means that midi messages are accurate to about < 1ms accuracy, which is probably fine for most purposes, but you may want to increase or decrease this value for your synth. + + If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. + + If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed + to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate + (this can sometimes help to avoid quantisation or phasing issues). */ - void setMinimumRenderingSubdivisionSize (int numSamples) noexcept; + void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; protected: //============================================================================== @@ -615,6 +621,7 @@ private: double sampleRate; uint32 lastNoteOnCounter; int minimumSubBlockSize; + bool subBlockSubdivisionIsStrict; bool shouldStealNotes; BigInteger sustainPedalsDown; diff --git a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp index 9c1e21a07..75280231f 100644 --- a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -86,72 +86,12 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler) }; -//============================================================================== -// This is an AudioTransportSource which will own it's assigned source -struct AudioSourceOwningTransportSource : public AudioTransportSource -{ - AudioSourceOwningTransportSource (PositionableAudioSource* s) : source (s) - { - AudioTransportSource::setSource (s); - } - - ~AudioSourceOwningTransportSource() - { - setSource (nullptr); - } - -private: - ScopedPointer source; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourceOwningTransportSource) -}; - -//============================================================================== -// An AudioSourcePlayer which will remove itself from the AudioDeviceManager's -// callback list once it finishes playing its source -struct AutoRemovingSourcePlayer : public AudioSourcePlayer, - private Timer -{ - AutoRemovingSourcePlayer (AudioDeviceManager& dm, AudioTransportSource* ts, bool ownSource) - : manager (dm), transportSource (ts, ownSource) - { - jassert (ts != nullptr); - manager.addAudioCallback (this); - AudioSourcePlayer::setSource (transportSource); - startTimerHz (10); - } - - ~AutoRemovingSourcePlayer() - { - setSource (nullptr); - manager.removeAudioCallback (this); - } - - void timerCallback() override - { - if (getCurrentSource() == nullptr || ! transportSource->isPlaying()) - delete this; - } - - void audioDeviceStopped() override - { - AudioSourcePlayer::audioDeviceStopped(); - setSource (nullptr); - } - -private: - AudioDeviceManager& manager; - OptionalScopedPointer transportSource; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoRemovingSourcePlayer) -}; - //============================================================================== AudioDeviceManager::AudioDeviceManager() : numInputChansNeeded (0), numOutputChansNeeded (2), listNeedsScanning (true), - inputLevel (0), + testSoundPosition (0), cpuUsageMs (0), timeToCpuScale (0) { @@ -162,10 +102,6 @@ AudioDeviceManager::~AudioDeviceManager() { currentAudioDevice = nullptr; defaultMidiOutput = nullptr; - - for (int i = 0; i < callbacks.size(); ++i) - if (AutoRemovingSourcePlayer* p = dynamic_cast (callbacks.getUnchecked(i))) - delete p; } //============================================================================== @@ -346,8 +282,8 @@ String AudioDeviceManager::initialiseFromXML (const XmlElement& xml, currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); } - setup.bufferSize = xml.getIntAttribute ("audioDeviceBufferSize"); - setup.sampleRate = xml.getDoubleAttribute ("audioDeviceRate"); + setup.bufferSize = xml.getIntAttribute ("audioDeviceBufferSize", setup.bufferSize); + setup.sampleRate = xml.getDoubleAttribute ("audioDeviceRate", setup.sampleRate); setup.inputChannels .parseString (xml.getStringAttribute ("audioDeviceInChans", "11"), 2); setup.outputChannels.parseString (xml.getStringAttribute ("audioDeviceOutChans", "11"), 2); @@ -650,6 +586,8 @@ void AudioDeviceManager::stopDevice() { if (currentAudioDevice != nullptr) currentAudioDevice->stop(); + + testSound = nullptr; } void AudioDeviceManager::closeAudioDevice() @@ -761,31 +699,8 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat { const ScopedLock sl (audioCallbackLock); - if (inputLevelMeasurementEnabledCount.get() > 0 && numInputChannels > 0) - { - for (int j = 0; j < numSamples; ++j) - { - float s = 0; - - for (int i = 0; i < numInputChannels; ++i) - s += std::abs (inputChannelData[i][j]); - - s /= numInputChannels; - - const double decayFactor = 0.99992; - - if (s > inputLevel) - inputLevel = s; - else if (inputLevel > 0.001f) - inputLevel *= decayFactor; - else - inputLevel = 0; - } - } - else - { - inputLevel = 0; - } + inputLevelMeter.updateLevel (inputChannelData, numInputChannels, numSamples); + outputLevelMeter.updateLevel (const_cast (outputChannelData), numOutputChannels, numSamples); if (callbacks.size() > 0) { @@ -821,6 +736,20 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat for (int i = 0; i < numOutputChannels; ++i) zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); } + + if (testSound != nullptr) + { + const int numSamps = jmin (numSamples, testSound->getNumSamples() - testSoundPosition); + const float* const src = testSound->getReadPointer (0, testSoundPosition); + + for (int i = 0; i < numOutputChannels; ++i) + for (int j = 0; j < numSamps; ++j) + outputChannelData [i][j] += src[j]; + + testSoundPosition += numSamps; + if (testSoundPosition >= testSound->getNumSamples()) + testSound = nullptr; + } } void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device) @@ -989,170 +918,87 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) } //============================================================================== -// An AudioSource which simply outputs a buffer -class AudioSampleBufferSource : public PositionableAudioSource -{ -public: - AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool ownBuffer, bool playOnAllChannels) - : buffer (audioBuffer, ownBuffer), - position (0), looping (false), playAcrossAllChannels (playOnAllChannels) - {} +AudioDeviceManager::LevelMeter::LevelMeter() noexcept : level() {} - //============================================================================== - void setNextReadPosition (int64 newPosition) override - { - jassert (newPosition >= 0); - - if (looping) - newPosition = newPosition % static_cast (buffer->getNumSamples()); - - position = jmin (buffer->getNumSamples(), static_cast (newPosition)); - } - - int64 getNextReadPosition() const override { return static_cast (position); } - int64 getTotalLength() const override { return static_cast (buffer->getNumSamples()); } - - bool isLooping() const override { return looping; } - void setLooping (bool shouldLoop) override { looping = shouldLoop; } - - //============================================================================== - void prepareToPlay (int, double) override {} - void releaseResources() override {} - - void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override +void AudioDeviceManager::LevelMeter::updateLevel (const float* const* channelData, int numChannels, int numSamples) noexcept +{ + if (enabled.get() != 0 && numChannels > 0) { - bufferToFill.clearActiveBufferRegion(); - - const int bufferSize = buffer->getNumSamples(); - const int samplesNeeded = bufferToFill.numSamples; - const int samplesToCopy = jmin (bufferSize - position, samplesNeeded); - - if (samplesToCopy > 0) + for (int j = 0; j < numSamples; ++j) { - int maxInChannels = buffer->getNumChannels(); - int maxOutChannels = bufferToFill.buffer->getNumChannels(); - - if (! playAcrossAllChannels) - maxOutChannels = jmin (maxOutChannels, maxInChannels); - - for (int i = 0; i < maxOutChannels; ++i) - bufferToFill.buffer->copyFrom (i, bufferToFill.startSample, *buffer, - i % maxInChannels, position, samplesToCopy); - } - - position += samplesNeeded; + float s = 0; - if (looping) - position %= bufferSize; - } + for (int i = 0; i < numChannels; ++i) + s += std::abs (channelData[i][j]); -private: - //============================================================================== - OptionalScopedPointer buffer; - int position; - bool looping, playAcrossAllChannels; + s /= numChannels; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSampleBufferSource) -}; - -void AudioDeviceManager::playSound (const File& file) -{ - if (file.existsAsFile()) - { - AudioFormatManager formatManager; + const double decayFactor = 0.99992; - formatManager.registerBasicFormats(); - playSound (formatManager.createReaderFor (file), true); + if (s > level) + level = s; + else if (level > 0.001f) + level *= decayFactor; + else + level = 0; + } } -} - -void AudioDeviceManager::playSound (const void* resourceData, size_t resourceSize) -{ - if (resourceData != nullptr && resourceSize > 0) + else { - AudioFormatManager formatManager; - formatManager.registerBasicFormats(); - MemoryInputStream* mem = new MemoryInputStream (resourceData, resourceSize, false); - playSound (formatManager.createReaderFor (mem), true); + level = 0; } } -void AudioDeviceManager::playSound (AudioFormatReader* reader, bool deleteWhenFinished) +void AudioDeviceManager::LevelMeter::setEnabled (bool shouldBeEnabled) noexcept { - if (reader != nullptr) - playSound (new AudioFormatReaderSource (reader, deleteWhenFinished), true); + enabled.set (shouldBeEnabled ? 1 : 0); + level = 0; } -void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished, bool playOnAllOutputChannels) +double AudioDeviceManager::LevelMeter::getCurrentLevel() const noexcept { - if (buffer != nullptr) - playSound (new AudioSampleBufferSource (buffer, deleteWhenFinished, playOnAllOutputChannels), true); + jassert (enabled.get() != 0); // you need to call setEnabled (true) before using this! + return level; } -void AudioDeviceManager::playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished) +void AudioDeviceManager::playTestSound() { - if (audioSource != nullptr && currentAudioDevice != nullptr) - { - AudioTransportSource* transport = dynamic_cast (audioSource); + { // cunningly nested to swap, unlock and delete in that order. + ScopedPointer oldSound; - if (transport == nullptr) { - if (deleteWhenFinished) - { - transport = new AudioSourceOwningTransportSource (audioSource); - } - else - { - transport = new AudioTransportSource(); - transport->setSource (audioSource); - deleteWhenFinished = true; - } + const ScopedLock sl (audioCallbackLock); + oldSound = testSound; } - - transport->start(); - new AutoRemovingSourcePlayer (*this, transport, deleteWhenFinished); } - else - { - if (deleteWhenFinished) - delete audioSource; - } -} -void AudioDeviceManager::playTestSound() -{ - const double sampleRate = currentAudioDevice->getCurrentSampleRate(); - const int soundLength = (int) sampleRate; + testSoundPosition = 0; - const double frequency = 440.0; - const float amplitude = 0.5f; - - const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); + if (currentAudioDevice != nullptr) + { + const double sampleRate = currentAudioDevice->getCurrentSampleRate(); + const int soundLength = (int) sampleRate; - AudioSampleBuffer* newSound = new AudioSampleBuffer (1, soundLength); + const double frequency = 440.0; + const float amplitude = 0.5f; - for (int i = 0; i < soundLength; ++i) - newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample)); + const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); - newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); - newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); + AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength); - playSound (newSound, true, true); -} + for (int i = 0; i < soundLength; ++i) + newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample)); -//============================================================================== -void AudioDeviceManager::enableInputLevelMeasurement (const bool enableMeasurement) -{ - if (enableMeasurement) - ++inputLevelMeasurementEnabledCount; - else - --inputLevelMeasurementEnabledCount; + newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); + newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); - inputLevel = 0; + const ScopedLock sl (audioCallbackLock); + testSound = newSound; + } } -double AudioDeviceManager::getCurrentInputLevel() const -{ - jassert (inputLevelMeasurementEnabledCount.get() > 0); // you need to call enableInputLevelMeasurement() before using this! - return inputLevel; -} +double AudioDeviceManager::getCurrentInputLevel() const noexcept { return inputLevelMeter.getCurrentLevel(); } +double AudioDeviceManager::getCurrentOutputLevel() const noexcept { return outputLevelMeter.getCurrentLevel(); } + +void AudioDeviceManager::enableInputLevelMeasurement (bool enable) noexcept { inputLevelMeter.setEnabled (enable); } +void AudioDeviceManager::enableOutputLevelMeasurement (bool enable) noexcept { outputLevelMeter.setEnabled (enable); } diff --git a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h b/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h index 4d94b53b5..9d90f5beb 100644 --- a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h +++ b/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h @@ -404,78 +404,28 @@ public: */ void playTestSound(); - /** Plays a sound from a file. */ - void playSound (const File& file); - - /** Convenient method to play sound from a JUCE resource. */ - void playSound (const void* resourceData, size_t resourceSize); - - /** Plays the sound from an audio format reader. - - If deleteWhenFinished is true then the format reader will be - automatically deleted once the sound has finished playing. - */ - void playSound (AudioFormatReader* buffer, bool deleteWhenFinished = false); - - /** Plays the sound from a positionable audio source. - - This will output the sound coming from a positionable audio source. - This gives you slightly more control over the sound playback compared - to the other playSound methods. For example, if you would like to - stop the sound prematurely you can call this method with a - TransportAudioSource and then call audioSource->stop. Note that, - you must call audioSource->start to start the playback, if your - audioSource is a TransportAudioSource. - - The audio device manager will not hold any references to this audio - source once the audio source has stopped playing for any reason, - for example when the sound has finished playing or when you have - called audioSource->stop. Therefore, calling audioSource->start() on - a finished audioSource will not restart the sound again. If this is - desired simply call playSound with the same audioSource again. - - @param audioSource the audio source to play - @param deleteWhenFinished If this is true then the audio source will - be deleted once the device manager has finished playing. - */ - void playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished = false); - - /** Plays the sound from an audio sample buffer. - - This will output the sound contained in an audio sample buffer. If - deleteWhenFinished is true then the audio sample buffer will be - automatically deleted once the sound has finished playing. - - If playOnAllOutputChannels is true, then if there are more output channels - than buffer channels, then the ones that are available will be re-used on - multiple outputs so that something is sent to all output channels. If it - is false, then the buffer will just be played on the first output channels. - */ - void playSound (AudioSampleBuffer* buffer, - bool deleteWhenFinished = false, - bool playOnAllOutputChannels = false); - //============================================================================== - /** Turns on level-measuring. - - When enabled, the device manager will measure the peak input level - across all channels, and you can get this level by calling getCurrentInputLevel(). - - This is mainly intended for audio setup UI panels to use to create a mic - level display, so that the user can check that they've selected the right - device. + /** Turns on level-measuring for input channels. + @see getCurrentInputLevel() + */ + void enableInputLevelMeasurement (bool enableMeasurement) noexcept; - A simple filter is used to make the level decay smoothly, but this is - only intended for giving rough feedback, and not for any kind of accurate - measurement. + /** Turns on level-measuring for output channels. + @see getCurrentOutputLevel() */ - void enableInputLevelMeasurement (bool enableMeasurement); + void enableOutputLevelMeasurement (bool enableMeasurement) noexcept; /** Returns the current input level. To use this, you must first enable it by calling enableInputLevelMeasurement(). - See enableInputLevelMeasurement() for more info. + @see enableInputLevelMeasurement() */ - double getCurrentInputLevel() const; + double getCurrentInputLevel() const noexcept; + + /** Returns the current output level. + To use this, you must first enable it by calling enableOutputLevelMeasurement(). + @see enableOutputLevelMeasurement() + */ + double getCurrentOutputLevel() const noexcept; /** Returns the a lock that can be used to synchronise access to the audio callback. Obviously while this is locked, you're blocking the audio thread from running, so @@ -502,8 +452,6 @@ private: BigInteger inputChannels, outputChannels; ScopedPointer lastExplicitSettings; mutable bool listNeedsScanning; - Atomic inputLevelMeasurementEnabledCount; - double inputLevel; AudioSampleBuffer tempBuffer; struct MidiCallbackInfo @@ -520,8 +468,24 @@ private: ScopedPointer defaultMidiOutput; CriticalSection audioCallbackLock, midiCallbackLock; + ScopedPointer testSound; + int testSoundPosition; + double cpuUsageMs, timeToCpuScale; + struct LevelMeter + { + LevelMeter() noexcept; + void updateLevel (const float* const*, int numChannels, int numSamples) noexcept; + void setEnabled (bool) noexcept; + double getCurrentLevel() const noexcept; + + Atomic enabled; + double level; + }; + + LevelMeter inputLevelMeter, outputLevelMeter; + //============================================================================== class CallbackHandler; friend class CallbackHandler; diff --git a/source/modules/juce_audio_devices/juce_audio_devices.cpp b/source/modules/juce_audio_devices/juce_audio_devices.cpp index e5717830e..30366f806 100644 --- a/source/modules/juce_audio_devices/juce_audio_devices.cpp +++ b/source/modules/juce_audio_devices/juce_audio_devices.cpp @@ -31,6 +31,8 @@ #error "Incorrect use of JUCE cpp file" #endif +#include "AppConfig.h" + #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 @@ -45,7 +47,6 @@ #define Component CarbonDummyCompName #import #import - #import #import #undef Point #undef Component @@ -55,10 +56,14 @@ #import #import + #if TARGET_OS_SIMULATOR + #import + #endif + //============================================================================== #elif JUCE_WINDOWS #if JUCE_WASAPI - #include + #include #endif #if JUCE_ASIO @@ -84,15 +89,6 @@ #include #endif - #if JUCE_USE_CDBURNER - /* You'll need the Platform SDK for these headers - if you don't have it and don't - need to use CD-burning, then you might just want to set the JUCE_USE_CDBURNER flag - to 0, to avoid these includes. - */ - #include - #include - #endif - //============================================================================== #elif JUCE_LINUX #if JUCE_ALSA @@ -139,7 +135,6 @@ namespace juce #include "audio_io/juce_AudioIODeviceType.cpp" #include "midi_io/juce_MidiMessageCollector.cpp" #include "midi_io/juce_MidiOutput.cpp" -#include "audio_cd/juce_AudioCDReader.cpp" #include "sources/juce_AudioSourcePlayer.cpp" #include "sources/juce_AudioTransportSource.cpp" #include "native/juce_MidiDataConcatenator.h" @@ -149,14 +144,6 @@ namespace juce #include "native/juce_mac_CoreAudio.cpp" #include "native/juce_mac_CoreMidi.cpp" - #if JUCE_USE_CDREADER - #include "native/juce_mac_AudioCDReader.mm" - #endif - - #if JUCE_USE_CDBURNER - #include "native/juce_mac_AudioCDBurner.mm" - #endif - //============================================================================== #elif JUCE_IOS #include "native/juce_ios_Audio.cpp" @@ -179,14 +166,6 @@ namespace juce #include "native/juce_win32_ASIO.cpp" #endif - #if JUCE_USE_CDREADER - #include "native/juce_win32_AudioCDReader.cpp" - #endif - - #if JUCE_USE_CDBURNER - #include "native/juce_win32_AudioCDBurner.cpp" - #endif - //============================================================================== #elif JUCE_LINUX #if JUCE_ALSA @@ -199,10 +178,6 @@ namespace juce #include "native/juce_linux_JackAudio.cpp" #endif - #if JUCE_USE_CDREADER - #include "native/juce_linux_AudioCDReader.cpp" - #endif - //============================================================================== #elif JUCE_ANDROID #include "native/juce_android_Audio.cpp" diff --git a/source/modules/juce_audio_devices/juce_audio_devices.h b/source/modules/juce_audio_devices/juce_audio_devices.h index 9db9e4730..0fe52979b 100644 --- a/source/modules/juce_audio_devices/juce_audio_devices.h +++ b/source/modules/juce_audio_devices/juce_audio_devices.h @@ -22,12 +22,39 @@ ============================================================================== */ +/******************************************************************************* + The block below describes the properties of this module, and is read by + the Projucer to automatically generate project code that uses it. + For details about the syntax and how to create or use a module, see the + JUCE Module Format.txt file. + + + BEGIN_JUCE_MODULE_DECLARATION + + ID: juce_audio_devices + vendor: juce + version: 4.3.0 + name: JUCE audio and MIDI I/O device classes + description: Classes to play and record from audio and MIDI I/O devices + website: http://www.juce.com/juce + license: GPL/Commercial + + dependencies: juce_audio_basics, juce_events + OSXFrameworks: CoreAudio CoreMIDI AudioToolbox + iOSFrameworks: CoreAudio CoreMIDI AudioToolbox AVFoundation + linuxPackages: alsa + mingwLibs: winmm + + END_JUCE_MODULE_DECLARATION + +*******************************************************************************/ + + #ifndef JUCE_AUDIO_DEVICES_H_INCLUDED #define JUCE_AUDIO_DEVICES_H_INCLUDED -#include "../juce_events/juce_events.h" -#include "../juce_audio_basics/juce_audio_basics.h" -#include "../juce_audio_formats/juce_audio_formats.h" +#include +#include //============================================================================== /** Config: JUCE_ASIO @@ -90,21 +117,6 @@ #endif #endif -//============================================================================== -/** Config: JUCE_USE_CDREADER - Enables the AudioCDReader class (on supported platforms). -*/ -#ifndef JUCE_USE_CDREADER - #define JUCE_USE_CDREADER 0 -#endif - -/** Config: JUCE_USE_CDBURNER - Enables the AudioCDBurner class (on supported platforms). -*/ -#ifndef JUCE_USE_CDBURNER - #define JUCE_USE_CDBURNER 0 -#endif - //============================================================================== namespace juce { @@ -117,8 +129,6 @@ namespace juce #include "midi_io/juce_MidiOutput.h" #include "sources/juce_AudioSourcePlayer.h" #include "sources/juce_AudioTransportSource.h" -#include "audio_cd/juce_AudioCDBurner.h" -#include "audio_cd/juce_AudioCDReader.h" #include "audio_io/juce_AudioDeviceManager.h" } diff --git a/source/modules/juce_audio_devices/native/juce_MidiDataConcatenator.h b/source/modules/juce_audio_devices/native/juce_MidiDataConcatenator.h index 62eb5346b..28ba6672a 100644 --- a/source/modules/juce_audio_devices/native/juce_MidiDataConcatenator.h +++ b/source/modules/juce_audio_devices/native/juce_MidiDataConcatenator.h @@ -71,8 +71,7 @@ public: // the normal message, handle it now.. if (*d >= 0xf8 && *d <= 0xfe) { - const MidiMessage m (*d++, time); - callback.handleIncomingMidiMessage (input, m); + callback.handleIncomingMidiMessage (input, MidiMessage (*d++, time)); --numBytes; } else @@ -83,7 +82,15 @@ public: data[len++] = *d++; --numBytes; - if (len >= MidiMessage::getMessageLengthFromFirstByte (data[0])) + const uint8 firstByte = data[0]; + + if (firstByte < 0x80 || firstByte == 0xf7) + { + len = 0; + break; // ignore this malformed MIDI message.. + } + + if (len >= MidiMessage::getMessageLengthFromFirstByte (firstByte)) break; } } diff --git a/source/modules/juce_audio_devices/native/juce_android_Midi.cpp b/source/modules/juce_audio_devices/native/juce_android_Midi.cpp index 01a5ead54..dc972c925 100644 --- a/source/modules/juce_audio_devices/native/juce_android_Midi.cpp +++ b/source/modules/juce_audio_devices/native/juce_android_Midi.cpp @@ -153,7 +153,7 @@ JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceM class AndroidMidiDeviceManager { public: - AndroidMidiDeviceManager () + AndroidMidiDeviceManager() : deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager)) { } diff --git a/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp index 7102afeee..989552bba 100644 --- a/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp +++ b/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp @@ -332,7 +332,7 @@ private: void run() override { - setThreadToAudioPriority (); + setThreadToAudioPriority(); if (recorder != nullptr) recorder->start(); if (player != nullptr) player->start(); @@ -341,7 +341,7 @@ private: processBuffers(); } - void setThreadToAudioPriority () + void setThreadToAudioPriority() { // see android.os.Process.THREAD_PRIORITY_AUDIO const int THREAD_PRIORITY_AUDIO = -16; diff --git a/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp b/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp index a33544149..fc782c9f1 100644 --- a/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp +++ b/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp @@ -287,7 +287,14 @@ public: return r; } - int getDefaultBufferSize() override { return 256; } + int getDefaultBufferSize() override + { + #if TARGET_IPHONE_SIMULATOR + return 512; + #else + return 256; + #endif + } String open (const BigInteger& inputChannelsWanted, const BigInteger& outputChannelsWanted, @@ -431,6 +438,8 @@ public: void handleStatusChange (bool enabled, const char* reason) { + const ScopedLock myScopedLock (callbackLock); + JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason); isRunning = enabled; @@ -447,6 +456,8 @@ public: void handleRouteChange (const char* reason) { + const ScopedLock myScopedLock (callbackLock); + JUCE_IOS_AUDIO_LOG ("handleRouteChange: reason: " << reason); fixAudioRouteIfSetToReceiver(); @@ -518,9 +529,9 @@ private: if (audioInputIsAvailable && numInputChannels > 0) err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data); - const ScopedLock sl (callbackLock); + const ScopedTryLock stl (callbackLock); - if (callback != nullptr) + if (stl.isLocked() && callback != nullptr) { if ((int) numFrames > floatData.getNumSamples()) prepareFloatBuffers ((int) numFrames); @@ -679,7 +690,23 @@ private: AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format)); AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format)); + UInt32 framesPerSlice; + UInt32 dataSize = sizeof (framesPerSlice); + AudioUnitInitialize (audioUnit); + + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, 0, &actualBufferSize, sizeof (actualBufferSize)); + + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, 0, &framesPerSlice, &dataSize) == noErr + && dataSize == sizeof (framesPerSlice) && static_cast (framesPerSlice) != actualBufferSize) + { + actualBufferSize = static_cast (framesPerSlice); + prepareFloatBuffers (actualBufferSize); + } + return true; } @@ -755,8 +782,24 @@ void AudioSessionHolder::handleStatusChange (bool enabled, const char* reason) c void AudioSessionHolder::handleRouteChange (const char* reason) const { - for (auto device: activeDevices) - device->handleRouteChange (reason); + struct RouteChangeMessage : public CallbackMessage + { + RouteChangeMessage (Array devs, const char* r) + : devices (devs), changeReason (r) + { + } + + void messageCallback() override + { + for (auto device: devices) + device->handleRouteChange (changeReason); + } + + Array devices; + const char* changeReason; + }; + + (new RouteChangeMessage (activeDevices, reason))->post(); } #undef JUCE_NSERROR_CHECK diff --git a/source/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp b/source/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp index d9dd86462..0285726b0 100644 --- a/source/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp +++ b/source/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp @@ -183,7 +183,7 @@ public: // open output ports const StringArray outputChannels (getOutputChannelNames()); - for (int i = 0; i < outputChannels.size (); ++i) + for (int i = 0; i < outputChannels.size(); ++i) { String outputName; outputName << "out_" << ++totalNumberOfOutputChannels; @@ -290,6 +290,8 @@ public: } } + updateActivePorts(); + return lastError; } diff --git a/source/modules/juce_audio_devices/native/juce_linux_Midi.cpp b/source/modules/juce_audio_devices/native/juce_linux_Midi.cpp index f042c8559..1bea000bb 100644 --- a/source/modules/juce_audio_devices/native/juce_linux_Midi.cpp +++ b/source/modules/juce_audio_devices/native/juce_linux_Midi.cpp @@ -45,37 +45,17 @@ class AlsaClient : public ReferenceCountedObject public: typedef ReferenceCountedObjectPtr Ptr; - AlsaClient (bool forInput) - : input (forInput), handle (nullptr) + static Ptr getInstance (bool forInput) { - snd_seq_open (&handle, "default", forInput ? SND_SEQ_OPEN_INPUT - : SND_SEQ_OPEN_OUTPUT, 0); - } - - ~AlsaClient() - { - if (handle != nullptr) - { - snd_seq_close (handle); - handle = nullptr; - } - - jassert (activeCallbacks.size() == 0); + AlsaClient*& instance = (forInput ? inInstance : outInstance); + if (instance == nullptr) + instance = new AlsaClient (forInput); - if (inputThread) - { - inputThread->stopThread (3000); - inputThread = nullptr; - } + return instance; } bool isInput() const noexcept { return input; } - void setName (const String& name) - { - snd_seq_set_client_name (handle, name.toUTF8()); - } - void registerCallback (AlsaPortAndCallback* cb) { if (cb != nullptr) @@ -103,7 +83,8 @@ public: inputThread->signalThreadShouldExit(); } - void handleIncomingMidiMessage (const MidiMessage& message, int port); + void handleIncomingMidiMessage (snd_seq_event*, const MidiMessage&); + void handlePartialSysexMessage (snd_seq_event*, const uint8*, int, double); snd_seq_t* get() const noexcept { return handle; } @@ -114,12 +95,56 @@ private: Array activeCallbacks; CriticalSection callbackLock; + static AlsaClient* inInstance; + static AlsaClient* outInstance; + + //============================================================================== + friend class ReferenceCountedObjectPtr; + friend struct ContainerDeletePolicy; + + AlsaClient (bool forInput) + : input (forInput), handle (nullptr) + { + AlsaClient*& instance = (input ? inInstance : outInstance); + jassert (instance == nullptr); + + instance = this; + + snd_seq_open (&handle, "default", forInput ? SND_SEQ_OPEN_INPUT + : SND_SEQ_OPEN_OUTPUT, 0); + + snd_seq_set_client_name (handle, forInput ? JUCE_ALSA_MIDI_INPUT_NAME + : JUCE_ALSA_MIDI_OUTPUT_NAME); + } + + ~AlsaClient() + { + AlsaClient*& instance = (input ? inInstance : outInstance); + jassert (instance != nullptr); + + instance = nullptr; + + if (handle != nullptr) + { + snd_seq_close (handle); + handle = nullptr; + } + + jassert (activeCallbacks.size() == 0); + + if (inputThread) + { + inputThread->stopThread (3000); + inputThread = nullptr; + } + } + //============================================================================== class MidiInputThread : public Thread { public: MidiInputThread (AlsaClient& c) - : Thread ("Juce MIDI Input"), client (c) + : Thread ("Juce MIDI Input"), client (c), concatenator (2048) { jassert (client.input && client.get() != nullptr); } @@ -159,13 +184,9 @@ private: snd_midi_event_reset_decode (midiParser); - if (numBytes > 0) - { - const MidiMessage message ((const uint8*) buffer, (int) numBytes, - Time::getMillisecondCounter() * 0.001); - - client.handleIncomingMidiMessage (message, inputEvent->dest.port); - } + concatenator.pushMidiData (buffer, (int) numBytes, + Time::getMillisecondCounter() * 0.001, + inputEvent, client); snd_seq_free_event (inputEvent); } @@ -180,29 +201,14 @@ private: private: AlsaClient& client; + MidiDataConcatenator concatenator; }; ScopedPointer inputThread; }; - -static AlsaClient::Ptr globalAlsaSequencerIn() -{ - static AlsaClient::Ptr global (new AlsaClient (true)); - return global; -} - -static AlsaClient::Ptr globalAlsaSequencerOut() -{ - static AlsaClient::Ptr global (new AlsaClient (false)); - return global; -} - -static AlsaClient::Ptr globalAlsaSequencer (bool input) -{ - return input ? globalAlsaSequencerIn() - : globalAlsaSequencerOut(); -} +AlsaClient* AlsaClient::inInstance = nullptr; +AlsaClient* AlsaClient::outInstance = nullptr; //============================================================================== // represents an input or output port of the supplied AlsaClient @@ -282,6 +288,11 @@ public: callback->handleIncomingMidiMessage (midiInput, message); } + void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp) + { + callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp); + } + private: AlsaPort port; MidiInput* midiInput; @@ -291,14 +302,22 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlsaPortAndCallback) }; -void AlsaClient::handleIncomingMidiMessage (const MidiMessage& message, int port) +void AlsaClient::handleIncomingMidiMessage (snd_seq_event_t* event, const MidiMessage& message) { const ScopedLock sl (callbackLock); - if (AlsaPortAndCallback* const cb = activeCallbacks[port]) + if (AlsaPortAndCallback* const cb = activeCallbacks[event->dest.port]) cb->handleIncomingMidiMessage (message); } +void AlsaClient::handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp) +{ + const ScopedLock sl (callbackLock); + + if (AlsaPortAndCallback* const cb = activeCallbacks[event->dest.port]) + cb->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp); +} + //============================================================================== static AlsaPort iterateMidiClient (const AlsaClient::Ptr& seq, snd_seq_client_info_t* clientInfo, @@ -325,19 +344,23 @@ static AlsaPort iterateMidiClient (const AlsaClient::Ptr& seq, && (snd_seq_port_info_get_capability (portInfo) & (forInput ? SND_SEQ_PORT_CAP_READ : SND_SEQ_PORT_CAP_WRITE)) != 0) { - deviceNamesFound.add (snd_seq_client_info_get_name (clientInfo)); + const String clientName = snd_seq_client_info_get_name (clientInfo); + const String portName = snd_seq_port_info_get_name(portInfo); + + if (clientName == portName) + deviceNamesFound.add (clientName); + else + deviceNamesFound.add (clientName + ": " + portName); if (deviceNamesFound.size() == deviceIndexToOpen + 1) { const int sourcePort = snd_seq_port_info_get_port (portInfo); - const int sourceClient = snd_seq_client_info_get_client (clientInfo); if (sourcePort != -1) { - const String name (forInput ? JUCE_ALSA_MIDI_INPUT_NAME - : JUCE_ALSA_MIDI_OUTPUT_NAME); - seq->setName (name); - port.createPort (seq, name, forInput); + const int sourceClient = snd_seq_client_info_get_client (clientInfo); + + port.createPort (seq, portName, forInput); port.connectWith (sourceClient, sourcePort); } } @@ -355,7 +378,7 @@ static AlsaPort iterateMidiDevices (const bool forInput, const int deviceIndexToOpen) { AlsaPort port; - const AlsaClient::Ptr client (globalAlsaSequencer (forInput)); + const AlsaClient::Ptr client (AlsaClient::getInstance (forInput)); if (snd_seq_t* const seqHandle = client->get()) { @@ -387,19 +410,6 @@ static AlsaPort iterateMidiDevices (const bool forInput, return port; } -AlsaPort createMidiDevice (const bool forInput, const String& deviceNameToOpen) -{ - AlsaPort port; - AlsaClient::Ptr client (new AlsaClient (forInput)); - - if (client->get()) - { - client->setName (deviceNameToOpen + (forInput ? " Input" : " Output")); - port.createPort (client, forInput ? "in" : "out", forInput); - } - - return port; -} //============================================================================== class MidiOutputDevice @@ -450,7 +460,7 @@ public: numBytes -= numSent; data += numSent; - snd_seq_ev_set_source (&event, 0); + snd_seq_ev_set_source (&event, port.portId); snd_seq_ev_set_subs (&event); snd_seq_ev_set_direct (&event); @@ -507,8 +517,11 @@ MidiOutput* MidiOutput::openDevice (int deviceIndex) MidiOutput* MidiOutput::createNewDevice (const String& deviceName) { MidiOutput* newDevice = nullptr; + AlsaPort port; + + const AlsaClient::Ptr client (AlsaClient::getInstance (false)); - AlsaPort port (createMidiDevice (false, deviceName)); + port.createPort (client, deviceName, false); if (port.isValid()) { @@ -584,8 +597,11 @@ MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) { MidiInput* newDevice = nullptr; + AlsaPort port; + + const AlsaClient::Ptr client (AlsaClient::getInstance (true)); - AlsaPort port (createMidiDevice (true, deviceName)); + port.createPort (client, deviceName, true); if (port.isValid()) { diff --git a/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp b/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp index 7a0dbf30a..adbdd7050 100644 --- a/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp +++ b/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp @@ -149,6 +149,7 @@ bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool mute) { return SystemVol struct CoreAudioClasses { +class CoreAudioIODeviceType; class CoreAudioIODevice; //============================================================================== @@ -691,7 +692,7 @@ public: { const ScopedLock sl (callbackLock); } - // wait until it's definately stopped calling back.. + // wait until it's definitely stopped calling back.. for (int i = 40; --i >= 0;) { Thread::sleep (50); @@ -847,6 +848,11 @@ private: intern->deviceDetailsChanged(); break; + case kAudioObjectPropertyOwnedObjects: + intern->stop (false); + intern->owner.deviceType.triggerAsyncAudioDeviceListChange(); + break; + case kAudioDevicePropertyBufferSizeRange: case kAudioDevicePropertyVolumeScalar: case kAudioDevicePropertyMute: @@ -902,10 +908,12 @@ private: class CoreAudioIODevice : public AudioIODevice { public: - CoreAudioIODevice (const String& deviceName, + CoreAudioIODevice (CoreAudioIODeviceType& dt, + const String& deviceName, AudioDeviceID inputDeviceId, const int inputIndex_, AudioDeviceID outputDeviceId, const int outputIndex_) : AudioIODevice (deviceName, "CoreAudio"), + deviceType (dt), inputIndex (inputIndex_), outputIndex (outputIndex_), isOpen_ (false), @@ -1060,6 +1068,12 @@ public: return lastError; } + void audioDeviceListChanged() + { + deviceType.audioDeviceListChanged(); + } + + CoreAudioIODeviceType& deviceType; int inputIndex, outputIndex; private: @@ -1745,7 +1759,8 @@ private: //============================================================================== -class CoreAudioIODeviceType : public AudioIODeviceType +class CoreAudioIODeviceType : public AudioIODeviceType, + private AsyncUpdater { public: CoreAudioIODeviceType() @@ -1771,7 +1786,7 @@ public: } //============================================================================== - void scanForDevices() + void scanForDevices() override { hasScanned = true; @@ -1827,7 +1842,7 @@ public: outputDeviceNames.appendNumbersToDuplicates (false, true); } - StringArray getDeviceNames (bool wantInputNames) const + StringArray getDeviceNames (bool wantInputNames) const override { jassert (hasScanned); // need to call scanForDevices() before doing this @@ -1835,7 +1850,7 @@ public: : outputDeviceNames; } - int getDefaultDeviceIndex (bool forInput) const + int getDefaultDeviceIndex (bool forInput) const override { jassert (hasScanned); // need to call scanForDevices() before doing this @@ -1870,7 +1885,7 @@ public: return 0; } - int getIndexOfDevice (AudioIODevice* device, bool asInput) const + int getIndexOfDevice (AudioIODevice* device, bool asInput) const override { jassert (hasScanned); // need to call scanForDevices() before doing this @@ -1894,10 +1909,10 @@ public: return -1; } - bool hasSeparateInputsAndOutputs() const { return true; } + bool hasSeparateInputsAndOutputs() const override { return true; } AudioIODevice* createDevice (const String& outputDeviceName, - const String& inputDeviceName) + const String& inputDeviceName) override { jassert (hasScanned); // need to call scanForDevices() before doing this @@ -1913,15 +1928,15 @@ public: String combinedName (outputDeviceName.isEmpty() ? inputDeviceName : outputDeviceName); if (inputDeviceID == outputDeviceID) - return new CoreAudioIODevice (combinedName, inputDeviceID, inputIndex, outputDeviceID, outputIndex); + return new CoreAudioIODevice (*this, combinedName, inputDeviceID, inputIndex, outputDeviceID, outputIndex); ScopedPointer in, out; if (inputDeviceID != 0) - in = new CoreAudioIODevice (inputDeviceName, inputDeviceID, inputIndex, 0, -1); + in = new CoreAudioIODevice (*this, inputDeviceName, inputDeviceID, inputIndex, 0, -1); if (outputDeviceID != 0) - out = new CoreAudioIODevice (outputDeviceName, 0, -1, outputDeviceID, outputIndex); + out = new CoreAudioIODevice (*this, outputDeviceName, 0, -1, outputDeviceID, outputIndex); if (in == nullptr) return out.release(); if (out == nullptr) return in.release(); @@ -1932,6 +1947,17 @@ public: return combo.release(); } + void audioDeviceListChanged() + { + scanForDevices(); + callDeviceChangeListeners(); + } + + void triggerAsyncAudioDeviceListChange() + { + triggerAsyncUpdate(); + } + //============================================================================== private: StringArray inputDeviceNames, outputDeviceNames; @@ -1969,18 +1995,17 @@ private: return total; } - void audioDeviceListChanged() - { - scanForDevices(); - callDeviceChangeListeners(); - } - static OSStatus hardwareListenerProc (AudioDeviceID, UInt32, const AudioObjectPropertyAddress*, void* clientData) { static_cast (clientData)->audioDeviceListChanged(); return noErr; } + void handleAsyncUpdate() override + { + audioDeviceListChanged(); + } + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioIODeviceType) }; diff --git a/source/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp b/source/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp index a5d75b779..310de94f9 100644 --- a/source/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp +++ b/source/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp @@ -171,6 +171,10 @@ namespace CoreMidiHelpers static StringArray findDevices (const bool forInput) { + // It seems that OSX can be a bit picky about the thread that's first used to + // search for devices. It's safest to use the message thread for calling this. + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + const ItemCount num = forInput ? MIDIGetNumberOfSources() : MIDIGetNumberOfDestinations(); StringArray s; @@ -216,6 +220,14 @@ namespace CoreMidiHelpers // correctly when called from the message thread! jassert (MessageManager::getInstance()->isThisTheMessageThread()); + #if TARGET_OS_SIMULATOR + // Enable MIDI for iOS simulator + MIDINetworkSession* session = [MIDINetworkSession defaultSession]; + session.enabled = YES; + session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone; + #endif + + CoreMidiHelpers::ScopedCFString name; name.cfString = getGlobalMidiClientName().toCFString(); CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); diff --git a/source/modules/juce_audio_devices/native/juce_win32_ASIO.cpp b/source/modules/juce_audio_devices/native/juce_win32_ASIO.cpp index 670eb4115..9a54ec446 100644 --- a/source/modules/juce_audio_devices/native/juce_win32_ASIO.cpp +++ b/source/modules/juce_audio_devices/native/juce_win32_ASIO.cpp @@ -365,17 +365,18 @@ public: void updateSampleRates() { // find a list of sample rates.. - const int possibleSampleRates[] = { 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 }; Array newRates; if (asioObject != nullptr) { + const int possibleSampleRates[] = { 32000, 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 }; + for (int index = 0; index < numElementsInArray (possibleSampleRates); ++index) if (asioObject->canSampleRate ((double) possibleSampleRates[index]) == 0) newRates.add ((double) possibleSampleRates[index]); } - if (newRates.size() == 0) + if (newRates.isEmpty()) { double cr = getSampleRate(); JUCE_ASIO_LOG ("No sample rates supported - current rate: " + String ((int) cr)); @@ -1571,7 +1572,7 @@ private: DWORD dsize = sizeof (pathName); if (RegQueryValueEx (pathKey, 0, 0, &dtype, (LPBYTE) pathName, &dsize) == ERROR_SUCCESS) - // In older code, this used to check for the existance of the file, but there are situations + // In older code, this used to check for the existence of the file, but there are situations // where our process doesn't have access to it, but where the driver still loads ok.. ok = (pathName[0] != 0); diff --git a/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp b/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp index d2b6422be..708538f31 100644 --- a/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp +++ b/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp @@ -96,11 +96,18 @@ bool check (HRESULT hr) } #if JUCE_MINGW + #define JUCE_COMCLASS(name, guid) \ struct name; \ template<> struct UUIDGetter { static CLSID get() { return uuidFromString (guid); } }; \ struct name + #ifdef __uuidof + #undef __uuidof + #endif + + #define __uuidof(cls) UUIDGetter::get() + struct PROPERTYKEY { GUID fmtid; diff --git a/source/modules/juce_audio_devices/sources/juce_AudioTransportSource.h b/source/modules/juce_audio_devices/sources/juce_AudioTransportSource.h index d964d9200..459881a30 100644 --- a/source/modules/juce_audio_devices/sources/juce_AudioTransportSource.h +++ b/source/modules/juce_audio_devices/sources/juce_AudioTransportSource.h @@ -60,7 +60,7 @@ public: The source passed in will not be deleted by this object, so must be managed by the caller. - @param newSource the new input source to use. This may be zero + @param newSource the new input source to use. This may be a nullptr @param readAheadBufferSize a size of buffer to use for reading ahead. If this is zero, no reading ahead will be done; if it's greater than zero, a BufferingAudioSource will be used diff --git a/source/modules/juce_audio_formats/codecs/flac/alloc.h b/source/modules/juce_audio_formats/codecs/flac/alloc.h index 3aab81c27..04d26a8cc 100644 --- a/source/modules/juce_audio_formats/codecs/flac/alloc.h +++ b/source/modules/juce_audio_formats/codecs/flac/alloc.h @@ -41,11 +41,14 @@ * before #including this file, otherwise SIZE_MAX might not be defined */ -#include /* for SIZE_MAX */ -#if HAVE_STDINT_H -#include /* for SIZE_MAX in case limits.h didn't get it */ -#endif -#include /* for size_t, malloc(), etc */ +// JUCE: removed as JUCE already includes standard headers and including +// these in FlacNamespace will cause problems + +//#include /* for SIZE_MAX */ +//#if HAVE_STDINT_H +//#include /* for SIZE_MAX in case limits.h didn't get it */ +//#endif +//#include /* for size_t, malloc(), etc */ #include "compat.h" #ifndef SIZE_MAX diff --git a/source/modules/juce_audio_formats/codecs/flac/assert.h b/source/modules/juce_audio_formats/codecs/flac/assert.h index f02aeac10..ec5f4371b 100644 --- a/source/modules/juce_audio_formats/codecs/flac/assert.h +++ b/source/modules/juce_audio_formats/codecs/flac/assert.h @@ -35,7 +35,10 @@ /* we need this since some compilers (like MSVC) leave assert()s on release code (and we don't want to use their ASSERT) */ #ifdef DEBUG -#include +// JUCE: removed as JUCE already includes standard headers and including +// these in FlacNamespace will cause problems + +//#include #define FLAC__ASSERT(x) assert(x) #define FLAC__ASSERT_DECLARATION(x) x #else diff --git a/source/modules/juce_audio_formats/codecs/flac/callback.h b/source/modules/juce_audio_formats/codecs/flac/callback.h index 9928843bb..851add9ac 100644 --- a/source/modules/juce_audio_formats/codecs/flac/callback.h +++ b/source/modules/juce_audio_formats/codecs/flac/callback.h @@ -34,7 +34,10 @@ #define FLAC__CALLBACK_H #include "ordinals.h" -#include /* for size_t */ + +// JUCE: removed as JUCE already includes this and including stdlib +// in FlacNamespace will cause problems +//#include /** \file include/FLAC/callback.h * diff --git a/source/modules/juce_audio_formats/codecs/flac/compat.h b/source/modules/juce_audio_formats/codecs/flac/compat.h index a3dc7c7b9..543f49e83 100644 --- a/source/modules/juce_audio_formats/codecs/flac/compat.h +++ b/source/modules/juce_audio_formats/codecs/flac/compat.h @@ -39,15 +39,7 @@ #ifndef FLAC__SHARE__COMPAT_H #define FLAC__SHARE__COMPAT_H -#if defined _WIN32 && !defined __CYGWIN__ -/* where MSVC puts unlink() */ -# include -#else -# include -#endif - #if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ -#include /* for off_t */ #define FLAC__off_t __int64 /* use this instead of off_t to fix the 2 GB limit */ #if !defined __MINGW32__ #define fseeko _fseeki64 @@ -62,11 +54,6 @@ #define FLAC__off_t off_t #endif -#if HAVE_INTTYPES_H -#define __STDC_FORMAT_MACROS -#include -#endif - #if defined(_MSC_VER) #define strtoll _strtoi64 #define strtoull _strtoui64 @@ -95,33 +82,13 @@ #define FLAC__STRNCASECMP strncasecmp #endif -#if defined _MSC_VER || defined __MINGW32__ || defined __CYGWIN__ || defined __EMX__ -#include /* for _setmode(), chmod() */ -#include /* for _O_BINARY */ -#else -#include /* for chown(), unlink() */ -#endif - -#if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ -#if defined __BORLANDC__ -#include /* for utime() */ -#else -#include /* for utime() */ -#endif -#else -#include /* some flavors of BSD (like OS X) require this to get time_t */ -#include /* for utime() */ -#endif - #if defined _MSC_VER # if _MSC_VER >= 1600 /* Visual Studio 2010 has decent C99 support */ -# include # define PRIu64 "llu" # define PRId64 "lld" # define PRIx64 "llx" # else -# include # ifndef UINT32_MAX # define UINT32_MAX _UI32_MAX # endif diff --git a/source/modules/juce_audio_formats/codecs/flac/endswap.h b/source/modules/juce_audio_formats/codecs/flac/endswap.h index b2a7e8550..aa81dadf3 100644 --- a/source/modules/juce_audio_formats/codecs/flac/endswap.h +++ b/source/modules/juce_audio_formats/codecs/flac/endswap.h @@ -51,7 +51,9 @@ static inline unsigned short __builtin_bswap16(unsigned short a) #elif defined HAVE_BYTESWAP_H /* Linux */ -#include +// JUCE: removed as JUCE already includes standard headers and including +// these in FlacNamespace will cause problems +//#include #define ENDSWAP_16(x) (bswap_16 (x)) #define ENDSWAP_32(x) (bswap_32 (x)) diff --git a/source/modules/juce_audio_formats/codecs/flac/metadata.h b/source/modules/juce_audio_formats/codecs/flac/metadata.h index 02cfc3227..42e2aa575 100644 --- a/source/modules/juce_audio_formats/codecs/flac/metadata.h +++ b/source/modules/juce_audio_formats/codecs/flac/metadata.h @@ -33,7 +33,6 @@ #ifndef FLAC__METADATA_H #define FLAC__METADATA_H -#include /* for off_t */ #include "export.h" #include "callback.h" #include "format.h" diff --git a/source/modules/juce_audio_formats/codecs/flac/stream_decoder.h b/source/modules/juce_audio_formats/codecs/flac/stream_decoder.h index 50cd7544b..e0176dbbc 100644 --- a/source/modules/juce_audio_formats/codecs/flac/stream_decoder.h +++ b/source/modules/juce_audio_formats/codecs/flac/stream_decoder.h @@ -33,7 +33,6 @@ #ifndef FLAC__STREAM_DECODER_H #define FLAC__STREAM_DECODER_H -#include /* for FILE */ #include "export.h" #include "format.h" diff --git a/source/modules/juce_audio_formats/codecs/flac/stream_encoder.h b/source/modules/juce_audio_formats/codecs/flac/stream_encoder.h index 646efb799..b28e5a28f 100644 --- a/source/modules/juce_audio_formats/codecs/flac/stream_encoder.h +++ b/source/modules/juce_audio_formats/codecs/flac/stream_encoder.h @@ -33,7 +33,6 @@ #ifndef FLAC__STREAM_ENCODER_H #define FLAC__STREAM_ENCODER_H -#include /* for FILE */ #include "export.h" #include "format.h" #include "stream_decoder.h" diff --git a/source/modules/juce_audio_formats/codecs/flac/win_utf8_io.h b/source/modules/juce_audio_formats/codecs/flac/win_utf8_io.h index 8834e10e0..6333eef24 100644 --- a/source/modules/juce_audio_formats/codecs/flac/win_utf8_io.h +++ b/source/modules/juce_audio_formats/codecs/flac/win_utf8_io.h @@ -38,11 +38,6 @@ extern "C" { #endif -#include -#include -#include -#include - int get_utf8_argv(int *argc, char ***argv); int printf_utf8(const char *format, ...); diff --git a/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp b/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp index b6cb30639..186ed1ad0 100644 --- a/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp +++ b/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp @@ -668,13 +668,14 @@ public: //============================================================================== bool write (const int** data, int numSamples) override { + jassert (numSamples >= 0); jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel! if (writeFailed) return false; - const size_t bytes = (size_t) numSamples * numChannels * bitsPerSample / 8; - tempBlock.ensureSize ((size_t) bytes, false); + const size_t bytes = numChannels * (size_t) numSamples * bitsPerSample / 8; + tempBlock.ensureSize (bytes, false); switch (bitsPerSample) { @@ -695,13 +696,10 @@ public: writeFailed = true; return false; } - else - { - bytesWritten += bytes; - lengthInSamples += (uint64) numSamples; - return true; - } + bytesWritten += bytes; + lengthInSamples += (uint64) numSamples; + return true; } private: diff --git a/source/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp b/source/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp index 43972b60d..0cc4d1a1c 100644 --- a/source/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp +++ b/source/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp @@ -24,17 +24,79 @@ #if JUCE_USE_FLAC +} + +#if defined _WIN32 && !defined __CYGWIN__ + #include +#else + #include +#endif + +#if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ + #include /* for off_t */ +#endif + +#if HAVE_INTTYPES_H + #define __STDC_FORMAT_MACROS + #include +#endif + +#if defined _MSC_VER || defined __MINGW32__ || defined __CYGWIN__ || defined __EMX__ + #include /* for _setmode(), chmod() */ + #include /* for _O_BINARY */ +#else + #include /* for chown(), unlink() */ +#endif + +#if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ + #if defined __BORLANDC__ + #include /* for utime() */ + #else + #include /* for utime() */ + #endif +#else + #include /* some flavors of BSD (like OS X) require this to get time_t */ + #include /* for utime() */ +#endif + +#if defined _MSC_VER + #if _MSC_VER >= 1600 + #include + #else + #include + #endif +#endif + +#ifdef _WIN32 + #include + #include + #include + #include +#endif + +#ifdef DEBUG + #include +#endif + +#include +#include + +namespace juce +{ + namespace FlacNamespace { #if JUCE_INCLUDE_FLAC_CODE || ! defined (JUCE_INCLUDE_FLAC_CODE) #undef VERSION - #define VERSION "1.2.1" + #define VERSION "1.3.1" #define FLAC__NO_DLL 1 #if JUCE_MSVC #pragma warning (disable: 4267 4127 4244 4996 4100 4701 4702 4013 4133 4206 4312 4505 4365 4005 4334 181 111) + #else + #define HAVE_LROUND 1 #endif #if JUCE_MAC @@ -66,6 +128,7 @@ namespace FlacNamespace #define __STDC_LIMIT_MACROS 1 #define flac_max jmax #define flac_min jmin + #undef DEBUG // (some flac code dumps debug trace if the app defines this macro) #include "flac/all.h" #include "flac/libFLAC/bitmath.c" #include "flac/libFLAC/bitreader.c" @@ -324,7 +387,8 @@ class FlacWriter : public AudioFormatWriter { public: FlacWriter (OutputStream* const out, double rate, uint32 numChans, uint32 bits, int qualityOptionIndex) - : AudioFormatWriter (out, flacFormatName, rate, numChans, bits) + : AudioFormatWriter (out, flacFormatName, rate, numChans, bits), + streamStartPos (output != nullptr ? jmax (output->getPosition(), 0ll) : 0ll) { using namespace FlacNamespace; encoder = FLAC__stream_encoder_new(); @@ -432,7 +496,7 @@ public: packUint32 ((FLAC__uint32) info.total_samples, buffer + 14, 4); memcpy (buffer + 18, info.md5sum, 16); - const bool seekOk = output->setPosition (4); + const bool seekOk = output->setPosition (streamStartPos + 4); ignoreUnused (seekOk); // if this fails, you've given it an output stream that can't seek! It needs @@ -482,6 +546,7 @@ public: private: FlacNamespace::FLAC__StreamEncoder* encoder; + int64 streamStartPos; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacWriter) }; diff --git a/source/modules/juce_audio_formats/codecs/juce_QuickTimeAudioFormat.cpp b/source/modules/juce_audio_formats/codecs/juce_QuickTimeAudioFormat.cpp index ffcaf5147..1ad2e6f50 100644 --- a/source/modules/juce_audio_formats/codecs/juce_QuickTimeAudioFormat.cpp +++ b/source/modules/juce_audio_formats/codecs/juce_QuickTimeAudioFormat.cpp @@ -222,7 +222,7 @@ public: DisposeMovie (movie); #if JUCE_MAC - ExitMoviesOnThread (); + ExitMoviesOnThread(); #endif } } diff --git a/source/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp b/source/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp index 75603b152..52535970f 100644 --- a/source/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp +++ b/source/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp @@ -656,7 +656,10 @@ namespace WavFileHelpers if (infoLength > 0) { - infoLength = jlimit ((int64) 0, infoLength, (int64) input.readInt()); + infoLength = jmin (infoLength, (int64) input.readInt()); + + if (infoLength <= 0) + return; for (int i = 0; i < numElementsInArray (types); ++i) { @@ -664,7 +667,8 @@ namespace WavFileHelpers { MemoryBlock mb; input.readIntoMemoryBlock (mb, (ssize_t) infoLength); - values.set (types[i], mb.toString()); + values.set (types[i], String::createStringFromData ((const char*) mb.getData(), + (int) mb.getSize())); break; } } @@ -1258,7 +1262,7 @@ public: if (writeFailed) return false; - const size_t bytes = numChannels * (unsigned int) numSamples * bitsPerSample / 8; + const size_t bytes = numChannels * (size_t) numSamples * bitsPerSample / 8; tempBlock.ensureSize (bytes, false); switch (bitsPerSample) diff --git a/source/modules/juce_audio_formats/codecs/juce_WindowsMediaAudioFormat.cpp b/source/modules/juce_audio_formats/codecs/juce_WindowsMediaAudioFormat.cpp index 746ce640b..cc22f41a0 100644 --- a/source/modules/juce_audio_formats/codecs/juce_WindowsMediaAudioFormat.cpp +++ b/source/modules/juce_audio_formats/codecs/juce_WindowsMediaAudioFormat.cpp @@ -166,6 +166,9 @@ public: checkCoInitialiseCalled(); + clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, + startSampleInFile, numSamples, lengthInSamples); + const int stride = numChannels * sizeof (int16); while (numSamples > 0) @@ -297,7 +300,7 @@ private: sampleRate = inputFormat->nSamplesPerSec; numChannels = inputFormat->nChannels; - bitsPerSample = inputFormat->wBitsPerSample; + bitsPerSample = inputFormat->wBitsPerSample != 0 ? inputFormat->wBitsPerSample : 16; lengthInSamples = (lengthInNanoseconds * (int) sampleRate) / 10000000; } } diff --git a/source/modules/juce_audio_formats/juce_audio_formats.cpp b/source/modules/juce_audio_formats/juce_audio_formats.cpp index 499fae1bc..2494aa85f 100644 --- a/source/modules/juce_audio_formats/juce_audio_formats.cpp +++ b/source/modules/juce_audio_formats/juce_audio_formats.cpp @@ -31,6 +31,8 @@ #error "Incorrect use of JUCE cpp file" #endif +#include "AppConfig.h" + #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 diff --git a/source/modules/juce_audio_formats/juce_audio_formats.h b/source/modules/juce_audio_formats/juce_audio_formats.h index 24ca93422..b675a0114 100644 --- a/source/modules/juce_audio_formats/juce_audio_formats.h +++ b/source/modules/juce_audio_formats/juce_audio_formats.h @@ -22,10 +22,36 @@ ============================================================================== */ +/******************************************************************************* + The block below describes the properties of this module, and is read by + the Projucer to automatically generate project code that uses it. + For details about the syntax and how to create or use a module, see the + JUCE Module Format.txt file. + + + BEGIN_JUCE_MODULE_DECLARATION + + ID: juce_audio_formats + vendor: juce + version: 4.3.0 + name: JUCE audio file format codecs + description: Classes for reading and writing various audio file formats. + website: http://www.juce.com/juce + license: GPL/Commercial + + dependencies: juce_audio_basics + OSXFrameworks: CoreAudio CoreMIDI QuartzCore AudioToolbox + iOSFrameworks: AudioToolbox QuartzCore + + END_JUCE_MODULE_DECLARATION + +*******************************************************************************/ + + #ifndef JUCE_AUDIO_FORMATS_H_INCLUDED #define JUCE_AUDIO_FORMATS_H_INCLUDED -#include "../juce_audio_basics/juce_audio_basics.h" +#include //============================================================================== /** Config: JUCE_USE_FLAC diff --git a/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.cpp b/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.cpp index 9b534a8b7..029bf140e 100644 --- a/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.cpp +++ b/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.cpp @@ -22,5 +22,188 @@ ============================================================================== */ +namespace AudioPluginFormatHelpers +{ + struct CallbackInvoker + { + struct InvokeOnMessageThread : public CallbackMessage + { + InvokeOnMessageThread (AudioPluginInstance* inInstance, const String& inError, + AudioPluginFormat::InstantiationCompletionCallback* inCompletion, + CallbackInvoker* invoker) + : instance (inInstance), error (inError), compCallback (inCompletion), owner (invoker) + {} + + void messageCallback() override { compCallback->completionCallback (instance, error); } + + //============================================================================== + AudioPluginInstance* instance; + String error; + ScopedPointer compCallback; + ScopedPointer owner; + }; + + //============================================================================== + CallbackInvoker (AudioPluginFormat::InstantiationCompletionCallback* cc) : completion (cc) + {} + + void completionCallback (AudioPluginInstance* instance, const String& error) + { + (new InvokeOnMessageThread (instance, error, completion, this))->post(); + } + + static void staticCompletionCallback (void* userData, AudioPluginInstance* instance, const String& error) + { + reinterpret_cast (userData)->completionCallback (instance, error); + } + + //============================================================================== + AudioPluginFormat::InstantiationCompletionCallback* completion; + }; +} + AudioPluginFormat::AudioPluginFormat() noexcept {} AudioPluginFormat::~AudioPluginFormat() {} + +AudioPluginInstance* AudioPluginFormat::createInstanceFromDescription (const PluginDescription& desc, + double initialSampleRate, + int initialBufferSize) +{ + String errorMessage; + return createInstanceFromDescription (desc, initialSampleRate, initialBufferSize, errorMessage); +} + +//============================================================================== +struct EventSignaler : public AudioPluginFormat::InstantiationCompletionCallback +{ + EventSignaler (WaitableEvent& inEvent, AudioPluginInstance*& inInstance, String& inErrorMessage) + : event (inEvent), outInstance (inInstance), outErrorMessage (inErrorMessage) + {} + + void completionCallback (AudioPluginInstance* newInstance, const String& result) override + { + outInstance = newInstance; + outErrorMessage = result; + event.signal(); + } + + static void staticCompletionCallback (void* userData, AudioPluginInstance* pluginInstance, const String& error) + { + reinterpret_cast (userData)->completionCallback (pluginInstance, error); + } + + WaitableEvent& event; + AudioPluginInstance*& outInstance; + String& outErrorMessage; + + JUCE_DECLARE_NON_COPYABLE (EventSignaler) +}; + +AudioPluginInstance* AudioPluginFormat::createInstanceFromDescription (const PluginDescription& desc, + double initialSampleRate, + int initialBufferSize, + String& errorMessage) +{ + if (MessageManager::getInstance()->isThisTheMessageThread() + && requiresUnblockedMessageThreadDuringCreation(desc)) + { + errorMessage = NEEDS_TRANS ("This plug-in cannot be instantiated synchronously"); + return nullptr; + } + + WaitableEvent waitForCreation; + AudioPluginInstance* instance = nullptr; + + ScopedPointer eventSignaler (new EventSignaler (waitForCreation, instance, errorMessage)); + + if (! MessageManager::getInstance()->isThisTheMessageThread()) + createPluginInstanceAsync (desc, initialSampleRate, initialBufferSize, eventSignaler.release()); + else + createPluginInstance (desc, initialSampleRate, initialBufferSize, + eventSignaler, EventSignaler::staticCompletionCallback); + + + waitForCreation.wait(); + + return instance; +} + +void AudioPluginFormat::createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + AudioPluginFormat::InstantiationCompletionCallback* callback) +{ + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + createPluginInstanceOnMessageThread (description, initialSampleRate, initialBufferSize, callback); + return; + } + + //============================================================================== + struct InvokeOnMessageThread : public CallbackMessage + { + InvokeOnMessageThread (AudioPluginFormat* myself, + const PluginDescription& descriptionParam, + double initialSampleRateParam, + int initialBufferSizeParam, + AudioPluginFormat::InstantiationCompletionCallback* callbackParam) + : owner (myself), descr (descriptionParam), sampleRate (initialSampleRateParam), + bufferSize (initialBufferSizeParam), call (callbackParam) + {} + + void messageCallback() override + { + owner->createPluginInstanceOnMessageThread (descr, sampleRate, bufferSize, call); + } + + AudioPluginFormat* owner; + PluginDescription descr; + double sampleRate; + int bufferSize; + AudioPluginFormat::InstantiationCompletionCallback* call; + }; + + (new InvokeOnMessageThread (this, description, initialSampleRate, initialBufferSize, callback))->post(); +} + +#if JUCE_COMPILER_SUPPORTS_LAMBDAS +void AudioPluginFormat::createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + std::function f) +{ + struct CallbackInvoker : public AudioPluginFormat::InstantiationCompletionCallback + { + CallbackInvoker (std::function inCompletion) + : completion (inCompletion) + {} + + void completionCallback (AudioPluginInstance* instance, const String& error) override + { + completion (instance, error); + } + + std::function completion; + }; + + createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, new CallbackInvoker (f)); +} +#endif + +void AudioPluginFormat::createPluginInstanceOnMessageThread (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + AudioPluginFormat::InstantiationCompletionCallback* callback) +{ + jassert (callback != nullptr); + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + + //============================================================================== + + + //============================================================================== + AudioPluginFormatHelpers::CallbackInvoker* completion = new AudioPluginFormatHelpers::CallbackInvoker (callback); + + createPluginInstance (description, initialSampleRate, initialBufferSize, completion, + AudioPluginFormatHelpers::CallbackInvoker::staticCompletionCallback); +} diff --git a/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.h b/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.h index 8f299a881..e59b28c47 100644 --- a/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.h +++ b/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.h @@ -30,11 +30,20 @@ /** The base class for a type of plugin format, such as VST, AudioUnit, LADSPA, etc. - @see AudioFormatManager + @see AudioPluginFormatManager */ class JUCE_API AudioPluginFormat { public: + //============================================================================== + struct JUCE_API InstantiationCompletionCallback + { + virtual ~InstantiationCompletionCallback() {} + virtual void completionCallback (AudioPluginInstance* instance, const String& error) = 0; + + JUCE_LEAK_DETECTOR (InstantiationCompletionCallback) + }; + //============================================================================== /** Destructor. */ virtual ~AudioPluginFormat(); @@ -58,11 +67,36 @@ public: const String& fileOrIdentifier) = 0; /** Tries to recreate a type from a previously generated PluginDescription. - @see PluginDescription::createInstance + @see AudioPluginFormatManager::createInstance + */ + AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, + double initialSampleRate, + int initialBufferSize); + + /** Same as above but with the possibility of returning an error message. + + @see AudioPluginFormatManager::createInstance */ - virtual AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc, - double initialSampleRate, - int initialBufferSize) = 0; + AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, + double initialSampleRate, + int initialBufferSize, + String& errorMessage); + + /** Tries to recreate a type from a previously generated PluginDescription. + + @see AudioPluginFormatManager::createInstanceAsync + */ + void createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + InstantiationCompletionCallback* completionCallback); + + #if JUCE_COMPILER_SUPPORTS_LAMBDAS + void createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + std::function completionCallback); + #endif /** Should do a quick check to see if this file or directory might be a plugin of this format. @@ -82,7 +116,7 @@ public: It doesn't actually need to load it, just to check whether the file or component still exists. */ - virtual bool doesPluginStillExist (const PluginDescription& desc) = 0; + virtual bool doesPluginStillExist (const PluginDescription&) = 0; /** Returns true if this format needs to run a scan to find its list of plugins. */ virtual bool canScanForPlugins() const = 0; @@ -90,9 +124,17 @@ public: /** Searches a suggested set of directories for any plugins in this format. The path might be ignored, e.g. by AUs, which are found by the OS rather than manually. + + @param directoriesToSearch This specifies which directories shall be + searched for plug-ins. + @param recursive Should the search recursively traverse folders. + @param allowPluginsWhichRequireAsynchronousInstantiation + If this is false then plug-ins which require + asynchronous creation will be excluded. */ virtual StringArray searchPathsForPlugins (const FileSearchPath& directoriesToSearch, - bool recursive) = 0; + bool recursive, + bool allowPluginsWhichRequireAsynchronousInstantiation = false) = 0; /** Returns the typical places to look for this kind of plugin. @@ -103,8 +145,24 @@ public: protected: //============================================================================== + friend class AudioPluginFormatManager; + AudioPluginFormat() noexcept; + /** Implementors must override this function. This is guaranteed to be called on + the message thread. You may call the callback on any thread. + */ + virtual void createPluginInstance (const PluginDescription&, double initialSampleRate, + int initialBufferSize, void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) = 0; + + virtual bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept = 0; + +private: + /** @internal */ + void createPluginInstanceOnMessageThread (const PluginDescription&, double rate, int size, + AudioPluginFormat::InstantiationCompletionCallback*); + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormat) }; diff --git a/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.cpp b/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.cpp index f29e8adea..a6f8f1e3b 100644 --- a/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.cpp +++ b/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.cpp @@ -22,6 +22,39 @@ ============================================================================== */ +namespace PluginFormatManagerHelpers +{ + struct ErrorCallbackOnMessageThread : public CallbackMessage + { + ErrorCallbackOnMessageThread (const String& inError, + AudioPluginFormat::InstantiationCompletionCallback* inCallback) + : error (inError), callback (inCallback) + { + } + + void messageCallback() override { callback->completionCallback (nullptr, error); } + + String error; + ScopedPointer callback; + }; + + #if JUCE_COMPILER_SUPPORTS_LAMBDAS + struct ErrorLambdaOnMessageThread : public CallbackMessage + { + ErrorLambdaOnMessageThread (const String& inError, + std::function f) + : error (inError), lambda (f) + { + } + + void messageCallback() override { lambda (nullptr, error); } + + String error; + std::function lambda; + }; + #endif +} + AudioPluginFormatManager::AudioPluginFormatManager() {} AudioPluginFormatManager::~AudioPluginFormatManager() {} @@ -32,15 +65,15 @@ void AudioPluginFormatManager::addDefaultFormats() // you should only call this method once! for (int i = formats.size(); --i >= 0;) { - #if JUCE_PLUGINHOST_VST + #if JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS) jassert (dynamic_cast (formats[i]) == nullptr); #endif - #if JUCE_PLUGINHOST_VST3 + #if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) jassert (dynamic_cast (formats[i]) == nullptr); #endif - #if JUCE_PLUGINHOST_AU && JUCE_MAC + #if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) jassert (dynamic_cast (formats[i]) == nullptr); #endif @@ -50,15 +83,15 @@ void AudioPluginFormatManager::addDefaultFormats() } #endif - #if JUCE_PLUGINHOST_AU && JUCE_MAC + #if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) formats.add (new AudioUnitPluginFormat()); #endif - #if JUCE_PLUGINHOST_VST + #if JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS) formats.add (new VSTPluginFormat()); #endif - #if JUCE_PLUGINHOST_VST3 + #if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) formats.add (new VST3PluginFormat()); #endif @@ -85,12 +118,55 @@ void AudioPluginFormatManager::addFormat (AudioPluginFormat* const format) AudioPluginInstance* AudioPluginFormatManager::createPluginInstance (const PluginDescription& description, double rate, int blockSize, String& errorMessage) const { + if (AudioPluginFormat* format = findFormatForDescription (description, errorMessage)) + return format->createInstanceFromDescription (description, rate, blockSize, errorMessage); + + return nullptr; +} + +void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + AudioPluginFormat::InstantiationCompletionCallback* callback) +{ + String error; + + if (AudioPluginFormat* format = findFormatForDescription (description, error)) + return format->createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, callback); + + (new PluginFormatManagerHelpers::ErrorCallbackOnMessageThread (error, callback))->post(); +} + +#if JUCE_COMPILER_SUPPORTS_LAMBDAS +void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + std::function f) +{ + String error; + + if (AudioPluginFormat* format = findFormatForDescription (description, error)) + return format->createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, f); + + (new PluginFormatManagerHelpers::ErrorLambdaOnMessageThread (error, f))->post(); +} +#endif + +AudioPluginFormat* AudioPluginFormatManager::findFormatForDescription (const PluginDescription& description, String& errorMessage) const +{ + errorMessage = String(); + for (int i = 0; i < formats.size(); ++i) - if (AudioPluginInstance* result = formats.getUnchecked(i)->createInstanceFromDescription (description, rate, blockSize)) - return result; + { + AudioPluginFormat* format; + + if ((format = formats.getUnchecked (i))->getName() == description.pluginFormatName + && format->fileMightContainThisPluginType (description.fileOrIdentifier)) + return format; + } + + errorMessage = NEEDS_TRANS ("No compatible plug-in format exists for this plug-in"); - errorMessage = doesPluginStillExist (description) ? TRANS ("This plug-in failed to load correctly") - : TRANS ("This plug-in file no longer exists"); return nullptr; } diff --git a/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h b/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h index f810f32f8..65e563f75 100644 --- a/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h +++ b/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h @@ -75,12 +75,51 @@ public: If it can't load the plugin, it returns nullptr and leaves a message in the errorMessage string. + + If you intend to instantiate a AudioUnit v3 plug-in then you must either + use the non-blocking asynchrous version below - or call this method from a + thread other than the message thread and without blocking the message + thread. */ AudioPluginInstance* createPluginInstance (const PluginDescription& description, double initialSampleRate, int initialBufferSize, String& errorMessage) const; + /** Tries to asynchronously load the type for this description, by trying + all the formats that this manager knows about. + + The caller must supply a callback object which will be called when + the instantantiation has completed. + + If it can't load the plugin then the callback function will be called + passing a nullptr as the instance argument along with an error message. + + The callback function will be called on the message thread so the caller + must not block the message thread. + + The callback object will be deleted automatically after it has been + invoked. + + The caller is responsible for deleting the instance that is passed to + the callback function. + + If you intend to instantiate a AudioUnit v3 plug-in then you must use + this non-blocking asynchrous version - or call the synchrous method + from an auxiliary thread. + */ + void createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + AudioPluginFormat::InstantiationCompletionCallback* callback); + + #if JUCE_COMPILER_SUPPORTS_LAMBDAS + void createPluginInstanceAsync (const PluginDescription& description, + double initialSampleRate, + int initialBufferSize, + std::function completionCallback); + #endif + /** Checks that the file or component for this plugin actually still exists. (This won't try to load the plugin) @@ -89,6 +128,9 @@ public: private: //============================================================================== + //@internal + AudioPluginFormat* findFormatForDescription (const PluginDescription&, String& errorMessage) const; + OwnedArray formats; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormatManager) diff --git a/source/modules/juce_audio_processors/format_types/juce_AU_Shared.h b/source/modules/juce_audio_processors/format_types/juce_AU_Shared.h new file mode 100644 index 000000000..5ef785780 --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_AU_Shared.h @@ -0,0 +1,778 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +// This macro can be set if you need to override this internal name for some reason.. +#ifndef JUCE_STATE_DICTIONARY_KEY + #define JUCE_STATE_DICTIONARY_KEY "jucePluginState" +#endif + +struct AudioUnitHelpers +{ + // maps a channel index into an AU format to an index of a juce format + struct AUChannelStreamOrder + { + AudioChannelLayoutTag auLayoutTag; + AudioChannelSet::ChannelType speakerOrder[8]; + }; + + struct StreamOrder : public AudioChannelSet + { + static AUChannelStreamOrder auChannelStreamOrder[]; + }; + + static AudioChannelSet::ChannelType CoreAudioChannelLabelToJuceType (AudioChannelLabel label) noexcept + { + if (label >= kAudioChannelLabel_Discrete_0 && label <= kAudioChannelLabel_Discrete_65535) + { + const unsigned int discreteChannelNum = label - kAudioChannelLabel_Discrete_0; + return static_cast (AudioChannelSet::discreteChannel0 + discreteChannelNum); + } + + switch (label) + { + case kAudioChannelLabel_Center: + case kAudioChannelLabel_Mono: return AudioChannelSet::centre; + case kAudioChannelLabel_Left: + case kAudioChannelLabel_HeadphonesLeft: return AudioChannelSet::left; + case kAudioChannelLabel_Right: + case kAudioChannelLabel_HeadphonesRight: return AudioChannelSet::right; + case kAudioChannelLabel_LFEScreen: return AudioChannelSet::LFE; + case kAudioChannelLabel_LeftSurround: return AudioChannelSet::leftSurround; + case kAudioChannelLabel_RightSurround: return AudioChannelSet::rightSurround; + case kAudioChannelLabel_LeftCenter: return AudioChannelSet::leftCentre; + case kAudioChannelLabel_RightCenter: return AudioChannelSet::rightCentre; + case kAudioChannelLabel_CenterSurround: return AudioChannelSet::surround; + case kAudioChannelLabel_LeftSurroundDirect: return AudioChannelSet::leftSurroundSide; + case kAudioChannelLabel_RightSurroundDirect: return AudioChannelSet::rightSurroundSide; + case kAudioChannelLabel_TopCenterSurround: return AudioChannelSet::topMiddle; + case kAudioChannelLabel_VerticalHeightLeft: return AudioChannelSet::topFrontLeft; + case kAudioChannelLabel_VerticalHeightRight: return AudioChannelSet::topFrontRight; + case kAudioChannelLabel_VerticalHeightCenter: return AudioChannelSet::topFrontCentre; + case kAudioChannelLabel_TopBackLeft: return AudioChannelSet::topRearLeft; + case kAudioChannelLabel_RearSurroundLeft: return AudioChannelSet::leftSurroundRear; + case kAudioChannelLabel_TopBackRight: return AudioChannelSet::topRearRight; + case kAudioChannelLabel_RearSurroundRight: return AudioChannelSet::rightSurroundRear; + case kAudioChannelLabel_TopBackCenter: return AudioChannelSet::topRearCentre; + case kAudioChannelLabel_LFE2: return AudioChannelSet::LFE2; + case kAudioChannelLabel_LeftWide: return AudioChannelSet::wideLeft; + case kAudioChannelLabel_RightWide: return AudioChannelSet::wideRight; + case kAudioChannelLabel_Ambisonic_W: return AudioChannelSet::ambisonicW; + case kAudioChannelLabel_Ambisonic_X: return AudioChannelSet::ambisonicX; + case kAudioChannelLabel_Ambisonic_Y: return AudioChannelSet::ambisonicY; + case kAudioChannelLabel_Ambisonic_Z: return AudioChannelSet::ambisonicZ; + default: return AudioChannelSet::unknown; + } + } + + static AudioChannelLabel JuceChannelTypeToCoreAudioLabel (const AudioChannelSet::ChannelType& label) noexcept + { + if (label >= AudioChannelSet::discreteChannel0) + { + const unsigned int discreteChannelNum = label - AudioChannelSet::discreteChannel0;; + return static_cast (kAudioChannelLabel_Discrete_0 + discreteChannelNum); + } + + switch (label) + { + case AudioChannelSet::centre: return kAudioChannelLabel_Center; + case AudioChannelSet::left: return kAudioChannelLabel_Left; + case AudioChannelSet::right: return kAudioChannelLabel_Right; + case AudioChannelSet::LFE: return kAudioChannelLabel_LFEScreen; + case AudioChannelSet::leftSurroundRear: return kAudioChannelLabel_RearSurroundLeft; + case AudioChannelSet::rightSurroundRear: return kAudioChannelLabel_RearSurroundRight; + case AudioChannelSet::leftCentre: return kAudioChannelLabel_LeftCenter; + case AudioChannelSet::rightCentre: return kAudioChannelLabel_RightCenter; + case AudioChannelSet::surround: return kAudioChannelLabel_CenterSurround; + case AudioChannelSet::leftSurround: return kAudioChannelLabel_LeftSurround; + case AudioChannelSet::rightSurround: return kAudioChannelLabel_RightSurround; + case AudioChannelSet::topMiddle: return kAudioChannelLabel_TopCenterSurround; + case AudioChannelSet::topFrontLeft: return kAudioChannelLabel_VerticalHeightLeft; + case AudioChannelSet::topFrontRight: return kAudioChannelLabel_VerticalHeightRight; + case AudioChannelSet::topFrontCentre: return kAudioChannelLabel_VerticalHeightCenter; + case AudioChannelSet::topRearLeft: return kAudioChannelLabel_TopBackLeft; + case AudioChannelSet::topRearRight: return kAudioChannelLabel_TopBackRight; + case AudioChannelSet::topRearCentre: return kAudioChannelLabel_TopBackCenter; + case AudioChannelSet::LFE2: return kAudioChannelLabel_LFE2; + case AudioChannelSet::wideLeft: return kAudioChannelLabel_LeftWide; + case AudioChannelSet::wideRight: return kAudioChannelLabel_RightWide; + case AudioChannelSet::ambisonicW: return kAudioChannelLabel_Ambisonic_W; + case AudioChannelSet::ambisonicX: return kAudioChannelLabel_Ambisonic_X; + case AudioChannelSet::ambisonicY: return kAudioChannelLabel_Ambisonic_Y; + case AudioChannelSet::ambisonicZ: return kAudioChannelLabel_Ambisonic_Z; + case AudioChannelSet::leftSurroundSide: return kAudioChannelLabel_LeftSurroundDirect; + case AudioChannelSet::rightSurroundSide: return kAudioChannelLabel_RightSurroundDirect; + case AudioChannelSet::unknown: return kAudioChannelLabel_Unknown; + case AudioChannelSet::discreteChannel0: return kAudioChannelLabel_Discrete_0; + } + + return kAudioChannelLabel_Unknown; + } + + static AudioChannelSet CoreAudioChannelBitmapToJuceType (UInt32 bitmap) noexcept + { + AudioChannelSet set; + + if ((bitmap & kAudioChannelBit_Left) != 0) set.addChannel (AudioChannelSet::left); + if ((bitmap & kAudioChannelBit_Right) != 0) set.addChannel (AudioChannelSet::right); + if ((bitmap & kAudioChannelBit_Center) != 0) set.addChannel (AudioChannelSet::centre); + if ((bitmap & kAudioChannelBit_LFEScreen) != 0) set.addChannel (AudioChannelSet::LFE); + if ((bitmap & kAudioChannelBit_LeftSurroundDirect) != 0) set.addChannel (AudioChannelSet::leftSurroundSide); + if ((bitmap & kAudioChannelBit_RightSurroundDirect) != 0) set.addChannel (AudioChannelSet::rightSurroundSide); + if ((bitmap & kAudioChannelBit_LeftCenter) != 0) set.addChannel (AudioChannelSet::leftCentre); + if ((bitmap & kAudioChannelBit_RightCenter) != 0) set.addChannel (AudioChannelSet::rightCentre); + if ((bitmap & kAudioChannelBit_CenterSurround) != 0) set.addChannel (AudioChannelSet::surround); + if ((bitmap & kAudioChannelBit_LeftSurround) != 0) set.addChannel (AudioChannelSet::leftSurround); + if ((bitmap & kAudioChannelBit_RightSurround) != 0) set.addChannel (AudioChannelSet::rightSurround); + if ((bitmap & kAudioChannelBit_TopCenterSurround) != 0) set.addChannel (AudioChannelSet::topMiddle); + if ((bitmap & kAudioChannelBit_VerticalHeightLeft) != 0) set.addChannel (AudioChannelSet::topFrontLeft); + if ((bitmap & kAudioChannelBit_VerticalHeightCenter) != 0) set.addChannel (AudioChannelSet::topFrontCentre); + if ((bitmap & kAudioChannelBit_VerticalHeightRight) != 0) set.addChannel (AudioChannelSet::topFrontRight); + if ((bitmap & kAudioChannelBit_TopBackLeft) != 0) set.addChannel (AudioChannelSet::topRearLeft); + if ((bitmap & kAudioChannelBit_TopBackCenter) != 0) set.addChannel (AudioChannelSet::topRearCentre); + if ((bitmap & kAudioChannelBit_TopBackRight) != 0) set.addChannel (AudioChannelSet::topRearRight); + + return set; + } + + static AudioChannelSet CoreAudioChannelLayoutToJuceType (const AudioChannelLayout& layout) noexcept + { + const AudioChannelLayoutTag tag = layout.mChannelLayoutTag; + + if (tag == kAudioChannelLayoutTag_UseChannelBitmap) return CoreAudioChannelBitmapToJuceType (layout.mChannelBitmap); + if (tag == kAudioChannelLayoutTag_UseChannelDescriptions) + { + if (layout.mNumberChannelDescriptions <= 8) + { + // first try to convert the layout via the auChannelStreamOrder array + int layoutIndex; + for (layoutIndex = 0; StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != 0; ++layoutIndex) + { + const AUChannelStreamOrder& streamOrder = StreamOrder::auChannelStreamOrder[layoutIndex]; + + int numChannels; + for (numChannels = 0; numChannels < 8 && streamOrder.speakerOrder[numChannels] != 0;) + ++numChannels; + + if (numChannels != (int) layout.mNumberChannelDescriptions) + continue; + + int ch; + for (ch = 0; ch < numChannels; ++ch) + if (JuceChannelTypeToCoreAudioLabel (streamOrder.speakerOrder[ch]) != layout.mChannelDescriptions[ch].mChannelLabel) + break; + + // match! + if (ch == numChannels) + break; + } + + if (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != 0) + return CALayoutTagToChannelSet (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag); + } + AudioChannelSet set; + for (unsigned int i = 0; i < layout.mNumberChannelDescriptions; ++i) + set.addChannel (CoreAudioChannelLabelToJuceType (layout.mChannelDescriptions[i].mChannelLabel)); + + return set; + } + + return CALayoutTagToChannelSet (tag); + } + + static AudioChannelSet CALayoutTagToChannelSet (AudioChannelLayoutTag tag) noexcept + { + switch (tag) + { + case kAudioChannelLayoutTag_Unknown: return AudioChannelSet::disabled(); + case kAudioChannelLayoutTag_Mono: return AudioChannelSet::mono(); + case kAudioChannelLayoutTag_Stereo: + case kAudioChannelLayoutTag_StereoHeadphones: + case kAudioChannelLayoutTag_Binaural: return AudioChannelSet::stereo(); + case kAudioChannelLayoutTag_Quadraphonic: return AudioChannelSet::quadraphonic(); + case kAudioChannelLayoutTag_Pentagonal: return AudioChannelSet::pentagonal(); + case kAudioChannelLayoutTag_Hexagonal: return AudioChannelSet::hexagonal(); + case kAudioChannelLayoutTag_Octagonal: return AudioChannelSet::octagonal(); + case kAudioChannelLayoutTag_Ambisonic_B_Format: return AudioChannelSet::ambisonic(); + case kAudioChannelLayoutTag_AudioUnit_6_0: return AudioChannelSet::create6point0(); + case kAudioChannelLayoutTag_DTS_6_0_A: return AudioChannelSet::create6point0Music(); + case kAudioChannelLayoutTag_MPEG_6_1_A: return AudioChannelSet::create6point1(); + case kAudioChannelLayoutTag_DTS_6_1_A: return AudioChannelSet::create6point1Music(); + case kAudioChannelLayoutTag_MPEG_5_0_B: + case kAudioChannelLayoutTag_MPEG_5_0_A: + return AudioChannelSet::create5point0(); + case kAudioChannelLayoutTag_MPEG_5_1_A: return AudioChannelSet::create5point1(); + case kAudioChannelLayoutTag_DTS_7_1: + case kAudioChannelLayoutTag_AudioUnit_7_0: return AudioChannelSet::create7point0(); + case kAudioChannelLayoutTag_AudioUnit_7_0_Front: return AudioChannelSet::create7point0SDDS(); + case kAudioChannelLayoutTag_MPEG_7_1_A: return AudioChannelSet::create7point1SDDS(); + case kAudioChannelLayoutTag_MPEG_3_0_A: + case kAudioChannelLayoutTag_MPEG_3_0_B: return AudioChannelSet::createLCR(); + case kAudioChannelLayoutTag_MPEG_4_0_A: + case kAudioChannelLayoutTag_MPEG_4_0_B: return AudioChannelSet::createLCRS(); + case kAudioChannelLayoutTag_ITU_2_1: return AudioChannelSet::createLRS(); + case kAudioChannelLayoutTag_MPEG_7_1_C: return AudioChannelSet::create7point1(); + } + + if (int numChannels = static_cast (tag) & 0xffff) + return AudioChannelSet::discreteChannels (numChannels); + + // Bitmap and channel description array layout tags are currently unsupported :-( + jassertfalse; + return AudioChannelSet(); + } + + static AudioChannelLayoutTag ChannelSetToCALayoutTag (const AudioChannelSet& set) noexcept + { + if (set == AudioChannelSet::mono()) return kAudioChannelLayoutTag_Mono; + if (set == AudioChannelSet::stereo()) return kAudioChannelLayoutTag_Stereo; + if (set == AudioChannelSet::createLCR()) return kAudioChannelLayoutTag_MPEG_3_0_A; + if (set == AudioChannelSet::createLRS()) return kAudioChannelLayoutTag_ITU_2_1; + if (set == AudioChannelSet::createLCRS()) return kAudioChannelLayoutTag_MPEG_4_0_A; + if (set == AudioChannelSet::quadraphonic()) return kAudioChannelLayoutTag_Quadraphonic; + if (set == AudioChannelSet::pentagonal()) return kAudioChannelLayoutTag_Pentagonal; + if (set == AudioChannelSet::hexagonal()) return kAudioChannelLayoutTag_Hexagonal; + if (set == AudioChannelSet::octagonal()) return kAudioChannelLayoutTag_Octagonal; + if (set == AudioChannelSet::ambisonic()) return kAudioChannelLayoutTag_Ambisonic_B_Format; + if (set == AudioChannelSet::create5point0()) return kAudioChannelLayoutTag_MPEG_5_0_A; + if (set == AudioChannelSet::create5point1()) return kAudioChannelLayoutTag_MPEG_5_1_A; + if (set == AudioChannelSet::create6point0()) return kAudioChannelLayoutTag_AudioUnit_6_0; + if (set == AudioChannelSet::create6point0Music()) return kAudioChannelLayoutTag_DTS_6_0_A; + if (set == AudioChannelSet::create6point1Music()) return kAudioChannelLayoutTag_DTS_6_1_A; + if (set == AudioChannelSet::create6point1()) return kAudioChannelLayoutTag_MPEG_6_1_A; + if (set == AudioChannelSet::create7point0()) return kAudioChannelLayoutTag_AudioUnit_7_0; + if (set == AudioChannelSet::create7point1()) return kAudioChannelLayoutTag_MPEG_7_1_C; + if (set == AudioChannelSet::create7point0SDDS()) return kAudioChannelLayoutTag_AudioUnit_7_0_Front; + if (set == AudioChannelSet::create7point1SDDS()) return kAudioChannelLayoutTag_MPEG_7_1_A; + if (set == AudioChannelSet::disabled()) return kAudioChannelLayoutTag_Unknown; + + return static_cast ((int) kAudioChannelLayoutTag_DiscreteInOrder | set.size()); + } + + static int auChannelIndexToJuce (int auIndex, const AudioChannelSet& channelSet) + { + if (auIndex >= 8) return auIndex; + + AudioChannelLayoutTag currentLayout = ChannelSetToCALayoutTag (channelSet); + + int layoutIndex; + for (layoutIndex = 0; StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != currentLayout; ++layoutIndex) + if (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag == 0) return auIndex; + + AudioChannelSet::ChannelType channelType + = StreamOrder::auChannelStreamOrder[layoutIndex].speakerOrder[auIndex]; + + const int juceIndex = channelSet.getChannelTypes().indexOf (channelType); + + jassert (juceIndex >= 0); + return juceIndex >= 0 ? juceIndex : auIndex; + } + + static int juceChannelIndexToAu (int juceIndex, const AudioChannelSet& channelSet) + { + if (channelSet.isDiscreteLayout()) + return juceIndex; + + AudioChannelLayoutTag currentLayout = ChannelSetToCALayoutTag (channelSet); + + int layoutIndex; + for (layoutIndex = 0; StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != currentLayout; ++layoutIndex) + { + if (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag == 0) + { + jassertfalse; + return juceIndex; + } + } + + const AUChannelStreamOrder& channelOrder = StreamOrder::auChannelStreamOrder[layoutIndex]; + AudioChannelSet::ChannelType channelType = channelSet.getTypeOfChannel (juceIndex); + + for (int i = 0; i < 8 && channelOrder.speakerOrder[i] != 0; ++i) + if (channelOrder.speakerOrder[i] == channelType) + return i; + + jassertfalse; + return juceIndex; + } + + class ChannelRemapper + { + public: + ChannelRemapper (AudioProcessor& p) : processor (p), inputLayoutMap (nullptr), outputLayoutMap (nullptr) {} + ~ChannelRemapper() {} + + void alloc() + { + const int numInputBuses = processor.getBusCount (true); + const int numOutputBuses = processor.getBusCount (false); + + initializeChannelMapArray (true, numInputBuses); + initializeChannelMapArray (false, numOutputBuses); + + for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) + fillLayoutChannelMaps (true, busIdx); + + for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) + fillLayoutChannelMaps (false, busIdx); + } + + void release() + { + inputLayoutMap = outputLayoutMap = nullptr; + inputLayoutMapPtrStorage.free(); + outputLayoutMapPtrStorage.free(); + inputLayoutMapStorage.free(); + outputLayoutMapStorage.free(); + } + + inline const int* get (bool input, int bus) const noexcept { return (input ? inputLayoutMap : outputLayoutMap) [bus]; } + + private: + //============================================================================== + AudioProcessor& processor; + HeapBlock inputLayoutMapPtrStorage, outputLayoutMapPtrStorage; + HeapBlock inputLayoutMapStorage, outputLayoutMapStorage; + int** inputLayoutMap; + int** outputLayoutMap; + + //============================================================================== + void initializeChannelMapArray (bool isInput, const int numBuses) + { + HeapBlock& layoutMapPtrStorage = isInput ? inputLayoutMapPtrStorage : outputLayoutMapPtrStorage; + HeapBlock& layoutMapStorage = isInput ? inputLayoutMapStorage : outputLayoutMapStorage; + int**& layoutMap = isInput ? inputLayoutMap : outputLayoutMap; + + const int totalInChannels = processor.getTotalNumInputChannels(); + const int totalOutChannels = processor.getTotalNumOutputChannels(); + + layoutMapPtrStorage.calloc (static_cast (numBuses)); + layoutMapStorage.calloc (static_cast (isInput ? totalInChannels : totalOutChannels)); + + layoutMap = layoutMapPtrStorage. getData(); + + int ch = 0; + for (int busIdx = 0; busIdx < numBuses; ++busIdx) + { + layoutMap[busIdx] = layoutMapStorage.getData() + ch; + ch += processor.getChannelCountOfBus (isInput, busIdx); + } + } + + void fillLayoutChannelMaps (bool isInput, int busNr) + { + int* layoutMap = (isInput ? inputLayoutMap : outputLayoutMap)[busNr]; + const AudioChannelSet& channelFormat = processor.getChannelLayoutOfBus (isInput, busNr); + const int numChannels = channelFormat.size(); + + for (int i = 0; i < numChannels; ++i) + layoutMap[i] = AudioUnitHelpers::juceChannelIndexToAu (i, channelFormat); + } + }; + + //============================================================================== + class CoreAudioBufferList + { + public: + CoreAudioBufferList() { reset(); } + + //============================================================================== + void prepare (int inChannels, int outChannels, int maxFrames) + { + const int numChannels = jmax (inChannels, outChannels); + + scratch.setSize (numChannels, maxFrames); + channels.calloc (static_cast (numChannels)); + + reset(); + } + + void release() + { + scratch.setSize (0, 0); + channels.free(); + } + + void reset() noexcept + { + pushIdx = 0; + popIdx = 0; + zeromem (channels.getData(), sizeof(float*) * static_cast (scratch.getNumChannels())); + } + + //============================================================================== + float* setBuffer (const int idx, float* ptr = nullptr) noexcept + { + jassert (idx < scratch.getNumChannels()); + return (channels [idx] = uniqueBuffer (idx, ptr)); + } + + //============================================================================== + float* push() noexcept + { + jassert (pushIdx < scratch.getNumChannels()); + return channels [pushIdx++]; + } + + void push (AudioBufferList& bufferList, const int* channelMap) noexcept + { + jassert (pushIdx < scratch.getNumChannels()); + + if (bufferList.mNumberBuffers > 0) + { + const UInt32 n = bufferList.mBuffers [0].mDataByteSize / + (bufferList.mBuffers [0].mNumberChannels * sizeof (float)); + const bool isInterleaved = isAudioBufferInterleaved (bufferList); + const int numChannels = static_cast (isInterleaved ? bufferList.mBuffers [0].mNumberChannels + : bufferList.mNumberBuffers); + + for (int ch = 0; ch < numChannels; ++ch) + { + float* data = push(); + + int mappedChannel = channelMap [ch]; + if (isInterleaved || static_cast (bufferList.mBuffers [mappedChannel].mData) != data) + copyAudioBuffer (bufferList, mappedChannel, n, data); + } + } + } + + //============================================================================== + float* pop() noexcept + { + jassert (popIdx < scratch.getNumChannels()); + return channels[popIdx++]; + } + + void pop (AudioBufferList& buffer, const int* channelMap) noexcept + { + if (buffer.mNumberBuffers > 0) + { + const UInt32 n = buffer.mBuffers [0].mDataByteSize / (buffer.mBuffers [0].mNumberChannels * sizeof (float)); + const bool isInterleaved = isAudioBufferInterleaved (buffer); + const int numChannels = static_cast (isInterleaved ? buffer.mBuffers [0].mNumberChannels : buffer.mNumberBuffers); + + for (int ch = 0; ch < numChannels; ++ch) + { + int mappedChannel = channelMap [ch]; + float* nextBuffer = pop(); + + if (nextBuffer == buffer.mBuffers [mappedChannel].mData && ! isInterleaved) + continue; // no copying necessary + + if (buffer.mBuffers [mappedChannel].mData == nullptr && ! isInterleaved) + buffer.mBuffers [mappedChannel].mData = nextBuffer; + else + copyAudioBuffer (nextBuffer, mappedChannel, n, buffer); + } + } + } + + //============================================================================== + AudioSampleBuffer& getBuffer (UInt32 frames) noexcept + { + jassert (pushIdx == scratch.getNumChannels()); + + #if JUCE_DEBUG + for (int i = 0; i < pushIdx; ++i) + jassert (channels [i] != nullptr); + #endif + + mutableBuffer.setDataToReferTo (channels, pushIdx, static_cast (frames)); + return mutableBuffer; + } + + private: + float* uniqueBuffer (int idx, float* buffer) noexcept + { + if (buffer == nullptr) + return scratch.getWritePointer (idx); + + for (int ch = 0; ch < idx; ++ch) + if (buffer == channels[ch]) + return scratch.getWritePointer (idx); + + return buffer; + } + + //============================================================================== + AudioSampleBuffer scratch; + AudioSampleBuffer mutableBuffer; + + HeapBlock channels; + int pushIdx, popIdx; + }; + + static bool isAudioBufferInterleaved (const AudioBufferList& audioBuffer) noexcept + { + return (audioBuffer.mNumberBuffers == 1 && audioBuffer.mBuffers[0].mNumberChannels > 1); + } + + static void clearAudioBuffer (const AudioBufferList& audioBuffer) noexcept + { + for (unsigned int ch = 0; ch < audioBuffer.mNumberBuffers; ++ch) + zeromem (audioBuffer.mBuffers[ch].mData, audioBuffer.mBuffers[ch].mDataByteSize); + } + + static void copyAudioBuffer (const AudioBufferList& audioBuffer, const int channel, const UInt32 size, float* dst) noexcept + { + if (! isAudioBufferInterleaved (audioBuffer)) + { + jassert (channel < static_cast (audioBuffer.mNumberBuffers)); + jassert (audioBuffer.mBuffers[channel].mDataByteSize == (size * sizeof (float))); + + memcpy (dst, audioBuffer.mBuffers[channel].mData, size * sizeof (float)); + } + else + { + const int numChannels = static_cast (audioBuffer.mBuffers[0].mNumberChannels); + const UInt32 n = static_cast (numChannels) * size; + const float* src = static_cast (audioBuffer.mBuffers[0].mData); + + jassert (channel < numChannels); + jassert (audioBuffer.mBuffers[0].mDataByteSize == (n * sizeof (float))); + + for (const float* inData = src; inData < (src + n); inData += numChannels) + *dst++ = inData[channel]; + } + } + + static void copyAudioBuffer (const float *src, const int channel, const UInt32 size, AudioBufferList& audioBuffer) noexcept + { + if (! isAudioBufferInterleaved (audioBuffer)) + { + jassert (channel < static_cast (audioBuffer.mNumberBuffers)); + jassert (audioBuffer.mBuffers[channel].mDataByteSize == (size * sizeof (float))); + + memcpy (audioBuffer.mBuffers[channel].mData, src, size * sizeof (float)); + } + else + { + const int numChannels = static_cast (audioBuffer.mBuffers[0].mNumberChannels); + const UInt32 n = static_cast (numChannels) * size; + float* dst = static_cast (audioBuffer.mBuffers[0].mData); + + jassert (channel < numChannels); + jassert (audioBuffer.mBuffers[0].mDataByteSize == (n * sizeof (float))); + + for (float* outData = dst; outData < (dst + n); outData += numChannels) + outData[channel] = *src++; + } + } + + template + static bool isLayoutSupported (const AudioProcessor& processor, + bool isInput, int busIdx, + int numChannels, + const short (&channelLayoutList) [numLayouts][2], + bool hasLayoutMap = true) + { + if (const AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) + { + if (! bus->isNumberOfChannelsSupported (numChannels)) + return false; + + if (! hasLayoutMap) + return true; + + const int numConfigs = sizeof (channelLayoutList) / sizeof (short[2]); + + for (int i = 0; i < numConfigs; ++i) + { + if (channelLayoutList[i][isInput ? 0 : 1] == numChannels) + return true; + } + } + + return false; + } + + static Array getAUChannelInfo (const AudioProcessor& processor) + { + Array channelInfo; + + const bool hasMainInputBus = (processor.getBusCount (true) > 0); + const bool hasMainOutputBus = (processor.getBusCount (false) > 0); + + if ((! hasMainInputBus) && (! hasMainOutputBus)) + { + // midi effect plug-in: no audio + AUChannelInfo info; + info.inChannels = 0; + info.outChannels = 0; + + channelInfo.add (info); + return channelInfo; + } + else + { + const uint32_t maxNumChanToCheckFor = 9; + + uint32_t defaultInputs = static_cast (processor.getChannelCountOfBus (true, 0)); + uint32_t defaultOutputs = static_cast (processor.getChannelCountOfBus (false, 0)); + + uint32_t lastInputs = defaultInputs; + uint32_t lastOutputs = defaultOutputs; + + SortedSet supportedChannels; + + // add the current configuration + if (lastInputs != 0 || lastOutputs != 0) + supportedChannels.add ((lastInputs << 16) | lastOutputs); + + for (uint32_t inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) + { + const AudioProcessor::Bus* inBus = processor.getBus (true, 0); + + if (inBus != nullptr && (! inBus->isNumberOfChannelsSupported ((int) inChanNum))) + continue; + + for (uint32_t outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) + { + const AudioProcessor::Bus* outBus = processor.getBus (false, 0); + + if (outBus != nullptr && (! outBus->isNumberOfChannelsSupported ((int) outChanNum))) + continue; + + uint32_t channelConfiguration = (inChanNum << 16) | outChanNum; + + // did we already try this configuration? + if (supportedChannels.contains (channelConfiguration)) continue; + + if (lastInputs != inChanNum && (inChanNum > 0 && inBus != nullptr)) + { + AudioChannelSet set = inBus->supportedLayoutWithChannels ((int) inChanNum); + AudioProcessor::BusesLayout layouts = inBus->getBusesLayoutForLayoutChangeOfBus (set); + + lastInputs = inChanNum; + lastOutputs = hasMainOutputBus ? static_cast (layouts.outputBuses.getReference (0).size()) : 0; + + supportedChannels.add ((lastInputs << 16) | lastOutputs); + } + + if (lastOutputs != outChanNum && (outChanNum > 0 && outBus != nullptr)) + { + AudioChannelSet set = outBus->supportedLayoutWithChannels ((int) outChanNum); + AudioProcessor::BusesLayout layouts = outBus->getBusesLayoutForLayoutChangeOfBus (set); + + lastOutputs = outChanNum; + lastInputs = hasMainInputBus ? static_cast (layouts.inputBuses.getReference (0).size()) : 0; + + supportedChannels.add ((lastInputs << 16) | lastOutputs); + } + } + } + + bool hasInOutMismatch = false; + for (int i = 0; i < supportedChannels.size(); ++i) + { + const uint32_t numInputs = (supportedChannels[i] >> 16) & 0xffff; + const uint32_t numOutputs = (supportedChannels[i] >> 0) & 0xffff; + + if (numInputs != numOutputs) + { + hasInOutMismatch = true; + break; + } + } + + bool hasUnsupportedInput = ! hasMainOutputBus, hasUnsupportedOutput = ! hasMainInputBus; + for (uint32_t inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) + { + uint32_t channelConfiguration = (inChanNum << 16) | (hasInOutMismatch ? defaultOutputs : inChanNum); + if (! supportedChannels.contains (channelConfiguration)) + { + hasUnsupportedInput = true; + break; + } + } + + for (uint32_t outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) + { + uint32_t channelConfiguration = ((hasInOutMismatch ? defaultInputs : outChanNum) << 16) | outChanNum; + if (! supportedChannels.contains (channelConfiguration)) + { + hasUnsupportedOutput = true; + break; + } + } + + for (int i = 0; i < supportedChannels.size(); ++i) + { + const int numInputs = (supportedChannels[i] >> 16) & 0xffff; + const int numOutputs = (supportedChannels[i] >> 0) & 0xffff; + + AUChannelInfo info; + + // see here: https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/TheAudioUnit/TheAudioUnit.html + info.inChannels = static_cast (hasMainInputBus ? (hasUnsupportedInput ? numInputs : (hasInOutMismatch && (! hasUnsupportedOutput) ? -2 : -1)) : 0); + info.outChannels = static_cast (hasMainOutputBus ? (hasUnsupportedOutput ? numOutputs : (hasInOutMismatch && (! hasUnsupportedInput) ? -2 : -1)) : 0); + + if (info.inChannels == -2 && info.outChannels == -2) + info.inChannels = -1; + + int j; + for (j = 0; j < channelInfo.size(); ++j) + if (channelInfo[j].inChannels == info.inChannels && channelInfo[j].outChannels == info.outChannels) + break; + + if (j >= channelInfo.size()) + channelInfo.add (info); + } + } + + return channelInfo; + } +}; + +AudioUnitHelpers::AUChannelStreamOrder AudioUnitHelpers::StreamOrder::auChannelStreamOrder[] = +{ + {kAudioChannelLayoutTag_Mono, {centre, unknown, unknown, unknown, unknown, unknown, unknown, unknown}}, + {kAudioChannelLayoutTag_Stereo, {left, right, unknown, unknown, unknown, unknown, unknown, unknown}}, + {kAudioChannelLayoutTag_StereoHeadphones, {left, right, unknown, unknown, unknown, unknown, unknown, unknown}}, + {kAudioChannelLayoutTag_Binaural, {left, right, unknown, unknown, unknown, unknown, unknown, unknown}}, + {kAudioChannelLayoutTag_Quadraphonic, {left, right, leftSurround, rightSurround, unknown, unknown, unknown, unknown}}, + {kAudioChannelLayoutTag_Pentagonal, {left, right, leftSurroundRear, rightSurroundRear, centre, unknown, unknown, unknown}}, + {kAudioChannelLayoutTag_Hexagonal, {left, right, leftSurroundRear, rightSurroundRear, centre, centreSurround, unknown, unknown}}, + {kAudioChannelLayoutTag_Octagonal, {left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight}}, + {kAudioChannelLayoutTag_Ambisonic_B_Format, {ambisonicW, ambisonicX, ambisonicY, ambisonicZ, unknown, unknown, unknown, unknown}}, + {kAudioChannelLayoutTag_MPEG_5_0_A, {left, right, centre, leftSurround, rightSurround, unknown, unknown, unknown}}, + {kAudioChannelLayoutTag_MPEG_5_0_B, {left, right, leftSurround, rightSurround, centre, unknown, unknown, unknown}}, + {kAudioChannelLayoutTag_MPEG_5_1_A, {left, right, centre, LFE, leftSurround, rightSurround, unknown, unknown}}, + {kAudioChannelLayoutTag_AudioUnit_6_0, {left, right, leftSurround, rightSurround, centre, centreSurround, unknown, unknown}}, + {kAudioChannelLayoutTag_DTS_6_0_A, {left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide, unknown, unknown}}, // TODO check this one + {kAudioChannelLayoutTag_MPEG_6_1_A, {left, right, centre, LFE, leftSurround, rightSurround, centre, unknown}}, + {kAudioChannelLayoutTag_DTS_6_1_A, {leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround, LFE, unknown}}, + {kAudioChannelLayoutTag_AudioUnit_7_0, {left, right, leftSurroundSide, rightSurroundSide, centre, leftSurroundRear, rightSurroundRear, unknown}}, + {kAudioChannelLayoutTag_MPEG_7_1_C, {left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear}}, + {kAudioChannelLayoutTag_AudioUnit_7_0_Front,{left, right, leftSurround, rightSurround, centre, leftCentre, rightCentre, unknown}}, + {kAudioChannelLayoutTag_MPEG_7_1_A, {left, right, centre, LFE, leftSurround, rightSurround, leftCentre, rightCentre}}, + {kAudioChannelLayoutTag_DTS_7_1, {leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround, LFE}}, + {kAudioChannelLayoutTag_MPEG_3_0_A, {left, right, centre, unknown, unknown, unknown, unknown, unknown}}, + {kAudioChannelLayoutTag_MPEG_3_0_B, {centre, left, right, unknown, unknown, unknown, unknown, unknown}}, + {kAudioChannelLayoutTag_MPEG_4_0_A, {left, right, centre, centreSurround, unknown, unknown, unknown, unknown}}, + {kAudioChannelLayoutTag_MPEG_4_0_B, {centre, left, right, centreSurround, unknown, unknown, unknown, unknown}}, + {kAudioChannelLayoutTag_ITU_2_1, {left, right, centreSurround, unknown, unknown, unknown, unknown, unknown}}, + {kAudioChannelLayoutTag_EAC3_7_1_C, {left, centre, right, leftSurround, rightSurround, LFE, leftSurroundSide, rightSurroundSide}}, + {unknown, {unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown}} +}; diff --git a/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h b/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h index 21d575bea..5aed3fe4b 100644 --- a/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h +++ b/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h @@ -22,7 +22,7 @@ ============================================================================== */ -#if (JUCE_PLUGINHOST_AU && JUCE_MAC) || DOXYGEN +#if (JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS)) || DOXYGEN //============================================================================== /** @@ -38,16 +38,24 @@ public: //============================================================================== String getName() const override { return "AudioUnit"; } void findAllTypesForFile (OwnedArray&, const String& fileOrIdentifier) override; - AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc, double, int) override; bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; bool pluginNeedsRescanning (const PluginDescription&) override; - StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive) override; + StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; bool doesPluginStillExist (const PluginDescription&) override; FileSearchPath getDefaultLocationsToSearch() override; bool canScanForPlugins() const override { return true; } private: + //============================================================================== + void createPluginInstance (const PluginDescription&, + double initialSampleRate, + int initialBufferSize, + void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) override; + + bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginFormat) }; @@ -55,8 +63,10 @@ private: #endif //============================================================================== +#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 enum { /** Custom AudioUnit property used to indicate MPE support */ - kAudioUnitProperty_SupportsMPE = 75001 + kAudioUnitProperty_SupportsMPE = 58 }; +#endif diff --git a/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index 62c3bcbcd..6fcd01038 100644 --- a/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -22,20 +22,37 @@ ============================================================================== */ -#if JUCE_PLUGINHOST_AU && JUCE_MAC +#if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) } // (juce namespace) #include +#if JUCE_MAC #include #include #include +#endif + #include #if JUCE_SUPPORT_CARBON #include #endif +#ifndef JUCE_SUPPORTS_AUv3 + #if JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES && __OBJC2__ \ + && ((defined (MAC_OS_X_VERSION_MIN_REQUIRED) && defined (MAC_OS_X_VERSION_10_11) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) \ + || (defined (__IPHONE_OS_VERSION_MIN_REQUIRED) && defined (__IPHONE_9_0) && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_9_0))) + #define JUCE_SUPPORTS_AUv3 1 + #else + #define JUCE_SUPPORTS_AUv3 0 + #endif +#endif + +#if JUCE_SUPPORTS_AUv3 + #include +#endif + namespace juce { @@ -45,6 +62,10 @@ namespace juce #include "../../juce_gui_extra/native/juce_mac_CarbonViewWrapperComponent.h" #endif +#include "../../juce_core/native/juce_osx_ObjCHelpers.h" + +#include "juce_AU_Shared.h" + // Change this to disable logging of various activities #ifndef AU_LOGGING #define AU_LOGGING 1 @@ -99,6 +120,8 @@ namespace AudioUnitFormatHelpers s << "Generators/"; else if (desc.componentType == kAudioUnitType_Panner) s << "Panners/"; + else if (desc.componentType == kAudioUnitType_Mixer) + s << "Mixers/"; s << osTypeToString (desc.componentType) << "," << osTypeToString (desc.componentSubType) << "," @@ -177,8 +200,13 @@ namespace AudioUnitFormatHelpers { zerostruct (desc); + #if JUCE_IOS + ignoreUnused (fileOrIdentifier, name, version, manufacturer); + + return false; + #else const File file (fileOrIdentifier); - if (! file.hasFileExtension (".component")) + if (! file.hasFileExtension (".component") && ! file.hasFileExtension (".appex")) return false; const char* const utf8 = fileOrIdentifier.toUTF8(); @@ -225,7 +253,8 @@ namespace AudioUnitFormatHelpers || types[0] == kAudioUnitType_MusicEffect || types[0] == kAudioUnitType_Effect || types[0] == kAudioUnitType_Generator - || types[0] == kAudioUnitType_Panner) + || types[0] == kAudioUnitType_Panner + || types[0] == kAudioUnitType_Mixer) { desc.componentType = types[0]; desc.componentSubType = types[1]; @@ -248,6 +277,7 @@ namespace AudioUnitFormatHelpers } return desc.componentType != 0 && desc.componentSubType != 0; + #endif } const char* getCategory (OSType type) noexcept @@ -259,6 +289,7 @@ namespace AudioUnitFormatHelpers case kAudioUnitType_MusicDevice: return "Synth"; case kAudioUnitType_Generator: return "Generator"; case kAudioUnitType_Panner: return "Panner"; + case kAudioUnitType_Mixer: return "Mixer"; default: break; } @@ -274,44 +305,35 @@ class AudioUnitPluginWindowCocoa; class AudioUnitPluginInstance : public AudioPluginInstance { public: - AudioUnitPluginInstance (const String& fileOrId) - : fileOrIdentifier (fileOrId), + AudioUnitPluginInstance (AudioComponentInstance au) + : AudioPluginInstance (getBusesProperties (au)), + auComponent (AudioComponentInstanceGetComponent (au)), wantsMidiMessages (false), producesMidiMessages (false), wasPlaying (false), prepared (false), + isAUv3 (false), currentBuffer (nullptr), - numInputBusChannels (0), - numOutputBusChannels (0), - numInputBusses (0), - numOutputBusses (0), - audioUnit (nullptr), + audioUnit (au), + #if JUCE_MAC eventListenerRef (0), + #endif midiConcatenator (2048) { using namespace AudioUnitFormatHelpers; - #if JUCE_DEBUG - ++*insideCallback; - #endif - - JUCE_AU_LOG ("Opening AU: " + fileOrIdentifier); + AudioComponentGetDescription (auComponent, &componentDesc); - if (getComponentDescFromIdentifier (fileOrIdentifier, componentDesc, pluginName, version, manufacturer) - || getComponentDescFromFile (fileOrIdentifier, componentDesc, pluginName, version, manufacturer)) - { - if (AudioComponent comp = AudioComponentFindNext (0, &componentDesc)) - { - AudioComponentInstanceNew (comp, &audioUnit); + #if JUCE_SUPPORTS_AUv3 + isAUv3 = ((componentDesc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0); + #endif - wantsMidiMessages = componentDesc.componentType == kAudioUnitType_MusicDevice - || componentDesc.componentType == kAudioUnitType_MusicEffect; - } - } + wantsMidiMessages = componentDesc.componentType == kAudioUnitType_MusicDevice + || componentDesc.componentType == kAudioUnitType_MusicEffect; - #if JUCE_DEBUG - --*insideCallback; - #endif + AudioComponentDescription ignore; + getComponentDescFromIdentifier (createPluginIdentifier (componentDesc), ignore, pluginName, version, manufacturer); + updateSupportedLayouts(); } ~AudioUnitPluginInstance() @@ -324,42 +346,293 @@ public: jassert (AudioUnitFormatHelpers::insideCallback.get() == 0); #endif + if (audioUnit != nullptr) + { + struct AUDeleter : public CallbackMessage + { + AUDeleter (AudioUnitPluginInstance& inInstance, WaitableEvent& inEvent) + : auInstance (inInstance), completionSignal (inEvent) + {} + + void messageCallback() override + { + auInstance.cleanup(); + completionSignal.signal(); + } + + AudioUnitPluginInstance& auInstance; + WaitableEvent& completionSignal; + }; + + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + cleanup(); + } + else + { + WaitableEvent completionEvent; + (new AUDeleter (*this, completionEvent))->post(); + completionEvent.wait(); + } + } + } + + // called from the destructer above + void cleanup() + { + #if JUCE_MAC if (eventListenerRef != 0) { AUListenerDispose (eventListenerRef); eventListenerRef = 0; } + #endif - if (audioUnit != nullptr) - { - if (prepared) - releaseResources(); + if (prepared) + releaseResources(); - AudioComponentInstanceDispose (audioUnit); - audioUnit = nullptr; - } + AudioComponentInstanceDispose (audioUnit); + audioUnit = nullptr; } - void initialise (double rate, int blockSize) + bool initialise (double rate, int blockSize) { - refreshParameterList(); - updateNumChannels(); producesMidiMessages = canProduceMidiOutput(); - setPlayConfigDetails ((int) (numInputBusChannels * numInputBusses), - (int) (numOutputBusChannels * numOutputBusses), - rate, blockSize); + setRateAndBufferSizeDetails (rate, blockSize); setLatencySamples (0); + refreshParameterList(); + createPluginCallbacks(); - if (parameters.size() == 0) + return true; + } + + //============================================================================== + bool canAddBus (bool isInput) const override { return isBusCountWritable (isInput); } + bool canRemoveBus (bool isInput) const override { return isBusCountWritable (isInput); } + + bool canApplyBusCountChange (bool isInput, bool isAdding, BusProperties& outProperties) override + { + int currentCount = getBusCount (isInput); + int newCount = currentCount + (isAdding ? 1 : -1); + AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + + if (AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ElementCount, scope, 0, &newCount, sizeof (newCount)) == noErr) { - // some plugins crash if initialiseAudioUnit() is called too soon (sigh..), so we'll - // only call it here if it seems like they it's one of the awkward plugins that can - // only create their parameters after it has been initialised. - initialiseAudioUnit(); - refreshParameterList(); + getBusProperties (isInput, currentCount, outProperties.busName, outProperties.defaultLayout); + outProperties.isActivatedByDefault = true; + updateSupportedLayouts(); + + return true; } - setPluginCallbacks(); + return false; + } + + //============================================================================== + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + if (layouts == getBusesLayout()) + return true; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const Array& requestedLayouts = (isInput ? layouts.inputBuses : layouts.outputBuses); + const Array& oppositeRequestedLayouts = (isInput ? layouts.outputBuses : layouts.inputBuses); + const Array >& supported = (isInput ? supportedInLayouts : supportedOutLayouts); + const int n = getBusCount (isInput); + + for (int busIdx = 0; busIdx < n; ++busIdx) + { + const AudioChannelSet& requested = requestedLayouts.getReference (busIdx); + const int oppositeBusIdx = jmin (getBusCount (! isInput) - 1, busIdx); + const bool hasOppositeBus = (oppositeBusIdx >= 0); + const AudioChannelSet oppositeRequested = (hasOppositeBus ? oppositeRequestedLayouts.getReference (oppositeBusIdx) : AudioChannelSet()); + const Array& possible = supported.getReference (busIdx); + + if (requested.isDisabled()) + return false; + + if (possible.size() > 0 && ! possible.contains (requested)) + return false; + + int i; + for (i = 0; i < numChannelInfos; ++i) + { + const AUChannelInfo& info = channelInfos[i]; + const SInt16& thisChannels = (isInput ? info.inChannels : info.outChannels); + const SInt16& opChannels = (isInput ? info.outChannels : info.inChannels); + + // this bus + if (thisChannels == 0) continue; + else if (thisChannels > 0 && requested.size() != thisChannels) continue; + else if (thisChannels < -2 && requested.size() > (thisChannels * -1)) continue; + + // opposite bus + if (opChannels == 0 && hasOppositeBus) continue; + else if (opChannels > 0 && oppositeRequested.size() != opChannels) continue; + else if (opChannels < -2 && oppositeRequested.size() > (opChannels * -1)) continue; + + // both buses + if (thisChannels == -2 && opChannels == -2) continue; + if (thisChannels == -1 && opChannels == -1) + { + int numOppositeBuses = getBusCount (! isInput); + int j; + for (j = 0; j < numOppositeBuses; ++j) + if (requested.size() != oppositeRequestedLayouts.getReference (j).size()) + break; + + if (j < numOppositeBuses) continue; + } + + break; + } + + if (i >= numChannelInfos) + return false; + } + } + + return true; + } + + bool syncBusLayouts (const BusesLayout& layouts, bool isInitialized, bool& layoutHasChanged) const + { + layoutHasChanged = false; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + const int n = getBusCount (isInput); + + if (getElementCount (scope) != n && isBusCountWritable (isInput)) + { + OSStatus err; + UInt32 newCount = static_cast (n); + layoutHasChanged = true; + + err = AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ElementCount, scope, 0, &newCount, sizeof (newCount)); + jassert (err == noErr); + } + + for (int i = 0; i < n; ++i) + { + Float64 sampleRate; + UInt32 sampleRateSize = sizeof (sampleRate); + + AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, static_cast (i), &sampleRate, &sampleRateSize); + + const AudioChannelSet& set = layouts.getChannelSet (isInput, i); + const int requestedNumChannels = set.size(); + + { + AudioStreamBasicDescription stream; + UInt32 dataSize = sizeof (stream); + OSStatus err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, static_cast (i), &stream, &dataSize); + if (err != noErr || dataSize < sizeof (stream)) + return false; + + const int actualNumChannels = static_cast (stream.mChannelsPerFrame); + + if (actualNumChannels != requestedNumChannels) + { + layoutHasChanged = true; + zerostruct (stream); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) + stream.mSampleRate = sampleRate; + stream.mFormatID = kAudioFormatLinearPCM; + stream.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagsNativeEndian; + stream.mFramesPerPacket = 1; + stream.mBytesPerPacket = 4; + stream.mBytesPerFrame = 4; + stream.mBitsPerChannel = 32; + stream.mChannelsPerFrame = static_cast (requestedNumChannels); + + err = AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, static_cast (i), &stream, sizeof (stream)); + if (err != noErr) return false; + } + } + + if (! set.isDiscreteLayout()) + { + const AudioChannelLayoutTag requestedTag = AudioUnitHelpers::ChannelSetToCALayoutTag (set); + + AudioChannelLayout layout; + const UInt32 minDataSize = sizeof (layout) - sizeof (AudioChannelDescription); + UInt32 dataSize = minDataSize; + + AudioChannelLayoutTag actualTag = kAudioChannelLayoutTag_Unknown; + OSStatus err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, scope, static_cast (i), &layout, &dataSize); + bool supportsLayouts = (err == noErr && dataSize >= minDataSize); + + if (supportsLayouts) + { + const UInt32 expectedSize = + minDataSize + (sizeof (AudioChannelDescription) * layout.mNumberChannelDescriptions); + + HeapBlock layoutBuffer; + layoutBuffer.malloc (1, expectedSize); + dataSize = expectedSize; + + err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, scope, + static_cast (i), layoutBuffer.getData(), &dataSize); + + if (err != noErr || dataSize < expectedSize) + return false; + + actualTag = AudioUnitHelpers::ChannelSetToCALayoutTag (AudioUnitHelpers::CoreAudioChannelLayoutToJuceType (layout)); + } + + if (actualTag != requestedTag) + { + zerostruct (layout); + layout.mChannelLayoutTag = requestedTag; + + err = AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, scope, static_cast (i), &layout, minDataSize); + + // only bail out if the plug-in claims to support layouts + // See AudioUnit headers on kAudioUnitProperty_AudioChannelLayout + if (err != noErr && supportsLayouts && isInitialized) + return false; + } + } + } + } + + return true; + } + + bool canApplyBusesLayout (const BusesLayout& layouts) const override + { + // You cannot call setBusesLayout when the AudioProcessor is processing. + // Call releaseResources first! + jassert (! prepared); + + bool layoutHasChanged = false; + + if (! syncBusLayouts (layouts, false, layoutHasChanged)) + return false; + + // did anything actually change + if (layoutHasChanged) + { + bool success = (AudioUnitInitialize (audioUnit) == noErr); + + // Some plug-ins require the LayoutTag to be set after initialization + if (success) + success = syncBusLayouts (layouts, true, layoutHasChanged); + + AudioUnitUninitialize (audioUnit); + + if (! success) + // make sure that the layout is back to it's original state + syncBusLayouts (getBusesLayout(), false, layoutHasChanged); + + return success; + } + + return true; } //============================================================================== @@ -410,76 +683,75 @@ public: if (audioUnit != nullptr) { releaseResources(); - updateNumChannels(); - - Float64 sampleRateIn = 0, sampleRateOut = 0; - UInt32 sampleRateSize = sizeof (sampleRateIn); - const Float64 sr = newSampleRate; - for (AudioUnitElement i = 0; i < numInputBusses; ++i) + for (int dir = 0; dir < 2; ++dir) { - AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, i, &sampleRateIn, &sampleRateSize); + const bool isInput = (dir == 0); + const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + const int n = getBusCount (isInput); - if (sampleRateIn != sr) - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, i, &sr, sizeof (sr)); - } + for (int i = 0; i < n; ++i) + { + Float64 sampleRate; + UInt32 sampleRateSize = sizeof (sampleRate); + const Float64 sr = newSampleRate; - for (AudioUnitElement i = 0; i < numOutputBusses; ++i) - { - AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, i, &sampleRateOut, &sampleRateSize); + AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, static_cast (i), &sampleRate, &sampleRateSize); + + if (sampleRate != sr) + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, static_cast (i), &sr, sizeof (sr)); + + if (isInput) + { + AURenderCallbackStruct info; + zerostruct (info); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) + + info.inputProcRefCon = this; + info.inputProc = renderGetInputCallback; - if (sampleRateOut != sr) - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, i, &sr, sizeof (sr)); + AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, + static_cast (i), &info, sizeof (info)); + } + else + { + outputBufferList.add (new AUBuffer (static_cast (getChannelCountOfBus (false, i)))); + } + } } UInt32 frameSize = (UInt32) estimatedSamplesPerBlock; AudioUnitSetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &frameSize, sizeof (frameSize)); - setPlayConfigDetails ((int) (numInputBusChannels * numInputBusses), - (int) (numOutputBusChannels * numOutputBusses), - (double) newSampleRate, estimatedSamplesPerBlock); + setRateAndBufferSizeDetails ((double) newSampleRate, estimatedSamplesPerBlock); updateLatency(); - { - AudioStreamBasicDescription stream; - zerostruct (stream); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) - stream.mSampleRate = sr; - stream.mFormatID = kAudioFormatLinearPCM; - stream.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagsNativeEndian; - stream.mFramesPerPacket = 1; - stream.mBytesPerPacket = 4; - stream.mBytesPerFrame = 4; - stream.mBitsPerChannel = 32; - stream.mChannelsPerFrame = numInputBusChannels; - - for (AudioUnitElement i = 0; i < numInputBusses; ++i) - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, i, &stream, sizeof (stream)); - - stream.mChannelsPerFrame = numOutputBusChannels; - - for (AudioUnitElement i = 0; i < numOutputBusses; ++i) - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Output, i, &stream, sizeof (stream)); - } - - if (numOutputBusses != 0 && numOutputBusChannels != 0) - outputBufferList.calloc (numOutputBusses, getAudioBufferSizeInBytes()); - zerostruct (timeStamp); timeStamp.mSampleTime = 0; - timeStamp.mHostTime = AudioGetCurrentHostTime(); + timeStamp.mHostTime = GetCurrentHostTime (0, newSampleRate, isAUv3); timeStamp.mFlags = kAudioTimeStampSampleTimeValid | kAudioTimeStampHostTimeValid; currentBuffer = nullptr; wasPlaying = false; - resetBusses(); + resetBuses(); + + bool ignore; - jassert (! prepared); - initialiseAudioUnit(); + if (! syncBusLayouts (getBusesLayout(), false, ignore)) + return; + + prepared = (AudioUnitInitialize (audioUnit) == noErr); + + if (prepared) + { + if (! syncBusLayouts (getBusesLayout(), true, ignore)) + { + prepared = false; + AudioUnitUninitialize (audioUnit); + } + } } } @@ -488,10 +760,10 @@ public: if (prepared) { AudioUnitUninitialize (audioUnit); - resetBusses(); + resetBuses(); AudioUnitReset (audioUnit, kAudioUnitScope_Global, 0); - outputBufferList.free(); + outputBufferList.clear(); currentBuffer = nullptr; prepared = false; } @@ -499,18 +771,10 @@ public: incomingMidi.clear(); } - bool initialiseAudioUnit() + void resetBuses() { - if (! prepared) - prepared = (AudioUnitInitialize (audioUnit) == noErr); - - return prepared; - } - - void resetBusses() - { - for (AudioUnitElement i = 0; i < numInputBusses; ++i) AudioUnitReset (audioUnit, kAudioUnitScope_Input, i); - for (AudioUnitElement i = 0; i < numOutputBusses; ++i) AudioUnitReset (audioUnit, kAudioUnitScope_Output, i); + for (int i = 0; i < getBusCount (true); ++i) AudioUnitReset (audioUnit, kAudioUnitScope_Input, static_cast (i)); + for (int i = 0; i < getBusCount (false); ++i) AudioUnitReset (audioUnit, kAudioUnitScope_Output, static_cast (i)); } void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override @@ -519,19 +783,21 @@ public: if (prepared) { - timeStamp.mHostTime = AudioGetCurrentHostTime(); + timeStamp.mHostTime = GetCurrentHostTime (numSamples, getSampleRate(), isAUv3); - for (AudioUnitElement i = 0; i < numOutputBusses; ++i) + int chIdx = 0; + const int numOutputBuses = getBusCount (false); + for (int i = 0; i < numOutputBuses; ++i) { - if (AudioBufferList* const abl = getAudioBufferListForBus(i)) + if (AUBuffer* buf = outputBufferList[i]) { - abl->mNumberBuffers = numOutputBusChannels; + AudioBufferList& abl = *buf; - for (AudioUnitElement j = 0; j < numOutputBusChannels; ++j) + for (AudioUnitElement j = 0; j < abl.mNumberBuffers; ++j) { - abl->mBuffers[j].mNumberChannels = 1; - abl->mBuffers[j].mDataByteSize = (UInt32) (sizeof (float) * (size_t) numSamples); - abl->mBuffers[j].mData = buffer.getWritePointer ((int) (i * numOutputBusChannels + j)); + abl.mBuffers[j].mNumberChannels = 1; + abl.mBuffers[j].mDataByteSize = (UInt32) (sizeof (float) * (size_t) numSamples); + abl.mBuffers[j].mData = buffer.getWritePointer (chIdx++); } } } @@ -557,10 +823,12 @@ public: } - for (AudioUnitElement i = 0; i < numOutputBusses; ++i) + for (int i = 0; i < numOutputBuses; ++i) { AudioUnitRenderActionFlags flags = 0; - AudioUnitRender (audioUnit, &flags, &timeStamp, i, (UInt32) numSamples, getAudioBufferListForBus (i)); + + if (AUBuffer* buf = outputBufferList[i]) + AudioUnitRender (audioUnit, &flags, &timeStamp, static_cast (i), (UInt32) numSamples, buf->bufferList.getData()); } timeStamp.mSampleTime += numSamples; @@ -584,6 +852,30 @@ public: bool hasEditor() const override { return true; } AudioProcessorEditor* createEditor() override; + static AudioProcessor::BusesProperties getBusesProperties (AudioComponentInstance comp) + { + AudioProcessor::BusesProperties busProperties; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const int n = getElementCount (comp, isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output); + + for (int i = 0; i < n; ++i) + { + String busName; + AudioChannelSet currentLayout; + + getBusProperties (comp, isInput, i, busName, currentLayout); + jassert (! currentLayout.isDisabled()); + + busProperties.addBus (isInput, busName, currentLayout, true); + } + } + + return busProperties; + } + //============================================================================== const String getInputChannelName (int index) const override { @@ -647,6 +939,7 @@ public: void sendParameterChangeEvent (int index) { + #if JUCE_MAC jassert (audioUnit != nullptr); const ParamInfo& p = *parameters.getUnchecked (index); @@ -659,10 +952,14 @@ public: ev.mArgument.mParameter.mElement = 0; AUEventListenerNotify (nullptr, nullptr, &ev); + #else + ignoreUnused (index); + #endif } void sendAllParametersChangedEvents() { + #if JUCE_MAC jassert (audioUnit != nullptr); AudioUnitParameter param; @@ -670,6 +967,7 @@ public: param.mParameterID = kAUParameterListener_AnyParameter; AUParameterListenerNotify (nullptr, nullptr, ¶m); + #endif } const String getParameterName (int index) override @@ -814,8 +1112,6 @@ public: if (propertyList != 0) { - initialiseAudioUnit(); - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &propertyList, sizeof (propertyList)); @@ -904,18 +1200,42 @@ private: friend class AudioUnitPluginFormat; AudioComponentDescription componentDesc; + AudioComponent auComponent; String pluginName, manufacturer, version; String fileOrIdentifier; CriticalSection lock; - bool wantsMidiMessages, producesMidiMessages, wasPlaying, prepared; + bool wantsMidiMessages, producesMidiMessages, wasPlaying, prepared, isAUv3; - HeapBlock outputBufferList; + struct AUBuffer + { + AUBuffer (size_t numBuffers) + { + bufferList.calloc (1, (sizeof (AudioBufferList) - sizeof (::AudioBuffer)) + (sizeof (::AudioBuffer) * numBuffers)); + AudioBufferList& buffer = *bufferList.getData(); + + buffer.mNumberBuffers = static_cast (numBuffers); + } + + operator AudioBufferList&() + { + return *bufferList.getData(); + } + + HeapBlock bufferList; + }; + + OwnedArray outputBufferList; AudioTimeStamp timeStamp; AudioSampleBuffer* currentBuffer; - AudioUnitElement numInputBusChannels, numOutputBusChannels, numInputBusses, numOutputBusses; + Array > supportedInLayouts, supportedOutLayouts; + + int numChannelInfos; + HeapBlock channelInfos; AudioUnit audioUnit; + #if JUCE_MAC AUEventListenerRef eventListenerRef; + #endif struct ParamInfo { @@ -931,22 +1251,11 @@ private: CriticalSection midiInLock; MidiBuffer incomingMidi; - void setPluginCallbacks() + void createPluginCallbacks() { if (audioUnit != nullptr) { - { - AURenderCallbackStruct info; - zerostruct (info); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) - - info.inputProcRefCon = this; - info.inputProc = renderGetInputCallback; - - for (AudioUnitElement i = 0; i < numInputBusses; ++i) - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, i, &info, sizeof (info)); - } - + #if JUCE_MAC if (producesMidiMessages) { AUMIDIOutputCallbackStruct info; @@ -958,6 +1267,7 @@ private: producesMidiMessages = (AudioUnitSetProperty (audioUnit, kAudioUnitProperty_MIDIOutputCallback, kAudioUnitScope_Global, 0, &info, sizeof (info)) == noErr); } + #endif { HostCallbackInfo info; @@ -971,7 +1281,7 @@ private: AudioUnitSetProperty (audioUnit, kAudioUnitProperty_HostCallbacks, kAudioUnitScope_Global, 0, &info, sizeof (info)); } - + #if JUCE_MAC AUEventListenerCreate (eventListenerCallback, this, CFRunLoopGetMain(), kCFRunLoopDefaultMode, 0, 0, &eventListenerRef); @@ -996,16 +1306,18 @@ private: addPropertyChangeListener (kAudioUnitProperty_PresentPreset); addPropertyChangeListener (kAudioUnitProperty_ParameterList); addPropertyChangeListener (kAudioUnitProperty_Latency); + #endif } } + #if JUCE_MAC void addPropertyChangeListener (AudioUnitPropertyID type) const { + AudioUnitEvent event; event.mEventType = kAudioUnitEvent_PropertyChange; event.mArgument.mProperty.mPropertyID = type; event.mArgument.mProperty.mAudioUnit = audioUnit; - event.mArgument.mProperty.mPropertyID = kAudioUnitProperty_PresentPreset; event.mArgument.mProperty.mScope = kAudioUnitScope_Global; event.mArgument.mProperty.mElement = 0; AUEventListenerAddEventType (eventListenerRef, nullptr, &event); @@ -1013,28 +1325,39 @@ private: void eventCallback (const AudioUnitEvent& event, AudioUnitParameterValue newValue) { + int paramIndex = -1; + + if (event.mEventType == kAudioUnitEvent_ParameterValueChange + || event.mEventType == kAudioUnitEvent_BeginParameterChangeGesture + || event.mEventType == kAudioUnitEvent_EndParameterChangeGesture) + { + for (paramIndex = 0; paramIndex < parameters.size(); ++paramIndex) + { + const ParamInfo& p = *parameters.getUnchecked(paramIndex); + + if (p.paramID == event.mArgument.mParameter.mParameterID) + break; + } + + if (! isPositiveAndBelow (paramIndex, parameters.size())) + return; + } + switch (event.mEventType) { case kAudioUnitEvent_ParameterValueChange: - for (int i = 0; i < parameters.size(); ++i) { - const ParamInfo& p = *parameters.getUnchecked(i); - - if (p.paramID == event.mArgument.mParameter.mParameterID) - { - sendParamChangeMessageToListeners (i, (newValue - p.minValue) / (p.maxValue - p.minValue)); - break; - } + const ParamInfo& p = *parameters.getUnchecked(paramIndex); + sendParamChangeMessageToListeners (paramIndex, (newValue - p.minValue) / (p.maxValue - p.minValue)); } - break; case kAudioUnitEvent_BeginParameterChangeGesture: - beginParameterChangeGesture ((int) event.mArgument.mParameter.mParameterID); + beginParameterChangeGesture (paramIndex); break; case kAudioUnitEvent_EndParameterChangeGesture: - endParameterChangeGesture ((int) event.mArgument.mParameter.mParameterID); + endParameterChangeGesture (paramIndex); break; default: @@ -1055,6 +1378,7 @@ private: jassert (event != nullptr); static_cast (userRef)->eventCallback (*event, value); } + #endif //============================================================================== OSStatus renderGetInput (AudioUnitRenderActionFlags*, @@ -1067,15 +1391,17 @@ private: { // if this ever happens, might need to add extra handling jassert (inNumberFrames == (UInt32) currentBuffer->getNumSamples()); + AudioSampleBuffer buffer = + (static_cast (inBusNumber) < getBusCount (true) + ? getBusBuffer (*currentBuffer, true, static_cast (inBusNumber)) + : AudioSampleBuffer()); - for (UInt32 i = 0; i < ioData->mNumberBuffers; ++i) + for (int i = 0; i < static_cast (ioData->mNumberBuffers); ++i) { - const int bufferChannel = (int) (inBusNumber * numInputBusChannels + i); - - if (bufferChannel < currentBuffer->getNumChannels()) + if (i < buffer.getNumChannels()) { memcpy (ioData->mBuffers[i].mData, - currentBuffer->getReadPointer (bufferChannel), + buffer.getReadPointer (i), sizeof (float) * inNumberFrames); } else @@ -1229,89 +1555,200 @@ private: } //============================================================================== - size_t getAudioBufferSizeInBytes() const noexcept + static inline UInt64 GetCurrentHostTime (int numSamples, double sampleRate, bool isAUv3) noexcept + { + #if ! JUCE_IOS + if (! isAUv3) + return AudioGetCurrentHostTime(); + #else + ignoreUnused (isAUv3); + #endif + + UInt64 currentTime = mach_absolute_time(); + static mach_timebase_info_data_t sTimebaseInfo = {0, 0}; + + if (sTimebaseInfo.denom == 0) + mach_timebase_info (&sTimebaseInfo); + + double bufferNanos = static_cast (numSamples) * 1.0e9 / sampleRate; + UInt64 bufferTicks = static_cast (std::ceil (bufferNanos * (static_cast (sTimebaseInfo.denom) / static_cast (sTimebaseInfo.numer)))); + currentTime += bufferTicks; + + return currentTime; + } + + bool isBusCountWritable (bool isInput) const noexcept { - return offsetof (AudioBufferList, mBuffers) + (sizeof (::AudioBuffer) * numOutputBusChannels); + UInt32 countSize; + Boolean writable; + OSStatus err; + AudioUnitScope scope = (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output); + + err = AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_ElementCount, scope, 0, &countSize, &writable); + + return (err == noErr && writable != 0 && countSize == sizeof (UInt32)); } - AudioBufferList* getAudioBufferListForBus (AudioUnitElement busIndex) const noexcept + //============================================================================== + int getElementCount (AudioUnitScope scope) const noexcept { - return addBytesToPointer (outputBufferList.getData(), getAudioBufferSizeInBytes() * busIndex); + return static_cast (getElementCount (audioUnit, scope)); } - AudioUnitElement getElementCount (AudioUnitScope scope) const noexcept + static int getElementCount (AudioUnit comp, AudioUnitScope scope) noexcept { UInt32 count; UInt32 countSize = sizeof (count); - if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ElementCount, scope, 0, &count, &countSize) != noErr - || countSize == 0) - count = 1; + OSStatus err = AudioUnitGetProperty (comp, kAudioUnitProperty_ElementCount, scope, 0, &count, &countSize); + jassert (err == noErr); + ignoreUnused (err); - return count; + return static_cast (count); } - void updateNumChannels() + //============================================================================== + void getBusProperties (bool isInput, int busIdx, String& busName, AudioChannelSet& currentLayout) const { - numInputBusses = getElementCount (kAudioUnitScope_Input); - numOutputBusses = getElementCount (kAudioUnitScope_Output); + getBusProperties (audioUnit, isInput, busIdx, busName, currentLayout); + } - AUChannelInfo supportedChannels [128]; - UInt32 supportedChannelsSize = sizeof (supportedChannels); + static void getBusProperties (AudioUnit comp, bool isInput, int busIdx, String& busName, AudioChannelSet& currentLayout) + { + const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + busName = (isInput ? "Input #" : "Output #") + String (busIdx + 1); - if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, - 0, supportedChannels, &supportedChannelsSize) == noErr - && supportedChannelsSize > 0) { - int explicitNumIns = 0; - int explicitNumOuts = 0; - int maximumNumIns = 0; - int maximumNumOuts = 0; + CFStringRef busNameCF = nullptr; + UInt32 propertySize = sizeof (busNameCF); - for (int i = 0; i < (int) (supportedChannelsSize / sizeof (AUChannelInfo)); ++i) + if (AudioUnitGetProperty (comp, kAudioUnitProperty_ElementName, scope, static_cast (busIdx), &busNameCF, &propertySize) == noErr + && busNameCF != nullptr) { - const int inChannels = (int) supportedChannels[i].inChannels; - const int outChannels = (int) supportedChannels[i].outChannels; + busName = nsStringToJuce ((NSString*) busNameCF); + CFRelease (busNameCF); + } - if (inChannels < 0) - maximumNumIns = jmin (maximumNumIns, inChannels); - else - explicitNumIns = jmax (explicitNumIns, inChannels); + { + AudioChannelLayout auLayout; + propertySize = sizeof (auLayout); - if (outChannels < 0) - maximumNumOuts = jmin (maximumNumOuts, outChannels); - else - explicitNumOuts = jmax (explicitNumOuts, outChannels); + if (AudioUnitGetProperty (comp, kAudioUnitProperty_AudioChannelLayout, scope, static_cast (busIdx), &auLayout, &propertySize) == noErr) + currentLayout = AudioUnitHelpers::CoreAudioChannelLayoutToJuceType (auLayout); } - if ((maximumNumIns == -1 && maximumNumOuts == -1) // (special meaning: any number of ins/outs, as long as they match) - || (maximumNumIns == -2 && maximumNumOuts == -1) // (special meaning: any number of ins/outs, even if they don't match) - || (maximumNumIns == -1 && maximumNumOuts == -2)) + if (currentLayout.isDisabled()) { - numInputBusChannels = numOutputBusChannels = 2; + AudioStreamBasicDescription descr; + propertySize = sizeof (descr); + + if (AudioUnitGetProperty (comp, kAudioUnitProperty_StreamFormat, scope, static_cast (busIdx), &descr, &propertySize) == noErr) + currentLayout = AudioChannelSet::canonicalChannelSet (static_cast (descr.mChannelsPerFrame)); } - else + } + } + + //============================================================================== + void numBusesChanged() override + { + updateSupportedLayouts(); + } + + void updateSupportedLayouts() + { + supportedInLayouts.clear(); + supportedOutLayouts.clear(); + numChannelInfos = 0; + channelInfos.free(); + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const AudioUnitScope scope = isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output; + const int n = getElementCount (scope); + + for (int busIdx = 0; busIdx < n; ++busIdx) { - numInputBusChannels = (AudioUnitElement) explicitNumIns; - numOutputBusChannels = (AudioUnitElement) explicitNumOuts; + Array supported; + AudioChannelSet currentLayout; - if (maximumNumIns == -1 || (maximumNumIns < 0 && explicitNumIns <= -maximumNumIns)) - numInputBusChannels = 2; + { + AudioChannelLayout auLayout; + UInt32 propertySize = sizeof (auLayout); + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, scope, static_cast (busIdx), &auLayout, &propertySize) == noErr) + currentLayout = AudioUnitHelpers::CoreAudioChannelLayoutToJuceType (auLayout); + } - if (maximumNumOuts == -1 || (maximumNumOuts < 0 && explicitNumOuts <= -maximumNumOuts)) - numOutputBusChannels = 2; + if (currentLayout.isDisabled()) + { + AudioStreamBasicDescription descr; + UInt32 propertySize = sizeof (descr); + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, scope, static_cast (busIdx), &descr, &propertySize) == noErr) + currentLayout = AudioChannelSet::canonicalChannelSet (static_cast (descr.mChannelsPerFrame)); + } + + supported.clear(); + { + UInt32 propertySize = 0; + Boolean writable; + + if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_SupportedChannelLayoutTags, scope, static_cast (busIdx), &propertySize, &writable) == noErr + && propertySize > 0) + { + const size_t numElements = propertySize / sizeof (AudioChannelLayoutTag); + HeapBlock layoutTags (numElements); + propertySize = static_cast (sizeof (AudioChannelLayoutTag) * numElements); + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SupportedChannelLayoutTags, scope, + static_cast (busIdx), layoutTags.getData(), &propertySize) == noErr) + { + for (int j = 0; j < static_cast (numElements); ++j) + { + const AudioChannelLayoutTag tag = layoutTags[j]; + + if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) + supported.addIfNotAlreadyThere (AudioUnitHelpers::CALayoutTagToChannelSet (tag)); + } + + if (supported.size() > 0) + supported.addIfNotAlreadyThere (currentLayout); + } + } + } + + (isInput ? supportedInLayouts : supportedOutLayouts).add (supported); } } - else + { - // (this really means the plugin will take any number of ins/outs as long - // as they are the same) - numInputBusChannels = numOutputBusChannels = 2; + UInt32 propertySize = 0; + Boolean writable; + + if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, &propertySize, &writable) == noErr + && propertySize > 0) + { + numChannelInfos = propertySize / sizeof (AUChannelInfo); + channelInfos.malloc (static_cast (numChannelInfos)); + propertySize = static_cast (sizeof (AUChannelInfo) * static_cast (numChannelInfos)); + + if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, channelInfos.getData(), &propertySize) != noErr) + numChannelInfos = 0; + } + else + { + numChannelInfos = 1; + channelInfos.malloc (static_cast (numChannelInfos)); + channelInfos.getData()->inChannels = -1; + channelInfos.getData()->outChannels = -1; + } } } bool canProduceMidiOutput() { + #if JUCE_MAC UInt32 dataSize = 0; Boolean isWritable = false; @@ -1331,6 +1768,9 @@ private: } return false; + #else + return false; + #endif } bool supportsMPE() const override @@ -1363,10 +1803,15 @@ class AudioUnitPluginWindowCocoa : public AudioProcessorEditor public: AudioUnitPluginWindowCocoa (AudioUnitPluginInstance& p, bool createGenericViewIfNeeded) : AudioProcessorEditor (&p), - plugin (p) + plugin (p), waitingForViewCallback (false) { addAndMakeVisible (wrapper); + #if JUCE_SUPPORTS_AUv3 + viewControllerCallback = + CreateObjCBlock (this, &AudioUnitPluginWindowCocoa::requestViewControllerCallback); + #endif + setOpaque (true); setVisible (true); setSize (100, 100); @@ -1376,7 +1821,7 @@ public: ~AudioUnitPluginWindowCocoa() { - if (isValid()) + if (wrapper.getView() != nil) { wrapper.setVisible (false); removeChildComponent (&wrapper); @@ -1385,7 +1830,24 @@ public: } } - bool isValid() const { return wrapper.getView() != nil; } + #if JUCE_SUPPORTS_AUv3 + void embedViewController (JUCE_IOS_MAC_VIEW* pluginView, const CGSize& size) + { + wrapper.setView (pluginView); + waitingForViewCallback = false; + + #if JUCE_MAC + ignoreUnused (size); + if (pluginView != nil) + wrapper.resizeToFitView(); + #else + [pluginView setBounds: CGRectMake (0.f, 0.f, static_cast (size.width), static_cast (size.height))]; + wrapper.setSize (static_cast (size.width), static_cast (size.height)); + #endif + } + #endif + + bool isValid() const { return wrapper.getView() != nil || waitingForViewCallback; } void paint (Graphics& g) override { @@ -1403,19 +1865,24 @@ public: } private: - AudioUnitPluginInstance& plugin; + AudioUnitPluginInstance& plugin; AutoResizingNSViewComponent wrapper; + #if JUCE_SUPPORTS_AUv3 + typedef void (^ViewControllerCallbackBlock)(AUViewControllerBase *); + ObjCBlock viewControllerCallback; + #endif + + bool waitingForViewCallback; + bool createView (const bool createGenericViewIfNeeded) { - if (! plugin.initialiseAudioUnit()) - return false; - - NSView* pluginView = nil; + JUCE_IOS_MAC_VIEW* pluginView = nil; UInt32 dataSize = 0; Boolean isWritable = false; + #if JUCE_MAC if (AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr && dataSize != 0 @@ -1450,7 +1917,32 @@ private: CFRelease (info->mCocoaAUViewBundleLocation); } } + #endif + + dataSize = 0; + isWritable = false; + + #if JUCE_SUPPORTS_AUv3 + if (AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_RequestViewController, kAudioUnitScope_Global, + 0, &dataSize, &isWritable) == noErr + && dataSize == sizeof (ViewControllerCallbackBlock) + && AudioUnitGetPropertyInfo (plugin.audioUnit, kAudioUnitProperty_RequestViewController, kAudioUnitScope_Global, + 0, &dataSize, &isWritable) == noErr) + { + waitingForViewCallback = true; + ViewControllerCallbackBlock callback; + callback = viewControllerCallback; + + ViewControllerCallbackBlock* info = &callback; + + if (noErr == AudioUnitSetProperty (plugin.audioUnit, kAudioUnitProperty_RequestViewController, kAudioUnitScope_Global, 0, info, dataSize)) + return true; + waitingForViewCallback = false; + } + #endif + + #if JUCE_MAC if (createGenericViewIfNeeded && (pluginView == nil)) { { @@ -1463,6 +1955,9 @@ private: pluginView = [[AUGenericView alloc] initWithAudioUnit: plugin.audioUnit]; } + #else + ignoreUnused (createGenericViewIfNeeded); + #endif wrapper.setView (pluginView); @@ -1471,6 +1966,41 @@ private: return pluginView != nil; } + + #if JUCE_SUPPORTS_AUv3 + void requestViewControllerCallback (AUViewControllerBase* controller) + { + auto nsSize = [controller preferredContentSize]; + auto viewSize = CGSizeMake (nsSize.width, nsSize.height); + + if (! MessageManager::getInstance()->isThisTheMessageThread()) + { + struct AsyncViewControllerCallback : public CallbackMessage + { + AudioUnitPluginWindowCocoa* owner; + JUCE_IOS_MAC_VIEW* controllerView; + CGSize size; + + AsyncViewControllerCallback (AudioUnitPluginWindowCocoa* plugInWindow, JUCE_IOS_MAC_VIEW* inView, + const CGSize& preferredSize) + : owner (plugInWindow), controllerView ([inView retain]), size (preferredSize) + {} + + void messageCallback() override + { + owner->embedViewController (controllerView, size); + [controllerView release]; + } + }; + + (new AsyncViewControllerCallback (this, [controller view], viewSize))->post(); + } + else + { + embedViewController ([controller view], viewSize); + } + } + #endif }; #if JUCE_SUPPORT_CARBON @@ -1654,6 +2184,10 @@ void AudioUnitPluginFormat::findAllTypesForFile (OwnedArray& desc.fileOrIdentifier = fileOrIdentifier; desc.uid = 0; + if (MessageManager::getInstance()->isThisTheMessageThread() + && requiresUnblockedMessageThreadDuringCreation (desc)) + return; + try { ScopedPointer createdInstance (createInstanceFromDescription (desc, 44100.0, 512)); @@ -1667,23 +2201,141 @@ void AudioUnitPluginFormat::findAllTypesForFile (OwnedArray& } } -AudioPluginInstance* AudioUnitPluginFormat::createInstanceFromDescription (const PluginDescription& desc, double rate, int blockSize) +void AudioUnitPluginFormat::createPluginInstance (const PluginDescription& desc, + double rate, + int blockSize, + void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) { + using namespace AudioUnitFormatHelpers; + if (fileMightContainThisPluginType (desc.fileOrIdentifier)) { - ScopedPointer result (new AudioUnitPluginInstance (desc.fileOrIdentifier)); - if (result->audioUnit != nullptr) + String pluginName, version, manufacturer; + AudioComponentDescription componentDesc; + AudioComponent auComponent; + String errMessage = NEEDS_TRANS ("Cannot find AudioUnit from description"); + + if ((! getComponentDescFromIdentifier (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer)) + && (! getComponentDescFromFile (desc.fileOrIdentifier, componentDesc, pluginName, version, manufacturer))) + { + callback (userData, nullptr, errMessage); + return; + } + + if ((auComponent = AudioComponentFindNext (0, &componentDesc)) == nullptr) + { + callback (userData, nullptr, errMessage); + return; + } + + if (AudioComponentGetDescription (auComponent, &componentDesc) != noErr) { - result->initialise (rate, blockSize); - return result.release(); + callback (userData, nullptr, errMessage); + return; } + + struct AUAsyncInitializationCallback + { + #if JUCE_SUPPORTS_AUv3 + typedef void (^AUCompletionCallbackBlock)(AudioComponentInstance, OSStatus); + #endif + + AUAsyncInitializationCallback (double inSampleRate, int inFramesPerBuffer, + void* inUserData, void (*inOriginalCallback) (void*, AudioPluginInstance*, const String&)) + : sampleRate (inSampleRate), framesPerBuffer (inFramesPerBuffer), + passUserData (inUserData), originalCallback (inOriginalCallback) + { + #if JUCE_SUPPORTS_AUv3 + block = CreateObjCBlock (this, &AUAsyncInitializationCallback::completion); + #endif + } + + #if JUCE_SUPPORTS_AUv3 + AUCompletionCallbackBlock getBlock() noexcept { return block; } + #endif + + void completion (AudioComponentInstance audioUnit, OSStatus err) + { + if (err == noErr) + { + ScopedPointer instance (new AudioUnitPluginInstance (audioUnit)); + + if (instance->initialise (sampleRate, framesPerBuffer)) + originalCallback (passUserData, instance.release(), StringRef()); + else + originalCallback (passUserData, nullptr, + NEEDS_TRANS ("Unable to initialise the AudioUnit plug-in")); + } + else + { + String errMsg = NEEDS_TRANS ("An OS error occurred during initialisation of the plug-in (XXX)"); + originalCallback (passUserData, nullptr, errMsg.replace ("XXX", String (err))); + } + + delete this; + } + + double sampleRate; + int framesPerBuffer; + void* passUserData; + void (*originalCallback) (void*, AudioPluginInstance*, const String&); + + #if JUCE_SUPPORTS_AUv3 + ObjCBlock block; + #endif + }; + + AUAsyncInitializationCallback* callbackBlock + = new AUAsyncInitializationCallback (rate, blockSize, userData, callback); + + #if JUCE_SUPPORTS_AUv3 + //============================================================================== + bool isAUv3 = ((componentDesc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0); + + if (isAUv3) + { + AudioComponentInstantiate (auComponent, kAudioComponentInstantiation_LoadOutOfProcess, + callbackBlock->getBlock()); + + return; + } + #endif // JUCE_SUPPORTS_AUv3 + + AudioComponentInstance audioUnit; + OSStatus err = AudioComponentInstanceNew(auComponent, &audioUnit); + callbackBlock->completion (err != noErr ? nullptr : audioUnit, err); + } + else + { + callback (userData, nullptr, NEEDS_TRANS ("Plug-in description is not an AudioUnit plug-in")); } +} + +bool AudioUnitPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription& desc) const noexcept +{ + #if JUCE_SUPPORTS_AUv3 + String pluginName, version, manufacturer; + AudioComponentDescription componentDesc; - return nullptr; + if (AudioUnitFormatHelpers::getComponentDescFromIdentifier (desc.fileOrIdentifier, componentDesc, + pluginName, version, manufacturer) + || AudioUnitFormatHelpers::getComponentDescFromFile (desc.fileOrIdentifier, componentDesc, + pluginName, version, manufacturer)) + { + if (AudioComponent auComp = AudioComponentFindNext (0, &componentDesc)) + if (AudioComponentGetDescription (auComp, &componentDesc) == noErr) + return ((componentDesc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0); + } + #else + ignoreUnused (desc); + #endif + + return false; } -StringArray AudioUnitPluginFormat::searchPathsForPlugins (const FileSearchPath&, bool /*recursive*/) +StringArray AudioUnitPluginFormat::searchPathsForPlugins (const FileSearchPath&, bool /*recursive*/, bool allowPluginsWhichRequireAsynchronousInstantiation) { StringArray result; AudioComponent comp = nullptr; @@ -1698,15 +2350,24 @@ StringArray AudioUnitPluginFormat::searchPathsForPlugins (const FileSearchPath&, if (comp == nullptr) break; - AudioComponentGetDescription (comp, &desc); + if (AudioComponentGetDescription (comp, &desc) != noErr) + continue; if (desc.componentType == kAudioUnitType_MusicDevice || desc.componentType == kAudioUnitType_MusicEffect || desc.componentType == kAudioUnitType_Effect || desc.componentType == kAudioUnitType_Generator - || desc.componentType == kAudioUnitType_Panner) + || desc.componentType == kAudioUnitType_Panner + || desc.componentType == kAudioUnitType_Mixer) { - result.add (AudioUnitFormatHelpers::createPluginIdentifier (desc)); + ignoreUnused (allowPluginsWhichRequireAsynchronousInstantiation); + + #if JUCE_SUPPORTS_AUv3 + bool isAUv3 = ((desc.componentFlags & kAudioComponentFlag_IsV3AudioUnit) != 0); + + if (allowPluginsWhichRequireAsynchronousInstantiation || ! isAUv3) + #endif + result.add (AudioUnitFormatHelpers::createPluginIdentifier (desc)); } } @@ -1723,7 +2384,7 @@ bool AudioUnitPluginFormat::fileMightContainThisPluginType (const String& fileOr const File f (File::createFileWithoutCheckingPath (fileOrIdentifier)); - return f.hasFileExtension (".component") + return (f.hasFileExtension (".component") || f.hasFileExtension (".appex")) && f.isDirectory(); } diff --git a/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp index 08d86f8ed..2add8e364 100644 --- a/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp @@ -610,11 +610,14 @@ void LADSPAPluginFormat::findAllTypesForFile (OwnedArray & re } } -AudioPluginInstance* LADSPAPluginFormat::createInstanceFromDescription (const PluginDescription& desc, - double sampleRate, int blockSize) +void LADSPAPluginFormat::createPluginInstance (const PluginDescription& desc, + double sampleRate, int blockSize, + void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) { ScopedPointer result; + if (fileMightContainThisPluginType (desc.fileOrIdentifier)) { File file (desc.fileOrIdentifier); @@ -639,7 +642,17 @@ AudioPluginInstance* LADSPAPluginFormat::createInstanceFromDescription (const Pl previousWorkingDirectory.setAsCurrentWorkingDirectory(); } - return result.release(); + String errorMsg; + + if (result == nullptr) + errorMsg = String (NEEDS_TRANS ("Unable to load XXX plug-in file")).replace ("XXX", "LADSPA"); + + callback (userData, result.release(), errorMsg); +} + +bool LADSPAPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept +{ + return false; } bool LADSPAPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) @@ -663,7 +676,7 @@ bool LADSPAPluginFormat::doesPluginStillExist (const PluginDescription& desc) return File::createFileWithoutCheckingPath (desc.fileOrIdentifier).exists(); } -StringArray LADSPAPluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive) +StringArray LADSPAPluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive, bool) { StringArray results; diff --git a/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.h b/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.h index 15c10957f..4a8a3ce9f 100644 --- a/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.h +++ b/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.h @@ -31,22 +31,28 @@ class JUCE_API LADSPAPluginFormat : public AudioPluginFormat { public: - //============================================================================== LADSPAPluginFormat(); ~LADSPAPluginFormat(); //============================================================================== String getName() const override { return "LADSPA"; } void findAllTypesForFile (OwnedArray&, const String& fileOrIdentifier) override; - AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, double, int) override; bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; bool pluginNeedsRescanning (const PluginDescription&) override; - StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive) override; + StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; bool doesPluginStillExist (const PluginDescription&) override; FileSearchPath getDefaultLocationsToSearch() override; bool canScanForPlugins() const override { return true; } +private: + //============================================================================== + void createPluginInstance (const PluginDescription&, double initialSampleRate, + int initialBufferSize, void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) override; + + bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; + private: void recursiveFileSearch (StringArray&, const File&, bool recursive); diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3Common.h b/source/modules/juce_audio_processors/format_types/juce_VST3Common.h index e44cb02e8..efc4e337c 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/source/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -67,17 +67,17 @@ inline juce::String toString (const Steinberg::char16* string) noexcept { re inline juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast (string)); } inline juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast (string)); } -static void toString128 (Steinberg::Vst::String128 result, const char* source) +inline void toString128 (Steinberg::Vst::String128 result, const char* source) { Steinberg::UString (result, 128).fromAscii (source); } -static void toString128 (Steinberg::Vst::String128 result, const juce::String& source) +inline void toString128 (Steinberg::Vst::String128 result, const juce::String& source) { Steinberg::UString (result, 128).fromAscii (source.toUTF8()); } -static Steinberg::Vst::TChar* toString (const juce::String& source) noexcept +inline Steinberg::Vst::TChar* toString (const juce::String& source) noexcept { return reinterpret_cast (source.toUTF16().getAddress()); } @@ -146,22 +146,22 @@ static inline Steinberg::Vst::Speaker getSpeakerType (AudioChannelSet::ChannelTy case AudioChannelSet::left: return kSpeakerL; case AudioChannelSet::right: return kSpeakerR; case AudioChannelSet::centre: return kSpeakerC; - case AudioChannelSet::subbass: return kSpeakerLfe; - case AudioChannelSet::surroundLeft: return kSpeakerLs; - case AudioChannelSet::surroundRight: return kSpeakerRs; - case AudioChannelSet::centreLeft: return kSpeakerLc; - case AudioChannelSet::centreRight: return kSpeakerRc; - case AudioChannelSet::surround: return kSpeakerS; - case AudioChannelSet::sideLeft: return kSpeakerSl; - case AudioChannelSet::sideRight: return kSpeakerSr; - case AudioChannelSet::topMiddle: return kSpeakerTm; + case AudioChannelSet::LFE: return kSpeakerLfe; + case AudioChannelSet::leftSurround: return kSpeakerLs; + case AudioChannelSet::rightSurround: return kSpeakerRs; + case AudioChannelSet::leftCentre: return kSpeakerLc; + case AudioChannelSet::rightCentre: return kSpeakerRc; + case AudioChannelSet::centreSurround: return kSpeakerCs; + case AudioChannelSet::leftSurroundRear: return kSpeakerSl; + case AudioChannelSet::rightSurroundRear: return kSpeakerSr; + case AudioChannelSet::topMiddle: return (1 << 11); /* kSpeakerTm */ case AudioChannelSet::topFrontLeft: return kSpeakerTfl; case AudioChannelSet::topFrontCentre: return kSpeakerTfc; case AudioChannelSet::topFrontRight: return kSpeakerTfr; case AudioChannelSet::topRearLeft: return kSpeakerTrl; case AudioChannelSet::topRearCentre: return kSpeakerTrc; case AudioChannelSet::topRearRight: return kSpeakerTrr; - case AudioChannelSet::subbass2: return kSpeakerLfe2; + case AudioChannelSet::LFE2: return kSpeakerLfe2; default: break; } @@ -177,30 +177,49 @@ static inline AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::Speak case kSpeakerL: return AudioChannelSet::left; case kSpeakerR: return AudioChannelSet::right; case kSpeakerC: return AudioChannelSet::centre; - case kSpeakerLfe: return AudioChannelSet::subbass; - case kSpeakerLs: return AudioChannelSet::surroundLeft; - case kSpeakerRs: return AudioChannelSet::surroundRight; - case kSpeakerLc: return AudioChannelSet::centreLeft; - case kSpeakerRc: return AudioChannelSet::centreRight; - case kSpeakerS: return AudioChannelSet::surround; - case kSpeakerSl: return AudioChannelSet::sideLeft; - case kSpeakerSr: return AudioChannelSet::sideRight; - case kSpeakerTm: return AudioChannelSet::topMiddle; + case kSpeakerLfe: return AudioChannelSet::LFE; + case kSpeakerLs: return AudioChannelSet::leftSurround; + case kSpeakerRs: return AudioChannelSet::rightSurround; + case kSpeakerLc: return AudioChannelSet::leftCentre; + case kSpeakerRc: return AudioChannelSet::rightCentre; + case kSpeakerCs: return AudioChannelSet::centreSurround; + case kSpeakerSl: return AudioChannelSet::leftSurroundRear; + case kSpeakerSr: return AudioChannelSet::rightSurroundRear; + case (1 << 11): return AudioChannelSet::topMiddle; /* kSpeakerTm */ case kSpeakerTfl: return AudioChannelSet::topFrontLeft; case kSpeakerTfc: return AudioChannelSet::topFrontCentre; case kSpeakerTfr: return AudioChannelSet::topFrontRight; case kSpeakerTrl: return AudioChannelSet::topRearLeft; case kSpeakerTrc: return AudioChannelSet::topRearCentre; case kSpeakerTrr: return AudioChannelSet::topRearRight; - case kSpeakerLfe2: return AudioChannelSet::subbass2; + case kSpeakerLfe2: return AudioChannelSet::LFE2; default: break; } return AudioChannelSet::unknown; } -static inline Steinberg::Vst::SpeakerArrangement getSpeakerArrangement (const AudioChannelSet& channels) noexcept +static inline Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const AudioChannelSet& channels) noexcept { + if (channels == AudioChannelSet::disabled()) return Steinberg::Vst::SpeakerArr::kEmpty; + else if (channels == AudioChannelSet::mono()) return Steinberg::Vst::SpeakerArr::kMono; + else if (channels == AudioChannelSet::stereo()) return Steinberg::Vst::SpeakerArr::kStereo; + else if (channels == AudioChannelSet::createLCR()) return Steinberg::Vst::SpeakerArr::k30Cine; + else if (channels == AudioChannelSet::createLRS()) return Steinberg::Vst::SpeakerArr::k30Music; + else if (channels == AudioChannelSet::createLCRS()) return Steinberg::Vst::SpeakerArr::k40Cine; + else if (channels == AudioChannelSet::create5point0()) return Steinberg::Vst::SpeakerArr::k50; + else if (channels == AudioChannelSet::create5point1()) return Steinberg::Vst::SpeakerArr::k51; + else if (channels == AudioChannelSet::create6point0()) return Steinberg::Vst::SpeakerArr::k60Cine; + else if (channels == AudioChannelSet::create6point1()) return Steinberg::Vst::SpeakerArr::k61Cine; + else if (channels == AudioChannelSet::create6point0Music()) return Steinberg::Vst::SpeakerArr::k60Music; + else if (channels == AudioChannelSet::create6point1Music()) return Steinberg::Vst::SpeakerArr::k61Music; + else if (channels == AudioChannelSet::create7point0()) return Steinberg::Vst::SpeakerArr::k70Music; + else if (channels == AudioChannelSet::create7point0SDDS()) return Steinberg::Vst::SpeakerArr::k70Cine; + else if (channels == AudioChannelSet::create7point1()) return Steinberg::Vst::SpeakerArr::k71CineSideFill; + else if (channels == AudioChannelSet::create7point1SDDS()) return Steinberg::Vst::SpeakerArr::k71Cine; + else if (channels == AudioChannelSet::ambisonic()) return Steinberg::Vst::SpeakerArr::kBFormat; + else if (channels == AudioChannelSet::quadraphonic()) return Steinberg::Vst::SpeakerArr::k40Music; + Steinberg::Vst::SpeakerArrangement result = 0; Array types (channels.getChannelTypes()); @@ -213,6 +232,25 @@ static inline Steinberg::Vst::SpeakerArrangement getSpeakerArrangement (const Au static inline AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept { + if (arr == Steinberg::Vst::SpeakerArr::kEmpty) return AudioChannelSet::disabled(); + else if (arr == Steinberg::Vst::SpeakerArr::kMono) return AudioChannelSet::mono(); + else if (arr == Steinberg::Vst::SpeakerArr::kStereo) return AudioChannelSet::stereo(); + else if (arr == Steinberg::Vst::SpeakerArr::k30Cine) return AudioChannelSet::createLCR(); + else if (arr == Steinberg::Vst::SpeakerArr::k30Music) return AudioChannelSet::createLRS(); + else if (arr == Steinberg::Vst::SpeakerArr::k40Cine) return AudioChannelSet::createLCRS(); + else if (arr == Steinberg::Vst::SpeakerArr::k50) return AudioChannelSet::create5point0(); + else if (arr == Steinberg::Vst::SpeakerArr::k51) return AudioChannelSet::create5point1(); + else if (arr == Steinberg::Vst::SpeakerArr::k60Cine) return AudioChannelSet::create6point0(); + else if (arr == Steinberg::Vst::SpeakerArr::k61Cine) return AudioChannelSet::create6point1(); + else if (arr == Steinberg::Vst::SpeakerArr::k60Music) return AudioChannelSet::create6point0Music(); + else if (arr == Steinberg::Vst::SpeakerArr::k61Music) return AudioChannelSet::create6point1Music(); + else if (arr == Steinberg::Vst::SpeakerArr::k70Music) return AudioChannelSet::create7point0(); + else if (arr == Steinberg::Vst::SpeakerArr::k70Cine) return AudioChannelSet::create7point0SDDS(); + else if (arr == Steinberg::Vst::SpeakerArr::k71CineSideFill) return AudioChannelSet::create7point1(); + else if (arr == Steinberg::Vst::SpeakerArr::k71Cine) return AudioChannelSet::create7point1SDDS(); + else if (arr == Steinberg::Vst::SpeakerArr::kBFormat) return AudioChannelSet::ambisonic(); + else if (arr == Steinberg::Vst::SpeakerArr::k40Music) return AudioChannelSet::quadraphonic(); + AudioChannelSet result; for (Steinberg::Vst::Speaker speaker = 1; speaker <= Steinberg::Vst::kSpeakerRcs; speaker <<= 1) @@ -462,12 +500,12 @@ struct VST3BufferExchange vstBuffers.silenceFlags = 0; } - static void mapArrangementToBusses (int& channelIndexOffset, int index, + static void mapArrangementToBuses (int& channelIndexOffset, int index, Array& result, - BusMap& busMapToUse, Steinberg::Vst::SpeakerArrangement arrangement, + BusMap& busMapToUse, const AudioChannelSet& arrangement, AudioBuffer& source) { - const int numChansForBus = BigInteger ((juce::int64) arrangement).countNumberOfSetBits(); + const int numChansForBus = arrangement.size(); if (index >= result.size()) result.add (Steinberg::Vst::AudioBusBuffers()); @@ -483,26 +521,26 @@ struct VST3BufferExchange channelIndexOffset += numChansForBus; } - static inline void mapBufferToBusses (Array& result, BusMap& busMapToUse, - const Array& arrangements, + static inline void mapBufferToBuses (Array& result, BusMap& busMapToUse, + const Array& arrangements, AudioBuffer& source) { int channelIndexOffset = 0; for (int i = 0; i < arrangements.size(); ++i) - mapArrangementToBusses (channelIndexOffset, i, result, busMapToUse, + mapArrangementToBuses (channelIndexOffset, i, result, busMapToUse, arrangements.getUnchecked (i), source); } - static inline void mapBufferToBusses (Array& result, + static inline void mapBufferToBuses (Array& result, Steinberg::Vst::IAudioProcessor& processor, - BusMap& busMapToUse, bool isInput, int numBusses, + BusMap& busMapToUse, bool isInput, int numBuses, AudioBuffer& source) { int channelIndexOffset = 0; - for (int i = 0; i < numBusses; ++i) - mapArrangementToBusses (channelIndexOffset, i, + for (int i = 0; i < numBuses; ++i) + mapArrangementToBuses (channelIndexOffset, i, result, busMapToUse, getArrangementForBus (&processor, isInput, i), source); diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h b/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h index fbd58c827..9438a16e6 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h +++ b/source/modules/juce_audio_processors/format_types/juce_VST3Headers.h @@ -47,12 +47,15 @@ #pragma clang diagnostic ignored "-Wdelete-non-virtual-dtor" #endif +#undef DEVELOPMENT +#define DEVELOPMENT 0 // This avoids a Clang warning in Steinberg code about unused values + /* These files come with the Steinberg VST3 SDK - to get them, you'll need to visit the Steinberg website and agree to whatever is currently required to get them. Then, you'll need to make sure your include path contains your "VST3 SDK" - directory (or whatever you've named it on your machine). The Introjucer has + directory (or whatever you've named it on your machine). The Projucer has a special box for setting this path. */ #if JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY @@ -78,6 +81,7 @@ #include #include #include + #include #else #if JUCE_MINGW #define _set_abort_behavior(...) @@ -128,6 +132,10 @@ namespace Steinberg #pragma clang diagnostic pop #endif +#if JUCE_WINDOWS + #include +#endif + //============================================================================== #undef ASSERT #undef WARNING diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 58d5b5dc7..2f4118124 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -22,20 +22,13 @@ ============================================================================== */ -#if JUCE_PLUGINHOST_VST3 +#if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) } // namespace juce -#if JucePlugin_Build_VST3 - #undef JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY - #define JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY 1 -#endif - #include #include "juce_VST3Headers.h" -#undef JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY - namespace juce { @@ -93,7 +86,7 @@ static int getHashForTUID (const TUID& tuid) noexcept return value; } -template +template static void fillDescriptionWith (PluginDescription& description, ObjectType& object) { description.version = toString (object.version).trim(); @@ -128,7 +121,7 @@ static void createPluginDescription (PluginDescription& description, description.isInstrument = description.category.containsIgnoreCase ("Instrument"); // This seems to be the only way to find that out! ARGH! } -static int getNumSingleDirectionBussesFor (Vst::IComponent* component, +static int getNumSingleDirectionBusesFor (Vst::IComponent* component, bool checkInputs, bool checkAudioChannels) { @@ -155,13 +148,13 @@ static int getNumSingleDirectionChannelsFor (Vst::IComponent* component, { Vst::BusInfo busInfo; warnOnFailure (component->getBusInfo (mediaType, direction, i, busInfo)); - numChannels += (int) busInfo.channelCount; + numChannels += ((busInfo.flags & Vst::BusInfo::kDefaultActive) != 0 ? (int) busInfo.channelCount : 0); } return numChannels; } -static void setStateForAllBussesOfType (Vst::IComponent* component, +static void setStateForAllBusesOfType (Vst::IComponent* component, bool state, bool activateInputs, bool activateAudioChannels) @@ -368,7 +361,7 @@ class VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 public Vst::IUnitHandler { public: - VST3HostContext (VST3PluginInstance* pluginInstance) : owner (pluginInstance) + VST3HostContext() : plugin (nullptr) { appName = File::getSpecialLocation (File::currentApplicationFile).getFileNameWithoutExtension(); attributeList = new AttributeList (this); @@ -388,58 +381,78 @@ public: //============================================================================== tresult PLUGIN_API beginEdit (Vst::ParamID paramID) override { - const int index = getIndexOfParamID (paramID); + if (plugin != nullptr) + { + const int index = getIndexOfParamID (paramID); - if (index < 0) - return kResultFalse; + if (index < 0) + return kResultFalse; + + plugin->beginParameterChangeGesture (index); + } - owner->beginParameterChangeGesture (index); return kResultTrue; } tresult PLUGIN_API performEdit (Vst::ParamID paramID, Vst::ParamValue valueNormalized) override { - const int index = getIndexOfParamID (paramID); + if (plugin != nullptr) + { + const int index = getIndexOfParamID (paramID); - if (index < 0) - return kResultFalse; + if (index < 0) + return kResultFalse; + + plugin->sendParamChangeMessageToListeners (index, (float) valueNormalized); - owner->sendParamChangeMessageToListeners (index, (float) valueNormalized); - return owner->editController->setParamNormalized (paramID, valueNormalized); + { + Steinberg::int32 eventIndex; + plugin->inputParameterChanges->addParameterData (paramID, eventIndex)->addPoint (0, valueNormalized, eventIndex); + } + + // did the plug-in already update the parameter internally + if (plugin->editController->getParamNormalized (paramID) != (float) valueNormalized) + return plugin->editController->setParamNormalized (paramID, valueNormalized); + } + + return kResultTrue; } tresult PLUGIN_API endEdit (Vst::ParamID paramID) override { - const int index = getIndexOfParamID (paramID); + if (plugin != nullptr) + { + const int index = getIndexOfParamID (paramID); - if (index < 0) - return kResultFalse; + if (index < 0) + return kResultFalse; - owner->endParameterChangeGesture (index); + plugin->endParameterChangeGesture (index); + } return kResultTrue; } tresult PLUGIN_API restartComponent (Steinberg::int32 flags) override { - if (owner != nullptr) + if (plugin != nullptr) { if (hasFlag (flags, Vst::kReloadComponent)) - owner->reset(); + plugin->reset(); if (hasFlag (flags, Vst::kIoChanged)) { - const double sampleRate = owner->getSampleRate(); - const int blockSize = owner->getBlockSize(); + const double sampleRate = plugin->getSampleRate(); + const int blockSize = plugin->getBlockSize(); - owner->prepareToPlay (sampleRate >= 8000 ? sampleRate : 44100.0, + plugin->prepareToPlay (sampleRate >= 8000 ? sampleRate : 44100.0, blockSize > 0 ? blockSize : 1024); } if (hasFlag (flags, Vst::kLatencyChanged)) - if (owner->processor != nullptr) - owner->setLatencySamples (jmax (0, (int) owner->processor->getLatencySamples())); + if (plugin->processor != nullptr) + plugin->setLatencySamples (jmax (0, (int) plugin->processor->getLatencySamples())); - owner->updateHostDisplay(); + plugin->updateHostDisplay(); return kResultTrue; } @@ -472,6 +485,12 @@ public: return kResultFalse; } + void setPlugin (VST3PluginInstance* instance) + { + jassert (plugin == nullptr); + plugin = instance; + } + //============================================================================== class ContextMenu : public Vst::IContextMenu { @@ -634,8 +653,8 @@ public: Vst::IContextMenu* PLUGIN_API createContextMenu (IPlugView*, const Vst::ParamID*) override { - if (owner != nullptr) - return new ContextMenu (*owner); + if (plugin != nullptr) + return new ContextMenu (*plugin); return nullptr; } @@ -693,8 +712,10 @@ public: tresult PLUGIN_API notifyProgramListChange (Vst::ProgramListID, Steinberg::int32) override { - jassertfalse; - return kResultFalse; + if (plugin != nullptr) + plugin->syncProgramNames(); + + return kResultTrue; } //============================================================================== @@ -720,7 +741,7 @@ public: private: //============================================================================== - VST3PluginInstance* const owner; + VST3PluginInstance* plugin; Atomic refCount; String appName; @@ -729,19 +750,19 @@ private: int getIndexOfParamID (Vst::ParamID paramID) { - if (owner == nullptr || owner->editController == nullptr) + if (plugin == nullptr || plugin->editController == nullptr) return -1; int result = getMappedParamID (paramID); if (result < 0) { - const int numParams = owner->editController->getParameterCount(); + const int numParams = plugin->editController->getParameterCount(); for (int i = 0; i < numParams; ++i) { Vst::ParameterInfo paramInfo; - owner->editController->getParameterInfo (i, paramInfo); + plugin->editController->getParameterInfo (i, paramInfo); paramToIndexMap[paramInfo.id] = i; } @@ -901,7 +922,7 @@ private: Atomic refCount; //============================================================================== - template + template void addMessageToQueue (AttrID id, const Type& value) { jassert (id != nullptr); @@ -920,7 +941,7 @@ private: owner->messageQueue.add (ComSmartPtr (new Message (*owner, this, id, value))); } - template + template bool findMessageOnQueueWithID (AttrID id, Type& value) { jassert (id != nullptr); @@ -1154,7 +1175,10 @@ struct DLLHandle if (GetFactoryProc proc = (GetFactoryProc) getFunction ("GetPluginFactory")) factory = proc(); - jassert (factory != nullptr); // The plugin NEEDS to provide a factory to be able to be called a VST3! + // The plugin NEEDS to provide a factory to be able to be called a VST3! + // Most likely you are trying to load a 32-bit VST3 from a 64-bit host + // or vice versa. + jassert (factory != nullptr); return factory; } @@ -1190,6 +1214,7 @@ private: if (library.open (filePath)) { typedef bool (PLUGIN_API *InitModuleProc) (); + if (InitModuleProc proc = (InitModuleProc) getFunction ("InitDll")) { if (proc()) @@ -1289,7 +1314,7 @@ public: if (pluginFactory != nullptr) { - ComSmartPtr host (new VST3HostContext (nullptr)); + ComSmartPtr host (new VST3HostContext()); DescriptionLister lister (host, pluginFactory); const Result result (lister.findDescriptionsAndPerform (File (fileOrIdentifier))); @@ -1351,7 +1376,7 @@ private: if (pluginFactory != nullptr) { - ComSmartPtr host (new VST3HostContext (nullptr)); + ComSmartPtr host (new VST3HostContext()); MatchingDescriptionFinder finder (host, pluginFactory, description); const Result result (finder.findDescriptionsAndPerform (f)); @@ -1582,23 +1607,205 @@ private: #pragma warning (disable: 4996) // warning about overriding deprecated methods #endif +//============================================================================== +struct VST3ComponentHolder +{ + VST3ComponentHolder (const VST3ModuleHandle::Ptr& handle) + : module (handle), + isComponentInitialised (false) + { + host = new VST3HostContext(); + } + + ~VST3ComponentHolder() + { + terminate(); + + component = nullptr; + host = nullptr; + factory = nullptr; + module = nullptr; + } + + // transfers ownership to the plugin instance! + AudioPluginInstance* createPluginInstance(); + + bool fetchController (ComSmartPtr& editController) + { + if (! isComponentInitialised && ! initialise()) + return false; + + // Get the IEditController: + TUID controllerCID = { 0 }; + + if (component->getControllerClassId (controllerCID) == kResultTrue && FUID (controllerCID).isValid()) + editController.loadFrom (factory, controllerCID); + + if (editController == nullptr) + { + // Try finding the IEditController the long way around: + const Steinberg::int32 numClasses = factory->countClasses(); + for (Steinberg::int32 i = 0; i < numClasses; ++i) + { + PClassInfo classInfo; + factory->getClassInfo (i, &classInfo); + + if (std::strcmp (classInfo.category, kVstComponentControllerClass) == 0) + editController.loadFrom (factory, classInfo.cid); + } + } + + if (editController == nullptr) + editController.loadFrom (component); + + return (editController != nullptr); + } + + //============================================================================== + void fillInPluginDescription (PluginDescription& description) const + { + jassert (module != nullptr && isComponentInitialised); + + PFactoryInfo factoryInfo; + factory->getFactoryInfo (&factoryInfo); + + int classIdx; + if ((classIdx = getClassIndex (module->name)) >= 0) + { + PClassInfo info; + bool success = (factory->getClassInfo (classIdx, &info) == kResultOk); + ignoreUnused (success); + jassert (success); + + ComSmartPtr pf2; + ComSmartPtr pf3; + + ScopedPointer info2; + ScopedPointer infoW; + + if (pf2.loadFrom (factory)) + { + info2 = new PClassInfo2(); + pf2->getClassInfo2 (classIdx, info2); + } + else + { + info2 = nullptr; + } + + if (pf3.loadFrom (factory)) + { + pf3->setHostContext (host->getFUnknown()); + infoW = new PClassInfoW(); + pf3->getClassInfoUnicode (classIdx, infoW); + } + else + { + infoW = nullptr; + } + + Vst::BusInfo bus; + int totalNumInputChannels = 0, totalNumOutputChannels = 0; + + int n = component->getBusCount(Vst::kAudio, Vst::kInput); + for (int i = 0; i < n; ++i) + if (component->getBusInfo (Vst::kAudio, Vst::kInput, i, bus) == kResultOk) + totalNumInputChannels += ((bus.flags & Vst::BusInfo::kDefaultActive) != 0 ? bus.channelCount : 0); + + n = component->getBusCount(Vst::kAudio, Vst::kOutput); + for (int i = 0; i < n; ++i) + if (component->getBusInfo (Vst::kAudio, Vst::kOutput, i, bus) == kResultOk) + totalNumOutputChannels += ((bus.flags & Vst::BusInfo::kDefaultActive) != 0 ? bus.channelCount : 0); + + createPluginDescription (description, module->file, + factoryInfo.vendor, module->name, + info, info2, infoW, + totalNumInputChannels, + totalNumOutputChannels); + } + + jassertfalse; + } + + //============================================================================== + bool initialise() + { + if (isComponentInitialised) return true; + + #if JUCE_WINDOWS + // On Windows it's highly advisable to create your plugins using the message thread, + // because many plugins need a chance to create HWNDs that will get their messages + // delivered by the main message thread, and that's not possible from a background thread. + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + #endif + + factory = ComSmartPtr (module->getPluginFactory()); + + int classIdx; + if ((classIdx = getClassIndex (module->name)) < 0) + return false; + + PClassInfo info; + if (factory->getClassInfo (classIdx, &info) != kResultOk) + return false; + + if (! component.loadFrom (factory, info.cid) || component == nullptr) + return false; + + if (warnOnFailure (component->initialize (host->getFUnknown())) != kResultOk) + return false; + + isComponentInitialised = true; + + return true; + } + + void terminate() + { + if (isComponentInitialised) component->terminate(); + isComponentInitialised = false; + } + + //============================================================================== + int getClassIndex (const String& className) const + { + PClassInfo info; + const Steinberg::int32 numClasses = factory->countClasses(); + + for (Steinberg::int32 j = 0; j < numClasses; ++j) + if (factory->getClassInfo (j, &info) == kResultOk + && std::strcmp (info.category, kVstAudioEffectClass) == 0 + && toString (info.name).trim() == className) + return j; + + return -1; + } + + //============================================================================== + VST3ModuleHandle::Ptr module; + ComSmartPtr factory; + ComSmartPtr host; + ComSmartPtr component; + + bool isComponentInitialised; +}; + //============================================================================== class VST3PluginInstance : public AudioPluginInstance { public: - VST3PluginInstance (const VST3ModuleHandle::Ptr& handle) - : module (handle), - numInputAudioBusses (0), - numOutputAudioBusses (0), + VST3PluginInstance (VST3ComponentHolder* componentHolder) + : AudioPluginInstance (getBusProperties (componentHolder->component)), + holder (componentHolder), + programParameterID ((Vst::ParamID) -1), inputParameterChanges (new ParamValueQueueList()), outputParameterChanges (new ParamValueQueueList()), midiInputs (new MidiEventList()), midiOutputs (new MidiEventList()), - isComponentInitialised (false), isControllerInitialised (false), isActive (false) { - host = new VST3HostContext (this); + holder->host->setPlugin (this); } ~VST3PluginInstance() @@ -1616,7 +1823,7 @@ public: editController->setComponentHandler (nullptr); if (isControllerInitialised) editController->terminate(); - if (isComponentInitialised) component->terminate(); + holder->terminate(); componentConnection = nullptr; editControllerConnection = nullptr; @@ -1628,9 +1835,6 @@ public: processor = nullptr; editController2 = nullptr; editController = nullptr; - component = nullptr; - host = nullptr; - module = nullptr; } bool initialise() @@ -1642,61 +1846,68 @@ public: jassert (MessageManager::getInstance()->isThisTheMessageThread()); #endif - ComSmartPtr factory (module->getPluginFactory()); - - PFactoryInfo factoryInfo; - factory->getFactoryInfo (&factoryInfo); - company = toString (factoryInfo.vendor).trim(); - - if (! fetchComponentAndController (factory, factory->countClasses())) + if (! holder->initialise()) return false; + if (! isControllerInitialised) + { + if (! holder->fetchController (editController)) + return false; + } + // (May return an error if the plugin combines the IComponent and IEditController implementations) - editController->initialize (host->getFUnknown()); + editController->initialize (holder->host->getFUnknown()); isControllerInitialised = true; - editController->setComponentHandler (host); + editController->setComponentHandler (holder->host); grabInformationObjects(); - synchroniseStates(); interconnectComponentAndController(); + synchroniseStates(); + syncProgramNames(); setupIO(); return true; } - //============================================================================== - void fillInPluginDescription (PluginDescription& description) const override - { - jassert (module != nullptr); - - createPluginDescription (description, module->file, - company, module->name, - *info, info2, infoW, - getTotalNumInputChannels(), - getTotalNumOutputChannels()); - } - - void* getPlatformSpecificData() override { return component; } + void* getPlatformSpecificData() override { return holder->component; } void refreshParameterList() override {} //============================================================================== const String getName() const override { + VST3ModuleHandle::Ptr& module = holder->module; return module != nullptr ? module->name : String(); } - void repopulateArrangements() + void repopulateArrangements (Array& inputArrangements, Array& outputArrangements) const { inputArrangements.clearQuick(); outputArrangements.clearQuick(); - // NB: Some plugins need a valid arrangement despite specifying 0 for their I/O busses - for (int i = 0; i < jmax (1, numInputAudioBusses); ++i) + const int numInputAudioBuses = getBusCount (true); + const int numOutputAudioBuses = getBusCount (false); + + for (int i = 0; i < numInputAudioBuses; ++i) inputArrangements.add (getArrangementForBus (processor, true, i)); - for (int i = 0; i < jmax (1, numOutputAudioBusses); ++i) + for (int i = 0; i < numOutputAudioBuses; ++i) outputArrangements.add (getArrangementForBus (processor, false, i)); } + void processorLayoutsToArrangements (Array& inputArrangements, Array& outputArrangements) + { + inputArrangements.clearQuick(); + outputArrangements.clearQuick(); + + const int numInputBuses = getBusCount (true); + const int numOutputBuses = getBusCount (false); + + for (int i = 0; i < numInputBuses; ++i) + inputArrangements.add (getVst3SpeakerArrangement (getBus (true, i)->getLastEnabledLayout())); + + for (int i = 0; i < numOutputBuses; ++i) + outputArrangements.add (getVst3SpeakerArrangement (getBus (false, i)->getLastEnabledLayout())); + } + void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override { // Avoid redundantly calling things like setActive, which can be a heavy-duty call for some plugins: @@ -1715,41 +1926,37 @@ public: warnOnFailure (processor->setupProcessing (setup)); - if (! isComponentInitialised) - isComponentInitialised = component->initialize (host->getFUnknown()) == kResultTrue; + holder->initialise(); + editController->setComponentHandler (holder->host); - editController->setComponentHandler (host); - if (inputArrangements.size() <= 0 || outputArrangements.size() <= 0) - repopulateArrangements(); + Array inputArrangements, outputArrangements; + processorLayoutsToArrangements (inputArrangements, outputArrangements); - warnOnFailure (processor->setBusArrangements (inputArrangements.getRawDataPointer(), numInputAudioBusses, - outputArrangements.getRawDataPointer(), numOutputAudioBusses)); + warnOnFailure (processor->setBusArrangements (inputArrangements.getRawDataPointer(), inputArrangements.size(), + outputArrangements.getRawDataPointer(), outputArrangements.size())); - // Update the num. busses in case the configuration has been modified by the plugin. (May affect number of channels!): - const int newNumInputAudioBusses = getNumSingleDirectionBussesFor (component, true, true); - const int newNumOutputAudioBusses = getNumSingleDirectionBussesFor (component, false, true); + Array actualInArr, actualOutArr; + repopulateArrangements (actualInArr, actualOutArr); - // Repopulate arrangements if the number of busses have changed: - if (numInputAudioBusses != newNumInputAudioBusses - || numOutputAudioBusses != newNumOutputAudioBusses) - { - numInputAudioBusses = newNumInputAudioBusses; - numOutputAudioBusses = newNumOutputAudioBusses; - - repopulateArrangements(); - } + jassert (actualInArr == inputArrangements && actualOutArr == outputArrangements); // Needed for having the same sample rate in processBlock(); some plugins need this! - setPlayConfigDetails (getNumSingleDirectionChannelsFor (component, true, true), - getNumSingleDirectionChannelsFor (component, false, true), - newSampleRate, estimatedSamplesPerBlock); + setRateAndBufferSizeDetails (newSampleRate, estimatedSamplesPerBlock); + + const int numInputBuses = getBusCount (true); + const int numOutputBuses = getBusCount (false); - setStateForAllBusses (true); + for (int i = 0; i < numInputBuses; ++i) + warnOnFailure (holder->component->activateBus (Vst::kAudio, Vst::kInput, i, getBus (true, i)->isEnabled() ? 1 : 0)); + + for (int i = 0; i < numOutputBuses; ++i) + warnOnFailure (holder->component->activateBus (Vst::kAudio, Vst::kOutput, i, getBus (false, i)->isEnabled() ? 1 : 0)); setLatencySamples (jmax (0, (int) processor->getLatencySamples())); + cachedBusLayouts = getBusesLayout(); - warnOnFailure (component->setActive (true)); + warnOnFailure (holder->component->setActive (true)); warnOnFailure (processor->setProcessing (true)); isActive = true; @@ -1762,13 +1969,13 @@ public: isActive = false; - setStateForAllBusses (false); + setStateForAllMidiBuses (false); if (processor != nullptr) warnOnFailure (processor->setProcessing (false)); - if (component != nullptr) - warnOnFailure (component->setActive (false)); + if (holder->component != nullptr) + warnOnFailure (holder->component->setActive (false)); } bool supportsDoublePrecisionProcessing() const override @@ -1799,11 +2006,14 @@ public: using namespace Vst; const int numSamples = buffer.getNumSamples(); + const int numInputAudioBuses = getBusCount (true); + const int numOutputAudioBuses = getBusCount (false); + ProcessData data; data.processMode = isNonRealtime() ? kOffline : kRealtime; data.symbolicSampleSize = sampleSize; - data.numInputs = numInputAudioBusses; - data.numOutputs = numOutputAudioBusses; + data.numInputs = numInputAudioBuses; + data.numOutputs = numOutputAudioBuses; data.inputParameterChanges = inputParameterChanges; data.outputParameterChanges = outputParameterChanges; data.numSamples = (Steinberg::int32) numSamples; @@ -1823,13 +2033,94 @@ public: inputParameterChanges->clearAllQueues(); } + //============================================================================== + bool canAddBus (bool) const override { return false; } + bool canRemoveBus (bool) const override { return false; } + + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + // if the processor is not active, we ask the underlying plug-in if the + // layout is actually supported + if (! isActive) + return canApplyBusesLayout (layouts); + + // not much we can do to check the layout while the audio processor is running + // Let's at least check if it is a VST3 compatible layout + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const int n = getBusCount (isInput); + for (int i = 0; i < n; ++i) + if (getChannelLayoutOfBus(isInput, i).isDiscreteLayout()) + return false; + } + + return true; + } + + bool syncBusLayouts (const BusesLayout& layouts) const + { + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const int n = getBusCount (isInput); + const Vst::BusDirection vstDir = (isInput ? Vst::kInput : Vst::kOutput); + + for (int busIdx = 0; busIdx < n; ++busIdx) + { + const bool isEnabled = (! layouts.getChannelSet (isInput, busIdx).isDisabled()); + if (holder->component->activateBus (Vst::kAudio, vstDir, busIdx, (isEnabled ? 1 : 0)) != kResultOk) + return false; + } + } + + Array inputArrangements, outputArrangements; + + for (int i = 0; i < layouts.inputBuses.size(); ++i) + { + const AudioChannelSet& requested = layouts.getChannelSet (true, i); + inputArrangements.add (getVst3SpeakerArrangement (requested.isDisabled() ? getBus (true, i)->getLastEnabledLayout() : requested)); + } + + for (int i = 0; i < layouts.outputBuses.size(); ++i) + { + const AudioChannelSet& requested = layouts.getChannelSet (false, i); + outputArrangements.add (getVst3SpeakerArrangement (requested.isDisabled() ? getBus (false, i)->getLastEnabledLayout() : requested)); + } + + if (processor->setBusArrangements (inputArrangements.getRawDataPointer(), inputArrangements.size(), + outputArrangements.getRawDataPointer(), outputArrangements.size()) != kResultTrue) + return false; + + // check if the layout matches the request + Array actualIn, actualOut; + repopulateArrangements (actualIn, actualOut); + + return (actualIn == inputArrangements && actualOut == outputArrangements); + } + + bool canApplyBusesLayout (const BusesLayout& layouts) const override + { + // someone tried to change the layout while the AudioProcessor is running + // call releaseResources first! + jassert (! isActive); + + bool result = syncBusLayouts (layouts); + + // didn't succeed? Make sure it's back in it's original state + if (! result) + syncBusLayouts (getBusesLayout()); + + return result; + } + //============================================================================== String getChannelName (int channelIndex, bool forInput, bool forAudioChannel) const { - const int numBusses = getNumSingleDirectionBussesFor (component, forInput, forAudioChannel); + const int numBuses = getNumSingleDirectionBusesFor (holder->component, forInput, forAudioChannel); int numCountedChannels = 0; - for (int i = 0; i < numBusses; ++i) + for (int i = 0; i < numBuses; ++i) { Vst::BusInfo busInfo (getBusInfo (forInput, forAudioChannel, i)); @@ -1947,25 +2238,34 @@ public: } //============================================================================== - int getNumPrograms() override { return getProgramListInfo (0).programCount; } - int getCurrentProgram() override { return 0; } - void setCurrentProgram (int) override {} + int getNumPrograms() override { return programNames.size(); } + const String getProgramName (int index) override { return programNames[index]; } + int getCurrentProgram() override { return jmax (0, (int) editController->getParamNormalized (programParameterID) * (programNames.size() - 1)); } void changeProgramName (int, const String&) override {} - const String getProgramName (int index) override + void setCurrentProgram (int program) override { - Vst::String128 result; - unitInfo->getProgramName (getProgramListInfo (0).id, index, result); - return toString (result); + if (programNames.size() > 0 && editController != nullptr) + { + Vst::ParamValue value = + static_cast (program) / static_cast (programNames.size()); + + editController->setParamNormalized (programParameterID, value); + Steinberg::int32 index; + inputParameterChanges->addParameterData (programParameterID, index)->addPoint (0, value, index); + } } //============================================================================== void reset() override { - if (component != nullptr) + if (holder->component != nullptr && processor != nullptr) { - component->setActive (false); - component->setActive (true); + processor->setProcessing (false); + holder->component->setActive (false); + + holder->component->setActive (true); + processor->setProcessing (true); } } @@ -1974,7 +2274,7 @@ public: { XmlElement state ("VST3PluginState"); - appendStateFrom (state, component, "IComponent"); + appendStateFrom (state, holder->component, "IComponent"); appendStateFrom (state, editController, "IEditController"); AudioProcessor::copyXmlToBinary (state, destData); @@ -1988,8 +2288,8 @@ public: { ComSmartPtr s (createMemoryStreamForState (*head, "IComponent")); - if (s != nullptr && component != nullptr) - component->setState (s); + if (s != nullptr && holder->component != nullptr) + holder->component->setState (s); if (editController != nullptr) { @@ -2004,6 +2304,12 @@ public: } } + //============================================================================== + void fillInPluginDescription (PluginDescription& description) const override + { + holder->fillInPluginDescription (description); + } + /** @note Not applicable to VST3 */ void getCurrentProgramStateInformation (MemoryBlock& destData) override { @@ -2022,18 +2328,18 @@ public: class ParamValueQueueList : public Vst::IParameterChanges { public: - ParamValueQueueList() {} + ParamValueQueueList() : numQueuesUsed (0) {} virtual ~ParamValueQueueList() {} JUCE_DECLARE_VST3_COM_REF_METHODS JUCE_DECLARE_VST3_COM_QUERY_METHODS - Steinberg::int32 PLUGIN_API getParameterCount() override { return (Steinberg::int32) queues.size(); } - Vst::IParamValueQueue* PLUGIN_API getParameterData (Steinberg::int32 index) override { return queues[(int) index]; } + Steinberg::int32 PLUGIN_API getParameterCount() override { return numQueuesUsed; } + Vst::IParamValueQueue* PLUGIN_API getParameterData (Steinberg::int32 index) override { return isPositiveAndBelow (static_cast (index), numQueuesUsed) ? queues[(int) index] : nullptr; } Vst::IParamValueQueue* PLUGIN_API addParameterData (const Vst::ParamID& id, Steinberg::int32& index) override { - for (int i = queues.size(); --i >= 0;) + for (int i = numQueuesUsed; --i >= 0;) { if (queues.getUnchecked (i)->getParameterId() == id) { @@ -2042,25 +2348,32 @@ public: } } - index = getParameterCount(); - return queues.add (new ParamValueQueue (id)); + index = numQueuesUsed++; + ParamValueQueue* valueQueue = (index < queues.size() ? queues[index] + : queues.add (new ParamValueQueue)); + + valueQueue->clear(); + valueQueue->setParamID (id); + + return valueQueue; } void clearAllQueues() noexcept { - for (int i = queues.size(); --i >= 0;) - queues.getUnchecked (i)->clear(); + numQueuesUsed = 0; } struct ParamValueQueue : public Vst::IParamValueQueue { - ParamValueQueue (Vst::ParamID parameterID) : paramID (parameterID) + ParamValueQueue() : paramID (static_cast (-1)) { points.ensureStorageAllocated (1024); } virtual ~ParamValueQueue() {} + void setParamID (Vst::ParamID pID) noexcept { paramID = pID; } + JUCE_DECLARE_VST3_COM_REF_METHODS JUCE_DECLARE_VST3_COM_QUERY_METHODS @@ -2112,7 +2425,7 @@ public: }; Atomic refCount; - const Vst::ParamID paramID; + Vst::ParamID paramID; Array points; CriticalSection pointLock; @@ -2121,16 +2434,16 @@ public: Atomic refCount; OwnedArray queues; + int numQueuesUsed; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParamValueQueueList) }; private: //============================================================================== - VST3ModuleHandle::Ptr module; + ScopedPointer holder; friend VST3HostContext; - ComSmartPtr host; // Information objects: String company; @@ -2139,7 +2452,6 @@ private: ScopedPointer infoW; // Rudimentary interfaces: - ComSmartPtr component; ComSmartPtr editController; ComSmartPtr editController2; ComSmartPtr processor; @@ -2151,14 +2463,16 @@ private: ComSmartPtr componentConnection; ComSmartPtr editControllerConnection; - /** The number of IO busses MUST match that of the plugin, + /** The number of IO buses MUST match that of the plugin, even if there aren't enough channels to process, as very poorly specified by the Steinberg SDK */ - int numInputAudioBusses, numOutputAudioBusses; - Array inputArrangements, outputArrangements; // Caching to improve performance and to avoid possible non-thread-safe calls to getBusArrangements(). VST3FloatAndDoubleBusMapComposite inputBusMap, outputBusMap; - Array inputBusses, outputBusses; + Array inputBuses, outputBuses; + AudioProcessor::BusesLayout cachedBusLayouts; + + StringArray programNames; + Vst::ParamID programParameterID; //============================================================================== template @@ -2198,123 +2512,19 @@ private: ComSmartPtr inputParameterChanges, outputParameterChanges; ComSmartPtr midiInputs, midiOutputs; Vst::ProcessContext timingInfo; //< Only use this in processBlock()! - bool isComponentInitialised, isControllerInitialised, isActive; + bool isControllerInitialised, isActive; //============================================================================== - bool fetchComponentAndController (IPluginFactory* factory, const Steinberg::int32 numClasses) - { - jassert (numClasses >= 0); // The plugin must provide at least an IComponent and IEditController! - - for (Steinberg::int32 j = 0; j < numClasses; ++j) - { - info = new PClassInfo(); - factory->getClassInfo (j, info); - - if (std::strcmp (info->category, kVstAudioEffectClass) != 0) - continue; - - const String name (toString (info->name).trim()); - - if (module->name != name) - continue; - - { - ComSmartPtr pf2; - ComSmartPtr pf3; - - if (pf2.loadFrom (factory)) - { - info2 = new PClassInfo2(); - pf2->getClassInfo2 (j, info2); - } - else - { - info2 = nullptr; - } - - if (pf3.loadFrom (factory)) - { - pf3->setHostContext (host->getFUnknown()); - infoW = new PClassInfoW(); - pf3->getClassInfoUnicode (j, infoW); - } - else - { - infoW = nullptr; - } - } - - bool failed = true; - - if (component.loadFrom (factory, info->cid) && component != nullptr) - { - warnOnFailure (component->setIoMode (isNonRealtime() ? Vst::kOffline : Vst::kRealtime)); - - if (warnOnFailure (component->initialize (host->getFUnknown())) != kResultOk) - return false; - - isComponentInitialised = true; - - // Get the IEditController: - TUID controllerCID = { 0 }; - - if (component->getControllerClassId (controllerCID) == kResultTrue && FUID (controllerCID).isValid()) - editController.loadFrom (factory, controllerCID); - - if (editController == nullptr) - { - // Try finding the IEditController the long way around: - for (Steinberg::int32 i = 0; i < numClasses; ++i) - { - PClassInfo classInfo; - factory->getClassInfo (i, &classInfo); - - if (std::strcmp (classInfo.category, kVstComponentControllerClass) == 0) - editController.loadFrom (factory, classInfo.cid); - } - } - - if (editController == nullptr) - editController.loadFrom (component); - - failed = editController == nullptr; - } - - if (failed) - { - jassertfalse; // The plugin won't function without a valid IComponent and IEditController implementation! - - if (component != nullptr) - { - component->terminate(); - component = nullptr; - } - - if (editController != nullptr) - { - editController->terminate(); - editController = nullptr; - } - - break; - } - - return true; - } - - return false; - } - /** Some plugins need to be "connected" to intercommunicate between their implemented classes */ void interconnectComponentAndController() { - componentConnection.loadFrom (component); + componentConnection.loadFrom (holder->component); editControllerConnection.loadFrom (editController); if (componentConnection != nullptr && editControllerConnection != nullptr) { - warnOnFailure (editControllerConnection->connect (componentConnection)); warnOnFailure (componentConnection->connect (editControllerConnection)); + warnOnFailure (editControllerConnection->connect (componentConnection)); } } @@ -2322,20 +2532,20 @@ private: { Steinberg::MemoryStream stream; - if (component->getState (&stream) == kResultTrue) + if (holder->component->getState (&stream) == kResultTrue) if (stream.seek (0, Steinberg::IBStream::kIBSeekSet, nullptr) == kResultTrue) warnOnFailure (editController->setComponentState (&stream)); } void grabInformationObjects() { - processor.loadFrom (component); - unitInfo.loadFrom (component); - programListData.loadFrom (component); - unitData.loadFrom (component); - editController2.loadFrom (component); - componentHandler.loadFrom (component); - componentHandler2.loadFrom (component); + processor.loadFrom (holder->component); + unitInfo.loadFrom (holder->component); + programListData.loadFrom (holder->component); + unitData.loadFrom (holder->component); + editController2.loadFrom (holder->component); + componentHandler.loadFrom (holder->component); + componentHandler2.loadFrom (holder->component); if (processor == nullptr) processor.loadFrom (editController); if (unitInfo == nullptr) unitInfo.loadFrom (editController); @@ -2346,17 +2556,15 @@ private: if (componentHandler2 == nullptr) componentHandler2.loadFrom (editController); } - void setStateForAllBusses (bool newState) + void setStateForAllMidiBuses (bool newState) { - setStateForAllBussesOfType (component, newState, true, true); // Activate/deactivate audio inputs - setStateForAllBussesOfType (component, newState, false, true); // Activate/deactivate audio outputs - setStateForAllBussesOfType (component, newState, true, false); // Activate/deactivate MIDI inputs - setStateForAllBussesOfType (component, newState, false, false); // Activate/deactivate MIDI outputs + setStateForAllBusesOfType (holder->component, newState, true, false); // Activate/deactivate MIDI inputs + setStateForAllBusesOfType (holder->component, newState, false, false); // Activate/deactivate MIDI outputs } void setupIO() { - setStateForAllBusses (true); + setStateForAllMidiBuses (true); Vst::ProcessSetup setup; setup.symbolicSampleSize = Vst::kSample32; @@ -2366,12 +2574,44 @@ private: warnOnFailure (processor->setupProcessing (setup)); - numInputAudioBusses = getNumSingleDirectionBussesFor (component, true, true); - numOutputAudioBusses = getNumSingleDirectionBussesFor (component, false, true); + cachedBusLayouts = getBusesLayout(); + setRateAndBufferSizeDetails (setup.sampleRate, (int) setup.maxSamplesPerBlock); + } + + static AudioProcessor::BusesProperties getBusProperties (ComSmartPtr& component) + { + AudioProcessor::BusesProperties busProperties; + ComSmartPtr processor; + processor.loadFrom (component.get()); + + for (int dirIdx = 0; dirIdx < 2; ++dirIdx) + { + const bool isInput = (dirIdx == 0); + const Vst::BusDirection dir = (isInput ? Vst::kInput : Vst::kOutput); + const int numBuses = component->getBusCount (Vst::kAudio, dir); + + for (int i = 0; i < numBuses; ++i) + { + Vst::BusInfo info; + + if (component->getBusInfo (Vst::kAudio, dir, (Steinberg::int32) i, info) != kResultOk) + continue; + + if (info.channelCount == 0) + continue; - setPlayConfigDetails (getNumSingleDirectionChannelsFor (component, true, true), - getNumSingleDirectionChannelsFor (component, false, true), - setup.sampleRate, (int) setup.maxSamplesPerBlock); + AudioChannelSet layout = AudioChannelSet::discreteChannels (info.channelCount); + + Vst::SpeakerArrangement arr; + if (processor != nullptr && processor->getBusArrangement (dir, i, arr) == kResultOk) + layout = getChannelSetForSpeakerArrangement (arr); + + busProperties.addBus (isInput, toString (info.name), layout, + (info.flags & Vst::BusInfo::kDefaultActive) != 0); + } + } + + return busProperties; } //============================================================================== @@ -2380,9 +2620,10 @@ private: Vst::BusInfo busInfo; busInfo.mediaType = forAudio ? Vst::kAudio : Vst::kEvent; busInfo.direction = forInput ? Vst::kInput : Vst::kOutput; + busInfo.channelCount = 0; - component->getBusInfo (busInfo.mediaType, busInfo.direction, - (Steinberg::int32) index, busInfo); + holder->component->getBusInfo (busInfo.mediaType, busInfo.direction, + (Steinberg::int32) index, busInfo); return busInfo; } @@ -2402,11 +2643,11 @@ private: template void associateTo (Vst::ProcessData& destination, AudioBuffer& buffer) { - VST3BufferExchange::mapBufferToBusses (inputBusses, inputBusMap.get(), inputArrangements, buffer); - VST3BufferExchange::mapBufferToBusses (outputBusses, outputBusMap.get(), outputArrangements, buffer); + VST3BufferExchange::mapBufferToBuses (inputBuses, inputBusMap.get(), cachedBusLayouts.inputBuses, buffer); + VST3BufferExchange::mapBufferToBuses (outputBuses, outputBusMap.get(), cachedBusLayouts.outputBuses, buffer); - destination.inputs = inputBusses.getRawDataPointer(); - destination.outputs = outputBusses.getRawDataPointer(); + destination.inputs = inputBuses.getRawDataPointer(); + destination.outputs = outputBuses.getRawDataPointer(); } void associateTo (Vst::ProcessData& destination, MidiBuffer& midiBuffer) @@ -2446,6 +2687,77 @@ private: return paramInfo; } + void syncProgramNames() + { + programNames.clear(); + + if (processor == nullptr || editController == nullptr) + return; + + Vst::UnitID programUnitID; + Vst::ParameterInfo paramInfo = { 0 }; + + { + int idx, num = editController->getParameterCount(); + for (idx = 0; idx < num; ++idx) + if (editController->getParameterInfo (idx, paramInfo) == kResultOk + && (paramInfo.flags & Steinberg::Vst::ParameterInfo::kIsProgramChange) != 0) + break; + + if (idx >= num) return; + + programParameterID = paramInfo.id; + programUnitID = paramInfo.unitId; + } + + if (unitInfo != nullptr) + { + Vst::UnitInfo uInfo = { 0 }; + const int unitCount = unitInfo->getUnitCount(); + + for (int idx = 0; idx < unitCount; ++idx) + { + if (unitInfo->getUnitInfo(idx, uInfo) == kResultOk + && uInfo.id == programUnitID) + { + const int programListCount = unitInfo->getProgramListCount(); + + for (int j = 0; j < programListCount; ++j) + { + Vst::ProgramListInfo programListInfo = { 0 }; + + if (unitInfo->getProgramListInfo (j, programListInfo) == kResultOk + && programListInfo.id == uInfo.programListId) + { + Vst::String128 name; + + for (int k = 0; k < programListInfo.programCount; ++k) + if (unitInfo->getProgramName (programListInfo.id, k, name) == kResultOk) + programNames.add (toString (name)); + + return; + } + } + + break; + } + } + } + + if (editController != nullptr + && paramInfo.stepCount > 0) + { + const int numPrograms = paramInfo.stepCount + 1; + for (int i = 0; i < numPrograms; ++i) + { + Vst::String128 programName; + Vst::ParamValue valueNormalized = static_cast (i) / static_cast (paramInfo.stepCount); + if (editController->getParamStringByValue (paramInfo.id, valueNormalized, programName) == kResultOk) + programNames.add (toString (programName)); + } + } + } + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginInstance) }; @@ -2455,6 +2767,18 @@ private: }; +//============================================================================== +AudioPluginInstance* VST3Classes::VST3ComponentHolder::createPluginInstance() +{ + if (! initialise()) + return nullptr; + + VST3PluginInstance* plugin = new VST3PluginInstance (this); + host->setPlugin (plugin); + return plugin; +} + + //============================================================================== VST3PluginFormat::VST3PluginFormat() {} VST3PluginFormat::~VST3PluginFormat() {} @@ -2467,7 +2791,11 @@ void VST3PluginFormat::findAllTypesForFile (OwnedArray& resul VST3Classes::VST3ModuleHandle::getAllDescriptionsForFile (results, fileOrIdentifier); } -AudioPluginInstance* VST3PluginFormat::createInstanceFromDescription (const PluginDescription& description, double, int) +void VST3PluginFormat::createPluginInstance (const PluginDescription& description, + double, + int, + void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) { ScopedPointer result; @@ -2480,16 +2808,29 @@ AudioPluginInstance* VST3PluginFormat::createInstanceFromDescription (const Plug if (const VST3Classes::VST3ModuleHandle::Ptr module = VST3Classes::VST3ModuleHandle::findOrCreateModule (file, description)) { - result = new VST3Classes::VST3PluginInstance (module); - - if (! result->initialise()) - result = nullptr; + ScopedPointer holder = new VST3Classes::VST3ComponentHolder (module); + if (holder->initialise()) + { + result = new VST3Classes::VST3PluginInstance (holder.release()); + if (! result->initialise()) + result = nullptr; + } } previousWorkingDirectory.setAsCurrentWorkingDirectory(); } - return result.release(); + String errorMsg; + + if (result == nullptr) + errorMsg = String (NEEDS_TRANS ("Unable to load XXX plug-in file")).replace ("XXX", "VST-3"); + + callback (userData, result.release(), errorMsg); +} + +bool VST3PluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept +{ + return false; } bool VST3PluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) @@ -2519,7 +2860,7 @@ bool VST3PluginFormat::doesPluginStillExist (const PluginDescription& descriptio return File (description.fileOrIdentifier).exists(); } -StringArray VST3PluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive) +StringArray VST3PluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive, bool) { StringArray results; diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h index ceabcbc59..e9650e292 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h +++ b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h @@ -25,11 +25,12 @@ #ifndef JUCE_VST3PLUGINFORMAT_H_INCLUDED #define JUCE_VST3PLUGINFORMAT_H_INCLUDED -#if JUCE_PLUGINHOST_VST3 +#if (JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS)) || DOXYGEN + /** Implements a plugin format for VST3s. */ -class JUCE_API VST3PluginFormat : public AudioPluginFormat +class JUCE_API VST3PluginFormat : public AudioPluginFormat { public: /** Constructor */ @@ -39,32 +40,27 @@ public: ~VST3PluginFormat(); //============================================================================== - /** @internal */ - String getName() const override { return "VST3"; } - /** @internal */ - void findAllTypesForFile (OwnedArray& results, const String& fileOrIdentifier) override; - /** @internal */ - AudioPluginInstance* createInstanceFromDescription (const PluginDescription& description, double, int) override; - /** @internal */ + String getName() const override { return "VST3"; } + void findAllTypesForFile (OwnedArray&, const String& fileOrIdentifier) override; bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; - /** @internal */ String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; - /** @internal */ - bool pluginNeedsRescanning (const PluginDescription& description) override; - /** @internal */ - StringArray searchPathsForPlugins (const FileSearchPath& searchPath, bool recursive) override; - /** @internal */ - bool doesPluginStillExist (const PluginDescription& description) override; - /** @internal */ + bool pluginNeedsRescanning (const PluginDescription&) override; + StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; + bool doesPluginStillExist (const PluginDescription&) override; FileSearchPath getDefaultLocationsToSearch() override; - /** @internal */ - bool canScanForPlugins() const override { return true; } + bool canScanForPlugins() const override { return true; } + +private: + void createPluginInstance (const PluginDescription&, double initialSampleRate, + int initialBufferSize, void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) override; + + bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; private: //============================================================================== void recursiveFileSearch (StringArray&, const File&, bool recursive); - //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginFormat) }; diff --git a/source/modules/juce_audio_processors/format_types/juce_VSTCommon.h b/source/modules/juce_audio_processors/format_types/juce_VSTCommon.h new file mode 100644 index 000000000..1869941d3 --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_VSTCommon.h @@ -0,0 +1,238 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_VSTCOMMON_H_INCLUDED +#define JUCE_VSTCOMMON_H_INCLUDED + +//============================================================================== +struct SpeakerMappings : private AudioChannelSet // (inheritance only to give easier access to items in the namespace) +{ + struct Mapping + { + int32 vst2; + ChannelType channels[13]; + + bool matches (const Array& chans) const noexcept + { + const int n = sizeof (channels) / sizeof (ChannelType); + + for (int i = 0; i < n; ++i) + { + if (channels[i] == unknown) return (i == chans.size()); + if (i == chans.size()) return (channels[i] == unknown); + + if (channels[i] != chans.getUnchecked(i)) + return false; + } + + return true; + } + }; + + static AudioChannelSet vstArrangementTypeToChannelSet (int32 arr, int fallbackNumChannels) + { + if (arr == vstSpeakerConfigTypeEmpty) return AudioChannelSet::disabled(); + else if (arr == vstSpeakerConfigTypeMono) return AudioChannelSet::mono(); + else if (arr == vstSpeakerConfigTypeLR) return AudioChannelSet::stereo(); + else if (arr == vstSpeakerConfigTypeLRC) return AudioChannelSet::createLCR(); + else if (arr == vstSpeakerConfigTypeLRS) return AudioChannelSet::createLRS(); + else if (arr == vstSpeakerConfigTypeLRCS) return AudioChannelSet::createLCRS(); + else if (arr == vstSpeakerConfigTypeLRCLsRs) return AudioChannelSet::create5point0(); + else if (arr == vstSpeakerConfigTypeLRCLfeLsRs) return AudioChannelSet::create5point1(); + else if (arr == vstSpeakerConfigTypeLRCLsRsCs) return AudioChannelSet::create6point0(); + else if (arr == vstSpeakerConfigTypeLRCLfeLsRsCs) return AudioChannelSet::create6point1(); + else if (arr == vstSpeakerConfigTypeLRLsRsSlSr) return AudioChannelSet::create6point0Music(); + else if (arr == vstSpeakerConfigTypeLRLfeLsRsSlSr) return AudioChannelSet::create6point1Music(); + else if (arr == vstSpeakerConfigTypeLRCLsRsSlSr) return AudioChannelSet::create7point0(); + else if (arr == vstSpeakerConfigTypeLRCLsRsLcRc) return AudioChannelSet::create7point0SDDS(); + else if (arr == vstSpeakerConfigTypeLRCLfeLsRsSlSr) return AudioChannelSet::create7point1(); + else if (arr == vstSpeakerConfigTypeLRCLfeLsRsLcRc) return AudioChannelSet::create7point1SDDS(); + else if (arr == vstSpeakerConfigTypeLRLsRs) return AudioChannelSet::quadraphonic(); + + for (const Mapping* m = getMappings(); m->vst2 != vstSpeakerConfigTypeEmpty; ++m) + { + if (m->vst2 == arr) + { + AudioChannelSet s; + + for (int i = 0; m->channels[i] != 0; ++i) + s.addChannel (m->channels[i]); + + return s; + } + } + + return AudioChannelSet::discreteChannels (fallbackNumChannels); + } + + static AudioChannelSet vstArrangementTypeToChannelSet (const VstSpeakerConfiguration& arr) + { + return vstArrangementTypeToChannelSet (arr.type, arr.numberOfChannels); + } + + static int32 channelSetToVstArrangementType (AudioChannelSet channels) + { + if (channels == AudioChannelSet::disabled()) return vstSpeakerConfigTypeEmpty; + else if (channels == AudioChannelSet::mono()) return vstSpeakerConfigTypeMono; + else if (channels == AudioChannelSet::stereo()) return vstSpeakerConfigTypeLR; + else if (channels == AudioChannelSet::createLCR()) return vstSpeakerConfigTypeLRC; + else if (channels == AudioChannelSet::createLRS()) return vstSpeakerConfigTypeLRS; + else if (channels == AudioChannelSet::createLCRS()) return vstSpeakerConfigTypeLRCS; + else if (channels == AudioChannelSet::create5point0()) return vstSpeakerConfigTypeLRCLsRs; + else if (channels == AudioChannelSet::create5point1()) return vstSpeakerConfigTypeLRCLfeLsRs; + else if (channels == AudioChannelSet::create6point0()) return vstSpeakerConfigTypeLRCLsRsCs; + else if (channels == AudioChannelSet::create6point1()) return vstSpeakerConfigTypeLRCLfeLsRsCs; + else if (channels == AudioChannelSet::create6point0Music()) return vstSpeakerConfigTypeLRLsRsSlSr; + else if (channels == AudioChannelSet::create6point1Music()) return vstSpeakerConfigTypeLRLfeLsRsSlSr; + else if (channels == AudioChannelSet::create7point0()) return vstSpeakerConfigTypeLRCLsRsSlSr; + else if (channels == AudioChannelSet::create7point0SDDS()) return vstSpeakerConfigTypeLRCLsRsLcRc; + else if (channels == AudioChannelSet::create7point1()) return vstSpeakerConfigTypeLRCLfeLsRsSlSr; + else if (channels == AudioChannelSet::create7point1SDDS()) return vstSpeakerConfigTypeLRCLfeLsRsLcRc; + else if (channels == AudioChannelSet::quadraphonic()) return vstSpeakerConfigTypeLRLsRs; + + Array chans (channels.getChannelTypes()); + + if (channels == AudioChannelSet::disabled()) + return vstSpeakerConfigTypeEmpty; + + for (const Mapping* m = getMappings(); m->vst2 != vstSpeakerConfigTypeEmpty; ++m) + if (m->matches (chans)) + return m->vst2; + + return vstSpeakerConfigTypeUser; + } + + static void channelSetToVstArrangement (const AudioChannelSet& channels, VstSpeakerConfiguration& result) + { + result.type = channelSetToVstArrangementType (channels); + result.numberOfChannels = channels.size(); + + for (int i = 0; i < result.numberOfChannels; ++i) + { + VstIndividualSpeakerInfo& speaker = result.speakers[i]; + + zeromem (&speaker, sizeof (VstIndividualSpeakerInfo)); + speaker.type = getSpeakerType (channels.getTypeOfChannel (i)); + } + } + + static const Mapping* getMappings() noexcept + { + static const Mapping mappings[] = + { + { vstSpeakerConfigTypeMono, { centre, unknown } }, + { vstSpeakerConfigTypeLR, { left, right, unknown } }, + { vstSpeakerConfigTypeLsRs, { leftSurround, rightSurround, unknown } }, + { vstSpeakerConfigTypeLcRc, { leftCentre, rightCentre, unknown } }, + { vstSpeakerConfigTypeSlSr, { leftSurroundRear, rightSurroundRear, unknown } }, + { vstSpeakerConfigTypeCLfe, { centre, LFE, unknown } }, + { vstSpeakerConfigTypeLRC, { left, right, centre, unknown } }, + { vstSpeakerConfigTypeLRS, { left, right, surround, unknown } }, + { vstSpeakerConfigTypeLRCLfe, { left, right, centre, LFE, unknown } }, + { vstSpeakerConfigTypeLRLfeS, { left, right, LFE, surround, unknown } }, + { vstSpeakerConfigTypeLRCS, { left, right, centre, surround, unknown } }, + { vstSpeakerConfigTypeLRLsRs, { left, right, leftSurround, rightSurround, unknown } }, + { vstSpeakerConfigTypeLRCLfeS, { left, right, centre, LFE, surround, unknown } }, + { vstSpeakerConfigTypeLRLfeLsRs, { left, right, LFE, leftSurround, rightSurround, unknown } }, + { vstSpeakerConfigTypeLRCLsRs, { left, right, centre, leftSurround, rightSurround, unknown } }, + { vstSpeakerConfigTypeLRCLfeLsRs, { left, right, centre, LFE, leftSurround, rightSurround, unknown } }, + { vstSpeakerConfigTypeLRCLsRsCs, { left, right, centre, leftSurround, rightSurround, surround, unknown } }, + { vstSpeakerConfigTypeLRLsRsSlSr, { left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, + { vstSpeakerConfigTypeLRCLfeLsRsCs, { left, right, centre, LFE, leftSurround, rightSurround, surround, unknown } }, + { vstSpeakerConfigTypeLRLfeLsRsSlSr, { left, right, LFE, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, + { vstSpeakerConfigTypeLRCLsRsLcRc, { left, right, centre, leftSurround, rightSurround, topFrontLeft, topFrontRight, unknown } }, + { vstSpeakerConfigTypeLRCLsRsSlSr, { left, right, centre, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, + { vstSpeakerConfigTypeLRCLfeLsRsLcRc, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, unknown } }, + { vstSpeakerConfigTypeLRCLfeLsRsSlSr, { left, right, centre, LFE, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, + { vstSpeakerConfigTypeLRCLsRsLcRcCs, { left, right, centre, leftSurround, rightSurround, topFrontLeft, topFrontRight, surround, unknown } }, + { vstSpeakerConfigTypeLRCLsRsCsSlSr, { left, right, centre, leftSurround, rightSurround, surround, leftSurroundRear, rightSurroundRear, unknown } }, + { vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, surround, unknown } }, + { vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, { left, right, centre, LFE, leftSurround, rightSurround, surround, leftSurroundRear, rightSurroundRear, unknown } }, + { vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontCentre, topFrontRight, topRearLeft, topRearRight, LFE2, unknown } }, + { vstSpeakerConfigTypeEmpty, { unknown } } + }; + + return mappings; + } + + static inline int32 getSpeakerType (AudioChannelSet::ChannelType type) noexcept + { + switch (type) + { + case AudioChannelSet::left: return vstIndividualSpeakerTypeLeft; + case AudioChannelSet::right: return vstIndividualSpeakerTypeRight; + case AudioChannelSet::centre: return vstIndividualSpeakerTypeCentre; + case AudioChannelSet::LFE: return vstIndividualSpeakerTypeLFE; + case AudioChannelSet::leftSurround: return vstIndividualSpeakerTypeLeftSurround; + case AudioChannelSet::rightSurround: return vstIndividualSpeakerTypeRightSurround; + case AudioChannelSet::leftCentre: return vstIndividualSpeakerTypeLeftCentre; + case AudioChannelSet::rightCentre: return vstIndividualSpeakerTypeRightCentre; + case AudioChannelSet::surround: return vstIndividualSpeakerTypeSurround; + case AudioChannelSet::leftSurroundRear: return vstIndividualSpeakerTypeLeftRearSurround; + case AudioChannelSet::rightSurroundRear: return vstIndividualSpeakerTypeRightRearSurround; + case AudioChannelSet::topMiddle: return vstIndividualSpeakerTypeTopMiddle; + case AudioChannelSet::topFrontLeft: return vstIndividualSpeakerTypeTopFrontLeft; + case AudioChannelSet::topFrontCentre: return vstIndividualSpeakerTypeTopFrontCentre; + case AudioChannelSet::topFrontRight: return vstIndividualSpeakerTypeTopFrontRight; + case AudioChannelSet::topRearLeft: return vstIndividualSpeakerTypeTopRearLeft; + case AudioChannelSet::topRearCentre: return vstIndividualSpeakerTypeTopRearCentre; + case AudioChannelSet::topRearRight: return vstIndividualSpeakerTypeTopRearRight; + case AudioChannelSet::LFE2: return vstIndividualSpeakerTypeLFE2; + default: break; + } + + return 0; + } + + static inline AudioChannelSet::ChannelType getChannelType (int32 type) noexcept + { + switch (type) + { + case vstIndividualSpeakerTypeLeft: return AudioChannelSet::left; + case vstIndividualSpeakerTypeRight: return AudioChannelSet::right; + case vstIndividualSpeakerTypeCentre: return AudioChannelSet::centre; + case vstIndividualSpeakerTypeLFE: return AudioChannelSet::LFE; + case vstIndividualSpeakerTypeLeftSurround: return AudioChannelSet::leftSurround; + case vstIndividualSpeakerTypeRightSurround: return AudioChannelSet::rightSurround; + case vstIndividualSpeakerTypeLeftCentre: return AudioChannelSet::leftCentre; + case vstIndividualSpeakerTypeRightCentre: return AudioChannelSet::rightCentre; + case vstIndividualSpeakerTypeSurround: return AudioChannelSet::surround; + case vstIndividualSpeakerTypeLeftRearSurround: return AudioChannelSet::leftSurroundRear; + case vstIndividualSpeakerTypeRightRearSurround: return AudioChannelSet::rightSurroundRear; + case vstIndividualSpeakerTypeTopMiddle: return AudioChannelSet::topMiddle; + case vstIndividualSpeakerTypeTopFrontLeft: return AudioChannelSet::topFrontLeft; + case vstIndividualSpeakerTypeTopFrontCentre: return AudioChannelSet::topFrontCentre; + case vstIndividualSpeakerTypeTopFrontRight: return AudioChannelSet::topFrontRight; + case vstIndividualSpeakerTypeTopRearLeft: return AudioChannelSet::topRearLeft; + case vstIndividualSpeakerTypeTopRearCentre: return AudioChannelSet::topRearCentre; + case vstIndividualSpeakerTypeTopRearRight: return AudioChannelSet::topRearRight; + case vstIndividualSpeakerTypeLFE2: return AudioChannelSet::LFE2; + default: break; + } + + return AudioChannelSet::unknown; + } +}; + +#endif // JUCE_VSTCOMMON_H_INCLUDED diff --git a/source/modules/juce_audio_processors/format_types/juce_VSTInterface.h b/source/modules/juce_audio_processors/format_types/juce_VSTInterface.h new file mode 100644 index 000000000..502f3672b --- /dev/null +++ b/source/modules/juce_audio_processors/format_types/juce_VSTInterface.h @@ -0,0 +1,458 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_VSTINTERFACE_H_INCLUDED +#define JUCE_VSTINTERFACE_H_INCLUDED + +#include "../../juce_core/juce_core.h" + +using namespace juce; + +#if JUCE_MSVC + #define VSTINTERFACECALL __cdecl + #pragma pack(push) + #pragma pack(8) +#elif JUCE_MAC || JUCE_IOS + #define VSTINTERFACECALL + #if JUCE_64BIT + #pragma options align=power + #else + #pragma options align=mac68k + #endif +#else + #define VSTINTERFACECALL + #pragma pack(push, 8) +#endif + +const int32 juceVstInterfaceVersion = 2400; +const int32 juceVstInterfaceIdentifier = 0x56737450; // The "magic" identifier in the SDK is 'VstP'. + +//============================================================================== +struct VstEffectInterface +{ + int32 interfaceIdentifier; + pointer_sized_int (VSTINTERFACECALL* dispatchFunction) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); + void (VSTINTERFACECALL* processAudioFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); + void (VSTINTERFACECALL* setParameterValueFunction) (VstEffectInterface*, int32 parameterIndex, float value); + float (VSTINTERFACECALL* getParameterValueFunction) (VstEffectInterface*, int32 parameterIndex); + int32 numPrograms; + int32 numParameters; + int32 numInputChannels; + int32 numOutputChannels; + int32 flags; + pointer_sized_int hostSpace1; + pointer_sized_int hostSpace2; + int32 latency; + int32 deprecated1; + int32 deprecated2; + float deprecated3; + void* effectPointer; + void* userPointer; + int32 plugInIdentifier; + int32 plugInVersion; + void (VSTINTERFACECALL* processAudioInplaceFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); + void (VSTINTERFACECALL* processDoubleAudioInplaceFunction) (VstEffectInterface*, double** inputs, double** outputs, int32 numSamples); + char emptySpace[56]; +}; + +typedef pointer_sized_int (VSTINTERFACECALL* VstHostCallback) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); + +enum VstEffectInterfaceFlags +{ + vstEffectFlagHasEditor = 1, + vstEffectFlagInplaceAudio = 16, + vstEffectFlagDataInChunks = 32, + vstEffectFlagIsSynth = 256, + vstEffectFlagInplaceDoubleAudio = 4096 +}; + +//============================================================================== +enum VstHostToPlugInOpcodes +{ + plugInOpcodeOpen, + plugInOpcodeClose, + plugInOpcodeSetCurrentProgram, + plugInOpcodeGetCurrentProgram, + plugInOpcodeSetCurrentProgramName, + plugInOpcodeGetCurrentProgramName, + plugInOpcodeGetParameterLabel, + plugInOpcodeGetParameterText, + plugInOpcodeGetParameterName, + plugInOpcodeSetSampleRate = plugInOpcodeGetParameterName + 2, + plugInOpcodeSetBlockSize, + plugInOpcodeResumeSuspend, + plugInOpcodeGetEditorBounds, + plugInOpcodeOpenEditor, + plugInOpcodeCloseEditor, + plugInOpcodeDrawEditor, + plugInOpcodeGetMouse, + plugInOpcodeEditorIdle = plugInOpcodeGetMouse + 2, + plugInOpcodeeffEditorTop, + plugInOpcodeSleepEditor, + plugInOpcodeIdentify, + plugInOpcodeGetData, + plugInOpcodeSetData, + plugInOpcodePreAudioProcessingEvents, + plugInOpcodeIsParameterAutomatable, + plugInOpcodeParameterValueForText, + plugInOpcodeGetProgramName = plugInOpcodeParameterValueForText + 2, + plugInOpcodeConnectInput = plugInOpcodeGetProgramName + 2, + plugInOpcodeConnectOutput, + plugInOpcodeGetInputPinProperties, + plugInOpcodeGetOutputPinProperties, + plugInOpcodeGetPlugInCategory, + plugInOpcodeSetSpeakerConfiguration = plugInOpcodeGetPlugInCategory + 7, + plugInOpcodeSetBypass = plugInOpcodeSetSpeakerConfiguration + 2, + plugInOpcodeGetPlugInName, + plugInOpcodeGetManufacturerName = plugInOpcodeGetPlugInName + 2, + plugInOpcodeGetManufacturerProductName, + plugInOpcodeGetManufacturerVersion, + plugInOpcodeManufacturerSpecific, + plugInOpcodeCanPlugInDo, + plugInOpcodeGetTailSize, + plugInOpcodeIdle, + plugInOpcodeKeyboardFocusRequired = plugInOpcodeIdle + 4, + plugInOpcodeGetVstInterfaceVersion, + plugInOpcodeGetCurrentMidiProgram = plugInOpcodeGetVstInterfaceVersion + 5, + plugInOpcodeGetSpeakerArrangement = plugInOpcodeGetCurrentMidiProgram + 6, + plugInOpcodeNextPlugInUniqueID, + plugInOpcodeStartProcess, + plugInOpcodeStopProcess, + plugInOpcodeSetNumberOfSamplesToProcess, + plugInOpcodeSetSampleFloatType = plugInOpcodeSetNumberOfSamplesToProcess + 4, + plugInOpcodeMaximum = plugInOpcodeSetSampleFloatType +}; + + +enum VstPlugInToHostOpcodes +{ + hostOpcodeParameterChanged, + hostOpcodeVstVersion, + hostOpcodeCurrentId, + hostOpcodeIdle, + hostOpcodePinConnected, + hostOpcodePlugInWantsMidi = hostOpcodePinConnected + 2, + hostOpcodeGetTimingInfo, + hostOpcodePreAudioProcessingEvents, + hostOpcodeSetTime, + hostOpcodeTempoAt, + hostOpcodeGetNumberOfAutomatableParameters, + hostOpcodeGetParameterInterval, + hostOpcodeIOModified, + hostOpcodeNeedsIdle, + hostOpcodeWindowSize, + hostOpcodeGetSampleRate, + hostOpcodeGetBlockSize, + hostOpcodeGetInputLatency, + hostOpcodeGetOutputLatency, + hostOpcodeGetPreviousPlugIn, + hostOpcodeGetNextPlugIn, + hostOpcodeWillReplace, + hostOpcodeGetCurrentAudioProcessingLevel, + hostOpcodeGetAutomationState, + hostOpcodeOfflineStart, + hostOpcodeOfflineReadSource, + hostOpcodeOfflineWrite, + hostOpcodeOfflineGetCurrentPass, + hostOpcodeOfflineGetCurrentMetaPass, + hostOpcodeSetOutputSampleRate, + hostOpcodeGetOutputSpeakerConfiguration, + hostOpcodeGetManufacturerName, + hostOpcodeGetProductName, + hostOpcodeGetManufacturerVersion, + hostOpcodeManufacturerSpecific, + hostOpcodeSetIcon, + hostOpcodeCanHostDo, + hostOpcodeGetLanguage, + hostOpcodeOpenEditorWindow, + hostOpcodeCloseEditorWindow, + hostOpcodeGetDirectory, + hostOpcodeUpdateView, + hostOpcodeParameterChangeGestureBegin, + hostOpcodeParameterChangeGestureEnd, +}; + +//============================================================================== +enum VstProcessingSampleType +{ + vstProcessingSampleTypeFloat, + vstProcessingSampleTypeDouble +}; + +//============================================================================== +// These names must be identical to the Steinberg SDK so JUCE users can set +// exactly what they want. +enum VstPlugInCategory +{ + kPlugCategUnknown, + kPlugCategEffect, + kPlugCategSynth, + kPlugCategAnalysis, + kPlugCategMastering, + kPlugCategSpacializer, + kPlugCategRoomFx, + kPlugSurroundFx, + kPlugCategRestoration, + kPlugCategOfflineProcess, + kPlugCategShell, + kPlugCategGenerator +}; + +//============================================================================== +struct VstEditorBounds +{ + int16 upper; + int16 leftmost; + int16 lower; + int16 rightmost; +}; + +//============================================================================== +enum VstMaxStringLengths +{ + vstMaxNameLength = 64, + vstMaxParameterOrPinLabelLength = 64, + vstMaxParameterOrPinShortLabelLength = 8, + vstMaxCategoryLength = 24, + vstMaxManufacturerStringLength = 64, + vstMaxPlugInNameStringLength = 64 +}; + +//============================================================================== +struct VstPinInfo +{ + char text[vstMaxParameterOrPinLabelLength]; + int32 flags; + int32 configurationType; + char shortText[vstMaxParameterOrPinShortLabelLength]; + char unused[48]; +}; + +enum VstPinInfoFlags +{ + vstPinInfoFlagIsActive = 1, + vstPinInfoFlagIsStereo = 2, + vstPinInfoFlagValid = 4 +}; + +//============================================================================== +struct VstEvent +{ + int32 type; + int32 size; + int32 sampleOffset; + int32 flags; + char content[16]; +}; + +enum VstEventTypes +{ + vstMidiEventType = 1, + vstSysExEventType = 6 +}; + +struct VstEventBlock +{ + int32 numberOfEvents; + pointer_sized_int future; + VstEvent* events[2]; +}; + +struct VstMidiEvent +{ + int32 type; + int32 size; + int32 sampleOffset; + int32 flags; + int32 noteSampleLength; + int32 noteSampleOffset; + char midiData[4]; + char tuning; + char noteVelocityOff; + char future1; + char future2; +}; + +enum VstMidiEventFlags +{ + vstMidiEventIsRealtime = 1 +}; + +struct VstSysExEvent +{ + int32 type; + int32 size; + int32 offsetSamples; + int32 flags; + int32 sysExDumpSize; + pointer_sized_int future1; + char* sysExDump; + pointer_sized_int future2; +}; + +//============================================================================== +struct VstTimingInformation +{ + double samplePosition; + double sampleRate; + double systemTimeNanoseconds; + double musicalPosition; + double tempoBPM; + double lastBarPosition; + double loopStartPosition; + double loopEndPosition; + int32 timeSignatureNumerator; + int32 timeSignatureDenominator; + int32 smpteOffset; + int32 smpteRate; + int32 samplesToNearestClock; + int32 flags; +}; + +enum VstTimingInformationFlags +{ + vstTimingInfoFlagTransportChanged = 1, + vstTimingInfoFlagCurrentlyPlaying = 2, + vstTimingInfoFlagLoopActive = 4, + vstTimingInfoFlagCurrentlyRecording = 8, + vstTimingInfoFlagAutomationWriteModeActive = 64, + vstTimingInfoFlagAutomationReadModeActive = 128, + vstTimingInfoFlagNanosecondsValid = 256, + vstTimingInfoFlagMusicalPositionValid = 512, + vstTimingInfoFlagTempoValid = 1024, + vstTimingInfoFlagLastBarPositionValid = 2056, + vstTimingInfoFlagLoopPositionValid = 4096, + vstTimingInfoFlagTimeSignatureValid = 8192, + vstTimingInfoFlagSmpteValid = 16384, + vstTimingInfoFlagNearestClockValid = 32768 +}; + +//============================================================================== +enum VstSmpteRates +{ + vstSmpteRateFps24, + vstSmpteRateFps25, + vstSmpteRateFps2997, + vstSmpteRateFps30, + vstSmpteRateFps2997drop, + vstSmpteRateFps30drop, + + vstSmpteRate16mmFilm, + vstSmpteRate35mmFilm, + + vstSmpteRateFps239 = vstSmpteRate35mmFilm + 3, + vstSmpteRateFps249 , + vstSmpteRateFps599, + vstSmpteRateFps60 +}; + +//============================================================================== +struct VstIndividualSpeakerInfo +{ + float azimuthalAngle; + float elevationAngle; + float radius; + float reserved; + char label[vstMaxNameLength]; + int32 type; + char unused[28]; +}; + +enum VstIndividualSpeakerType +{ + vstIndividualSpeakerTypeUndefined = 0x7fffffff, + vstIndividualSpeakerTypeMono = 0, + vstIndividualSpeakerTypeLeft, + vstIndividualSpeakerTypeRight, + vstIndividualSpeakerTypeCentre, + vstIndividualSpeakerTypeLFE, + vstIndividualSpeakerTypeLeftSurround, + vstIndividualSpeakerTypeRightSurround, + vstIndividualSpeakerTypeLeftCentre, + vstIndividualSpeakerTypeRightCentre, + vstIndividualSpeakerTypeSurround, + vstIndividualSpeakerTypeCentreSurround = vstIndividualSpeakerTypeSurround, + vstIndividualSpeakerTypeLeftRearSurround, + vstIndividualSpeakerTypeRightRearSurround, + vstIndividualSpeakerTypeTopMiddle, + vstIndividualSpeakerTypeTopFrontLeft, + vstIndividualSpeakerTypeTopFrontCentre, + vstIndividualSpeakerTypeTopFrontRight, + vstIndividualSpeakerTypeTopRearLeft, + vstIndividualSpeakerTypeTopRearCentre, + vstIndividualSpeakerTypeTopRearRight, + vstIndividualSpeakerTypeLFE2 +}; + +struct VstSpeakerConfiguration +{ + int32 type; + int32 numberOfChannels; + VstIndividualSpeakerInfo speakers[8]; +}; + +enum VstSpeakerConfigurationType +{ + vstSpeakerConfigTypeUser = -2, + vstSpeakerConfigTypeEmpty = -1, + vstSpeakerConfigTypeMono = 0, + vstSpeakerConfigTypeLR, + vstSpeakerConfigTypeLsRs, + vstSpeakerConfigTypeLcRc, + vstSpeakerConfigTypeSlSr, + vstSpeakerConfigTypeCLfe, + vstSpeakerConfigTypeLRC, + vstSpeakerConfigTypeLRS, + vstSpeakerConfigTypeLRCLfe, + vstSpeakerConfigTypeLRLfeS, + vstSpeakerConfigTypeLRCS, + vstSpeakerConfigTypeLRLsRs, + vstSpeakerConfigTypeLRCLfeS, + vstSpeakerConfigTypeLRLfeLsRs, + vstSpeakerConfigTypeLRCLsRs, + vstSpeakerConfigTypeLRCLfeLsRs, + vstSpeakerConfigTypeLRCLsRsCs, + vstSpeakerConfigTypeLRLsRsSlSr, + vstSpeakerConfigTypeLRCLfeLsRsCs, + vstSpeakerConfigTypeLRLfeLsRsSlSr, + vstSpeakerConfigTypeLRCLsRsLcRc, + vstSpeakerConfigTypeLRCLsRsSlSr, + vstSpeakerConfigTypeLRCLfeLsRsLcRc, + vstSpeakerConfigTypeLRCLfeLsRsSlSr, + vstSpeakerConfigTypeLRCLsRsLcRcCs, + vstSpeakerConfigTypeLRCLsRsCsSlSr, + vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, + vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, + vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2 +}; + +#if JUCE_MSVC + #pragma pack(pop) +#elif JUCE_MAC || JUCE_IOS + #pragma options align=reset +#else + #pragma pack(pop) +#endif + +#endif // JUCE_VSTINTERFACE_H_INCLUDED diff --git a/source/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h b/source/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h index 11cbe4f7f..2d486a59b 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h +++ b/source/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h @@ -22,7 +22,8 @@ ============================================================================== */ -#ifdef __aeffect__ // NB: this must come first, *before* the header-guard. +// NB: this must come first, *before* the header-guard. +#ifdef JUCE_VSTINTERFACE_H_INCLUDED #ifndef JUCE_VSTMIDIEVENTLIST_H_INCLUDED #define JUCE_VSTMIDIEVENTLIST_H_INCLUDED @@ -53,7 +54,7 @@ public: numEventsUsed = 0; if (events != nullptr) - events->numEvents = 0; + events->numberOfEvents = 0; } void addEvent (const void* const midiData, const int numBytes, const int frameOffset) @@ -61,65 +62,65 @@ public: ensureSize (numEventsUsed + 1); VstMidiEvent* const e = (VstMidiEvent*) (events->events [numEventsUsed]); - events->numEvents = ++numEventsUsed; + events->numberOfEvents = ++numEventsUsed; if (numBytes <= 4) { - if (e->type == kVstSysExType) + if (e->type == vstSysExEventType) { - delete[] (((VstMidiSysexEvent*) e)->sysexDump); - e->type = kVstMidiType; - e->byteSize = sizeof (VstMidiEvent); - e->noteLength = 0; - e->noteOffset = 0; - e->detune = 0; - e->noteOffVelocity = 0; + delete[] (((VstSysExEvent*) e)->sysExDump); + e->type = vstMidiEventType; + e->size = sizeof (VstMidiEvent); + e->noteSampleLength = 0; + e->noteSampleOffset = 0; + e->tuning = 0; + e->noteVelocityOff = 0; } - e->deltaFrames = frameOffset; + e->sampleOffset = frameOffset; memcpy (e->midiData, midiData, (size_t) numBytes); } else { - VstMidiSysexEvent* const se = (VstMidiSysexEvent*) e; + VstSysExEvent* const se = (VstSysExEvent*) e; - if (se->type == kVstSysExType) - delete[] se->sysexDump; + if (se->type == vstSysExEventType) + delete[] se->sysExDump; - se->sysexDump = new char [numBytes]; - memcpy (se->sysexDump, midiData, (size_t) numBytes); + se->sysExDump = new char [(size_t) numBytes]; + memcpy (se->sysExDump, midiData, (size_t) numBytes); - se->type = kVstSysExType; - se->byteSize = sizeof (VstMidiSysexEvent); - se->deltaFrames = frameOffset; + se->type = vstSysExEventType; + se->size = sizeof (VstSysExEvent); + se->offsetSamples = frameOffset; se->flags = 0; - se->dumpBytes = numBytes; - se->resvd1 = 0; - se->resvd2 = 0; + se->sysExDumpSize = numBytes; + se->future1 = 0; + se->future2 = 0; } } //============================================================================== // Handy method to pull the events out of an event buffer supplied by the host // or plugin. - static void addEventsToMidiBuffer (const VstEvents* events, MidiBuffer& dest) + static void addEventsToMidiBuffer (const VstEventBlock* events, MidiBuffer& dest) { - for (int i = 0; i < events->numEvents; ++i) + for (int i = 0; i < events->numberOfEvents; ++i) { const VstEvent* const e = events->events[i]; if (e != nullptr) { - if (e->type == kVstMidiType) + if (e->type == vstMidiEventType) { dest.addEvent ((const juce::uint8*) ((const VstMidiEvent*) e)->midiData, - 4, e->deltaFrames); + 4, e->sampleOffset); } - else if (e->type == kVstSysExType) + else if (e->type == vstSysExEventType) { - dest.addEvent ((const juce::uint8*) ((const VstMidiSysexEvent*) e)->sysexDump, - (int) ((const VstMidiSysexEvent*) e)->dumpBytes, - e->deltaFrames); + dest.addEvent ((const juce::uint8*) ((const VstSysExEvent*) e)->sysExDump, + (int) ((const VstSysExEvent*) e)->sysExDumpSize, + e->sampleOffset); } } } @@ -160,24 +161,24 @@ public: } //============================================================================== - HeapBlock events; + HeapBlock events; private: int numEventsUsed, numEventsAllocated; static VstEvent* allocateVSTEvent() { - VstEvent* const e = (VstEvent*) std::calloc (1, sizeof (VstMidiEvent) > sizeof (VstMidiSysexEvent) ? sizeof (VstMidiEvent) - : sizeof (VstMidiSysexEvent)); - e->type = kVstMidiType; - e->byteSize = sizeof (VstMidiEvent); + VstEvent* const e = (VstEvent*) std::calloc (1, sizeof (VstMidiEvent) > sizeof (VstSysExEvent) ? sizeof (VstMidiEvent) + : sizeof (VstSysExEvent)); + e->type = vstMidiEventType; + e->size = sizeof (VstMidiEvent); return e; } static void freeVSTEvent (VstEvent* e) { - if (e->type == kVstSysExType) - delete[] (((VstMidiSysexEvent*) e)->sysexDump); + if (e->type == vstSysExEventType) + delete[] (((VstSysExEvent*) e)->sysExDump); std::free (e); } diff --git a/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index b9d38ba94..d381031c7 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -22,7 +22,7 @@ ============================================================================== */ -#if JUCE_PLUGINHOST_VST +#if JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS) //============================================================================== #if JUCE_MAC && JUCE_SUPPORT_CARBON @@ -31,7 +31,6 @@ //============================================================================== #undef PRAGMA_ALIGN_SUPPORTED -#define VST_FORCE_DEPRECATED 0 #if JUCE_MSVC #pragma warning (push) @@ -40,13 +39,12 @@ #define __cdecl #endif -/* Obviously you're going to need the Steinberg vstsdk2.4 folder in - your include path if you want to add VST support. +namespace +{ +#include "juce_VSTInterface.h" +} - If you're not interested in VSTs, you can disable them by setting the - JUCE_PLUGINHOST_VST flag to 0. -*/ -#include "pluginterfaces/vst2.x/aeffectx.h" +#include "juce_VSTCommon.h" #if JUCE_MSVC #pragma warning (pop) @@ -83,67 +81,67 @@ namespace struct fxProgram { - VstInt32 chunkMagic; // 'CcnK' - VstInt32 byteSize; // of this chunk, excl. magic + byteSize - VstInt32 fxMagic; // 'FxCk' - VstInt32 version; - VstInt32 fxID; // fx unique id - VstInt32 fxVersion; - VstInt32 numParams; + int32 chunkMagic; // 'CcnK' + int32 byteSize; // of this chunk, excl. magic + byteSize + int32 fxMagic; // 'FxCk' + int32 version; + int32 fxID; // fx unique id + int32 fxVersion; + int32 numParams; char prgName[28]; float params[1]; // variable no. of parameters }; struct fxSet { - VstInt32 chunkMagic; // 'CcnK' - VstInt32 byteSize; // of this chunk, excl. magic + byteSize - VstInt32 fxMagic; // 'FxBk' - VstInt32 version; - VstInt32 fxID; // fx unique id - VstInt32 fxVersion; - VstInt32 numPrograms; + int32 chunkMagic; // 'CcnK' + int32 byteSize; // of this chunk, excl. magic + byteSize + int32 fxMagic; // 'FxBk' + int32 version; + int32 fxID; // fx unique id + int32 fxVersion; + int32 numPrograms; char future[128]; fxProgram programs[1]; // variable no. of programs }; struct fxChunkSet { - VstInt32 chunkMagic; // 'CcnK' - VstInt32 byteSize; // of this chunk, excl. magic + byteSize - VstInt32 fxMagic; // 'FxCh', 'FPCh', or 'FBCh' - VstInt32 version; - VstInt32 fxID; // fx unique id - VstInt32 fxVersion; - VstInt32 numPrograms; + int32 chunkMagic; // 'CcnK' + int32 byteSize; // of this chunk, excl. magic + byteSize + int32 fxMagic; // 'FxCh', 'FPCh', or 'FBCh' + int32 version; + int32 fxID; // fx unique id + int32 fxVersion; + int32 numPrograms; char future[128]; - VstInt32 chunkSize; + int32 chunkSize; char chunk[8]; // variable }; struct fxProgramSet { - VstInt32 chunkMagic; // 'CcnK' - VstInt32 byteSize; // of this chunk, excl. magic + byteSize - VstInt32 fxMagic; // 'FxCh', 'FPCh', or 'FBCh' - VstInt32 version; - VstInt32 fxID; // fx unique id - VstInt32 fxVersion; - VstInt32 numPrograms; + int32 chunkMagic; // 'CcnK' + int32 byteSize; // of this chunk, excl. magic + byteSize + int32 fxMagic; // 'FxCh', 'FPCh', or 'FBCh' + int32 version; + int32 fxID; // fx unique id + int32 fxVersion; + int32 numPrograms; char name[28]; - VstInt32 chunkSize; + int32 chunkSize; char chunk[8]; // variable }; // Compares a magic value in either endianness. - static bool compareMagic (VstInt32 magic, const char* name) noexcept + static bool compareMagic (int32 magic, const char* name) noexcept { - return magic == (VstInt32) ByteOrder::littleEndianInt (name) - || magic == (VstInt32) ByteOrder::bigEndianInt (name); + return magic == (int32) ByteOrder::littleEndianInt (name) + || magic == (int32) ByteOrder::bigEndianInt (name); } - static VstInt32 fxbName (const char* name) noexcept { return (VstInt32) ByteOrder::littleEndianInt (name); } - static VstInt32 fxbSwap (const VstInt32 x) noexcept { return (VstInt32) ByteOrder::swapIfLittleEndian ((uint32) x); } + static int32 fxbName (const char* name) noexcept { return (int32) ByteOrder::littleEndianInt (name); } + static int32 fxbSwap (const int32 x) noexcept { return (int32) ByteOrder::swapIfLittleEndian ((uint32) x); } static float fxbSwapFloat (const float x) noexcept { @@ -165,7 +163,7 @@ namespace { #if JUCE_WINDOWS return timeGetTime() * 1000000.0; - #elif JUCE_LINUX + #elif JUCE_LINUX || JUCE_IOS timeval micro; gettimeofday (µ, 0); return micro.tv_usec * 1000.0; @@ -203,23 +201,11 @@ namespace return FSPathMakeRef (reinterpret_cast (path.toRawUTF8()), destFSRef, 0) == noErr; } #endif - - #if JUCE_MAC && JUCE_PPC - static void* newCFMFromMachO (void* const machofp) noexcept - { - void* result = (void*) new char[8]; - - ((void**) result)[0] = machofp; - ((void**) result)[1] = result; - - return result; - } - #endif } //============================================================================== -typedef AEffect* (VSTCALLBACK *MainCall) (audioMasterCallback); -static VstIntPtr VSTCALLBACK audioMaster (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt); +typedef VstEffectInterface* (VSTINTERFACECALL *MainCall) (VstHostCallback); +static pointer_sized_int VSTINTERFACECALL audioMaster (VstEffectInterface* effect, int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt); //============================================================================== // Change this to disable logging of various VST activities @@ -354,7 +340,7 @@ public: } //============================================================================== - static ModuleHandle* findOrCreateModule (const File& file) + static Ptr findOrCreateModule (const File& file) { for (int i = getActiveModules().size(); --i >= 0;) { @@ -364,36 +350,36 @@ public: return module; } - _fpreset(); // (doesn't do any harm) - const IdleCallRecursionPreventer icrp; shellUIDToCreate = 0; + _fpreset(); JUCE_VST_LOG ("Attempting to load VST: " + file.getFullPathName()); - ScopedPointer m (new ModuleHandle (file)); - - if (! m->open()) - m = nullptr; + Ptr m = new ModuleHandle (file, nullptr); - _fpreset(); // (doesn't do any harm) + if (m->open()) + { + _fpreset(); + return m; + } - return m.release(); + return nullptr; } //============================================================================== - ModuleHandle (const File& f) - : file (f), moduleMain (nullptr), customMain (nullptr) + ModuleHandle (const File& f, MainCall customMainCall) + : file (f), moduleMain (customMainCall), customMain (nullptr) + #if JUCE_MAC || JUCE_IOS + , resHandle (0), bundleRef (0) #if JUCE_MAC - #if JUCE_PPC - , fragId (0) - #endif - , resHandle (0), bundleRef (0), resFileId (0) + , resFileId (0) + #endif #endif { getActiveModules().add (this); - #if JUCE_WINDOWS || JUCE_LINUX + #if JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS fullParentDirectoryPathName = f.getParentDirectory().getFullPathName(); #elif JUCE_MAC FSRef ref; @@ -409,12 +395,18 @@ public: } //============================================================================== -#if JUCE_WINDOWS || JUCE_LINUX - DynamicLibrary module; + #if ! JUCE_MAC String fullParentDirectoryPathName; + #endif + + #if JUCE_WINDOWS || JUCE_LINUX + DynamicLibrary module; bool open() { + if (moduleMain != nullptr) + return true; + pluginName = file.getFileNameWithoutExtension(); module.open (file.getFullPathName()); @@ -446,9 +438,9 @@ public: module.close(); } - void closeEffect (AEffect* eff) + void closeEffect (VstEffectInterface* eff) { - eff->dispatcher (eff, effClose, 0, 0, 0, 0); + eff->dispatchFunction (eff, plugInOpcodeClose, 0, 0, 0, 0); } #if JUCE_WINDOWS @@ -472,17 +464,20 @@ public: return String(); } #endif -#else - #if JUCE_PPC - CFragConnectionID fragId; - #endif + #else Handle resHandle; CFBundleRef bundleRef; + + #if JUCE_MAC + CFBundleRefNum resFileId; FSSpec parentDirFSSpec; - ResFileRefNum resFileId; + #endif bool open() { + if (moduleMain != nullptr) + return true; + bool ok = false; if (file.hasFileExtension (".vst")) @@ -522,13 +517,18 @@ public: if (pluginName.isEmpty()) pluginName = file.getFileNameWithoutExtension(); + #if JUCE_MAC resFileId = CFBundleOpenBundleResourceMap (bundleRef); + #endif ok = true; Array vstXmlFiles; - file.getChildFile ("Contents") + file + #if JUCE_MAC + .getChildFile ("Contents") .getChildFile ("Resources") + #endif .findChildFiles (vstXmlFiles, File::findFiles, false, "*.vstxml"); if (vstXmlFiles.size() > 0) @@ -545,83 +545,17 @@ public: } } } - #if JUCE_PPC - else - { - FSRef fn; - - if (FSPathMakeRef ((UInt8*) file.getFullPathName().toRawUTF8(), &fn, 0) == noErr) - { - resFileId = FSOpenResFile (&fn, fsRdPerm); - - if (resFileId != -1) - { - const int numEffs = Count1Resources ('aEff'); - - for (int i = 0; i < numEffs; ++i) - { - resHandle = Get1IndResource ('aEff', i + 1); - - if (resHandle != 0) - { - OSType type; - Str255 name; - SInt16 id; - GetResInfo (resHandle, &id, &type, name); - pluginName = String ((const char*) name + 1, name[0]); - DetachResource (resHandle); - HLock (resHandle); - - ::Ptr ptr; - Str255 errorText; - - OSErr err = GetMemFragment (*resHandle, GetHandleSize (resHandle), - name, kPrivateCFragCopy, - &fragId, &ptr, errorText); - - if (err == noErr) - { - moduleMain = (MainCall) newMachOFromCFM (ptr); - ok = true; - } - else - { - HUnlock (resHandle); - } - - break; - } - } - - if (! ok) - CloseResFile (resFileId); - } - } - } - #endif return ok; } void close() { - #if JUCE_PPC - if (fragId != 0) - { - if (moduleMain != nullptr) - disposeMachOFromCFM ((void*) moduleMain); - - CloseConnection (&fragId); - HUnlock (resHandle); - - if (resFileId != 0) - CloseResFile (resFileId); - } - else - #endif if (bundleRef != 0) { + #if JUCE_MAC CFBundleCloseBundleResourceMap (bundleRef, resFileId); + #endif if (CFGetRetainCount (bundleRef) == 1) CFBundleUnloadExecutable (bundleRef); @@ -631,68 +565,12 @@ public: } } - void closeEffect (AEffect* eff) + void closeEffect (VstEffectInterface* eff) { - #if JUCE_PPC - if (fragId != 0) - { - Array thingsToDelete; - thingsToDelete.add ((void*) eff->dispatcher); - thingsToDelete.add ((void*) eff->process); - thingsToDelete.add ((void*) eff->setParameter); - thingsToDelete.add ((void*) eff->getParameter); - thingsToDelete.add ((void*) eff->processReplacing); - - eff->dispatcher (eff, effClose, 0, 0, 0, 0); - - for (int i = thingsToDelete.size(); --i >= 0;) - disposeMachOFromCFM (thingsToDelete[i]); - } - else - #endif - { - eff->dispatcher (eff, effClose, 0, 0, 0, 0); - } - } - - #if JUCE_PPC - static void* newMachOFromCFM (void* cfmfp) - { - if (cfmfp == 0) - return nullptr; - - UInt32* const mfp = new UInt32[6]; - - mfp[0] = 0x3d800000 | ((UInt32) cfmfp >> 16); - mfp[1] = 0x618c0000 | ((UInt32) cfmfp & 0xffff); - mfp[2] = 0x800c0000; - mfp[3] = 0x804c0004; - mfp[4] = 0x7c0903a6; - mfp[5] = 0x4e800420; - - MakeDataExecutable (mfp, sizeof (UInt32) * 6); - return mfp; - } - - static void disposeMachOFromCFM (void* ptr) - { - delete[] static_cast (ptr); + eff->dispatchFunction (eff, plugInOpcodeClose, 0, 0, 0, 0); } - void coerceAEffectFunctionCalls (AEffect* eff) - { - if (fragId != 0) - { - eff->dispatcher = (AEffectDispatcherProc) newMachOFromCFM ((void*) eff->dispatcher); - eff->process = (AEffectProcessProc) newMachOFromCFM ((void*) eff->process); - eff->setParameter = (AEffectSetParameterProc) newMachOFromCFM ((void*) eff->setParameter); - eff->getParameter = (AEffectGetParameterProc) newMachOFromCFM ((void*) eff->getParameter); - eff->processReplacing = (AEffectProcessProc) newMachOFromCFM ((void*) eff->processReplacing); - } - } - #endif - -#endif + #endif private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModuleHandle) @@ -712,72 +590,59 @@ class VSTPluginInstance : public AudioPluginInstance, private Timer, private AsyncUpdater { -public: - VSTPluginInstance (const ModuleHandle::Ptr& mh) - : effect (nullptr), - module (mh), +private: + VSTPluginInstance (const ModuleHandle::Ptr& mh, const BusesProperties& ioConfig, VstEffectInterface* effect) + : AudioPluginInstance (ioConfig), + vstEffect (effect), + vstModule (mh), usesCocoaNSView (false), name (mh->pluginName), wantsMidiMessages (false), initialised (false), isPowerOn (false) + {} + +public: + ~VSTPluginInstance() { - try + if (vstEffect != nullptr && vstEffect->interfaceIdentifier == juceVstInterfaceIdentifier) { - const IdleCallRecursionPreventer icrp; - _fpreset(); - - JUCE_VST_LOG ("Creating VST instance: " + name); - - #if JUCE_MAC - if (module->resFileId != 0) - UseResFile (module->resFileId); - - #if JUCE_PPC - if (module->fragId != 0) + struct VSTDeleter : public CallbackMessage { - static void* audioMasterCoerced = nullptr; - if (audioMasterCoerced == nullptr) - audioMasterCoerced = newCFMFromMachO ((void*) &audioMaster); + VSTDeleter (VSTPluginInstance& inInstance, WaitableEvent& inEvent) + : vstInstance (inInstance), completionSignal (inEvent) + {} - effect = module->moduleMain ((audioMasterCallback) audioMasterCoerced); - } - else - #endif - #endif - { - JUCE_VST_WRAPPER_INVOKE_MAIN - } - - if (effect != nullptr && effect->magic == kEffectMagic) - { - #if JUCE_PPC - module->coerceAEffectFunctionCalls (effect); - #endif + void messageCallback() override + { + vstInstance.cleanup(); + completionSignal.signal(); + } - jassert (effect->resvd2 == 0); - jassert (effect->object != 0); + VSTPluginInstance& vstInstance; + WaitableEvent& completionSignal; + }; - _fpreset(); // some dodgy plugs fuck around with this + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + cleanup(); } else { - effect = nullptr; + WaitableEvent completionEvent; + (new VSTDeleter (*this, completionEvent))->post(); + completionEvent.wait(); } } - catch (...) - {} } - ~VSTPluginInstance() + void cleanup() { - const ScopedLock sl (lock); - - if (effect != nullptr && effect->magic == kEffectMagic) + if (vstEffect != nullptr && vstEffect->interfaceIdentifier == juceVstInterfaceIdentifier) { #if JUCE_MAC - if (module->resFileId != 0) - UseResFile (module->resFileId); + if (vstModule->resFileId != 0) + UseResFile (vstModule->resFileId); #endif // Must delete any editors before deleting the plugin instance! @@ -785,49 +650,88 @@ public: _fpreset(); // some dodgy plugs fuck around with this - module->closeEffect (effect); + vstModule->closeEffect (vstEffect); + } + + vstModule = nullptr; + vstEffect = nullptr; + } + + static VSTPluginInstance* create (const ModuleHandle::Ptr& newModule, + double initialSampleRate, + int initialBlockSize) + { + if (VstEffectInterface* newEffect = constructEffect (newModule)) + { + newEffect->hostSpace2 = 0; + + newEffect->dispatchFunction (newEffect, plugInOpcodeIdentify, 0, 0, 0, 0); + + newEffect->dispatchFunction (newEffect, plugInOpcodeSetSampleRate, 0, 0, 0, static_cast (initialSampleRate)); + newEffect->dispatchFunction (newEffect, plugInOpcodeSetBlockSize, 0, jmax (32, initialBlockSize), 0, 0); + + newEffect->dispatchFunction (newEffect, plugInOpcodeOpen, 0, 0, 0, 0); + BusesProperties ioConfig = queryBusIO (newEffect); + newEffect->dispatchFunction (newEffect, plugInOpcodeClose, 0, 0, 0, 0); + + newEffect = constructEffect (newModule); + + if (newEffect != nullptr) + return new VSTPluginInstance (newModule, ioConfig, newEffect); } - module = nullptr; - effect = nullptr; + return nullptr; } + //============================================================================== void fillInPluginDescription (PluginDescription& desc) const override { desc.name = name; { - char buffer [512] = { 0 }; - dispatch (effGetEffectName, 0, 0, buffer, 0); + char buffer[512] = { 0 }; + dispatch (plugInOpcodeGetPlugInName, 0, 0, buffer, 0); - desc.descriptiveName = String::fromUTF8 (buffer).trim(); + desc.descriptiveName = String::createStringFromData (buffer, (int) sizeof (buffer)).trim(); if (desc.descriptiveName.isEmpty()) desc.descriptiveName = name; } - desc.fileOrIdentifier = module->file.getFullPathName(); + desc.fileOrIdentifier = vstModule->file.getFullPathName(); desc.uid = getUID(); - desc.lastFileModTime = module->file.getLastModificationTime(); + desc.lastFileModTime = vstModule->file.getLastModificationTime(); desc.lastInfoUpdateTime = Time::getCurrentTime(); desc.pluginFormatName = "VST"; desc.category = getCategory(); { - char buffer [kVstMaxVendorStrLen + 8] = { 0 }; - dispatch (effGetVendorString, 0, 0, buffer, 0); - desc.manufacturerName = String::fromUTF8 (buffer); + char buffer[512] = { 0 }; + dispatch (plugInOpcodeGetManufacturerName, 0, 0, buffer, 0); + desc.manufacturerName = String::createStringFromData (buffer, (int) sizeof (buffer)).trim(); } desc.version = getVersion(); desc.numInputChannels = getTotalNumInputChannels(); desc.numOutputChannels = getTotalNumOutputChannels(); - desc.isInstrument = (effect != nullptr && (effect->flags & effFlagsIsSynth) != 0); + desc.isInstrument = (vstEffect != nullptr && (vstEffect->flags & vstEffectFlagIsSynth) != 0); + } + + bool initialiseEffect (double initialSampleRate, int initialBlockSize) + { + if (vstEffect != nullptr) + { + vstEffect->hostSpace2 = (pointer_sized_int) (pointer_sized_int) this; + initialise (initialSampleRate, initialBlockSize); + return true; + } + + return false; } void initialise (double initialSampleRate, int initialBlockSize) { - if (initialised || effect == nullptr) + if (initialised || vstEffect == nullptr) return; #if JUCE_WINDOWS @@ -838,32 +742,30 @@ public: jassert (MessageManager::getInstance()->isThisTheMessageThread()); #endif - JUCE_VST_LOG ("Initialising VST: " + module->pluginName + " (" + getVersion() + ")"); + JUCE_VST_LOG ("Initialising VST: " + vstModule->pluginName + " (" + getVersion() + ")"); initialised = true; - setPlayConfigDetails (effect->numInputs, effect->numOutputs, - initialSampleRate, initialBlockSize); + setRateAndBufferSizeDetails (initialSampleRate, initialBlockSize); - dispatch (effIdentify, 0, 0, 0, 0); + dispatch (plugInOpcodeIdentify, 0, 0, 0, 0); if (getSampleRate() > 0) - dispatch (effSetSampleRate, 0, 0, 0, (float) getSampleRate()); + dispatch (plugInOpcodeSetSampleRate, 0, 0, 0, (float) getSampleRate()); if (getBlockSize() > 0) - dispatch (effSetBlockSize, 0, jmax (32, getBlockSize()), 0, 0); + dispatch (plugInOpcodeSetBlockSize, 0, jmax (32, getBlockSize()), 0, 0); - dispatch (effOpen, 0, 0, 0, 0); + dispatch (plugInOpcodeOpen, 0, 0, 0, 0); - setPlayConfigDetails (effect->numInputs, effect->numOutputs, - getSampleRate(), getBlockSize()); + setRateAndBufferSizeDetails (getSampleRate(), getBlockSize()); if (getNumPrograms() > 1) setCurrentProgram (0); else - dispatch (effSetProgram, 0, 0, 0, 0); + dispatch (plugInOpcodeSetCurrentProgram, 0, 0, 0, 0); - for (int i = effect->numInputs; --i >= 0;) dispatch (effConnectInput, i, 1, 0, 0); - for (int i = effect->numOutputs; --i >= 0;) dispatch (effConnectOutput, i, 1, 0, 0); + for (int i = vstEffect->numInputChannels; --i >= 0;) dispatch (plugInOpcodeConnectInput, i, 1, 0, 0); + for (int i = vstEffect->numOutputChannels; --i >= 0;) dispatch (plugInOpcodeConnectOutput, i, 1, 0, 0); if (getVstCategory() != kPlugCategShell) // (workaround for Waves 5 plugins which crash during this call) updateStoredProgramNames(); @@ -871,28 +773,45 @@ public: wantsMidiMessages = pluginCanDo ("receiveVstMidiEvent") > 0; #if JUCE_MAC && JUCE_SUPPORT_CARBON - usesCocoaNSView = (pluginCanDo ("hasCockosViewAsConfig") & (int) 0xffff0000) == 0xbeef0000; + usesCocoaNSView = ((unsigned int) pluginCanDo ("hasCockosViewAsConfig") & 0xffff0000ul) == 0xbeef0000ul; #endif - setLatencySamples (effect->initialDelay); + setLatencySamples (vstEffect->latency); } - void* getPlatformSpecificData() override { return effect; } - const String getName() const override { return name; } + void* getPlatformSpecificData() override { return vstEffect; } + + const String getName() const override + { + if (vstEffect != nullptr) + { + char buffer[512] = { 0 }; + + if (dispatch (plugInOpcodeGetManufacturerProductName, 0, 0, buffer, 0) != 0) + { + String productName = String::createStringFromData (buffer, (int) sizeof (buffer)); + + if (productName.isNotEmpty()) + return productName; + } + } + + return name; + } int getUID() const { - int uid = effect != nullptr ? effect->uniqueID : 0; + int uid = vstEffect != nullptr ? vstEffect->plugInIdentifier : 0; if (uid == 0) - uid = module->file.hashCode(); + uid = vstModule->file.hashCode(); return uid; } double getTailLengthSeconds() const override { - if (effect == nullptr) + if (vstEffect == nullptr) return 0.0; const double sampleRate = getSampleRate(); @@ -900,7 +819,7 @@ public: if (sampleRate <= 0) return 0.0; - VstIntPtr samples = dispatch (effGetTailSize, 0, 0, 0, 0); + pointer_sized_int samples = dispatch (plugInOpcodeGetTailSize, 0, 0, 0, 0); return samples / sampleRate; } @@ -908,21 +827,30 @@ public: bool producesMidi() const override { return pluginCanDo ("sendVstMidiEvent") > 0; } bool supportsMPE() const override { return pluginCanDo ("MPE") > 0; } - VstPlugCategory getVstCategory() const noexcept { return (VstPlugCategory) dispatch (effGetPlugCategory, 0, 0, 0, 0); } + VstPlugInCategory getVstCategory() const noexcept { return (VstPlugInCategory) dispatch (plugInOpcodeGetPlugInCategory, 0, 0, 0, 0); } - int pluginCanDo (const char* text) const { return (int) dispatch (effCanDo, 0, 0, (void*) text, 0); } + int pluginCanDo (const char* text) const { return (int) dispatch (plugInOpcodeCanPlugInDo, 0, 0, (void*) text, 0); } //============================================================================== void prepareToPlay (double rate, int samplesPerBlockExpected) override { - setPlayConfigDetails (effect->numInputs, effect->numOutputs, rate, samplesPerBlockExpected); + setRateAndBufferSizeDetails (rate, samplesPerBlockExpected); + + VstSpeakerConfiguration inArr, outArr; - vstHostTime.tempo = 120.0; - vstHostTime.timeSigNumerator = 4; - vstHostTime.timeSigDenominator = 4; + SpeakerMappings::channelSetToVstArrangement (getChannelLayoutOfBus (true, 0), inArr); + SpeakerMappings::channelSetToVstArrangement (getChannelLayoutOfBus (false, 0), outArr); + + dispatch (plugInOpcodeSetSpeakerConfiguration, 0, reinterpret_cast (&inArr), &outArr, 0.0f); + + vstHostTime.tempoBPM = 120.0; + vstHostTime.timeSignatureNumerator = 4; + vstHostTime.timeSignatureDenominator = 4; vstHostTime.sampleRate = rate; - vstHostTime.samplePos = 0; - vstHostTime.flags = kVstNanosValid | kVstAutomationWriting | kVstAutomationReading; + vstHostTime.samplePosition = 0; + vstHostTime.flags = vstTimingInfoFlagNanosecondsValid + | vstTimingInfoFlagAutomationWriteModeActive + | vstTimingInfoFlagAutomationReadModeActive; initialise (rate, samplesPerBlockExpected); @@ -937,22 +865,18 @@ public: incomingMidi.clear(); - dispatch (effSetSampleRate, 0, 0, 0, (float) rate); - dispatch (effSetBlockSize, 0, jmax (16, samplesPerBlockExpected), 0, 0); + dispatch (plugInOpcodeSetSampleRate, 0, 0, 0, (float) rate); + dispatch (plugInOpcodeSetBlockSize, 0, jmax (16, samplesPerBlockExpected), 0, 0); if (supportsDoublePrecisionProcessing()) { - VstInt32 vstPrecision = isUsingDoublePrecision() ? kVstProcessPrecision64 - : kVstProcessPrecision32; - - // if you get an assertion here then your plug-in claims it supports double precision - // but returns an error when we try to change the precision - VstIntPtr err = dispatch (effSetProcessPrecision, 0, (VstIntPtr) vstPrecision, 0, 0); - jassert (err > 0); - ignoreUnused (err); + int32 vstPrecision = isUsingDoublePrecision() ? vstProcessingSampleTypeDouble + : vstProcessingSampleTypeFloat; + + dispatch (plugInOpcodeSetSampleFloatType, 0, (pointer_sized_int) vstPrecision, 0, 0); } - tempBuffer.setSize (jmax (1, effect->numOutputs), samplesPerBlockExpected); + tempBuffer.setSize (jmax (1, vstEffect->numInputChannels), samplesPerBlockExpected); if (! isPowerOn) setPower (true); @@ -965,9 +889,9 @@ public: setParameter (0, old); } - dispatch (effStartProcess, 0, 0, 0, 0); + dispatch (plugInOpcodeStartProcess, 0, 0, 0, 0); - setLatencySamples (effect->initialDelay); + setLatencySamples (vstEffect->latency); } } @@ -975,7 +899,7 @@ public: { if (initialised) { - dispatch (effStopProcess, 0, 0, 0, 0); + dispatch (plugInOpcodeStopProcess, 0, 0, 0, 0); setPower (false); } @@ -1008,12 +932,34 @@ public: bool supportsDoublePrecisionProcessing() const override { - return ((effect->flags & effFlagsCanReplacing) != 0 - && (effect->flags & effFlagsCanDoubleReplacing) != 0); + return ((vstEffect->flags & vstEffectFlagInplaceAudio) != 0 + && (vstEffect->flags & vstEffectFlagInplaceDoubleAudio) != 0); } //============================================================================== - bool hasEditor() const override { return effect != nullptr && (effect->flags & effFlagsHasEditor) != 0; } + bool canAddBus (bool) const override { return false; } + bool canRemoveBus (bool) const override { return false; } + + bool isBusesLayoutSupported (const BusesLayout& layouts) const override + { + const int numInputBuses = getBusCount (true); + const int numOutputBuses = getBusCount (false); + + // it's not possible to change layout if there are sidechains/aux buses + if (numInputBuses > 1 || numOutputBuses > 1) + return (layouts == getBusesLayout()); + + return (layouts.getNumChannels (true, 0) <= vstEffect->numInputChannels + && layouts.getNumChannels (false, 0) <= vstEffect->numOutputChannels); + } + + //============================================================================== + #if JUCE_IOS + bool hasEditor() const override { return false; } + #else + bool hasEditor() const override { return vstEffect != nullptr && (vstEffect->flags & vstEffectFlagHasEditor) != 0; } + #endif + AudioProcessorEditor* createEditor() override; //============================================================================== @@ -1021,9 +967,9 @@ public: { if (isValidChannel (index, true)) { - VstPinProperties pinProps; - if (dispatch (effGetInputProperties, index, 0, &pinProps, 0.0f) != 0) - return String (pinProps.label, sizeof (pinProps.label)); + VstPinInfo pinProps; + if (dispatch (plugInOpcodeGetInputPinProperties, index, 0, &pinProps, 0.0f) != 0) + return String (pinProps.text, sizeof (pinProps.text)); } return String(); @@ -1034,9 +980,9 @@ public: if (! isValidChannel (index, true)) return false; - VstPinProperties pinProps; - if (dispatch (effGetInputProperties, index, 0, &pinProps, 0.0f) != 0) - return (pinProps.flags & kVstPinIsStereo) != 0; + VstPinInfo pinProps; + if (dispatch (plugInOpcodeGetInputPinProperties, index, 0, &pinProps, 0.0f) != 0) + return (pinProps.flags & vstPinInfoFlagIsStereo) != 0; return true; } @@ -1045,9 +991,9 @@ public: { if (isValidChannel (index, false)) { - VstPinProperties pinProps; - if (dispatch (effGetOutputProperties, index, 0, &pinProps, 0.0f) != 0) - return String (pinProps.label, sizeof (pinProps.label)); + VstPinInfo pinProps; + if (dispatch (plugInOpcodeGetOutputPinProperties, index, 0, &pinProps, 0.0f) != 0) + return String (pinProps.text, sizeof (pinProps.text)); } return String(); @@ -1058,9 +1004,9 @@ public: if (! isValidChannel (index, false)) return false; - VstPinProperties pinProps; - if (dispatch (effGetOutputProperties, index, 0, &pinProps, 0.0f) != 0) - return (pinProps.flags & kVstPinIsStereo) != 0; + VstPinInfo pinProps; + if (dispatch (plugInOpcodeGetOutputPinProperties, index, 0, &pinProps, 0.0f) != 0) + return (pinProps.flags & vstPinInfoFlagIsStereo) != 0; return true; } @@ -1072,14 +1018,14 @@ public: } //============================================================================== - int getNumParameters() override { return effect != nullptr ? effect->numParams : 0; } + int getNumParameters() override { return vstEffect != nullptr ? vstEffect->numParameters : 0; } float getParameter (int index) override { - if (effect != nullptr && isPositiveAndBelow (index, (int) effect->numParams)) + if (vstEffect != nullptr && isPositiveAndBelow (index, (int) vstEffect->numParameters)) { const ScopedLock sl (lock); - return effect->getParameter (effect, index); + return vstEffect->getParameterValueFunction (vstEffect, index); } return 0.0f; @@ -1087,40 +1033,40 @@ public: void setParameter (int index, float newValue) override { - if (effect != nullptr && isPositiveAndBelow (index, (int) effect->numParams)) + if (vstEffect != nullptr && isPositiveAndBelow (index, (int) vstEffect->numParameters)) { const ScopedLock sl (lock); - if (effect->getParameter (effect, index) != newValue) - effect->setParameter (effect, index, newValue); + if (vstEffect->getParameterValueFunction (vstEffect, index) != newValue) + vstEffect->setParameterValueFunction (vstEffect, index, newValue); } } - const String getParameterName (int index) override { return getTextForOpcode (index, effGetParamName); } - const String getParameterText (int index) override { return getTextForOpcode (index, effGetParamDisplay); } - String getParameterLabel (int index) const override { return getTextForOpcode (index, effGetParamLabel); } + const String getParameterName (int index) override { return getTextForOpcode (index, plugInOpcodeGetParameterName); } + const String getParameterText (int index) override { return getTextForOpcode (index, plugInOpcodeGetParameterText); } + String getParameterLabel (int index) const override { return getTextForOpcode (index, plugInOpcodeGetParameterLabel); } bool isParameterAutomatable (int index) const override { - if (effect != nullptr) + if (vstEffect != nullptr) { - jassert (index >= 0 && index < effect->numParams); - return dispatch (effCanBeAutomated, index, 0, 0, 0) != 0; + jassert (index >= 0 && index < vstEffect->numParameters); + return dispatch (plugInOpcodeIsParameterAutomatable, index, 0, 0, 0) != 0; } return false; } //============================================================================== - int getNumPrograms() override { return effect != nullptr ? jmax (0, effect->numPrograms) : 0; } + int getNumPrograms() override { return vstEffect != nullptr ? jmax (0, vstEffect->numPrograms) : 0; } // NB: some plugs return negative numbers from this function. - int getCurrentProgram() override { return (int) dispatch (effGetProgram, 0, 0, 0, 0); } + int getCurrentProgram() override { return (int) dispatch (plugInOpcodeGetCurrentProgram, 0, 0, 0, 0); } void setCurrentProgram (int newIndex) override { if (getNumPrograms() > 0 && newIndex != getCurrentProgram()) - dispatch (effSetProgram, 0, jlimit (0, getNumPrograms() - 1, newIndex), 0, 0); + dispatch (plugInOpcodeSetCurrentProgram, 0, jlimit (0, getNumPrograms() - 1, newIndex), 0, 0); } const String getProgramName (int index) override @@ -1130,11 +1076,11 @@ public: if (index == getCurrentProgram()) return getCurrentProgramName(); - if (effect != nullptr) + if (vstEffect != nullptr) { char nm[264] = { 0 }; - if (dispatch (effGetProgramNameIndexed, jlimit (0, getNumPrograms(), index), -1, nm, 0) != 0) + if (dispatch (plugInOpcodeGetProgramName, jlimit (0, getNumPrograms(), index), -1, nm, 0) != 0) return String::fromUTF8 (nm).trim(); } } @@ -1147,7 +1093,7 @@ public: if (index >= 0 && index == getCurrentProgram()) { if (getNumPrograms() > 0 && newName != getCurrentProgramName()) - dispatch (effSetProgramName, 0, 0, (void*) newName.substring (0, 24).toRawUTF8(), 0.0f); + dispatch (plugInOpcodeSetCurrentProgramName, 0, 0, (void*) newName.substring (0, 24).toRawUTF8(), 0.0f); } else { @@ -1165,7 +1111,7 @@ public: //============================================================================== void timerCallback() override { - if (dispatch (effIdle, 0, 0, 0, 0) == 0) + if (dispatch (plugInOpcodeIdle, 0, 0, 0, 0) == 0) stopTimer(); } @@ -1175,30 +1121,30 @@ public: updateHostDisplay(); } - VstIntPtr handleCallback (VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) + pointer_sized_int handleCallback (int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) { switch (opcode) { - case audioMasterAutomate: sendParamChangeMessageToListeners (index, opt); break; - case audioMasterProcessEvents: handleMidiFromPlugin ((const VstEvents*) ptr); break; + case hostOpcodeParameterChanged: sendParamChangeMessageToListeners (index, opt); break; + case hostOpcodePreAudioProcessingEvents: handleMidiFromPlugin ((const VstEventBlock*) ptr); break; #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4311) #endif - case audioMasterGetTime: return (VstIntPtr) &vstHostTime; + case hostOpcodeGetTimingInfo: return (pointer_sized_int) &vstHostTime; #if JUCE_MSVC #pragma warning (pop) #endif - case audioMasterIdle: + case hostOpcodeIdle: if (insideVSTCallback == 0 && MessageManager::getInstance()->isThisTheMessageThread()) { const IdleCallRecursionPreventer icrp; #if JUCE_MAC if (getActiveEditor() != nullptr) - dispatch (effEditIdle, 0, 0, 0, 0); + dispatch (plugInOpcodeEditorIdle, 0, 0, 0, 0); #endif Timer::callPendingTimersSynchronously(); @@ -1211,7 +1157,7 @@ public: } break; - case audioMasterSizeWindow: + case hostOpcodeWindowSize: if (AudioProcessorEditor* ed = getActiveEditor()) { #if JUCE_LINUX @@ -1222,53 +1168,54 @@ public: return 1; - case audioMasterUpdateDisplay: triggerAsyncUpdate(); break; - case audioMasterIOChanged: setLatencySamples (effect->initialDelay); break; - case audioMasterNeedIdle: startTimer (50); break; + case hostOpcodeUpdateView: triggerAsyncUpdate(); break; + case hostOpcodeIOModified: setLatencySamples (vstEffect->latency); break; + case hostOpcodeNeedsIdle: startTimer (50); break; - case audioMasterGetSampleRate: return (VstIntPtr) (getSampleRate() > 0 ? getSampleRate() : defaultVSTSampleRateValue); - case audioMasterGetBlockSize: return (VstIntPtr) (getBlockSize() > 0 ? getBlockSize() : defaultVSTBlockSizeValue); - case audioMasterWantMidi: wantsMidiMessages = true; break; - case audioMasterGetDirectory: return getVstDirectory(); + case hostOpcodeGetSampleRate: return (pointer_sized_int) (getSampleRate() > 0 ? getSampleRate() : defaultVSTSampleRateValue); + case hostOpcodeGetBlockSize: return (pointer_sized_int) (getBlockSize() > 0 ? getBlockSize() : defaultVSTBlockSizeValue); + case hostOpcodePlugInWantsMidi: wantsMidiMessages = true; break; + case hostOpcodeGetDirectory: return getVstDirectory(); - case audioMasterTempoAt: + case hostOpcodeTempoAt: if (extraFunctions != nullptr) - return (VstIntPtr) extraFunctions->getTempoAt ((int64) value); + return (pointer_sized_int) extraFunctions->getTempoAt ((int64) value); break; - case audioMasterGetAutomationState: + case hostOpcodeGetAutomationState: if (extraFunctions != nullptr) - return (VstIntPtr) extraFunctions->getAutomationState(); + return (pointer_sized_int) extraFunctions->getAutomationState(); break; - case audioMasterPinConnected: + case hostOpcodePinConnected: return isValidChannel (index, value == 0) ? 0 : 1; // (yes, 0 = true) - case audioMasterGetCurrentProcessLevel: + case hostOpcodeGetCurrentAudioProcessingLevel: return isNonRealtime() ? 4 : 0; - // none of these are handled (yet).. - case audioMasterBeginEdit: - case audioMasterEndEdit: - case audioMasterSetTime: - case audioMasterGetParameterQuantization: - case audioMasterGetInputLatency: - case audioMasterGetOutputLatency: - case audioMasterGetPreviousPlug: - case audioMasterGetNextPlug: - case audioMasterWillReplaceOrAccumulate: - case audioMasterOfflineStart: - case audioMasterOfflineRead: - case audioMasterOfflineWrite: - case audioMasterOfflineGetCurrentPass: - case audioMasterOfflineGetCurrentMetaPass: - case audioMasterVendorSpecific: - case audioMasterSetIcon: - case audioMasterGetLanguage: - case audioMasterOpenWindow: - case audioMasterCloseWindow: + // none of these are handled (yet)... + case hostOpcodeSetTime: + case hostOpcodeGetParameterInterval: + case hostOpcodeGetInputLatency: + case hostOpcodeGetOutputLatency: + case hostOpcodeGetPreviousPlugIn: + case hostOpcodeGetNextPlugIn: + case hostOpcodeWillReplace: + case hostOpcodeOfflineStart: + case hostOpcodeOfflineReadSource: + case hostOpcodeOfflineWrite: + case hostOpcodeOfflineGetCurrentPass: + case hostOpcodeOfflineGetCurrentMetaPass: + case hostOpcodeGetOutputSpeakerConfiguration: + case hostOpcodeManufacturerSpecific: + case hostOpcodeSetIcon: + case hostOpcodeGetLanguage: + case hostOpcodeOpenEditorWindow: + case hostOpcodeCloseEditorWindow: + case hostOpcodeParameterChangeGestureBegin: + case hostOpcodeParameterChangeGestureEnd: break; default: @@ -1279,11 +1226,11 @@ public: } // handles non plugin-specific callbacks.. - static VstIntPtr handleGeneralCallback (VstInt32 opcode, VstInt32 /*index*/, VstIntPtr /*value*/, void *ptr, float /*opt*/) + static pointer_sized_int handleGeneralCallback (int32 opcode, int32 /*index*/, pointer_sized_int /*value*/, void *ptr, float /*opt*/) { switch (opcode) { - case audioMasterCanDo: + case hostOpcodeCanHostDo: { static const char* canDos[] = { "supplyIdle", "sendVstEvents", @@ -1302,27 +1249,27 @@ public: return 0; } - case audioMasterVersion: return 2400; - case audioMasterCurrentId: return shellUIDToCreate; - case audioMasterGetNumAutomatableParameters: return 0; - case audioMasterGetAutomationState: return 1; - case audioMasterGetVendorVersion: return 0x0101; + case hostOpcodeVstVersion: return 2400; + case hostOpcodeCurrentId: return shellUIDToCreate; + case hostOpcodeGetNumberOfAutomatableParameters: return 0; + case hostOpcodeGetAutomationState: return 1; + case hostOpcodeGetManufacturerVersion: return 0x0101; - case audioMasterGetVendorString: - case audioMasterGetProductString: + case hostOpcodeGetManufacturerName: + case hostOpcodeGetProductName: { String hostName ("Juce VST Host"); if (JUCEApplicationBase* app = JUCEApplicationBase::getInstance()) hostName = app->getApplicationName(); - hostName.copyToUTF8 ((char*) ptr, (size_t) jmin (kVstMaxVendorStrLen, kVstMaxProductStrLen) - 1); + hostName.copyToUTF8 ((char*) ptr, (size_t) jmin (vstMaxManufacturerStringLength, vstMaxPlugInNameStringLength) - 1); break; } - case audioMasterGetSampleRate: return (VstIntPtr) defaultVSTSampleRateValue; - case audioMasterGetBlockSize: return (VstIntPtr) defaultVSTBlockSizeValue; - case audioMasterSetOutputSampleRate: return 0; + case hostOpcodeGetSampleRate: return (pointer_sized_int) defaultVSTSampleRateValue; + case hostOpcodeGetBlockSize: return (pointer_sized_int) defaultVSTBlockSizeValue; + case hostOpcodeSetOutputSampleRate: return 0; default: DBG ("*** Unhandled VST Callback: " + String ((int) opcode)); @@ -1333,11 +1280,11 @@ public: } //============================================================================== - VstIntPtr dispatch (const int opcode, const int index, const VstIntPtr value, void* const ptr, float opt) const + pointer_sized_int dispatch (const int opcode, const int index, const pointer_sized_int value, void* const ptr, float opt) const { - VstIntPtr result = 0; + pointer_sized_int result = 0; - if (effect != nullptr) + if (vstEffect != nullptr) { const ScopedLock sl (lock); const IdleCallRecursionPreventer icrp; @@ -1347,17 +1294,17 @@ public: #if JUCE_MAC const ResFileRefNum oldResFile = CurResFile(); - if (module->resFileId != 0) - UseResFile (module->resFileId); + if (vstModule->resFileId != 0) + UseResFile (vstModule->resFileId); #endif - result = effect->dispatcher (effect, opcode, index, value, ptr, opt); + result = vstEffect->dispatchFunction (vstEffect, opcode, index, value, ptr, opt); #if JUCE_MAC const ResFileRefNum newResFile = CurResFile(); if (newResFile != oldResFile) // avoid confusing the parent app's resource file with the plug-in's { - module->resFileId = newResFile; + vstModule->resFileId = newResFile; UseResFile (oldResFile); } #endif @@ -1481,7 +1428,7 @@ public: set->fxID = fxbSwap (getUID()); set->fxVersion = fxbSwap (getVersionNumber()); set->numPrograms = fxbSwap (numPrograms); - set->chunkSize = fxbSwap ((VstInt32) chunk.getSize()); + set->chunkSize = fxbSwap ((int32) chunk.getSize()); chunk.copyTo (set->chunk, 0, chunk.getSize()); } @@ -1498,7 +1445,7 @@ public: set->fxID = fxbSwap (getUID()); set->fxVersion = fxbSwap (getVersionNumber()); set->numPrograms = fxbSwap (numPrograms); - set->chunkSize = fxbSwap ((VstInt32) chunk.getSize()); + set->chunkSize = fxbSwap ((int32) chunk.getSize()); getCurrentProgramName().copyToUTF8 (set->name, sizeof (set->name) - 1); chunk.copyTo (set->chunk, 0, chunk.getSize()); @@ -1553,14 +1500,14 @@ public: return true; } - bool usesChunks() const noexcept { return effect != nullptr && (effect->flags & effFlagsProgramChunks) != 0; } + bool usesChunks() const noexcept { return vstEffect != nullptr && (vstEffect->flags & vstEffectFlagDataInChunks) != 0; } bool getChunkData (MemoryBlock& mb, bool isPreset, int maxSizeMB) const { if (usesChunks()) { void* data = nullptr; - const size_t bytes = (size_t) dispatch (effGetChunk, isPreset ? 1 : 0, 0, &data, 0.0f); + const size_t bytes = (size_t) dispatch (plugInOpcodeGetData, isPreset ? 1 : 0, 0, &data, 0.0f); if (data != nullptr && bytes <= (size_t) maxSizeMB * 1024 * 1024) { @@ -1578,7 +1525,7 @@ public: { if (size > 0 && usesChunks()) { - dispatch (effSetChunk, isPreset ? 1 : 0, size, (void*) data, 0.0f); + dispatch (plugInOpcodeSetData, isPreset ? 1 : 0, size, (void*) data, 0.0f); if (! isPreset) updateStoredProgramNames(); @@ -1589,8 +1536,8 @@ public: return false; } - AEffect* effect; - ModuleHandle::Ptr module; + VstEffectInterface* vstEffect; + ModuleHandle::Ptr vstModule; ScopedPointer extraFunctions; bool usesCocoaNSView; @@ -1604,7 +1551,102 @@ private: CriticalSection midiInLock; MidiBuffer incomingMidi; VSTMidiEventList midiEventsToSend; - VstTimeInfo vstHostTime; + VstTimingInformation vstHostTime; + + //============================================================================== + static VstEffectInterface* constructEffect(const ModuleHandle::Ptr& module) + { + VstEffectInterface* effect = nullptr; + try + { + const IdleCallRecursionPreventer icrp; + _fpreset(); + + JUCE_VST_LOG ("Creating VST instance: " + module->pluginName); + + #if JUCE_MAC + if (module->resFileId != 0) + UseResFile (module->resFileId); + #endif + + { + JUCE_VST_WRAPPER_INVOKE_MAIN + } + + if (effect != nullptr && effect->interfaceIdentifier == juceVstInterfaceIdentifier) + { + jassert (effect->hostSpace2 == 0); + jassert (effect->effectPointer != 0); + + _fpreset(); // some dodgy plugs mess around with this + } + else + { + effect = nullptr; + } + } + catch (...) + {} + + return effect; + } + + static BusesProperties queryBusIO (VstEffectInterface* effect) + { + BusesProperties returnValue; + + VstSpeakerConfiguration* inArr = nullptr, *outArr = nullptr; + if (effect->dispatchFunction (effect, plugInOpcodeGetSpeakerArrangement, 0, reinterpret_cast (&inArr), &outArr, 0.0f) == 0) + inArr = outArr = nullptr; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const int opcode = (isInput ? plugInOpcodeGetInputPinProperties : plugInOpcodeGetOutputPinProperties); + const int maxChannels = (isInput ? effect->numInputChannels : effect->numOutputChannels); + const VstSpeakerConfiguration* arr = (isInput ? inArr : outArr); + bool busAdded = false; + + VstPinInfo pinProps; + AudioChannelSet layout; + for (int ch = 0; ch < maxChannels; ch += layout.size()) + { + if (effect->dispatchFunction (effect, opcode, ch, 0, &pinProps, 0.0f) == 0) + break; + + if ((pinProps.flags & vstPinInfoFlagValid) != 0) + { + layout = SpeakerMappings::vstArrangementTypeToChannelSet (pinProps.configurationType, 0); + if (layout.isDisabled()) + break; + } + else + { + layout = ((pinProps.flags & vstPinInfoFlagIsStereo) != 0 ? AudioChannelSet::stereo() : AudioChannelSet::mono()); + } + + busAdded = true; + returnValue.addBus (isInput, pinProps.text, layout, true); + } + + // no buses? + if (! busAdded && maxChannels > 0) + { + String busName = (isInput ? "Input" : "Output"); + if (effect->dispatchFunction (effect, opcode, 0, 0, &pinProps, 0.0f) != 0) + busName = pinProps.text; + + if (arr != nullptr) + layout = SpeakerMappings::vstArrangementTypeToChannelSet (*arr); + else + layout = AudioChannelSet::canonicalChannelSet (maxChannels); + + returnValue.addBus (isInput, busName, layout, true); + } + } + + return returnValue; + } //============================================================================== template @@ -1621,22 +1663,26 @@ private: if (currentPlayHead->getCurrentPosition (position)) { - vstHostTime.samplePos = (double) position.timeInSamples; - vstHostTime.tempo = position.bpm; - vstHostTime.timeSigNumerator = position.timeSigNumerator; - vstHostTime.timeSigDenominator = position.timeSigDenominator; - vstHostTime.ppqPos = position.ppqPosition; - vstHostTime.barStartPos = position.ppqPositionOfLastBarStart; - vstHostTime.flags |= kVstTempoValid | kVstTimeSigValid | kVstPpqPosValid | kVstBarsValid; - - VstInt32 newTransportFlags = 0; - if (position.isPlaying) newTransportFlags |= kVstTransportPlaying; - if (position.isRecording) newTransportFlags |= kVstTransportRecording; - - if (newTransportFlags != (vstHostTime.flags & (kVstTransportPlaying | kVstTransportRecording))) - vstHostTime.flags = (vstHostTime.flags & ~(kVstTransportPlaying | kVstTransportRecording)) | newTransportFlags | kVstTransportChanged; + vstHostTime.samplePosition = (double) position.timeInSamples; + vstHostTime.tempoBPM = position.bpm; + vstHostTime.timeSignatureNumerator = position.timeSigNumerator; + vstHostTime.timeSignatureDenominator = position.timeSigDenominator; + vstHostTime.musicalPosition = position.ppqPosition; + vstHostTime.lastBarPosition = position.ppqPositionOfLastBarStart; + vstHostTime.flags |= vstTimingInfoFlagTempoValid + | vstTimingInfoFlagTimeSignatureValid + | vstTimingInfoFlagMusicalPositionValid + | vstTimingInfoFlagLastBarPositionValid; + + int32 newTransportFlags = 0; + if (position.isPlaying) newTransportFlags |= vstTimingInfoFlagCurrentlyPlaying; + if (position.isRecording) newTransportFlags |= vstTimingInfoFlagCurrentlyRecording; + + if (newTransportFlags != (vstHostTime.flags & (vstTimingInfoFlagCurrentlyPlaying + | vstTimingInfoFlagCurrentlyRecording))) + vstHostTime.flags = (vstHostTime.flags & ~(vstTimingInfoFlagCurrentlyPlaying | vstTimingInfoFlagCurrentlyRecording)) | newTransportFlags | vstTimingInfoFlagTransportChanged; else - vstHostTime.flags &= ~kVstTransportChanged; + vstHostTime.flags &= ~vstTimingInfoFlagTransportChanged; switch (position.frameRate) { @@ -1651,18 +1697,18 @@ private: if (position.isLooping) { - vstHostTime.cycleStartPos = position.ppqLoopStart; - vstHostTime.cycleEndPos = position.ppqLoopEnd; - vstHostTime.flags |= (kVstCyclePosValid | kVstTransportCycleActive); + vstHostTime.loopStartPosition = position.ppqLoopStart; + vstHostTime.loopEndPosition = position.ppqLoopEnd; + vstHostTime.flags |= (vstTimingInfoFlagLoopPositionValid | vstTimingInfoFlagLoopActive); } else { - vstHostTime.flags &= ~(kVstCyclePosValid | kVstTransportCycleActive); + vstHostTime.flags &= ~(vstTimingInfoFlagLoopPositionValid | vstTimingInfoFlagLoopActive); } } } - vstHostTime.nanoSeconds = getVSTHostTimeNanoseconds(); + vstHostTime.systemTimeNanoseconds = getVSTHostTimeNanoseconds(); if (wantsMidiMessages) { @@ -1679,7 +1725,7 @@ private: jlimit (0, numSamples - 1, samplePosition)); } - effect->dispatcher (effect, effProcessEvents, 0, 0, midiEventsToSend.events, 0); + vstEffect->dispatchFunction (vstEffect, plugInOpcodePreAudioProcessingEvents, 0, 0, midiEventsToSend.events, 0); } _clearfp(); @@ -1703,35 +1749,35 @@ private: } //============================================================================== - inline void invokeProcessFunction (AudioBuffer& buffer, VstInt32 sampleFrames) + inline void invokeProcessFunction (AudioBuffer& buffer, int32 sampleFrames) { - if ((effect->flags & effFlagsCanReplacing) != 0) + if ((vstEffect->flags & vstEffectFlagInplaceAudio) != 0) { - effect->processReplacing (effect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), sampleFrames); + vstEffect->processAudioInplaceFunction (vstEffect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), sampleFrames); } else { - tempBuffer.setSize (effect->numOutputs, sampleFrames); + tempBuffer.setSize (vstEffect->numOutputChannels, sampleFrames); tempBuffer.clear(); - effect->process (effect, buffer.getArrayOfWritePointers(), tempBuffer.getArrayOfWritePointers(), sampleFrames); + vstEffect->processAudioFunction (vstEffect, buffer.getArrayOfWritePointers(), tempBuffer.getArrayOfWritePointers(), sampleFrames); - for (int i = effect->numOutputs; --i >= 0;) + for (int i = vstEffect->numOutputChannels; --i >= 0;) buffer.copyFrom (i, 0, tempBuffer.getReadPointer (i), sampleFrames); } } - inline void invokeProcessFunction (AudioBuffer& buffer, VstInt32 sampleFrames) + inline void invokeProcessFunction (AudioBuffer& buffer, int32 sampleFrames) { - effect->processDoubleReplacing (effect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), sampleFrames); + vstEffect->processDoubleAudioInplaceFunction (vstEffect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), sampleFrames); } //============================================================================== void setHostTimeFrameRate (long frameRateIndex, double frameRate, double currentTime) noexcept { - vstHostTime.flags |= kVstSmpteValid; - vstHostTime.smpteFrameRate = (VstInt32) frameRateIndex; - vstHostTime.smpteOffset = (VstInt32) (currentTime * 80.0 * frameRate + 0.5); + vstHostTime.flags |= vstTimingInfoFlagSmpteValid; + vstHostTime.smpteRate = (int32) frameRateIndex; + vstHostTime.smpteOffset = (int32) (currentTime * 80.0 * frameRate + 0.5); } bool restoreProgramSettings (const fxProgram* const prog) @@ -1750,27 +1796,27 @@ private: return false; } - String getTextForOpcode (const int index, const AEffectOpcodes opcode) const + String getTextForOpcode (const int index, const VstHostToPlugInOpcodes opcode) const { - if (effect == nullptr) + if (vstEffect == nullptr) return String(); - jassert (index >= 0 && index < effect->numParams); - char nm [256] = { 0 }; + jassert (index >= 0 && index < vstEffect->numParameters); + char nm[256] = { 0 }; dispatch (opcode, index, 0, nm, 0); - return String (CharPointer_UTF8 (nm)).trim(); + return String::createStringFromData (nm, (int) sizeof (nm)).trim(); } String getCurrentProgramName() { String progName; - if (effect != nullptr) + if (vstEffect != nullptr) { { char nm[256] = { 0 }; - dispatch (effGetProgramName, 0, 0, nm, 0); - progName = String (CharPointer_UTF8 (nm)).trim(); + dispatch (plugInOpcodeGetCurrentProgramName, 0, 0, nm, 0); + progName = String::createStringFromData (nm, (int) sizeof (nm)).trim(); } const int index = getCurrentProgram(); @@ -1807,12 +1853,12 @@ private: void updateStoredProgramNames() { - if (effect != nullptr && getNumPrograms() > 0) + if (vstEffect != nullptr && getNumPrograms() > 0) { char nm[256] = { 0 }; // only do this if the plugin can't use indexed names.. - if (dispatch (effGetProgramNameIndexed, 0, -1, nm, 0) == 0) + if (dispatch (plugInOpcodeGetProgramName, 0, -1, nm, 0) == 0) { const int oldProgram = getCurrentProgram(); MemoryBlock oldSettings; @@ -1830,7 +1876,7 @@ private: } } - void handleMidiFromPlugin (const VstEvents* const events) + void handleMidiFromPlugin (const VstEventBlock* const events) { if (events != nullptr) { @@ -1861,21 +1907,21 @@ private: setParameter (i, p[i]); } - VstIntPtr getVstDirectory() const + pointer_sized_int getVstDirectory() const { #if JUCE_MAC - return (VstIntPtr) (void*) &module->parentDirFSSpec; + return (pointer_sized_int) (void*) &vstModule->parentDirFSSpec; #else - return (VstIntPtr) (pointer_sized_uint) module->fullParentDirectoryPathName.toRawUTF8(); + return (pointer_sized_int) (pointer_sized_uint) vstModule->fullParentDirectoryPathName.toRawUTF8(); #endif } //============================================================================== - int getVersionNumber() const noexcept { return effect != nullptr ? effect->version : 0; } + int getVersionNumber() const noexcept { return vstEffect != nullptr ? vstEffect->plugInVersion : 0; } String getVersion() const { - unsigned int v = (unsigned int) dispatch (effGetVendorVersion, 0, 0, 0, 0); + unsigned int v = (unsigned int) dispatch (plugInOpcodeGetManufacturerVersion, 0, 0, 0, 0); String s; @@ -1936,7 +1982,7 @@ private: void setPower (const bool on) { - dispatch (effMainsChanged, 0, on ? 1 : 0, 0, 0); + dispatch (plugInOpcodeResumeSuspend, 0, on ? 1 : 0, 0, 0); isPowerOn = on; } @@ -1944,6 +1990,7 @@ private: }; //============================================================================== +#if ! JUCE_IOS class VSTPluginWindow; static Array activeVSTWindows; @@ -1976,6 +2023,8 @@ public: pluginProc = None; #elif JUCE_MAC + ignoreUnused (recursiveResize, pluginRefusesToResize, alreadyInside); + #if JUCE_SUPPORT_CARBON if (! plug.usesCocoaNSView) addAndMakeVisible (carbonWrapper = new CarbonWrapperComponent (*this)); @@ -2141,7 +2190,7 @@ public: if (! reentrantGuard) { reentrantGuard = true; - plugin.dispatch (effEditIdle, 0, 0, 0, 0); + plugin.dispatch (plugInOpcodeEditorIdle, 0, 0, 0, 0); reentrantGuard = false; } @@ -2185,7 +2234,7 @@ public: activeVSTWindows.add (this); #if JUCE_MAC - dispatch (effEditTop, 0, 0, 0, 0); + dispatch (plugInOpcodeeffEditorTop, 0, 0, 0, 0); #endif } @@ -2228,24 +2277,24 @@ private: isOpen = true; - ERect* rect = nullptr; - dispatch (effEditGetRect, 0, 0, &rect, 0); - dispatch (effEditOpen, 0, 0, parentWindow, 0); + VstEditorBounds* rect = nullptr; + dispatch (plugInOpcodeGetEditorBounds, 0, 0, &rect, 0); + dispatch (plugInOpcodeOpenEditor, 0, 0, parentWindow, 0); // do this before and after like in the steinberg example - dispatch (effEditGetRect, 0, 0, &rect, 0); - dispatch (effGetProgram, 0, 0, 0, 0); // also in steinberg code + dispatch (plugInOpcodeGetEditorBounds, 0, 0, &rect, 0); + dispatch (plugInOpcodeGetCurrentProgram, 0, 0, 0, 0); // also in steinberg code // Install keyboard hooks - pluginWantsKeys = (dispatch (effKeysRequired, 0, 0, 0, 0) == 0); + pluginWantsKeys = (dispatch (plugInOpcodeKeyboardFocusRequired, 0, 0, 0, 0) == 0); // double-check it's not too tiny int w = 250, h = 150; if (rect != nullptr) { - w = rect->right - rect->left; - h = rect->bottom - rect->top; + w = rect->rightmost - rect->leftmost; + h = rect->lower - rect->upper; if (w == 0 || h == 0) { @@ -2272,16 +2321,16 @@ private: JUCE_VST_LOG ("Opening VST UI: " + plugin.getName()); isOpen = true; - ERect* rect = nullptr; - dispatch (effEditGetRect, 0, 0, &rect, 0); - dispatch (effEditOpen, 0, 0, getWindowHandle(), 0); + VstEditorBounds* rect = nullptr; + dispatch (plugInOpcodeGetEditorBounds, 0, 0, &rect, 0); + dispatch (plugInOpcodeOpenEditor, 0, 0, getWindowHandle(), 0); // do this before and after like in the steinberg example - dispatch (effEditGetRect, 0, 0, &rect, 0); - dispatch (effGetProgram, 0, 0, 0, 0); // also in steinberg code + dispatch (plugInOpcodeGetEditorBounds, 0, 0, &rect, 0); + dispatch (plugInOpcodeGetCurrentProgram, 0, 0, 0, 0); // also in steinberg code // Install keyboard hooks - pluginWantsKeys = (dispatch (effKeysRequired, 0, 0, 0, 0) == 0); + pluginWantsKeys = (dispatch (plugInOpcodeKeyboardFocusRequired, 0, 0, 0, 0) == 0); #if JUCE_WINDOWS originalWndProc = 0; @@ -2312,8 +2361,8 @@ private: if (rect != nullptr) { - const int rw = rect->right - rect->left; - const int rh = rect->bottom - rect->top; + const int rw = rect->rightmost - rect->leftmost; + const int rh = rect->lower - rect->upper; if ((rw > 50 && rh > 50 && rw < 2000 && rh < 2000 && rw != w && rh != h) || ((w == 0 && rw > 0) || (h == 0 && rh > 0))) @@ -2345,8 +2394,8 @@ private: if (rect != nullptr) { - w = rect->right - rect->left; - h = rect->bottom - rect->top; + w = rect->rightmost - rect->leftmost; + h = rect->lower - rect->upper; if (w == 0 || h == 0) { @@ -2385,7 +2434,7 @@ private: JUCE_VST_LOG ("Closing VST UI: " + plugin.getName()); isOpen = false; - dispatch (effEditClose, 0, 0, 0, 0); + dispatch (plugInOpcodeCloseEditor, 0, 0, 0, 0); stopTimer(); #if JUCE_WINDOWS @@ -2405,7 +2454,7 @@ private: } //============================================================================== - VstIntPtr dispatch (const int opcode, const int index, const int value, void* const ptr, float opt) + pointer_sized_int dispatch (const int opcode, const int index, const int value, void* const ptr, float opt) { return plugin.dispatch (opcode, index, value, ptr, opt); } @@ -2611,17 +2660,17 @@ private: if (owner.isOpen) { owner.isOpen = false; - owner.dispatch (effEditClose, 0, 0, 0, 0); - owner.dispatch (effEditSleep, 0, 0, 0, 0); + owner.dispatch (plugInOpcodeCloseEditor, 0, 0, 0, 0); + owner.dispatch (plugInOpcodeSleepEditor, 0, 0, 0, 0); } } bool getEmbeddedViewSize (int& w, int& h) override { - ERect* rect = nullptr; - owner.dispatch (effEditGetRect, 0, 0, &rect, 0); - w = rect->right - rect->left; - h = rect->bottom - rect->top; + VstEditorBounds* rect = nullptr; + owner.dispatch (plugInOpcodeGetEditorBounds, 0, 0, &rect, 0); + w = rect->rightmost - rect->leftmost; + h = rect->lower - rect->upper; return true; } @@ -2631,7 +2680,7 @@ private: { alreadyInside = true; getTopLevelComponent()->toFront (true); - owner.dispatch (effEditMouse, x, y, 0, 0); + owner.dispatch (plugInOpcodeGetMouse, x, y, 0, 0); alreadyInside = false; } else @@ -2645,13 +2694,13 @@ private: if (ComponentPeer* const peer = getPeer()) { const Point pos (peer->globalToLocal (getScreenPosition())); - ERect r; - r.left = (VstInt16) pos.getX(); - r.top = (VstInt16) pos.getY(); - r.right = (VstInt16) (r.left + getWidth()); - r.bottom = (VstInt16) (r.top + getHeight()); + VstEditorBounds r; + r.leftmost = (int16) pos.getX(); + r.upper = (int16) pos.getY(); + r.rightmost = (int16) (r.leftmost + getWidth()); + r.lower = (int16) (r.upper + getHeight()); - owner.dispatch (effEditDraw, 0, 0, &r, 0); + owner.dispatch (plugInOpcodeDrawEditor, 0, 0, &r, 0); } } @@ -2682,7 +2731,7 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginWindow) }; - +#endif #if JUCE_MSVC #pragma warning (pop) #endif @@ -2690,16 +2739,20 @@ private: //============================================================================== AudioProcessorEditor* VSTPluginInstance::createEditor() { + #if JUCE_IOS + return nullptr; + #else return hasEditor() ? new VSTPluginWindow (*this) : nullptr; + #endif } //============================================================================== // entry point for all callbacks from the plugin -static VstIntPtr VSTCALLBACK audioMaster (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) +static pointer_sized_int VSTINTERFACECALL audioMaster (VstEffectInterface* effect, int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) { if (effect != nullptr) - if (VSTPluginInstance* instance = (VSTPluginInstance*) (effect->resvd2)) + if (VSTPluginInstance* instance = (VSTPluginInstance*) (effect->hostSpace2)) return instance->handleCallback (opcode, index, value, ptr, opt); return VSTPluginInstance::handleGeneralCallback (opcode, index, value, ptr, opt); @@ -2716,8 +2769,8 @@ static VSTPluginInstance* createAndUpdateDesc (VSTPluginFormat& format, PluginDe if (VSTPluginInstance* instance = dynamic_cast (p)) { #if JUCE_MAC - if (instance->module->resFileId != 0) - UseResFile (instance->module->resFileId); + if (instance->vstModule->resFileId != 0) + UseResFile (instance->vstModule->resFileId); #endif instance->fillInPluginDescription (desc); @@ -2750,7 +2803,7 @@ void VSTPluginFormat::findAllTypesForFile (OwnedArray& result // Normal plugin... results.add (new PluginDescription (desc)); - instance->dispatch (effOpen, 0, 0, 0, 0); + instance->dispatch (plugInOpcodeOpen, 0, 0, 0, 0); } else { @@ -2758,7 +2811,7 @@ void VSTPluginFormat::findAllTypesForFile (OwnedArray& result for (;;) { char shellEffectName [256] = { 0 }; - const int uid = (int) instance->dispatch (effShellGetNextPlugin, 0, 0, shellEffectName, 0); + const int uid = (int) instance->dispatch (plugInOpcodeNextPlugInUniqueID, 0, 0, shellEffectName, 0); if (uid == 0) break; @@ -2783,8 +2836,11 @@ void VSTPluginFormat::findAllTypesForFile (OwnedArray& result } } -AudioPluginInstance* VSTPluginFormat::createInstanceFromDescription (const PluginDescription& desc, - double sampleRate, int blockSize) +void VSTPluginFormat::createPluginInstance (const PluginDescription& desc, + double sampleRate, + int blockSize, + void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) { ScopedPointer result; @@ -2799,51 +2855,34 @@ AudioPluginInstance* VSTPluginFormat::createInstanceFromDescription (const Plugi { shellUIDToCreate = desc.uid; - result = new VSTPluginInstance (module); + result = VSTPluginInstance::create (module, sampleRate, blockSize); - if (result->effect != nullptr) - { - result->effect->resvd2 = (VstIntPtr) (pointer_sized_int) (VSTPluginInstance*) result; - result->initialise (sampleRate, blockSize); - } - else - { + if (result != nullptr && ! result->initialiseEffect (sampleRate, blockSize)) result = nullptr; - } } previousWorkingDirectory.setAsCurrentWorkingDirectory(); } - return result.release(); + String errorMsg; + + if (result == nullptr) + errorMsg = String (NEEDS_TRANS ("Unable to load XXX plug-in file")).replace ("XXX", "VST-2"); + + callback (userData, result.release(), errorMsg); +} + +bool VSTPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept +{ + return false; } bool VSTPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) { const File f (File::createFileWithoutCheckingPath (fileOrIdentifier)); - #if JUCE_MAC - if (f.isDirectory() && f.hasFileExtension (".vst")) - return true; - - #if JUCE_PPC - FSRef fileRef; - if (makeFSRefFromPath (&fileRef, f.getFullPathName())) - { - const short resFileId = FSOpenResFile (&fileRef, fsRdPerm); - - if (resFileId != -1) - { - const int numEffects = Count1Resources ('aEff'); - CloseResFile (resFileId); - - if (numEffects > 0) - return true; - } - } - #endif - - return false; + #if JUCE_MAC || JUCE_IOS + return f.isDirectory() && f.hasFileExtension (".vst"); #elif JUCE_WINDOWS return f.existsAsFile() && f.hasFileExtension (".dll"); #elif JUCE_LINUX @@ -2866,7 +2905,7 @@ bool VSTPluginFormat::doesPluginStillExist (const PluginDescription& desc) return File (desc.fileOrIdentifier).exists(); } -StringArray VSTPluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive) +StringArray VSTPluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive, bool) { StringArray results; @@ -2917,14 +2956,27 @@ FileSearchPath VSTPluginFormat::getDefaultLocationsToSearch() paths.add (WindowsRegistry::getValue ("HKEY_LOCAL_MACHINE\\Software\\VST\\VSTPluginsPath", programFiles + "\\VstPlugins")); return paths; + #elif JUCE_IOS + // on iOS you can only load plug-ins inside the hosts bundle folder + CFURLRef relativePluginDir = CFBundleCopyBuiltInPlugInsURL (CFBundleGetMainBundle()); + CFURLRef pluginDir = CFURLCopyAbsoluteURL (relativePluginDir); + CFRelease (relativePluginDir); + + CFStringRef path = CFURLCopyFileSystemPath (pluginDir, kCFURLPOSIXPathStyle); + CFRelease (pluginDir); + + FileSearchPath retval (String (CFStringGetCStringPtr (path, kCFStringEncodingUTF8))); + CFRelease (path); + + return retval; #endif } const XmlElement* VSTPluginFormat::getVSTXML (AudioPluginInstance* plugin) { if (VSTPluginInstance* const vst = dynamic_cast (plugin)) - if (vst->module != nullptr) - return vst->module->vstXml.get(); + if (vst->vstModule != nullptr) + return vst->vstModule->vstXml.get(); return nullptr; } @@ -2961,6 +3013,22 @@ bool VSTPluginFormat::setChunkData (AudioPluginInstance* plugin, const void* dat return false; } +AudioPluginInstance* VSTPluginFormat::createCustomVSTFromMainCall (void* entryPointFunction, + double initialSampleRate, int initialBufferSize) +{ + ModuleHandle::Ptr module = new ModuleHandle (File(), (MainCall) entryPointFunction); + + if (module->open()) + { + ScopedPointer result (VSTPluginInstance::create (module, initialSampleRate, initialBufferSize)); + + if (result != nullptr && result->initialiseEffect (initialSampleRate, initialBufferSize)) + return result.release(); + } + + return nullptr; +} + void VSTPluginFormat::setExtraFunctions (AudioPluginInstance* plugin, ExtraFunctions* functions) { ScopedPointer f (functions); @@ -2969,7 +3037,16 @@ void VSTPluginFormat::setExtraFunctions (AudioPluginInstance* plugin, ExtraFunct vst->extraFunctions = f; } -VSTPluginFormat::VstIntPtr JUCE_CALLTYPE VSTPluginFormat::dispatcher (AudioPluginInstance* plugin, int32 opcode, int32 index, VstIntPtr value, void* ptr, float opt) +AudioPluginInstance* VSTPluginFormat::getPluginInstanceFromVstEffectInterface (void* aEffect) +{ + if (VstEffectInterface* vstAEffect = reinterpret_cast (aEffect)) + if (VSTPluginInstance* instanceVST = reinterpret_cast (vstAEffect->hostSpace2)) + return dynamic_cast (instanceVST); + + return nullptr; +} + +pointer_sized_int JUCE_CALLTYPE VSTPluginFormat::dispatcher (AudioPluginInstance* plugin, int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) { if (VSTPluginInstance* vst = dynamic_cast (plugin)) return vst->dispatch (opcode, index, value, ptr, opt); diff --git a/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.h b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.h index 400d75de5..d3b7d9fa8 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.h +++ b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.h @@ -22,7 +22,7 @@ ============================================================================== */ -#if JUCE_PLUGINHOST_VST || DOXYGEN +#if (JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS)) || DOXYGEN //============================================================================== /** @@ -36,7 +36,7 @@ public: ~VSTPluginFormat(); //============================================================================== - /** Attempts to retreive the VSTXML data from a plugin. + /** Attempts to retrieve the VSTXML data from a plugin. Will return nullptr if the plugin isn't a VST, or if it doesn't have any VSTXML. */ static const XmlElement* getVSTXML (AudioPluginInstance* plugin); @@ -53,6 +53,13 @@ public: /** Attempts to set a VST's state from a chunk of memory. */ static bool setChunkData (AudioPluginInstance* plugin, const void* data, int size, bool isPreset); + /** Given a suitable function pointer to a VSTPluginMain function, this will attempt to + instantiate and return a plugin for it. + */ + static AudioPluginInstance* createCustomVSTFromMainCall (void* entryPointFunction, + double initialSampleRate, + int initialBufferSize); + //============================================================================== /** Base class for some extra functions that can be attached to a VST plugin instance. */ class ExtraFunctions @@ -75,23 +82,21 @@ public: static void setExtraFunctions (AudioPluginInstance* plugin, ExtraFunctions* functions); //============================================================================== - #if JUCE_64BIT - typedef int64 VstIntPtr; - #else - typedef int32 VstIntPtr; - #endif - /** This simply calls directly to the VST's AEffect::dispatcher() function. */ - static VstIntPtr JUCE_CALLTYPE dispatcher (AudioPluginInstance*, int32, int32, VstIntPtr, void*, float); + static pointer_sized_int JUCE_CALLTYPE dispatcher (AudioPluginInstance*, int32, int32, pointer_sized_int, void*, float); + + /** Given a VstEffectInterface* (aka vst::AEffect*), this will return the juce AudioPluginInstance + that is being used to wrap it + */ + static AudioPluginInstance* getPluginInstanceFromVstEffectInterface (void* aEffect); //============================================================================== String getName() const override { return "VST"; } void findAllTypesForFile (OwnedArray&, const String& fileOrIdentifier) override; - AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, double, int) override; bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; bool pluginNeedsRescanning (const PluginDescription&) override; - StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive) override; + StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; bool doesPluginStillExist (const PluginDescription&) override; FileSearchPath getDefaultLocationsToSearch() override; bool canScanForPlugins() const override { return true; } @@ -103,6 +108,14 @@ public: */ virtual void aboutToScanVSTShellPlugin (const PluginDescription&); +private: + //============================================================================== + void createPluginInstance (const PluginDescription&, double initialSampleRate, + int initialBufferSize, void* userData, + void (*callback) (void*, AudioPluginInstance*, const String&)) override; + + bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; + private: void recursiveFileSearch (StringArray&, const File&, bool recursive); diff --git a/source/modules/juce_audio_processors/juce_audio_processors.cpp b/source/modules/juce_audio_processors/juce_audio_processors.cpp index b584aa26a..f5060dc0f 100644 --- a/source/modules/juce_audio_processors/juce_audio_processors.cpp +++ b/source/modules/juce_audio_processors/juce_audio_processors.cpp @@ -31,10 +31,15 @@ #error "Incorrect use of JUCE cpp file" #endif +#include "AppConfig.h" + #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 #include "juce_audio_processors.h" -#include "../juce_gui_extra/juce_gui_extra.h" + +#if ! JUCE_AUDIO_PROCESSOR_NO_GUI + #include +#endif //============================================================================== #if JUCE_MAC @@ -70,9 +75,18 @@ static inline bool arrayContainsPlugin (const OwnedArray& lis return false; } -#if JUCE_MAC +#if JUCE_MAC || JUCE_IOS + +#if JUCE_IOS + #define JUCE_IOS_MAC_VIEW UIView + typedef UIViewComponent ViewComponentBaseClass; +#else + #define JUCE_IOS_MAC_VIEW NSView + typedef NSViewComponent ViewComponentBaseClass; +#endif + //============================================================================== - + struct AutoResizingNSViewComponent : public NSViewComponent, private AsyncUpdater { AutoResizingNSViewComponent(); @@ -84,7 +98,7 @@ struct AutoResizingNSViewComponent : public NSViewComponent, struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewComponent, private Timer { AutoResizingNSViewComponentWithParent(); - NSView* getChildView() const; + JUCE_IOS_MAC_VIEW* getChildView() const; void timerCallback() override; }; @@ -112,27 +126,29 @@ void AutoResizingNSViewComponent::handleAsyncUpdate() resizeToFitView(); } +//============================================================================== + AutoResizingNSViewComponentWithParent::AutoResizingNSViewComponentWithParent() { - NSView* v = [[NSView alloc] init]; + JUCE_IOS_MAC_VIEW* v = [[JUCE_IOS_MAC_VIEW alloc] init]; setView (v); [v release]; - - startTimer(500); + + startTimer(30); } -NSView* AutoResizingNSViewComponentWithParent::getChildView() const +JUCE_IOS_MAC_VIEW* AutoResizingNSViewComponentWithParent::getChildView() const { - if (NSView* parent = (NSView*)getView()) + if (JUCE_IOS_MAC_VIEW* parent = (JUCE_IOS_MAC_VIEW*)getView()) if ([[parent subviews] count] > 0) return [[parent subviews] objectAtIndex: 0]; - + return nil; } void AutoResizingNSViewComponentWithParent::timerCallback() { - if (NSView* child = getChildView()) + if (JUCE_IOS_MAC_VIEW* child = getChildView()) { stopTimer(); setView(child); @@ -148,12 +164,10 @@ void AutoResizingNSViewComponentWithParent::timerCallback() #include "format/juce_AudioPluginFormat.cpp" #include "format/juce_AudioPluginFormatManager.cpp" #include "processors/juce_AudioProcessor.cpp" -#include "processors/juce_AudioChannelSet.cpp" #include "processors/juce_AudioProcessorEditor.cpp" #include "processors/juce_AudioProcessorGraph.cpp" #include "processors/juce_GenericAudioProcessorEditor.cpp" #include "processors/juce_PluginDescription.cpp" -#include "processors/AudioProcessorGraphMultiThreaded.cpp" #include "format_types/juce_LADSPAPluginFormat.cpp" #include "format_types/juce_VSTPluginFormat.cpp" #include "format_types/juce_VST3PluginFormat.cpp" diff --git a/source/modules/juce_audio_processors/juce_audio_processors.h b/source/modules/juce_audio_processors/juce_audio_processors.h index f5a0126f3..fb27cfc1b 100644 --- a/source/modules/juce_audio_processors/juce_audio_processors.h +++ b/source/modules/juce_audio_processors/juce_audio_processors.h @@ -22,17 +22,42 @@ ============================================================================== */ +/******************************************************************************* + The block below describes the properties of this module, and is read by + the Projucer to automatically generate project code that uses it. + For details about the syntax and how to create or use a module, see the + JUCE Module Format.txt file. + + + BEGIN_JUCE_MODULE_DECLARATION + + ID: juce_audio_processors + vendor: juce + version: 4.3.0 + name: JUCE audio processor classes + description: Classes for loading and playing VST, AU, or internally-generated audio processors. + website: http://www.juce.com/juce + license: GPL/Commercial + + dependencies: juce_gui_extra, juce_audio_basics + OSXFrameworks: CoreAudio CoreMIDI AudioToolbox + iOSFrameworks: AudioToolbox + + END_JUCE_MODULE_DECLARATION + +*******************************************************************************/ + + #ifndef JUCE_AUDIO_PROCESSORS_H_INCLUDED #define JUCE_AUDIO_PROCESSORS_H_INCLUDED -#include "../juce_gui_basics/juce_gui_basics.h" -#include "../juce_audio_basics/juce_audio_basics.h" +#include +#include //============================================================================== /** Config: JUCE_PLUGINHOST_VST - Enables the VST audio plugin hosting classes. This requires the Steinberg VST SDK to be - installed on your machine. + Enables the VST audio plugin hosting classes. @see VSTPluginFormat, VST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU, JUCE_PLUGINHOST_VST3 */ @@ -63,10 +88,14 @@ // #error "You need to set either the JUCE_PLUGINHOST_AU and/or JUCE_PLUGINHOST_VST and/or JUCE_PLUGINHOST_VST3 flags if you're using this module!" #endif -#if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT) +#if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT || JUCE_IOS) #define JUCE_SUPPORT_CARBON 1 #endif +#ifndef JUCE_SUPPORT_LEGACY_AUDIOPROCESSOR + #define JUCE_SUPPORT_LEGACY_AUDIOPROCESSOR 1 +#endif + //============================================================================== //============================================================================== namespace juce @@ -77,13 +106,11 @@ class AudioProcessor; #include "processors/juce_AudioProcessorEditor.h" #include "processors/juce_AudioProcessorListener.h" #include "processors/juce_AudioProcessorParameter.h" -#include "processors/juce_AudioChannelSet.h" #include "processors/juce_AudioProcessor.h" #include "processors/juce_PluginDescription.h" #include "processors/juce_AudioPluginInstance.h" #include "processors/juce_AudioProcessorGraph.h" #include "processors/juce_GenericAudioProcessorEditor.h" -#include "processors/AudioProcessorGraphMultiThreaded.h" #include "format/juce_AudioPluginFormat.h" #include "format/juce_AudioPluginFormatManager.h" #include "scanning/juce_KnownPluginList.h" diff --git a/source/modules/juce_audio_processors/processors/juce_AudioPluginInstance.h b/source/modules/juce_audio_processors/processors/juce_AudioPluginInstance.h index 675cab452..04308a961 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioPluginInstance.h +++ b/source/modules/juce_audio_processors/processors/juce_AudioPluginInstance.h @@ -78,6 +78,9 @@ public: protected: //============================================================================== AudioPluginInstance() {} + AudioPluginInstance (const BusesProperties& ioLayouts) : AudioProcessor (ioLayouts) {} + template + AudioPluginInstance (const short channelLayoutList[numLayouts][2]) : AudioProcessor (channelLayoutList) {} JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginInstance) }; diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index a96c7d98c..33a5f0818 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -30,37 +30,45 @@ void JUCE_CALLTYPE AudioProcessor::setTypeOfNextNewPlugin (AudioProcessor::Wrapp } AudioProcessor::AudioProcessor() - : wrapperType (wrapperTypeBeingCreated.get()), - playHead (nullptr), - currentSampleRate (0), - blockSize (0), - latencySamples (0), - #if JUCE_DEBUG - textRecursionCheck (false), - #endif - suspended (false), - nonRealtime (false), - processingPrecision (singlePrecision) -{ - #ifdef JucePlugin_PreferredChannelConfigurations - const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations }; - #else - const short channelConfigs[][2] = { {2, 2} }; - #endif +{ + initialise (BusesProperties().withInput ("Input", AudioChannelSet::stereo(), false) + .withOutput ("Output", AudioChannelSet::stereo(), false)); +} - #if ! JucePlugin_IsMidiEffect - #if ! JucePlugin_IsSynth - busArrangement.inputBuses.add (AudioProcessorBus ("Input", AudioChannelSet::canonicalChannelSet (channelConfigs[0][0]))); - #endif - busArrangement.outputBuses.add (AudioProcessorBus ("Output", AudioChannelSet::canonicalChannelSet (channelConfigs[0][1]))); +AudioProcessor::AudioProcessor(const BusesProperties& ioConfig) +{ + initialise (ioConfig); +} - #ifdef JucePlugin_PreferredChannelConfigurations - #if ! JucePlugin_IsSynth - AudioProcessor::setPreferredBusArrangement (true, 0, AudioChannelSet::stereo()); +void AudioProcessor::initialise (const BusesProperties& ioConfig) +{ + cachedTotalIns = 0; + cachedTotalOuts = 0; + + wrapperType = wrapperTypeBeingCreated.get(); + playHead = nullptr; + currentSampleRate = 0; + blockSize = 0; + latencySamples = 0; + + #if JUCE_DEBUG + textRecursionCheck = false; #endif - AudioProcessor::setPreferredBusArrangement (false, 0, AudioChannelSet::stereo()); - #endif - #endif + + suspended = false; + nonRealtime = false; + + processingPrecision = singlePrecision; + + const int numInputBuses = ioConfig.inputLayouts.size(); + const int numOutputBuses = ioConfig.outputLayouts.size(); + + for (int i = 0; i < numInputBuses; ++i) + createBus (true, ioConfig.inputLayouts. getReference (i)); + + for (int i = 0; i < numOutputBuses; ++i) + createBus (false, ioConfig.outputLayouts.getReference (i)); + updateSpeakerFormatStrings(); } @@ -79,6 +87,272 @@ AudioProcessor::~AudioProcessor() #endif } +//============================================================================== +bool AudioProcessor::addBus (bool isInput) +{ + if (! canAddBus (isInput)) + return false; + + BusProperties BusesProperties; + if (! canApplyBusCountChange (isInput, true, BusesProperties)) + return false; + + createBus (isInput, BusesProperties); + return true; +} + +bool AudioProcessor::removeBus (bool inputBus) +{ + const int numBuses = getBusCount (inputBus); + if (numBuses == 0) + return false; + + if (! canRemoveBus (inputBus)) + return false; + + BusProperties BusesProperties; + if (! canApplyBusCountChange (inputBus, false, BusesProperties)) + return false; + + const int busIdx = numBuses - 1; + const int numChannels = getChannelCountOfBus (inputBus, busIdx); + (inputBus ? inputBuses : outputBuses).remove (busIdx); + + audioIOChanged (true, numChannels > 0); + + return true; +} + + +//============================================================================== +bool AudioProcessor::setBusesLayout (const BusesLayout& arr) +{ + jassert (arr.inputBuses. size() == getBusCount (true) + && arr.outputBuses.size() == getBusCount (false)); + + if (arr == getBusesLayout()) + return true; + + BusesLayout copy = arr; + if (! canApplyBusesLayout (copy)) + return false; + + return applyBusLayouts (copy); +} + +bool AudioProcessor::setBusesLayoutWithoutEnabling (const BusesLayout& arr) +{ + const int numIns = getBusCount (true); + const int numOuts = getBusCount (false); + + jassert (arr.inputBuses. size() == numIns + && arr.outputBuses.size() == numOuts); + + BusesLayout request = arr; + const BusesLayout current = getBusesLayout(); + + for (int i = 0; i < numIns; ++i) + if (request.getNumChannels (true, i) == 0) + request.getChannelSet (true, i) = current.getChannelSet (true, i); + + for (int i = 0; i < numOuts; ++i) + if (request.getNumChannels (false, i) == 0) + request.getChannelSet (false, i) = current.getChannelSet (false, i); + + if (! checkBusesLayoutSupported(request)) + return false; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir != 0); + + for (int i = 0; i < (isInput ? numIns : numOuts); ++i) + { + Bus& bus = *getBus (isInput, i); + AudioChannelSet& set = request.getChannelSet (isInput, i); + + if (! bus.isEnabled()) + { + if (! set.isDisabled()) + bus.lastLayout = set; + + set = AudioChannelSet::disabled(); + } + } + } + + return setBusesLayout (request); +} + +AudioProcessor::BusesLayout AudioProcessor::getBusesLayout() const +{ + BusesLayout layouts; + const int numInputs = getBusCount (true); + const int numOutputs = getBusCount (false); + + for (int i = 0; i < numInputs; ++i) + layouts.inputBuses. add (getBus (true, i)->getCurrentLayout()); + + for (int i = 0; i < numOutputs; ++i) + layouts.outputBuses.add (getBus (false, i)->getCurrentLayout()); + + return layouts; +} + +AudioChannelSet AudioProcessor::getChannelLayoutOfBus (bool isInput, int busIdx) const noexcept +{ + const OwnedArray& buses = (isInput ? inputBuses : outputBuses); + if (Bus* bus = buses[busIdx]) + return bus->getCurrentLayout(); + + return AudioChannelSet(); +} + +bool AudioProcessor::setChannelLayoutOfBus (bool isInputBus, int busIdx, const AudioChannelSet& layout) +{ + if (Bus* bus = getBus (isInputBus, busIdx)) + { + BusesLayout layouts = bus->getBusesLayoutForLayoutChangeOfBus (layout); + + if (layouts.getChannelSet (isInputBus, busIdx) == layout) + return applyBusLayouts (layouts); + + return false; + } + + // busIdx parameter is invalid + jassertfalse; + + return false; +} + +bool AudioProcessor::enableAllBuses() +{ + BusesLayout layouts; + const int numInputs = getBusCount (true); + const int numOutputs = getBusCount (false); + + for (int i = 0; i < numInputs; ++i) + layouts.inputBuses. add (getBus (true, i)->lastLayout); + + for (int i = 0; i < numOutputs; ++i) + layouts.outputBuses.add (getBus (false, i)->lastLayout); + + return setBusesLayout (layouts); +} + +bool AudioProcessor::checkBusesLayoutSupported (const BusesLayout& layouts) const +{ + const int numInputBuses = getBusCount (true); + const int numOutputBuses = getBusCount (false); + + if (layouts.inputBuses. size() == numInputBuses + && layouts.outputBuses.size() == numOutputBuses) + return isBusesLayoutSupported (layouts); + + return false; +} + +AudioProcessor::BusesLayout AudioProcessor::getNextBestLayout (const BusesLayout& layouts) const +{ + // if you are hitting this assertion then you are requesting a next + // best layout which does not have the same number of buses as the + // audio processor. + jassert (layouts.inputBuses. size() == getBusCount (true) + && layouts.outputBuses.size() == getBusCount (false)); + + if (checkBusesLayoutSupported (layouts)) return layouts; + + BusesLayout originalState = getBusesLayout(); + BusesLayout currentState = originalState; + BusesLayout bestSupported = currentState; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir > 0); + + Array& currentLayouts = (isInput ? currentState.inputBuses : currentState.outputBuses); + const Array& bestLayouts = (isInput ? bestSupported.inputBuses : bestSupported.outputBuses); + const Array& requestedLayouts = (isInput ? layouts.inputBuses : layouts.outputBuses); + const Array& originalLayouts = (isInput ? originalState.inputBuses : originalState.outputBuses); + + for (int busIdx = 0; busIdx < requestedLayouts.size(); ++busIdx) + { + AudioChannelSet& best = bestLayouts .getReference (busIdx); + const AudioChannelSet& requested = requestedLayouts.getReference (busIdx); + const AudioChannelSet& original = originalLayouts .getReference (busIdx); + + // do we need to do anything + if (original == requested) + continue; + + currentState = bestSupported; + AudioChannelSet& current = currentLayouts .getReference (busIdx); + + // already supported? + current = requested; + if (checkBusesLayoutSupported (currentState)) + { + bestSupported = currentState; + continue; + } + + // try setting the opposite bus to the identical layout + const bool oppositeDirection = ! isInput; + if (getBusCount (oppositeDirection) > busIdx) + { + AudioChannelSet& oppositeLayout = (oppositeDirection ? currentState.inputBuses : currentState.outputBuses).getReference (busIdx); + oppositeLayout = requested; + + if (checkBusesLayoutSupported (currentState)) + { + bestSupported = currentState; + continue; + } + + // try setting the default layout + oppositeLayout = getBus (oppositeDirection, busIdx)->getDefaultLayout(); + if (checkBusesLayoutSupported (currentState)) + { + bestSupported = currentState; + continue; + } + } + + // try setting all other buses to the identical layout + BusesLayout allTheSame; + for (int oDir = 0; oDir < 2; ++oDir) + { + const bool oIsInput = (oDir == 0); + const int oBusNum = getBusCount (oIsInput); + + for (int oBusIdx = 0; oBusIdx < oBusNum; ++oBusIdx) + (oIsInput ? allTheSame.inputBuses : allTheSame.outputBuses).add (requested); + } + + if (checkBusesLayoutSupported (allTheSame)) + { + bestSupported = allTheSame; + continue; + } + + // what is closer the default or the current layout? + int distance = abs (best.size() - requested.size()); + const AudioChannelSet& defaultLayout = getBus (isInput, busIdx)->getDefaultLayout(); + + if (abs (defaultLayout.size() - requested.size()) < distance) + { + current = defaultLayout; + if (checkBusesLayoutSupported (currentState)) + bestSupported = currentState; + } + } + } + + return bestSupported; +} + +//============================================================================== void AudioProcessor::setPlayHead (AudioPlayHead* const newPlayHead) { playHead = newPlayHead; @@ -101,26 +375,22 @@ void AudioProcessor::setPlayConfigDetails (const int newNumIns, const double newSampleRate, const int newBlockSize) { - const int oldNumInputs = getTotalNumInputChannels(); - const int oldNumOutputs = getTotalNumOutputChannels(); + bool success = true; - // if the user is using this method then they do not want any side-buses or aux outputs - disableNonMainBuses (true); - disableNonMainBuses (false); + if (getTotalNumInputChannels() != newNumIns) + success &= setChannelLayoutOfBus (true, 0, AudioChannelSet::canonicalChannelSet (newNumIns)); + + if (getTotalNumOutputChannels() != newNumOuts) + success &= setChannelLayoutOfBus (false, 0, AudioChannelSet::canonicalChannelSet (newNumOuts)); - if (getTotalNumInputChannels() != newNumIns) setPreferredBusArrangement (true, 0, AudioChannelSet::canonicalChannelSet (newNumIns)); - if (getTotalNumOutputChannels() != newNumOuts) setPreferredBusArrangement (false, 0, AudioChannelSet::canonicalChannelSet (newNumOuts)); + // if the user is using this method then they do not want any side-buses or aux outputs + success &= disableNonMainBuses(); + jassert (success); // the processor may not support this arrangement at all - jassert (newNumIns == getTotalNumInputChannels() && newNumOuts == getTotalNumOutputChannels()); + jassert (success && newNumIns == getTotalNumInputChannels() && newNumOuts == getTotalNumOutputChannels()); setRateAndBufferSizeDetails (newSampleRate, newBlockSize); - - if (oldNumInputs != newNumIns || oldNumOutputs != newNumOuts) - { - updateSpeakerFormatStrings(); - numChannelsChanged(); - } } void AudioProcessor::setRateAndBufferSizeDetails (double newSampleRate, int newBlockSize) noexcept @@ -129,20 +399,44 @@ void AudioProcessor::setRateAndBufferSizeDetails (double newSampleRate, int newB blockSize = newBlockSize; } -int AudioProcessor::getMainBusNumInputChannels() const noexcept +//============================================================================== +static int countTotalChannels (const OwnedArray& buses) noexcept { - const Array& buses = busArrangement.inputBuses; - return buses.size() > 0 ? buses.getReference (0).channels.size() : 0; + int n = 0; + + for (int i = 0; i < buses.size(); ++i) + n += buses[i]->getNumberOfChannels(); + + return n; } -int AudioProcessor::getMainBusNumOutputChannels() const noexcept +void AudioProcessor::numChannelsChanged() {} +void AudioProcessor::numBusesChanged() {} +void AudioProcessor::processorLayoutsChanged() {} + +int AudioProcessor::getChannelIndexInProcessBlockBuffer (bool isInput, int busIndex, int channelIndex) const noexcept { - const Array& buses = busArrangement.outputBuses; - return buses.size() > 0 ? buses.getReference (0).channels.size() : 0; + const OwnedArray& ioBus = isInput ? inputBuses : outputBuses; + jassert (isPositiveAndBelow(busIndex, ioBus.size())); + + for (int i = 0; i < ioBus.size() && i < busIndex; ++i) + channelIndex += getChannelCountOfBus (isInput, i); + + return channelIndex; } -void AudioProcessor::numChannelsChanged() {} +int AudioProcessor::getOffsetInBusBufferForAbsoluteChannelIndex (bool isInput, int absoluteChannelIndex, /*out*/ int& busIdx) const noexcept +{ + const int n = getBusCount (isInput); + int numChannels = 0; + + for (busIdx = 0; busIdx < n && absoluteChannelIndex >= (numChannels = getChannelLayoutOfBus (isInput, busIdx).size()); ++busIdx) + absoluteChannelIndex -= numChannels; + return busIdx >= n ? -1 : absoluteChannelIndex; +} + +//============================================================================== void AudioProcessor::setNonRealtime (const bool newNonRealtime) noexcept { nonRealtime = newNonRealtime; @@ -274,6 +568,15 @@ const String AudioProcessor::getParameterName (int index) return String(); } +String AudioProcessor::getParameterID (int index) +{ + // Don't use getParamChecked here, as this must also work for legacy plug-ins + if (AudioProcessorParameterWithID* p = dynamic_cast (managedParameters[index])) + return p->paramID; + + return String (index); +} + String AudioProcessor::getParameterName (int index, int maximumStringLength) { if (AudioProcessorParameter* p = managedParameters[index]) @@ -376,8 +679,16 @@ void AudioProcessor::suspendProcessing (const bool shouldBeSuspended) } void AudioProcessor::reset() {} -void AudioProcessor::processBlockBypassed (AudioBuffer&, MidiBuffer&) {} -void AudioProcessor::processBlockBypassed (AudioBuffer&, MidiBuffer&) {} + +template +void AudioProcessor::processBypassed (AudioBuffer& buffer, MidiBuffer&) +{ + for (int ch = getMainBusNumInputChannels(); ch < getTotalNumOutputChannels(); ++ch) + buffer.clear (ch, 0, buffer.getNumSamples()); +} + +void AudioProcessor::processBlockBypassed (AudioBuffer& buffer, MidiBuffer& midi) { processBypassed (buffer, midi); } +void AudioProcessor::processBlockBypassed (AudioBuffer& buffer, MidiBuffer& midi) { processBypassed (buffer, midi); } void AudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) { @@ -390,6 +701,11 @@ void AudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midi jassertfalse; } +bool AudioProcessor::supportsDoublePrecisionProcessing() const +{ + return false; +} + void AudioProcessor::setProcessingPrecision (ProcessingPrecision precision) noexcept { // If you hit this assertion then you're trying to use double precision @@ -399,133 +715,151 @@ void AudioProcessor::setProcessingPrecision (ProcessingPrecision precision) noex processingPrecision = precision; } -bool AudioProcessor::supportsDoublePrecisionProcessing() const -{ - return false; -} - //============================================================================== -static String getChannelName (const Array& buses, int index) +static String getChannelName (const OwnedArray& buses, int index) { - return buses.size() > 0 ? AudioChannelSet::getChannelTypeName (buses.getReference(0).channels.getTypeOfChannel (index)) - : String(); + return buses.size() > 0 ? AudioChannelSet::getChannelTypeName (buses[0]->getCurrentLayout().getTypeOfChannel (index)) : String(); } -const String AudioProcessor::getInputChannelName (int index) const { return getChannelName (busArrangement.inputBuses, index); } -const String AudioProcessor::getOutputChannelName (int index) const { return getChannelName (busArrangement.outputBuses, index); } +const String AudioProcessor::getInputChannelName (int index) const { return getChannelName (inputBuses, index); } +const String AudioProcessor::getOutputChannelName (int index) const { return getChannelName (outputBuses, index); } -static bool isStereoPair (const Array& buses, int index) +static bool isStereoPair (const OwnedArray& buses, int index) { return index < 2 && buses.size() > 0 - && buses.getReference(0).channels == AudioChannelSet::stereo(); + && buses[0]->getCurrentLayout() == AudioChannelSet::stereo(); } -bool AudioProcessor::isInputChannelStereoPair (int index) const { return isStereoPair (busArrangement.inputBuses, index); } -bool AudioProcessor::isOutputChannelStereoPair (int index) const { return isStereoPair (busArrangement.outputBuses, index); } +bool AudioProcessor::isInputChannelStereoPair (int index) const { return isStereoPair (inputBuses, index); } +bool AudioProcessor::isOutputChannelStereoPair (int index) const { return isStereoPair (outputBuses, index); } //============================================================================== -bool AudioProcessor::setPreferredBusArrangement (bool isInput, int busIndex, const AudioChannelSet& preferredSet) +void AudioProcessor::createBus (bool inputBus, const BusProperties& ioConfig) { - const int oldNumInputs = getTotalNumInputChannels(); - const int oldNumOutputs = getTotalNumOutputChannels(); + (inputBus ? inputBuses : outputBuses).add (new Bus (*this, ioConfig.busName, ioConfig.defaultLayout, ioConfig.isActivatedByDefault)); - Array& buses = isInput ? busArrangement.inputBuses : busArrangement.outputBuses; + audioIOChanged (true, ioConfig.isActivatedByDefault); +} - const int numBuses = buses.size(); +//============================================================================== +AudioProcessor::BusesProperties AudioProcessor::busesPropertiesFromLayoutArray (const Array& config) +{ + BusesProperties ioProps; - if (! isPositiveAndBelow (busIndex, numBuses)) - return false; + if (config[0].inChannels > 0) + ioProps.addBus (true, String ("Input"), AudioChannelSet::canonicalChannelSet (config[0].inChannels)); - AudioProcessorBus& bus = buses.getReference (busIndex); + if (config[0].outChannels > 0) + ioProps.addBus (false, String ("Output"), AudioChannelSet::canonicalChannelSet (config[0].outChannels)); - #ifdef JucePlugin_PreferredChannelConfigurations - // the user is using the deprecated way to specify channel configurations - if (numBuses > 0 && busIndex == 0) - { - const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations }; - const int numChannelConfigs = sizeof (channelConfigs) / sizeof (*channelConfigs); + return ioProps; +} - // we need the main bus in the opposite direction - Array& oppositeBuses = isInput ? busArrangement.outputBuses : busArrangement.inputBuses; - AudioProcessorBus* oppositeBus = (busIndex < oppositeBuses.size()) ? &oppositeBuses.getReference (0) : nullptr; +AudioProcessor::BusesLayout AudioProcessor::getNextBestLayoutInList (const BusesLayout& layouts, + const Array& legacyLayouts) const +{ + const int numChannelConfigs = legacyLayouts.size(); + jassert (numChannelConfigs > 0); - // get the target number of channels - const int mainBusNumChannels = preferredSet.size(); - const int mainBusOppositeChannels = (oppositeBus != nullptr) ? oppositeBus->channels.size() : 0; - const int dir = isInput ? 0 : 1; + bool hasInputs = false, hasOutputs = false; - // find a compatible channel configuration on the opposite bus which is the closest match - // to the current number of channels on that bus - int distance = std::numeric_limits::max(); - int bestConfiguration = -1; + for (int i = 0; i < numChannelConfigs; ++i) + { + if (legacyLayouts[i].inChannels > 0) + { + hasInputs = true; + break; + } - for (int i = 0; i < numChannelConfigs; ++i) + if (legacyLayouts[i].outChannels > 0) { - // is the configuration compatible with the preferred set - if (channelConfigs[i][dir] == mainBusNumChannels) - { - const int configChannels = channelConfigs[i][dir^1]; - const int channelDifference = std::abs (configChannels - mainBusOppositeChannels); + hasOutputs = true; + break; + } + } - if (channelDifference < distance) - { - distance = channelDifference; - bestConfiguration = configChannels; + BusesLayout nearest = layouts; + nearest.inputBuses .resize (hasInputs ? 1 : 0); + nearest.outputBuses.resize (hasOutputs ? 1 : 0); - // we can exit if we found a perfect match - if (distance == 0) - break; - } - } - } + AudioChannelSet* inBus = (hasInputs ? &nearest.inputBuses. getReference (0) : nullptr); + AudioChannelSet* outBus = (hasOutputs ? &nearest.outputBuses.getReference (0) : nullptr); + + const int16 inNumChannelsRequested = static_cast (inBus != nullptr ? inBus->size() : 0); + const int16 outNumChannelsRequested = static_cast (outBus != nullptr ? outBus->size() : 0); + + int32 distance = std::numeric_limits::max(); + int bestConfiguration = 0; + + for (int i = 0; i < numChannelConfigs; ++i) + { + const int16 inChannels = legacyLayouts.getReference (i).inChannels; + const int16 outChannels = legacyLayouts.getReference (i).outChannels; - // unable to find a good configuration - if (bestConfiguration == -1) - return false; + const int32 channelDifference = ((std::abs (inChannels - inNumChannelsRequested) & 0xffff) << 16) | + ((std::abs (outChannels - outNumChannelsRequested) & 0xffff) << 0); - // did the number of channels change on the opposite bus? - if (mainBusOppositeChannels != bestConfiguration && oppositeBus != nullptr) + if (channelDifference < distance) { - // if the channels on the opposite bus are the same as the preferred set - // then also copy over the layout information. If not, then assume - // a cononical channel layout - if (bestConfiguration == mainBusNumChannels) - oppositeBus->channels = preferredSet; - else - oppositeBus->channels = AudioChannelSet::canonicalChannelSet (bestConfiguration); + distance = channelDifference; + bestConfiguration = i; + + // we can exit if we found a perfect match + if (distance == 0) return nearest; } } - #endif - bus.channels = preferredSet; + const int16 inChannels = legacyLayouts.getReference (bestConfiguration).inChannels; + const int16 outChannels = legacyLayouts.getReference (bestConfiguration).outChannels; - if (oldNumInputs != getTotalNumInputChannels() || oldNumOutputs != getTotalNumOutputChannels()) + BusesLayout currentState = getBusesLayout(); + AudioChannelSet currentInLayout = (getBusCount (true) > 0 ? currentState.inputBuses .getReference(0) : AudioChannelSet()); + AudioChannelSet currentOutLayout = (getBusCount (false) > 0 ? currentState.outputBuses.getReference(0) : AudioChannelSet()); + + + if (inBus != nullptr) { - updateSpeakerFormatStrings(); - numChannelsChanged(); + if (inChannels == 0) *inBus = AudioChannelSet::disabled(); + else if (inChannels == currentInLayout. size()) *inBus = currentInLayout; + else if (inChannels == currentOutLayout.size()) *inBus = currentOutLayout; + else *inBus = AudioChannelSet::canonicalChannelSet (inChannels); } - return true; + if (outBus != nullptr) + { + if (outChannels == 0) *outBus = AudioChannelSet::disabled(); + else if (outChannels == currentOutLayout.size()) *outBus = currentOutLayout; + else if (outChannels == currentInLayout .size()) *outBus = currentInLayout; + else *outBus = AudioChannelSet::canonicalChannelSet (outChannels); + } + + return nearest; +} + +bool AudioProcessor::containsLayout (const BusesLayout& layouts, const Array& channelLayouts) +{ + if (layouts.inputBuses.size() > 1 || layouts.outputBuses.size() > 1) + return false; + + const InOutChannelPair mainLayout (static_cast (layouts.getNumChannels (true, 0)), + static_cast (layouts.getNumChannels (false, 0))); + + return channelLayouts.contains (mainLayout); } -void AudioProcessor::disableNonMainBuses (bool isInput) +//============================================================================== +bool AudioProcessor::disableNonMainBuses() { - const Array& buses = (isInput ? busArrangement.inputBuses : busArrangement.outputBuses); + BusesLayout layouts = getBusesLayout(); - for (int busIdx = 1; busIdx < buses.size(); ++busIdx) - { - if (buses.getReference (busIdx).channels != AudioChannelSet::disabled()) - { - bool success = setPreferredBusArrangement (isInput, busIdx, AudioChannelSet::disabled()); + for (int busIdx = 1; busIdx < layouts.inputBuses.size(); ++busIdx) + layouts.inputBuses.getReference (busIdx) = AudioChannelSet::disabled(); - ignoreUnused (success); - // You are using the setPlayConfigDetails method which should only be used on processors - // with no aux outputs and sidechains. Please use setRateAndBufferSizeDetails and - // setPreferredBusArrangement instead. - jassert (success); - } - } + for (int busIdx = 1; busIdx < layouts.outputBuses.size(); ++busIdx) + layouts.outputBuses.getReference (busIdx) = AudioChannelSet::disabled(); + + return setBusesLayout (layouts); } // Unfortunately the deprecated getInputSpeakerArrangement/getOutputSpeakerArrangement return @@ -536,11 +870,83 @@ void AudioProcessor::updateSpeakerFormatStrings() cachedInputSpeakerArrString.clear(); cachedOutputSpeakerArrString.clear(); - if (busArrangement.inputBuses.size() > 0) - cachedInputSpeakerArrString = busArrangement.inputBuses. getReference (0).channels.getSpeakerArrangementAsString(); + if (getBusCount (true) > 0) + cachedInputSpeakerArrString = getBus (true, 0)->getCurrentLayout().getSpeakerArrangementAsString(); - if (busArrangement.outputBuses.size() > 0) - cachedOutputSpeakerArrString = busArrangement.outputBuses.getReference (0).channels.getSpeakerArrangementAsString(); + if (getBusCount (false) > 0) + cachedOutputSpeakerArrString = getBus (false, 0)->getCurrentLayout().getSpeakerArrangementAsString(); +} + +bool AudioProcessor::applyBusLayouts (const BusesLayout& layouts) +{ + if (layouts == getBusesLayout()) + return true; + + const int numInputBuses = getBusCount (true); + const int numOutputBuses = getBusCount (false); + + const int oldNumberOfIns = getTotalNumInputChannels(); + const int oldNumberOfOuts = getTotalNumOutputChannels(); + + if (layouts.inputBuses. size() != numInputBuses + || layouts.outputBuses.size() != numOutputBuses) + return false; + + for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) + { + Bus& bus = *getBus (true, busIdx); + const AudioChannelSet& set = layouts.getChannelSet (true, busIdx); + + bus.layout = set; + if (! set.isDisabled()) + bus.lastLayout = set; + } + + for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) + { + Bus& bus = *getBus (false, busIdx); + const AudioChannelSet& set = layouts.getChannelSet (false, busIdx); + + bus.layout = set; + if (! set.isDisabled()) + bus.lastLayout = set; + } + + const bool channelNumChanged = (oldNumberOfIns != getTotalNumInputChannels() || oldNumberOfOuts != getTotalNumOutputChannels()); + audioIOChanged (false, channelNumChanged); + + return true; +} + +void AudioProcessor::audioIOChanged (bool busNumberChanged, bool channelNumChanged) +{ + const int numInputBuses = getBusCount (true); + const int numOutputBuses = getBusCount (false); + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const int n = (isInput ? numInputBuses : numOutputBuses); + + for (int i = 0; i < n; ++i) + { + if (Bus* bus = getBus (isInput, i)) + bus->updateChannelCount(); + } + } + + cachedTotalIns = countTotalChannels (inputBuses); + cachedTotalOuts = countTotalChannels (outputBuses); + + updateSpeakerFormatStrings(); + + if (busNumberChanged) + numBusesChanged(); + + if (channelNumChanged) + numChannelsChanged(); + + processorLayoutsChanged(); } #if ! JUCE_AUDIO_PROCESSOR_NO_GUI @@ -621,36 +1027,246 @@ XmlElement* AudioProcessor::getXmlFromBinary (const void* data, const int sizeIn return nullptr; } +bool AudioProcessor::canApplyBusCountChange (bool isInput, bool isAdding, + AudioProcessor::BusProperties& outProperties) +{ + if ( isAdding && ! canAddBus (isInput)) return false; + if (! isAdding && ! canRemoveBus (isInput)) return false; + + const int num = getBusCount (isInput); + + // No way for me to find out the default layout if there are no other busses!! + if (num == 0) return false; + + if (isAdding) + { + outProperties.busName = String (isInput ? "Input #" : "Output #") + String (getBusCount (isInput)); + outProperties.defaultLayout = (num > 0 ? getBus (isInput, num - 1)->getDefaultLayout() : AudioChannelSet()); + outProperties.isActivatedByDefault = true; + } + + return true; +} + //============================================================================== -int AudioProcessor::AudioBusArrangement::getChannelIndexInProcessBlockBuffer (bool isInput, int busIndex, int channelIndex) const noexcept +AudioProcessor::Bus::Bus (AudioProcessor& processor, const String& busName, + const AudioChannelSet& defaultLayout, bool isDfltEnabled) + : owner (processor), name (busName), + layout (isDfltEnabled ? defaultLayout : AudioChannelSet()), + dfltLayout (defaultLayout), lastLayout (defaultLayout), + enabledByDefault (isDfltEnabled) { - const Array& ioBus = isInput ? inputBuses : outputBuses; - jassert (busIndex < ioBus.size()); + // Your default layout cannot be disabled + jassert (! dfltLayout.isDisabled()); +} - for (int i = 0; i < ioBus.size() && i < busIndex; ++i) - channelIndex += ioBus.getReference(i).channels.size(); +bool AudioProcessor::Bus::isInput() const +{ + return owner.inputBuses.contains (this); +} + +int AudioProcessor::Bus::getBusIndex() const +{ + bool ignore; + int idx; + busDirAndIndex (ignore, idx); - return channelIndex; + return idx; } -static int countTotalChannels (const Array& buses) noexcept +void AudioProcessor::Bus::busDirAndIndex (bool& input, int& idx) const noexcept { - int n = 0; + idx = owner.inputBuses.indexOf (this); + input = (idx >= 0); - for (int i = 0; i < buses.size(); ++i) - n += buses.getReference(i).channels.size(); + if (! input) + idx = owner.outputBuses.indexOf (this); +} - return n; +bool AudioProcessor::Bus::setCurrentLayout (const AudioChannelSet& busLayout) +{ + bool isInput; + int idx; + busDirAndIndex (isInput, idx); + + return owner.setChannelLayoutOfBus (isInput, idx, busLayout); +} + +bool AudioProcessor::Bus::setCurrentLayoutWithoutEnabling (const AudioChannelSet& set) +{ + if (! set.isDisabled()) + { + if (isEnabled()) + return setCurrentLayout (set); + + if (isLayoutSupported (set)) + { + lastLayout = set; + return true; + } + + return false; + } + + return isLayoutSupported (set); +} + +bool AudioProcessor::Bus::setNumberOfChannels (int channels) +{ + bool isInputBus; + int busIdx; + busDirAndIndex (isInputBus, busIdx); + + if (owner.setChannelLayoutOfBus (isInputBus, busIdx, AudioChannelSet::canonicalChannelSet (channels))) + return true; + + if (channels == 0) + return false; + + AudioChannelSet namedSet = AudioChannelSet::namedChannelSet (channels); + if (! namedSet.isDisabled() && owner.setChannelLayoutOfBus (isInputBus, busIdx, namedSet)) + return true; + + return owner.setChannelLayoutOfBus (isInputBus, busIdx, AudioChannelSet::discreteChannels (channels)); +} + +bool AudioProcessor::Bus::enable (bool shouldEnable) +{ + if (isEnabled() == shouldEnable) + return true; + + return setCurrentLayout (shouldEnable ? lastLayout : AudioChannelSet::disabled()); +} + +int AudioProcessor::Bus::getMaxSupportedChannels (int limit) const +{ + for (int ch = limit; ch > 1; --ch) + if (isNumberOfChannelsSupported (ch)) + return ch; + + return (isMain() && isLayoutSupported (AudioChannelSet::disabled())) ? 0 : -1; +} + +bool AudioProcessor::Bus::isLayoutSupported (const AudioChannelSet& set) const +{ + bool isInputBus; + int busIdx; + busDirAndIndex (isInputBus, busIdx); + + BusesLayout layouts = getBusesLayoutForLayoutChangeOfBus (set); + return (layouts.getChannelSet (isInputBus, busIdx) == set); +} + +bool AudioProcessor::Bus::isNumberOfChannelsSupported (int channels) const +{ + if (channels == 0) return isLayoutSupported(AudioChannelSet::disabled()); + + const AudioChannelSet set = supportedLayoutWithChannels (channels); + return (! set.isDisabled()) && isLayoutSupported (set); +} + +AudioChannelSet AudioProcessor::Bus::supportedLayoutWithChannels (int channels) const +{ + if (channels == 0) return AudioChannelSet::disabled(); + + { + AudioChannelSet set; + if (! (set = AudioChannelSet::namedChannelSet (channels)).isDisabled() && isLayoutSupported (set)) + return set; + + if (! (set = AudioChannelSet::discreteChannels (channels)).isDisabled() && isLayoutSupported (set)) + return set; + } + + Array sets = AudioChannelSet::channelSetsWithNumberOfChannels (channels); + const int n = sets.size(); + + for (int i = 0; i < n; ++i) + { + const AudioChannelSet set = sets.getReference (i); + + if (isLayoutSupported (set)) + return set; + } + + return AudioChannelSet::disabled(); +} + +AudioProcessor::BusesLayout AudioProcessor::Bus::getBusesLayoutForLayoutChangeOfBus (const AudioChannelSet& set) const +{ + bool isInputBus; + int busIdx; + busDirAndIndex (isInputBus, busIdx); + + BusesLayout currentLayout = owner.getBusesLayout(); + Array& potentialBusLayout = + (isInputBus ? currentLayout.inputBuses : currentLayout.outputBuses); + + if (potentialBusLayout.getReference (busIdx) == set) + return currentLayout; + + potentialBusLayout.getReference (busIdx) = set; + + BusesLayout nearest = owner.getNextBestLayout (currentLayout); + + // Nearest layout has a different number of buses. JUCE plug-ins MUST + // have fixed number of buses. + jassert (currentLayout.inputBuses. size() == owner.getBusCount (true) + && currentLayout.outputBuses.size() == owner.getBusCount (false)); + + return nearest; +} + +int AudioProcessor::Bus::getChannelIndexInProcessBlockBuffer (int channelIndex) const noexcept +{ + bool isInputBus; + int busIdx; + busDirAndIndex (isInputBus, busIdx); + + return owner.getChannelIndexInProcessBlockBuffer (isInputBus, busIdx, channelIndex); +} + +void AudioProcessor::Bus::updateChannelCount() noexcept +{ + cachedChannelCount = layout.size(); +} + +//============================================================================== +void AudioProcessor::BusesProperties::addBus (bool isInput, const String& name, + const AudioChannelSet& dfltLayout, bool isActivatedByDefault) +{ + jassert (dfltLayout.size() != 0); + + BusProperties props; + + props.busName = name; + props.defaultLayout = dfltLayout; + props.isActivatedByDefault = isActivatedByDefault; + + (isInput ? inputLayouts : outputLayouts).add (props); } -int AudioProcessor::AudioBusArrangement::getTotalNumInputChannels() const noexcept { return countTotalChannels (inputBuses); } -int AudioProcessor::AudioBusArrangement::getTotalNumOutputChannels() const noexcept { return countTotalChannels (outputBuses); } +AudioProcessor::BusesProperties AudioProcessor::BusesProperties::withInput (const String& name, + const AudioChannelSet& dfltLayout, + bool isActivatedByDefault) const +{ + BusesProperties retval (*this); + retval.addBus (true, name, dfltLayout, isActivatedByDefault); + + return retval; +} -AudioProcessor::AudioProcessorBus::AudioProcessorBus (const String& nm, const AudioChannelSet& chans) - : name (nm), channels (chans) +AudioProcessor::BusesProperties AudioProcessor::BusesProperties::withOutput (const String& name, + const AudioChannelSet& dfltLayout, + bool isActivatedByDefault) const { + BusesProperties retval (*this); + retval.addBus (false, name, dfltLayout, isActivatedByDefault); + + return retval; } + //============================================================================== void AudioProcessorListener::audioProcessorParameterChangeGestureBegin (AudioProcessor*, int) {} void AudioProcessorListener::audioProcessorParameterChangeGestureEnd (AudioProcessor*, int) {} diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h index fa1b1cfb7..f6c72ee1a 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -25,6 +25,7 @@ #ifndef JUCE_AUDIOPROCESSOR_H_INCLUDED #define JUCE_AUDIOPROCESSOR_H_INCLUDED +struct PluginBusUtilities; //============================================================================== /** @@ -43,10 +44,41 @@ class JUCE_API AudioProcessor { protected: + struct BusesProperties; + //============================================================================== - /** Constructor. */ + /** Constructor. + + This constructor will create a main input and output bus which are diabled + by default. If you need more fine grain control then use the other + constructors. + */ AudioProcessor(); + /** Constructor for multibus AudioProcessors + + If your AudioProcessor supports multiple buses than use this constructor + to initialise the bus layouts and bus names of your plug-in. + */ + AudioProcessor (const BusesProperties& ioLayouts); + + /** Constructor for AudioProcessors which use layout maps + + If your AudioProcessor uses layout maps then use this constructor. + */ + #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS + AudioProcessor (const std::initializer_list& channelLayoutList) + { + initialise (busesPropertiesFromLayoutArray (layoutListToArray (channelLayoutList))); + } + #else + template + AudioProcessor (const short channelLayoutList[numLayouts][2]) + { + initialise (busesPropertiesFromLayoutArray (layoutListToArray (channelLayoutList))); + } + #endif + public: //============================================================================== enum ProcessingPrecision @@ -70,18 +102,20 @@ public: playback stops. You can call getTotalNumInputChannels and getTotalNumOutputChannels - or query the busArrangement member variable to find out the number of + or query the busLayout member variable to find out the number of channels your processBlock callback must process. - The estimatedSamplesPerBlock value is a HINT about the typical number of - samples that will be processed for each callback, but isn't any kind - of guarantee. The actual block sizes that the host uses may be different - each time the callback happens, and may be more or less than this value. + The maximumExpectedSamplesPerBlock value is a strong hint about the maximum + number of samples that will be provided in each block. You may want to use + this value to resize internal buffers. You should program defensively in + case a buggy host exceeds this value. The actual block sizes that the host + uses may be different each time the callback happens: completely variable + block sizes can be expected from some hosts. - @see busArrangement, getTotalNumInputChannels, getTotalNumOutputChannels + @see busLayout, getTotalNumInputChannels, getTotalNumOutputChannels */ virtual void prepareToPlay (double sampleRate, - int estimatedSamplesPerBlock) = 0; + int maximumExpectedSamplesPerBlock) = 0; /** Called after playback has stopped, to let the filter free up any resources it no longer needs. @@ -107,7 +141,7 @@ public: If your plug-in has more than one input or output buses then the buffer passed to the processBlock methods will contain a bundle of all channels of each bus. - Use AudioBusArrangement::getBusBuffer to obtain an audio buffer for a + Use AudiobusLayout::getBusBuffer to obtain an audio buffer for a particular bus. Note that if you have more outputs than inputs, then only those channels that @@ -144,7 +178,7 @@ public: processBlock() method to send out an asynchronous message. You could also use the AsyncUpdater class in a similar way. - @see AudioBusArrangement::getBusBuffer + @see AudiobusLayout::getBusBuffer */ virtual void processBlock (AudioBuffer& buffer, @@ -169,7 +203,7 @@ public: If your plug-in has more than one input or output buses then the buffer passed to the processBlock methods will contain a bundle of all channels of - each bus. Use AudioBusArrangement::getBusBuffer to obtain a audio buffer + each bus. Use AudiobusLayout::getBusBuffer to obtain a audio buffer for a particular bus. Note that if you have more outputs than inputs, then only those channels that @@ -182,8 +216,8 @@ public: but you should only read/write from the ones that your filter is supposed to be using. - If your plugin uses buses, then you should use AudioBusArrangement::getBusBuffer() - or AudioBusArrangement::getChannelIndexInProcessBlockBuffer() to find out which + If your plugin uses buses, then you should use AudiobusLayout::getBusBuffer() + or AudiobusLayout::getChannelIndexInProcessBlockBuffer() to find out which of the input and output channels correspond to which of the buses. The number of samples in these buffers is NOT guaranteed to be the same for every @@ -210,12 +244,13 @@ public: processBlock() method to send out an asynchronous message. You could also use the AsyncUpdater class in a similar way. - @see AudioBusArrangement::getBusBuffer + @see AudiobusLayout::getBusBuffer */ virtual void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages); /** Renders the next block when the processor is being bypassed. + The default implementation of this method will pass-through any incoming audio, but you may override this method e.g. to add latency compensation to the data to match the processor's latency characteristics. This will avoid situations where bypassing @@ -227,6 +262,7 @@ public: MidiBuffer& midiMessages); /** Renders the next block when the processor is being bypassed. + The default implementation of this method will pass-through any incoming audio, but you may override this method e.g. to add latency compensation to the data to match the processor's latency characteristics. This will avoid situations where bypassing @@ -237,103 +273,372 @@ public: virtual void processBlockBypassed (AudioBuffer& buffer, MidiBuffer& midiMessages); + //============================================================================== - /** Describes the layout and properties of an audio bus. - Effectively a bus description is a named set of channel types. - @see AudioChannelSet + /** + Represents the bus layout state of a plug-in */ - struct AudioProcessorBus + struct BusesLayout { - /** Creates a bus from a name and set of channel types. */ - AudioProcessorBus (const String& busName, const AudioChannelSet& channelTypes); + /** An array containing the list of input buses that this processor supports. */ + Array inputBuses; - /** The bus's name. */ - String name; + /** An array containing the list of output buses that this processor supports. */ + Array outputBuses; + + /** Get the number of channels of a particular bus */ + int getNumChannels (bool isInput, int busIndex) const noexcept + { + const Array& bus = (isInput ? inputBuses : outputBuses); + return isPositiveAndBelow(busIndex, bus.size()) ? bus.getReference (busIndex).size() : 0; + } + + /** Get the channel set of a particular bus */ + AudioChannelSet& getChannelSet (bool isInput, int busIndex) + { + Array& sets = isInput ? inputBuses : outputBuses; + jassert (isPositiveAndBelow (busIndex, sets.size())); + + return sets.getReference (busIndex); + } - /** The set of channel types that the bus contains. */ - AudioChannelSet channels; + /** Get the channel set of a particular bus */ + AudioChannelSet getChannelSet (bool isInput, int busIndex) const noexcept + { + const Array& sets = isInput ? inputBuses : outputBuses; + + if (isPositiveAndBelow (busIndex, sets.size())) + return sets.getReference (busIndex); + else + return AudioChannelSet(); + } + + /** Get the input channel layout on the main bus. */ + AudioChannelSet getMainInputChannelSet() const noexcept { return getChannelSet (true, 0); } + + /** Get the output channel layout on the main bus. */ + AudioChannelSet getMainOutputChannelSet() const noexcept { return getChannelSet (false, 0); } + + /** Get the number of input channels on the main bus. */ + int getMainInputChannels() const noexcept { return getNumChannels (true, 0); } + + /** Get the number of output channels on the main bus. */ + int getMainOutputChannels() const noexcept { return getNumChannels (false, 0); } + + bool operator== (const BusesLayout& other) const noexcept { return inputBuses == other.inputBuses && outputBuses == other.outputBuses; } + bool operator!= (const BusesLayout& other) const noexcept { return inputBuses != other.inputBuses || outputBuses != other.outputBuses; } }; //============================================================================== /** - Represents a set of input and output buses for an AudioProcessor. - */ - struct AudioBusArrangement + Describes the layout and properties of an audio bus. + Effectively a bus description is a named set of channel types. + + @see AudioChannelSet, AudioProcessor::addBus + */ + class Bus { - /** An array containing the list of input buses that this processor supports. */ - Array inputBuses; + public: + /** Returns true if this bus is an input bus. */ + bool isInput() const; - /** An array containing the list of output buses that this processor supports. */ - Array outputBuses; + /** Returns the index of this bus. */ + int getBusIndex() const; + + /** Returns true if the current bus is the main input or output bus. */ + bool isMain() const { return getBusIndex() == 0; } + + //============================================================================== + /** The bus's name. */ + const String &getName() const noexcept { return name; } + + /** Get the default layout of this bus. + + @see AudioChannelSet + */ + const AudioChannelSet& getDefaultLayout() const noexcept { return dfltLayout; } + + //============================================================================== + /** The bus's current layout. This will be AudioChannelSet::disabled() if the current + layout is dfisabled. + + @see AudioChannelSet + */ + const AudioChannelSet& getCurrentLayout() const noexcept { return layout; } + + /** Return the bus's last active channel layout. + + If the bus is currently enabled then the result will be identical to getCurrentLayout + otherwise it will return the last enabled layout. + + @see AudioChannelSet + */ + const AudioChannelSet& getLastEnabledLayout() const noexcept { return lastLayout; } + + /** Sets the bus's current layout. + + If the AudioProcessor does not support this layout then this will return false. + + @see AudioChannelSet + */ + bool setCurrentLayout (const AudioChannelSet& layout); + + /** Sets the bus's current layout without changing the enabled state. + + If the AudioProcessor does not support this layout then this will return false. + + @see AudioChannelSet + */ + bool setCurrentLayoutWithoutEnabling (const AudioChannelSet& layout); + + /** Return the number of channels of the current bus. */ + inline int getNumberOfChannels() const noexcept { return cachedChannelCount; } + + /** Set the number of channles of this bus. This will return false if the AudioProcessor + does not support this layout. */ + bool setNumberOfChannels (int channels); + + //============================================================================== + /** Checks if a particular layout is supported. + + @param set The AudioChannelSet which is to be probed. + @see AudioChannelSet + */ + bool isLayoutSupported (const AudioChannelSet& set) const; + + /** Checks if this bus can support a given number of channels. */ + bool isNumberOfChannelsSupported (int channels) const; + + /** Returns a ChannelSet that the bus supports with a given number of channels. */ + AudioChannelSet supportedLayoutWithChannels (int channels) const; + + /** Returns the maximum number of channels that this bus can support. + @param limit The maximum value to return. + */ + int getMaxSupportedChannels (int limit = AudioChannelSet::maxChannelsOfNamedLayout) const; + + /** Returns the resulting layouts of all buses after changing the layout of this bus. + + Changing an individual layout of a bus may also change the layout of all the other + buses. This method returns what the layouts of all the buses of the audio processor + would be, if you were to change the layout of this bus to the given layout. If there + is no way to support the given layout then this method will return the next best + layout. + */ + BusesLayout getBusesLayoutForLayoutChangeOfBus (const AudioChannelSet& set) const; + + //============================================================================== + /** Returns true if the current bus is enabled. */ + bool isEnabled() const noexcept { return ! layout.isDisabled(); } + + /** Enable or disable this bus. This will return false if the AudioProcessor + does not support disabling this bus. */ + bool enable (bool shouldEnable = true); + + /** Returns if this bus is enabled by default. */ + bool isEnabledByDefault() const noexcept { return enabledByDefault; } //============================================================================== /** Returns the position of a bus's channels within the processBlock buffer. This can be called in processBlock to figure out which channel of the master AudioSampleBuffer maps onto a specific bus's channel. - */ - int getChannelIndexInProcessBlockBuffer (bool isInput, int busIndex, int channelIndex) const noexcept; + */ + int getChannelIndexInProcessBlockBuffer (int channelIndex) const noexcept; + /** Returns an AudioBuffer containing a set of channel pointers for a specific bus. This can be called in processBlock to get a buffer containing a sub-group of the master AudioSampleBuffer which contains all the plugin channels. - */ + */ template - AudioBuffer getBusBuffer (AudioBuffer& processBlockBuffer, bool isInput, int busIndex) const + AudioBuffer getBusBuffer (AudioBuffer& processBlockBuffer) const { - const int busNumChannels = (isInput ? inputBuses : outputBuses).getReference (busIndex).channels.size(); - const int channelOffset = getChannelIndexInProcessBlockBuffer (isInput, busIndex, 0); - - return AudioBuffer (processBlockBuffer.getArrayOfWritePointers() + channelOffset, - busNumChannels, processBlockBuffer.getNumSamples()); + bool isIn; + int busIdx; + busDirAndIndex (isIn, busIdx); + return owner.getBusBuffer (processBlockBuffer, isIn, busIdx); } + private: + friend class AudioProcessor; + Bus (AudioProcessor&, const String&, const AudioChannelSet&, bool); + void busDirAndIndex (bool&, int&) const noexcept; + void updateChannelCount() noexcept; - /** Returns the total number of channels in all the input buses. */ - int getTotalNumInputChannels() const noexcept; + AudioProcessor& owner; + String name; + AudioChannelSet layout, dfltLayout, lastLayout; + bool enabledByDefault; + int cachedChannelCount; - /** Returns the total number of channels in all the output buses. */ - int getTotalNumOutputChannels() const noexcept; + JUCE_DECLARE_NON_COPYABLE (Bus); }; - /** The processor's bus arrangement. + //============================================================================== + /** Returns the number of buses on the input or output side */ + int getBusCount (bool isInput) const noexcept { return (isInput ? inputBuses : outputBuses).size(); } + + /** Returns the audio bus with a given index and direction. + + If busIdx is invalid then this method will return a nullptr. + */ + Bus* getBus (bool isInput, int busIdx) noexcept { return (isInput ? inputBuses : outputBuses)[busIdx]; } + + /** Returns the audio bus with a given index and direction. + + If busIdx is invalid then this method will return a nullptr. + */ + const Bus* getBus (bool isInput, int busIdx) const noexcept { return const_cast (this)->getBus (isInput, busIdx); } + + //============================================================================== + /** Callback to query if a bus can currently be added. + + This callback probes if a bus can currently be added. You should override + this callback if you want to support dynamically adding/removing buses by + the host. This is useful for mixer audio processors. + + The default implementation will always return false. + + @see addBus + */ + virtual bool canAddBus (bool /*inputBus*/) const { return false; } + + /** Callback to query if the last bus can currently be removed. + + This callback probes if the last bus can currently be removed. You should + override this callback if you want to support dynamically adding/removing + buses by the host. This is useful for mixer audio processors. + + If you return true in this callback then the AudioProcessor will go ahead + and delete the bus. + + The default implementation will always return false. + */ + virtual bool canRemoveBus (bool /*inputBus*/) const { return false; } + + /** Dynamically request an additional bus. + + Request an additional bus from the audio processor. If the audio processor + does not support adding additional buses then this method will return false. + + Most audio processors will not allow you to dynamically add/remove + audio buses and will return false. + + This method will invoke the canApplyBusCountChange callback to probe + if a bus can be added and, if yes, will use the supplied bus properties + of the canApplyBusCountChange callback to create a new bus. + + @see canApplyBusCountChange, removeBus + */ + bool addBus (bool isInput); + + /** Dynamically remove the latest added bus. + + Request the removal of the last bus from the audio processor. If the + audio processor does not support removing buses then this method will + return false. + + Most audio processors will not allow you to dynamically add/remove + audio buses and will return false. + + The default implementation will return false. - Your plugin can modify this either - - in the plugin's constructor - - in the setPreferredBusArrangement() callback - Changing it at other times can result in undefined behaviour. + This method will invoke the canApplyBusCountChange callback to probe if + a bus can currently be removed and, if yes, will go ahead and remove it. - The host will negotiate with the plugin over its bus configuration by making calls - to setPreferredBusArrangement(). + @see addBus, canRemoveBus + */ + bool removeBus (bool isInput); + + //============================================================================== + /** Set the channel layouts of this audio processor. - @see setPreferredBusArrangement + If the layout is not supported by this audio processor then + this method will return false. You can use the checkBusesLayoutSupported + and getNextBestLayout methods to probe which layouts this audio + processor supports. + */ + bool setBusesLayout (const BusesLayout& arr); + + /** Set the channel layouts of this audio processor without changing the + enablement state of the buses. + + If the layout is not supported by this audio processor then + this method will return false. You can use the checkBusesLayoutSupported + and getNextBestLayout methods to probe which layouts this audio + processor supports. */ - AudioBusArrangement busArrangement; + bool setBusesLayoutWithoutEnabling (const BusesLayout& arr); + + /** Provides the current channel layouts of this audio processor. */ + BusesLayout getBusesLayout() const; + + /** Provides the channel layout of the bus with a given index and direction. + + If the index, direction combination is invalid then this will return an + AudioChannelSet with no channels. + */ + AudioChannelSet getChannelLayoutOfBus (bool isInput, int busIdx) const noexcept; + + /** Set the channel layout of the bus with a given index and direction. + + If the index, direction combination is invalid or the layout is not + supported by the audio processor then this method will return false. + */ + bool setChannelLayoutOfBus (bool isInput, int busIdx, const AudioChannelSet& layout); + + /** Provides the number of channels of the bus with a given index and direction. + + If the index, direction combination is invalid then this will return zero. + */ + inline int getChannelCountOfBus (bool isInput, int busIdx) const noexcept + { + if (const Bus* bus = getBus (isInput, busIdx)) + return bus->getNumberOfChannels(); + + return 0; + } + + /** Enables all buses */ + bool enableAllBuses(); + + /** Disables all non-main buses (aux and sidechains). */ + bool disableNonMainBuses(); //============================================================================== - /** Called by the host, this attempts to change the plugin's channel layout on a particular bus. - The base class implementation will perform some basic sanity-checking and then apply the - changes to the processor's busArrangement value. - You may override it and return false if you want to make your plugin smarter about refusing - certain layouts that you don't want to support. Your plug-in may also respond to this call by - changing the channel layout of other buses, for example, if your plug-in requires the same - number of input and output channels. + /** Returns the position of a bus's channels within the processBlock buffer. + This can be called in processBlock to figure out which channel of the master AudioSampleBuffer + maps onto a specific bus's channel. + */ + int getChannelIndexInProcessBlockBuffer (bool isInput, int busIndex, int channelIndex) const noexcept; - For most basic plug-ins, which do not require side-chains, aux buses or detailed audio - channel layout information, it is easier to specify the acceptable channel configurations - via the "PlugIn Channel Configurations" field in the Introjucer. In this case, you should - not override this method. + /** Returns the offset in a bus's buffer from an absolute channel indes. - If, on the other hand, you decide to override this method then you need to make sure that - "PlugIn Channel Configurations" field in the Introjucer is empty. + This method returns the offset in a bus's buffer given an absolute channel index. + It also provides the bus index. For example, this method would return one + for a processor with two stereo buses when given the absolute channel index. + */ + int getOffsetInBusBufferForAbsoluteChannelIndex (bool isInput, int absoluteChannelIndex, /*out*/ int& busIdx) const noexcept; - Note, that you must not do any heavy allocations or calculations in this callback as it may - be called several hundred times during initialization. If you require any layout specific - allocations then defer these to prepareToPlay callback. + /** Returns an AudioBuffer containing a set of channel pointers for a specific bus. + This can be called in processBlock to get a buffer containing a sub-group of the master + AudioSampleBuffer which contains all the plugin channels. + */ + template + AudioBuffer getBusBuffer (AudioBuffer& processBlockBuffer, bool isInput, int busIndex) const + { + const int busNumChannels = getChannelCountOfBus (isInput, busIndex); + const int channelOffset = getChannelIndexInProcessBlockBuffer (isInput, busIndex, 0); - @returns false if there is no way for the processor to support the given format on the specified bus. + return AudioBuffer (processBlockBuffer.getArrayOfWritePointers() + channelOffset, + busNumChannels, processBlockBuffer.getNumSamples()); + } - @see prepareToPlay, busArrangement, AudioBusArrangement::getBusBuffer, getTotalNumInputChannels, getTotalNumOutputChannels + //============================================================================== + /** Returns true if the Audio processor is likely to support a given layout. + + This can be called regardless if the processor is currently running. */ - virtual bool setPreferredBusArrangement (bool isInputBus, int busIndex, const AudioChannelSet& preferredSet); + bool checkBusesLayoutSupported (const BusesLayout& layouts) const; //============================================================================== /** Returns true if the Audio processor supports double precision floating point processing. @@ -403,7 +708,7 @@ public: getMainBusNumInputChannels if your processor does not have any sidechains or aux buses. */ - int getTotalNumInputChannels() const noexcept { return busArrangement.getTotalNumInputChannels(); } + int getTotalNumInputChannels() const noexcept { return cachedTotalIns; } /** Returns the total number of output channels. @@ -417,13 +722,56 @@ public: getMainBusNumOutputChannels if your processor does not have any sidechains or aux buses. */ - int getTotalNumOutputChannels() const noexcept { return busArrangement.getTotalNumOutputChannels(); } + int getTotalNumOutputChannels() const noexcept { return cachedTotalOuts; } /** Returns the number of input channels on the main bus. */ - int getMainBusNumInputChannels() const noexcept; + inline int getMainBusNumInputChannels() const noexcept { return getChannelCountOfBus (true, 0); } /** Returns the number of output channels on the main bus. */ - int getMainBusNumOutputChannels() const noexcept; + inline int getMainBusNumOutputChannels() const noexcept { return getChannelCountOfBus (false, 0); } + + //============================================================================== + /** Returns true if the channel layout map contains a certain layout. + + You can use this method to help you implement the checkBusesLayoutSupported + method. For example + + @code + bool checkBusesLayoutSupported (const BusesLayout& layouts) override + { + return containsLayout (layouts, {{1,1},{2,2}}); + } + @endcode + */ + #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS + static bool containsLayout (const BusesLayout& layouts, const std::initializer_list& channelLayoutList) + { + return containsLayout (layouts, layoutListToArray (channelLayoutList)); + } + #endif + template + static bool containsLayout (const BusesLayout& layouts, const short (&channelLayoutList) [numLayouts][2]) + { + return containsLayout (layouts, layoutListToArray (channelLayoutList)); + } + + /** Returns the next best layout which is contained in a channel layout map. + + You can use this mehtod to help you implement getNextBestLayout. For example: + + @code + BusesLayout getNextBestLayout (const BusesLayout& layouts) override + { + return getNextBestLayoutInLayoutList (layouts, {{1,1},{2,2}}); + } + @endcode + */ + template + BusesLayout getNextBestLayoutInLayoutList (const BusesLayout& layouts, + const short (&channelLayoutList) [numLayouts][2]) + { + return getNextBestLayoutInList (layouts, layoutListToArray (channelLayoutList)); + } //============================================================================== /** Returns the current sample rate. @@ -473,6 +821,9 @@ public: /** Returns true if the processor supports MPE. */ virtual bool supportsMPE() const { return false; } + /** Returns true if this is a midi effect plug-in and does no audio processing. */ + virtual bool isMidiEffect() const { return false; } + //============================================================================== /** This returns a critical section that will automatically be locked while the host is calling the processBlock() method. @@ -611,6 +962,17 @@ public: */ virtual const String getParameterName (int parameterIndex); + /** Returns the ID of a particular parameter. + + The ID is used to communicate the value or mapping of a particular parameter with + the host. By default this method will simply return a string representation of + index. + + NOTE! This method will eventually be deprecated! It's recommended that you use the + AudioProcessorParameterWithID class instead to manage your parameters. + */ + virtual String getParameterID (int index); + /** Called by the host to find out the value of one of the filter's parameters. The host will expect the value returned to be between 0 and 1.0. @@ -628,7 +990,7 @@ public: If you want to provide customised short versions of your parameter names that will look better in constrained spaces (e.g. the displays on hardware controller devices or mixing desks) then you should implement this method. - If you don't override it, the default implementation will call getParameterText(int), + If you don't override it, the default implementation will call getParameterName(int), and truncate the result. NOTE! This method will eventually be deprecated! It's recommended that you use @@ -864,9 +1226,15 @@ public: */ virtual void setCurrentProgramStateInformation (const void* data, int sizeInBytes); - /** This method is called when the number of input or output channels is changed. */ + /** This method is called when the total number of input or output channels is changed. */ virtual void numChannelsChanged(); + /** This method is called when the number of buses is changed. */ + virtual void numBusesChanged(); + + /** This method is called when the layout of the audio processor changes. */ + virtual void processorLayoutsChanged(); + //============================================================================== /** LV2 specific calls, saving/restore as string. */ virtual String getStateInformationString () { return String::empty; } @@ -894,9 +1262,9 @@ public: /** This is called by the processor to specify its details before being played. You should call this function after having informed the processor about the channel - and bus layouts via setPreferredBusArrangement. + and bus layouts via setBusesLayout. - @see setPreferredBusArrangement + @see setBusesLayout */ void setRateAndBufferSizeDetails (double sampleRate, int blockSize) noexcept; @@ -913,6 +1281,7 @@ public: wrapperType_VST, wrapperType_VST3, wrapperType_AudioUnit, + wrapperType_AudioUnitv3, wrapperType_RTAS, wrapperType_AAX, wrapperType_Standalone @@ -939,7 +1308,7 @@ public: /** Returns the name of one of the processor's input channels. These functions are deprecated: your audio processor can inform the host - on channel layouts and names via the methods in the AudioBusArrangement class. + on channel layouts and names via the methods in the AudiobusLayout class. */ JUCE_DEPRECATED (virtual const String getInputChannelName (int channelIndex) const); JUCE_DEPRECATED (virtual const String getOutputChannelName (int channelIndex) const); @@ -947,7 +1316,7 @@ public: /** Returns true if the specified channel is part of a stereo pair with its neighbour. These functions are deprecated: your audio processor should specify the audio - channel pairing information by modifying the busArrangement member variable in + channel pairing information by modifying the busLayout member variable in the constructor. */ JUCE_DEPRECATED (virtual bool isInputChannelStereoPair (int index) const); JUCE_DEPRECATED (virtual bool isOutputChannelStereoPair (int index) const); @@ -976,6 +1345,110 @@ public: static void JUCE_CALLTYPE setTypeOfNextNewPlugin (WrapperType); protected: + /** Callback to query if the AudioProcessor supports a specific layout. + + This callback is called when the host probes the supported bus layouts via + the checkBusesLayoutSupported method. You should override this callback if you + would like to limit the layouts that your AudioProcessor supports. The default + implementation will accept any layout. JUCE does basic sanity checks so that + the provided layouts parameter will have the same number of buses as your + AudioProcessor. + + @see checkBusesLayoutSupported + */ + virtual bool isBusesLayoutSupported (const BusesLayout& /*layouts*/) const { return true; } + + /** Callback to check if a certain bus layout can now be applied + + Most subclasses will not need to override this method and should instead + override the isBusesLayoutSupported callback to reject certain layout changes. + + This callback is called when the user requests a layout change. It will only be + called if processing of the AudioProcessor has been stopped by a previous call to + releaseResources or after the construction of the AudioProcessor. It will be called + just before the actual layout change. By returning false you will abort the layout + change and setBusesLayout will return false indicating that the layout change + was not successful. + + The default implementation will simply call isBusesLayoutSupported. + + You only need to override this method if there is a chance that your AudioProcessor + may not accept a layout although you have previously claimed to support it via the + isBusesLayoutSupported callback. This can occur if your AudioProcessor's supported + layouts depend on other plug-in parameters which may have changed since the last + call to isBusesLayoutSupported, such as the format of an audio file which can be + selected by the user in the AudioProcessor's editor. This callback gives the + AudioProcessor a last chance to reject a layout if conditions have changed as it + is always called just before the actual layout change. + + As it is never called while the AudioProcessor is processing audio, it can also + be used for AudioProcessors which wrap other plug-in formats to apply the current + layout to the underlying plug-in. This callback gives such AudioProcessors a + chance to reject the layout change should an error occur with the underlying plug-in + during the layout change. + + @see isBusesLayoutSupported, setBusesLayout + */ + virtual bool canApplyBusesLayout (const BusesLayout& layouts) const { return isBusesLayoutSupported (layouts); } + + //============================================================================== + /** Structure used for AudioProcessor Callbacks */ + struct BusProperties + { + /** The name of the bus */ + String busName; + + /** The default layout of the bus */ + AudioChannelSet defaultLayout; + + /** Is this bus activated by default? */ + bool isActivatedByDefault; + }; + + struct BusesProperties + { + /** The layouts of the input buses */ + Array inputLayouts; + + /** The layouts of the output buses */ + Array outputLayouts; + + void addBus (bool isInput, const String& name, const AudioChannelSet& dfltLayout, bool isActivatedByDefault = true); + + BusesProperties withInput (const String& name, const AudioChannelSet& dfltLayout, bool isActivatedByDefault = true) const; + BusesProperties withOutput (const String& name, const AudioChannelSet& dfltLayout, bool isActivatedByDefault = true) const; + }; + + /** Callback to query if adding/removing buses currently possible. + + This callback is called when the host calls addBus or removeBus. + Similar to canApplyBusesLayout, this callback is only called while + the AudioProcessor is stopped and gives the processor a last + chance to reject a requested bus change. It can also be used to apply + the bus count change to an underlying wrapped plug-in. + + When adding a bus, isAddingBuses will be true and the plug-in is + expected to fill out outNewBusProperties with the properties of the + bus which will be created just after the succesful return of this callback. + + Implementations of AudioProcessor will rarely need to override this + method. Only override this method if your processor supports adding + and removing buses and if it needs more fine grain control over the + naming of new buses or may reject bus number changes although canAddBus + or canRemoveBus returned true. + + The default implementation will return false if canAddBus/canRemoveBus + returns false (the default behavior). Otherwise, this method returns + "Input #busIdx" for input buses and "Output #busIdx" for output buses + where busIdx is the index for newly created buses. The default layout + in this case will be the layout of the previous bus of the same direction. + */ + virtual bool canApplyBusCountChange (bool isInput, bool isAddingBuses, + BusProperties& outNewBusProperties); + + //============================================================================== + friend struct PluginBusUtilities; + /** @internal */ AudioPlayHead* playHead; @@ -983,6 +1456,67 @@ protected: void sendParamChangeMessageToListeners (int parameterIndex, float newValue); private: + //============================================================================== + struct InOutChannelPair + { + int16 inChannels, outChannels; + + InOutChannelPair() noexcept : inChannels (0), outChannels (0) {} + InOutChannelPair (short inCh, short outCh) noexcept : inChannels (inCh), outChannels (outCh) {} + InOutChannelPair (const InOutChannelPair& o) noexcept : inChannels (o.inChannels), outChannels (o.outChannels) {} + InOutChannelPair (const short (&config)[2]) noexcept : inChannels (config[0]), outChannels (config[1]) {} + + InOutChannelPair& operator= (const InOutChannelPair& o) { inChannels = o.inChannels; outChannels = o.outChannels; return *this; } + + bool operator== (const InOutChannelPair& other) const noexcept + { + return (other.inChannels == inChannels && other.outChannels == outChannels); + } + }; + + template + static Array layoutListToArray (const short (&configuration) [numLayouts][2]) + { + Array layouts; + + for (int i = 0; i < numLayouts; ++i) + { + InOutChannelPair pair (configuration [i]); + layouts.add (pair); + } + + return layouts; + } + + #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS + static Array layoutListToArray (const std::initializer_list& configuration) + { + Array layouts; + + for (std::initializer_list::const_iterator it = configuration.begin(); + it != configuration.end(); ++it) + { + InOutChannelPair pair (*it); + layouts.add (pair); + } + + return layouts; + } + #endif + + //============================================================================== + static BusesProperties busesPropertiesFromLayoutArray (const Array&); + + //============================================================================== + BusesLayout getNextBestLayoutInList (const BusesLayout& layouts, + const Array& channelLayouts) const; + static bool containsLayout (const BusesLayout& layouts, const Array& channelLayouts); + + //============================================================================== + void initialise (const BusesProperties& ioLayouts); + void createBus (bool inputBus, const BusProperties&); + + //============================================================================== Array listeners; #if ! JUCE_AUDIO_PROCESSOR_NO_GUI Component::SafePointer activeEditor; @@ -996,9 +1530,15 @@ private: ProcessingPrecision processingPrecision; CriticalSection callbackLock, listenerLock; + friend class Bus; + mutable OwnedArray inputBuses; + mutable OwnedArray outputBuses; + String cachedInputSpeakerArrString; String cachedOutputSpeakerArrString; + int cachedTotalIns, cachedTotalOuts; + OwnedArray managedParameters; AudioProcessorParameter* getParamChecked (int) const noexcept; @@ -1007,8 +1547,13 @@ private: #endif AudioProcessorListener* getListenerLocked (int) const noexcept; - void disableNonMainBuses (bool isInput); void updateSpeakerFormatStrings(); + bool applyBusLayouts (const BusesLayout& arr); + void audioIOChanged (bool busNumberChanged, bool channelNumChanged); + BusesLayout getNextBestLayout (const BusesLayout& layouts) const; + + template + void processBypassed (AudioBuffer&, MidiBuffer&); // This method is no longer used - you can delete it from your AudioProcessor classes. JUCE_DEPRECATED_WITH_BODY (virtual bool silenceInProducesSilenceOut() const, { return false; }); diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp b/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp index 07f865e82..344d8c4e8 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp @@ -22,14 +22,16 @@ ============================================================================== */ -AudioProcessorEditor::AudioProcessorEditor (AudioProcessor& p) noexcept : processor (p) +AudioProcessorEditor::AudioProcessorEditor (AudioProcessor& p) noexcept : processor (p), constrainer (nullptr) { + initialise(); } -AudioProcessorEditor::AudioProcessorEditor (AudioProcessor* p) noexcept : processor (*p) +AudioProcessorEditor::AudioProcessorEditor (AudioProcessor* p) noexcept : processor (*p), constrainer (nullptr) { // the filter must be valid.. jassert (p != nullptr); + initialise(); } AudioProcessorEditor::~AudioProcessorEditor() @@ -37,7 +39,132 @@ AudioProcessorEditor::~AudioProcessorEditor() // if this fails, then the wrapper hasn't called editorBeingDeleted() on the // filter for some reason.. jassert (processor.getActiveEditor() != this); + removeComponentListener (resizeListener); } void AudioProcessorEditor::setControlHighlight (ParameterControlHighlightInfo) {} int AudioProcessorEditor::getControlParameterIndex (Component&) { return -1; } + +void AudioProcessorEditor::initialise() +{ + resizable = false; + + attachConstrainer (&defaultConstrainer); + addComponentListener (resizeListener = new AudioProcessorEditorListener (this)); +} + +//============================================================================== +void AudioProcessorEditor::setResizable (const bool shouldBeResizable, const bool useBottomRightCornerResizer) +{ + if (shouldBeResizable != resizable) + { + resizable = shouldBeResizable; + + if (! resizable) + { + setConstrainer (&defaultConstrainer); + + if (getWidth() > 0 && getHeight() > 0) + { + defaultConstrainer.setSizeLimits (getWidth(), getHeight(), getWidth(), getHeight()); + resized(); + } + } + } + + const bool shouldHaveCornerResizer = (useBottomRightCornerResizer && shouldBeResizable); + if (shouldHaveCornerResizer != (resizableCorner != nullptr)) + { + if (shouldHaveCornerResizer) + { + Component::addChildComponent (resizableCorner = new ResizableCornerComponent (this, constrainer)); + resizableCorner->setAlwaysOnTop (true); + } + else + resizableCorner = nullptr; + } +} + +void AudioProcessorEditor::setResizeLimits (const int newMinimumWidth, + const int newMinimumHeight, + const int newMaximumWidth, + const int newMaximumHeight) noexcept +{ + // if you've set up a custom constrainer then these settings won't have any effect.. + jassert (constrainer == &defaultConstrainer || constrainer == nullptr); + + const bool shouldEnableResize = (newMinimumWidth != newMaximumWidth || newMinimumHeight != newMaximumHeight); + const bool shouldHaveCornerResizer = (shouldEnableResize != resizable || resizableCorner != nullptr); + + setResizable (shouldEnableResize, shouldHaveCornerResizer); + + if (constrainer == nullptr) + setConstrainer (&defaultConstrainer); + + defaultConstrainer.setSizeLimits (newMinimumWidth, newMinimumHeight, + newMaximumWidth, newMaximumHeight); + + setBoundsConstrained (getBounds()); +} + +void AudioProcessorEditor::setConstrainer (ComponentBoundsConstrainer* newConstrainer) +{ + if (constrainer != newConstrainer) + { + resizable = true; + attachConstrainer (newConstrainer); + } +} + +void AudioProcessorEditor::attachConstrainer (ComponentBoundsConstrainer* newConstrainer) +{ + if (constrainer != newConstrainer) + { + constrainer = newConstrainer; + updatePeer(); + } +} + +void AudioProcessorEditor::setBoundsConstrained (Rectangle newBounds) +{ + if (constrainer != nullptr) + constrainer->setBoundsForComponent (this, newBounds, false, false, false, false); + else + setBounds (newBounds); +} + +void AudioProcessorEditor::editorResized (bool wasResized) +{ + if (wasResized) + { + bool resizerHidden = false; + + if (ComponentPeer* peer = getPeer()) + resizerHidden = peer->isFullScreen() || peer->isKioskMode(); + + if (resizableCorner != nullptr) + { + resizableCorner->setVisible (! resizerHidden); + + const int resizerSize = 18; + resizableCorner->setBounds (getWidth() - resizerSize, + getHeight() - resizerSize, + resizerSize, resizerSize); + } + + + if (! resizable) + { + if (getWidth() > 0 && getHeight() > 0) + defaultConstrainer.setSizeLimits (getWidth(), getHeight(), + getWidth(), getHeight()); + } + } +} + +void AudioProcessorEditor::updatePeer() +{ + if (isOnDesktop()) + if (ComponentPeer* const peer = getPeer()) + peer->setConstrainer (constrainer); +} diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h b/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h index e043c37be..e6be249be 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h @@ -25,7 +25,7 @@ #ifndef JUCE_AUDIOPROCESSOREDITOR_H_INCLUDED #define JUCE_AUDIOPROCESSOREDITOR_H_INCLUDED - +class AudioProcessorEditorListener; //============================================================================== /** Base class for the component that acts as the GUI for an AudioProcessor. @@ -84,7 +84,91 @@ public: */ virtual int getControlParameterIndex (Component&); + //============================================================================== + /** Marks the host's editor window as resizable + + @param allowHostToResize whether the editor's parent window can be resized + by the user or the host. Even if this is false, you + can still resize your window yourself by calling + setBounds (for example, when a user clicks on a button + in your editor to drop out a panel) which will bypass any + resizable/constraints checks. If you are using + your own corner resizer than this will also bypass + any checks. + @param useBottomRightCornerResizer + @see setResizeLimits, isResizable + */ + void setResizable (bool allowHostToResize, bool useBottomRightCornerResizer); + + /** Returns true if the host is allowed to resize editor's parent window + + @see setResizable + */ + bool isResizable() const noexcept { return resizable; } + + /** This sets the maximum and minimum sizes for the window. + + If the window's current size is outside these limits, it will be resized to + make sure it's within them. + + A direct call to setBounds() will bypass any constraint checks, but when the + window is dragged by the user or resized by other indirect means, the constrainer + will limit the numbers involved. + + @see setResizable + */ + void setResizeLimits (int newMinimumWidth, + int newMinimumHeight, + int newMaximumWidth, + int newMaximumHeight) noexcept; + + + /** Returns the bounds constrainer object that this window is using. + You can access this to change its properties. + */ + ComponentBoundsConstrainer* getConstrainer() noexcept { return constrainer; } + + /** Sets the bounds-constrainer object to use for resizing and dragging this window. + + A pointer to the object you pass in will be kept, but it won't be deleted + by this object, so it's the caller's responsibility to manage it. + + If you pass a nullptr, then no contraints will be placed on the positioning of the window. + */ + void setConstrainer (ComponentBoundsConstrainer* newConstrainer); + + /** Calls the window's setBounds method, after first checking these bounds + with the current constrainer. + + @see setConstrainer + */ + void setBoundsConstrained (Rectangle newBounds); + + ScopedPointer resizableCorner; + private: + //============================================================================== + struct AudioProcessorEditorListener : ComponentListener + { + AudioProcessorEditorListener (AudioProcessorEditor* audioEditor) : e (audioEditor) {} + + void componentMovedOrResized (Component&, bool, bool wasResized) override { e->editorResized (wasResized); } + void componentParentHierarchyChanged (Component&) override { e->updatePeer(); } + AudioProcessorEditor* e; + }; + + //============================================================================== + void initialise(); + void editorResized (bool wasResized); + void updatePeer(); + void attachConstrainer (ComponentBoundsConstrainer* newConstrainer); + + //============================================================================== + ScopedPointer resizeListener; + bool resizable; + ComponentBoundsConstrainer defaultConstrainer; + ComponentBoundsConstrainer* constrainer; + JUCE_DECLARE_NON_COPYABLE (AudioProcessorEditor) }; diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index 92b635165..d1534a297 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -264,7 +264,16 @@ struct ProcessBufferOp : public AudioGraphRenderingOp AudioBuffer buffer (channels, totalChans, numSamples); - callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); + if (processor->isSuspended()) + { + buffer.clear(); + } + else + { + ScopedLock lock (processor->getCallbackLock()); + + callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); + } } void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) @@ -960,14 +969,7 @@ void AudioProcessorGraph::Node::prepare (const double newSampleRate, const int n processor->setProcessingPrecision (processor->supportsDoublePrecisionProcessing() ? precision : singlePrecision); - processor->setPlayConfigDetails (processor->getMainBusNumInputChannels(), - processor->getMainBusNumOutputChannels(), - newSampleRate, newBlockSize); - - // AudioProcessorGraph currently does not support processors with multiple buses - jassert (processor->getMainBusNumInputChannels() == processor->getTotalNumInputChannels() - && processor->getMainBusNumOutputChannels() == processor->getTotalNumOutputChannels()); - + processor->setRateAndBufferSizeDetails (newSampleRate, newBlockSize); processor->prepareToPlay (newSampleRate, newBlockSize); } } @@ -1035,7 +1037,7 @@ struct AudioProcessorGraph::AudioProcessorGraphBufferHelpers //============================================================================== AudioProcessorGraph::AudioProcessorGraph() : lastNodeId (0), audioBuffers (new AudioProcessorGraphBufferHelpers), - currentMidiInputBuffer (nullptr) + currentMidiInputBuffer (nullptr), isPrepared (false) { } @@ -1102,7 +1104,9 @@ AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const n Node* const n = new Node (nodeId, newProcessor); nodes.add (n); - triggerAsyncUpdate(); + + if (isPrepared) + triggerAsyncUpdate(); n->setParentGraph (this); return n; @@ -1117,7 +1121,9 @@ bool AudioProcessorGraph::removeNode (const uint32 nodeId) if (nodes.getUnchecked(i)->nodeId == nodeId) { nodes.remove (i); - triggerAsyncUpdate(); + + if (isPrepared) + triggerAsyncUpdate(); return true; } @@ -1126,6 +1132,15 @@ bool AudioProcessorGraph::removeNode (const uint32 nodeId) return false; } +bool AudioProcessorGraph::removeNode (Node* node) +{ + if (node != nullptr) + return removeNode (node->nodeId); + + jassertfalse; + return false; +} + //============================================================================== const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const uint32 sourceNodeId, const int sourceChannelIndex, @@ -1168,14 +1183,14 @@ bool AudioProcessorGraph::canConnect (const uint32 sourceNodeId, const Node* const source = getNodeForId (sourceNodeId); if (source == nullptr - || (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getMainBusNumOutputChannels()) + || (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getTotalNumOutputChannels()) || (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi())) return false; const Node* const dest = getNodeForId (destNodeId); if (dest == nullptr - || (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getMainBusNumInputChannels()) + || (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getTotalNumInputChannels()) || (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) return false; @@ -1194,14 +1209,19 @@ bool AudioProcessorGraph::addConnection (const uint32 sourceNodeId, GraphRenderingOps::ConnectionSorter sorter; connections.addSorted (sorter, new Connection (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex)); - triggerAsyncUpdate(); + + if (isPrepared) + triggerAsyncUpdate(); + return true; } void AudioProcessorGraph::removeConnection (const int index) { connections.remove (index); - triggerAsyncUpdate(); + + if (isPrepared) + triggerAsyncUpdate(); } bool AudioProcessorGraph::removeConnection (const uint32 sourceNodeId, const int sourceChannelIndex, @@ -1253,9 +1273,9 @@ bool AudioProcessorGraph::isConnectionLegal (const Connection* const c) const return source != nullptr && dest != nullptr - && (c->sourceChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->sourceChannelIndex, source->processor->getMainBusNumOutputChannels()) + && (c->sourceChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->sourceChannelIndex, source->processor->getTotalNumOutputChannels()) : source->processor->producesMidi()) - && (c->destChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->destChannelIndex, dest->processor->getMainBusNumInputChannels()) + && (c->destChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->destChannelIndex, dest->processor->getTotalNumInputChannels()) : dest->processor->acceptsMidi()); } @@ -1383,6 +1403,8 @@ void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSam clearRenderingSequence(); buildRenderingSequence(); + + isPrepared = true; } bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const @@ -1392,6 +1414,8 @@ bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const void AudioProcessorGraph::releaseResources() { + isPrepared = false; + for (int i = 0; i < nodes.size(); ++i) nodes.getUnchecked(i)->unprepare(); @@ -1516,13 +1540,13 @@ void AudioProcessorGraph::AudioGraphIOProcessor::fillInPluginDescription (Plugin d.version = "1.0"; d.isInstrument = false; - d.numInputChannels = getMainBusNumInputChannels(); + d.numInputChannels = getTotalNumInputChannels(); if (type == audioOutputNode && graph != nullptr) - d.numInputChannels = graph->getMainBusNumInputChannels(); + d.numInputChannels = graph->getTotalNumInputChannels(); - d.numOutputChannels = getMainBusNumOutputChannels(); + d.numOutputChannels = getTotalNumOutputChannels(); if (type == audioInputNode && graph != nullptr) - d.numOutputChannels = graph->getMainBusNumOutputChannels(); + d.numOutputChannels = graph->getTotalNumOutputChannels(); } void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int) @@ -1639,8 +1663,8 @@ void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorG if (graph != nullptr) { - setPlayConfigDetails (type == audioOutputNode ? graph->getMainBusNumOutputChannels() : 0, - type == audioInputNode ? graph->getMainBusNumInputChannels() : 0, + setPlayConfigDetails (type == audioOutputNode ? graph->getTotalNumOutputChannels() : 0, + type == audioInputNode ? graph->getTotalNumInputChannels() : 0, getSampleRate(), getBlockSize()); diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h index bf28b1370..690ff7b1e 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h @@ -183,6 +183,12 @@ public: */ bool removeNode (uint32 nodeId); + /** Deletes a node within the graph which has the specified ID. + + This will also delete any connections that are attached to this node. + */ + bool removeNode (Node* node); + //============================================================================== /** Returns the number of connections in the graph. */ int getNumConnections() const { return connections.size(); } @@ -390,6 +396,8 @@ private: MidiBuffer* currentMidiInputBuffer; MidiBuffer currentMidiOutputBuffer; + bool isPrepared; + void handleAsyncUpdate() override; void clearRenderingSequence(); void buildRenderingSequence(); diff --git a/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp b/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp index e1ba865af..a8918a731 100644 --- a/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp +++ b/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp @@ -105,6 +105,16 @@ private: } } + void startedDragging() override + { + owner.beginParameterChangeGesture(index); + } + + void stoppedDragging() override + { + owner.endParameterChangeGesture(index); + } + String getTextFromValue (double /*value*/) override { return owner.getParameterText (index) + " " + owner.getParameterLabel (index).trimEnd(); diff --git a/source/modules/juce_audio_processors/processors/juce_PluginDescription.h b/source/modules/juce_audio_processors/processors/juce_PluginDescription.h index 4b888ec10..488a3c134 100644 --- a/source/modules/juce_audio_processors/processors/juce_PluginDescription.h +++ b/source/modules/juce_audio_processors/processors/juce_PluginDescription.h @@ -118,7 +118,7 @@ public: given identifier string. Note that this isn't quite as simple as them just calling createIdentifierString() - and comparing the strings, because the identifers can differ (thanks to shell plug-ins). + and comparing the strings, because the identifiers can differ (thanks to shell plug-ins). */ bool matchesIdentifierString (const String& identifierString) const; diff --git a/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp index ce569230f..5d45ac6ea 100644 --- a/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp +++ b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp @@ -27,6 +27,8 @@ KnownPluginList::~KnownPluginList() {} void KnownPluginList::clear() { + ScopedLock lock (typesArrayLock); + if (types.size() > 0) { types.clear(); @@ -36,6 +38,8 @@ void KnownPluginList::clear() PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifier) const { + ScopedLock lock (typesArrayLock); + for (int i = 0; i < types.size(); ++i) if (types.getUnchecked(i)->fileOrIdentifier == fileOrIdentifier) return types.getUnchecked(i); @@ -45,6 +49,8 @@ PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifi PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& identifierString) const { + ScopedLock lock (typesArrayLock); + for (int i = 0; i < types.size(); ++i) if (types.getUnchecked(i)->matchesIdentifierString (identifierString)) return types.getUnchecked(i); @@ -54,27 +60,37 @@ PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& id bool KnownPluginList::addType (const PluginDescription& type) { - for (int i = types.size(); --i >= 0;) { - if (types.getUnchecked(i)->isDuplicateOf (type)) + ScopedLock lock (typesArrayLock); + + for (int i = types.size(); --i >= 0;) { - // strange - found a duplicate plugin with different info.. - jassert (types.getUnchecked(i)->name == type.name); - jassert (types.getUnchecked(i)->isInstrument == type.isInstrument); + if (types.getUnchecked(i)->isDuplicateOf (type)) + { + // strange - found a duplicate plugin with different info.. + jassert (types.getUnchecked(i)->name == type.name); + jassert (types.getUnchecked(i)->isInstrument == type.isInstrument); - *types.getUnchecked(i) = type; - return false; + *types.getUnchecked(i) = type; + return false; + } } + + types.insert (0, new PluginDescription (type)); } - types.insert (0, new PluginDescription (type)); sendChangeMessage(); return true; } void KnownPluginList::removeType (const int index) { - types.remove (index); + { + ScopedLock lock (typesArrayLock); + + types.remove (index); + } + sendChangeMessage(); } @@ -84,6 +100,8 @@ bool KnownPluginList::isListingUpToDate (const String& fileOrIdentifier, if (getTypeForFile (fileOrIdentifier) == nullptr) return false; + ScopedLock lock (typesArrayLock); + for (int i = types.size(); --i >= 0;) { const PluginDescription* const d = types.getUnchecked(i); @@ -103,7 +121,7 @@ void KnownPluginList::setCustomScanner (CustomScanner* newScanner) bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, const bool dontRescanIfAlreadyInList, - OwnedArray & typesFound, + OwnedArray& typesFound, AudioPluginFormat& format) { const ScopedLock sl (scanLock); @@ -113,6 +131,8 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, { bool needsRescanning = false; + ScopedLock lock (typesArrayLock); + for (int i = types.size(); --i >= 0;) { const PluginDescription* const d = types.getUnchecked(i); @@ -133,7 +153,7 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, if (blacklist.contains (fileOrIdentifier)) return false; - OwnedArray found; + OwnedArray found; { const ScopedUnlock sl2 (scanLock); @@ -163,7 +183,7 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, void KnownPluginList::scanAndAddDragAndDroppedFiles (AudioPluginFormatManager& formatManager, const StringArray& files, - OwnedArray & typesFound) + OwnedArray& typesFound) { for (int i = 0; i < files.size(); ++i) { @@ -298,12 +318,17 @@ void KnownPluginList::sort (const SortMethod method, bool forwards) if (method != defaultOrder) { Array oldOrder, newOrder; - oldOrder.addArray (types); - PluginSorter sorter (method, forwards); - types.sort (sorter, true); + { + ScopedLock lock (typesArrayLock); + + oldOrder.addArray (types); - newOrder.addArray (types); + PluginSorter sorter (method, forwards); + types.sort (sorter, true); + + newOrder.addArray (types); + } if (oldOrder != newOrder) sendChangeMessage(); @@ -315,8 +340,12 @@ XmlElement* KnownPluginList::createXml() const { XmlElement* const e = new XmlElement ("KNOWNPLUGINS"); - for (int i = types.size(); --i >= 0;) - e->prependChildElement (types.getUnchecked(i)->createXml()); + { + ScopedLock lock (typesArrayLock); + + for (int i = types.size(); --i >= 0;) + e->prependChildElement (types.getUnchecked(i)->createXml()); + } for (int i = 0; i < blacklist.size(); ++i) e->createNewChildElement ("BLACKLISTED")->setAttribute ("id", blacklist[i]); @@ -348,7 +377,7 @@ struct PluginTreeUtils { enum { menuIdBase = 0x324503f4 }; - static void buildTreeByFolder (KnownPluginList::PluginTree& tree, const Array & allPlugins) + static void buildTreeByFolder (KnownPluginList::PluginTree& tree, const Array& allPlugins) { for (int i = 0; i < allPlugins.size(); ++i) { @@ -392,7 +421,7 @@ struct PluginTreeUtils } static void buildTreeByCategory (KnownPluginList::PluginTree& tree, - const Array & sorted, + const Array& sorted, const KnownPluginList::SortMethod sortMethod) { String lastType; @@ -475,15 +504,21 @@ struct PluginTreeUtils return false; } - static void addToMenu (const KnownPluginList::PluginTree& tree, PopupMenu& m, const OwnedArray & allPlugins) + static bool addToMenu (const KnownPluginList::PluginTree& tree, PopupMenu& m, + const OwnedArray& allPlugins, + const String& currentlyTickedPluginID) { + bool isTicked = false; + for (int i = 0; i < tree.subFolders.size(); ++i) { const KnownPluginList::PluginTree& sub = *tree.subFolders.getUnchecked(i); PopupMenu subMenu; - addToMenu (sub, subMenu, allPlugins); - m.addSubMenu (sub.folder, subMenu); + const bool isItemTicked = addToMenu (sub, subMenu, allPlugins, currentlyTickedPluginID); + isTicked = isTicked || isItemTicked; + + m.addSubMenu (sub.folder, subMenu, true, nullptr, isItemTicked, 0); } for (int i = 0; i < tree.plugins.size(); ++i) @@ -495,16 +530,22 @@ struct PluginTreeUtils if (containsDuplicateNames (tree.plugins, name)) name << " (" << plugin->pluginFormatName << ')'; - m.addItem (allPlugins.indexOf (plugin) + menuIdBase, name, true, false); + const bool isItemTicked = plugin->matchesIdentifierString (currentlyTickedPluginID); + isTicked = isTicked || isItemTicked; + + m.addItem (allPlugins.indexOf (plugin) + menuIdBase, name, true, isItemTicked); } + + return isTicked; } }; KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortMethod) const { - Array sorted; + Array sorted; { + ScopedLock lock (typesArrayLock); PluginSorter sorter (sortMethod, true); for (int i = 0; i < types.size(); ++i) @@ -531,10 +572,11 @@ KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortM } //============================================================================== -void KnownPluginList::addToMenu (PopupMenu& menu, const SortMethod sortMethod) const +void KnownPluginList::addToMenu (PopupMenu& menu, const SortMethod sortMethod, + const String& currentlyTickedPluginID) const { ScopedPointer tree (createTree (sortMethod)); - PluginTreeUtils::addToMenu (*tree, menu, types); + PluginTreeUtils::addToMenu (*tree, menu, types, currentlyTickedPluginID); } int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const diff --git a/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h index 7bd38f06c..7be3c7617 100644 --- a/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h +++ b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h @@ -148,7 +148,8 @@ public: Use getIndexChosenByMenu() to find out the type that was chosen. */ - void addToMenu (PopupMenu& menu, SortMethod sortMethod) const; + void addToMenu (PopupMenu& menu, SortMethod sortMethod, + const String& currentlyTickedPluginID = String()) const; /** Converts a menu item index that has been chosen into its index in this list. Returns -1 if it's not an ID that was used. @@ -215,7 +216,7 @@ private: OwnedArray types; StringArray blacklist; ScopedPointer scanner; - CriticalSection scanLock; + CriticalSection scanLock, typesArrayLock; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownPluginList) }; diff --git a/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp b/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp index 805f80930..68405ded3 100644 --- a/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp +++ b/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp @@ -34,15 +34,17 @@ PluginDirectoryScanner::PluginDirectoryScanner (KnownPluginList& listToAddTo, AudioPluginFormat& formatToLookFor, FileSearchPath directoriesToSearch, const bool recursive, - const File& deadMansPedal) + const File& deadMansPedal, + bool allowPluginsWhichRequireAsynchronousInstantiation) : list (listToAddTo), format (formatToLookFor), deadMansPedalFile (deadMansPedal), - progress (0) + progress (0), + allowAsync (allowPluginsWhichRequireAsynchronousInstantiation) { directoriesToSearch.removeRedundantPaths(); - filesOrIdentifiersToScan = format.searchPathsForPlugins (directoriesToSearch, recursive); + filesOrIdentifiersToScan = format.searchPathsForPlugins (directoriesToSearch, recursive, allowAsync); // If any plugins have crashed recently when being loaded, move them to the // end of the list to give the others a chance to load correctly.. diff --git a/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.h b/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.h index 8087c1ccb..4f40981bf 100644 --- a/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.h +++ b/source/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.h @@ -44,12 +44,11 @@ public: @param formatToLookFor this is the type of format that you want to look for @param directoriesToSearch the path to search @param searchRecursively true to search recursively - @param deadMansPedalFile if this isn't File::nonexistent, then it will - be used as a file to store the names of any plugins - that crash during initialisation. If there are - any plugins listed in it, then these will always - be scanned after all other possible files have - been tried - in this way, even if there's a few + @param deadMansPedalFile if this isn't File(), then it will be used as a file + to store the names of any plugins that crash during + initialisation. If there are any plugins listed in it, + then these will always be scanned after all other possible + files have been tried - in this way, even if there's a few dodgy plugins in your path, then a couple of rescans will still manage to find all the proper plugins. It's probably best to choose a file in the user's @@ -57,12 +56,16 @@ public: settings file) for this. The file format it uses is just a list of filenames of the modules that failed. + @param allowPluginsWhichRequireAsynchronousInstantiation + If this is false then the scanner will exclude plug-ins + asynchronous creation - such as AUv3 plug-ins. */ PluginDirectoryScanner (KnownPluginList& listToAddResultsTo, AudioPluginFormat& formatToLookFor, FileSearchPath directoriesToSearch, bool searchRecursively, - const File& deadMansPedalFile); + const File& deadMansPedalFile, + bool allowPluginsWhichRequireAsynchronousInstantiation = false); /** Destructor. */ ~PluginDirectoryScanner(); @@ -116,6 +119,7 @@ private: StringArray failedFiles; Atomic nextIndex; float progress; + bool allowAsync; void updateProgress(); void setDeadMansPedalFile (const StringArray& newContents); diff --git a/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp b/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp index 8ff304ddf..790ee1e17 100644 --- a/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp +++ b/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp @@ -123,13 +123,15 @@ public: //============================================================================== PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, KnownPluginList& listToEdit, - const File& deadMansPedal, PropertiesFile* const props) + const File& deadMansPedal, PropertiesFile* const props, + bool allowPluginsWhichRequireAsynchronousInstantiation) : formatManager (manager), list (listToEdit), deadMansPedalFile (deadMansPedal), optionsButton ("Options..."), propertiesToUse (props), - numThreads (0) + allowAsync (allowPluginsWhichRequireAsynchronousInstantiation), + numThreads (allowAsync ? 1 : 0) { tableModel = new TableModel (*this, listToEdit); @@ -331,18 +333,25 @@ class PluginListComponent::Scanner : private Timer { public: Scanner (PluginListComponent& plc, AudioPluginFormat& format, PropertiesFile* properties, - int threads, const String& title, const String& text) + bool allowPluginsWhichRequireAsynchronousInstantiation, int threads, + const String& title, const String& text) : owner (plc), formatToScan (format), propertiesToUse (properties), - pathChooserWindow (TRANS("Select folders to scan..."), String::empty, AlertWindow::NoIcon), + pathChooserWindow (TRANS("Select folders to scan..."), String(), AlertWindow::NoIcon), progressWindow (title, text, AlertWindow::NoIcon), - progress (0.0), numThreads (threads), finished (false) + progress (0.0), numThreads (threads), allowAsync (allowPluginsWhichRequireAsynchronousInstantiation), + finished (false) { FileSearchPath path (formatToScan.getDefaultLocationsToSearch()); + // You need to use at least one thread when scanning plug-ins asynchronously + jassert (! allowAsync || (numThreads > 0)); + if (path.getNumPaths() > 0) // if the path is empty, then paths aren't used for this format. { + #if ! JUCE_IOS if (propertiesToUse != nullptr) path = getLastSearchPath (*propertiesToUse, formatToScan); + #endif pathList.setSize (500, 300); pathList.setPath (path); @@ -381,7 +390,7 @@ private: String pluginBeingScanned; double progress; int numThreads; - bool finished; + bool allowAsync, finished; ScopedPointer pool; static void startScanCallback (int result, AlertWindow* alert, Scanner* scanner) @@ -413,7 +422,7 @@ private: + TRANS ("Are you sure you want to scan the folder \"XYZ\"?") .replace ("XYZ", f.getFullPathName()), TRANS ("Scan"), - String::empty, + String(), nullptr, ModalCallbackFunction::create (warnAboutStupidPathsCallback, this)); return; @@ -465,7 +474,7 @@ private: pathChooserWindow.setVisible (false); scanner = new PluginDirectoryScanner (owner.list, formatToScan, pathList.getPath(), - true, owner.deadMansPedalFile); + true, owner.deadMansPedalFile, allowAsync); if (propertiesToUse != nullptr) { @@ -545,7 +554,7 @@ private: void PluginListComponent::scanFor (AudioPluginFormat& format) { - currentScanner = new Scanner (*this, format, propertiesToUse, numThreads, + currentScanner = new Scanner (*this, format, propertiesToUse, allowAsync, numThreads, dialogTitle.isNotEmpty() ? dialogTitle : TRANS("Scanning for plug-ins..."), dialogText.isNotEmpty() ? dialogText : TRANS("Searching for all possible plug-in files...")); } diff --git a/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.h b/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.h index a1d6f1d75..0d9104ed4 100644 --- a/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.h +++ b/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.h @@ -47,7 +47,8 @@ public: PluginListComponent (AudioPluginFormatManager& formatManager, KnownPluginList& listToRepresent, const File& deadMansPedalFile, - PropertiesFile* propertiesToUse); + PropertiesFile* propertiesToUse, + bool allowPluginsWhichRequireAsynchronousInstantiation = false); /** Destructor. */ ~PluginListComponent(); @@ -60,8 +61,10 @@ public: const String& textForProgressWindowDescription); /** Sets how many threads to simultaneously scan for plugins. - If this is 0, then all scanning happens on the message thread (this is the default) - */ + If this is 0, then all scanning happens on the message thread (this is the default when + allowPluginsWhichRequireAsynchronousInstantiation is false). If + allowPluginsWhichRequireAsynchronousInstantiation is true then numThreads must not + be zero (it is one by default). */ void setNumberOfThreadsForScanning (int numThreads); /** Returns the last search path stored in a given properties file for the specified format. */ @@ -96,6 +99,7 @@ private: TextButton optionsButton; PropertiesFile* propertiesToUse; String dialogTitle, dialogText; + bool allowAsync; int numThreads; class TableModel; diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h b/source/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h index 740f95c7e..5989ab012 100644 --- a/source/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h +++ b/source/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h @@ -2,7 +2,7 @@ ============================================================================== This file is part of the JUCE library. - Copyright (c) 2013 - Raw Material Software Ltd. + Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) @@ -22,7 +22,6 @@ ============================================================================== */ - /** Provides a class of AudioProcessorParameter that can be used as a boolean value. diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h b/source/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h index 3ad01a88a..09c1faa22 100644 --- a/source/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h +++ b/source/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h @@ -2,7 +2,7 @@ ============================================================================== This file is part of the JUCE library. - Copyright (c) 2013 - Raw Material Software Ltd. + Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) @@ -22,7 +22,6 @@ ============================================================================== */ - /** Provides a class of AudioProcessorParameter that can be used to select an indexed, named choice from a list. diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h b/source/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h index 58498d4a8..519defdbb 100644 --- a/source/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h +++ b/source/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h @@ -2,7 +2,7 @@ ============================================================================== This file is part of the JUCE library. - Copyright (c) 2013 - Raw Material Software Ltd. + Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) @@ -22,7 +22,6 @@ ============================================================================== */ - /** A subclass of AudioProcessorParameter that provides an easy way to create a parameter which maps onto a given NormalisableRange. diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h b/source/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h index aa7e38f2d..8e13e7d12 100644 --- a/source/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h +++ b/source/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h @@ -2,7 +2,7 @@ ============================================================================== This file is part of the JUCE library. - Copyright (c) 2013 - Raw Material Software Ltd. + Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) @@ -22,7 +22,6 @@ ============================================================================== */ - /** Provides a class of AudioProcessorParameter that can be used as an integer value with a given range. diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h index 4687e18b1..8fd62d1a9 100644 --- a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h +++ b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h @@ -2,7 +2,7 @@ ============================================================================== This file is part of the JUCE library. - Copyright (c) 2013 - Raw Material Software Ltd. + Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) @@ -22,7 +22,6 @@ ============================================================================== */ - /** This abstract base class is used by some AudioProcessorParameter helper classes. @@ -45,9 +44,10 @@ public: /** Provides access to the parameter's name. */ const String name; -private: - String label; + /** Provides access to the parameter's label. */ + const String label; +private: String getName (int) const override; String getLabel() const override; diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp index 858a396b9..9e5c67911 100644 --- a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp +++ b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorParameters.cpp @@ -2,7 +2,7 @@ ============================================================================== This file is part of the JUCE library. - Copyright (c) 2013 - Raw Material Software Ltd. + Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp index 27a4ac9fd..79e1379f0 100644 --- a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp +++ b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp @@ -2,7 +2,7 @@ ============================================================================== This file is part of the JUCE library. - Copyright (c) 2013 - Raw Material Software Ltd. + Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) @@ -22,11 +22,10 @@ ============================================================================== */ - #if JUCE_COMPILER_SUPPORTS_LAMBDAS //============================================================================== -struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParameter, +struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParameterWithID, private ValueTree::Listener { Parameter (AudioProcessorValueTreeState& s, @@ -34,7 +33,8 @@ struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParamete NormalisableRange r, float defaultVal, std::function valueToText, std::function textToValue) - : owner (s), paramID (parameterID), name (paramName), label (labelText), + : AudioProcessorParameterWithID (parameterID, paramName), + owner (s), label (labelText), valueToTextFunction (valueToText), textToValueFunction (textToValue), range (r), value (defaultVal), defaultValue (defaultVal), @@ -52,7 +52,6 @@ struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParamete float getValue() const override { return range.convertTo0to1 (value); } float getDefaultValue() const override { return range.convertTo0to1 (defaultValue); } - String getName (int maximumStringLength) const override { return name.substring (0, maximumStringLength); } String getLabel() const override { return label; } float getValueForText (const String& text) const override @@ -67,6 +66,14 @@ struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParamete : AudioProcessorParameter::getText (v, length); } + int getNumSteps() const override + { + if (range.interval > 0) + return (static_cast ((range.end - range.start) / range.interval) + 1); + + return AudioProcessor::getDefaultNumParameterSteps(); + } + void setValue (float newValue) override { newValue = range.snapToLegalValue (range.convertFrom0to1 (newValue)); @@ -142,7 +149,7 @@ struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParamete AudioProcessorValueTreeState& owner; ValueTree state; - String paramID, name, label; + String label; ListenerList listeners; std::function valueToTextFunction; std::function textToValueFunction; @@ -359,6 +366,18 @@ struct AttachedControlBase : public AudioProcessorValueTreeState::Listener, } } + void beginParameterChange() + { + if (AudioProcessorParameter* p = state.getParameter (paramID)) + p->beginChangeGesture(); + } + + void endParameterChange() + { + if (AudioProcessorParameter* p = state.getParameter (paramID)) + p->endChangeGesture(); + } + void handleAsyncUpdate() override { setValue (lastValue); @@ -382,6 +401,7 @@ struct AudioProcessorValueTreeState::SliderAttachment::Pimpl : private Attached { NormalisableRange range (s.getParameterRange (paramID)); slider.setRange (range.start, range.end, range.interval); + slider.setSkewFactor (range.skew, range.symmetricSkew); if (AudioProcessorParameter* param = state.getParameter (paramID)) slider.setDoubleClickReturnValue (true, range.convertFrom0to1 (param->getDefaultValue())); @@ -407,17 +427,8 @@ struct AudioProcessorValueTreeState::SliderAttachment::Pimpl : private Attached setNewUnnormalisedValue ((float) s->getValue()); } - void sliderDragStarted (Slider*) override - { - if (AudioProcessorParameter* p = state.getParameter (paramID)) - p->beginChangeGesture(); - } - - void sliderDragEnded (Slider*) override - { - if (AudioProcessorParameter* p = state.getParameter (paramID)) - p->endChangeGesture(); - } + void sliderDragStarted (Slider*) override { beginParameterChange(); } + void sliderDragEnded (Slider*) override { endParameterChange(); } Slider& slider; @@ -455,7 +466,9 @@ struct AudioProcessorValueTreeState::ComboBoxAttachment::Pimpl : private Attach void comboBoxChanged (ComboBox* comboBox) override { + beginParameterChange(); setNewUnnormalisedValue ((float) comboBox->getSelectedId() - 1.0f); + endParameterChange(); } ComboBox& combo; @@ -494,7 +507,9 @@ struct AudioProcessorValueTreeState::ButtonAttachment::Pimpl : private Attached void buttonClicked (Button* b) override { + beginParameterChange(); setNewUnnormalisedValue (b->getToggleState() ? 1.0f : 0.0f); + endParameterChange(); } Button& button; diff --git a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h index 5928d5a8e..92f7abc2e 100644 --- a/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h +++ b/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h @@ -2,7 +2,7 @@ ============================================================================== This file is part of the JUCE library. - Copyright (c) 2013 - Raw Material Software Ltd. + Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) @@ -25,7 +25,7 @@ #ifndef JUCE_AUDIOPROCESSORVALUETREESTATE_H_INCLUDED #define JUCE_AUDIOPROCESSORVALUETREESTATE_H_INCLUDED -#if JUCE_COMPILER_SUPPORTS_LAMBDAS || defined (DOXYGEN) +#if JUCE_COMPILER_SUPPORTS_LAMBDAS /** This class contains a ValueTree which is used to manage an AudioProcessor's entire state. @@ -149,6 +149,7 @@ public: private: struct Pimpl; + friend struct ContainerDeletePolicy; ScopedPointer pimpl; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SliderAttachment) }; @@ -157,7 +158,7 @@ public: /** An object of this class maintains a connection between a ComboBox and a parameter in an AudioProcessorValueTreeState. - During the lifetime of this SliderAttachment object, it keeps the two things in + During the lifetime of this ComboBoxAttachment object, it keeps the two things in sync, making it easy to connect a combo box to a parameter. When this object is deleted, the connection is broken. Make sure that your AudioProcessorValueTreeState and ComboBox aren't deleted before this object! @@ -172,6 +173,7 @@ public: private: struct Pimpl; + friend struct ContainerDeletePolicy; ScopedPointer pimpl; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBoxAttachment) }; @@ -180,7 +182,7 @@ public: /** An object of this class maintains a connection between a Button and a parameter in an AudioProcessorValueTreeState. - During the lifetime of this SliderAttachment object, it keeps the two things in + During the lifetime of this ButtonAttachment object, it keeps the two things in sync, making it easy to connect a button to a parameter. When this object is deleted, the connection is broken. Make sure that your AudioProcessorValueTreeState and Button aren't deleted before this object! @@ -195,6 +197,7 @@ public: private: struct Pimpl; + friend struct ContainerDeletePolicy; ScopedPointer pimpl; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonAttachment) }; diff --git a/source/modules/juce_core/containers/juce_Array.h b/source/modules/juce_core/containers/juce_Array.h index a927d8705..3b673c201 100644 --- a/source/modules/juce_core/containers/juce_Array.h +++ b/source/modules/juce_core/containers/juce_Array.h @@ -223,7 +223,7 @@ public: } /** Returns true if the array is empty, false otherwise. */ - inline bool empty() const noexcept + inline bool isEmpty() const noexcept { return size() == 0; } @@ -519,7 +519,7 @@ public: { insertPos += indexToInsertAt; const int numberToMove = numUsed - indexToInsertAt; - memmove (insertPos + numberOfElements, insertPos, numberToMove * sizeof (ElementType)); + memmove (insertPos + numberOfElements, insertPos, (size_t) numberToMove * sizeof (ElementType)); } else { @@ -540,13 +540,17 @@ public: will be done. @param newElement the new object to add to the array + @return true if the element was added to the array; false otherwise. */ - void addIfNotAlreadyThere (ParameterType newElement) + bool addIfNotAlreadyThere (ParameterType newElement) { const ScopedLockType lock (getLock()); - if (! contains (newElement)) - add (newElement); + if (contains (newElement)) + return false; + + add (newElement); + return true; } /** Replaces an element with a new value. @@ -787,6 +791,26 @@ public: } //============================================================================== + /** Removes an element from the array. + + This will remove the element at a given index, and move back + all the subsequent elements to close the gap. + If the index passed in is out-of-range, nothing will happen. + + @param indexToRemove the index of the element to remove + @see removeAndReturn, removeFirstMatchingValue, removeAllInstancesOf, removeRange + */ + void remove (int indexToRemove) + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + jassert (data.elements != nullptr); + removeInternal (indexToRemove); + } + } + /** Removes an element from the array. This will remove the element at a given index, and move back @@ -797,7 +821,7 @@ public: @returns the element that has been removed @see removeFirstMatchingValue, removeAllInstancesOf, removeRange */ - ElementType remove (const int indexToRemove) + ElementType removeAndReturn (const int indexToRemove) { const ScopedLockType lock (getLock()); @@ -820,7 +844,7 @@ public: array, behaviour is undefined. @param elementToRemove a pointer to the element to remove - @see removeFirstMatchingValue, removeAllInstancesOf, removeRange + @see removeFirstMatchingValue, removeAllInstancesOf, removeRange, removeIf */ void remove (const ElementType* elementToRemove) { @@ -829,7 +853,12 @@ public: jassert (data.elements != nullptr); const int indexToRemove = int (elementToRemove - data.elements); - jassert (isPositiveAndBelow (indexToRemove, numUsed)); + + if (! isPositiveAndBelow (indexToRemove, numUsed)) + { + jassertfalse; + return; + } removeInternal (indexToRemove); } @@ -840,7 +869,7 @@ public: If the item isn't found, no action is taken. @param valueToRemove the object to try to remove - @see remove, removeRange + @see remove, removeRange, removeIf */ void removeFirstMatchingValue (ParameterType valueToRemove) { @@ -857,21 +886,59 @@ public: } } - /** Removes an item from the array. + /** Removes items from the array. This will remove all occurrences of the given element from the array. If no such items are found, no action is taken. @param valueToRemove the object to try to remove - @see remove, removeRange + @return how many objects were removed. + @see remove, removeRange, removeIf */ - void removeAllInstancesOf (ParameterType valueToRemove) + int removeAllInstancesOf (ParameterType valueToRemove) { + int numRemoved = 0; const ScopedLockType lock (getLock()); for (int i = numUsed; --i >= 0;) + { if (valueToRemove == data.elements[i]) + { + removeInternal (i); + ++numRemoved; + } + } + + return numRemoved; + } + + /** Removes items from the array. + + This will remove all objects from the array that match a condition. + If no such items are found, no action is taken. + + @param predicate the condition when to remove an item. Must be a callable + type that takes an ElementType and returns a bool + + @return how many objects were removed. + @see remove, removeRange, removeAllInstancesOf + */ + template + int removeIf (PredicateType predicate) + { + int numRemoved = 0; + const ScopedLockType lock (getLock()); + + for (int i = numUsed; --i >= 0;) + { + if (predicate (data.elements[i]) == true) + { removeInternal (i); + ++numRemoved; + } + } + + return numRemoved; } /** Removes a range of elements from the array. @@ -884,7 +951,7 @@ public: @param startIndex the index of the first element to remove @param numberToRemove how many elements should be removed - @see remove, removeFirstMatchingValue, removeAllInstancesOf + @see remove, removeFirstMatchingValue, removeAllInstancesOf, removeIf */ void removeRange (int startIndex, int numberToRemove) { diff --git a/source/modules/juce_core/containers/juce_DynamicObject.h b/source/modules/juce_core/containers/juce_DynamicObject.h index 01336765c..aaf80b640 100644 --- a/source/modules/juce_core/containers/juce_DynamicObject.h +++ b/source/modules/juce_core/containers/juce_DynamicObject.h @@ -58,7 +58,7 @@ public: virtual bool hasProperty (const Identifier& propertyName) const; /** Returns a named property. - This returns var::null if no such property exists. + This returns var() if no such property exists. */ virtual const var& getProperty (const Identifier& propertyName) const; diff --git a/source/modules/juce_core/containers/juce_ListenerList.h b/source/modules/juce_core/containers/juce_ListenerList.h index edc08771b..405ddc877 100644 --- a/source/modules/juce_core/containers/juce_ListenerList.h +++ b/source/modules/juce_core/containers/juce_ListenerList.h @@ -292,10 +292,9 @@ public: /** A dummy bail-out checker that always returns false. See the ListenerList notes for more info about bail-out checkers. */ - class DummyBailOutChecker + struct DummyBailOutChecker { - public: - inline bool shouldBailOut() const noexcept { return false; } + bool shouldBailOut() const noexcept { return false; } }; //============================================================================== diff --git a/source/modules/juce_core/containers/juce_NamedValueSet.cpp b/source/modules/juce_core/containers/juce_NamedValueSet.cpp index 94ea828be..68afddde8 100644 --- a/source/modules/juce_core/containers/juce_NamedValueSet.cpp +++ b/source/modules/juce_core/containers/juce_NamedValueSet.cpp @@ -26,40 +26,6 @@ ============================================================================== */ -struct NamedValueSet::NamedValue -{ - NamedValue() noexcept {} - NamedValue (const Identifier& n, const var& v) : name (n), value (v) {} - NamedValue (const NamedValue& other) : name (other.name), value (other.value) {} - - #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS - NamedValue (NamedValue&& other) noexcept - : name (static_cast (other.name)), - value (static_cast (other.value)) - { - } - - NamedValue (Identifier&& n, var&& v) - : name (static_cast (n)), - value (static_cast (v)) - { - } - - NamedValue& operator= (NamedValue&& other) noexcept - { - name = static_cast (other.name); - value = static_cast (other.value); - return *this; - } - #endif - - bool operator== (const NamedValue& other) const noexcept { return name == other.name && value == other.value; } - bool operator!= (const NamedValue& other) const noexcept { return ! operator== (other); } - - Identifier name; - var value; -}; - //============================================================================== NamedValueSet::NamedValueSet() noexcept { @@ -114,12 +80,27 @@ int NamedValueSet::size() const noexcept return values.size(); } +bool NamedValueSet::isEmpty() const noexcept +{ + return values.isEmpty(); +} + +static const var& getNullVarRef() noexcept +{ + #if JUCE_ALLOW_STATIC_NULL_VARIABLES + return var::null; + #else + static var nullVar; + return nullVar; + #endif +} + const var& NamedValueSet::operator[] (const Identifier& name) const noexcept { if (const var* v = getVarPointer (name)) return *v; - return var::null; + return getNullVarRef(); } var NamedValueSet::getWithDefault (const Identifier& name, const var& defaultReturnValue) const @@ -218,7 +199,7 @@ const var& NamedValueSet::getValueAt (const int index) const noexcept return values.getReference (index).value; jassertfalse; - return var::null; + return getNullVarRef(); } var* NamedValueSet::getVarPointerAt (int index) const noexcept diff --git a/source/modules/juce_core/containers/juce_NamedValueSet.h b/source/modules/juce_core/containers/juce_NamedValueSet.h index 21b75b0e3..a62ffc23b 100644 --- a/source/modules/juce_core/containers/juce_NamedValueSet.h +++ b/source/modules/juce_core/containers/juce_NamedValueSet.h @@ -60,9 +60,51 @@ public: bool operator!= (const NamedValueSet&) const; //============================================================================== + struct NamedValue + { + NamedValue() noexcept {} + NamedValue (const Identifier& n, const var& v) : name (n), value (v) {} + NamedValue (const NamedValue& other) : name (other.name), value (other.value) {} + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + NamedValue (NamedValue&& other) noexcept + : name (static_cast (other.name)), + value (static_cast (other.value)) + { + } + + NamedValue (Identifier&& n, var&& v) noexcept + : name (static_cast (n)), + value (static_cast (v)) + { + } + + NamedValue& operator= (NamedValue&& other) noexcept + { + name = static_cast (other.name); + value = static_cast (other.value); + return *this; + } + #endif + + bool operator== (const NamedValue& other) const noexcept { return name == other.name && value == other.value; } + bool operator!= (const NamedValue& other) const noexcept { return ! operator== (other); } + + Identifier name; + var value; + }; + + NamedValueSet::NamedValue* begin() { return values.begin(); } + NamedValueSet::NamedValue* end() { return values.end(); } + + //============================================================================== + /** Returns the total number of values that the set contains. */ int size() const noexcept; + /** Returns true if the set is empty. */ + bool isEmpty() const noexcept; + /** Returns the value of a named item. If the name isn't found, this will return a void variant. @see getProperty @@ -137,7 +179,6 @@ public: private: //============================================================================== - struct NamedValue; Array values; }; diff --git a/source/modules/juce_core/containers/juce_OwnedArray.h b/source/modules/juce_core/containers/juce_OwnedArray.h index 31078f619..a64c9cbed 100644 --- a/source/modules/juce_core/containers/juce_OwnedArray.h +++ b/source/modules/juce_core/containers/juce_OwnedArray.h @@ -126,6 +126,12 @@ public: return numUsed; } + /** Returns true if the array is empty, false otherwise. */ + inline bool isEmpty() const noexcept + { + return size() == 0; + } + /** Returns a pointer to the object at this index in the array. If the index is out-of-range, this will return a null pointer, (and @@ -372,16 +378,17 @@ public: If the array already contains a matching object, nothing will be done. @param newObject the new object to add to the array - @returns the new object that was added + @returns true if the new object was added, false otherwise */ - ObjectClass* addIfNotAlreadyThere (ObjectClass* newObject) noexcept + bool addIfNotAlreadyThere (ObjectClass* newObject) noexcept { const ScopedLockType lock (getLock()); - if (! contains (newObject)) - add (newObject); + if (contains (newObject)) + return false; - return newObject; + add (newObject); + return true; } /** Replaces an object in the array with a different one. diff --git a/source/modules/juce_core/containers/juce_PropertySet.h b/source/modules/juce_core/containers/juce_PropertySet.h index 88284e6d6..310810f7e 100644 --- a/source/modules/juce_core/containers/juce_PropertySet.h +++ b/source/modules/juce_core/containers/juce_PropertySet.h @@ -131,8 +131,8 @@ public: /** Sets a named property to an XML element. @param keyName the name of the property to set. (This mustn't be an empty string) - @param xml the new element to set it to. If this is zero, the value will be set to - an empty string + @param xml the new element to set it to. If this is a nullptr, the value will + be set to an empty string @see getXmlValue */ void setValue (const String& keyName, const XmlElement* xml); diff --git a/source/modules/juce_core/containers/juce_ReferenceCountedArray.h b/source/modules/juce_core/containers/juce_ReferenceCountedArray.h index 1b4668eda..e5dfe965e 100644 --- a/source/modules/juce_core/containers/juce_ReferenceCountedArray.h +++ b/source/modules/juce_core/containers/juce_ReferenceCountedArray.h @@ -149,6 +149,12 @@ public: return numUsed; } + /** Returns true if the array is empty, false otherwise. */ + inline bool isEmpty() const noexcept + { + return size() == 0; + } + /** Returns a pointer to the object at this index in the array. If the index is out-of-range, this will return a null pointer, (and @@ -380,12 +386,17 @@ public: If the array already contains a matching object, nothing will be done. @param newObject the new object to add to the array + @returns true if the object has been added, false otherwise */ - void addIfNotAlreadyThere (ObjectClass* const newObject) noexcept + bool addIfNotAlreadyThere (ObjectClass* const newObject) noexcept { const ScopedLockType lock (getLock()); - if (! contains (newObject)) - add (newObject); + + if (contains (newObject)) + return false; + + add (newObject); + return true; } /** Replaces an object in the array with a different one. diff --git a/source/modules/juce_core/containers/juce_SortedSet.h b/source/modules/juce_core/containers/juce_SortedSet.h index 90a239639..207c65ee4 100644 --- a/source/modules/juce_core/containers/juce_SortedSet.h +++ b/source/modules/juce_core/containers/juce_SortedSet.h @@ -136,6 +136,12 @@ public: return data.size(); } + /** Returns true if the set is empty, false otherwise. */ + inline bool isEmpty() const noexcept + { + return size() == 0; + } + /** Returns one of the elements in the set. If the index passed in is beyond the range of valid elements, this @@ -370,7 +376,7 @@ public: */ ElementType remove (const int indexToRemove) noexcept { - return data.remove (indexToRemove); + return data.removeAndReturn (indexToRemove); } /** Removes an item from the set. diff --git a/source/modules/juce_core/containers/juce_Variant.cpp b/source/modules/juce_core/containers/juce_Variant.cpp index 9e3335df5..52a8e81af 100644 --- a/source/modules/juce_core/containers/juce_Variant.cpp +++ b/source/modules/juce_core/containers/juce_Variant.cpp @@ -434,7 +434,9 @@ var::var() noexcept : type (&VariantType_Void::instance) {} var::var (const VariantType& t) noexcept : type (&t) {} var::~var() noexcept { type->cleanUp (value); } +#if JUCE_ALLOW_STATIC_NULL_VARIABLES const var var::null; +#endif //============================================================================== var::var (const var& valueToCopy) : type (valueToCopy.type) @@ -504,6 +506,7 @@ var& var::operator= (const double v) { type->cleanUp (value); type = var& var::operator= (const char* const v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } var& var::operator= (const wchar_t* const v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } var& var::operator= (const String& v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } +var& var::operator= (const MemoryBlock& v) { type->cleanUp (value); type = &VariantType_Binary::instance; value.binaryValue = new MemoryBlock (v); return *this; } var& var::operator= (const Array& v) { var v2 (v); swapWith (v2); return *this; } var& var::operator= (ReferenceCountedObject* v) { var v2 (v); swapWith (v2); return *this; } var& var::operator= (NativeFunction v) { var v2 (v); swapWith (v2); return *this; } @@ -581,7 +584,7 @@ const var& var::operator[] (const Identifier& propertyName) const if (DynamicObject* const o = getDynamicObject()) return o->getProperty (propertyName); - return var::null; + return getNullVarRef(); } const var& var::operator[] (const char* const propertyName) const diff --git a/source/modules/juce_core/containers/juce_Variant.h b/source/modules/juce_core/containers/juce_Variant.h index 25d0dcd70..f5c77cb64 100644 --- a/source/modules/juce_core/containers/juce_Variant.h +++ b/source/modules/juce_core/containers/juce_Variant.h @@ -70,8 +70,10 @@ public: /** Destructor. */ ~var() noexcept; + #if JUCE_ALLOW_STATIC_NULL_VARIABLES /** A static var object that can be used where you need an empty variant object. */ static const var null; + #endif var (const var& valueToCopy); var (int value) noexcept; @@ -95,17 +97,18 @@ public: var& operator= (const char* value); var& operator= (const wchar_t* value); var& operator= (const String& value); + var& operator= (const MemoryBlock& value); var& operator= (const Array& value); var& operator= (ReferenceCountedObject* object); var& operator= (NativeFunction method); #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS - var (var&& other) noexcept; - var (String&& value); - var (MemoryBlock&& binaryData); - var (Array&& value); - var& operator= (var&& other) noexcept; - var& operator= (String&& value); + var (var&&) noexcept; + var (String&&); + var (MemoryBlock&&); + var (Array&&); + var& operator= (var&&) noexcept; + var& operator= (String&&); #endif void swapWith (var& other) noexcept; @@ -314,13 +317,30 @@ private: }; /** Compares the values of two var objects, using the var::equals() comparison. */ -bool operator== (const var& v1, const var& v2) noexcept; +bool operator== (const var&, const var&) noexcept; /** Compares the values of two var objects, using the var::equals() comparison. */ -bool operator!= (const var& v1, const var& v2) noexcept; -bool operator== (const var& v1, const String& v2); -bool operator!= (const var& v1, const String& v2); -bool operator== (const var& v1, const char* v2); -bool operator!= (const var& v1, const char* v2); +bool operator!= (const var&, const var&) noexcept; +bool operator== (const var&, const String&); +bool operator!= (const var&, const String&); +bool operator== (const var&, const char*); +bool operator!= (const var&, const char*); + +//============================================================================== +/** This template-overloaded class can be used to convert between var and custom types. */ +template +struct VariantConverter +{ + static Type fromVar (const var& v) { return static_cast (v); } + static var toVar (const Type& t) { return t; } +}; + +/** This template-overloaded class can be used to convert between var and custom types. */ +template <> +struct VariantConverter +{ + static String fromVar (const var& v) { return v.toString(); } + static var toVar (const String& s) { return s; } +}; #endif // JUCE_VARIANT_H_INCLUDED diff --git a/source/modules/juce_core/files/juce_File.cpp b/source/modules/juce_core/files/juce_File.cpp index 4463a04fd..e2a23beb9 100644 --- a/source/modules/juce_core/files/juce_File.cpp +++ b/source/modules/juce_core/files/juce_File.cpp @@ -68,24 +68,47 @@ File& File::operator= (File&& other) noexcept } #endif +#if JUCE_ALLOW_STATIC_NULL_VARIABLES const File File::nonexistent; +#endif //============================================================================== static String removeEllipsis (const String& path) { - StringArray toks; - toks.addTokens (path, File::separatorString, StringRef()); - - for (int i = 1; i < toks.size(); ++i) + // This will quickly find both /../ and /./ at the expense of a minor + // false-positive performance hit when path elements end in a dot. + #if JUCE_WINDOWS + if (path.contains (".\\")) + #else + if (path.contains ("./")) + #endif { - if (toks[i] == ".." && toks[i - 1] != "..") + StringArray toks; + toks.addTokens (path, File::separatorString, StringRef()); + bool anythingChanged = false; + + for (int i = 1; i < toks.size(); ++i) { - toks.removeRange (i - 1, 2); - i = jmax (0, i - 2); + const String& t = toks[i]; + + if (t == ".." && toks[i - 1] != "..") + { + anythingChanged = true; + toks.removeRange (i - 1, 2); + i = jmax (0, i - 2); + } + else if (t == ".") + { + anythingChanged = true; + toks.remove (i--); + } } + + if (anythingChanged) + return toks.joinIntoString (File::separatorString); } - return toks.joinIntoString (File::separatorString); + return path; } String File::parseAbsolutePath (const String& p) @@ -95,10 +118,7 @@ String File::parseAbsolutePath (const String& p) #if JUCE_WINDOWS // Windows.. - String path (p.replaceCharacter ('/', '\\')); - - if (path.contains ("\\..\\")) - path = removeEllipsis (path); + String path (removeEllipsis (p.replaceCharacter ('/', '\\'))); if (path.startsWithChar (separator)) { @@ -137,10 +157,7 @@ String File::parseAbsolutePath (const String& p) // If that's why you've ended up here, use File::getChildFile() to build your paths instead. jassert ((! p.containsChar ('\\')) || (p.indexOfChar ('/') >= 0 && p.indexOfChar ('/') < p.indexOfChar ('\\'))); - String path (p); - - if (path.contains ("/../")) - path = removeEllipsis (path); + String path (removeEllipsis (p)); if (path.startsWithChar ('~')) { @@ -285,6 +302,21 @@ bool File::copyFileTo (const File& newFile) const || (exists() && newFile.deleteFile() && copyInternal (newFile)); } +bool File::replaceFileIn (const File& newFile) const +{ + if (newFile.fullPath == fullPath) + return true; + + if (! newFile.exists()) + return moveFileTo (newFile); + + if (! replaceInternal (newFile)) + return false; + + deleteFile(); + return true; +} + bool File::copyDirectoryTo (const File& newDirectory) const { if (isDirectory() && newDirectory.createDirectory()) @@ -951,16 +983,16 @@ bool File::createSymbolicLink (const File& linkFileToCreate, bool overwriteExist } //============================================================================== -MemoryMappedFile::MemoryMappedFile (const File& file, MemoryMappedFile::AccessMode mode) +MemoryMappedFile::MemoryMappedFile (const File& file, MemoryMappedFile::AccessMode mode, bool exclusive) : address (nullptr), range (0, file.getSize()), fileHandle (0) { - openInternal (file, mode); + openInternal (file, mode, exclusive); } -MemoryMappedFile::MemoryMappedFile (const File& file, const Range& fileRange, AccessMode mode) +MemoryMappedFile::MemoryMappedFile (const File& file, const Range& fileRange, AccessMode mode, bool exclusive) : address (nullptr), range (fileRange.getIntersectionWith (Range (0, file.getSize()))), fileHandle (0) { - openInternal (file, mode); + openInternal (file, mode, exclusive); } @@ -979,9 +1011,9 @@ public: const File home (File::getSpecialLocation (File::userHomeDirectory)); const File temp (File::getSpecialLocation (File::tempDirectory)); - expect (! File::nonexistent.exists()); - expect (! File::nonexistent.existsAsFile()); - expect (! File::nonexistent.isDirectory()); + expect (! File().exists()); + expect (! File().existsAsFile()); + expect (! File().isDirectory()); #if ! JUCE_WINDOWS expect (File("/").isDirectory()); #endif @@ -1058,7 +1090,9 @@ public: expect (home.getChildFile ("././xyz") == home.getChildFile ("xyz")); expect (home.getChildFile ("../xyz") == home.getParentDirectory().getChildFile ("xyz")); expect (home.getChildFile (".././xyz") == home.getParentDirectory().getChildFile ("xyz")); + expect (home.getChildFile (".././xyz/./abc") == home.getParentDirectory().getChildFile ("xyz/abc")); expect (home.getChildFile ("./../xyz") == home.getParentDirectory().getChildFile ("xyz")); + expect (home.getChildFile ("a1/a2/a3/./../../a4") == home.getChildFile ("a1/a4")); { FileOutputStream fo (tempFile); diff --git a/source/modules/juce_core/files/juce_File.h b/source/modules/juce_core/files/juce_File.h index 9848c13da..dbe01c5a9 100644 --- a/source/modules/juce_core/files/juce_File.h +++ b/source/modules/juce_core/files/juce_File.h @@ -49,8 +49,7 @@ public: /** Creates an (invalid) file object. The file is initially set to an empty path, so getFullPathName() will return - an empty string, and comparing the file to File::nonexistent will return - true. + an empty string. You can use its operator= method to point it at a proper file. */ @@ -95,8 +94,13 @@ public: #endif //============================================================================== - /** This static constant is used for referring to an 'invalid' file. */ + #if JUCE_ALLOW_STATIC_NULL_VARIABLES + /** This static constant is used for referring to an 'invalid' file. + Bear in mind that you should avoid this kind of static variable, and always prefer + to use File() or {} if you need a default-constructed File object. + */ static const File nonexistent; + #endif //============================================================================== /** Checks whether the file actually exists. @@ -503,6 +507,18 @@ public: */ bool copyFileTo (const File& targetLocation) const; + /** Replaces a file. + + Replace the file in the given location, assuming the replaced files identity. + Depending on the file system this will preserve file attributes such as + creation date, short file name, etc. + + If replacement succeeds the original file is deleted. + + @returns true if the operation succeeds + */ + bool replaceFileIn (const File& targetLocation) const; + /** Copies a directory. Tries to copy an entire directory, recursively. @@ -982,6 +998,7 @@ private: Result createDirectoryInternal (const String&) const; bool copyInternal (const File&) const; bool moveInternal (const File&) const; + bool replaceInternal (const File&) const; bool setFileTimesInternal (int64 m, int64 a, int64 c) const; void getFileTimesInternal (int64& m, int64& a, int64& c) const; bool setFileReadOnlyInternal (bool) const; diff --git a/source/modules/juce_core/files/juce_FileOutputStream.h b/source/modules/juce_core/files/juce_FileOutputStream.h index 83c8f12a7..b72c7c83b 100644 --- a/source/modules/juce_core/files/juce_FileOutputStream.h +++ b/source/modules/juce_core/files/juce_FileOutputStream.h @@ -51,6 +51,10 @@ public: use File::deleteFile() before opening the stream, or use setPosition(0) after it's opened (although this won't truncate the file). + Destroying a FileOutputStream object does not force the operating system + to write the buffered data to disk immediately. If this is required you + should call flush() before triggering the destructor. + @see TemporaryFile */ FileOutputStream (const File& fileToWriteTo, diff --git a/source/modules/juce_core/files/juce_FileSearchPath.cpp b/source/modules/juce_core/files/juce_FileSearchPath.cpp index 5531c62a8..629ac6049 100644 --- a/source/modules/juce_core/files/juce_FileSearchPath.cpp +++ b/source/modules/juce_core/files/juce_FileSearchPath.cpp @@ -87,13 +87,14 @@ void FileSearchPath::add (const File& dir, const int insertIndex) directories.insert (insertIndex, dir.getFullPathName()); } -void FileSearchPath::addIfNotAlreadyThere (const File& dir) +bool FileSearchPath::addIfNotAlreadyThere (const File& dir) { for (int i = 0; i < directories.size(); ++i) if (File (directories[i]) == dir) - return; + return false; add (dir); + return true; } void FileSearchPath::remove (const int index) diff --git a/source/modules/juce_core/files/juce_FileSearchPath.h b/source/modules/juce_core/files/juce_FileSearchPath.h index 3744a49b3..1089c8b19 100644 --- a/source/modules/juce_core/files/juce_FileSearchPath.h +++ b/source/modules/juce_core/files/juce_FileSearchPath.h @@ -92,8 +92,11 @@ public: void add (const File& directoryToAdd, int insertIndex = -1); - /** Adds a new directory to the search path if it's not already in there. */ - void addIfNotAlreadyThere (const File& directoryToAdd); + /** Adds a new directory to the search path if it's not already in there. + + @return true if the directory has been added, false otherwise. + */ + bool addIfNotAlreadyThere (const File& directoryToAdd); /** Removes a directory from the search path. */ void remove (int indexToRemove); diff --git a/source/modules/juce_core/files/juce_MemoryMappedFile.h b/source/modules/juce_core/files/juce_MemoryMappedFile.h index 48e0eb6a0..fdfae6893 100644 --- a/source/modules/juce_core/files/juce_MemoryMappedFile.h +++ b/source/modules/juce_core/files/juce_MemoryMappedFile.h @@ -56,8 +56,13 @@ public: will lazily pull the data into memory when blocks are accessed. If the file can't be opened for some reason, the getData() method will return a null pointer. + + If exclusive is false then other apps can also open the same memory mapped file and use this + mapping as an effective way of communicating. If exclusive is true then the mapped file will + be opened exclusively - preventing other apps to access the file which may improve the + performance of accessing the file. */ - MemoryMappedFile (const File& file, AccessMode mode); + MemoryMappedFile (const File& file, AccessMode mode, bool exclusive = false); /** Opens a section of a file and maps it to an area of virtual memory. @@ -77,7 +82,8 @@ public: */ MemoryMappedFile (const File& file, const Range& fileRange, - AccessMode mode); + AccessMode mode, + bool exclusive = false); /** Destructor. */ ~MemoryMappedFile(); @@ -106,7 +112,7 @@ private: int fileHandle; #endif - void openInternal (const File&, AccessMode); + void openInternal (const File&, AccessMode, bool); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedFile) }; diff --git a/source/modules/juce_core/files/juce_TemporaryFile.cpp b/source/modules/juce_core/files/juce_TemporaryFile.cpp index acb1af0c9..337f7f3b2 100644 --- a/source/modules/juce_core/files/juce_TemporaryFile.cpp +++ b/source/modules/juce_core/files/juce_TemporaryFile.cpp @@ -86,7 +86,7 @@ bool TemporaryFile::overwriteTargetFileWithTemporary() const // Have a few attempts at overwriting the file before giving up.. for (int i = 5; --i >= 0;) { - if (temporaryFile.moveFileTo (targetFile)) + if (temporaryFile.replaceFileIn (targetFile)) return true; Thread::sleep (100); diff --git a/source/modules/juce_core/javascript/juce_JSON.cpp b/source/modules/juce_core/javascript/juce_JSON.cpp index 9f7eca708..4a4e13445 100644 --- a/source/modules/juce_core/javascript/juce_JSON.cpp +++ b/source/modules/juce_core/javascript/juce_JSON.cpp @@ -571,6 +571,19 @@ public: return CharPointer_ASCII (buffer); } + // (creates a random double that can be easily stringified, to avoid + // false failures when decimal places are rounded or truncated slightly) + static var createRandomDouble (Random& r) + { + for (;;) + { + var v (String (r.nextDouble() * 1000.0, 20).getDoubleValue()); + + if (v.toString() == String (static_cast (v), 20)) + return v; + } + } + static var createRandomVar (Random& r, int depth) { switch (r.nextInt (depth > 3 ? 6 : 8)) @@ -579,7 +592,7 @@ public: case 1: return r.nextInt(); case 2: return r.nextInt64(); case 3: return r.nextBool(); - case 4: return String (r.nextDouble(), 8).getDoubleValue(); + case 4: return createRandomDouble (r); case 5: return createRandomWideCharString (r); case 6: @@ -612,7 +625,7 @@ public: beginTest ("JSON"); Random r = getRandom(); - expect (JSON::parse (String::empty) == var::null); + expect (JSON::parse (String()) == var()); expect (JSON::parse ("{}").isObject()); expect (JSON::parse ("[]").isArray()); expect (JSON::parse ("[ 1234 ]")[0].isInt()); diff --git a/source/modules/juce_core/javascript/juce_JSON.h b/source/modules/juce_core/javascript/juce_JSON.h index 31a2ff593..522495052 100644 --- a/source/modules/juce_core/javascript/juce_JSON.h +++ b/source/modules/juce_core/javascript/juce_JSON.h @@ -52,7 +52,7 @@ public: it will contain an error message. If you're not interested in the error message, you can use one of the other - shortcut parse methods, which simply return a var::null if the parsing fails. + shortcut parse methods, which simply return a var() if the parsing fails. Note that this will only parse valid JSON, which means that the item given must be either an object or an array definition. If you want to also be able to parse @@ -62,7 +62,7 @@ public: /** Attempts to parse some JSON-formatted text, and returns the result as a var object. - If the parsing fails, this simply returns var::null - if you need to find out more + If the parsing fails, this simply returns var() - if you need to find out more detail about the parse error, use the alternative parse() method which returns a Result. Note that this will only parse valid JSON, which means that the item given must @@ -77,7 +77,7 @@ public: Note that this is just a short-cut for reading the entire file into a string and parsing the result. - If the parsing fails, this simply returns var::null - if you need to find out more + If the parsing fails, this simply returns var() - if you need to find out more detail about the parse error, use the alternative parse() method which returns a Result. */ static var parse (const File& file); @@ -88,7 +88,7 @@ public: Note that this is just a short-cut for reading the entire stream into a string and parsing the result. - If the parsing fails, this simply returns var::null - if you need to find out more + If the parsing fails, this simply returns var() - if you need to find out more detail about the parse error, use the alternative parse() method which returns a Result. */ static var parse (InputStream& input); diff --git a/source/modules/juce_core/javascript/juce_Javascript.cpp b/source/modules/juce_core/javascript/juce_Javascript.cpp index 4b88a7882..13d572c30 100644 --- a/source/modules/juce_core/javascript/juce_Javascript.cpp +++ b/source/modules/juce_core/javascript/juce_Javascript.cpp @@ -67,12 +67,13 @@ struct JavascriptEngine::RootObject : public DynamicObject { RootObject() { - setMethod ("exec", exec); - setMethod ("eval", eval); - setMethod ("trace", trace); - setMethod ("charToInt", charToInt); - setMethod ("parseInt", IntegerClass::parseInt); - setMethod ("typeof", typeof_internal); + setMethod ("exec", exec); + setMethod ("eval", eval); + setMethod ("trace", trace); + setMethod ("charToInt", charToInt); + setMethod ("parseInt", IntegerClass::parseInt); + setMethod ("typeof", typeof_internal); + setMethod ("parseFloat", parseFloat); } Time timeout; @@ -410,22 +411,46 @@ struct JavascriptEngine::RootObject : public DynamicObject var getResult (const Scope& s) const override { - if (const Array* array = object->getResult (s).getArray()) - return (*array) [static_cast (index->getResult (s))]; + var arrayVar (object->getResult (s)); // must stay alive for the scope of this method + var key = index->getResult (s); + + if (const Array* array = arrayVar.getArray()) + if (key.isInt() || key.isInt64() || key.isDouble()) + return (*array) [static_cast (key)]; + + if (DynamicObject* o = arrayVar.getDynamicObject()) + if (key.isString()) + if (const var* v = getPropertyPointer (o, Identifier (key))) + return *v; return var::undefined(); } void assign (const Scope& s, const var& newValue) const override { - if (Array* array = object->getResult (s).getArray()) + var arrayVar (object->getResult (s)); // must stay alive for the scope of this method + var key = index->getResult (s); + + if (Array* array = arrayVar.getArray()) { - const int i = index->getResult (s); - while (array->size() < i) - array->add (var::undefined()); + if (key.isInt() || key.isInt64() || key.isDouble()) + { + const int i = key; + while (array->size() < i) + array->add (var::undefined()); - array->set (i, newValue); - return; + array->set (i, newValue); + return; + } + } + + if (DynamicObject* o = arrayVar.getDynamicObject()) + { + if (key.isString()) + { + o->setProperty (Identifier (key), newValue); + return; + } } Expression::assign (s, newValue); @@ -1362,7 +1387,7 @@ struct JavascriptEngine::RootObject : public DynamicObject { ScopedPointer f (new FunctionCall (location)); f->object = new UnqualifiedName (location, "typeof"); - f->arguments.add (parseExpression()); + f->arguments.add (parseUnary()); return f.release(); } @@ -1501,6 +1526,8 @@ struct JavascriptEngine::RootObject : public DynamicObject setMethod ("remove", remove); setMethod ("join", join); setMethod ("push", push); + setMethod ("splice", splice); + setMethod ("indexOf", indexOf); } static Identifier getClassName() { static const Identifier i ("Array"); return i; } @@ -1544,6 +1571,52 @@ struct JavascriptEngine::RootObject : public DynamicObject return var::undefined(); } + + static var splice (Args a) + { + if (Array* array = a.thisObject.getArray()) + { + const int arraySize = array->size(); + int start = get (a, 0); + + if (start < 0) + start = jmax (0, arraySize + start); + else if (start > arraySize) + start = arraySize; + + const int num = a.numArguments > 1 ? jlimit (0, arraySize - start, getInt (a, 1)) + : arraySize - start; + + Array itemsRemoved; + itemsRemoved.ensureStorageAllocated (num); + + for (int i = 0; i < num; ++i) + itemsRemoved.add (array->getReference (start + i)); + + array->removeRange (start, num); + + for (int i = 2; i < a.numArguments; ++i) + array->insert (start++, get (a, i)); + + return itemsRemoved; + } + + return var::undefined(); + } + + static var indexOf (Args a) + { + if (const Array* array = a.thisObject.getArray()) + { + const var target (get (a, 0)); + + for (int i = (a.numArguments > 1 ? getInt (a, 1) : 0); i < array->size(); ++i) + if (array->getReference(i) == target) + return i; + } + + return -1; + } }; //============================================================================== @@ -1673,6 +1746,7 @@ struct JavascriptEngine::RootObject : public DynamicObject //============================================================================== static var trace (Args a) { Logger::outputDebugString (JSON::toString (a.thisObject)); return var::undefined(); } static var charToInt (Args a) { return (int) (getString (a, 0)[0]); } + static var parseFloat (Args a) { return getDouble (a, 0); } static var typeof_internal (Args a) { diff --git a/source/modules/juce_core/juce_core.cpp b/source/modules/juce_core/juce_core.cpp index b052bf54e..55c53e295 100644 --- a/source/modules/juce_core/juce_core.cpp +++ b/source/modules/juce_core/juce_core.cpp @@ -35,6 +35,8 @@ #error "Incorrect use of JUCE cpp file" #endif +#include "AppConfig.h" + #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 @@ -43,6 +45,7 @@ #include #include +#include #if ! JUCE_ANDROID #include @@ -81,6 +84,7 @@ #endif #if JUCE_LINUX + #include #include #include #include @@ -117,7 +121,7 @@ //============================================================================== #ifndef JUCE_STANDALONE_APPLICATION - JUCE_COMPILER_WARNING ("Please re-save your Introjucer project with the latest Introjucer version to avoid this warning") + JUCE_COMPILER_WARNING ("Please re-save your project with the latest Projucer version to avoid this warning") #define JUCE_STANDALONE_APPLICATION 0 #endif diff --git a/source/modules/juce_core/juce_core.h b/source/modules/juce_core/juce_core.h index 7f7523ed8..d78f1a10a 100644 --- a/source/modules/juce_core/juce_core.h +++ b/source/modules/juce_core/juce_core.h @@ -26,6 +26,36 @@ ============================================================================== */ + +/******************************************************************************* + The block below describes the properties of this module, and is read by + the Projucer to automatically generate project code that uses it. + For details about the syntax and how to create or use a module, see the + JUCE Module Format.txt file. + + + BEGIN_JUCE_MODULE_DECLARATION + + ID: juce_core + vendor: juce + version: 4.3.0 + name: JUCE core classes + description: The essential set of basic JUCE classes, as required by all the other JUCE modules. Includes text, container, memory, threading and i/o functionality. + website: http://www.juce.com/juce + license: GPL/Commercial + + dependencies: + OSXFrameworks: Cocoa IOKit + iOSFrameworks: Foundation + linuxLibs: rt dl pthread + linuxPackages: libcurl + mingwLibs: uuid wsock32 wininet version ole32 ws2_32 oleaut32 imm32 comdlg32 shlwapi rpcrt4 winmm + + END_JUCE_MODULE_DECLARATION + +*******************************************************************************/ + + #ifndef JUCE_CORE_H_INCLUDED #define JUCE_CORE_H_INCLUDED @@ -39,6 +69,7 @@ #endif #endif +#include "../AppConfig.h" #include "system/juce_TargetPlatform.h" //============================================================================== @@ -109,7 +140,7 @@ /** Config: JUCE_USE_CURL Enables http/https support via libcurl (Linux only). Enabling this will add an additional - run-time dynmic dependency to libcurl. + run-time dynamic dependency to libcurl. If you disable this then https/ssl support will not be available on linux. */ @@ -118,7 +149,7 @@ #endif -/* Config: JUCE_CATCH_UNHANDLED_EXCEPTIONS +/** Config: JUCE_CATCH_UNHANDLED_EXCEPTIONS If enabled, this will add some exception-catching code to forward unhandled exceptions to your JUCEApplicationBase::unhandledException() callback. */ @@ -126,6 +157,16 @@ //#define JUCE_CATCH_UNHANDLED_EXCEPTIONS 1 #endif +/** Config: JUCE_ALLOW_STATIC_NULL_VARIABLES + If disabled, this will turn off dangerous static globals like String::empty, var::null, etc + which can cause nasty order-of-initialisation problems if they are referenced during static + constructor code. +*/ +#ifndef JUCE_ALLOW_STATIC_NULL_VARIABLES + #define JUCE_ALLOW_STATIC_NULL_VARIABLES 1 +#endif + + #ifndef JUCE_STRING_UTF_TYPE #define JUCE_STRING_UTF_TYPE 8 #endif @@ -312,6 +353,11 @@ extern JUCE_API void JUCE_CALLTYPE logAssertion (const char* file, int line) noe #if JUCE_MSVC #pragma warning (pop) + + // In DLL builds, need to disable this warnings for other modules + #if defined (JUCE_DLL_BUILD) || defined (JUCE_DLL) + #pragma warning (disable: 4251) + #endif #endif #endif // JUCE_CORE_H_INCLUDED diff --git a/source/modules/juce_core/maths/juce_BigInteger.cpp b/source/modules/juce_core/maths/juce_BigInteger.cpp index 0e2b1d938..44b9d68f1 100644 --- a/source/modules/juce_core/maths/juce_BigInteger.cpp +++ b/source/modules/juce_core/maths/juce_BigInteger.cpp @@ -28,76 +28,110 @@ namespace { - inline size_t bitToIndex (const int bit) noexcept { return (size_t) (bit >> 5); } - inline uint32 bitToMask (const int bit) noexcept { return (uint32) 1 << (bit & 31); } + inline uint32 bitToMask (const int bit) noexcept { return (uint32) 1 << (bit & 31); } + inline size_t bitToIndex (const int bit) noexcept { return (size_t) (bit >> 5); } + inline size_t sizeNeededToHold (int highestBit) noexcept { return (size_t) (highestBit >> 5) + 1; } + + inline int highestBitInInt (uint32 n) noexcept + { + jassert (n != 0); // (the built-in functions may not work for n = 0) + + #if JUCE_GCC || JUCE_CLANG + return 31 - __builtin_clz (n); + #elif JUCE_MSVC + unsigned long highest; + _BitScanReverse (&highest, n); + return (int) highest; + #else + n |= (n >> 1); + n |= (n >> 2); + n |= (n >> 4); + n |= (n >> 8); + n |= (n >> 16); + return countBitsInInt32 (n >> 1); + #endif + } } //============================================================================== BigInteger::BigInteger() - : numValues (4), + : allocatedSize (numPreallocatedInts), highestBit (-1), negative (false) { - values.calloc (numValues + 1); + for (int i = 0; i < numPreallocatedInts; ++i) + preallocated[i] = 0; } BigInteger::BigInteger (const int32 value) - : numValues (4), + : allocatedSize (numPreallocatedInts), highestBit (31), negative (value < 0) { - values.calloc (numValues + 1); - values[0] = (uint32) abs (value); + preallocated[0] = (uint32) std::abs (value); + + for (int i = 1; i < numPreallocatedInts; ++i) + preallocated[i] = 0; + highestBit = getHighestBit(); } BigInteger::BigInteger (const uint32 value) - : numValues (4), + : allocatedSize (numPreallocatedInts), highestBit (31), negative (false) { - values.calloc (numValues + 1); - values[0] = value; + preallocated[0] = value; + + for (int i = 1; i < numPreallocatedInts; ++i) + preallocated[i] = 0; + highestBit = getHighestBit(); } BigInteger::BigInteger (int64 value) - : numValues (4), + : allocatedSize (numPreallocatedInts), highestBit (63), negative (value < 0) { - values.calloc (numValues + 1); - if (value < 0) value = -value; - values[0] = (uint32) value; - values[1] = (uint32) (value >> 32); + preallocated[0] = (uint32) value; + preallocated[1] = (uint32) (value >> 32); + + for (int i = 2; i < numPreallocatedInts; ++i) + preallocated[i] = 0; + highestBit = getHighestBit(); } BigInteger::BigInteger (const BigInteger& other) - : numValues ((size_t) jmax ((size_t) 4, bitToIndex (other.highestBit) + 1)), + : allocatedSize (other.allocatedSize), highestBit (other.getHighestBit()), negative (other.negative) { - values.malloc (numValues + 1); - memcpy (values, other.values, sizeof (uint32) * (numValues + 1)); + if (allocatedSize > numPreallocatedInts) + heapAllocation.malloc (allocatedSize); + + memcpy (getValues(), other.getValues(), sizeof (uint32) * allocatedSize); } #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS BigInteger::BigInteger (BigInteger&& other) noexcept - : values (static_cast&&> (other.values)), - numValues (other.numValues), + : heapAllocation (static_cast&&> (other.heapAllocation)), + allocatedSize (other.allocatedSize), highestBit (other.highestBit), negative (other.negative) { + memcpy (preallocated, other.preallocated, sizeof (preallocated)); } BigInteger& BigInteger::operator= (BigInteger&& other) noexcept { - values = static_cast&&> (other.values); - numValues = other.numValues; + heapAllocation = static_cast&&> (other.heapAllocation); + memcpy (preallocated, other.preallocated, sizeof (preallocated)); + allocatedSize = other.allocatedSize; highestBit = other.highestBit; negative = other.negative; return *this; @@ -110,8 +144,11 @@ BigInteger::~BigInteger() void BigInteger::swapWith (BigInteger& other) noexcept { - values.swapWith (other.values); - std::swap (numValues, other.numValues); + for (int i = 0; i < numPreallocatedInts; ++i) + std::swap (preallocated[i], other.preallocated[i]); + + heapAllocation.swapWith (other.heapAllocation); + std::swap (allocatedSize, other.allocatedSize); std::swap (highestBit, other.highestBit); std::swap (negative, other.negative); } @@ -121,44 +158,70 @@ BigInteger& BigInteger::operator= (const BigInteger& other) if (this != &other) { highestBit = other.getHighestBit(); - jassert (other.numValues >= 4); - numValues = (size_t) jmax ((size_t) 4, bitToIndex (highestBit) + 1); + const size_t newAllocatedSize = (size_t) jmax ((size_t) numPreallocatedInts, sizeNeededToHold (highestBit)); + + if (newAllocatedSize <= numPreallocatedInts) + heapAllocation.free(); + else if (newAllocatedSize != allocatedSize) + heapAllocation.malloc (newAllocatedSize); + + allocatedSize = newAllocatedSize; + + memcpy (getValues(), other.getValues(), sizeof (uint32) * allocatedSize); negative = other.negative; - values.malloc (numValues + 1); - memcpy (values, other.values, sizeof (uint32) * (numValues + 1)); } return *this; } -void BigInteger::ensureSize (const size_t numVals) +uint32* BigInteger::getValues() const noexcept { - if (numVals + 2 >= numValues) + jassert (heapAllocation != nullptr || allocatedSize <= numPreallocatedInts); + + return heapAllocation != nullptr ? heapAllocation + : (uint32*) preallocated; +} + +uint32* BigInteger::ensureSize (const size_t numVals) +{ + if (numVals > allocatedSize) { - size_t oldSize = numValues; - numValues = ((numVals + 2) * 3) / 2; - values.realloc (numValues + 1); + size_t oldSize = allocatedSize; + allocatedSize = ((numVals + 2) * 3) / 2; + + if (heapAllocation == nullptr) + { + heapAllocation.calloc (allocatedSize); + memcpy (heapAllocation, preallocated, sizeof (uint32) * numPreallocatedInts); + } + else + { + heapAllocation.realloc (allocatedSize); - while (oldSize < numValues) - values [oldSize++] = 0; + for (uint32* values = getValues(); oldSize < allocatedSize; ++oldSize) + values[oldSize] = 0; + } } + + return getValues(); } //============================================================================== bool BigInteger::operator[] (const int bit) const noexcept { return bit <= highestBit && bit >= 0 - && ((values [bitToIndex (bit)] & bitToMask (bit)) != 0); + && ((getValues() [bitToIndex (bit)] & bitToMask (bit)) != 0); } int BigInteger::toInteger() const noexcept { - const int n = (int) (values[0] & 0x7fffffff); + const int n = (int) (getValues()[0] & 0x7fffffff); return negative ? -n : n; } int64 BigInteger::toInt64() const noexcept { + const uint32* values = getValues(); const int64 n = (((int64) (values[1] & 0x7fffffff)) << 32) | values[0]; return negative ? -n : n; } @@ -167,13 +230,12 @@ BigInteger BigInteger::getBitRange (int startBit, int numBits) const { BigInteger r; numBits = jmin (numBits, getHighestBit() + 1 - startBit); - r.ensureSize ((size_t) bitToIndex (numBits)); + uint32* const destValues = r.ensureSize (sizeNeededToHold (numBits)); r.highestBit = numBits; - int i = 0; - while (numBits > 0) + for (int i = 0; numBits > 0;) { - r.values[i++] = getBitRangeAsInt (startBit, (int) jmin (32, numBits)); + destValues[i++] = getBitRangeAsInt (startBit, (int) jmin (32, numBits)); numBits -= 32; startBit += 32; } @@ -198,6 +260,7 @@ uint32 BigInteger::getBitRangeAsInt (const int startBit, int numBits) const noex const size_t pos = bitToIndex (startBit); const int offset = startBit & 31; const int endSpace = 32 - numBits; + const uint32* values = getValues(); uint32 n = ((uint32) values [pos]) >> offset; @@ -223,20 +286,15 @@ void BigInteger::setBitRangeAsInt (const int startBit, int numBits, uint32 value } //============================================================================== -void BigInteger::clear() +void BigInteger::clear() noexcept { - if (numValues > 16) - { - numValues = 4; - values.calloc (numValues + 1); - } - else - { - values.clear (numValues + 1); - } - + heapAllocation.free(); + allocatedSize = numPreallocatedInts; highestBit = -1; negative = false; + + for (int i = 0; i < numPreallocatedInts; ++i) + preallocated[i] = 0; } void BigInteger::setBit (const int bit) @@ -245,11 +303,11 @@ void BigInteger::setBit (const int bit) { if (bit > highestBit) { - ensureSize (bitToIndex (bit)); + ensureSize (sizeNeededToHold (bit)); highestBit = bit; } - values [bitToIndex (bit)] |= bitToMask (bit); + getValues() [bitToIndex (bit)] |= bitToMask (bit); } } @@ -264,7 +322,12 @@ void BigInteger::setBit (const int bit, const bool shouldBeSet) void BigInteger::clearBit (const int bit) noexcept { if (bit >= 0 && bit <= highestBit) - values [bitToIndex (bit)] &= ~bitToMask (bit); + { + getValues() [bitToIndex (bit)] &= ~bitToMask (bit); + + if (bit == highestBit) + highestBit = getHighestBit(); + } } void BigInteger::setRange (int startBit, int numBits, const bool shouldBeSet) @@ -307,35 +370,16 @@ void BigInteger::negate() noexcept negative = (! negative) && ! isZero(); } -#if JUCE_USE_MSVC_INTRINSICS && ! defined (__INTEL_COMPILER) +#if JUCE_MSVC && ! defined (__INTEL_COMPILER) #pragma intrinsic (_BitScanReverse) #endif -inline static int highestBitInInt (uint32 n) noexcept -{ - jassert (n != 0); // (the built-in functions may not work for n = 0) - - #if JUCE_GCC || JUCE_CLANG - return 31 - __builtin_clz (n); - #elif JUCE_USE_MSVC_INTRINSICS - unsigned long highest; - _BitScanReverse (&highest, n); - return (int) highest; - #else - n |= (n >> 1); - n |= (n >> 2); - n |= (n >> 4); - n |= (n >> 8); - n |= (n >> 16); - return countBitsInInt32 (n >> 1); - #endif -} - int BigInteger::countNumberOfSetBits() const noexcept { int total = 0; + const uint32* values = getValues(); - for (int i = (int) bitToIndex (highestBit) + 1; --i >= 0;) + for (int i = (int) sizeNeededToHold (highestBit); --i >= 0;) total += countNumberOfBits (values[i]); return total; @@ -343,19 +387,19 @@ int BigInteger::countNumberOfSetBits() const noexcept int BigInteger::getHighestBit() const noexcept { - for (int i = (int) bitToIndex (highestBit + 1); i >= 0; --i) - { - const uint32 n = values[i]; + const uint32* values = getValues(); - if (n != 0) + for (int i = (int) bitToIndex (highestBit); i >= 0; --i) + if (uint32 n = values[i]) return highestBitInInt (n) + (i << 5); - } return -1; } int BigInteger::findNextSetBit (int i) const noexcept { + const uint32* values = getValues(); + for (; i <= highestBit; ++i) if ((values [bitToIndex (i)] & bitToMask (i)) != 0) return i; @@ -365,6 +409,8 @@ int BigInteger::findNextSetBit (int i) const noexcept int BigInteger::findNextClearBit (int i) const noexcept { + const uint32* values = getValues(); + for (; i <= highestBit; ++i) if ((values [bitToIndex (i)] & bitToMask (i)) == 0) break; @@ -375,6 +421,9 @@ int BigInteger::findNextClearBit (int i) const noexcept //============================================================================== BigInteger& BigInteger::operator+= (const BigInteger& other) { + if (this == &other) + return operator+= (BigInteger (other)); + if (other.isNegative()) return operator-= (-other); @@ -385,34 +434,30 @@ BigInteger& BigInteger::operator+= (const BigInteger& other) BigInteger temp (*this); temp.negate(); *this = other; - operator-= (temp); + *this -= temp; } else { negate(); - operator-= (other); + *this -= other; negate(); } } else { - if (other.highestBit > highestBit) - highestBit = other.highestBit; - - ++highestBit; - - const size_t numInts = bitToIndex (highestBit) + 1; - ensureSize (numInts); + highestBit = jmax (highestBit, other.highestBit) + 1; + const size_t numInts = sizeNeededToHold (highestBit); + uint32* const values = ensureSize (numInts); + const uint32* const otherValues = other.getValues(); int64 remainder = 0; - for (size_t i = 0; i <= numInts; ++i) + for (size_t i = 0; i < numInts; ++i) { - if (i < numValues) - remainder += values[i]; + remainder += values[i]; - if (i < other.numValues) - remainder += other.values[i]; + if (i < other.allocatedSize) + remainder += otherValues[i]; values[i] = (uint32) remainder; remainder >>= 32; @@ -427,36 +472,43 @@ BigInteger& BigInteger::operator+= (const BigInteger& other) BigInteger& BigInteger::operator-= (const BigInteger& other) { + if (this == &other) + { + clear(); + return *this; + } + if (other.isNegative()) return operator+= (-other); - if (! isNegative()) + if (isNegative()) { - if (compareAbsolute (other) < 0) - { - BigInteger temp (other); - swapWith (temp); - operator-= (temp); - negate(); - return *this; - } + negate(); + *this += other; + negate(); + return *this; } - else + + if (compareAbsolute (other) < 0) { - negate(); - operator+= (other); + BigInteger temp (other); + swapWith (temp); + *this -= temp; negate(); return *this; } - const size_t numInts = bitToIndex (highestBit) + 1; - const size_t maxOtherInts = bitToIndex (other.highestBit) + 1; + const size_t numInts = sizeNeededToHold (getHighestBit()); + const size_t maxOtherInts = sizeNeededToHold (other.getHighestBit()); + jassert (numInts >= maxOtherInts); + uint32* const values = getValues(); + const uint32* const otherValues = other.getValues(); int64 amountToSubtract = 0; - for (size_t i = 0; i <= numInts; ++i) + for (size_t i = 0; i < numInts; ++i) { - if (i <= maxOtherInts) - amountToSubtract += (int64) other.values[i]; + if (i < maxOtherInts) + amountToSubtract += (int64) otherValues[i]; if (values[i] >= amountToSubtract) { @@ -471,34 +523,60 @@ BigInteger& BigInteger::operator-= (const BigInteger& other) } } + highestBit = getHighestBit(); return *this; } BigInteger& BigInteger::operator*= (const BigInteger& other) { - BigInteger total; - highestBit = getHighestBit(); + if (this == &other) + return operator*= (BigInteger (other)); + + int n = getHighestBit(); + int t = other.getHighestBit(); + const bool wasNegative = isNegative(); setNegative (false); - for (int i = 0; i <= highestBit; ++i) + BigInteger total; + total.highestBit = n + t + 1; + uint32* const totalValues = total.ensureSize (sizeNeededToHold (total.highestBit) + 1); + + n >>= 5; + t >>= 5; + + BigInteger m (other); + m.setNegative (false); + + const uint32* const mValues = m.getValues(); + const uint32* const values = getValues(); + + for (int i = 0; i <= t; ++i) { - if (operator[](i)) + uint32 c = 0; + + for (int j = 0; j <= n; ++j) { - BigInteger n (other); - n.setNegative (false); - n <<= i; - total += n; + uint64 uv = (uint64) totalValues[i + j] + (uint64) values[j] * (uint64) mValues[i] + (uint64) c; + totalValues[i + j] = (uint32) uv; + c = uv >> 32; } + + totalValues[i + n + 1] = c; } + total.highestBit = total.getHighestBit(); total.setNegative (wasNegative ^ other.isNegative()); swapWith (total); + return *this; } void BigInteger::divideBy (const BigInteger& divisor, BigInteger& remainder) { + if (this == &divisor) + return divideBy (BigInteger (divisor), remainder); + jassert (this != &remainder); // (can't handle passing itself in to get the remainder) const int divHB = divisor.getHighestBit(); @@ -550,17 +628,21 @@ BigInteger& BigInteger::operator/= (const BigInteger& other) BigInteger& BigInteger::operator|= (const BigInteger& other) { + if (this == &other) + return *this; + // this operation doesn't take into account negative values.. jassert (isNegative() == other.isNegative()); if (other.highestBit >= 0) { - ensureSize (bitToIndex (other.highestBit)); + uint32* const values = ensureSize (sizeNeededToHold (other.highestBit)); + const uint32* const otherValues = other.getValues(); int n = (int) bitToIndex (other.highestBit) + 1; while (--n >= 0) - values[n] |= other.values[n]; + values[n] |= otherValues[n]; if (other.highestBit > highestBit) highestBit = other.highestBit; @@ -573,16 +655,22 @@ BigInteger& BigInteger::operator|= (const BigInteger& other) BigInteger& BigInteger::operator&= (const BigInteger& other) { + if (this == &other) + return *this; + // this operation doesn't take into account negative values.. jassert (isNegative() == other.isNegative()); - int n = (int) numValues; + uint32* const values = getValues(); + const uint32* const otherValues = other.getValues(); - while (n > (int) other.numValues) + int n = (int) allocatedSize; + + while (n > (int) other.allocatedSize) values[--n] = 0; while (--n >= 0) - values[n] &= other.values[n]; + values[n] &= otherValues[n]; if (other.highestBit < highestBit) highestBit = other.highestBit; @@ -593,17 +681,24 @@ BigInteger& BigInteger::operator&= (const BigInteger& other) BigInteger& BigInteger::operator^= (const BigInteger& other) { + if (this == &other) + { + clear(); + return *this; + } + // this operation will only work with the absolute values jassert (isNegative() == other.isNegative()); if (other.highestBit >= 0) { - ensureSize (bitToIndex (other.highestBit)); + uint32* const values = ensureSize (sizeNeededToHold (other.highestBit)); + const uint32* const otherValues = other.getValues(); int n = (int) bitToIndex (other.highestBit) + 1; while (--n >= 0) - values[n] ^= other.values[n]; + values[n] ^= otherValues[n]; if (other.highestBit > highestBit) highestBit = other.highestBit; @@ -644,15 +739,15 @@ BigInteger& BigInteger::operator>>= (const int numBits) { shiftBits //============================================================================== int BigInteger::compare (const BigInteger& other) const noexcept { - if (isNegative() == other.isNegative()) + const bool isNeg = isNegative(); + + if (isNeg == other.isNegative()) { const int absComp = compareAbsolute (other); - return isNegative() ? -absComp : absComp; - } - else - { - return isNegative() ? -1 : 1; + return isNeg ? -absComp : absComp; } + + return isNeg ? -1 : 1; } int BigInteger::compareAbsolute (const BigInteger& other) const noexcept @@ -660,23 +755,24 @@ int BigInteger::compareAbsolute (const BigInteger& other) const noexcept const int h1 = getHighestBit(); const int h2 = other.getHighestBit(); - if (h1 > h2) - return 1; - else if (h1 < h2) - return -1; + if (h1 > h2) return 1; + if (h1 < h2) return -1; - for (int i = (int) bitToIndex (h1) + 1; --i >= 0;) - if (values[i] != other.values[i]) - return (values[i] > other.values[i]) ? 1 : -1; + const uint32* const values = getValues(); + const uint32* const otherValues = other.getValues(); + + for (int i = (int) bitToIndex (h1); i >= 0; --i) + if (values[i] != otherValues[i]) + return values[i] > otherValues[i] ? 1 : -1; return 0; } bool BigInteger::operator== (const BigInteger& other) const noexcept { return compare (other) == 0; } bool BigInteger::operator!= (const BigInteger& other) const noexcept { return compare (other) != 0; } -bool BigInteger::operator< (const BigInteger& other) const noexcept { return compare (other) < 0; } +bool BigInteger::operator< (const BigInteger& other) const noexcept { return compare (other) < 0; } bool BigInteger::operator<= (const BigInteger& other) const noexcept { return compare (other) <= 0; } -bool BigInteger::operator> (const BigInteger& other) const noexcept { return compare (other) > 0; } +bool BigInteger::operator> (const BigInteger& other) const noexcept { return compare (other) > 0; } bool BigInteger::operator>= (const BigInteger& other) const noexcept { return compare (other) >= 0; } //============================================================================== @@ -684,27 +780,27 @@ void BigInteger::shiftLeft (int bits, const int startBit) { if (startBit > 0) { - for (int i = highestBit + 1; --i >= startBit;) - setBit (i + bits, operator[] (i)); + for (int i = highestBit; i >= startBit; --i) + setBit (i + bits, (*this) [i]); while (--bits >= 0) clearBit (bits + startBit); } else { - ensureSize (bitToIndex (highestBit + bits) + 1); + uint32* const values = ensureSize (sizeNeededToHold (highestBit + bits)); const size_t wordsToMove = bitToIndex (bits); - size_t top = 1 + bitToIndex (highestBit); + size_t numOriginalInts = bitToIndex (highestBit); highestBit += bits; if (wordsToMove > 0) { - for (int i = (int) top; --i >= 0;) - values [(size_t) i + wordsToMove] = values [i]; + for (int i = (int) numOriginalInts; i >= 0; --i) + values[(size_t) i + wordsToMove] = values[i]; for (size_t j = 0; j < wordsToMove; ++j) - values [j] = 0; + values[j] = 0; bits &= 31; } @@ -713,10 +809,10 @@ void BigInteger::shiftLeft (int bits, const int startBit) { const int invBits = 32 - bits; - for (size_t i = top + 1 + wordsToMove; --i > wordsToMove;) - values[i] = (values[i] << bits) | (values [i - 1] >> invBits); + for (size_t i = bitToIndex (highestBit); i > wordsToMove; --i) + values[i] = (values[i] << bits) | (values[i - 1] >> invBits); - values [wordsToMove] = values [wordsToMove] << bits; + values[wordsToMove] = values[wordsToMove] << bits; } highestBit = getHighestBit(); @@ -728,7 +824,7 @@ void BigInteger::shiftRight (int bits, const int startBit) if (startBit > 0) { for (int i = startBit; i <= highestBit; ++i) - setBit (i, operator[] (i + bits)); + setBit (i, (*this) [i + bits]); highestBit = getHighestBit(); } @@ -743,15 +839,16 @@ void BigInteger::shiftRight (int bits, const int startBit) const size_t wordsToMove = bitToIndex (bits); size_t top = 1 + bitToIndex (highestBit) - wordsToMove; highestBit -= bits; + uint32* const values = getValues(); if (wordsToMove > 0) { size_t i; for (i = 0; i < top; ++i) - values [i] = values [i + wordsToMove]; + values[i] = values[i + wordsToMove]; for (i = 0; i < wordsToMove; ++i) - values [top + i] = 0; + values[top + i] = 0; bits &= 31; } @@ -759,10 +856,10 @@ void BigInteger::shiftRight (int bits, const int startBit) if (bits != 0) { const int invBits = 32 - bits; - --top; + for (size_t i = 0; i < top; ++i) - values[i] = (values[i] >> bits) | (values [i + 1] << invBits); + values[i] = (values[i] >> bits) | (values[i + 1] << invBits); values[top] = (values[top] >> bits); } @@ -803,7 +900,7 @@ BigInteger BigInteger::findGreatestCommonDivisor (BigInteger n) const while (! n.isZero()) { - if (abs (m.getHighestBit() - n.getHighestBit()) <= 16) + if (std::abs (m.getHighestBit() - n.getHighestBit()) <= 16) return simpleGCD (&m, &n); BigInteger temp2; @@ -818,25 +915,128 @@ BigInteger BigInteger::findGreatestCommonDivisor (BigInteger n) const void BigInteger::exponentModulo (const BigInteger& exponent, const BigInteger& modulus) { + *this %= modulus; BigInteger exp (exponent); exp %= modulus; - BigInteger value (1); - swapWith (value); - value %= modulus; + if (modulus.getHighestBit() <= 32 || modulus % 2 == 0) + { + BigInteger a (*this); + + const int n = exp.getHighestBit(); + + for (int i = n; --i >= 0;) + { + *this *= *this; + + if (exp[i]) + *this *= a; - while (! exp.isZero()) + if (compareAbsolute (modulus) >= 0) + *this %= modulus; + } + } + else { - if (exp [0]) + const int Rfactor = modulus.getHighestBit() + 1; + BigInteger R (1); + R.shiftLeft (Rfactor, 0); + + BigInteger R1, m1, g; + g.extendedEuclidean (modulus, R, m1, R1); + + if (! g.isOne()) { - operator*= (value); - operator%= (modulus); + BigInteger a (*this); + + for (int i = exp.getHighestBit(); --i >= 0;) + { + *this *= *this; + + if (exp[i]) + *this *= a; + + if (compareAbsolute (modulus) >= 0) + *this %= modulus; + } } + else + { + BigInteger am (((*this) * R) % modulus); + BigInteger xm (am); + BigInteger um (R % modulus); + + for (int i = exp.getHighestBit(); --i >= 0;) + { + xm.montgomeryMultiplication (xm, modulus, m1, Rfactor); + + if (exp[i]) + xm.montgomeryMultiplication (am, modulus, m1, Rfactor); + } + + xm.montgomeryMultiplication (1, modulus, m1, Rfactor); + swapWith (xm); + } + } +} + +void BigInteger::montgomeryMultiplication (const BigInteger& other, const BigInteger& modulus, + const BigInteger& modulusp, const int k) +{ + *this *= other; + + BigInteger t (*this); + + setRange (k, highestBit - k + 1, false); + *this *= modulusp; + + setRange (k, highestBit - k + 1, false); + *this *= modulus; + *this += t; + shiftRight (k, 0); + + if (compare (modulus) >= 0) + *this -= modulus; + else if (isNegative()) + *this += modulus; +} + +void BigInteger::extendedEuclidean (const BigInteger& a, const BigInteger& b, + BigInteger& x, BigInteger& y) +{ + BigInteger p(a), q(b), gcd(1); + + Array tempValues; + + while (! q.isZero()) + { + tempValues.add (p / q); + gcd = q; + q = p % q; + p = gcd; + } + + x.clear(); + y = 1; - value *= value; - value %= modulus; - exp >>= 1; + for (int i = 1; i < tempValues.size(); ++i) + { + const BigInteger& v = tempValues.getReference (tempValues.size() - i - 1); + + if ((i & 1) != 0) + x += y * v; + else + y += x * v; + } + + if (gcd.compareAbsolute (y * b - x * a) != 0) + { + x.negate(); + x.swapWith (y); + x.negate(); } + + swapWith (gcd); } void BigInteger::inverseModulo (const BigInteger& modulus) @@ -848,22 +1048,19 @@ void BigInteger::inverseModulo (const BigInteger& modulus) } if (isNegative() || compareAbsolute (modulus) >= 0) - operator%= (modulus); + *this %= modulus; if (isOne()) return; - if (! (*this)[0]) + if (findGreatestCommonDivisor (modulus) != 1) { - // not invertible - clear(); + clear(); // not invertible! return; } - BigInteger a1 (modulus); - BigInteger a2 (*this); - BigInteger b1 (modulus); - BigInteger b2 (1); + BigInteger a1 (modulus), a2 (*this); + BigInteger b1 (modulus), b2 (1); while (! a2.isOne()) { @@ -964,8 +1161,8 @@ void BigInteger::parseString (StringRef text, const int base) if (((uint32) digit) < (uint32) base) { - operator<<= (bits); - operator+= (digit); + *this <<= bits; + *this += digit; } else if (c == 0) { @@ -983,8 +1180,8 @@ void BigInteger::parseString (StringRef text, const int base) if (c >= '0' && c <= '9') { - operator*= (ten); - operator+= ((int) (c - '0')); + *this *= ten; + *this += (int) (c - '0'); } else if (c == 0) { @@ -998,6 +1195,7 @@ MemoryBlock BigInteger::toMemoryBlock() const { const int numBytes = (getHighestBit() + 8) >> 3; MemoryBlock mb ((size_t) numBytes); + const uint32* const values = getValues(); for (int i = 0; i < numBytes; ++i) mb[i] = (char) ((values[i / 4] >> ((i & 3) * 8)) & 0xff); @@ -1008,14 +1206,13 @@ MemoryBlock BigInteger::toMemoryBlock() const void BigInteger::loadFromMemoryBlock (const MemoryBlock& data) { const size_t numBytes = data.getSize(); - numValues = 1 + (numBytes / sizeof (uint32)); - values.malloc (numValues + 1); + const size_t numInts = 1 + (numBytes / sizeof (uint32)); + uint32* const values = ensureSize (numInts); - for (int i = 0; i < (int) numValues - 1; ++i) + for (int i = 0; i < (int) numInts - 1; ++i) values[i] = (uint32) ByteOrder::littleEndianInt (addBytesToPointer (data.getData(), sizeof (uint32) * (size_t) i)); - values[numValues - 1] = 0; - values[numValues] = 0; + values[numInts - 1] = 0; for (int i = (int) (numBytes & ~3u); i < (int) numBytes; ++i) this->setBitRangeAsInt (i << 3, 8, (uint32) data [i]); @@ -1024,6 +1221,76 @@ void BigInteger::loadFromMemoryBlock (const MemoryBlock& data) highestBit = getHighestBit(); } +//============================================================================== +void writeLittleEndianBitsInBuffer (void* buffer, uint32 startBit, uint32 numBits, uint32 value) noexcept +{ + jassert (buffer != nullptr); + jassert (numBits > 0 && numBits <= 32); + jassert (numBits == 32 || (value >> numBits) == 0); + + uint8* data = static_cast (buffer) + startBit / 8; + + if (const uint32 offset = (startBit & 7)) + { + const uint32 bitsInByte = 8 - offset; + const uint8 current = *data; + + if (bitsInByte >= numBits) + { + *data = (uint8) ((current & ~(((1u << numBits) - 1u) << offset)) | (value << offset)); + return; + } + + *data++ = current ^ (uint8) (((value << offset) ^ current) & (((1u << bitsInByte) - 1u) << offset)); + numBits -= bitsInByte; + value >>= bitsInByte; + } + + while (numBits >= 8) + { + *data++ = (uint8) value; + value >>= 8; + numBits -= 8; + } + + if (numBits > 0) + *data = (uint8) ((*data & (0xff << numBits)) | value); +} + +uint32 readLittleEndianBitsInBuffer (const void* buffer, uint32 startBit, uint32 numBits) noexcept +{ + jassert (buffer != nullptr); + jassert (numBits > 0 && numBits <= 32); + + uint32 result = 0; + uint32 bitsRead = 0; + const uint8* data = static_cast (buffer) + startBit / 8; + + if (const uint32 offset = (startBit & 7)) + { + const uint32 bitsInByte = 8 - offset; + result = (*data >> offset); + + if (bitsInByte >= numBits) + return result & ((1u << numBits) - 1u); + + numBits -= bitsInByte; + bitsRead += bitsInByte; + ++data; + } + + while (numBits >= 8) + { + result |= (((uint32) *data++) << bitsRead); + bitsRead += 8; + numBits -= 8; + } + + if (numBits > 0) + result |= ((*data & ((1u << numBits) - 1u)) << bitsRead); + + return result; +} //============================================================================== //============================================================================== @@ -1046,33 +1313,64 @@ public: void runTest() override { - beginTest ("BigInteger"); + { + beginTest ("BigInteger"); - Random r = getRandom(); + Random r = getRandom(); - expect (BigInteger().isZero()); - expect (BigInteger(1).isOne()); + expect (BigInteger().isZero()); + expect (BigInteger(1).isOne()); + + for (int j = 10000; --j >= 0;) + { + BigInteger b1 (getBigRandom(r)), + b2 (getBigRandom(r)); + + BigInteger b3 = b1 + b2; + expect (b3 > b1 && b3 > b2); + expect (b3 - b1 == b2); + expect (b3 - b2 == b1); + + BigInteger b4 = b1 * b2; + expect (b4 > b1 && b4 > b2); + expect (b4 / b1 == b2); + expect (b4 / b2 == b1); + expect (((b4 << 1) >> 1) == b4); + expect (((b4 << 10) >> 10) == b4); + expect (((b4 << 100) >> 100) == b4); + + // TODO: should add tests for other ops (although they also get pretty well tested in the RSA unit test) + + BigInteger b5; + b5.loadFromMemoryBlock (b3.toMemoryBlock()); + expect (b3 == b5); + } + } - for (int j = 10000; --j >= 0;) { - BigInteger b1 (getBigRandom(r)), - b2 (getBigRandom(r)); + beginTest ("Bit setting"); - BigInteger b3 = b1 + b2; - expect (b3 > b1 && b3 > b2); - expect (b3 - b1 == b2); - expect (b3 - b2 == b1); + Random r = getRandom(); + static uint8 test[2048]; - BigInteger b4 = b1 * b2; - expect (b4 > b1 && b4 > b2); - expect (b4 / b1 == b2); - expect (b4 / b2 == b1); + for (int j = 100000; --j >= 0;) + { + uint32 offset = static_cast (r.nextInt (200) + 10); + uint32 num = static_cast (r.nextInt (32) + 1); + uint32 value = static_cast (r.nextInt()); - // TODO: should add tests for other ops (although they also get pretty well tested in the RSA unit test) + if (num < 32) + value &= ((1u << num) - 1); - BigInteger b5; - b5.loadFromMemoryBlock (b3.toMemoryBlock()); - expect (b3 == b5); + auto old1 = readLittleEndianBitsInBuffer (test, offset - 6, 6); + auto old2 = readLittleEndianBitsInBuffer (test, offset + num, 6); + writeLittleEndianBitsInBuffer (test, offset, num, value); + auto result = readLittleEndianBitsInBuffer (test, offset, num); + + expect (result == value); + expect (old1 == readLittleEndianBitsInBuffer (test, offset - 6, 6)); + expect (old2 == readLittleEndianBitsInBuffer (test, offset + num, 6)); + } } } }; diff --git a/source/modules/juce_core/maths/juce_BigInteger.h b/source/modules/juce_core/maths/juce_BigInteger.h index 2f99424a3..4a663317b 100644 --- a/source/modules/juce_core/maths/juce_BigInteger.h +++ b/source/modules/juce_core/maths/juce_BigInteger.h @@ -106,7 +106,7 @@ public: //============================================================================== /** Resets the value to 0. */ - void clear(); + void clear() noexcept; /** Clears a particular bit in the number. */ void clearBit (int bitNumber) noexcept; @@ -180,6 +180,22 @@ public: */ int getHighestBit() const noexcept; + //============================================================================== + /** Returns true if the value is less than zero. + @see setNegative, negate + */ + bool isNegative() const noexcept; + + /** Changes the sign of the number to be positive or negative. + @see isNegative, negate + */ + void setNegative (bool shouldBeNegative) noexcept; + + /** Inverts the sign of the number. + @see isNegative, setNegative + */ + void negate() noexcept; + //============================================================================== // All the standard arithmetic ops... @@ -236,6 +252,7 @@ public: */ int compareAbsolute (const BigInteger& other) const noexcept; + //============================================================================== /** Divides this value by another one and returns the remainder. This number is divided by other, leaving the quotient in this number, @@ -243,7 +260,7 @@ public: */ void divideBy (const BigInteger& divisor, BigInteger& remainder); - /** Returns the largest value that will divide both this value and the one passed-in. */ + /** Returns the largest value that will divide both this value and the argument. */ BigInteger findGreatestCommonDivisor (BigInteger other) const; /** Performs a combined exponent and modulo operation. @@ -256,21 +273,20 @@ public: */ void inverseModulo (const BigInteger& modulus); - //============================================================================== - /** Returns true if the value is less than zero. - @see setNegative, negate - */ - bool isNegative() const noexcept; - - /** Changes the sign of the number to be positive or negative. - @see isNegative, negate + /** Performs the Montgomery Multiplication with modulo. + This object is left containing the result value: ((this * other) * R1) % modulus. + To get this result, we need modulus, modulusp and k such as R = 2^k, with + modulus * modulusp - R * R1 = GCD(modulus, R) = 1 */ - void setNegative (bool shouldBeNegative) noexcept; + void montgomeryMultiplication (const BigInteger& other, const BigInteger& modulus, + const BigInteger& modulusp, int k); - /** Inverts the sign of the number. - @see isNegative, setNegative + /** Performs the Extended Euclidean algorithm. + This method will set the xOut and yOut arguments such that (a * xOut) - (b * yOut) = GCD (a, b). + On return, this object is left containing the value of the GCD. */ - void negate() noexcept; + void extendedEuclidean (const BigInteger& a, const BigInteger& b, + BigInteger& xOut, BigInteger& yOut); //============================================================================== /** Converts the number to a string. @@ -309,12 +325,15 @@ public: private: //============================================================================== - HeapBlock values; - size_t numValues; + enum { numPreallocatedInts = 4 }; + HeapBlock heapAllocation; + uint32 preallocated[numPreallocatedInts]; + size_t allocatedSize; int highestBit; bool negative; - void ensureSize (size_t); + uint32* getValues() const noexcept; + uint32* ensureSize (size_t); void shiftLeft (int bits, int startBit); void shiftRight (int bits, int startBit); diff --git a/source/modules/juce_core/maths/juce_MathsFunctions.h b/source/modules/juce_core/maths/juce_MathsFunctions.h index de92d9718..7de74a316 100644 --- a/source/modules/juce_core/maths/juce_MathsFunctions.h +++ b/source/modules/juce_core/maths/juce_MathsFunctions.h @@ -93,7 +93,7 @@ typedef unsigned int uint32; #endif //============================================================================== -// Some indispensible min/max functions +// Some indispensable min/max functions /** Returns the larger of two values. */ template @@ -545,6 +545,25 @@ NumericType square (NumericType n) noexcept return n * n; } +//============================================================================== +/** Writes a number of bits into a memory buffer at a given bit index. + The buffer is treated as a sequence of 8-bit bytes, and the value is encoded in little-endian order, + so for example if startBit = 10, and numBits = 11 then the lower 6 bits of the value would be written + into bits 2-8 of targetBuffer[1], and the upper 5 bits of value into bits 0-5 of targetBuffer[2]. + + @see readLittleEndianBitsInBuffer +*/ +void writeLittleEndianBitsInBuffer (void* targetBuffer, uint32 startBit, uint32 numBits, uint32 value) noexcept; + +/** Reads a number of bits from a buffer at a given bit index. + The buffer is treated as a sequence of 8-bit bytes, and the value is encoded in little-endian order, + so for example if startBit = 10, and numBits = 11 then the lower 6 bits of the result would be read + from bits 2-8 of sourceBuffer[1], and the upper 5 bits of the result from bits 0-5 of sourceBuffer[2]. + + @see writeLittleEndianBitsInBuffer +*/ +uint32 readLittleEndianBitsInBuffer (const void* sourceBuffer, uint32 startBit, uint32 numBits) noexcept; + //============================================================================== #if JUCE_INTEL || defined (DOXYGEN) diff --git a/source/modules/juce_core/maths/juce_NormalisableRange.h b/source/modules/juce_core/maths/juce_NormalisableRange.h index 150162da6..694a83e86 100644 --- a/source/modules/juce_core/maths/juce_NormalisableRange.h +++ b/source/modules/juce_core/maths/juce_NormalisableRange.h @@ -40,17 +40,18 @@ @see Range */ -template +template class NormalisableRange { public: /** Creates a continuous range that performs a dummy mapping. */ - NormalisableRange() noexcept : start(), end (1), interval(), skew (static_cast (1)) {} + NormalisableRange() noexcept : start(), end (1), interval(), skew (static_cast (1)), symmetricSkew (false) {} /** Creates a copy of another range. */ NormalisableRange (const NormalisableRange& other) noexcept : start (other.start), end (other.end), - interval (other.interval), skew (other.skew) + interval (other.interval), skew (other.skew), + symmetricSkew (other.symmetricSkew) { checkInvariants(); } @@ -62,6 +63,7 @@ public: end = other.end; interval = other.interval; skew = other.skew; + symmetricSkew = other.symmetricSkew; checkInvariants(); return *this; } @@ -70,9 +72,10 @@ public: NormalisableRange (ValueType rangeStart, ValueType rangeEnd, ValueType intervalValue, - ValueType skewFactor) noexcept - : start (rangeStart), end (rangeEnd), - interval (intervalValue), skew (skewFactor) + ValueType skewFactor, + bool useSymmetricSkew = false) noexcept + : start (rangeStart), end (rangeEnd), interval (intervalValue), + skew (skewFactor), symmetricSkew (useSymmetricSkew) { checkInvariants(); } @@ -81,8 +84,8 @@ public: NormalisableRange (ValueType rangeStart, ValueType rangeEnd, ValueType intervalValue) noexcept - : start (rangeStart), end (rangeEnd), - interval (intervalValue), skew (static_cast (1)) + : start (rangeStart), end (rangeEnd), interval (intervalValue), + skew (static_cast (1)), symmetricSkew (false) { checkInvariants(); } @@ -90,8 +93,8 @@ public: /** Creates a NormalisableRange with a given range, continuous interval, but a dummy skew-factor. */ NormalisableRange (ValueType rangeStart, ValueType rangeEnd) noexcept - : start (rangeStart), end (rangeEnd), - interval(), skew (static_cast (1)) + : start (rangeStart), end (rangeEnd), interval(), + skew (static_cast (1)), symmetricSkew (false) { checkInvariants(); } @@ -103,10 +106,18 @@ public: { ValueType proportion = (v - start) / (end - start); - if (skew != static_cast (1)) - proportion = std::pow (proportion, skew); + if (skew == static_cast (1)) + return proportion; - return proportion; + if (! symmetricSkew) + return std::pow (proportion, skew); + + ValueType distanceFromMiddle = static_cast (2) * proportion - static_cast (1); + + return (static_cast (1) + std::pow (std::abs (distanceFromMiddle), skew) + * (distanceFromMiddle < static_cast (0) ? static_cast (-1) + : static_cast (1))) + / static_cast (2); } /** Uses the properties of this mapping to convert a normalised 0->1 value to @@ -114,10 +125,22 @@ public: */ ValueType convertFrom0to1 (ValueType proportion) const noexcept { - if (skew != static_cast (1) && proportion > ValueType()) - proportion = std::exp (std::log (proportion) / skew); + if (! symmetricSkew) + { + if (skew != static_cast (1) && proportion > ValueType()) + proportion = std::exp (std::log (proportion) / skew); + + return start + (end - start) * proportion; + } - return start + (end - start) * proportion; + ValueType distanceFromMiddle = static_cast (2) * proportion - static_cast (1); + + if (skew != static_cast (1) && distanceFromMiddle != static_cast (0)) + distanceFromMiddle = std::exp (std::log (std::abs (distanceFromMiddle)) / skew) + * (distanceFromMiddle < static_cast (0) ? static_cast (-1) + : static_cast (1)); + + return start + (end - start) / static_cast (2) * (static_cast (1) + distanceFromMiddle); } /** Takes a non-normalised value and snaps it based on the interval property of @@ -150,7 +173,7 @@ public: /** An optional skew factor that alters the way values are distribute across the range. The skew factor lets you skew the mapping logarithmically so that larger or smaller - values are given a larger proportion of the avilable space. + values are given a larger proportion of the available space. A factor of 1.0 has no skewing effect at all. If the factor is < 1.0, the lower end of the range will fill more of the slider's length; if the factor is > 1.0, the upper @@ -158,6 +181,9 @@ public: */ ValueType skew; + /** If true, the skew factor applies from the middle of the slider to each of its ends. */ + bool symmetricSkew; + private: void checkInvariants() const { diff --git a/source/modules/juce_core/maths/juce_Random.h b/source/modules/juce_core/maths/juce_Random.h index 9db453413..09462e368 100644 --- a/source/modules/juce_core/maths/juce_Random.h +++ b/source/modules/juce_core/maths/juce_Random.h @@ -80,12 +80,12 @@ public: int64 nextInt64() noexcept; /** Returns the next random floating-point number. - @returns a random value in the range 0 to 1.0 + @returns a random value in the range 0 (inclusive) to 1.0 (exclusive) */ float nextFloat() noexcept; /** Returns the next random floating-point number. - @returns a random value in the range 0 to 1.0 + @returns a random value in the range 0 (inclusive) to 1.0 (exclusive) */ double nextDouble() noexcept; diff --git a/source/modules/juce_core/maths/juce_Range.h b/source/modules/juce_core/maths/juce_Range.h index 67a4324b9..5f9d409e4 100644 --- a/source/modules/juce_core/maths/juce_Range.h +++ b/source/modules/juce_core/maths/juce_Range.h @@ -172,6 +172,15 @@ public: return Range (start, start + newLength); } + /** Returns a range which has its start moved down and its end moved up by the + given amount. + @returns The returned range will be (start - amount, end + amount) + */ + Range expanded (ValueType amount) const noexcept + { + return Range (start - amount, end + amount); + } + //============================================================================== /** Adds an amount to the start and end of the range. */ inline Range operator+= (const ValueType amountToAdd) noexcept diff --git a/source/modules/juce_core/memory/juce_Atomic.h b/source/modules/juce_core/memory/juce_Atomic.h index 1e8a65cc4..b392171fb 100644 --- a/source/modules/juce_core/memory/juce_Atomic.h +++ b/source/modules/juce_core/memory/juce_Atomic.h @@ -147,7 +147,7 @@ public: #endif /** The raw value that this class operates on. - This is exposed publically in case you need to manipulate it directly + This is exposed publicly in case you need to manipulate it directly for performance reasons. */ volatile Type value; @@ -187,19 +187,9 @@ private: /* The following code is in the header so that the atomics can be inlined where possible... */ -#if JUCE_MAC && (JUCE_PPC || __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 2)) +#if JUCE_MAC && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 2)) #define JUCE_ATOMICS_MAC_LEGACY 1 // Older OSX builds using gcc4.1 or earlier - #if JUCE_PPC - // None of these atomics are available for PPC or for iOS 3.1 or earlier!! - template static Type OSAtomicAdd64Barrier (Type b, volatile Type* a) noexcept { jassertfalse; return *a += b; } - template static Type OSAtomicIncrement64Barrier (volatile Type* a) noexcept { jassertfalse; return ++*a; } - template static Type OSAtomicDecrement64Barrier (volatile Type* a) noexcept { jassertfalse; return --*a; } - template static bool OSAtomicCompareAndSwap64Barrier (Type old, Type newValue, volatile Type* value) noexcept - { jassertfalse; if (old == *value) { *value = newValue; return true; } return false; } - #define JUCE_64BIT_ATOMICS_UNAVAILABLE 1 - #endif - //============================================================================== #elif JUCE_GCC || JUCE_CLANG #define JUCE_ATOMICS_GCC 1 // GCC with intrinsics @@ -212,27 +202,17 @@ private: #else #define JUCE_ATOMICS_WINDOWS 1 // Windows with intrinsics - #if JUCE_USE_MSVC_INTRINSICS - #ifndef __INTEL_COMPILER - #pragma intrinsic (_InterlockedExchange, _InterlockedIncrement, _InterlockedDecrement, _InterlockedCompareExchange, \ - _InterlockedCompareExchange64, _InterlockedExchangeAdd, _ReadWriteBarrier) - #endif - #define juce_InterlockedExchange(a, b) _InterlockedExchange(a, b) - #define juce_InterlockedIncrement(a) _InterlockedIncrement(a) - #define juce_InterlockedDecrement(a) _InterlockedDecrement(a) - #define juce_InterlockedExchangeAdd(a, b) _InterlockedExchangeAdd(a, b) - #define juce_InterlockedCompareExchange(a, b, c) _InterlockedCompareExchange(a, b, c) - #define juce_InterlockedCompareExchange64(a, b, c) _InterlockedCompareExchange64(a, b, c) - #define juce_MemoryBarrier _ReadWriteBarrier - #else - long juce_InterlockedExchange (volatile long* a, long b) noexcept; - long juce_InterlockedIncrement (volatile long* a) noexcept; - long juce_InterlockedDecrement (volatile long* a) noexcept; - long juce_InterlockedExchangeAdd (volatile long* a, long b) noexcept; - long juce_InterlockedCompareExchange (volatile long* a, long b, long c) noexcept; - __int64 juce_InterlockedCompareExchange64 (volatile __int64* a, __int64 b, __int64 c) noexcept; - inline void juce_MemoryBarrier() noexcept { long x = 0; juce_InterlockedIncrement (&x); } + #ifndef __INTEL_COMPILER + #pragma intrinsic (_InterlockedExchange, _InterlockedIncrement, _InterlockedDecrement, _InterlockedCompareExchange, \ + _InterlockedCompareExchange64, _InterlockedExchangeAdd, _ReadWriteBarrier) #endif + #define juce_InterlockedExchange(a, b) _InterlockedExchange(a, b) + #define juce_InterlockedIncrement(a) _InterlockedIncrement(a) + #define juce_InterlockedDecrement(a) _InterlockedDecrement(a) + #define juce_InterlockedExchangeAdd(a, b) _InterlockedExchangeAdd(a, b) + #define juce_InterlockedCompareExchange(a, b, c) _InterlockedCompareExchange(a, b, c) + #define juce_InterlockedCompareExchange64(a, b, c) _InterlockedCompareExchange64(a, b, c) + #define juce_MemoryBarrier _ReadWriteBarrier #if JUCE_64BIT #ifndef __INTEL_COMPILER diff --git a/source/modules/juce_core/memory/juce_ByteOrder.h b/source/modules/juce_core/memory/juce_ByteOrder.h index 02c0f71ae..c11763ffc 100644 --- a/source/modules/juce_core/memory/juce_ByteOrder.h +++ b/source/modules/juce_core/memory/juce_ByteOrder.h @@ -140,17 +140,13 @@ private: //============================================================================== -#if JUCE_USE_MSVC_INTRINSICS && ! defined (__INTEL_COMPILER) +#if JUCE_MSVC && ! defined (__INTEL_COMPILER) #pragma intrinsic (_byteswap_ulong) #endif inline uint16 ByteOrder::swap (uint16 n) noexcept { - #if JUCE_USE_MSVC_INTRINSICSxxx // agh - the MS compiler has an internal error when you try to use this intrinsic! - return static_cast (_byteswap_ushort (n)); - #else return static_cast ((n << 8) | (n >> 8)); - #endif } inline uint32 ByteOrder::swap (uint32 n) noexcept @@ -160,15 +156,8 @@ inline uint32 ByteOrder::swap (uint32 n) noexcept #elif (JUCE_GCC || JUCE_CLANG) && JUCE_INTEL && ! JUCE_NO_INLINE_ASM asm("bswap %%eax" : "=a"(n) : "a"(n)); return n; - #elif JUCE_USE_MSVC_INTRINSICS + #elif JUCE_MSVC return _byteswap_ulong (n); - #elif JUCE_MSVC && ! JUCE_NO_INLINE_ASM - __asm { - mov eax, n - bswap eax - mov n, eax - } - return n; #elif JUCE_ANDROID return bswap_32 (n); #else @@ -180,7 +169,7 @@ inline uint64 ByteOrder::swap (uint64 value) noexcept { #if JUCE_MAC || JUCE_IOS return OSSwapInt64 (value); - #elif JUCE_USE_MSVC_INTRINSICS + #elif JUCE_MSVC return _byteswap_uint64 (value); #else return (((uint64) swap ((uint32) value)) << 32) | swap ((uint32) (value >> 32)); diff --git a/source/modules/juce_core/memory/juce_LeakedObjectDetector.h b/source/modules/juce_core/memory/juce_LeakedObjectDetector.h index ac6803052..9a12e93ad 100644 --- a/source/modules/juce_core/memory/juce_LeakedObjectDetector.h +++ b/source/modules/juce_core/memory/juce_LeakedObjectDetector.h @@ -29,7 +29,6 @@ #ifndef JUCE_LEAKEDOBJECTDETECTOR_H_INCLUDED #define JUCE_LEAKEDOBJECTDETECTOR_H_INCLUDED -#define DBG2(dbgtext) { juce::String str; str << dbgtext; std::cout << str.toRawUTF8() << std::endl; } //============================================================================== /** @@ -56,7 +55,7 @@ public: { if (--(getCounter().numObjects) < 0) { - DBG2 ("*** Dangling pointer deletion! Class: " << getLeakedObjectClassName()); + DBG ("*** Dangling pointer deletion! Class: " << getLeakedObjectClassName()); /** If you hit this, then you've managed to delete more instances of this class than you've created.. That indicates that you're deleting some dangling pointers. @@ -69,7 +68,7 @@ public: your object management. Tut, tut. Always, always use ScopedPointers, OwnedArrays, ReferenceCountedObjects, etc, and avoid the 'delete' operator at all costs! */ - //jassertfalse; + jassertfalse; } } @@ -84,7 +83,7 @@ private: { if (numObjects.value > 0) { - DBG2 ("*** Leaked objects detected: " << numObjects.value << " instance(s) of class " << getLeakedObjectClassName()); + DBG ("*** Leaked objects detected: " << numObjects.value << " instance(s) of class " << getLeakedObjectClassName()); /** If you hit this, then you've leaked one or more objects of the type specified by the 'OwnerClass' template parameter - the name should have been printed by the line above. @@ -93,7 +92,7 @@ private: your object management. Tut, tut. Always, always use ScopedPointers, OwnedArrays, ReferenceCountedObjects, etc, and avoid the 'delete' operator at all costs! */ - //jassertfalse; + jassertfalse; } } diff --git a/source/modules/juce_core/memory/juce_MemoryBlock.h b/source/modules/juce_core/memory/juce_MemoryBlock.h index cbe025cc8..1bf964e9e 100644 --- a/source/modules/juce_core/memory/juce_MemoryBlock.h +++ b/source/modules/juce_core/memory/juce_MemoryBlock.h @@ -222,21 +222,29 @@ public: size_t numBitsToRead) const noexcept; //============================================================================== - /** Returns a string of characters that represent the binary contents of this block. + /** Returns a string of characters in a JUCE-specific text encoding that represents the + binary contents of this block. - Uses a 64-bit encoding system to allow binary data to be turned into a string - of simple non-extended characters, e.g. for storage in XML. + This uses a JUCE-specific (i.e. not standard!) 64-bit encoding system to convert binary + data into a string of ASCII characters for purposes like storage in XML. + Note that this proprietary format is mainly kept here for backwards-compatibility, and + you may prefer to use the Base64::toBase64() method if you want to use the standard + base-64 encoding. - @see fromBase64Encoding + @see fromBase64Encoding, Base64::toBase64, Base64::convertToBase64 */ String toBase64Encoding() const; - /** Takes a string of encoded characters and turns it into binary data. + /** Takes a string created by MemoryBlock::toBase64Encoding() and extracts the original data. The string passed in must have been created by to64BitEncoding(), and this block will be resized to recreate the original data block. - @see toBase64Encoding + Note that these methods use a JUCE-specific (i.e. not standard!) 64-bit encoding system. + You may prefer to use the Base64::convertFromBase64() method if you want to use the + standard base-64 encoding. + + @see toBase64Encoding, Base64::convertFromBase64 */ bool fromBase64Encoding (StringRef encodedString); diff --git a/source/modules/juce_core/memory/juce_ReferenceCountedObject.h b/source/modules/juce_core/memory/juce_ReferenceCountedObject.h index fca745eba..cf60756ff 100644 --- a/source/modules/juce_core/memory/juce_ReferenceCountedObject.h +++ b/source/modules/juce_core/memory/juce_ReferenceCountedObject.h @@ -246,6 +246,14 @@ public: incIfNotNull (refCountedObject); } + #if JUCE_COMPILER_SUPPORTS_NULLPTR + /** Creates a pointer to a null object. */ + ReferenceCountedObjectPtr (decltype (nullptr)) noexcept + : referencedObject (nullptr) + { + } + #endif + /** Copies another pointer. This will increment the object's reference-count. */ @@ -258,7 +266,7 @@ public: /** Copies another pointer. This will increment the object's reference-count (if it is non-null). */ - template + template ReferenceCountedObjectPtr (const ReferenceCountedObjectPtr& other) noexcept : referencedObject (static_cast (other.get())) { @@ -278,7 +286,7 @@ public: The reference count of the old object is decremented, and it might be deleted if it hits zero. The new object's count is incremented. */ - template + template ReferenceCountedObjectPtr& operator= (const ReferenceCountedObjectPtr& other) { return operator= (static_cast (other.get())); @@ -369,43 +377,43 @@ private: //============================================================================== -/** Compares two ReferenceCountedObjectPointers. */ -template +/** Compares two ReferenceCountedObjectPtrs. */ +template bool operator== (const ReferenceCountedObjectPtr& object1, ReferenceCountedObjectClass* const object2) noexcept { return object1.get() == object2; } -/** Compares two ReferenceCountedObjectPointers. */ -template +/** Compares two ReferenceCountedObjectPtrs. */ +template bool operator== (const ReferenceCountedObjectPtr& object1, const ReferenceCountedObjectPtr& object2) noexcept { return object1.get() == object2.get(); } -/** Compares two ReferenceCountedObjectPointers. */ -template +/** Compares two ReferenceCountedObjectPtrs. */ +template bool operator== (ReferenceCountedObjectClass* object1, const ReferenceCountedObjectPtr& object2) noexcept { return object1 == object2.get(); } -/** Compares two ReferenceCountedObjectPointers. */ -template +/** Compares two ReferenceCountedObjectPtrs. */ +template bool operator!= (const ReferenceCountedObjectPtr& object1, const ReferenceCountedObjectClass* object2) noexcept { return object1.get() != object2; } -/** Compares two ReferenceCountedObjectPointers. */ -template +/** Compares two ReferenceCountedObjectPtrs. */ +template bool operator!= (const ReferenceCountedObjectPtr& object1, const ReferenceCountedObjectPtr& object2) noexcept { return object1.get() != object2.get(); } -/** Compares two ReferenceCountedObjectPointers. */ -template +/** Compares two ReferenceCountedObjectPtrs. */ +template bool operator!= (ReferenceCountedObjectClass* object1, const ReferenceCountedObjectPtr& object2) noexcept { return object1 != object2.get(); diff --git a/source/modules/juce_core/memory/juce_ScopedPointer.h b/source/modules/juce_core/memory/juce_ScopedPointer.h index 557104eeb..581471b51 100644 --- a/source/modules/juce_core/memory/juce_ScopedPointer.h +++ b/source/modules/juce_core/memory/juce_ScopedPointer.h @@ -56,7 +56,7 @@ can use the release() method. Something to note is the main difference between this class and the std::auto_ptr class, - which is that ScopedPointer provides a cast-to-object operator, wheras std::auto_ptr + which is that ScopedPointer provides a cast-to-object operator, whereas std::auto_ptr requires that you always call get() to retrieve the pointer. The advantages of providing the cast is that you don't need to call get(), so can use the ScopedPointer in pretty much exactly the same way as a raw pointer. The disadvantage is that the compiler is free to @@ -76,6 +76,13 @@ public: { } + #if JUCE_COMPILER_SUPPORTS_NULLPTR + /** Creates a ScopedPointer containing a null pointer. */ + inline ScopedPointer (decltype (nullptr)) noexcept : object (nullptr) + { + } + #endif + /** Creates a ScopedPointer that owns the specified object. */ inline ScopedPointer (ObjectType* const objectToTakePossessionOf) noexcept : object (objectToTakePossessionOf) @@ -153,6 +160,7 @@ public: ScopedPointer& operator= (ScopedPointer&& other) noexcept { + ContainerDeletePolicy::destroy (object); object = other.object; other.object = nullptr; return *this; diff --git a/source/modules/juce_core/memory/juce_SharedResourcePointer.h b/source/modules/juce_core/memory/juce_SharedResourcePointer.h index 307b55951..12a63ba4b 100644 --- a/source/modules/juce_core/memory/juce_SharedResourcePointer.h +++ b/source/modules/juce_core/memory/juce_SharedResourcePointer.h @@ -122,7 +122,7 @@ public: SharedObjectType& get() const noexcept { return *sharedObject; } /** Returns the object that this pointer references. - The pointer returned may be zero, of course. + The pointer returned may be a nullptr, of course. */ SharedObjectType& getObject() const noexcept { return *sharedObject; } diff --git a/source/modules/juce_core/misc/juce_RuntimePermissions.h b/source/modules/juce_core/misc/juce_RuntimePermissions.h index 814cecc69..c40a413ce 100644 --- a/source/modules/juce_core/misc/juce_RuntimePermissions.h +++ b/source/modules/juce_core/misc/juce_RuntimePermissions.h @@ -68,7 +68,7 @@ class JUCE_API RuntimePermissions { public: - //========================================================================== + //============================================================================== enum PermissionID { /** Permission to access the microphone (required on Android). @@ -84,7 +84,7 @@ public: bluetoothMidi = 2, }; - //========================================================================== + //============================================================================== /** Function type of runtime permission request callbacks. */ #if JUCE_COMPILER_SUPPORTS_LAMBDAS typedef std::function Callback; @@ -92,7 +92,7 @@ public: typedef void (*Callback) (bool); #endif - //========================================================================== + //============================================================================== /** Call this method to request a runtime permission. @param permission The PermissionID of the permission you want to request. diff --git a/source/modules/juce_core/native/java/AndroidRuntimePermissions.java b/source/modules/juce_core/native/java/AndroidRuntimePermissions.java index b2a8a461c..6e1b160a8 100644 --- a/source/modules/juce_core/native/java/AndroidRuntimePermissions.java +++ b/source/modules/juce_core/native/java/AndroidRuntimePermissions.java @@ -1,5 +1,3 @@ - private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback); - @Override public void onRequestPermissionsResult (int permissionID, String permissions[], int[] grantResults) { diff --git a/source/modules/juce_core/native/java/JuceAppActivity.java b/source/modules/juce_core/native/java/JuceAppActivity.java index 63f80e09f..bb6b2079c 100644 --- a/source/modules/juce_core/native/java/JuceAppActivity.java +++ b/source/modules/juce_core/native/java/JuceAppActivity.java @@ -59,7 +59,7 @@ import android.media.MediaScannerConnection.MediaScannerConnectionClient; import android.support.v4.content.ContextCompat; import android.support.v4.app.ActivityCompat; import android.Manifest; -$$JuceAndroidMidiImports$$ // If you get an error here, you need to re-save your project with the introjucer! +$$JuceAndroidMidiImports$$ // If you get an error here, you need to re-save your project with the Projucer! //============================================================================== @@ -136,7 +136,9 @@ public class JuceAppActivity extends Activity } } - $$JuceAndroidRuntimePermissionsCode$$ // If you get an error here, you need to re-save your project with the introjucer! + private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback); + + $$JuceAndroidRuntimePermissionsCode$$ // If you get an error here, you need to re-save your project with the Projucer! //============================================================================== public static class MidiPortID extends Object @@ -187,7 +189,7 @@ public class JuceAppActivity extends Activity } //============================================================================== - $$JuceAndroidMidiCode$$ // If you get an error here, you need to re-save your project with the introjucer! + $$JuceAndroidMidiCode$$ // If you get an error here, you need to re-save your project with the Projucer! //============================================================================== @Override @@ -218,6 +220,13 @@ public class JuceAppActivity extends Activity protected void onPause() { suspendApp(); + + try + { + Thread.sleep (1000); // This is a bit of a hack to avoid some hard-to-track-down + // openGL glitches when pausing/resuming apps.. + } catch (InterruptedException e) {} + super.onPause(); } diff --git a/source/modules/juce_core/native/juce_BasicNativeHeaders.h b/source/modules/juce_core/native/juce_BasicNativeHeaders.h index bdf0d60ea..4b24355c7 100644 --- a/source/modules/juce_core/native/juce_BasicNativeHeaders.h +++ b/source/modules/juce_core/native/juce_BasicNativeHeaders.h @@ -117,6 +117,10 @@ #include #endif + #ifndef S_FALSE + #define S_FALSE (1) // (apparently some obscure win32 dev environments don't define this) + #endif + #undef PACKED #if JUCE_MSVC diff --git a/source/modules/juce_core/native/juce_android_RuntimePermissions.cpp b/source/modules/juce_core/native/juce_android_RuntimePermissions.cpp index d36841574..4b75fbc7a 100644 --- a/source/modules/juce_core/native/juce_android_RuntimePermissions.cpp +++ b/source/modules/juce_core/native/juce_android_RuntimePermissions.cpp @@ -57,7 +57,7 @@ void RuntimePermissions::request (PermissionID permission, Callback callback) { // Error! If you want to be able to request this runtime permission, you // also need to declare it in your app's manifest. You can do so via - // the Introjucer. Otherwise this can't work. + // the Projucer. Otherwise this can't work. jassertfalse; callback (false); diff --git a/source/modules/juce_core/native/juce_curl_Network.cpp b/source/modules/juce_core/native/juce_curl_Network.cpp index f3a63832c..b32b4d1d0 100644 --- a/source/modules/juce_core/native/juce_curl_Network.cpp +++ b/source/modules/juce_core/native/juce_curl_Network.cpp @@ -133,10 +133,18 @@ private: const int maxRedirects, const String& headers, bool isPost, const String& httpRequest, size_t postSize) { + curl_version_info_data* data = curl_version_info (CURLVERSION_NOW); + jassert (data != nullptr); + + String userAgent = String ("curl/") + data->version; + if (curl_easy_setopt (curl, CURLOPT_URL, address.toRawUTF8()) == CURLE_OK && curl_easy_setopt (curl, CURLOPT_WRITEDATA, this) == CURLE_OK && curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, StaticCurlWrite) == CURLE_OK - && curl_easy_setopt (curl, CURLOPT_MAXREDIRS, static_cast (maxRedirects)) == CURLE_OK) + && curl_easy_setopt (curl, CURLOPT_MAXREDIRS, static_cast (maxRedirects)) == CURLE_OK + && curl_easy_setopt (curl, CURLOPT_USERAGENT, userAgent.toRawUTF8()) == CURLE_OK + && curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, (maxRedirects > 0 ? 1 : 0)) == CURLE_OK + && curl_easy_setopt (curl, CURLOPT_COOKIEFILE, "") == CURLE_OK) { if (isPost) { @@ -185,9 +193,11 @@ private: if (timeOutMs > 0) { - long timeOutSecs = static_cast (ceil (static_cast (timeOutMs) / 1000.0)); + long timeOutSecs = ((long) timeOutMs + 999) / 1000; - if (curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, timeOutSecs) != CURLE_OK) + if (curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, timeOutSecs) != CURLE_OK + || curl_easy_setopt (curl, CURLOPT_LOW_SPEED_LIMIT, 100) != CURLE_OK + || curl_easy_setopt (curl, CURLOPT_LOW_SPEED_TIME, timeOutSecs) != CURLE_OK) return false; } @@ -432,10 +442,17 @@ private: size_t len = size * nmemb; - curlHeaders += String (ptr, len); + String header (ptr, len); + + if (! header.contains (":") && header.startsWithIgnoreCase ("HTTP/")) + curlHeaders.clear(); + else + curlHeaders += header; + return len; } + //============================================================================== // Static method wrappers static size_t StaticCurlWrite (char* ptr, size_t size, size_t nmemb, void* userdata) diff --git a/source/modules/juce_core/native/juce_linux_Network.cpp b/source/modules/juce_core/native/juce_linux_Network.cpp index a05518c9a..6401ca070 100644 --- a/source/modules/juce_core/native/juce_linux_Network.cpp +++ b/source/modules/juce_core/native/juce_linux_Network.cpp @@ -429,14 +429,15 @@ private: writeValueIfNotPresent (header, userHeaders, "Connection:", "close"); if (isPost) - { writeValueIfNotPresent (header, userHeaders, "Content-Length:", String ((int) postData.getSize())); - header << userHeaders << "\r\n" << postData; - } - else - { - header << "\r\n" << userHeaders << "\r\n"; - } + + if (userHeaders.isNotEmpty()) + header << "\r\n" << userHeaders; + + header << "\r\n"; + + if (isPost) + header << postData; return header.getMemoryBlock(); } diff --git a/source/modules/juce_core/native/juce_mac_Files.mm b/source/modules/juce_core/native/juce_mac_Files.mm index 6a5c86f61..e5b17f5ff 100644 --- a/source/modules/juce_core/native/juce_mac_Files.mm +++ b/source/modules/juce_core/native/juce_mac_Files.mm @@ -217,7 +217,7 @@ File File::getSpecialLocation (const SpecialLocationType type) case invokedExecutableFile: if (juce_argv != nullptr && juce_argc > 0) - return File (CharPointer_UTF8 (juce_argv[0])); + return File::getCurrentWorkingDirectory().getChildFile (CharPointer_UTF8 (juce_argv[0])); // deliberate fall-through... case currentExecutableFile: @@ -228,13 +228,13 @@ File File::getSpecialLocation (const SpecialLocationType type) const File exe (juce_getExecutableFile()); const File parent (exe.getParentDirectory()); - #if JUCE_IOS + #if JUCE_IOS return parent; - #else + #else return parent.getFullPathName().endsWithIgnoreCase ("Contents/MacOS") ? parent.getParentDirectory().getParentDirectory() : exe; - #endif + #endif } case hostApplicationPath: @@ -404,13 +404,19 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& #if JUCE_IOS ignoreUnused (parameters); + + if (SystemStats::isRunningInAppExtensionSandbox()) + return false; + return [[UIApplication sharedApplication] openURL: filenameAsURL]; #else NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; if (parameters.isEmpty()) - return [workspace openFile: juceStringToNS (fileName)] - || [workspace openURL: filenameAsURL]; + // NB: the length check here is because of strange failures involving long filenames, + // probably due to filesystem name length limitations.. + return (fileName.length() < 1024 && [workspace openFile: juceStringToNS (fileName)]) + || [workspace openURL: filenameAsURL]; const File file (fileName); diff --git a/source/modules/juce_core/native/juce_mac_Network.mm b/source/modules/juce_core/native/juce_mac_Network.mm index f14adc744..64e9324ef 100644 --- a/source/modules/juce_core/native/juce_mac_Network.mm +++ b/source/modules/juce_core/native/juce_mac_Network.mm @@ -35,6 +35,7 @@ void MACAddress::findAllAddresses (Array& result) for (const ifaddrs* cursor = addrs; cursor != nullptr; cursor = cursor->ifa_next) { sockaddr_storage* sto = (sockaddr_storage*) cursor->ifa_addr; + if (sto->ss_family == AF_LINK) { const sockaddr_dl* const sadd = (const sockaddr_dl*) cursor->ifa_addr; @@ -45,7 +46,7 @@ void MACAddress::findAllAddresses (Array& result) if (sadd->sdl_type == IFT_ETHER) { - MACAddress ma (MACAddress (((const uint8*) sadd->sdl_data) + sadd->sdl_nlen)); + MACAddress ma (((const uint8*) sadd->sdl_data) + sadd->sdl_nlen); if (! ma.isNull()) result.addIfNotAlreadyThere (ma); @@ -108,6 +109,276 @@ bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& targetEmailA #endif } +//============================================================================== +// Unfortunately, we need to have this ugly ifdef here as long as some older OS X versions do not support NSURLSession +#if JUCE_IOS || (defined (__MAC_OS_X_VERSION_MIN_REQUIRED) && defined (__MAC_10_10) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10) + +//============================================================================== +class URLConnectionState : private Thread +{ +public: + URLConnectionState (NSURLRequest* req, const int maxRedirects) + : Thread ("http connection"), + request ([req retain]), + data ([[NSMutableData data] retain]), + numRedirectsToFollow (maxRedirects) + { + static DelegateClass cls; + delegate = [cls.createInstance() init]; + DelegateClass::setState (delegate, this); + } + + ~URLConnectionState() + { + signalThreadShouldExit(); + + { + const ScopedLock sl (dataLock); + isBeingDeleted = true; + [task cancel]; + DelegateClass::setState (delegate, nullptr); + } + + stopThread (10000); + [task release]; + [request release]; + [headers release]; + [session release]; + + const ScopedLock sl (dataLock); + [delegate release]; + [data release]; + } + + bool start (URL::OpenStreamProgressCallback* callback, void* context) + { + startThread(); + + while (isThreadRunning() && ! initialised) + { + if (callback != nullptr) + if (! callback (context, (int) latestTotalBytes, (int) [[request HTTPBody] length])) + return false; + + Thread::sleep (1); + } + + return true; + } + + int read (char* dest, int numBytes) + { + int numDone = 0; + + while (numBytes > 0) + { + const int available = jmin (numBytes, (int) [data length]); + + if (available > 0) + { + const ScopedLock sl (dataLock); + [data getBytes: dest length: (NSUInteger) available]; + [data replaceBytesInRange: NSMakeRange (0, (NSUInteger) available) withBytes: nil length: 0]; + + numDone += available; + numBytes -= available; + dest += available; + } + else + { + if (hasFailed || hasFinished) + break; + + Thread::sleep (1); + } + } + + return numDone; + } + + void didReceiveResponse (NSURLResponse* response, id completionHandler) + { + { + const ScopedLock sl (dataLock); + if (isBeingDeleted) + return; + + [data setLength: 0]; + } + + contentLength = [response expectedContentLength]; + + [headers release]; + headers = nil; + + if ([response isKindOfClass: [NSHTTPURLResponse class]]) + { + auto httpResponse = (NSHTTPURLResponse*) response; + headers = [[httpResponse allHeaderFields] retain]; + statusCode = (int) [httpResponse statusCode]; + } + + initialised = true; + + if (completionHandler != nil) + { + // Need to wrangle this parameter back into an obj-C block, + // and call it to allow the transfer to continue.. + void (^callbackBlock)(NSURLSessionResponseDisposition) = completionHandler; + callbackBlock (NSURLSessionResponseAllow); + } + } + + void didComplete (NSError* error) + { + const ScopedLock sl (dataLock); + if (isBeingDeleted) + return; + + #if JUCE_DEBUG + if (error != nullptr) + DBG (nsStringToJuce ([error description])); + #endif + + hasFailed = (error != nullptr); + initialised = true; + signalThreadShouldExit(); + } + + void didReceiveData (NSData* newData) + { + const ScopedLock sl (dataLock); + if (isBeingDeleted) + return; + + [data appendData: newData]; + initialised = true; + } + + void didSendBodyData (int64_t totalBytesWritten) + { + latestTotalBytes = static_cast (totalBytesWritten); + } + + void willPerformHTTPRedirection (NSURLRequest* urlRequest, void (^completionHandler)(NSURLRequest *)) + { + { + const ScopedLock sl (dataLock); + if (isBeingDeleted) + return; + } + + completionHandler (numRedirects++ < numRedirectsToFollow ? urlRequest : nil); + } + + void run() override + { + jassert (task == nil && session == nil); + + session = [[NSURLSession sessionWithConfiguration: [NSURLSessionConfiguration defaultSessionConfiguration] + delegate: delegate + delegateQueue: [NSOperationQueue currentQueue]] retain]; + + task = [session dataTaskWithRequest: request]; + + if (task == nil) + return; + + [task retain]; + [task resume]; + + while (! threadShouldExit()) + wait (5); + + hasFinished = true; + initialised = true; + } + + int64 contentLength = -1; + CriticalSection dataLock; + id delegate = nil; + NSURLRequest* request = nil; + NSURLSession* session = nil; + NSURLSessionTask* task = nil; + NSMutableData* data = nil; + NSDictionary* headers = nil; + int statusCode = 0; + bool initialised = false, hasFailed = false, hasFinished = false, isBeingDeleted = false; + const int numRedirectsToFollow; + int numRedirects = 0; + int64 latestTotalBytes = 0; + +private: + //============================================================================== + struct DelegateClass : public ObjCClass + { + DelegateClass() : ObjCClass ("JUCE_URLDelegate_") + { + addIvar ("state"); + + addMethod (@selector (URLSession:dataTask:didReceiveResponse:completionHandler:), + didReceiveResponse, "v@:@@@@"); + addMethod (@selector (URLSession:didBecomeInvalidWithError:), didBecomeInvalidWithError, "v@:@@"); + addMethod (@selector (URLSession:dataTask:didReceiveData:), didReceiveData, "v@:@@@"); + addMethod (@selector (URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:), + didSendBodyData, "v@:@@qqq"); + addMethod (@selector (URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:), + willPerformHTTPRedirection, "v@:@@@@@"); + + addMethod (@selector (URLSession:task:didCompleteWithError:), didCompleteWithError, "v@:@@@"); + + registerClass(); + } + + static void setState (id self, URLConnectionState* state) { object_setInstanceVariable (self, "state", state); } + static URLConnectionState* getState (id self) { return getIvar (self, "state"); } + + private: + static void didReceiveResponse (id self, SEL, NSURLSession*, NSURLSessionDataTask*, NSURLResponse* response, id completionHandler) + { + if (auto state = getState (self)) state->didReceiveResponse (response, completionHandler); + } + + static void didBecomeInvalidWithError (id self, SEL, NSURLSession*, NSError* error) + { + if (auto state = getState (self)) state->didComplete (error); + } + + static void didReceiveData (id self, SEL, NSURLSession*, NSURLSessionDataTask*, NSData* newData) + { + if (auto state = getState (self)) state->didReceiveData (newData); + } + + static void didSendBodyData (id self, SEL, NSURLSession*, NSURLSessionTask*, int64_t, int64_t totalBytesWritten, int64_t) + { + if (auto state = getState (self)) state->didSendBodyData (totalBytesWritten); + } + + static void willPerformHTTPRedirection (id self, SEL, NSURLSession*, NSURLSessionTask*, NSHTTPURLResponse*, + NSURLRequest* request, void (^completionHandler)(NSURLRequest *)) + { + if (auto state = getState (self)) state->willPerformHTTPRedirection (request, completionHandler); + } + + static void didCompleteWithError (id self, SEL, NSURLConnection*, NSURLSessionTask*, NSError* error) + { + if (auto state = getState (self)) state->didComplete (error); + } + }; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState) +}; + +//============================================================================== +#else + +// This version is only used for backwards-compatibility with older OSX targets, +// so we'll turn off deprecation warnings. This code will be removed at some point +// in the future. + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" + //============================================================================== class URLConnectionState : public Thread { @@ -137,10 +408,10 @@ public: { stop(); [connection release]; - [data release]; [request release]; [headers release]; [delegate release]; + [data release]; } bool start (URL::OpenStreamProgressCallback* callback, void* context) @@ -150,7 +421,8 @@ public: while (isThreadRunning() && ! initialised) { if (callback != nullptr) - callback (context, latestTotalBytes, (int) [[request HTTPBody] length]); + if (! callback (context, latestTotalBytes, (int) [[request HTTPBody] length])) + return false; Thread::sleep (1); } @@ -160,7 +432,11 @@ public: void stop() { - [connection cancel]; + { + const ScopedLock sl (dataLock); + [connection cancel]; + } + stopThread (10000); } @@ -339,6 +615,10 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState) }; +#pragma clang diagnostic pop + +#endif + //============================================================================== class WebInputStream : public InputStream @@ -372,8 +652,13 @@ public: } } + ~WebInputStream() + { + connection = nullptr; + } + //============================================================================== - bool isError() const { return connection == nullptr; } + bool isError() const { return (connection == nullptr || connection->headers == nullptr); } int64 getTotalLength() override { return connection == nullptr ? -1 : connection->contentLength; } bool isExhausted() override { return finished; } int64 getPosition() override { return position; } @@ -433,14 +718,11 @@ private: { jassert (connection == nullptr); - NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: juceStringToNS (address)] - cachePolicy: NSURLRequestReloadIgnoringLocalCacheData - timeoutInterval: timeOutMs <= 0 ? 60.0 : (timeOutMs / 1000.0)]; - - if (req != nil) + if (NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: juceStringToNS (address)] + cachePolicy: NSURLRequestReloadIgnoringLocalCacheData + timeoutInterval: timeOutMs <= 0 ? 60.0 : (timeOutMs / 1000.0)]) { [req setHTTPMethod: [NSString stringWithUTF8String: httpRequestCmd.toRawUTF8()]]; - //[req setCachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData]; StringArray headerLines; headerLines.addLines (headers); @@ -448,8 +730,8 @@ private: for (int i = 0; i < headerLines.size(); ++i) { - const String key (headerLines[i].upToFirstOccurrenceOf (":", false, false).trim()); - const String value (headerLines[i].fromFirstOccurrenceOf (":", false, false).trim()); + String key = headerLines[i].upToFirstOccurrenceOf (":", false, false).trim(); + String value = headerLines[i].fromFirstOccurrenceOf (":", false, false).trim(); if (key.isNotEmpty() && value.isNotEmpty()) [req addValue: juceStringToNS (value) forHTTPHeaderField: juceStringToNS (key)]; diff --git a/source/modules/juce_core/native/juce_mac_SystemStats.mm b/source/modules/juce_core/native/juce_mac_SystemStats.mm index 002ec0fda..daf3435f0 100644 --- a/source/modules/juce_core/native/juce_mac_SystemStats.mm +++ b/source/modules/juce_core/native/juce_mac_SystemStats.mm @@ -81,8 +81,8 @@ void CPUInformation::initialise() noexcept has3DNow = (b & (1u << 31)) != 0; hasSSE3 = (c & (1u << 0)) != 0; hasSSSE3 = (c & (1u << 9)) != 0; - hasSSE41 = (c & (1u << 20)) != 0; - hasSSE42 = (c & (1u << 19)) != 0; + hasSSE41 = (c & (1u << 19)) != 0; + hasSSE42 = (c & (1u << 20)) != 0; hasAVX = (c & (1u << 28)) != 0; SystemStatsHelpers::doCPUID (a, b, c, d, 7); diff --git a/source/modules/juce_core/native/juce_mac_Threads.mm b/source/modules/juce_core/native/juce_mac_Threads.mm index b8b793b63..f24ce0ab3 100644 --- a/source/modules/juce_core/native/juce_mac_Threads.mm +++ b/source/modules/juce_core/native/juce_mac_Threads.mm @@ -38,6 +38,9 @@ bool isIOSAppActive = true; //============================================================================== JUCE_API bool JUCE_CALLTYPE Process::isForegroundProcess() { + if (SystemStats::isRunningInAppExtensionSandbox()) + return true; + #if JUCE_MAC return [NSApp isActive]; #else @@ -48,14 +51,16 @@ JUCE_API bool JUCE_CALLTYPE Process::isForegroundProcess() JUCE_API void JUCE_CALLTYPE Process::makeForegroundProcess() { #if JUCE_MAC - [NSApp activateIgnoringOtherApps: YES]; + if (! SystemStats::isRunningInAppExtensionSandbox()) + [NSApp activateIgnoringOtherApps: YES]; #endif } JUCE_API void JUCE_CALLTYPE Process::hide() { #if JUCE_MAC - [NSApp hide: nil]; + if (! SystemStats::isRunningInAppExtensionSandbox()) + [NSApp hide: nil]; #endif } diff --git a/source/modules/juce_core/native/juce_osx_ObjCHelpers.h b/source/modules/juce_core/native/juce_osx_ObjCHelpers.h index afd1d0228..e0470ae5f 100644 --- a/source/modules/juce_core/native/juce_osx_ObjCHelpers.h +++ b/source/modules/juce_core/native/juce_osx_ObjCHelpers.h @@ -65,12 +65,28 @@ namespace static_cast (r.getWidth()), static_cast (r.getHeight())); } + #endif + #if JUCE_MAC || JUCE_IOS + #if JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES + + // This is necessary as on iOS builds, some arguments may be passed on registers + // depending on the argument type. The re-cast objc_msgSendSuper to a function + // take the same arguments as the target method. + template + static inline ReturnValue ObjCMsgSendSuper (struct objc_super* s, SEL sel, Params... params) + { + typedef ReturnValue (*SuperFn)(struct objc_super*, SEL, Params...); + SuperFn fn = reinterpret_cast (objc_msgSendSuper); + return fn (s, sel, params...); + } + + #endif // These hacks are a workaround for newer Xcode builds which by default prevent calls to these objc functions.. typedef id (*MsgSendSuperFn) (struct objc_super*, SEL, ...); static inline MsgSendSuperFn getMsgSendSuperFn() noexcept { return (MsgSendSuperFn) (void*) objc_msgSendSuper; } - #if ! JUCE_PPC + #if ! JUCE_IOS typedef double (*MsgSendFPRetFn) (id, SEL op, ...); static inline MsgSendFPRetFn getMsgSendFPRetFn() noexcept { return (MsgSendFPRetFn) (void*) objc_msgSend_fpret; } #endif @@ -149,7 +165,7 @@ struct ObjCClass jassert (b); ignoreUnused (b); } - #if JUCE_MAC + #if JUCE_MAC || JUCE_IOS static id sendSuperclassMessage (id self, SEL selector) { objc_super s = { self, [SuperclassType class] }; @@ -176,5 +192,37 @@ private: JUCE_DECLARE_NON_COPYABLE (ObjCClass) }; +#if JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES + +template +ReturnT (^CreateObjCBlock(Class* object, ReturnT (Class::*fn)(Params...))) (Params...) +{ + __block Class* _this = object; + __block ReturnT (Class::*_fn)(Params...) = fn; + + return [[^ReturnT (Params... params) { return (_this->*_fn) (params...); } copy] autorelease]; +} + +template +class ObjCBlock +{ +public: + ObjCBlock() { block = nullptr; } + template + ObjCBlock (C* _this, R (C::*fn)(P...)) : block (CreateObjCBlock (_this, fn)) {} + ObjCBlock (BlockType b) : block ([b copy]) {} + ObjCBlock& operator= (const BlockType& other) { if (block != nullptr) { [block release]; } block = [other copy]; return *this; } + bool operator== (const void* ptr) const { return (block == ptr); } + bool operator!= (const void* ptr) const { return (block != ptr); } + ~ObjCBlock() { if (block != nullptr) [block release]; } + + operator BlockType() { return block; } + +private: + BlockType block; +}; + +#endif + #endif // JUCE_OSX_OBJCHELPERS_H_INCLUDED diff --git a/source/modules/juce_core/native/juce_posix_SharedCode.h b/source/modules/juce_core/native/juce_posix_SharedCode.h index f713585e3..9ca2774c7 100644 --- a/source/modules/juce_core/native/juce_posix_SharedCode.h +++ b/source/modules/juce_core/native/juce_posix_SharedCode.h @@ -332,11 +332,21 @@ uint64 File::getFileIdentifier() const return juce_stat (fullPath, info) ? (uint64) info.st_ino : 0; } +static bool hasEffectiveRootFilePermissions() +{ + #if JUCE_LINUX + return (geteuid() == 0); + #else + return false; + #endif +} + //============================================================================== bool File::hasWriteAccess() const { if (exists()) - return access (fullPath.toUTF8(), W_OK) == 0; + return (hasEffectiveRootFilePermissions() + || access (fullPath.toUTF8(), W_OK) == 0); if ((! isDirectory()) && fullPath.containsChar (separator)) return getParentDirectory().hasWriteAccess(); @@ -430,6 +440,11 @@ bool File::moveInternal (const File& dest) const return false; } +bool File::replaceInternal (const File& dest) const +{ + return moveInternal (dest); +} + Result File::createDirectoryInternal (const String& fileName) const { return getResultForReturnValue (mkdir (fileName.toUTF8(), 0777)); @@ -576,7 +591,7 @@ String SystemStats::getEnvironmentVariable (const String& name, const String& de } //============================================================================== -void MemoryMappedFile::openInternal (const File& file, AccessMode mode) +void MemoryMappedFile::openInternal (const File& file, AccessMode mode, bool exclusive) { jassert (mode == readOnly || mode == readWrite); @@ -593,7 +608,7 @@ void MemoryMappedFile::openInternal (const File& file, AccessMode mode) { void* m = mmap (0, (size_t) range.getLength(), mode == readWrite ? (PROT_READ | PROT_WRITE) : PROT_READ, - MAP_SHARED, fileHandle, + exclusive ? MAP_PRIVATE : MAP_SHARED, fileHandle, (off_t) range.getStart()); if (m != MAP_FAILED) @@ -629,7 +644,8 @@ File juce_getExecutableFile() static String getFilename() { Dl_info exeInfo; - dladdr ((void*) juce_getExecutableFile, &exeInfo); + void* localSymbol = (void*) juce_getExecutableFile; + dladdr (localSymbol, &exeInfo); const CharPointer_UTF8 filename (exeInfo.dli_fname); // if the filename is absolute simply return it @@ -1117,14 +1133,14 @@ public: close (pipeHandles[0]); // close the read handle if ((streamFlags & wantStdOut) != 0) - dup2 (pipeHandles[1], 1); // turns the pipe into stdout + dup2 (pipeHandles[1], STDOUT_FILENO); // turns the pipe into stdout else - close (STDOUT_FILENO); + dup2 (open ("/dev/null", O_WRONLY), STDOUT_FILENO); if ((streamFlags & wantStdErr) != 0) - dup2 (pipeHandles[1], 2); + dup2 (pipeHandles[1], STDERR_FILENO); else - close (STDERR_FILENO); + dup2 (open ("/dev/null", O_WRONLY), STDERR_FILENO); close (pipeHandles[1]); #endif @@ -1140,8 +1156,6 @@ public: close (pipeHandles[1]); // close the write handle } } - - ignoreUnused(streamFlags); } ~ActiveProcess() @@ -1277,7 +1291,12 @@ struct HighResolutionTimer::Pimpl shouldStop = true; while (thread != 0 && thread != pthread_self()) + { + // if the timer callback itself calls startTimer then we need + // to override this + shouldStop = true; Thread::yield(); + } } } @@ -1309,6 +1328,10 @@ private: while (! shouldStop) { clock.wait(); + + if (shouldStop) + break; + owner.hiResTimerCallback(); if (lastPeriod != periodMs) diff --git a/source/modules/juce_core/native/juce_win32_ComSmartPtr.h b/source/modules/juce_core/native/juce_win32_ComSmartPtr.h index 380e9d843..d8c96c2c2 100644 --- a/source/modules/juce_core/native/juce_win32_ComSmartPtr.h +++ b/source/modules/juce_core/native/juce_win32_ComSmartPtr.h @@ -29,9 +29,23 @@ #ifndef JUCE_WIN32_COMSMARTPTR_H_INCLUDED #define JUCE_WIN32_COMSMARTPTR_H_INCLUDED -#if ! (defined (_MSC_VER) || defined (__uuidof)) +#if JUCE_MINGW || (! (defined (_MSC_VER) || defined (__uuidof))) +#ifdef __uuidof + #undef __uuidof +#endif + template struct UUIDGetter { static CLSID get() { jassertfalse; return CLSID(); } }; #define __uuidof(x) UUIDGetter::get() + + template <> + struct UUIDGetter<::IUnknown> + { + static CLSID get() + { + GUID g = { 0, 0, 0, { 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } }; + return g; + } + }; #endif inline GUID uuidFromString (const char* const s) noexcept @@ -135,7 +149,7 @@ protected: JUCE_COMRESULT QueryInterface (REFIID refId, void** result) { - if (refId == IID_IUnknown) + if (refId == __uuidof (IUnknown)) return castToType (result); *result = 0; diff --git a/source/modules/juce_core/native/juce_win32_Files.cpp b/source/modules/juce_core/native/juce_win32_Files.cpp index ebf480f75..5d09584f4 100644 --- a/source/modules/juce_core/native/juce_win32_Files.cpp +++ b/source/modules/juce_core/native/juce_win32_Files.cpp @@ -220,6 +220,15 @@ bool File::moveInternal (const File& dest) const return MoveFile (fullPath.toWideCharPointer(), dest.getFullPathName().toWideCharPointer()) != 0; } +bool File::replaceInternal (const File& dest) const +{ + void* lpExclude = 0; + void* lpReserved = 0; + + return ReplaceFile (dest.getFullPathName().toWideCharPointer(), fullPath.toWideCharPointer(), + 0, REPLACEFILE_IGNORE_MERGE_ERRORS, lpExclude, lpReserved) != 0; +} + Result File::createDirectoryInternal (const String& fileName) const { return CreateDirectory (fileName.toWideCharPointer(), 0) ? Result::ok() @@ -325,7 +334,7 @@ Result FileOutputStream::truncate() } //============================================================================== -void MemoryMappedFile::openInternal (const File& file, AccessMode mode) +void MemoryMappedFile::openInternal (const File& file, AccessMode mode, bool exclusive) { jassert (mode == readOnly || mode == readWrite); @@ -348,7 +357,8 @@ void MemoryMappedFile::openInternal (const File& file, AccessMode mode) access = FILE_MAP_ALL_ACCESS; } - HANDLE h = CreateFile (file.getFullPathName().toWideCharPointer(), accessMode, FILE_SHARE_READ, 0, + HANDLE h = CreateFile (file.getFullPathName().toWideCharPointer(), accessMode, + exclusive ? 0 : (FILE_SHARE_READ | (mode == readWrite ? FILE_SHARE_WRITE : 0)), 0, createType, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0); if (h != INVALID_HANDLE_VALUE) @@ -524,7 +534,9 @@ bool File::isOnHardDisk() const if (fullPath.toLowerCase()[0] <= 'b' && fullPath[1] == ':') return n != DRIVE_REMOVABLE; - return n != DRIVE_CDROM && n != DRIVE_REMOTE; + return n != DRIVE_CDROM + && n != DRIVE_REMOTE + && n != DRIVE_NO_ROOT_DIR; } bool File::isOnRemovableDrive() const @@ -728,7 +740,7 @@ class DirectoryIterator::NativeIterator::Pimpl { public: Pimpl (const File& directory, const String& wildCard) - : directoryWithWildCard (File::addTrailingSeparator (directory.getFullPathName()) + wildCard), + : directoryWithWildCard (directory.getFullPathName().isNotEmpty() ? File::addTrailingSeparator (directory.getFullPathName()) + wildCard : String()), handle (INVALID_HANDLE_VALUE) { } diff --git a/source/modules/juce_core/native/juce_win32_Network.cpp b/source/modules/juce_core/native/juce_win32_Network.cpp index dc80f7733..3282a6009 100644 --- a/source/modules/juce_core/native/juce_win32_Network.cpp +++ b/source/modules/juce_core/native/juce_win32_Network.cpp @@ -53,7 +53,7 @@ public: if (! isError()) { DWORD bufferSizeBytes = 4096; - StringPairArray dataHeaders (false); + StringPairArray dataHeaders; for (;;) { diff --git a/source/modules/juce_core/native/juce_win32_SystemStats.cpp b/source/modules/juce_core/native/juce_win32_SystemStats.cpp index bc207acaa..beb8dcf43 100644 --- a/source/modules/juce_core/native/juce_win32_SystemStats.cpp +++ b/source/modules/juce_core/native/juce_win32_SystemStats.cpp @@ -38,47 +38,30 @@ void Logger::outputDebugString (const String& text) #endif //============================================================================== -#if JUCE_USE_MSVC_INTRINSICS - -// CPU info functions using intrinsics... - #pragma intrinsic (__cpuid) #pragma intrinsic (__rdtsc) -static void callCPUID (int result[4], int infoType) +#if JUCE_MINGW +static void callCPUID (int result[4], uint32 type) { - __cpuid (result, infoType); + uint32 la = result[0], lb = result[1], lc = result[2], ld = result[3]; + + asm ("mov %%ebx, %%esi \n\t" + "cpuid \n\t" + "xchg %%esi, %%ebx" + : "=a" (la), "=S" (lb), "=c" (lc), "=d" (ld) : "a" (type) + #if JUCE_64BIT + , "b" (lb), "c" (lc), "d" (ld) + #endif + ); + + result[0] = la; result[1] = lb; result[2] = lc; result[3] = ld; } - #else - static void callCPUID (int result[4], int infoType) { - #if ! JUCE_MINGW - __try - #endif - { - #if JUCE_GCC || JUCE_CLANG - __asm__ __volatile__ ("cpuid" : "=a" (result[0]), "=b" (result[1]), "=c" (result[2]),"=d" (result[3]) : "a" (infoType)); - #else - __asm - { - mov esi, result - mov eax, infoType - xor ecx, ecx - cpuid - mov dword ptr [esi + 0], eax - mov dword ptr [esi + 4], ebx - mov dword ptr [esi + 8], ecx - mov dword ptr [esi + 12], edx - } - #endif - } - #if ! JUCE_MINGW - __except (EXCEPTION_EXECUTE_HANDLER) {} - #endif + __cpuid (result, infoType); } - #endif String SystemStats::getCpuVendor() @@ -295,17 +278,6 @@ public: { LARGE_INTEGER ticks; QueryPerformanceCounter (&ticks); - - const int64 mainCounterAsHiResTicks = (juce_millisecondsSinceStartup() * hiResTicksPerSecond) / 1000; - const int64 newOffset = mainCounterAsHiResTicks - ticks.QuadPart; - - // fix for a very obscure PCI hardware bug that can make the counter - // sometimes jump forwards by a few seconds.. - const int64 offsetDrift = abs64 (newOffset - hiResTicksOffset); - - if (offsetDrift > (hiResTicksPerSecond >> 1)) - hiResTicksOffset = newOffset; - return ticks.QuadPart + hiResTicksOffset; } @@ -327,7 +299,7 @@ double Time::getMillisecondCounterHiRes() noexcept { return hiResCounterHa //============================================================================== static int64 juce_getClockCycleCounter() noexcept { - #if JUCE_USE_MSVC_INTRINSICS + #if JUCE_MSVC // MS intrinsics version... return (int64) __rdtsc(); @@ -348,19 +320,7 @@ static int64 juce_getClockCycleCounter() noexcept return (int64) ((((uint64) hi) << 32) | lo); #else - // MSVC inline asm version... - unsigned int hi = 0, lo = 0; - - __asm - { - xor eax, eax - xor edx, edx - rdtsc - mov lo, eax - mov hi, edx - } - - return (int64) ((((uint64) hi) << 32) | lo); + #error "unknown compiler?" #endif } diff --git a/source/modules/juce_core/native/juce_win32_Threads.cpp b/source/modules/juce_core/native/juce_win32_Threads.cpp index a25168a2e..02d7a195e 100644 --- a/source/modules/juce_core/native/juce_win32_Threads.cpp +++ b/source/modules/juce_core/native/juce_win32_Threads.cpp @@ -35,38 +35,11 @@ void* getUser32Function (const char* functionName) return (void*) GetProcAddress (module, functionName); } -//============================================================================== -#if ! JUCE_USE_MSVC_INTRINSICS -// In newer compilers, the inline versions of these are used (in juce_Atomic.h), but in -// older ones we have to actually call the ops as win32 functions.. -long juce_InterlockedExchange (volatile long* a, long b) noexcept { return InterlockedExchange (a, b); } -long juce_InterlockedIncrement (volatile long* a) noexcept { return InterlockedIncrement (a); } -long juce_InterlockedDecrement (volatile long* a) noexcept { return InterlockedDecrement (a); } -long juce_InterlockedExchangeAdd (volatile long* a, long b) noexcept { return InterlockedExchangeAdd (a, b); } -long juce_InterlockedCompareExchange (volatile long* a, long b, long c) noexcept { return InterlockedCompareExchange (a, b, c); } - -__int64 juce_InterlockedCompareExchange64 (volatile __int64* value, __int64 newValue, __int64 valueToCompare) noexcept -{ - jassertfalse; // This operation isn't available in old MS compiler versions! - - __int64 oldValue = *value; - if (oldValue == valueToCompare) - *value = newValue; - - return oldValue; -} - -#endif - //============================================================================== CriticalSection::CriticalSection() noexcept { // (just to check the MS haven't changed this structure and broken things...) - #if JUCE_VC7_OR_EARLIER - static_jassert (sizeof (CRITICAL_SECTION) <= 24); - #else static_jassert (sizeof (CRITICAL_SECTION) <= sizeof (lock)); - #endif InitializeCriticalSection ((CRITICAL_SECTION*) lock); } diff --git a/source/modules/juce_core/network/juce_Socket.cpp b/source/modules/juce_core/network/juce_Socket.cpp index 98e6edb79..836b1789e 100644 --- a/source/modules/juce_core/network/juce_Socket.cpp +++ b/source/modules/juce_core/network/juce_Socket.cpp @@ -333,45 +333,58 @@ namespace SocketHelpers const int portNumber, const int timeOutMillisecs) noexcept { - struct addrinfo* info = getAddressInfo (false, hostName, portNumber); + bool success = false; - if (info == nullptr) - return false; + if (struct addrinfo* info = getAddressInfo (false, hostName, portNumber)) + { + for (struct addrinfo* i = info; i != nullptr; i = i->ai_next) + { + const SocketHandle newHandle = socket (i->ai_family, i->ai_socktype, 0); - if (handle < 0) - handle = (int) socket (info->ai_family, info->ai_socktype, 0); + if (newHandle >= 0) + { + setSocketBlockingState (newHandle, false); + const int result = ::connect (newHandle, i->ai_addr, (socklen_t) i->ai_addrlen); + success = (result >= 0); - if (handle < 0) - { - freeaddrinfo (info); - return false; - } + if (! success) + { + #if JUCE_WINDOWS + if (result == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK) + #else + if (errno == EINPROGRESS) + #endif + { + const volatile int cvHandle = (int) newHandle; + if (waitForReadiness (cvHandle, readLock, false, timeOutMillisecs) == 1) + success = true; + } + } - setSocketBlockingState (handle, false); - const int result = ::connect (handle, info->ai_addr, (socklen_t) info->ai_addrlen); - freeaddrinfo (info); + if (success) + { + handle = (int) newHandle; + break; + } + + #if JUCE_WINDOWS + closesocket (newHandle); + #else + ::close (newHandle); + #endif + } + } - bool retval = (result >= 0); + freeaddrinfo (info); - if (result < 0) - { - #if JUCE_WINDOWS - if (result == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK) - #else - if (errno == EINPROGRESS) - #endif + if (success) { - if (waitForReadiness (handle, readLock, false, timeOutMillisecs) == 1) - retval = true; + setSocketBlockingState (handle, true); + resetSocketOptions (handle, false, false); } } - setSocketBlockingState (handle, true); - - if (retval) - resetSocketOptions (handle, false, false); - - return retval; + return success; } static void makeReusable (int handle) noexcept diff --git a/source/modules/juce_core/network/juce_Socket.h b/source/modules/juce_core/network/juce_Socket.h index c6be4e71c..33648bbcc 100644 --- a/source/modules/juce_core/network/juce_Socket.h +++ b/source/modules/juce_core/network/juce_Socket.h @@ -247,7 +247,7 @@ public: This is useful if you need to know to which port the OS has actually bound your socket when bindToPort was called with zero. - Returns -1 if the socket didn't bind to any port yet or an error occured. */ + Returns -1 if the socket didn't bind to any port yet or an error occurred. */ int getBoundPort() const noexcept; /** Returns the OS's socket handle that's currently open. */ diff --git a/source/modules/juce_core/network/juce_URL.cpp b/source/modules/juce_core/network/juce_URL.cpp index 9a0a3e66a..4c34bda68 100644 --- a/source/modules/juce_core/network/juce_URL.cpp +++ b/source/modules/juce_core/network/juce_URL.cpp @@ -136,7 +136,7 @@ namespace URLHelpers || url[i] == '+' || url[i] == '-' || url[i] == '.') ++i; - return url[i] == ':' ? i + 1 : 0; + return url.substring (i).startsWith ("://") ? i + 1 : 0; } static int findStartOfNetLocation (const String& url) @@ -222,6 +222,13 @@ int URL::getPort() const return colonPos > 0 ? url.substring (colonPos + 1).getIntValue() : 0; } +URL URL::withNewDomainAndPath (const String& newURL) const +{ + URL u (*this); + u.url = newURL; + return u; +} + URL URL::withNewSubPath (const String& newPath) const { const int startOfPath = URLHelpers::findStartOfPath (url); @@ -489,10 +496,13 @@ String URL::removeEscapeChars (const String& s) return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size()); } -String URL::addEscapeChars (const String& s, const bool isParameter) +String URL::addEscapeChars (const String& s, const bool isParameter, bool roundBracketsAreLegal) { - const CharPointer_UTF8 legalChars (isParameter ? "_-.*!'()" - : ",$_-.*!'()"); + String legalChars (isParameter ? "_-.*!'" + : ",$_-.*!'"); + + if (roundBracketsAreLegal) + legalChars += "()"; Array utf8 (s.toRawUTF8(), (int) s.getNumBytesAsUTF8()); @@ -501,7 +511,7 @@ String URL::addEscapeChars (const String& s, const bool isParameter) const char c = utf8.getUnchecked(i); if (! (CharacterFunctions::isLetterOrDigit (c) - || legalChars.indexOf ((juce_wchar) c) >= 0)) + || legalChars.containsChar ((juce_wchar) c))) { utf8.set (i, '%'); utf8.insert (++i, "0123456789ABCDEF" [((uint8) c) >> 4]); diff --git a/source/modules/juce_core/network/juce_URL.h b/source/modules/juce_core/network/juce_URL.h index a2b3cb765..bf49b3a4e 100644 --- a/source/modules/juce_core/network/juce_URL.h +++ b/source/modules/juce_core/network/juce_URL.h @@ -106,10 +106,19 @@ public: */ int getPort() const; - /** Returns a new version of this URL that uses a different sub-path. + /** Returns a new version of this URL with a different domain and path. + + E.g. if the URL is "http://www.xyz.com/foo?x=1" and you call this with + "abc.com/zzz", it'll return "http://abc.com/zzz?x=1". + @see withNewSubPath + */ + URL withNewDomainAndPath (const String& newFullPath) const; + + /** Returns a new version of this URL with a different sub-path. E.g. if the URL is "http://www.xyz.com/foo?x=1" and you call this with "bar", it'll return "http://www.xyz.com/bar?x=1". + @see withNewDomainAndPath */ URL withNewSubPath (const String& newPath) const; @@ -274,7 +283,7 @@ public: if the parameter httpRequestCmd is not specified (or empty) then this parameter will determine which HTTP request command will be used (POST or GET). - @param progressCallback if this is non-zero, it lets you supply a callback function + @param progressCallback if this is not a nullptr, it lets you supply a callback function to keep track of the operation's progress. This can be useful for lengthy POST operations, so that you can provide user feedback. @param progressCallbackContext if a callback is specified, this value will be passed to @@ -366,14 +375,20 @@ public: This is the opposite of removeEscapeChars(). - If isParameter is true, it means that the string is going to be used - as a parameter, so it also encodes '$' and ',' (which would otherwise - be legal in a URL. + @param stringToAddEscapeCharsTo The string to escape. + @param isParameter If true then the string is going to be + used as a parameter, so it also encodes + '$' and ',' (which would otherwise be + legal in a URL. + @param roundBracketsAreLegal Technically round brackets are ok in URLs, + however, some servers (like AWS) also want + round brackets to be escaped. @see removeEscapeChars */ static String addEscapeChars (const String& stringToAddEscapeCharsTo, - bool isParameter); + bool isParameter, + bool roundBracketsAreLegal = true); /** Replaces any escape character sequences in a string with their original character codes. diff --git a/source/modules/juce_core/system/juce_CompilerSupport.h b/source/modules/juce_core/system/juce_CompilerSupport.h index 9b903c8e1..5169a3733 100644 --- a/source/modules/juce_core/system/juce_CompilerSupport.h +++ b/source/modules/juce_core/system/juce_CompilerSupport.h @@ -98,7 +98,7 @@ #define JUCE_COMPILER_SUPPORTS_STATIC_ASSERT 1 #endif - #ifndef JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL + #if __has_feature (cxx_override_control) && (! defined (JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL)) #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 #endif diff --git a/source/modules/juce_core/system/juce_PlatformDefs.h b/source/modules/juce_core/system/juce_PlatformDefs.h index 67b5e872f..169107f0b 100644 --- a/source/modules/juce_core/system/juce_PlatformDefs.h +++ b/source/modules/juce_core/system/juce_PlatformDefs.h @@ -61,14 +61,14 @@ #endif //============================================================================== -#if JUCE_IOS || JUCE_LINUX || JUCE_ANDROID || JUCE_PPC +#if JUCE_IOS || JUCE_LINUX || JUCE_ANDROID /** This will try to break into the debugger if the app is currently being debugged. If called by an app that's not being debugged, the behaviour isn't defined - it may crash or not, depending on the platform. @see jassert() */ #define JUCE_BREAK_IN_DEBUGGER { ::kill (0, SIGTRAP); } -#elif JUCE_USE_MSVC_INTRINSICS +#elif JUCE_MSVC #ifndef __INTEL_COMPILER #pragma intrinsic (__debugbreak) #endif diff --git a/source/modules/juce_core/system/juce_StandardHeader.h b/source/modules/juce_core/system/juce_StandardHeader.h index dd5e86e88..153ec84b2 100644 --- a/source/modules/juce_core/system/juce_StandardHeader.h +++ b/source/modules/juce_core/system/juce_StandardHeader.h @@ -35,7 +35,7 @@ See also SystemStats::getJUCEVersion() for a string version. */ #define JUCE_MAJOR_VERSION 4 -#define JUCE_MINOR_VERSION 1 +#define JUCE_MINOR_VERSION 3 #define JUCE_BUILDNUMBER 0 /** Current Juce version number. @@ -50,8 +50,14 @@ //============================================================================== -#include // included before platform defs to provide a definition of _LIBCPP_VERSION +#include +#include +#include +#include +#include +#include +//============================================================================== #include "juce_CompilerSupport.h" #include "juce_PlatformDefs.h" @@ -60,23 +66,6 @@ #if JUCE_MSVC #pragma warning (push) #pragma warning (disable: 4514 4245 4100) -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if JUCE_USE_MSVC_INTRINSICS #include #endif @@ -85,6 +74,8 @@ #endif #if JUCE_LINUX + #include + #include #include #if __INTEL_COMPILER @@ -105,10 +96,12 @@ #endif #if JUCE_MINGW + #include #include #endif #if JUCE_ANDROID + #include #include #include #endif diff --git a/source/modules/juce_core/system/juce_SystemStats.cpp b/source/modules/juce_core/system/juce_SystemStats.cpp index 598fd871d..2cd16d919 100644 --- a/source/modules/juce_core/system/juce_SystemStats.cpp +++ b/source/modules/juce_core/system/juce_SystemStats.cpp @@ -188,3 +188,29 @@ void SystemStats::setApplicationCrashHandler (CrashHandlerFunction handler) } #endif } + +bool SystemStats::isRunningInAppExtensionSandbox() noexcept +{ + #if JUCE_MAC || JUCE_IOS + static bool firstQuery = true; + static bool isRunningInAppSandbox = false; + + if (firstQuery) + { + firstQuery = false; + + File bundle = File::getSpecialLocation (File::invokedExecutableFile).getParentDirectory(); + + #if JUCE_MAC + bundle = bundle.getParentDirectory().getParentDirectory(); + #endif + + if (bundle.isDirectory()) + isRunningInAppSandbox = (bundle.getFileExtension() == ".appex"); + } + + return isRunningInAppSandbox; + #else + return false; + #endif +} diff --git a/source/modules/juce_core/system/juce_SystemStats.h b/source/modules/juce_core/system/juce_SystemStats.h index 28f4399f7..be9285aa1 100644 --- a/source/modules/juce_core/system/juce_SystemStats.h +++ b/source/modules/juce_core/system/juce_SystemStats.h @@ -65,6 +65,7 @@ public: MacOSX_10_9 = MacOSX | 9, MacOSX_10_10 = MacOSX | 10, MacOSX_10_11 = MacOSX | 11, + MacOSX_10_12 = MacOSX | 12, Win2000 = Windows | 1, WinXP = Windows | 2, @@ -193,6 +194,12 @@ public: */ static void setApplicationCrashHandler (CrashHandlerFunction); + /** Returns true if this code is running inside an app extension sandbox. + + This function will always return false on windows, linux and android. + */ + static bool isRunningInAppExtensionSandbox() noexcept; + private: //============================================================================== SystemStats(); diff --git a/source/modules/juce_core/system/juce_TargetPlatform.h b/source/modules/juce_core/system/juce_TargetPlatform.h index 6b4c6f173..d6e2147ee 100644 --- a/source/modules/juce_core/system/juce_TargetPlatform.h +++ b/source/modules/juce_core/system/juce_TargetPlatform.h @@ -38,17 +38,29 @@ - One of JUCE_WINDOWS, JUCE_MAC JUCE_LINUX, JUCE_IOS, JUCE_ANDROID, etc. - Either JUCE_32BIT or JUCE_64BIT, depending on the architecture. - Either JUCE_LITTLE_ENDIAN or JUCE_BIG_ENDIAN. - - Either JUCE_INTEL or JUCE_PPC + - Either JUCE_INTEL or JUCE_ARM - Either JUCE_GCC or JUCE_CLANG or JUCE_MSVC */ //============================================================================== #ifdef JUCE_APP_CONFIG_HEADER #include JUCE_APP_CONFIG_HEADER -#else - // Your project must contain an AppConfig.h file with your project-specific settings in it, - // and your header search path must make it accessible to the module's files. - #include "AppConfig.h" +#elif ! defined (JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED) + /* + Most projects will contain a global header file containing various settings that + should be applied to all the code in your project. If you use the projucer, it'll + set up a global header file for you automatically, but if you're doing things manually, + you may want to set the JUCE_APP_CONFIG_HEADER macro with the name of a file to include, + or just include one before all the module cpp files, in which you set + JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1 to silence this error. + (Or if you don't need a global header, then you can just define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED + globally to avoid this error). + + Note for people who hit this error when trying to compile a JUCE project created by + a pre-v4.2 version of the Introjucer/Projucer, it's very easy to fix: just re-save + your project with the latest version of the Projucer, and it'll magically fix this! + */ + #error "No global header file was included!" #endif //============================================================================== @@ -129,7 +141,7 @@ #endif #if defined (__ppc__) || defined (__ppc64__) - #define JUCE_PPC 1 + #error "PowerPC is no longer supported by JUCE!" #elif defined (__arm__) || defined (__arm64__) #define JUCE_ARM 1 #else @@ -178,7 +190,7 @@ // Compiler type macros. #ifdef __clang__ - #define JUCE_CLANG 1 + #define JUCE_CLANG 1 #elif defined (__GNUC__) #define JUCE_GCC 1 #elif defined (_MSC_VER) @@ -188,17 +200,9 @@ #define JUCE_VC8_OR_EARLIER 1 #if _MSC_VER < 1400 - #define JUCE_VC7_OR_EARLIER 1 - - #if _MSC_VER < 1300 - #warning "MSVC 6.0 is no longer supported!" - #endif + #error "Visual Studio 2003 and earlier are no longer supported!" #endif #endif - - #if JUCE_64BIT || ! JUCE_VC7_OR_EARLIER - #define JUCE_USE_MSVC_INTRINSICS 1 - #endif #else #error unknown compiler #endif diff --git a/source/modules/juce_core/text/juce_CharPointer_ASCII.h b/source/modules/juce_core/text/juce_CharPointer_ASCII.h index a58028aae..5bc4af52e 100644 --- a/source/modules/juce_core/text/juce_CharPointer_ASCII.h +++ b/source/modules/juce_core/text/juce_CharPointer_ASCII.h @@ -215,12 +215,6 @@ public: CharacterFunctions::copyAll (*this, src); } - /** Copies a source string to this pointer, advancing this pointer as it goes. */ - void writeAll (const CharPointer_ASCII src) noexcept - { - strcpy (data, src.data); - } - /** Copies a source string to this pointer, advancing this pointer as it goes. The maxDestBytes parameter specifies the maximum number of bytes that can be written to the destination buffer before stopping. diff --git a/source/modules/juce_core/text/juce_CharacterFunctions.cpp b/source/modules/juce_core/text/juce_CharacterFunctions.cpp index d0a505f7f..4db016a2a 100644 --- a/source/modules/juce_core/text/juce_CharacterFunctions.cpp +++ b/source/modules/juce_core/text/juce_CharacterFunctions.cpp @@ -162,3 +162,16 @@ double CharacterFunctions::mulexp10 (const double value, int exponent) noexcept return negative ? (value / result) : (value * result); } + +juce_wchar CharacterFunctions::getUnicodeCharFromWindows1252Codepage (const uint8 c) noexcept +{ + if (c < 0x80 || c >= 0xa0) + return (juce_wchar) c; + + static const uint16 lookup[] = { 0x20AC, 0x0007, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, + 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x0007, 0x017D, 0x0007, + 0x0007, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x0007, 0x017E, 0x0178 }; + + return (juce_wchar) lookup[c - 0x80]; +} diff --git a/source/modules/juce_core/text/juce_CharacterFunctions.h b/source/modules/juce_core/text/juce_CharacterFunctions.h index 9628e792a..4b006e2ac 100644 --- a/source/modules/juce_core/text/juce_CharacterFunctions.h +++ b/source/modules/juce_core/text/juce_CharacterFunctions.h @@ -123,6 +123,9 @@ public: /** Returns 0 to 16 for '0' to 'F", or -1 for characters that aren't a legal hex digit. */ static int getHexDigitValue (juce_wchar digit) noexcept; + /** Converts a byte of Windows 1252 codepage to unicode. */ + static juce_wchar getUnicodeCharFromWindows1252Codepage (uint8 windows1252Char) noexcept; + //============================================================================== /** Parses a character string to read a floating-point number. Note that this will advance the pointer that is passed in, leaving it at diff --git a/source/modules/juce_core/text/juce_Identifier.h b/source/modules/juce_core/text/juce_Identifier.h index 789144381..6b9433365 100644 --- a/source/modules/juce_core/text/juce_Identifier.h +++ b/source/modules/juce_core/text/juce_Identifier.h @@ -93,6 +93,18 @@ public: /** Compares the identifier with a string. */ inline bool operator!= (StringRef other) const noexcept { return name != other; } + /** Compares the identifier with a string. */ + inline bool operator< (StringRef other) const noexcept { return name < other; } + + /** Compares the identifier with a string. */ + inline bool operator<= (StringRef other) const noexcept { return name <= other; } + + /** Compares the identifier with a string. */ + inline bool operator> (StringRef other) const noexcept { return name > other; } + + /** Compares the identifier with a string. */ + inline bool operator>= (StringRef other) const noexcept { return name >= other; } + /** Returns this identifier as a string. */ const String& toString() const noexcept { return name; } diff --git a/source/modules/juce_core/text/juce_String.cpp b/source/modules/juce_core/text/juce_String.cpp index 02cd66e29..b025dde5e 100644 --- a/source/modules/juce_core/text/juce_String.cpp +++ b/source/modules/juce_core/text/juce_String.cpp @@ -237,7 +237,9 @@ private: } }; +#if JUCE_ALLOW_STATIC_NULL_VARIABLES const String String::empty; +#endif //============================================================================== String::String() noexcept : text (&(emptyString.text)) @@ -319,7 +321,7 @@ String::String (const char* const t) because there's no other way to represent these strings in a way that isn't dependent on the compiler, source code editor and platform. - Note that the Introjucer has a handy string literal generator utility that will convert + Note that the Projucer has a handy string literal generator utility that will convert any unicode string to a valid C++ string literal, creating ascii escape sequences that will work in any compiler. */ @@ -342,7 +344,7 @@ String::String (const char* const t, const size_t maxChars) because there's no other way to represent these strings in a way that isn't dependent on the compiler, source code editor and platform. - Note that the Introjucer has a handy string literal generator utility that will convert + Note that the Projucer has a handy string literal generator utility that will convert any unicode string to a valid C++ string literal, creating ascii escape sequences that will work in any compiler. */ @@ -1387,6 +1389,10 @@ String String::replaceCharacter (const juce_wchar charToReplace, const juce_wcha String String::replaceCharacters (StringRef charactersToReplace, StringRef charactersToInsertInstead) const { + // Each character in the first string must have a matching one in the + // second, so the two strings must be the same length. + jassert (charactersToReplace.length() == charactersToInsertInstead.length()); + StringCreationHelper builder (text); for (;;) @@ -1969,7 +1975,18 @@ int String::getHexValue32() const noexcept { return CharacterFunctions::Hex int64 String::getHexValue64() const noexcept { return CharacterFunctions::HexParser::parse (text); } //============================================================================== -String String::createStringFromData (const void* const unknownData, const int size) +static String getStringFromWindows1252Codepage (const char* data, size_t num) +{ + HeapBlock unicode (num + 1); + + for (size_t i = 0; i < num; ++i) + unicode[i] = CharacterFunctions::getUnicodeCharFromWindows1252Codepage ((uint8) data[i]); + + unicode[num] = 0; + return CharPointer_UTF32 (unicode); +} + +String String::createStringFromData (const void* const unknownData, int size) { const uint8* const data = static_cast (unknownData); @@ -2003,13 +2020,19 @@ String String::createStringFromData (const void* const unknownData, const int si return builder.result; } - const uint8* start = data; + const char* start = (const char*) data; if (size >= 3 && CharPointer_UTF8::isByteOrderMark (data)) + { start += 3; + size -= 3; + } + + if (CharPointer_UTF8::isValidString (start, size)) + return String (CharPointer_UTF8 (start), + CharPointer_UTF8 (start + size)); - return String (CharPointer_UTF8 ((const char*) start), - CharPointer_UTF8 ((const char*) (data + size))); + return getStringFromWindows1252Codepage (start, (size_t) size); } //============================================================================== @@ -2249,7 +2272,7 @@ public: beginTest ("Basics"); expect (String().length() == 0); - expect (String() == String::empty); + expect (String() == String()); String s1, s2 ("abcd"); expect (s1.isEmpty() && ! s1.isNotEmpty()); expect (s2.isNotEmpty() && ! s2.isEmpty()); @@ -2261,16 +2284,16 @@ public: expect (String ("abcdefg", 4) == L"abcd"); expect (String ("abcdefg", 4) == String (L"abcdefg", 4)); expect (String::charToString ('x') == "x"); - expect (String::charToString (0) == String::empty); + expect (String::charToString (0) == String()); expect (s2 + "e" == "abcde" && s2 + 'e' == "abcde"); expect (s2 + L'e' == "abcde" && s2 + L"e" == "abcde"); expect (s1.equalsIgnoreCase ("abcD") && s1 < "abce" && s1 > "abbb"); expect (s1.startsWith ("ab") && s1.startsWith ("abcd") && ! s1.startsWith ("abcde")); expect (s1.startsWithIgnoreCase ("aB") && s1.endsWithIgnoreCase ("CD")); expect (s1.endsWith ("bcd") && ! s1.endsWith ("aabcd")); - expectEquals (s1.indexOf (String::empty), 0); - expectEquals (s1.indexOfIgnoreCase (String::empty), 0); - expect (s1.startsWith (String::empty) && s1.endsWith (String::empty) && s1.contains (String::empty)); + expectEquals (s1.indexOf (String()), 0); + expectEquals (s1.indexOfIgnoreCase (String()), 0); + expect (s1.startsWith (String()) && s1.endsWith (String()) && s1.contains (String())); expect (s1.contains ("cd") && s1.contains ("ab") && s1.contains ("abcd")); expect (s1.containsChar ('a')); expect (! s1.containsChar ('x')); @@ -2451,9 +2474,9 @@ public: } beginTest ("Numeric conversions"); - expect (String::empty.getIntValue() == 0); - expect (String::empty.getDoubleValue() == 0.0); - expect (String::empty.getFloatValue() == 0.0f); + expect (String().getIntValue() == 0); + expect (String().getDoubleValue() == 0.0); + expect (String().getFloatValue() == 0.0f); expect (s.getIntValue() == 12345678); expect (s.getLargeIntValue() == (int64) 12345678); expect (s.getDoubleValue() == 12345678.0); @@ -2490,11 +2513,11 @@ public: expect (s3.containsAnyOf (String (L"zzzFs"))); expect (s3.startsWith ("abcd")); expect (s3.startsWithIgnoreCase (String (L"abCD"))); - expect (s3.startsWith (String::empty)); + expect (s3.startsWith (String())); expect (s3.startsWithChar ('a')); expect (s3.endsWith (String ("HIJ"))); expect (s3.endsWithIgnoreCase (String (L"Hij"))); - expect (s3.endsWith (String::empty)); + expect (s3.endsWith (String())); expect (s3.endsWithChar (L'J')); expect (s3.indexOf ("HIJ") == 7); expect (s3.indexOf (String (L"HIJK")) == -1); @@ -2544,28 +2567,28 @@ public: expect (! String ("xx?y").matchesWildcard ("xx?y?", true)); expect (String ("xx?y").matchesWildcard ("xx??", true)); - expectEquals (s5.fromFirstOccurrenceOf (String::empty, true, false), s5); + expectEquals (s5.fromFirstOccurrenceOf (String(), true, false), s5); expectEquals (s5.fromFirstOccurrenceOf ("xword2", true, false), s5.substring (100)); expectEquals (s5.fromFirstOccurrenceOf (String (L"word2"), true, false), s5.substring (5)); expectEquals (s5.fromFirstOccurrenceOf ("Word2", true, true), s5.substring (5)); expectEquals (s5.fromFirstOccurrenceOf ("word2", false, false), s5.getLastCharacters (6)); expectEquals (s5.fromFirstOccurrenceOf ("Word2", false, true), s5.getLastCharacters (6)); - expectEquals (s5.fromLastOccurrenceOf (String::empty, true, false), s5); + expectEquals (s5.fromLastOccurrenceOf (String(), true, false), s5); expectEquals (s5.fromLastOccurrenceOf ("wordx", true, false), s5); expectEquals (s5.fromLastOccurrenceOf ("word", true, false), s5.getLastCharacters (5)); expectEquals (s5.fromLastOccurrenceOf ("worD", true, true), s5.getLastCharacters (5)); expectEquals (s5.fromLastOccurrenceOf ("word", false, false), s5.getLastCharacters (1)); expectEquals (s5.fromLastOccurrenceOf ("worD", false, true), s5.getLastCharacters (1)); - expect (s5.upToFirstOccurrenceOf (String::empty, true, false).isEmpty()); + expect (s5.upToFirstOccurrenceOf (String(), true, false).isEmpty()); expectEquals (s5.upToFirstOccurrenceOf ("word4", true, false), s5); expectEquals (s5.upToFirstOccurrenceOf ("word2", true, false), s5.substring (0, 10)); expectEquals (s5.upToFirstOccurrenceOf ("Word2", true, true), s5.substring (0, 10)); expectEquals (s5.upToFirstOccurrenceOf ("word2", false, false), s5.substring (0, 5)); expectEquals (s5.upToFirstOccurrenceOf ("Word2", false, true), s5.substring (0, 5)); - expectEquals (s5.upToLastOccurrenceOf (String::empty, true, false), s5); + expectEquals (s5.upToLastOccurrenceOf (String(), true, false), s5); expectEquals (s5.upToLastOccurrenceOf ("zword", true, false), s5); expectEquals (s5.upToLastOccurrenceOf ("word", true, false), s5.dropLastCharacters (1)); expectEquals (s5.dropLastCharacters(1).upToLastOccurrenceOf ("word", true, false), s5.dropLastCharacters (1)); @@ -2583,9 +2606,9 @@ public: expect (s5.replaceCharacters ("wo", "xy") != s5); expectEquals (s5.replaceCharacters ("wo", "xy").replaceCharacters ("xy", "wo"), s5); expectEquals (s5.retainCharacters ("1wordxya"), String ("wordwordword")); - expect (s5.retainCharacters (String::empty).isEmpty()); + expect (s5.retainCharacters (String()).isEmpty()); expect (s5.removeCharacters ("1wordxya") == " 2 3"); - expectEquals (s5.removeCharacters (String::empty), s5); + expectEquals (s5.removeCharacters (String()), s5); expect (s5.initialSectionContainingOnly ("word") == L"word"); expect (String ("word").initialSectionContainingOnly ("word") == L"word"); expectEquals (s5.initialSectionNotContaining (String ("xyz ")), String ("word")); @@ -2626,9 +2649,9 @@ public: expectEquals (s.joinIntoString ("-"), String ("4-3-2-1-0")); s.remove (2); expectEquals (s.joinIntoString ("--"), String ("4--3--1--0")); - expectEquals (s.joinIntoString (String::empty), String ("4310")); + expectEquals (s.joinIntoString (StringRef()), String ("4310")); s.clear(); - expectEquals (s.joinIntoString ("x"), String::empty); + expectEquals (s.joinIntoString ("x"), String()); StringArray toks; toks.addTokens ("x,,", ";,", ""); diff --git a/source/modules/juce_core/text/juce_String.h b/source/modules/juce_core/text/juce_String.h index d59b744cc..0a3b8bcca 100644 --- a/source/modules/juce_core/text/juce_String.h +++ b/source/modules/juce_core/text/juce_String.h @@ -147,12 +147,15 @@ public: ~String() noexcept; //============================================================================== - /** This is an empty string that can be used whenever one is needed. - - It's better to use this than String() because it explains what's going on - and is more efficient. + #if JUCE_ALLOW_STATIC_NULL_VARIABLES + /** This is a static empty string object that can be used if you need a reference to one. + The value of String::empty is exactly the same as String(), and in almost all cases + it's better to avoid String::empty and just use String() or {} instead, so that the compiler + only has to reason about locally-constructed objects, rather than taking into account + the fact that you're referencing a global shared static memory address. */ static const String empty; + #endif /** This is the character encoding type used internally to store the string. @@ -1199,6 +1202,8 @@ public: */ size_t copyToUTF32 (CharPointer_UTF32::CharType* destBuffer, size_t maxBufferSizeBytes) const noexcept; + static String fromSingleByteData (const void* data, size_t numBytes); + //============================================================================== /** Increases the string's internally allocated storage. diff --git a/source/modules/juce_core/text/juce_StringArray.cpp b/source/modules/juce_core/text/juce_StringArray.cpp index dc331d8dd..3df28610f 100644 --- a/source/modules/juce_core/text/juce_StringArray.cpp +++ b/source/modules/juce_core/text/juce_StringArray.cpp @@ -127,7 +127,12 @@ const String& StringArray::operator[] (const int index) const noexcept if (isPositiveAndBelow (index, strings.size())) return strings.getReference (index); + #if JUCE_ALLOW_STATIC_NULL_VARIABLES return String::empty; + #else + static String empty; + return empty; + #endif } String& StringArray::getReference (const int index) noexcept @@ -152,10 +157,13 @@ void StringArray::insert (const int index, const String& newString) strings.insert (index, newString); } -void StringArray::addIfNotAlreadyThere (const String& newString, const bool ignoreCase) +bool StringArray::addIfNotAlreadyThere (const String& newString, const bool ignoreCase) { - if (! contains (newString, ignoreCase)) - add (newString); + if (contains (newString, ignoreCase)) + return false; + + add (newString); + return true; } void StringArray::addArray (const StringArray& otherArray, int startIndex, int numElementsToAdd) diff --git a/source/modules/juce_core/text/juce_StringArray.h b/source/modules/juce_core/text/juce_StringArray.h index ee39e2993..d173a2c02 100644 --- a/source/modules/juce_core/text/juce_StringArray.h +++ b/source/modules/juce_core/text/juce_StringArray.h @@ -118,7 +118,10 @@ public: //============================================================================== /** Returns the number of strings in the array */ - inline int size() const noexcept { return strings.size(); }; + inline int size() const noexcept { return strings.size(); } + + /** Returns true if the array is empty, false otherwise. */ + inline bool isEmpty() const noexcept { return size() == 0; } /** Returns one of the strings from the array. @@ -188,8 +191,10 @@ public: /** Adds a string to the array as long as it's not already in there. The search can optionally be case-insensitive. + + @return true if the string has been added, false otherwise. */ - void addIfNotAlreadyThere (const String& stringToAdd, bool ignoreCase = false); + bool addIfNotAlreadyThere (const String& stringToAdd, bool ignoreCase = false); /** Replaces one of the strings in the array with another one. @@ -256,7 +261,7 @@ public: /** Returns an array containing the tokens in a given string. This will tokenise the given string using whitespace characters as the - token delimiters, and return these tokens as an array. + token delimiters, and return the parsed tokens as an array. @see addTokens */ static StringArray fromTokens (StringRef stringToTokenise, @@ -264,8 +269,8 @@ public: /** Returns an array containing the tokens in a given string. - This will tokenise the given string using whitespace characters as the - token delimiters, and return these tokens as an array. + This will tokenise the given string using the breakCharacters string to define + the token delimiters, and will return the parsed tokens as an array. @param stringToTokenise the string to tokenise @param breakCharacters a string of characters, any of which will be considered diff --git a/source/modules/juce_core/text/juce_TextDiff.cpp b/source/modules/juce_core/text/juce_TextDiff.cpp index a8bff4e7e..5044780b1 100644 --- a/source/modules/juce_core/text/juce_TextDiff.cpp +++ b/source/modules/juce_core/text/juce_TextDiff.cpp @@ -262,9 +262,9 @@ public: Random r = getRandom(); - testDiff (String::empty, String::empty); - testDiff ("x", String::empty); - testDiff (String::empty, "x"); + testDiff (String(), String()); + testDiff ("x", String()); + testDiff (String(), "x"); testDiff ("x", "x"); testDiff ("x", "y"); testDiff ("xxx", "x"); diff --git a/source/modules/juce_core/threads/juce_Thread.cpp b/source/modules/juce_core/threads/juce_Thread.cpp index 27ab0d5c5..d529a6746 100644 --- a/source/modules/juce_core/threads/juce_Thread.cpp +++ b/source/modules/juce_core/threads/juce_Thread.cpp @@ -163,6 +163,14 @@ void Thread::signalThreadShouldExit() shouldExit = true; } +bool Thread::currentThreadShouldExit() +{ + if (Thread* currentThread = getCurrentThread()) + return currentThread->threadShouldExit(); + + return false; +} + bool Thread::waitForThreadToExit (const int timeOutMilliseconds) const { // Doh! So how exactly do you expect this thread to wait for itself to stop?? diff --git a/source/modules/juce_core/threads/juce_Thread.h b/source/modules/juce_core/threads/juce_Thread.h index 88bcfe9a2..3925fdea2 100644 --- a/source/modules/juce_core/threads/juce_Thread.h +++ b/source/modules/juce_core/threads/juce_Thread.h @@ -148,9 +148,17 @@ public: Threads need to check this regularly, and if it returns true, they should return from their run() method at the first possible opportunity. - @see signalThreadShouldExit + @see signalThreadShouldExit, currentThreadShouldExit */ - inline bool threadShouldExit() const { return shouldExit; } + bool threadShouldExit() const { return shouldExit; } + + /** Checks whether the current thread has been told to stop running. + On the message thread, this will always return false, otherwise + it will return threadShouldExit() called on the current thread. + + @see threadShouldExit + */ + static bool currentThreadShouldExit(); /** Waits for the thread to stop. diff --git a/source/modules/juce_core/threads/juce_ThreadPool.cpp b/source/modules/juce_core/threads/juce_ThreadPool.cpp index f57359c62..40b8eca18 100644 --- a/source/modules/juce_core/threads/juce_ThreadPool.cpp +++ b/source/modules/juce_core/threads/juce_ThreadPool.cpp @@ -29,8 +29,8 @@ class ThreadPool::ThreadPoolThread : public Thread { public: - ThreadPoolThread (ThreadPool& p) - : Thread ("Pool"), currentJob (nullptr), pool (p) + ThreadPoolThread (ThreadPool& p, size_t stackSize = 0) + : Thread ("Pool", stackSize), currentJob (nullptr), pool (p) { } @@ -85,11 +85,11 @@ ThreadPoolJob* ThreadPoolJob::getCurrentThreadPoolJob() } //============================================================================== -ThreadPool::ThreadPool (const int numThreads) +ThreadPool::ThreadPool (const int numThreads, size_t threadStackSize) { jassert (numThreads > 0); // not much point having a pool without any threads! - createThreads (numThreads); + createThreads (numThreads, threadStackSize); } ThreadPool::ThreadPool() @@ -103,10 +103,10 @@ ThreadPool::~ThreadPool() stopThreads(); } -void ThreadPool::createThreads (int numThreads) +void ThreadPool::createThreads (int numThreads, size_t threadStackSize) { for (int i = jmax (1, numThreads); --i >= 0;) - threads.add (new ThreadPoolThread (*this)); + threads.add (new ThreadPoolThread (*this, threadStackSize)); for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->startThread(); @@ -148,6 +148,11 @@ int ThreadPool::getNumJobs() const return jobs.size(); } +int ThreadPool::getNumThreads() const +{ + return threads.size(); +} + ThreadPoolJob* ThreadPool::getJob (const int index) const { const ScopedLock sl (lock); diff --git a/source/modules/juce_core/threads/juce_ThreadPool.h b/source/modules/juce_core/threads/juce_ThreadPool.h index 3934215e5..edfffa0c8 100644 --- a/source/modules/juce_core/threads/juce_ThreadPool.h +++ b/source/modules/juce_core/threads/juce_ThreadPool.h @@ -152,10 +152,14 @@ public: //============================================================================== /** Creates a thread pool. Once you've created a pool, you can give it some jobs by calling addJob(). + @param numberOfThreads the number of threads to run. These will be started immediately, and will run until the pool is deleted. + @param threadStackSize the size of the stack of each thread. If this value + is zero then the default stack size of the OS will + be used. */ - ThreadPool (int numberOfThreads); + ThreadPool (int numberOfThreads, size_t threadStackSize = 0); /** Creates a thread pool with one thread per CPU core. Once you've created a pool, you can give it some jobs by calling addJob(). @@ -237,8 +241,8 @@ public: methods called to try to interrupt them @param timeOutMilliseconds the length of time this method should wait for all the jobs to finish before giving up and returning false - @param selectedJobsToRemove if this is non-zero, the JobSelector object is asked to decide which - jobs should be removed. If it is zero, all jobs are removed + @param selectedJobsToRemove if this is not a nullptr, the JobSelector object is asked to decide + which jobs should be removed. If it is a nullptr, all jobs are removed @returns true if all jobs are successfully stopped and removed; false if the timeout period expires while waiting for one or more jobs to stop */ @@ -246,10 +250,12 @@ public: int timeOutMilliseconds, JobSelector* selectedJobsToRemove = nullptr); - /** Returns the number of jobs currently running or queued. - */ + /** Returns the number of jobs currently running or queued. */ int getNumJobs() const; + /** Returns the number of threads assigned to this thread pool. */ + int getNumThreads() const; + /** Returns one of the jobs in the queue. Note that this can be a very volatile list as jobs might be continuously getting shifted @@ -307,7 +313,7 @@ private: bool runNextJob (ThreadPoolThread&); ThreadPoolJob* pickNextJobToRun(); void addToDeleteList (OwnedArray&, ThreadPoolJob*) const; - void createThreads (int numThreads); + void createThreads (int numThreads, size_t threadStackSize = 0); void stopThreads(); // Note that this method has changed, and no longer has a parameter to indicate diff --git a/source/modules/juce_core/time/juce_PerformanceCounter.h b/source/modules/juce_core/time/juce_PerformanceCounter.h index 5426636ac..7d279d88f 100644 --- a/source/modules/juce_core/time/juce_PerformanceCounter.h +++ b/source/modules/juce_core/time/juce_PerformanceCounter.h @@ -60,7 +60,7 @@ public: @param counterName the name used when printing out the statistics @param runsPerPrintout the number of start/stop iterations before calling printStatistics() - @param loggingFile a file to dump the results to - if this is File::nonexistent, + @param loggingFile a file to dump the results to - if this is File(), the results are just written to the debugger output */ PerformanceCounter (const String& counterName, diff --git a/source/modules/juce_core/time/juce_Time.cpp b/source/modules/juce_core/time/juce_Time.cpp index 1d0b613ff..454dc7508 100644 --- a/source/modules/juce_core/time/juce_Time.cpp +++ b/source/modules/juce_core/time/juce_Time.cpp @@ -136,15 +136,16 @@ namespace TimeHelpers static inline int daysFromJan1 (int year, int month) noexcept { - const short dayOfYear[] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, - 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; + const short dayOfYear[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, + 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }; return dayOfYear [(isLeapYear (year) ? 12 : 0) + month]; } static inline int64 daysFromYear0 (int year) noexcept { - return 365 * (year - 1) + (year / 400) - (year / 100) + (year / 4); + --year; + return 365 * year + (year / 400) - (year / 100) + (year / 4); } static inline int64 daysFrom1970 (int year) noexcept @@ -213,18 +214,9 @@ Time::Time (const int year, t.tm_sec = seconds; t.tm_isdst = -1; - const int64 time = useLocalTime ? (int64) mktime (&t) - : TimeHelpers::mktime_utc (t); - - if (time >= 0) - { - millisSinceEpoch = 1000 * time + milliseconds; - } - else - { - jassertfalse; // trying to create a date that is beyond the range that mktime supports! - millisSinceEpoch = 0; - } + millisSinceEpoch = 1000 * (useLocalTime ? (int64) mktime (&t) + : TimeHelpers::mktime_utc (t)) + + milliseconds; } Time::~Time() noexcept @@ -402,7 +394,8 @@ String Time::getTimeZone() const noexcept { String zone[2]; - #if JUCE_MSVC + #if JUCE_WINDOWS + #if JUCE_MSVC || JUCE_CLANG _tzset(); for (int i = 0; i < 2; ++i) @@ -412,12 +405,12 @@ String Time::getTimeZone() const noexcept _get_tzname (&length, name, 127, i); zone[i] = name; } - #else - #if JUCE_MINGW - #warning "Can't find a replacement for tzset on mingw - ideas welcome!" #else - tzset(); + #warning "Can't find a replacement for tzset on mingw - ideas welcome!" #endif + #else + tzset(); + const char** const zonePtr = (const char**) tzname; zone[0] = zonePtr[0]; zone[1] = zonePtr[1]; @@ -458,8 +451,8 @@ String Time::getUTCOffsetString (bool includeSemiColon) const String Time::toISO8601 (bool includeDividerCharacters) const { - return String::formatted (includeDividerCharacters ? "%04d-%02d-%02dT%02d:%02d:%02.03f" - : "%04d%02d%02dT%02d%02d%02.03f", + return String::formatted (includeDividerCharacters ? "%04d-%02d-%02dT%02d:%02d:%06.03f" + : "%04d%02d%02dT%02d%02d%06.03f", getYear(), getMonth() + 1, getDayOfMonth(), @@ -542,9 +535,7 @@ Time Time::fromISO8601 (StringRef iso) noexcept return Time(); } - Time result (year, month - 1, day, hours, minutes, 0, 0, false); - result.millisSinceEpoch += milliseconds; - return result; + return Time (year, month - 1, day, hours, minutes, 0, milliseconds, false); } String Time::getMonthName (const bool threeLetterVersion) const @@ -661,6 +652,14 @@ public: expect (Time::fromISO8601 ("2016-02-16T15:03:57.999-02:30") == Time (2016, 1, 16, 17, 33, 57, 999, false)); expect (Time::fromISO8601 ("20160216T150357.999-0230") == Time (2016, 1, 16, 17, 33, 57, 999, false)); + expect (Time (1970, 0, 1, 0, 0, 0, 0, false) == Time (0)); + expect (Time (2106, 1, 7, 6, 28, 15, 0, false) == Time (4294967295000)); + expect (Time (2007, 10, 7, 1, 7, 20, 0, false) == Time (1194397640000)); + expect (Time (2038, 0, 19, 3, 14, 7, 0, false) == Time (2147483647000)); + expect (Time (2016, 2, 7, 11, 20, 8, 0, false) == Time (1457349608000)); + expect (Time (1969, 11, 31, 23, 59, 59, 0, false) == Time (-1000)); + expect (Time (1901, 11, 13, 20, 45, 53, 0, false) == Time (-2147483647000)); + expect (Time (1982, 1, 1, 12, 0, 0, 0, true) + RelativeTime::days (365) == Time (1983, 1, 1, 12, 0, 0, 0, true)); expect (Time (1970, 1, 1, 12, 0, 0, 0, true) + RelativeTime::days (365) == Time (1971, 1, 1, 12, 0, 0, 0, true)); expect (Time (2038, 1, 1, 12, 0, 0, 0, true) + RelativeTime::days (365) == Time (2039, 1, 1, 12, 0, 0, 0, true)); diff --git a/source/modules/juce_core/unit_tests/juce_UnitTest.h b/source/modules/juce_core/unit_tests/juce_UnitTest.h index d698c95aa..72a2896fa 100644 --- a/source/modules/juce_core/unit_tests/juce_UnitTest.h +++ b/source/modules/juce_core/unit_tests/juce_UnitTest.h @@ -138,23 +138,78 @@ public: */ void expect (bool testResult, const String& failureMessage = String()); - /** Compares two values, and if they don't match, prints out a message containing the - expected and actual result values. + //============================================================================== + /** Compares a value to an expected value. + If they are not equal, prints out a message containing the expected and actual values. */ template void expectEquals (ValueType actual, ValueType expected, String failureMessage = String()) { - const bool result = (actual == expected); + bool result = actual == expected; + expectResultAndPrint (actual, expected, result, "", failureMessage); + } - if (! result) - { - if (failureMessage.isNotEmpty()) - failureMessage << " -- "; + /** Checks whether a value is not equal to a comparison value. + If this check fails, prints out a message containing the actual and comparison values. + */ + template + void expectNotEquals (ValueType value, ValueType valueToCompareTo, String failureMessage = String()) + { + bool result = value != valueToCompareTo; + expectResultAndPrint (value, valueToCompareTo, result, "unequal to", failureMessage); + } - failureMessage << "Expected value: " << expected << ", Actual value: " << actual; - } + /** Checks whether a value is greater than a comparison value. + If this check fails, prints out a message containing the actual and comparison values. + */ + template + void expectGreaterThan (ValueType value, ValueType valueToCompareTo, String failureMessage = String()) + { + bool result = value > valueToCompareTo; + expectResultAndPrint (value, valueToCompareTo, result, "greater than", failureMessage); + } - expect (result, failureMessage); + /** Checks whether a value is less than a comparison value. + If this check fails, prints out a message containing the actual and comparison values. + */ + template + void expectLessThan (ValueType value, ValueType valueToCompareTo, String failureMessage = String()) + { + bool result = value < valueToCompareTo; + expectResultAndPrint (value, valueToCompareTo, result, "less than", failureMessage); + } + + /** Checks whether a value is greater or equal to a comparison value. + If this check fails, prints out a message containing the actual and comparison values. + */ + template + void expectGreaterOrEqual (ValueType value, ValueType valueToCompareTo, String failureMessage = String()) + { + bool result = value >= valueToCompareTo; + expectResultAndPrint (value, valueToCompareTo, result, "greater or equal to", failureMessage); + } + + /** Checks whether a value is less or equal to a comparison value. + If this check fails, prints out a message containing the actual and comparison values. + */ + template + void expectLessOrEqual (ValueType value, ValueType valueToCompareTo, String failureMessage = String()) + { + bool result = value <= valueToCompareTo; + expectResultAndPrint (value, valueToCompareTo, result, "less or equal to", failureMessage); + } + + /** Computes the difference between a value and a comparison value, and if it is larger than a + specified maximum value, prints out a message containing the actual and comparison values + and the maximum allowed error. + */ + template + void expectWithinAbsoluteError (ValueType actual, ValueType expected, ValueType maxAbsoluteError, String failureMessage = String()) + { + const ValueType diff = std::abs (actual - expected); + const bool result = diff <= maxAbsoluteError; + + expectResultAndPrint (actual, expected, result, " within " + String (maxAbsoluteError) + " of" , failureMessage); } //============================================================================== @@ -221,6 +276,24 @@ public: Random getRandom() const; private: + //============================================================================== + template + void expectResultAndPrint (ValueType value, ValueType valueToCompareTo, bool result, + String compDescription, String failureMessage) + { + if (! result) + { + if (failureMessage.isNotEmpty()) + failureMessage << " -- "; + + failureMessage << "Expected value" << (compDescription.isEmpty() ? "" : " ") + << compDescription << ": " << valueToCompareTo + << ", Actual value: " << value; + } + + expect (result, failureMessage); + } + //============================================================================== const String name; UnitTestRunner* runner; diff --git a/source/modules/juce_core/xml/juce_XmlDocument.cpp b/source/modules/juce_core/xml/juce_XmlDocument.cpp index c0f016e3b..bf56f4f63 100644 --- a/source/modules/juce_core/xml/juce_XmlDocument.cpp +++ b/source/modules/juce_core/xml/juce_XmlDocument.cpp @@ -617,9 +617,17 @@ void XmlDocument::readChildElements (XmlElement& parent) } else { - for (;;) + for (;; ++input) { - const juce_wchar nextChar = *input; + juce_wchar nextChar = *input; + + if (nextChar == '\r') + { + nextChar = '\n'; + + if (input[1] == '\n') + continue; + } if (nextChar == '<' || nextChar == '&') break; @@ -633,7 +641,6 @@ void XmlDocument::readChildElements (XmlElement& parent) textElementContent.appendUTF8Char (nextChar); contentShouldBeUsed = contentShouldBeUsed || ! CharacterFunctions::isWhitespace (nextChar); - ++input; } } } diff --git a/source/modules/juce_core/xml/juce_XmlElement.cpp b/source/modules/juce_core/xml/juce_XmlElement.cpp index 745dcd786..73d4ccf21 100644 --- a/source/modules/juce_core/xml/juce_XmlElement.cpp +++ b/source/modules/juce_core/xml/juce_XmlElement.cpp @@ -407,6 +407,11 @@ bool XmlElement::writeToFile (const File& file, return false; writeToStream (out, dtdToUse, false, true, encodingType, lineWrapLength); + + out.flush(); // (called explicitly to force an fsync on posix) + + if (out.getStatus().failed()) + return false; } return tempFile.overwriteTargetFileWithTemporary(); @@ -455,12 +460,22 @@ int XmlElement::getNumAttributes() const noexcept return attributes.size(); } +static const String& getEmptyStringRef() noexcept +{ + #if JUCE_ALLOW_STATIC_NULL_VARIABLES + return String::empty; + #else + static String empty; + return empty; + #endif +} + const String& XmlElement::getAttributeName (const int index) const noexcept { if (const XmlAttributeNode* const att = attributes [index]) return att->name.toString(); - return String::empty; + return getEmptyStringRef(); } const String& XmlElement::getAttributeValue (const int index) const noexcept @@ -468,7 +483,7 @@ const String& XmlElement::getAttributeValue (const int index) const noexcept if (const XmlAttributeNode* const att = attributes [index]) return att->value; - return String::empty; + return getEmptyStringRef(); } XmlElement::XmlAttributeNode* XmlElement::getAttribute (StringRef attributeName) const noexcept @@ -491,7 +506,7 @@ const String& XmlElement::getStringAttribute (StringRef attributeName) const noe if (const XmlAttributeNode* att = getAttribute (attributeName)) return att->value; - return String::empty; + return getEmptyStringRef(); } String XmlElement::getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const diff --git a/source/modules/juce_core/xml/juce_XmlElement.h b/source/modules/juce_core/xml/juce_XmlElement.h index 2d23c6b75..e114aa700 100644 --- a/source/modules/juce_core/xml/juce_XmlElement.h +++ b/source/modules/juce_core/xml/juce_XmlElement.h @@ -457,8 +457,8 @@ public: Also, it's much easier and neater to use this method indirectly via the forEachXmlChildElement macro. - @returns the sibling element that follows this one, or zero if this is the last - element in its parent + @returns the sibling element that follows this one, or a nullptr if + this is the last element in its parent @see getNextElement, isTextElement, forEachXmlChildElement */ diff --git a/source/modules/juce_core/zip/juce_ZipFile.cpp b/source/modules/juce_core/zip/juce_ZipFile.cpp index b743b2798..ef02e2570 100644 --- a/source/modules/juce_core/zip/juce_ZipFile.cpp +++ b/source/modules/juce_core/zip/juce_ZipFile.cpp @@ -60,7 +60,7 @@ private: const int day = date & 31; const int hours = time >> 11; const int minutes = (time >> 5) & 63; - const int seconds = (time & 31) << 1; + const int seconds = (int) ((time & 31) << 1); return Time (year, month, day, hours, minutes, seconds); } diff --git a/source/modules/juce_core/zip/juce_ZipFile.h b/source/modules/juce_core/zip/juce_ZipFile.h index b253eeb76..84f7336df 100644 --- a/source/modules/juce_core/zip/juce_ZipFile.h +++ b/source/modules/juce_core/zip/juce_ZipFile.h @@ -91,7 +91,7 @@ public: int getNumEntries() const noexcept; /** Returns a structure that describes one of the entries in the zip file. - This may return zero if the index is out of range. + This may return a nullptr if the index is out of range. @see ZipFile::ZipEntry */ const ZipEntry* getEntry (int index) const noexcept; @@ -120,7 +120,7 @@ public: /** Creates a stream that can read from one of the zip file's entries. The stream that is returned must be deleted by the caller (and - zero might be returned if a stream can't be opened for some reason). + a nullptr might be returned if a stream can't be opened for some reason). The stream must not be used after the ZipFile object that created has been deleted. @@ -135,7 +135,7 @@ public: /** Creates a stream that can read from one of the zip file's entries. The stream that is returned must be deleted by the caller (and - zero might be returned if a stream can't be opened for some reason). + a nullptr might be returned if a stream can't be opened for some reason). The stream must not be used after the ZipFile object that created has been deleted. diff --git a/source/modules/juce_data_structures/juce_data_structures.cpp b/source/modules/juce_data_structures/juce_data_structures.cpp index e6d7784a4..67b3640a1 100644 --- a/source/modules/juce_data_structures/juce_data_structures.cpp +++ b/source/modules/juce_data_structures/juce_data_structures.cpp @@ -31,6 +31,8 @@ #error "Incorrect use of JUCE cpp file" #endif +#include "AppConfig.h" + #include "juce_data_structures.h" namespace juce @@ -39,6 +41,7 @@ namespace juce #include "values/juce_Value.cpp" #include "values/juce_ValueTree.cpp" #include "values/juce_ValueTreeSynchroniser.cpp" +#include "values/juce_CachedValue.cpp" #include "undomanager/juce_UndoManager.cpp" #include "app_properties/juce_ApplicationProperties.cpp" #include "app_properties/juce_PropertiesFile.cpp" diff --git a/source/modules/juce_data_structures/juce_data_structures.h b/source/modules/juce_data_structures/juce_data_structures.h index 48d321a63..3c8580a8f 100644 --- a/source/modules/juce_data_structures/juce_data_structures.h +++ b/source/modules/juce_data_structures/juce_data_structures.h @@ -22,11 +22,35 @@ ============================================================================== */ +/******************************************************************************* + The block below describes the properties of this module, and is read by + the Projucer to automatically generate project code that uses it. + For details about the syntax and how to create or use a module, see the + JUCE Module Format.txt file. + + + BEGIN_JUCE_MODULE_DECLARATION + + ID: juce_data_structures + vendor: juce + version: 4.3.0 + name: JUCE data model helper classes + description: Classes for undo/redo management, and smart data structures. + website: http://www.juce.com/juce + license: GPL/Commercial + + dependencies: juce_events + + END_JUCE_MODULE_DECLARATION + +*******************************************************************************/ + + #ifndef JUCE_DATA_STRUCTURES_H_INCLUDED #define JUCE_DATA_STRUCTURES_H_INCLUDED //============================================================================== -#include "../juce_events/juce_events.h" +#include namespace juce { @@ -36,6 +60,7 @@ namespace juce #include "values/juce_Value.h" #include "values/juce_ValueTree.h" #include "values/juce_ValueTreeSynchroniser.h" +#include "values/juce_CachedValue.h" #include "app_properties/juce_PropertiesFile.h" #include "app_properties/juce_ApplicationProperties.h" diff --git a/source/modules/juce_data_structures/undomanager/juce_UndoManager.cpp b/source/modules/juce_data_structures/undomanager/juce_UndoManager.cpp index 4dc27b870..819d55a10 100644 --- a/source/modules/juce_data_structures/undomanager/juce_UndoManager.cpp +++ b/source/modules/juce_data_structures/undomanager/juce_UndoManager.cpp @@ -153,7 +153,8 @@ bool UndoManager::perform (UndoableAction* const newAction) actionSet->actions.add (action.release()); newTransaction = false; - clearFutureTransactions(); + moveFutureTransactionsToStash(); + dropOldTransactionsIfTooLarge(); sendChangeMessage(); return true; } @@ -162,14 +163,41 @@ bool UndoManager::perform (UndoableAction* const newAction) return false; } -void UndoManager::clearFutureTransactions() +void UndoManager::moveFutureTransactionsToStash() +{ + if (nextIndex < transactions.size()) + { + stashedFutureTransactions.clear(); + + while (nextIndex < transactions.size()) + { + ActionSet* removed = transactions.removeAndReturn (nextIndex); + stashedFutureTransactions.add (removed); + totalUnitsStored -= removed->getTotalSize(); + } + } +} + +void UndoManager::restoreStashedFutureTransactions() { while (nextIndex < transactions.size()) { - totalUnitsStored -= transactions.getLast()->getTotalSize(); - transactions.removeLast(); + totalUnitsStored -= transactions.getUnchecked (nextIndex)->getTotalSize(); + transactions.remove (nextIndex); } + for (int i = 0; i < stashedFutureTransactions.size(); ++i) + { + ActionSet* action = stashedFutureTransactions.removeAndReturn (i); + totalUnitsStored += action->getTotalSize(); + transactions.add (action); + } + + stashedFutureTransactions.clearQuick (false); +} + +void UndoManager::dropOldTransactionsIfTooLarge() +{ while (nextIndex > 0 && totalUnitsStored > maxNumUnitsToKeep && transactions.size() > minimumTransactionsToKeep) @@ -290,7 +318,13 @@ Time UndoManager::getTimeOfRedoTransaction() const bool UndoManager::undoCurrentTransactionOnly() { - return newTransaction ? false : undo(); + if ((! newTransaction) && undo()) + { + restoreStashedFutureTransactions(); + return true; + } + + return false; } void UndoManager::getActionsInCurrentTransaction (Array& actionsFound) const diff --git a/source/modules/juce_data_structures/undomanager/juce_UndoManager.h b/source/modules/juce_data_structures/undomanager/juce_UndoManager.h index 1e740eea3..4bb9cf997 100644 --- a/source/modules/juce_data_structures/undomanager/juce_UndoManager.h +++ b/source/modules/juce_data_structures/undomanager/juce_UndoManager.h @@ -229,13 +229,15 @@ private: //============================================================================== struct ActionSet; friend struct ContainerDeletePolicy; - OwnedArray transactions; + OwnedArray transactions, stashedFutureTransactions; String newTransactionName; int totalUnitsStored, maxNumUnitsToKeep, minimumTransactionsToKeep, nextIndex; bool newTransaction, reentrancyCheck; ActionSet* getCurrentSet() const noexcept; ActionSet* getNextSet() const noexcept; - void clearFutureTransactions(); + void moveFutureTransactionsToStash(); + void restoreStashedFutureTransactions(); + void dropOldTransactionsIfTooLarge(); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UndoManager) }; diff --git a/source/modules/juce_data_structures/undomanager/juce_UndoableAction.h b/source/modules/juce_data_structures/undomanager/juce_UndoableAction.h index 479dd7692..c2bdd81e9 100644 --- a/source/modules/juce_data_structures/undomanager/juce_UndoableAction.h +++ b/source/modules/juce_data_structures/undomanager/juce_UndoableAction.h @@ -90,7 +90,7 @@ public: If possible, this method should create and return a single action that does the same job as this one followed by the supplied action. - If it's not possible to merge the two actions, the method should return zero. + If it's not possible to merge the two actions, the method should return a nullptr. */ virtual UndoableAction* createCoalescedAction (UndoableAction* nextAction) { ignoreUnused (nextAction); return nullptr; } }; diff --git a/source/modules/juce_data_structures/values/juce_CachedValue.cpp b/source/modules/juce_data_structures/values/juce_CachedValue.cpp new file mode 100644 index 000000000..5ef337349 --- /dev/null +++ b/source/modules/juce_data_structures/values/juce_CachedValue.cpp @@ -0,0 +1,152 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#if JUCE_UNIT_TESTS + +class CachedValueTests : public UnitTest +{ +public: + CachedValueTests() : UnitTest ("CachedValues") {} + + void runTest() override + { + beginTest ("default constructor"); + { + CachedValue cv; + expect (cv.isUsingDefault()); + expect (cv.get() == String()); + } + + beginTest ("without default value"); + { + ValueTree t ("root"); + t.setProperty ("testkey", "testvalue", nullptr); + + CachedValue cv (t, "testkey", nullptr); + + expect (! cv.isUsingDefault()); + expect (cv.get() == "testvalue"); + + cv.resetToDefault(); + + expect (cv.isUsingDefault()); + expect (cv.get() == String()); + } + + beginTest ("with default value"); + { + ValueTree t ("root"); + t.setProperty ("testkey", "testvalue", nullptr); + + CachedValue cv (t, "testkey", nullptr, "defaultvalue"); + + expect (! cv.isUsingDefault()); + expect (cv.get() == "testvalue"); + + cv.resetToDefault(); + + expect (cv.isUsingDefault()); + expect (cv.get() == "defaultvalue"); + } + + beginTest ("with default value (int)"); + { + ValueTree t ("root"); + t.setProperty ("testkey", 23, nullptr); + + CachedValue cv (t, "testkey", nullptr, 34); + + expect (! cv.isUsingDefault()); + expect (cv == 23); + expectEquals (cv.get(), 23); + + cv.resetToDefault(); + + expect (cv.isUsingDefault()); + expect (cv == 34); + } + + beginTest ("with void value"); + { + ValueTree t ("root"); + t.setProperty ("testkey", var(), nullptr); + + CachedValue cv (t, "testkey", nullptr, "defaultvalue"); + + expect (! cv.isUsingDefault()); + expect (cv == ""); + expectEquals (cv.get(), String()); + } + + beginTest ("with non-existent value"); + { + ValueTree t ("root"); + + CachedValue cv (t, "testkey", nullptr, "defaultvalue"); + + expect (cv.isUsingDefault()); + expect (cv == "defaultvalue"); + expect (cv.get() == "defaultvalue"); + } + + beginTest ("with value changing"); + { + ValueTree t ("root"); + t.setProperty ("testkey", "oldvalue", nullptr); + + CachedValue cv (t, "testkey", nullptr, "defaultvalue"); + expect (cv == "oldvalue"); + + t.setProperty ("testkey", "newvalue", nullptr); + expect (cv != "oldvalue"); + expect (cv == "newvalue"); + } + + beginTest ("set value"); + { + ValueTree t ("root"); + t.setProperty ("testkey", 23, nullptr); + + CachedValue cv (t, "testkey", nullptr, 45); + cv = 34; + + expectEquals ((int) t["testkey"], 34); + + cv.resetToDefault(); + expect (cv == 45); + expectEquals (cv.get(), 45); + + expect (t["testkey"] == var()); + } + + beginTest ("reset value"); + { + + } + } +}; + +static CachedValueTests cachedValueTests; + +#endif diff --git a/source/modules/juce_data_structures/values/juce_CachedValue.h b/source/modules/juce_data_structures/values/juce_CachedValue.h new file mode 100644 index 000000000..3e9e476a8 --- /dev/null +++ b/source/modules/juce_data_structures/values/juce_CachedValue.h @@ -0,0 +1,311 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_CACHEDVALUE_H_INCLUDED +#define JUCE_CACHEDVALUE_H_INCLUDED + + +//============================================================================== +/** + This class acts as a typed wrapper around a property inside a ValueTree. + + A CachedValue provides an easy way to read and write a ValueTree property with + a chosen type. So for example a CachedValue allows you to read or write the + property as an int, and a CachedValue lets you work with it as a String. + + It also allows efficient access to the value, by caching a copy of it in the + type that is being used. + + You can give the CachedValue an optional UndoManager which it will use when writing + to the underlying ValueTree. + + If the property inside the ValueTree is missing, the CachedValue will automatically + return an optional default value, which can be specified when initialising the CachedValue. + + To create one, you can either use the constructor to attach the CachedValue to a + ValueTree, or can create an uninitialised CachedValue with its default constructor and + then attach it later with the referTo() methods. + + Common types like String, int, double which can be easily converted to a var should work + out-of-the-box, but if you want to use more complex custom types, you may need to implement + some template specialisations of VariantConverter which this class uses to convert between + the type and the ValueTree's internal var. +*/ +template +class CachedValue : private ValueTree::Listener +{ +public: + //============================================================================== + /** Default constructor. + Creates a default CachedValue not referring to any property. To initialise the + object, call one of the referTo() methods. + */ + CachedValue(); + + /** Constructor. + + Creates a CachedValue referring to a Value property inside a ValueTree. + If you use this constructor, the fallback value will be a default-constructed + instance of Type. + + @param tree The ValueTree containing the property + @param propertyID The identifier of the property + @param undoManager The UndoManager to use when writing to the property + */ + CachedValue (ValueTree& tree, const Identifier& propertyID, + UndoManager* undoManager); + + /** Constructor. + + Creates a default Cached Value referring to a Value property inside a ValueTree, + and specifies a fallback value to use if the property does not exist. + + @param tree The ValueTree containing the property + @param propertyID The identifier of the property + @param undoManager The UndoManager to use when writing to the property + @param defaultToUse The fallback default value to use. + */ + CachedValue (ValueTree& tree, const Identifier& propertyID, + UndoManager* undoManager, const Type& defaultToUse); + + //============================================================================== + /** Returns the current value of the property. If the property does not exist, + returns the fallback default value. + + This is the same as calling get(). + */ + operator Type() const noexcept { return cachedValue; } + + /** Returns the current value of the property. If the property does not exist, + returns the fallback default value. + */ + Type get() const noexcept { return cachedValue; } + + /** Dereference operator. Provides direct access to the property. */ + Type& operator*() noexcept { return cachedValue; } + + /** Dereference operator. Provides direct access to members of the property + if it is of object type. + */ + Type* operator->() noexcept { return &cachedValue; } + + /** Returns true if the current value of the property (or the fallback value) + is equal to other. + */ + template + bool operator== (const OtherType& other) const { return cachedValue == other; } + + /** Returns true if the current value of the property (or the fallback value) + is not equal to other. + */ + template + bool operator!= (const OtherType& other) const { return cachedValue != other; } + + //============================================================================== + /** Returns the current property as a Value object. */ + Value getPropertyAsValue(); + + /** Returns true if the current property does not exist and the CachedValue is using + the fallback default value instead. + */ + bool isUsingDefault() const; + + /** Returns the current fallback default value. */ + Type getDefault() const { return defaultValue; } + + //============================================================================== + /** Sets the property. This will actually modify the property in the referenced ValueTree. */ + CachedValue& operator= (const Type& newValue); + + /** Sets the property. This will actually modify the property in the referenced ValueTree. */ + void setValue (const Type& newValue, UndoManager* undoManagerToUse); + + /** Removes the property from the referenced ValueTree and makes the CachedValue + return the fallback default value instead. + */ + void resetToDefault(); + + /** Removes the property from the referenced ValueTree and makes the CachedValue + return the fallback default value instead. + */ + void resetToDefault (UndoManager* undoManagerToUse); + + /** Resets the fallback default value. */ + void setDefault (const Type& value) { defaultValue = value; } + + //============================================================================== + /** Makes the CachedValue refer to the specified property inside the given ValueTree. */ + void referTo (ValueTree& tree, const Identifier& property, UndoManager* um); + + /** Makes the CachedValue refer to the specified property inside the given ValueTree, + and specifies a fallback value to use if the property does not exist. + */ + void referTo (ValueTree& tree, const Identifier& property, UndoManager* um, const Type& defaultVal); + + /** Force an update in case the referenced property has been changed from elsewhere. + + Note: The CachedValue is a ValueTree::Listener and therefore will be informed of + changes of the referenced property anyway (and update itself). But this may happen + asynchronously. forceUpdateOfCachedValue() forces an update immediately. + */ + void forceUpdateOfCachedValue(); + + //============================================================================== + /** Returns a reference to the ValueTree containing the referenced property. */ + ValueTree& getValueTree() noexcept { return targetTree; } + + /** Returns the property ID of the referenced property. */ + const Identifier& getPropertyID() const noexcept { return targetProperty; } + +private: + //============================================================================== + ValueTree targetTree; + Identifier targetProperty; + UndoManager* undoManager; + Type defaultValue; + Type cachedValue; + + //============================================================================== + void referToWithDefault (ValueTree&, const Identifier&, UndoManager*, const Type&); + Type getTypedValue() const; + + void valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) override; + void valueTreeChildAdded (ValueTree&, ValueTree&) override {} + void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override {} + void valueTreeChildOrderChanged (ValueTree&, int, int) override {} + void valueTreeParentChanged (ValueTree&) override {} + + JUCE_DECLARE_NON_COPYABLE (CachedValue) +}; + + +//============================================================================== +template +inline CachedValue::CachedValue() : undoManager (nullptr) {} + +template +inline CachedValue::CachedValue (ValueTree& v, const Identifier& i, UndoManager* um) + : targetTree (v), targetProperty (i), undoManager (um), + defaultValue(), cachedValue (getTypedValue()) +{ + targetTree.addListener (this); +} + +template +inline CachedValue::CachedValue (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultToUse) + : targetTree (v), targetProperty (i), undoManager (um), + defaultValue (defaultToUse), cachedValue (getTypedValue()) +{ + targetTree.addListener (this); +} + +template +inline Value CachedValue::getPropertyAsValue() +{ + return targetTree.getPropertyAsValue (targetProperty, undoManager); +} + +template +inline bool CachedValue::isUsingDefault() const +{ + return ! targetTree.hasProperty (targetProperty); +} + +template +inline CachedValue& CachedValue::operator= (const Type& newValue) +{ + setValue (newValue, undoManager); + return *this; +} + +template +inline void CachedValue::setValue (const Type& newValue, UndoManager* undoManagerToUse) +{ + if (cachedValue != newValue || isUsingDefault()) + { + cachedValue = newValue; + targetTree.setProperty (targetProperty, VariantConverter::toVar (newValue), undoManagerToUse); + } +} + +template +inline void CachedValue::resetToDefault() +{ + resetToDefault (undoManager); +} + +template +inline void CachedValue::resetToDefault (UndoManager* undoManagerToUse) +{ + targetTree.removeProperty (targetProperty, undoManagerToUse); + forceUpdateOfCachedValue(); +} + +template +inline void CachedValue::referTo (ValueTree& v, const Identifier& i, UndoManager* um) +{ + referToWithDefault (v, i, um, Type()); +} + +template +inline void CachedValue::referTo (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultVal) +{ + referToWithDefault (v, i, um, defaultVal); +} + +template +inline void CachedValue::forceUpdateOfCachedValue() +{ + cachedValue = getTypedValue(); +} + +template +inline void CachedValue::referToWithDefault (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultVal) +{ + targetTree.removeListener (this); + targetTree = v; + targetProperty = i; + undoManager = um; + defaultValue = defaultVal; + cachedValue = getTypedValue(); + targetTree.addListener (this); +} + +template +inline Type CachedValue::getTypedValue() const +{ + if (const var* property = targetTree.getPropertyPointer (targetProperty)) + return VariantConverter::fromVar (*property); + + return defaultValue; +} + +template +inline void CachedValue::valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) +{ + if (changedProperty == targetProperty && targetTree == changedTree) + forceUpdateOfCachedValue(); +} + +#endif // JUCE_CACHEDVALUE_H_INCLUDED diff --git a/source/modules/juce_data_structures/values/juce_ValueTree.cpp b/source/modules/juce_data_structures/values/juce_ValueTree.cpp index f601bf71c..51b041ca2 100644 --- a/source/modules/juce_data_structures/values/juce_ValueTree.cpp +++ b/source/modules/juce_data_structures/values/juce_ValueTree.cpp @@ -627,7 +627,9 @@ ValueTree::ValueTree() noexcept { } +#if JUCE_ALLOW_STATIC_NULL_VARIABLES const ValueTree ValueTree::invalid; +#endif ValueTree::ValueTree (const Identifier& type) : object (new ValueTree::SharedObject (type)) { @@ -721,20 +723,30 @@ ValueTree ValueTree::getParent() const noexcept ValueTree ValueTree::getSibling (const int delta) const noexcept { if (object == nullptr || object->parent == nullptr) - return invalid; + return ValueTree(); const int index = object->parent->indexOf (*this) + delta; return ValueTree (object->parent->children.getObjectPointer (index)); } +static const var& getNullVarRef() noexcept +{ + #if JUCE_ALLOW_STATIC_NULL_VARIABLES + return var::null; + #else + static var nullVar; + return nullVar; + #endif +} + const var& ValueTree::operator[] (const Identifier& name) const noexcept { - return object == nullptr ? var::null : object->properties[name]; + return object == nullptr ? getNullVarRef() : object->properties[name]; } const var& ValueTree::getProperty (const Identifier& name) const noexcept { - return object == nullptr ? var::null : object->properties[name]; + return object == nullptr ? getNullVarRef() : object->properties[name]; } var ValueTree::getProperty (const Identifier& name, const var& defaultReturnValue) const @@ -743,6 +755,12 @@ var ValueTree::getProperty (const Identifier& name, const var& defaultReturnValu : object->properties.getWithDefault (name, defaultReturnValue); } +const var* ValueTree::getPropertyPointer (const Identifier& name) const noexcept +{ + return object == nullptr ? nullptr + : object->properties.getVarPointer (name); +} + ValueTree& ValueTree::setProperty (const Identifier& name, const var& newValue, UndoManager* undoManager) { jassert (name.toString().isNotEmpty()); // Must have a valid property name! @@ -852,6 +870,29 @@ ValueTree ValueTree::getChild (int index) const : static_cast (nullptr)); } +ValueTree::Iterator::Iterator (const ValueTree& v, bool isEnd) noexcept + : internal (v.object != nullptr ? (isEnd ? v.object->children.end() : v.object->children.begin()) : nullptr) +{} + +ValueTree::Iterator& ValueTree::Iterator::operator++() noexcept +{ + internal = static_cast (internal) + 1; + return *this; +} + +bool ValueTree::Iterator::operator!= (const Iterator& other) const noexcept +{ + return internal != other.internal; +} + +ValueTree ValueTree::Iterator::operator*() const +{ + return ValueTree (*static_cast (internal)); +} + +ValueTree::Iterator ValueTree::begin() const noexcept { return Iterator (*this, false); } +ValueTree::Iterator ValueTree::end() const noexcept { return Iterator (*this, true); } + ValueTree ValueTree::getChildWithName (const Identifier& type) const { return object != nullptr ? object->getChildWithName (type) : ValueTree(); diff --git a/source/modules/juce_data_structures/values/juce_ValueTree.h b/source/modules/juce_data_structures/values/juce_ValueTree.h index c65a08150..52bb8d3c6 100644 --- a/source/modules/juce_data_structures/values/juce_ValueTree.h +++ b/source/modules/juce_data_structures/values/juce_ValueTree.h @@ -145,17 +145,23 @@ public: /** Returns the value of a named property. If no such property has been set, this will return a void variant. You can also use operator[] to get a property. - @see var, setProperty, hasProperty + @see var, setProperty, getPropertyPointer, hasProperty */ const var& getProperty (const Identifier& name) const noexcept; - /** Returns the value of a named property, or a user-specified default if the property doesn't exist. - If no such property has been set, this will return the value of defaultReturnValue. + /** Returns the value of a named property, or the value of defaultReturnValue + if the property doesn't exist. You can also use operator[] and getProperty to get a property. - @see var, getProperty, setProperty, hasProperty + @see var, getProperty, getPropertyPointer, setProperty, hasProperty */ var getProperty (const Identifier& name, const var& defaultReturnValue) const; + /** Returns a pointer to the value of a named property, or nullptr if the property + doesn't exist. + @see var, getProperty, setProperty, hasProperty + */ + const var* getPropertyPointer (const Identifier& name) const noexcept; + /** Returns the value of a named property. If no such property has been set, this will return a void variant. This is the same as calling getProperty(). @@ -322,6 +328,25 @@ public: */ ValueTree getSibling (int delta) const noexcept; + //============================================================================== + struct Iterator + { + Iterator (const ValueTree&, bool isEnd) noexcept; + Iterator& operator++() noexcept; + + bool operator!= (const Iterator&) const noexcept; + ValueTree operator*() const; + + private: + void* internal; + }; + + /** Returns a start iterator for the children in this tree. */ + Iterator begin() const noexcept; + + /** Returns an end iterator for the children in this tree. */ + Iterator end() const noexcept; + //============================================================================== /** Creates an XmlElement that holds a complete image of this node and all its children. @@ -498,10 +523,13 @@ public: } } + #if JUCE_ALLOW_STATIC_NULL_VARIABLES /** An invalid ValueTree that can be used if you need to return one as an error condition, etc. - This invalid object is equivalent to ValueTree created with its default constructor. + This invalid object is equivalent to ValueTree created with its default constructor, but + you should always prefer to avoid it and use ValueTree() or {} instead. */ static const ValueTree invalid; + #endif /** Returns the total number of references to the shared underlying data structure that this ValueTree is using. diff --git a/source/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp b/source/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp index b422a1cb6..3890f8a8b 100644 --- a/source/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp +++ b/source/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp @@ -33,13 +33,13 @@ InterprocessConnectionServer::~InterprocessConnectionServer() } //============================================================================== -bool InterprocessConnectionServer::beginWaitingForSocket (const int portNumber) +bool InterprocessConnectionServer::beginWaitingForSocket (const int portNumber, const String& bindAddress) { stop(); socket = new StreamingSocket(); - if (socket->createListener (portNumber)) + if (socket->createListener (portNumber, bindAddress)) { startThread(); return true; diff --git a/source/modules/juce_events/interprocess/juce_InterprocessConnectionServer.h b/source/modules/juce_events/interprocess/juce_InterprocessConnectionServer.h index 32350837b..01bee327d 100644 --- a/source/modules/juce_events/interprocess/juce_InterprocessConnectionServer.h +++ b/source/modules/juce_events/interprocess/juce_InterprocessConnectionServer.h @@ -57,9 +57,16 @@ public: Use stop() to stop the thread running. + @param portNumber The port on which the server will receive + connections + @param bindAddress The address on which the server will listen + for connections. An empty string indicates + that it should listen on all addresses + assigned to this machine. + @see createConnectionObject, stop */ - bool beginWaitingForSocket (int portNumber); + bool beginWaitingForSocket (int portNumber, const String& bindAddress = String()); /** Terminates the listener thread, if it's active. diff --git a/source/modules/juce_events/juce_events.cpp b/source/modules/juce_events/juce_events.cpp index 5514be444..0d5ee8886 100644 --- a/source/modules/juce_events/juce_events.cpp +++ b/source/modules/juce_events/juce_events.cpp @@ -31,6 +31,8 @@ #error "Incorrect use of JUCE cpp file" #endif +#include "AppConfig.h" + #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 diff --git a/source/modules/juce_events/juce_events.h b/source/modules/juce_events/juce_events.h index 637e8623f..93a12e06e 100644 --- a/source/modules/juce_events/juce_events.h +++ b/source/modules/juce_events/juce_events.h @@ -22,11 +22,36 @@ ============================================================================== */ +/******************************************************************************* + The block below describes the properties of this module, and is read by + the Projucer to automatically generate project code that uses it. + For details about the syntax and how to create or use a module, see the + JUCE Module Format.txt file. + + + BEGIN_JUCE_MODULE_DECLARATION + + ID: juce_events + vendor: juce + version: 4.3.0 + name: JUCE message and event handling classes + description: Classes for running an application's main event loop and sending/receiving messages, timers, etc. + website: http://www.juce.com/juce + license: GPL/Commercial + + dependencies: juce_core + linuxPackages: x11 + + END_JUCE_MODULE_DECLARATION + +*******************************************************************************/ + + #ifndef JUCE_EVENTS_H_INCLUDED #define JUCE_EVENTS_H_INCLUDED //============================================================================== -#include "../juce_core/juce_core.h" +#include namespace juce { diff --git a/source/modules/juce_events/messages/juce_ApplicationBase.cpp b/source/modules/juce_events/messages/juce_ApplicationBase.cpp index 66cd07f4b..5a2e3c374 100644 --- a/source/modules/juce_events/messages/juce_ApplicationBase.cpp +++ b/source/modules/juce_events/messages/juce_ApplicationBase.cpp @@ -257,6 +257,20 @@ bool JUCEApplicationBase::initialiseApp() } #endif + #if JUCE_WINDOWS && JUCE_STANDALONE_APPLICATION && (! defined (_CONSOLE)) && (! JUCE_MINGW) + if (AttachConsole (ATTACH_PARENT_PROCESS) != 0) + { + // if we've launched a GUI app from cmd.exe or PowerShell, we need this to enable printf etc. + // However, only reassign stdout, stderr, stdin if they have not been already opened by + // a redirect or similar. + FILE* ignore; + + if (_fileno(stdout) < 0) freopen_s (&ignore, "CONOUT$", "w", stdout); + if (_fileno(stderr) < 0) freopen_s (&ignore, "CONOUT$", "w", stderr); + if (_fileno(stdin) < 0) freopen_s (&ignore, "CONIN$", "r", stdin); + } + #endif + // let the app do its setting-up.. initialise (getCommandLineParameters()); diff --git a/source/modules/juce_events/native/juce_ios_MessageManager.mm b/source/modules/juce_events/native/juce_ios_MessageManager.mm index 998cace30..b633c2022 100644 --- a/source/modules/juce_events/native/juce_ios_MessageManager.mm +++ b/source/modules/juce_events/native/juce_ios_MessageManager.mm @@ -38,7 +38,9 @@ void MessageManager::runDispatchLoop() void MessageManager::stopDispatchLoop() { - [[[UIApplication sharedApplication] delegate] applicationWillTerminate: [UIApplication sharedApplication]]; + if (! SystemStats::isRunningInAppExtensionSandbox()) + [[[UIApplication sharedApplication] delegate] applicationWillTerminate: [UIApplication sharedApplication]]; + exit (0); // iOS apps get no mercy.. } diff --git a/source/modules/juce_events/native/juce_linux_Messaging.cpp b/source/modules/juce_events/native/juce_linux_Messaging.cpp index f90199a12..770a9aadc 100644 --- a/source/modules/juce_events/native/juce_linux_Messaging.cpp +++ b/source/modules/juce_events/native/juce_linux_Messaging.cpp @@ -346,7 +346,6 @@ void MessageManager::doPlatformSpecificShutdown() if (display != nullptr && ! LinuxErrorHandling::errorOccurred) { XDestroyWindow (display, juce_messageWindowHandle); - XCloseDisplay (display); juce_messageWindowHandle = 0; display = nullptr; @@ -357,13 +356,13 @@ void MessageManager::doPlatformSpecificShutdown() bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) { - if (LinuxErrorHandling::errorOccurred) - return false; - - if (InternalMessageQueue* const queue = InternalMessageQueue::getInstanceWithoutCreating()) + if (! LinuxErrorHandling::errorOccurred) { - queue->postMessage (message); - return true; + if (InternalMessageQueue* queue = InternalMessageQueue::getInstanceWithoutCreating()) + { + queue->postMessage (message); + return true; + } } return false; @@ -389,16 +388,16 @@ bool MessageManager::dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMes break; } - InternalMessageQueue* const queue = InternalMessageQueue::getInstanceWithoutCreating(); - jassert (queue != nullptr); - - if (queue->dispatchNextEvent()) - return true; + if (InternalMessageQueue* queue = InternalMessageQueue::getInstanceWithoutCreating()) + { + if (queue->dispatchNextEvent()) + return true; - if (returnIfNoPendingMessages) - break; + if (returnIfNoPendingMessages) + break; - queue->sleepUntilEvent (2000); + queue->sleepUntilEvent (2000); + } } return false; diff --git a/source/modules/juce_events/native/juce_mac_MessageManager.mm b/source/modules/juce_events/native/juce_mac_MessageManager.mm index b63282759..582540071 100644 --- a/source/modules/juce_events/native/juce_mac_MessageManager.mm +++ b/source/modules/juce_events/native/juce_mac_MessageManager.mm @@ -356,8 +356,8 @@ void MessageManager::broadcastMessage (const String& message) } // Special function used by some plugin classes to re-post carbon events -void repostCurrentNSEvent(); -void repostCurrentNSEvent() +void __attribute__ ((visibility("default"))) repostCurrentNSEvent(); +void __attribute__ ((visibility("default"))) repostCurrentNSEvent() { struct EventReposter : public CallbackMessage { diff --git a/source/modules/juce_events/timers/juce_Timer.h b/source/modules/juce_events/timers/juce_Timer.h index a6670e3fc..228e08653 100644 --- a/source/modules/juce_events/timers/juce_Timer.h +++ b/source/modules/juce_events/timers/juce_Timer.h @@ -97,11 +97,13 @@ public: /** Stops the timer. - No more callbacks will be made after this method returns. + No more timer callbacks will be triggered after this method returns. - If this is called from a different thread, any callbacks that may - be currently executing may be allowed to finish before the method - returns. + Note that if you call this from a background thread while the message-thread + is already in the middle of your callback, then this method will cancel any + future timer callbacks, but it will return without waiting for the current one + to finish. The current callback will continue, possibly still running some of + your timer code after this method has returned. */ void stopTimer() noexcept; diff --git a/source/modules/juce_graphics/colour/juce_ColourGradient.cpp b/source/modules/juce_graphics/colour/juce_ColourGradient.cpp index 59b87f3f3..e06e9970b 100644 --- a/source/modules/juce_graphics/colour/juce_ColourGradient.cpp +++ b/source/modules/juce_graphics/colour/juce_ColourGradient.cpp @@ -70,7 +70,13 @@ int ColourGradient::addColour (const double proportionAlongGradient, Colour colo // must be within the two end-points jassert (proportionAlongGradient >= 0 && proportionAlongGradient <= 1.0); - const double pos = jlimit (0.0, 1.0, proportionAlongGradient); + if (proportionAlongGradient <= 0) + { + colours.set (0, ColourPoint (0.0, colour)); + return 0; + } + + const double pos = jmin (1.0, proportionAlongGradient); int i; for (i = 0; i < colours.size(); ++i) diff --git a/source/modules/juce_graphics/colour/juce_FillType.cpp b/source/modules/juce_graphics/colour/juce_FillType.cpp index 34d6da9c6..6c166689e 100644 --- a/source/modules/juce_graphics/colour/juce_FillType.cpp +++ b/source/modules/juce_graphics/colour/juce_FillType.cpp @@ -104,7 +104,7 @@ bool FillType::operator!= (const FillType& other) const void FillType::setColour (Colour newColour) noexcept { gradient = nullptr; - image = Image::null; + image = Image(); colour = newColour; } @@ -116,7 +116,7 @@ void FillType::setGradient (const ColourGradient& newGradient) } else { - image = Image::null; + image = Image(); gradient = new ColourGradient (newGradient); colour = Colours::black; } diff --git a/source/modules/juce_graphics/contexts/juce_GraphicsContext.cpp b/source/modules/juce_graphics/contexts/juce_GraphicsContext.cpp index 2efb3aa99..4f3df5245 100644 --- a/source/modules/juce_graphics/contexts/juce_GraphicsContext.cpp +++ b/source/modules/juce_graphics/contexts/juce_GraphicsContext.cpp @@ -25,7 +25,7 @@ namespace { template - Rectangle coordsToRectangle (Type x, Type y, Type w, Type h) + Rectangle coordsToRectangle (Type x, Type y, Type w, Type h) noexcept { #if JUCE_DEBUG const int maxVal = 0x3fffffff; @@ -77,7 +77,7 @@ bool Graphics::isVectorDevice() const return context.isVectorDevice(); } -bool Graphics::reduceClipRegion (const Rectangle& area) +bool Graphics::reduceClipRegion (Rectangle area) { saveStateIfPending(); return context.clipToRectangle (area); @@ -85,7 +85,7 @@ bool Graphics::reduceClipRegion (const Rectangle& area) bool Graphics::reduceClipRegion (const int x, const int y, const int w, const int h) { - return reduceClipRegion (Rectangle (x, y, w, h)); + return reduceClipRegion (coordsToRectangle (x, y, w, h)); } bool Graphics::reduceClipRegion (const RectangleList& clipRegion) @@ -108,7 +108,7 @@ bool Graphics::reduceClipRegion (const Image& image, const AffineTransform& tran return ! context.isClipEmpty(); } -void Graphics::excludeClipRegion (const Rectangle& rectangleToExclude) +void Graphics::excludeClipRegion (Rectangle rectangleToExclude) { saveStateIfPending(); context.excludeClipRectangle (rectangleToExclude); @@ -164,7 +164,7 @@ void Graphics::addTransform (const AffineTransform& transform) context.addTransform (transform); } -bool Graphics::clipRegionIntersects (const Rectangle& area) const +bool Graphics::clipRegionIntersects (Rectangle area) const { return context.clipRegionIntersects (area); } @@ -281,7 +281,7 @@ void Graphics::drawMultiLineText (const String& text, const int startX, } } -void Graphics::drawText (const String& text, const Rectangle& area, +void Graphics::drawText (const String& text, Rectangle area, Justification justificationType, bool useEllipsesIfTooBig) const { if (text.isNotEmpty() && context.clipRegionIntersects (area.getSmallestIntegerContainer())) @@ -297,19 +297,19 @@ void Graphics::drawText (const String& text, const Rectangle& area, } } -void Graphics::drawText (const String& text, const Rectangle& area, +void Graphics::drawText (const String& text, Rectangle area, Justification justificationType, bool useEllipsesIfTooBig) const { drawText (text, area.toFloat(), justificationType, useEllipsesIfTooBig); } -void Graphics::drawText (const String& text, const int x, const int y, const int width, const int height, +void Graphics::drawText (const String& text, int x, int y, int width, int height, Justification justificationType, const bool useEllipsesIfTooBig) const { - drawText (text, Rectangle (x, y, width, height), justificationType, useEllipsesIfTooBig); + drawText (text, coordsToRectangle (x, y, width, height), justificationType, useEllipsesIfTooBig); } -void Graphics::drawFittedText (const String& text, const Rectangle& area, +void Graphics::drawFittedText (const String& text, Rectangle area, Justification justification, const int maximumNumberOfLines, const float minimumHorizontalScale) const @@ -328,7 +328,7 @@ void Graphics::drawFittedText (const String& text, const Rectangle& area, } } -void Graphics::drawFittedText (const String& text, const int x, const int y, const int width, const int height, +void Graphics::drawFittedText (const String& text, int x, int y, int width, int height, Justification justification, const int maximumNumberOfLines, const float minimumHorizontalScale) const @@ -338,12 +338,12 @@ void Graphics::drawFittedText (const String& text, const int x, const int y, con } //============================================================================== -void Graphics::fillRect (const Rectangle& r) const +void Graphics::fillRect (Rectangle r) const { context.fillRect (r, false); } -void Graphics::fillRect (const Rectangle& r) const +void Graphics::fillRect (Rectangle r) const { context.fillRect (r); } @@ -371,7 +371,7 @@ void Graphics::fillRectList (const RectangleList& rects) const void Graphics::setPixel (int x, int y) const { - context.fillRect (Rectangle (x, y, 1, 1), false); + context.fillRect (coordsToRectangle (x, y, 1, 1), false); } void Graphics::fillAll() const @@ -426,7 +426,7 @@ void Graphics::drawRect (int x, int y, int width, int height, int lineThickness) drawRect (coordsToRectangle (x, y, width, height), lineThickness); } -void Graphics::drawRect (const Rectangle& r, int lineThickness) const +void Graphics::drawRect (Rectangle r, int lineThickness) const { drawRect (r.toFloat(), (float) lineThickness); } @@ -444,7 +444,7 @@ void Graphics::drawRect (Rectangle r, const float lineThickness) const } //============================================================================== -void Graphics::fillEllipse (const Rectangle& area) const +void Graphics::fillEllipse (Rectangle area) const { Path p; p.addEllipse (area); @@ -453,19 +453,31 @@ void Graphics::fillEllipse (const Rectangle& area) const void Graphics::fillEllipse (float x, float y, float w, float h) const { - fillEllipse (Rectangle (x, y, w, h)); + fillEllipse (coordsToRectangle (x, y, w, h)); } void Graphics::drawEllipse (float x, float y, float width, float height, float lineThickness) const { - Path p; - p.addEllipse (x, y, width, height); - strokePath (p, PathStrokeType (lineThickness)); + drawEllipse (coordsToRectangle (x, y, width, height), lineThickness); } -void Graphics::drawEllipse (const Rectangle& area, float lineThickness) const +void Graphics::drawEllipse (Rectangle area, float lineThickness) const { - drawEllipse (area.getX(), area.getY(), area.getWidth(), area.getHeight(), lineThickness); + Path p; + + if (area.getWidth() == area.getHeight()) + { + // For a circle, we can avoid having to generate a stroke + p.addEllipse (area.expanded (lineThickness * 0.5f)); + p.addEllipse (area.reduced (lineThickness * 0.5f)); + p.setUsingNonZeroWinding (false); + fillPath (p); + } + else + { + p.addEllipse (area); + strokePath (p, PathStrokeType (lineThickness)); + } } void Graphics::fillRoundedRectangle (float x, float y, float width, float height, float cornerSize) const @@ -473,7 +485,7 @@ void Graphics::fillRoundedRectangle (float x, float y, float width, float height fillRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize); } -void Graphics::fillRoundedRectangle (const Rectangle& r, const float cornerSize) const +void Graphics::fillRoundedRectangle (Rectangle r, const float cornerSize) const { Path p; p.addRoundedRectangle (r, cornerSize); @@ -486,7 +498,7 @@ void Graphics::drawRoundedRectangle (float x, float y, float width, float height drawRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize, lineThickness); } -void Graphics::drawRoundedRectangle (const Rectangle& r, float cornerSize, float lineThickness) const +void Graphics::drawRoundedRectangle (Rectangle r, float cornerSize, float lineThickness) const { Path p; p.addRoundedRectangle (r, cornerSize); @@ -500,7 +512,7 @@ void Graphics::drawArrow (const Line& line, float lineThickness, float ar fillPath (p); } -void Graphics::fillCheckerBoard (const Rectangle& area, +void Graphics::fillCheckerBoard (Rectangle area, const int checkWidth, const int checkHeight, Colour colour1, Colour colour2) const { @@ -630,18 +642,22 @@ void Graphics::drawImageAt (const Image& imageToDraw, int x, int y, bool fillAlp fillAlphaChannel); } -void Graphics::drawImageWithin (const Image& imageToDraw, - int dx, int dy, int dw, int dh, - RectanglePlacement placementWithinTarget, - const bool fillAlphaChannelWithCurrentBrush) const +void Graphics::drawImage (const Image& imageToDraw, Rectangle targetArea, + RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush) const { if (imageToDraw.isValid()) drawImageTransformed (imageToDraw, - placementWithinTarget.getTransformToFit (imageToDraw.getBounds().toFloat(), - coordsToRectangle (dx, dy, dw, dh).toFloat()), + placementWithinTarget.getTransformToFit (imageToDraw.getBounds().toFloat(), targetArea), fillAlphaChannelWithCurrentBrush); } +void Graphics::drawImageWithin (const Image& imageToDraw, int dx, int dy, int dw, int dh, + RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush) const +{ + drawImage (imageToDraw, coordsToRectangle (dx, dy, dw, dh).toFloat(), + placementWithinTarget, fillAlphaChannelWithCurrentBrush); +} + void Graphics::drawImage (const Image& imageToDraw, int dx, int dy, int dw, int dh, int sx, int sy, int sw, int sh, diff --git a/source/modules/juce_graphics/contexts/juce_GraphicsContext.h b/source/modules/juce_graphics/contexts/juce_GraphicsContext.h index 3cfcc8cf6..cb9f92e8d 100644 --- a/source/modules/juce_graphics/contexts/juce_GraphicsContext.h +++ b/source/modules/juce_graphics/contexts/juce_GraphicsContext.h @@ -170,7 +170,7 @@ public: @see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText */ void drawText (const String& text, - const Rectangle& area, + Rectangle area, Justification justificationType, bool useEllipsesIfTooBig = true) const; @@ -184,7 +184,7 @@ public: @see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText */ void drawText (const String& text, - const Rectangle& area, + Rectangle area, Justification justificationType, bool useEllipsesIfTooBig = true) const; @@ -233,7 +233,7 @@ public: @see GlyphArrangement::addFittedText */ void drawFittedText (const String& text, - const Rectangle& area, + Rectangle area, Justification justificationFlags, int maximumNumberOfLines, float minimumHorizontalScale = 0.0f) const; @@ -257,12 +257,12 @@ public: /** Fills a rectangle with the current colour or brush. @see drawRect, fillRoundedRectangle */ - void fillRect (const Rectangle& rectangle) const; + void fillRect (Rectangle rectangle) const; /** Fills a rectangle with the current colour or brush. @see drawRect, fillRoundedRectangle */ - void fillRect (const Rectangle& rectangle) const; + void fillRect (Rectangle rectangle) const; /** Fills a rectangle with the current colour or brush. @see drawRect, fillRoundedRectangle @@ -297,11 +297,11 @@ public: /** Uses the current colour or brush to fill a rectangle with rounded corners. @see drawRoundedRectangle, Path::addRoundedRectangle */ - void fillRoundedRectangle (const Rectangle& rectangle, + void fillRoundedRectangle (Rectangle rectangle, float cornerSize) const; /** Fills a rectangle with a checkerboard pattern, alternating between two colours. */ - void fillCheckerBoard (const Rectangle& area, + void fillCheckerBoard (Rectangle area, int checkWidth, int checkHeight, Colour colour1, Colour colour2) const; @@ -321,7 +321,7 @@ public: The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards. @see fillRect */ - void drawRect (const Rectangle& rectangle, int lineThickness = 1) const; + void drawRect (Rectangle rectangle, int lineThickness = 1) const; /** Draws a rectangular outline, using the current colour or brush. The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards. @@ -338,7 +338,7 @@ public: /** Uses the current colour or brush to draw the outline of a rectangle with rounded corners. @see fillRoundedRectangle, Path::addRoundedRectangle */ - void drawRoundedRectangle (const Rectangle& rectangle, + void drawRoundedRectangle (Rectangle rectangle, float cornerSize, float lineThickness) const; /** Fills a 1x1 pixel using the current colour or brush. @@ -358,7 +358,7 @@ public: The ellipse is drawn to fit inside the given rectangle. @see drawEllipse, Path::addEllipse */ - void fillEllipse (const Rectangle& area) const; + void fillEllipse (Rectangle area) const; /** Draws an elliptical stroke using the current colour or brush. @see fillEllipse, Path::addEllipse @@ -369,7 +369,7 @@ public: /** Draws an elliptical stroke using the current colour or brush. @see fillEllipse, Path::addEllipse */ - void drawEllipse (const Rectangle& area, float lineThickness) const; + void drawEllipse (Rectangle area, float lineThickness) const; //============================================================================== /** Draws a line between two points. @@ -462,13 +462,13 @@ public: //============================================================================== /** Types of rendering quality that can be specified when drawing images. - @see blendImage, Graphics::setImageResamplingQuality + @see Graphics::setImageResamplingQuality */ enum ResamplingQuality { lowResamplingQuality = 0, /**< Just uses a nearest-neighbour algorithm for resampling. */ mediumResamplingQuality = 1, /**< Uses bilinear interpolation for upsampling and area-averaging for downsampling. */ - highResamplingQuality = 2 /**< Uses bicubic interpolation for upsampling and area-averaging for downsampling. */ + highResamplingQuality = 2, /**< Uses bicubic interpolation for upsampling and area-averaging for downsampling. */ }; /** Changes the quality that will be used when resampling images. @@ -542,6 +542,23 @@ public: const AffineTransform& transform, bool fillAlphaChannelWithCurrentBrush = false) const; + /** Draws an image to fit within a designated rectangle. + + @param imageToDraw the source image to draw + @param targetArea the target rectangle to fit it into + @param placementWithinTarget this specifies how the image should be positioned + within the target rectangle - see the RectanglePlacement + class for more details about this. + @param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the image, just its + alpha channel will be used as a mask with which to + draw with the current brush or colour. This is + similar to fillAlphaMap(), and see also drawImage() + @see drawImage, drawImageTransformed, drawImageAt, RectanglePlacement + */ + void drawImage (const Image& imageToDraw, Rectangle targetArea, + RectanglePlacement placementWithinTarget = RectanglePlacement::stretchToFit, + bool fillAlphaChannelWithCurrentBrush = false) const; + /** Draws an image to fit within a designated rectangle. If the image is too big or too small for the space, it will be rescaled @@ -568,7 +585,6 @@ public: RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush = false) const; - //============================================================================== /** Returns the position of the bounding box for the current clipping region. @see getClipRegion, clipRegionIntersects @@ -581,7 +597,7 @@ public: method can be used to optimise a component's paint() method, by letting it avoid drawing complex objects that aren't within the region being repainted. */ - bool clipRegionIntersects (const Rectangle& area) const; + bool clipRegionIntersects (Rectangle area) const; /** Intersects the current clipping region with another region. @@ -595,7 +611,7 @@ public: @returns true if the resulting clipping region is non-zero in size @see setOrigin, clipRegionIntersects */ - bool reduceClipRegion (const Rectangle& area); + bool reduceClipRegion (Rectangle area); /** Intersects the current clipping region with a rectangle list region. @@ -625,7 +641,7 @@ public: bool reduceClipRegion (const Image& image, const AffineTransform& transform); /** Excludes a rectangle to stop it being drawn into. */ - void excludeClipRegion (const Rectangle& rectangleToExclude); + void excludeClipRegion (Rectangle rectangleToExclude); /** Returns true if no drawing can be done because the clip region is zero. */ bool isClipEmpty() const; diff --git a/source/modules/juce_graphics/fonts/juce_CustomTypeface.h b/source/modules/juce_graphics/fonts/juce_CustomTypeface.h index 1d7183453..ff365fd66 100644 --- a/source/modules/juce_graphics/fonts/juce_CustomTypeface.h +++ b/source/modules/juce_graphics/fonts/juce_CustomTypeface.h @@ -38,7 +38,7 @@ NOTE! For most people this class is almost certainly NOT the right tool to use! If what you want to do is to embed a font into your exe, then your best plan is - probably to embed your TTF/OTF font file into your binary using the Introjucer, + probably to embed your TTF/OTF font file into your binary using the Projucer, and then call Typeface::createSystemTypefaceFor() to load it from memory. @see Typeface, Font diff --git a/source/modules/juce_graphics/fonts/juce_GlyphArrangement.h b/source/modules/juce_graphics/fonts/juce_GlyphArrangement.h index 6f4243dcc..3e3def239 100644 --- a/source/modules/juce_graphics/fonts/juce_GlyphArrangement.h +++ b/source/modules/juce_graphics/fonts/juce_GlyphArrangement.h @@ -195,7 +195,7 @@ public: float maxLineWidth, Justification horizontalLayout); - /** Tries to fit some text withing a given space. + /** Tries to fit some text within a given space. This does its best to make the given text readable within the specified rectangle, so it useful for labelling things. diff --git a/source/modules/juce_graphics/fonts/juce_TextLayout.cpp b/source/modules/juce_graphics/fonts/juce_TextLayout.cpp index 2418eefc1..5d7a4524d 100644 --- a/source/modules/juce_graphics/fonts/juce_TextLayout.cpp +++ b/source/modules/juce_graphics/fonts/juce_TextLayout.cpp @@ -242,7 +242,7 @@ void TextLayout::createLayout (const AttributedString& text, float maxWidth, flo if (! createNativeLayout (text)) createStandardLayout (text); - recalculateSize (text); + recalculateSize(); } void TextLayout::createLayoutWithBalancedLineLengths (const AttributedString& text, float maxWidth) @@ -540,7 +540,7 @@ namespace TextLayoutHelpers static String getTrimmedEndIfNotAllWhitespace (const String& s) { String trimmed (s.trimEnd()); - if (trimmed.isEmpty() && ! s.isEmpty()) + if (trimmed.isEmpty() && s.isNotEmpty()) trimmed = s.replaceCharacters ("\r\n\t", " "); return trimmed; @@ -560,9 +560,9 @@ void TextLayout::createStandardLayout (const AttributedString& text) l.createLayout (text, *this); } -void TextLayout::recalculateSize (const AttributedString& text) +void TextLayout::recalculateSize() { - if (lines.size() > 0 && text.getReadingDirection() != AttributedString::rightToLeft) + if (lines.size() > 0) { Rectangle bounds (lines.getFirst()->getLineBounds()); diff --git a/source/modules/juce_graphics/fonts/juce_TextLayout.h b/source/modules/juce_graphics/fonts/juce_TextLayout.h index 0aaa073c3..08c5ca182 100644 --- a/source/modules/juce_graphics/fonts/juce_TextLayout.h +++ b/source/modules/juce_graphics/fonts/juce_TextLayout.h @@ -188,7 +188,7 @@ private: void createStandardLayout (const AttributedString&); bool createNativeLayout (const AttributedString&); - void recalculateSize (const AttributedString&); + void recalculateSize(); JUCE_LEAK_DETECTOR (TextLayout) }; diff --git a/source/modules/juce_graphics/geometry/juce_AffineTransform.cpp b/source/modules/juce_graphics/geometry/juce_AffineTransform.cpp index a4c1a7e49..f58fe3156 100644 --- a/source/modules/juce_graphics/geometry/juce_AffineTransform.cpp +++ b/source/modules/juce_graphics/geometry/juce_AffineTransform.cpp @@ -79,7 +79,9 @@ bool AffineTransform::isIdentity() const noexcept && (mat11 == 1.0f); } +#if JUCE_ALLOW_STATIC_NULL_VARIABLES const AffineTransform AffineTransform::identity; +#endif //============================================================================== AffineTransform AffineTransform::followedBy (const AffineTransform& other) const noexcept diff --git a/source/modules/juce_graphics/geometry/juce_AffineTransform.h b/source/modules/juce_graphics/geometry/juce_AffineTransform.h index 77bb8ab7b..7562aedbe 100644 --- a/source/modules/juce_graphics/geometry/juce_AffineTransform.h +++ b/source/modules/juce_graphics/geometry/juce_AffineTransform.h @@ -67,15 +67,13 @@ public: /** Compares two transforms. */ bool operator!= (const AffineTransform& other) const noexcept; - /** A ready-to-use identity transform, which you can use to append other - transformations to. - - e.g. @code - AffineTransform myTransform = AffineTransform().rotated (.5f) - .scaled (2.0f); - @endcode + #if JUCE_ALLOW_STATIC_NULL_VARIABLES + /** A ready-to-use identity transform. + Note that you should always avoid using a static variable like this, and + prefer AffineTransform() or {} if you need a default-constructed instance. */ static const AffineTransform identity; + #endif //============================================================================== /** Transforms a 2D coordinate using this matrix. */ diff --git a/source/modules/juce_graphics/geometry/juce_Line.h b/source/modules/juce_graphics/geometry/juce_Line.h index a6dfadf37..3223f836f 100644 --- a/source/modules/juce_graphics/geometry/juce_Line.h +++ b/source/modules/juce_graphics/geometry/juce_Line.h @@ -50,23 +50,20 @@ public: /** Creates a copy of another line. */ Line (const Line& other) noexcept - : start (other.start), - end (other.end) + : start (other.start), end (other.end) { } /** Creates a line based on the coordinates of its start and end points. */ Line (ValueType startX, ValueType startY, ValueType endX, ValueType endY) noexcept - : start (startX, startY), - end (endX, endY) + : start (startX, startY), end (endX, endY) { } /** Creates a line from its start and end points. */ - Line (const Point startPoint, - const Point endPoint) noexcept - : start (startPoint), - end (endPoint) + Line (Point startPoint, + Point endPoint) noexcept + : start (startPoint), end (endPoint) { } @@ -113,7 +110,7 @@ public: void setEnd (const Point newEnd) noexcept { end = newEnd; } /** Returns a line that is the same as this one, but with the start and end reversed, */ - const Line reversed() const noexcept { return Line (end, start); } + Line reversed() const noexcept { return Line (end, start); } /** Applies an affine transform to the line's start and end points. */ void applyTransform (const AffineTransform& transform) noexcept @@ -142,6 +139,17 @@ public: */ typename Point::FloatType getAngle() const noexcept { return start.getAngleToPoint (end); } + /** Creates a line from a start point, length and angle. + + This angle is the number of radians clockwise from the 12 o'clock direction, + where the line's start point is considered to be at the centre. + */ + static Line fromStartAndAngle (Point startPoint, ValueType length, ValueType angle) noexcept + { + return Line (startPoint, startPoint.getPointOnCircumference (length, angle)); + } + + //============================================================================== /** Casts this line to float coordinates. */ Line toFloat() const noexcept { return Line (start.toFloat(), end.toFloat()); } @@ -150,10 +158,10 @@ public: //============================================================================== /** Compares two lines. */ - bool operator== (const Line& other) const noexcept { return start == other.start && end == other.end; } + bool operator== (Line other) const noexcept { return start == other.start && end == other.end; } /** Compares two lines. */ - bool operator!= (const Line& other) const noexcept { return start != other.start || end != other.end; } + bool operator!= (Line other) const noexcept { return start != other.start || end != other.end; } //============================================================================== /** Finds the intersection between two lines. @@ -161,7 +169,7 @@ public: @param line the line to intersect with @returns the point at which the lines intersect, even if this lies beyond the end of the lines */ - Point getIntersection (const Line& line) const noexcept + Point getIntersection (Line line) const noexcept { Point p; findIntersection (start, end, line.start, line.end, p); @@ -180,13 +188,13 @@ public: don't intersect, the intersection coordinates returned will still be valid */ - bool intersects (const Line& line, Point& intersection) const noexcept + bool intersects (Line line, Point& intersection) const noexcept { return findIntersection (start, end, line.start, line.end, intersection); } /** Returns true if this line intersects another. */ - bool intersects (const Line& other) const noexcept + bool intersects (Line other) const noexcept { Point ignored; return findIntersection (start, end, other.start, other.end, ignored); @@ -257,7 +265,7 @@ public: @returns the point's distance from the line @see getPositionAlongLineOfNearestPoint */ - ValueType getDistanceFromPoint (const Point targetPoint, + ValueType getDistanceFromPoint (Point targetPoint, Point& pointOnLine) const noexcept { const Point delta (end - start); @@ -298,7 +306,7 @@ public: turn this number into a position, use getPointAlongLineProportionally(). @see getDistanceFromPoint, getPointAlongLineProportionally */ - ValueType findNearestProportionalPositionTo (const Point point) const noexcept + ValueType findNearestProportionalPositionTo (Point point) const noexcept { const Point delta (end - start); const double length = delta.x * delta.x + delta.y * delta.y; @@ -312,7 +320,7 @@ public: /** Finds the point on this line which is nearest to a given point. @see getDistanceFromPoint, findNearestProportionalPositionTo */ - Point findNearestPointTo (const Point point) const noexcept + Point findNearestPointTo (Point point) const noexcept { return getPointAlongLineProportionally (findNearestProportionalPositionTo (point)); } @@ -323,7 +331,7 @@ public: coordinate of this line at the given x (assuming the line extends infinitely in both directions). */ - bool isPointAbove (const Point point) const noexcept + bool isPointAbove (Point point) const noexcept { return start.x != end.x && point.y < ((end.y - start.y) @@ -380,19 +388,22 @@ private: intersection = p1.withX (p3.x + along * d2.x); return along >= 0 && along <= static_cast (1); } - else if (d2.y == 0 && d1.y != 0) + + if (d2.y == 0 && d1.y != 0) { const ValueType along = (p3.y - p1.y) / d1.y; intersection = p3.withX (p1.x + along * d1.x); return along >= 0 && along <= static_cast (1); } - else if (d1.x == 0 && d2.x != 0) + + if (d1.x == 0 && d2.x != 0) { const ValueType along = (p1.x - p3.x) / d2.x; intersection = p1.withY (p3.y + along * d2.y); return along >= 0 && along <= static_cast (1); } - else if (d2.x == 0 && d1.x != 0) + + if (d2.x == 0 && d1.x != 0) { const ValueType along = (p3.x - p1.x) / d1.x; intersection = p3.withY (p1.y + along * d1.y); diff --git a/source/modules/juce_graphics/geometry/juce_Path.cpp b/source/modules/juce_graphics/geometry/juce_Path.cpp index 7eecd6621..a4ff1e178 100644 --- a/source/modules/juce_graphics/geometry/juce_Path.cpp +++ b/source/modules/juce_graphics/geometry/juce_Path.cpp @@ -60,6 +60,9 @@ const float Path::quadMarker = 100003.0f; const float Path::cubicMarker = 100004.0f; const float Path::closeSubPathMarker = 100005.0f; +const float Path::defaultToleranceForTesting = 1.0f; +const float Path::defaultToleranceForMeasurement = 0.6f; + //============================================================================== Path::PathBounds::PathBounds() noexcept : pathXMin (0), pathXMax (0), pathYMin (0), pathYMax (0) @@ -1060,7 +1063,7 @@ bool Path::contains (const Point point, const float tolerance) const return contains (point.x, point.y, tolerance); } -bool Path::intersectsLine (const Line& line, const float tolerance) +bool Path::intersectsLine (Line line, const float tolerance) { PathFlatteningIterator i (*this, AffineTransform(), tolerance); Point intersection; @@ -1072,7 +1075,7 @@ bool Path::intersectsLine (const Line& line, const float tolerance) return false; } -Line Path::getClippedLine (const Line& line, const bool keepSectionOutsidePath) const +Line Path::getClippedLine (Line line, const bool keepSectionOutsidePath) const { Line result (line); const bool startInside = contains (line.getStart()); @@ -1103,10 +1106,10 @@ Line Path::getClippedLine (const Line& line, const bool keepSectio return result; } -float Path::getLength (const AffineTransform& transform) const +float Path::getLength (const AffineTransform& transform, float tolerance) const { float length = 0; - PathFlatteningIterator i (*this, transform); + PathFlatteningIterator i (*this, transform, tolerance); while (i.next()) length += Line (i.x1, i.y1, i.x2, i.y2).getLength(); @@ -1114,9 +1117,11 @@ float Path::getLength (const AffineTransform& transform) const return length; } -Point Path::getPointAlongPath (float distanceFromStart, const AffineTransform& transform) const +Point Path::getPointAlongPath (float distanceFromStart, + const AffineTransform& transform, + float tolerance) const { - PathFlatteningIterator i (*this, transform); + PathFlatteningIterator i (*this, transform, tolerance); while (i.next()) { @@ -1133,9 +1138,10 @@ Point Path::getPointAlongPath (float distanceFromStart, const AffineTrans } float Path::getNearestPoint (const Point targetPoint, Point& pointOnPath, - const AffineTransform& transform) const + const AffineTransform& transform, + float tolerance) const { - PathFlatteningIterator i (*this, transform); + PathFlatteningIterator i (*this, transform, tolerance); float bestPosition = 0, bestDistance = std::numeric_limits::max(); float length = 0; Point pointOnLine; diff --git a/source/modules/juce_graphics/geometry/juce_Path.h b/source/modules/juce_graphics/geometry/juce_Path.h index 215314f2d..6ddca5dd1 100644 --- a/source/modules/juce_graphics/geometry/juce_Path.h +++ b/source/modules/juce_graphics/geometry/juce_Path.h @@ -83,12 +83,14 @@ public: bool operator== (const Path&) const noexcept; bool operator!= (const Path&) const noexcept; + static const float defaultToleranceForTesting; + static const float defaultToleranceForMeasurement; + //============================================================================== /** Returns true if the path doesn't contain any lines or curves. */ bool isEmpty() const noexcept; - /** Returns the smallest rectangle that contains all points within the path. - */ + /** Returns the smallest rectangle that contains all points within the path. */ Rectangle getBounds() const noexcept; /** Returns the smallest rectangle that contains all points within the path @@ -98,7 +100,7 @@ public: /** Checks whether a point lies within the path. - This is only relevent for closed paths (see closeSubPath()), and + This is only relevant for closed paths (see closeSubPath()), and may produce false results if used on a path which has open sub-paths. The path's winding rule is taken into account by this method. @@ -110,11 +112,11 @@ public: @see closeSubPath, setUsingNonZeroWinding */ bool contains (float x, float y, - float tolerance = 1.0f) const; + float tolerance = defaultToleranceForTesting) const; /** Checks whether a point lies within the path. - This is only relevent for closed paths (see closeSubPath()), and + This is only relevant for closed paths (see closeSubPath()), and may produce false results if used on a path which has open sub-paths. The path's winding rule is taken into account by this method. @@ -126,7 +128,7 @@ public: @see closeSubPath, setUsingNonZeroWinding */ bool contains (const Point point, - float tolerance = 1.0f) const; + float tolerance = defaultToleranceForTesting) const; /** Checks whether a line crosses the path. @@ -138,8 +140,8 @@ public: so this method could return a false positive when your point is up to this distance outside the path's boundary. */ - bool intersectsLine (const Line& line, - float tolerance = 1.0f); + bool intersectsLine (Line line, + float tolerance = defaultToleranceForTesting); /** Cuts off parts of a line to keep the parts that are either inside or outside this path. @@ -153,12 +155,13 @@ public: that will be kept; if false its the section inside the path */ - Line getClippedLine (const Line& line, bool keepSectionOutsidePath) const; + Line getClippedLine (Line line, bool keepSectionOutsidePath) const; /** Returns the length of the path. @see getPointAlongPath */ - float getLength (const AffineTransform& transform = AffineTransform()) const; + float getLength (const AffineTransform& transform = AffineTransform(), + float tolerance = defaultToleranceForMeasurement) const; /** Returns a point that is the specified distance along the path. If the distance is greater than the total length of the path, this will return the @@ -166,15 +169,17 @@ public: @see getLength */ Point getPointAlongPath (float distanceFromStart, - const AffineTransform& transform = AffineTransform()) const; + const AffineTransform& transform = AffineTransform(), + float tolerance = defaultToleranceForMeasurement) const; /** Finds the point along the path which is nearest to a given position. This sets pointOnPath to the nearest point, and returns the distance of this point from the start of the path. */ - float getNearestPoint (const Point targetPoint, + float getNearestPoint (Point targetPoint, Point& pointOnPath, - const AffineTransform& transform = AffineTransform()) const; + const AffineTransform& transform = AffineTransform(), + float tolerance = defaultToleranceForMeasurement) const; //============================================================================== /** Removes all lines and curves, resetting the path completely. */ diff --git a/source/modules/juce_graphics/geometry/juce_PathIterator.cpp b/source/modules/juce_graphics/geometry/juce_PathIterator.cpp index ce3c46310..76663ace0 100644 --- a/source/modules/juce_graphics/geometry/juce_PathIterator.cpp +++ b/source/modules/juce_graphics/geometry/juce_PathIterator.cpp @@ -26,8 +26,6 @@ #pragma optimize ("t", on) #endif -const float PathFlatteningIterator::defaultTolerance = 0.6f; - //============================================================================== PathFlatteningIterator::PathFlatteningIterator (const Path& path_, const AffineTransform& transform_, diff --git a/source/modules/juce_graphics/geometry/juce_PathIterator.h b/source/modules/juce_graphics/geometry/juce_PathIterator.h index 6994e28c3..0e0c5de79 100644 --- a/source/modules/juce_graphics/geometry/juce_PathIterator.h +++ b/source/modules/juce_graphics/geometry/juce_PathIterator.h @@ -53,7 +53,7 @@ public: */ PathFlatteningIterator (const Path& path, const AffineTransform& transform = AffineTransform(), - float tolerance = defaultTolerance); + float tolerance = Path::defaultToleranceForMeasurement); /** Destructor. */ ~PathFlatteningIterator(); @@ -90,9 +90,6 @@ public: /** Returns true if the current segment is the last in the current sub-path. */ bool isLastInSubpath() const noexcept; - /** This is the default value that should be used for the tolerance value (see the constructor parameters). */ - static const float defaultTolerance; - private: //============================================================================== const Path& path; diff --git a/source/modules/juce_graphics/geometry/juce_PathStrokeType.cpp b/source/modules/juce_graphics/geometry/juce_PathStrokeType.cpp index 1923f153d..f9a7fbc89 100644 --- a/source/modules/juce_graphics/geometry/juce_PathStrokeType.cpp +++ b/source/modules/juce_graphics/geometry/juce_PathStrokeType.cpp @@ -316,7 +316,7 @@ namespace PathStrokeHelpers if (style == PathStrokeType::square) { - // sqaure ends + // square ends destPath.lineTo (offx1, offy1); destPath.lineTo (offx2, offy2); destPath.lineTo (x2, y2); @@ -449,7 +449,7 @@ namespace PathStrokeHelpers { destPath.startNewSubPath (firstLine.rx2, firstLine.ry2); - if (arrowhead != nullptr) + if (arrowhead != nullptr && arrowhead->startWidth > 0.0f) addArrowhead (destPath, firstLine.rx2, firstLine.ry2, lastX1, lastY1, firstLine.x1, firstLine.y1, width, arrowhead->startWidth); else @@ -491,7 +491,7 @@ namespace PathStrokeHelpers { destPath.lineTo (lastX2, lastY2); - if (arrowhead != nullptr) + if (arrowhead != nullptr && arrowhead->endWidth > 0.0f) addArrowhead (destPath, lastX2, lastY2, lastLine.rx1, lastLine.ry1, lastLine.x2, lastLine.y2, width, arrowhead->endWidth); else @@ -570,7 +570,7 @@ namespace PathStrokeHelpers // Iterate the path, creating a list of the // left/right-hand lines along either side of it... - PathFlatteningIterator it (*sourcePath, transform, PathFlatteningIterator::defaultTolerance / extraAccuracy); + PathFlatteningIterator it (*sourcePath, transform, Path::defaultToleranceForMeasurement / extraAccuracy); Array subPath; subPath.ensureStorageAllocated (512); @@ -668,7 +668,7 @@ void PathStrokeType::createDashedStroke (Path& destPath, return; Path newDestPath; - PathFlatteningIterator it (sourcePath, transform, PathFlatteningIterator::defaultTolerance / extraAccuracy); + PathFlatteningIterator it (sourcePath, transform, Path::defaultToleranceForMeasurement / extraAccuracy); bool first = true; int dashNum = 0; @@ -680,9 +680,9 @@ void PathStrokeType::createDashedStroke (Path& destPath, const bool isSolid = ((dashNum & 1) == 0); const float dashLen = dashLengths [dashNum++ % numDashLengths]; - jassert (dashLen > 0); // must be a positive increment! + jassert (dashLen >= 0); // must be a positive increment! if (dashLen <= 0) - break; + continue; pos += dashLen; diff --git a/source/modules/juce_graphics/geometry/juce_Rectangle.h b/source/modules/juce_graphics/geometry/juce_Rectangle.h index d3f3e7b41..d37062c6d 100644 --- a/source/modules/juce_graphics/geometry/juce_Rectangle.h +++ b/source/modules/juce_graphics/geometry/juce_Rectangle.h @@ -538,6 +538,53 @@ public: return r; } + //============================================================================== + /** Returns the nearest point to the specified point that lies within this rectangle. */ + Point getConstrainedPoint (Point point) const noexcept + { + return Point (jlimit (pos.x, pos.x + w, point.x), + jlimit (pos.y, pos.y + h, point.y)); + } + + /** Returns a point within this rectangle, specified as proportional coordinates. + The relative X and Y values should be between 0 and 1, where 0 is the left or + top of this rectangle, and 1 is the right or bottom. (Out-of-bounds values + will return a point outside the rectangle). + */ + template + Point getRelativePoint (FloatType relativeX, FloatType relativeY) const noexcept + { + return Point (pos.x + static_cast (w * relativeX), + pos.y + static_cast (h * relativeY)); + } + + /** Returns a proportion of the width of this rectangle. */ + template + ValueType proportionOfWidth (FloatType proportion) const noexcept + { + return static_cast (w * proportion); + } + + /** Returns a proportion of the height of this rectangle. */ + template + ValueType proportionOfHeight (FloatType proportion) const noexcept + { + return static_cast (h * proportion); + } + + /** Returns a rectangle based on some proportional coordinates relative to this one. + So for example getProportion ({ 0.25f, 0.25f, 0.5f, 0.5f }) would return a rectangle + of half the original size, with the same centre. + */ + template + Rectangle getProportion (Rectangle proportionalRect) const noexcept + { + return Rectangle (pos.x + static_cast (w * proportionalRect.pos.x), + pos.y + static_cast (h * proportionalRect.pos.y), + proportionOfWidth (proportionalRect.w), + proportionOfHeight (proportionalRect.h)); + } + //============================================================================== /** Returns true if the two rectangles are identical. */ bool operator== (const Rectangle& other) const noexcept { return pos == other.pos && w == other.w && h == other.h; } @@ -564,24 +611,6 @@ public: && pos.x + w >= other.pos.x + other.w && pos.y + h >= other.pos.y + other.h; } - /** Returns the nearest point to the specified point that lies within this rectangle. */ - Point getConstrainedPoint (Point point) const noexcept - { - return Point (jlimit (pos.x, pos.x + w, point.x), - jlimit (pos.y, pos.y + h, point.y)); - } - - /** Returns a point within this rectangle, specified as proportional coordinates. - The relative X and Y values should be between 0 and 1, where 0 is the left or - top of this rectangle, and 1 is the right or bottom. (Out-of-bounds values - will return a point outside the rectangle). - */ - Point getRelativePoint (double relativeX, double relativeY) const noexcept - { - return Point (pos.x + static_cast (w * relativeX), - pos.y + static_cast (h * relativeY)); - } - /** Returns true if any part of another rectangle overlaps this one. */ bool intersects (const Rectangle& other) const noexcept { @@ -773,7 +802,7 @@ public: } /** Returns the smallest integer-aligned rectangle that completely contains this one. - This is only relevent for floating-point rectangles, of course. + This is only relevant for floating-point rectangles, of course. @see toFloat(), toNearestInt() */ Rectangle getSmallestIntegerContainer() const noexcept diff --git a/source/modules/juce_graphics/image_formats/juce_JPEGLoader.cpp b/source/modules/juce_graphics/image_formats/juce_JPEGLoader.cpp index ca01ff058..cf32f3a54 100644 --- a/source/modules/juce_graphics/image_formats/juce_JPEGLoader.cpp +++ b/source/modules/juce_graphics/image_formats/juce_JPEGLoader.cpp @@ -135,7 +135,7 @@ namespace JPEGHelpers { using namespace jpeglibNamespace; - #if ! JUCE_MSVC + #if ! (JUCE_WINDOWS && (JUCE_MSVC || JUCE_CLANG)) using jpeglibNamespace::boolean; #endif diff --git a/source/modules/juce_graphics/images/juce_Image.cpp b/source/modules/juce_graphics/images/juce_Image.cpp index cb51d5d88..4e6f4c634 100644 --- a/source/modules/juce_graphics/images/juce_Image.cpp +++ b/source/modules/juce_graphics/images/juce_Image.cpp @@ -102,7 +102,7 @@ public: sendDataChangeMessage(); } - ImagePixelData* clone() override + ImagePixelData::Ptr clone() override { SoftwarePixelData* s = new SoftwarePixelData (pixelFormat, width, height, false); memcpy (s->imageData, imageData, (size_t) (lineStride * height)); @@ -153,13 +153,13 @@ class SubsectionPixelData : public ImagePixelData public: SubsectionPixelData (ImagePixelData* const im, const Rectangle& r) : ImagePixelData (im->pixelFormat, r.getWidth(), r.getHeight()), - image (im), area (r) + sourceImage (im), area (r) { } LowLevelGraphicsContext* createLowLevelContext() override { - LowLevelGraphicsContext* g = image->createLowLevelContext(); + LowLevelGraphicsContext* g = sourceImage->createLowLevelContext(); g->clipToRectangle (area); g->setOrigin (area.getPosition()); return g; @@ -167,16 +167,16 @@ public: void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override { - image->initialiseBitmapData (bitmap, x + area.getX(), y + area.getY(), mode); + sourceImage->initialiseBitmapData (bitmap, x + area.getX(), y + area.getY(), mode); if (mode != Image::BitmapData::readOnly) sendDataChangeMessage(); } - ImagePixelData* clone() override + ImagePixelData::Ptr clone() override { jassert (getReferenceCount() > 0); // (This method can't be used on an unowned pointer, as it will end up self-deleting) - const ScopedPointer type (image->createType()); + const ScopedPointer type (createType()); Image newImage (type->create (pixelFormat, area.getWidth(), area.getHeight(), pixelFormat != Image::RGB)); @@ -185,18 +185,17 @@ public: g.drawImageAt (Image (this), 0, 0); } - newImage.getPixelData()->incReferenceCount(); return newImage.getPixelData(); } - ImageType* createType() const override { return image->createType(); } + ImageType* createType() const override { return sourceImage->createType(); } /* as we always hold a reference to image, don't double count */ - int getSharedCount() const noexcept override { return getReferenceCount() + image->getSharedCount() - 1; } + int getSharedCount() const noexcept override { return getReferenceCount() + sourceImage->getSharedCount() - 1; } private: friend class Image; - const ImagePixelData::Ptr image; + const ImagePixelData::Ptr sourceImage; const Rectangle area; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SubsectionPixelData) @@ -260,7 +259,9 @@ Image::~Image() { } +#if JUCE_ALLOW_STATIC_NULL_VARIABLES const Image Image::null; +#endif int Image::getReferenceCount() const noexcept { return image == nullptr ? 0 : image->getSharedCount(); } int Image::getWidth() const noexcept { return image == nullptr ? 0 : image->width; } diff --git a/source/modules/juce_graphics/images/juce_Image.h b/source/modules/juce_graphics/images/juce_Image.h index 189094c9f..e817cc7f4 100644 --- a/source/modules/juce_graphics/images/juce_Image.h +++ b/source/modules/juce_graphics/images/juce_Image.h @@ -146,10 +146,13 @@ public: */ inline bool isNull() const noexcept { return image == nullptr; } + #if JUCE_ALLOW_STATIC_NULL_VARIABLES /** A null Image object that can be used when you need to return an invalid image. - This object is the equivalient to an Image created with the default constructor. + This object is the equivalient to an Image created with the default constructor, and + you should always prefer to use Image() or {} when you need an empty image object. */ static const Image null; + #endif //============================================================================== /** Returns the image's width (in pixels). */ @@ -435,10 +438,12 @@ public: ImagePixelData (Image::PixelFormat, int width, int height); ~ImagePixelData(); + typedef ReferenceCountedObjectPtr Ptr; + /** Creates a context that will draw into this image. */ virtual LowLevelGraphicsContext* createLowLevelContext() = 0; /** Creates a copy of this image. */ - virtual ImagePixelData* clone() = 0; + virtual Ptr clone() = 0; /** Creates an instance of the type of this image. */ virtual ImageType* createType() const = 0; /** Initialises a BitmapData object. */ @@ -458,8 +463,6 @@ public: */ NamedValueSet userData; - typedef ReferenceCountedObjectPtr Ptr; - //============================================================================== struct Listener { diff --git a/source/modules/juce_graphics/images/juce_ImageCache.cpp b/source/modules/juce_graphics/images/juce_ImageCache.cpp index 797c796b2..9d6573936 100644 --- a/source/modules/juce_graphics/images/juce_ImageCache.cpp +++ b/source/modules/juce_graphics/images/juce_ImageCache.cpp @@ -47,7 +47,7 @@ public: return item->image; } - return Image::null; + return Image(); } void addImageToCache (const Image& image, const int64 hashCode) @@ -128,7 +128,7 @@ Image ImageCache::getFromHashCode (const int64 hashCode) if (Pimpl::getInstanceWithoutCreating() != nullptr) return Pimpl::getInstanceWithoutCreating()->getFromHashCode (hashCode); - return Image::null; + return Image(); } void ImageCache::addImageToCache (const Image& image, const int64 hashCode) diff --git a/source/modules/juce_graphics/juce_graphics.cpp b/source/modules/juce_graphics/juce_graphics.cpp index a87a16639..b2c64da10 100644 --- a/source/modules/juce_graphics/juce_graphics.cpp +++ b/source/modules/juce_graphics/juce_graphics.cpp @@ -31,6 +31,8 @@ #error "Incorrect use of JUCE cpp file" #endif +#include "AppConfig.h" + #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 @@ -44,6 +46,12 @@ #import #elif JUCE_WINDOWS + // get rid of some warnings in Window's own headers + #ifdef JUCE_MSVC + #pragma warning (push) + #pragma warning (disable : 4458) + #endif + #if JUCE_MINGW && JUCE_USE_DIRECTWRITE #warning "DirectWrite not currently implemented with mingw..." #undef JUCE_USE_DIRECTWRITE @@ -62,6 +70,10 @@ #include #endif + #ifdef JUCE_MSVC + #pragma warning (pop) + #endif + #elif JUCE_IOS #import #import diff --git a/source/modules/juce_graphics/juce_graphics.h b/source/modules/juce_graphics/juce_graphics.h index 9e334963a..9f6aaad28 100644 --- a/source/modules/juce_graphics/juce_graphics.h +++ b/source/modules/juce_graphics/juce_graphics.h @@ -22,11 +22,38 @@ ============================================================================== */ +/******************************************************************************* + The block below describes the properties of this module, and is read by + the Projucer to automatically generate project code that uses it. + For details about the syntax and how to create or use a module, see the + JUCE Module Format.txt file. + + + BEGIN_JUCE_MODULE_DECLARATION + + ID: juce_graphics + vendor: juce + version: 4.3.0 + name: JUCE graphics classes + description: Classes for 2D vector graphics, image loading/saving, font handling, etc. + website: http://www.juce.com/juce + license: GPL/Commercial + + dependencies: juce_events + OSXFrameworks: Cocoa QuartzCore + iOSFrameworks: CoreGraphics CoreText QuartzCore + linuxPackages: x11 xinerama xext freetype2 + + END_JUCE_MODULE_DECLARATION + +*******************************************************************************/ + + #ifndef JUCE_GRAPHICS_H_INCLUDED // %% #define JUCE_GRAPHICS_H_INCLUDED -#include "../juce_core/juce_core.h" -#include "../juce_events/juce_events.h" +#include +#include //============================================================================== /** Config: JUCE_USE_COREIMAGE_LOADER diff --git a/source/modules/juce_graphics/native/juce_RenderingHelpers.h b/source/modules/juce_graphics/native/juce_RenderingHelpers.h index 1930f1167..60df647e7 100644 --- a/source/modules/juce_graphics/native/juce_RenderingHelpers.h +++ b/source/modules/juce_graphics/native/juce_RenderingHelpers.h @@ -1257,9 +1257,9 @@ namespace EdgeTableFillers uint32 c = 256 * 128; c += src[0] * ((256 - subPixelX) * (256 - subPixelY)); src += this->srcData.pixelStride; - c += src[1] * (subPixelX * (256 - subPixelY)); + c += src[0] * (subPixelX * (256 - subPixelY)); src += this->srcData.lineStride; - c += src[1] * (subPixelX * subPixelY); + c += src[0] * (subPixelX * subPixelY); src -= this->srcData.pixelStride; c += src[0] * ((256 - subPixelX) * subPixelY); diff --git a/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm b/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm index 2aa0ffa05..7f320cc37 100644 --- a/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm +++ b/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm @@ -72,7 +72,7 @@ public: } } - ImagePixelData* clone() override + ImagePixelData::Ptr clone() override { CoreGraphicsImage* im = new CoreGraphicsImage (pixelFormat, width, height, false); memcpy (im->imageData, imageData, (size_t) (lineStride * height)); @@ -172,7 +172,7 @@ CoreGraphicsContext::CoreGraphicsContext (CGContextRef c, const float h, const f state (new SavedState()) { CGContextRetain (context); - CGContextSaveGState(context); + CGContextSaveGState (context); CGContextSetShouldSmoothFonts (context, true); CGContextSetAllowsFontSmoothing (context, true); CGContextSetShouldAntialias (context, true); @@ -385,9 +385,13 @@ void CoreGraphicsContext::setOpacity (float newOpacity) void CoreGraphicsContext::setInterpolationQuality (Graphics::ResamplingQuality quality) { - CGContextSetInterpolationQuality (context, quality == Graphics::lowResamplingQuality - ? kCGInterpolationLow - : kCGInterpolationHigh); + switch (quality) + { + case Graphics::lowResamplingQuality: CGContextSetInterpolationQuality (context, kCGInterpolationNone); return; + case Graphics::mediumResamplingQuality: CGContextSetInterpolationQuality (context, kCGInterpolationMedium); return; + case Graphics::highResamplingQuality: CGContextSetInterpolationQuality (context, kCGInterpolationHigh); return; + default: return; + } } //============================================================================== @@ -707,6 +711,10 @@ static CGGradientRef createGradient (const ColourGradient& g, CGColorSpaceRef co *comps++ = (CGFloat) colour.getFloatBlue(); *comps++ = (CGFloat) colour.getFloatAlpha(); locations[i] = (CGFloat) g.getColourPosition (i); + + // There's a bug (?) in the way the CG renderer works where it seems + // to go wrong if you have two colour stops both at position 0.. + jassert (i == 0 || locations[i] != 0); } return CGGradientCreateWithColorComponents (colourSpace, components, locations, (size_t) numColours); @@ -863,7 +871,7 @@ Image juce_loadWithCoreImage (InputStream& input) } } - return Image::null; + return Image(); } #endif diff --git a/source/modules/juce_graphics/native/juce_mac_Fonts.mm b/source/modules/juce_graphics/native/juce_mac_Fonts.mm index 239053f68..02689e618 100644 --- a/source/modules/juce_graphics/native/juce_mac_Fonts.mm +++ b/source/modules/juce_graphics/native/juce_mac_Fonts.mm @@ -245,9 +245,9 @@ namespace CoreTextTypeLayout { switch (text.getReadingDirection()) { - case AttributedString::ReadingDirection::rightToLeft: return kCTWritingDirectionRightToLeft; - case AttributedString::ReadingDirection::leftToRight: return kCTWritingDirectionLeftToRight; - default: return kCTWritingDirectionNatural; + case AttributedString::rightToLeft: return kCTWritingDirectionRightToLeft; + case AttributedString::leftToRight: return kCTWritingDirectionLeftToRight; + default: return kCTWritingDirectionNatural; } } @@ -545,7 +545,7 @@ public: ascent (0.0f), unitsToHeightScaleFactor (0.0f) { - CFDataRef cfData = CFDataCreate (kCFAllocatorDefault, (const UInt8*) data, (CFIndex) dataSize); + CFDataRef cfData = CFDataCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) data, (CFIndex) dataSize, kCFAllocatorNull); CGDataProviderRef provider = CGDataProviderCreateWithCFData (cfData); CFRelease (cfData); diff --git a/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp b/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp index c03ed3721..f2a70895d 100644 --- a/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp +++ b/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp @@ -22,7 +22,6 @@ ============================================================================== */ - #if JUCE_USE_DIRECTWRITE namespace DirectWriteTypeLayout { @@ -128,19 +127,22 @@ namespace DirectWriteTypeLayout const Point lineOrigin (layout->getLine (currentLine).lineOrigin); float x = baselineOriginX - lineOrigin.x; + const float extraKerning = glyphRunLayout->font.getExtraKerningFactor() + * glyphRunLayout->font.getHeight(); + for (UINT32 i = 0; i < glyphRun->glyphCount; ++i) { const float advance = glyphRun->glyphAdvances[i]; if ((glyphRun->bidiLevel & 1) != 0) - x -= advance; // RTL text + x -= advance + extraKerning; // RTL text glyphRunLayout->glyphs.add (TextLayout::Glyph (glyphRun->glyphIndices[i], Point (x, baselineOriginY - lineOrigin.y), advance)); if ((glyphRun->bidiLevel & 1) == 0) - x += advance; // LTR text + x += advance + extraKerning; // LTR text } return S_OK; diff --git a/source/modules/juce_gui_basics/buttons/juce_Button.cpp b/source/modules/juce_gui_basics/buttons/juce_Button.cpp index e33ab2677..81a49d39d 100644 --- a/source/modules/juce_gui_basics/buttons/juce_Button.cpp +++ b/source/modules/juce_gui_basics/buttons/juce_Button.cpp @@ -175,8 +175,15 @@ void Button::setToggleState (const bool shouldBeOn, const NotificationType notif return; } - if (getToggleState() != shouldBeOn) // this test means that if the value is void rather than explicitly set to - isOn = shouldBeOn; // false, it won't be changed unless the required value is true. + // This test is done so that if the value is void rather than explicitly set to + // false, the value won't be changed unless the required value is true. + if (getToggleState() != shouldBeOn) + { + isOn = shouldBeOn; + + if (deletionWatcher == nullptr) + return; + } lastToggleState = shouldBeOn; repaint(); @@ -456,7 +463,7 @@ void Button::mouseUp (const MouseEvent& e) { const bool wasDown = isDown(); const bool wasOver = isOver(); - updateState (isMouseOver(), false); + updateState (isMouseOrTouchOver (e), false); if (wasDown && wasOver && ! triggerOnMouseDown) { @@ -467,15 +474,23 @@ void Button::mouseUp (const MouseEvent& e) } } -void Button::mouseDrag (const MouseEvent&) +void Button::mouseDrag (const MouseEvent& e) { const ButtonState oldState = buttonState; - updateState (isMouseOver(), true); + updateState (isMouseOrTouchOver (e), true); if (autoRepeatDelay >= 0 && buttonState != oldState && isDown()) callbackHelper->startTimer (autoRepeatSpeed); } +bool Button::isMouseOrTouchOver (const MouseEvent& e) +{ + if (e.source.isTouch()) + return getLocalBounds().toFloat().contains (e.position); + + return isMouseOver(); +} + void Button::focusGained (FocusChangeType) { updateState(); diff --git a/source/modules/juce_gui_basics/buttons/juce_Button.h b/source/modules/juce_gui_basics/buttons/juce_Button.h index ef4a2e7d7..47c6fab54 100644 --- a/source/modules/juce_gui_basics/buttons/juce_Button.h +++ b/source/modules/juce_gui_basics/buttons/juce_Button.h @@ -295,7 +295,7 @@ public: E.g. if you are placing two buttons adjacent to each other, you could use this to indicate which edges are touching, and the LookAndFeel might choose to drawn them without rounded corners on the edges that connect. It's only a hint, so the - LookAndFeel can choose to ignore it if it's not relevent for this type of + LookAndFeel can choose to ignore it if it's not relevant for this type of button. */ void setConnectedEdges (int connectedEdgeFlags); @@ -503,6 +503,8 @@ private: void sendClickMessage (const ModifierKeys&); void sendStateMessage(); + bool isMouseOrTouchOver (const MouseEvent& e); + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Button) }; diff --git a/source/modules/juce_gui_basics/buttons/juce_DrawableButton.h b/source/modules/juce_gui_basics/buttons/juce_DrawableButton.h index 072cbbf74..7c142bcd7 100644 --- a/source/modules/juce_gui_basics/buttons/juce_DrawableButton.h +++ b/source/modules/juce_gui_basics/buttons/juce_DrawableButton.h @@ -73,19 +73,19 @@ public: The button will keep its own internal copies of these drawables. @param normalImage the thing to draw for the button's 'normal' state. An internal copy - will be made of the object passed-in if it is non-zero. + will be made of the object passed-in if it is non-null. @param overImage the thing to draw for the button's 'over' state - if this is - zero, the button's normal image will be used when the mouse is + null, the button's normal image will be used when the mouse is over it. An internal copy will be made of the object passed-in - if it is non-zero. + if it is non-null. @param downImage the thing to draw for the button's 'down' state - if this is - zero, the 'over' image will be used instead (or the normal image + null, the 'over' image will be used instead (or the normal image as a last resort). An internal copy will be made of the object - passed-in if it is non-zero. - @param disabledImage an image to draw when the button is disabled. If this is zero, + passed-in if it is non-null. + @param disabledImage an image to draw when the button is disabled. If this is null, the normal image will be drawn with a reduced opacity instead. An internal copy will be made of the object passed-in if it is - non-zero. + non-null. @param normalImageOn same as the normalImage, but this is used when the button's toggle state is 'on'. If this is nullptr, the normal image is used instead @param overImageOn same as the overImage, but this is used when the button's toggle diff --git a/source/modules/juce_gui_basics/buttons/juce_HyperlinkButton.cpp b/source/modules/juce_gui_basics/buttons/juce_HyperlinkButton.cpp index f448a1e2d..dd52f6f21 100644 --- a/source/modules/juce_gui_basics/buttons/juce_HyperlinkButton.cpp +++ b/source/modules/juce_gui_basics/buttons/juce_HyperlinkButton.cpp @@ -34,8 +34,8 @@ HyperlinkButton::HyperlinkButton (const String& linkText, setTooltip (linkURL.toString (false)); } -HyperlinkButton::HyperlinkButton () - : Button (String::empty), +HyperlinkButton::HyperlinkButton() + : Button (String()), font (14.0f, Font::underlined), resizeFont (true), justification (Justification::centred) diff --git a/source/modules/juce_gui_basics/buttons/juce_ImageButton.h b/source/modules/juce_gui_basics/buttons/juce_ImageButton.h index fed3cad40..e2669800d 100644 --- a/source/modules/juce_gui_basics/buttons/juce_ImageButton.h +++ b/source/modules/juce_gui_basics/buttons/juce_ImageButton.h @@ -46,7 +46,7 @@ public: @param name the name to give the component */ - explicit ImageButton (const String& name = String::empty); + explicit ImageButton (const String& name = String()); /** Destructor. */ ~ImageButton(); diff --git a/source/modules/juce_gui_basics/buttons/juce_ToggleButton.cpp b/source/modules/juce_gui_basics/buttons/juce_ToggleButton.cpp index c9a68d732..fd838075b 100644 --- a/source/modules/juce_gui_basics/buttons/juce_ToggleButton.cpp +++ b/source/modules/juce_gui_basics/buttons/juce_ToggleButton.cpp @@ -23,7 +23,7 @@ */ ToggleButton::ToggleButton() - : Button (String::empty) + : Button (String()) { setClickingTogglesState (true); } diff --git a/source/modules/juce_gui_basics/buttons/juce_ToggleButton.h b/source/modules/juce_gui_basics/buttons/juce_ToggleButton.h index 8c10fd0e2..291a32027 100644 --- a/source/modules/juce_gui_basics/buttons/juce_ToggleButton.h +++ b/source/modules/juce_gui_basics/buttons/juce_ToggleButton.h @@ -69,7 +69,9 @@ public: */ enum ColourIds { - textColourId = 0x1006501 /**< The colour to use for the button's text. */ + textColourId = 0x1006501, /**< The colour to use for the button's text. */ + tickColourId = 0x1006502, /**< The colour to use for the tick mark. */ + tickDisabledColourId = 0x1006503 /**< The colour to use for the disabled tick mark. */ }; protected: diff --git a/source/modules/juce_gui_basics/commands/juce_ApplicationCommandInfo.h b/source/modules/juce_gui_basics/commands/juce_ApplicationCommandInfo.h index 04604f791..23c571b17 100644 --- a/source/modules/juce_gui_basics/commands/juce_ApplicationCommandInfo.h +++ b/source/modules/juce_gui_basics/commands/juce_ApplicationCommandInfo.h @@ -135,7 +135,7 @@ struct JUCE_API ApplicationCommandInfo /** Indicates that the command can't currently be performed. The ApplicationCommandTarget::getCommandInfo() method must set this flag if it's - not currently permissable to perform the command. If the flag is set, then + not currently permissible to perform the command. If the flag is set, then components that trigger the command, e.g. PopupMenu, may choose to grey-out the command or show themselves as not being enabled. diff --git a/source/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.h b/source/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.h index b65218dad..6870042fa 100644 --- a/source/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.h +++ b/source/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.h @@ -219,7 +219,7 @@ public: */ bool isCommandActive (const CommandID commandID); - /** If this object is a Component, this method will seach upwards in its current + /** If this object is a Component, this method will search upwards in its current UI hierarchy for the next parent component that implements the ApplicationCommandTarget class. diff --git a/source/modules/juce_gui_basics/components/juce_Component.cpp b/source/modules/juce_gui_basics/components/juce_Component.cpp index 77c61c3e9..f544e7753 100644 --- a/source/modules/juce_gui_basics/components/juce_Component.cpp +++ b/source/modules/juce_gui_basics/components/juce_Component.cpp @@ -409,7 +409,7 @@ struct Component::ComponentHelpers if (child.isVisible() && ! child.isTransformed()) { - const Rectangle newClip (clipRect.getIntersection (child.bounds)); + const Rectangle newClip (clipRect.getIntersection (child.boundsRelativeToParent)); if (! newClip.isEmpty()) { @@ -438,6 +438,15 @@ struct Component::ComponentHelpers return Desktop::getInstance().getDisplays().getMainDisplay().userArea; } + + static void releaseAllCachedImageResources (Component& c) + { + if (CachedComponentImage* cached = c.getCachedComponentImage()) + cached->releaseResources(); + + for (int i = c.getNumChildComponents(); --i >= 0;) + releaseAllCachedImageResources (*c.getChildComponent (i)); + } }; //============================================================================== @@ -528,8 +537,7 @@ void Component::setVisible (bool shouldBeVisible) if (! shouldBeVisible) { - if (cachedImage != nullptr) - cachedImage->releaseResources(); + ComponentHelpers::releaseAllCachedImageResources (*this); if (currentlyFocusedComponent == this || isParentOf (currentlyFocusedComponent)) { @@ -657,7 +665,7 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) Desktop::getInstance().addDesktopComponent (this); - bounds.setPosition (topLeft); + boundsRelativeToParent.setPosition (topLeft); peer->updateBounds(); if (oldRenderingEngine >= 0) @@ -797,7 +805,7 @@ public: if (! owner.isOpaque()) { lg.setFill (Colours::transparentBlack); - lg.fillRect (imageBounds, true); + lg.fillRect (compBounds, true); lg.setFill (Colours::black); } @@ -813,7 +821,7 @@ public: bool invalidateAll() override { validArea.clear(); return true; } bool invalidate (const Rectangle& area) override { validArea.subtract (area); return true; } - void releaseResources() override { image = Image::null; } + void releaseResources() override { image = Image(); } private: Image image; @@ -1023,8 +1031,8 @@ bool Component::isAlwaysOnTop() const noexcept } //============================================================================== -int Component::proportionOfWidth (const float proportion) const noexcept { return roundToInt (proportion * bounds.getWidth()); } -int Component::proportionOfHeight (const float proportion) const noexcept { return roundToInt (proportion * bounds.getHeight()); } +int Component::proportionOfWidth (const float proportion) const noexcept { return roundToInt (proportion * boundsRelativeToParent.getWidth()); } +int Component::proportionOfHeight (const float proportion) const noexcept { return roundToInt (proportion * boundsRelativeToParent.getHeight()); } int Component::getParentWidth() const noexcept { @@ -1127,7 +1135,7 @@ void Component::setBounds (const int x, const int y, int w, int h) repaintParent(); } - bounds.setBounds (x, y, w, h); + boundsRelativeToParent.setBounds (x, y, w, h); if (showing) { @@ -1547,8 +1555,7 @@ Component* Component::removeChildComponent (const int index, bool sendParentEven childComponentList.remove (index); child->parentComponent = nullptr; - if (child->cachedImage != nullptr) - child->cachedImage->releaseResources(); + ComponentHelpers::releaseAllCachedImageResources (*child); // (NB: there are obscure situations where child->isShowing() = false, but it still has the focus) if (currentlyFocusedComponent == child || child->isParentOf (currentlyFocusedComponent)) @@ -1705,7 +1712,7 @@ int Component::runModalLoop() ->callFunctionOnMessageThread (&ComponentHelpers::runModalLoopCallback, this); } - if (! isCurrentlyModal()) + if (! isCurrentlyModal (false)) enterModalState (true); return ModalComponentManager::getInstance()->runEventLoopForCurrentComponent(); @@ -1721,7 +1728,7 @@ void Component::enterModalState (const bool shouldTakeKeyboardFocus, // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. ASSERT_MESSAGE_MANAGER_IS_LOCKED - if (! isCurrentlyModal()) + if (! isCurrentlyModal (false)) { ModalComponentManager& mcm = *ModalComponentManager::getInstance(); mcm.startModal (this, deleteWhenDismissed); @@ -1741,7 +1748,7 @@ void Component::enterModalState (const bool shouldTakeKeyboardFocus, void Component::exitModalState (const int returnValue) { - if (isCurrentlyModal()) + if (isCurrentlyModal (false)) { if (MessageManager::getInstance()->isThisTheMessageThread()) { @@ -1770,9 +1777,15 @@ void Component::exitModalState (const int returnValue) } } -bool Component::isCurrentlyModal() const noexcept +bool Component::isCurrentlyModal (bool onlyConsiderForemostModalComponent) const noexcept { - return getCurrentlyModalComponent() == this; + const int n = onlyConsiderForemostModalComponent ? 1 : getNumCurrentlyModalComponents(); + + for (int i = 0; i < n; ++i) + if (getCurrentlyModalComponent (i) == this) + return true; + + return false; } bool Component::isCurrentlyBlockedByAnotherModalComponent() const @@ -2236,13 +2249,13 @@ void Component::setPositioner (Positioner* newPositioner) //============================================================================== Rectangle Component::getLocalBounds() const noexcept { - return bounds.withZeroOrigin(); + return boundsRelativeToParent.withZeroOrigin(); } Rectangle Component::getBoundsInParent() const noexcept { - return affineTransform == nullptr ? bounds - : bounds.transformedBy (*affineTransform); + return affineTransform == nullptr ? boundsRelativeToParent + : boundsRelativeToParent.transformedBy (*affineTransform); } //============================================================================== @@ -2476,7 +2489,7 @@ void Component::internalMouseDown (MouseInputSource source, Point relativ } void Component::internalMouseUp (MouseInputSource source, Point relativePos, - Time time, const ModifierKeys oldModifiers) + Time time, const ModifierKeys oldModifiers, float pressure) { if (flags.mouseDownWasBlocked && isCurrentlyBlockedByAnotherModalComponent()) return; @@ -2487,7 +2500,7 @@ void Component::internalMouseUp (MouseInputSource source, Point relativeP repaint(); const MouseEvent me (source, relativePos, - oldModifiers, MouseInputSource::invalidPressure, this, this, time, + oldModifiers, pressure, this, this, time, getLocalPoint (nullptr, source.getLastMouseDownPosition()), source.getLastMouseDownTime(), source.getNumberOfMultipleClicks(), @@ -2672,7 +2685,7 @@ void Component::internalFocusLoss (const FocusChangeType cause) { const WeakReference safePointer (this); - focusLost (focusChangedDirectly); + focusLost (cause); if (safePointer != nullptr) internalChildFocusChange (cause, safePointer); diff --git a/source/modules/juce_gui_basics/components/juce_Component.h b/source/modules/juce_gui_basics/components/juce_Component.h index ce47460bc..e60dff79d 100644 --- a/source/modules/juce_gui_basics/components/juce_Component.h +++ b/source/modules/juce_gui_basics/components/juce_Component.h @@ -253,7 +253,7 @@ public: bounds will no longer be a direct reflection of the position at which it appears within its parent, as the transform will be applied to its bounding box. */ - int getX() const noexcept { return bounds.getX(); } + int getX() const noexcept { return boundsRelativeToParent.getX(); } /** Returns the y coordinate of the top of this component. This is a distance in pixels from the top edge of the component's parent. @@ -262,13 +262,13 @@ public: bounds will no longer be a direct reflection of the position at which it appears within its parent, as the transform will be applied to its bounding box. */ - int getY() const noexcept { return bounds.getY(); } + int getY() const noexcept { return boundsRelativeToParent.getY(); } /** Returns the component's width in pixels. */ - int getWidth() const noexcept { return bounds.getWidth(); } + int getWidth() const noexcept { return boundsRelativeToParent.getWidth(); } /** Returns the component's height in pixels. */ - int getHeight() const noexcept { return bounds.getHeight(); } + int getHeight() const noexcept { return boundsRelativeToParent.getHeight(); } /** Returns the x coordinate of the component's right-hand edge. This is a distance in pixels from the left edge of the component's parent. @@ -277,10 +277,10 @@ public: bounds will no longer be a direct reflection of the position at which it appears within its parent, as the transform will be applied to its bounding box. */ - int getRight() const noexcept { return bounds.getRight(); } + int getRight() const noexcept { return boundsRelativeToParent.getRight(); } /** Returns the component's top-left position as a Point. */ - Point getPosition() const noexcept { return bounds.getPosition(); } + Point getPosition() const noexcept { return boundsRelativeToParent.getPosition(); } /** Returns the y coordinate of the bottom edge of this component. This is a distance in pixels from the top edge of the component's parent. @@ -289,7 +289,7 @@ public: bounds will no longer be a direct reflection of the position at which it appears within its parent, as the transform will be applied to its bounding box. */ - int getBottom() const noexcept { return bounds.getBottom(); } + int getBottom() const noexcept { return boundsRelativeToParent.getBottom(); } /** Returns this component's bounding box. The rectangle returned is relative to the top-left of the component's parent. @@ -298,7 +298,7 @@ public: bounds will no longer be a direct reflection of the position at which it appears within its parent, as the transform will be applied to its bounding box. */ - const Rectangle& getBounds() const noexcept { return bounds; } + const Rectangle& getBounds() const noexcept { return boundsRelativeToParent; } /** Returns the component's bounds, relative to its own origin. This is like getBounds(), but returns the rectangle in local coordinates, In practice, it'll @@ -1952,12 +1952,14 @@ public: /** Returns true if this component is the modal one. It's possible to have nested modal components, e.g. a pop-up dialog box - that launches another pop-up, but this will only return true for - the one at the top of the stack. + that launches another pop-up. If onlyConsiderForemostModalComponent is + true then isCurrentlyModal will only return true for the one at the top + of the stack. If onlyConsiderForemostModalComponent is false then + isCurrentlyModal will return true for any modal component in the stack. @see getCurrentlyModalComponent */ - bool isCurrentlyModal() const noexcept; + bool isCurrentlyModal (bool onlyConsiderForemostModalComponent = true) const noexcept; /** Returns the number of components that are currently in a modal state. @see getCurrentlyModalComponent @@ -2036,7 +2038,7 @@ public: look-and-feel either, it will just return black. The colour IDs for various purposes are stored as enums in the components that - they are relevent to - for an example, see Slider::ColourIds, + they are relevant to - for an example, see Slider::ColourIds, Label::ColourIds, TextEditor::ColourIds, TreeView::ColourIds, etc. @see setColour, isColourSpecified, colourChanged, LookAndFeel::findColour, LookAndFeel::setColour @@ -2242,7 +2244,7 @@ private: //============================================================================== String componentName, componentID; Component* parentComponent; - Rectangle bounds; + Rectangle boundsRelativeToParent; ScopedPointer positioner; ScopedPointer affineTransform; Array childComponentList; @@ -2299,7 +2301,7 @@ private: void internalMouseEnter (MouseInputSource, Point, Time); void internalMouseExit (MouseInputSource, Point, Time); void internalMouseDown (MouseInputSource, Point, Time, float); - void internalMouseUp (MouseInputSource, Point, Time, const ModifierKeys oldModifiers); + void internalMouseUp (MouseInputSource, Point, Time, const ModifierKeys oldModifiers, float); void internalMouseDrag (MouseInputSource, Point, Time, float); void internalMouseMove (MouseInputSource, Point, Time); void internalMouseWheel (MouseInputSource, Point, Time, const MouseWheelDetails&); diff --git a/source/modules/juce_gui_basics/components/juce_Desktop.cpp b/source/modules/juce_gui_basics/components/juce_Desktop.cpp index 26cc3c9d5..1a591b482 100644 --- a/source/modules/juce_gui_basics/components/juce_Desktop.cpp +++ b/source/modules/juce_gui_basics/components/juce_Desktop.cpp @@ -36,6 +36,7 @@ Desktop::Desktop() Desktop::~Desktop() { setScreenSaverEnabled (true); + animator.cancelAllAnimations (false); jassert (instance == this); instance = nullptr; diff --git a/source/modules/juce_gui_basics/components/juce_Desktop.h b/source/modules/juce_gui_basics/components/juce_Desktop.h index 02657b63c..427a24785 100644 --- a/source/modules/juce_gui_basics/components/juce_Desktop.h +++ b/source/modules/juce_gui_basics/components/juce_Desktop.h @@ -159,7 +159,7 @@ public: If allowMenusAndBars is true, things like the menu and dock (on mac) are still allowed to pop up when the mouse moves onto them. If this is false, it'll try - to hide as much on-screen paraphenalia as possible. + to hide as much on-screen paraphernalia as possible. */ void setKioskModeComponent (Component* componentToUse, bool allowMenusAndBars = true); diff --git a/source/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp b/source/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp index cc46ac0b9..908c4d1f9 100644 --- a/source/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp +++ b/source/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp @@ -245,13 +245,14 @@ bool ModalComponentManager::cancelAllModalComponents() return numModal > 0; } +//============================================================================== #if JUCE_MODAL_LOOPS_PERMITTED class ModalComponentManager::ReturnValueRetriever : public ModalComponentManager::Callback { public: ReturnValueRetriever (int& v, bool& done) : value (v), finished (done) {} - void modalStateFinished (int returnValue) + void modalStateFinished (int returnValue) override { finished = true; value = returnValue; @@ -292,3 +293,21 @@ int ModalComponentManager::runEventLoopForCurrentComponent() return returnValue; } #endif + +//============================================================================== +#if JUCE_COMPILER_SUPPORTS_LAMBDAS +struct LambdaCallback : public ModalComponentManager::Callback +{ + LambdaCallback (std::function fn) noexcept : function (fn) {} + void modalStateFinished (int result) override { function (result); } + + std::function function; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LambdaCallback) +}; + +ModalComponentManager::Callback* ModalCallbackFunction::create (std::function f) +{ + return new LambdaCallback (f); +} +#endif diff --git a/source/modules/juce_gui_basics/components/juce_ModalComponentManager.h b/source/modules/juce_gui_basics/components/juce_ModalComponentManager.h index b0817518a..03b2990d9 100644 --- a/source/modules/juce_gui_basics/components/juce_ModalComponentManager.h +++ b/source/modules/juce_gui_basics/components/juce_ModalComponentManager.h @@ -184,9 +184,20 @@ public: static ModalComponentManager::Callback* create (void (*functionToCall) (int, ParamType), ParamType parameterValue) { - return new FunctionCaller1 (functionToCall, parameterValue); + return new FunctionCaller1 (functionToCall, parameterValue); } + #if JUCE_COMPILER_SUPPORTS_LAMBDAS + /** This is a utility function to create a ModalComponentManager::Callback that will + call a lambda function. + The lambda that you supply must take an integer parameter, which is the result code that + was returned when the modal component was dismissed. + + @see ModalComponentManager::Callback + */ + static ModalComponentManager::Callback* create (std::function); + #endif + //============================================================================== /** This is a utility function to create a ModalComponentManager::Callback that will call a static function with two custom parameters. @@ -214,7 +225,7 @@ public: ParamType1 parameterValue1, ParamType2 parameterValue2) { - return new FunctionCaller2 (functionToCall, parameterValue1, parameterValue2); + return new FunctionCaller2 (functionToCall, parameterValue1, parameterValue2); } //============================================================================== @@ -244,7 +255,7 @@ public: static ModalComponentManager::Callback* forComponent (void (*functionToCall) (int, ComponentType*), ComponentType* component) { - return new ComponentCaller1 (functionToCall, component); + return new ComponentCaller1 (functionToCall, component); } //============================================================================== @@ -275,21 +286,20 @@ public: ComponentType* component, ParamType param) { - return new ComponentCaller2 (functionToCall, component, param); + return new ComponentCaller2 (functionToCall, component, param); } private: //============================================================================== template - class FunctionCaller1 : public ModalComponentManager::Callback + struct FunctionCaller1 : public ModalComponentManager::Callback { - public: typedef void (*FunctionType) (int, ParamType); FunctionCaller1 (FunctionType& f, ParamType& p1) : function (f), param (p1) {} - void modalStateFinished (int returnValue) { function (returnValue, param); } + void modalStateFinished (int returnValue) override { function (returnValue, param); } private: const FunctionType function; @@ -299,15 +309,14 @@ private: }; template - class FunctionCaller2 : public ModalComponentManager::Callback + struct FunctionCaller2 : public ModalComponentManager::Callback { - public: typedef void (*FunctionType) (int, ParamType1, ParamType2); FunctionCaller2 (FunctionType& f, ParamType1& p1, ParamType2& p2) : function (f), param1 (p1), param2 (p2) {} - void modalStateFinished (int returnValue) { function (returnValue, param1, param2); } + void modalStateFinished (int returnValue) override { function (returnValue, param1, param2); } private: const FunctionType function; @@ -318,15 +327,14 @@ private: }; template - class ComponentCaller1 : public ModalComponentManager::Callback + struct ComponentCaller1 : public ModalComponentManager::Callback { - public: typedef void (*FunctionType) (int, ComponentType*); ComponentCaller1 (FunctionType& f, ComponentType* c) : function (f), comp (c) {} - void modalStateFinished (int returnValue) + void modalStateFinished (int returnValue) override { function (returnValue, static_cast (comp.get())); } @@ -339,15 +347,14 @@ private: }; template - class ComponentCaller2 : public ModalComponentManager::Callback + struct ComponentCaller2 : public ModalComponentManager::Callback { - public: typedef void (*FunctionType) (int, ComponentType*, ParamType1); ComponentCaller2 (FunctionType& f, ComponentType* c, ParamType1 p1) : function (f), comp (c), param1 (p1) {} - void modalStateFinished (int returnValue) + void modalStateFinished (int returnValue) override { function (returnValue, static_cast (comp.get()), param1); } diff --git a/source/modules/juce_gui_basics/drawables/juce_DrawableRectangle.cpp b/source/modules/juce_gui_basics/drawables/juce_DrawableRectangle.cpp index 034953bc4..c9cb67b40 100644 --- a/source/modules/juce_gui_basics/drawables/juce_DrawableRectangle.cpp +++ b/source/modules/juce_gui_basics/drawables/juce_DrawableRectangle.cpp @@ -31,6 +31,7 @@ DrawableRectangle::DrawableRectangle (const DrawableRectangle& other) bounds (other.bounds), cornerSize (other.cornerSize) { + rebuildPath(); } DrawableRectangle::~DrawableRectangle() diff --git a/source/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp b/source/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp index 96d8e9092..0f6952298 100644 --- a/source/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp +++ b/source/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp @@ -190,7 +190,7 @@ void DrawableShape::strokeChanged() strokePath.clear(); const float extraAccuracy = 4.0f; - if (dashLengths.empty()) + if (dashLengths.isEmpty()) strokeType.createStrokedPath (strokePath, path, AffineTransform(), extraAccuracy); else strokeType.createDashedStroke (strokePath, path, dashLengths.getRawDataPointer(), diff --git a/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp b/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp index 6ae599aba..e819d3fc1 100644 --- a/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp +++ b/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp @@ -66,6 +66,55 @@ public: const XmlPath* parent; }; + //============================================================================== + struct UsePathOp + { + const SVGState* state; + Path* targetPath; + + void operator() (const XmlPath& xmlPath) + { + state->parsePathElement (xmlPath, *targetPath); + } + }; + + struct GetClipPathOp + { + const SVGState* state; + Drawable* target; + + void operator() (const XmlPath& xmlPath) + { + state->applyClipPath (*target, xmlPath); + } + }; + + struct SetGradientStopsOp + { + const SVGState* state; + ColourGradient* gradient; + + void operator() (const XmlPath& xml) + { + state->addGradientStopsIn (*gradient, xml); + } + }; + + struct GetFillTypeOp + { + const SVGState* state; + const Path* path; + float opacity; + FillType fillType; + + void operator() (const XmlPath& xml) + { + if (xml->hasTagNameIgnoringNamespace ("linearGradient") + || xml->hasTagNameIgnoringNamespace ("radialGradient")) + fillType = state->getGradientFillType (xml, *path, opacity); + } + }; + //============================================================================== Drawable* parseSVGElement (const XmlPath& xml) { @@ -553,17 +602,6 @@ private: { const String linkedID = link.substring (1); - struct UsePathOp - { - const SVGState* state; - Path* targetPath; - - void operator() (const XmlPath& xmlPath) - { - state->parsePathElement (xmlPath, *targetPath); - } - }; - UsePathOp op = { this, &path }; topLevelXml.applyOperationToChildWithID (linkedID, op); } @@ -692,17 +730,6 @@ private: if (urlID.isNotEmpty()) { - struct GetClipPathOp - { - const SVGState* state; - Drawable* target; - - void operator() (const XmlPath& xmlPath) - { - state->applyClipPath (*target, xmlPath); - } - }; - GetClipPathOp op = { this, &d }; topLevelXml.applyOperationToChildWithID (urlID, op); } @@ -751,17 +778,6 @@ private: if (id.startsWithChar ('#')) { - struct SetGradientStopsOp - { - const SVGState* state; - ColourGradient* gradient; - - void operator() (const XmlPath& xml) - { - state->addGradientStopsIn (*gradient, xml); - } - }; - SetGradientStopsOp op = { this, &gradient, }; topLevelXml.applyOperationToChildWithID (id.substring (1), op); } @@ -769,10 +785,13 @@ private: addGradientStopsIn (gradient, fillXml); - if (gradient.getNumColours() > 0) + if (int numColours = gradient.getNumColours()) { - gradient.addColour (0.0, gradient.getColour (0)); - gradient.addColour (1.0, gradient.getColour (gradient.getNumColours() - 1)); + if (gradient.getColourPosition (0) > 0) + gradient.addColour (0.0, gradient.getColour (0)); + + if (gradient.getColourPosition (numColours - 1) < 1.0) + gradient.addColour (1.0, gradient.getColour (numColours - 1)); } else { @@ -890,22 +909,7 @@ private: if (urlID.isNotEmpty()) { - struct GetFillTypeOp - { - const SVGState* state; - const Path* path; - float opacity; - FillType fillType; - - void operator() (const XmlPath& xml) - { - if (xml->hasTagNameIgnoringNamespace ("linearGradient") - || xml->hasTagNameIgnoringNamespace ("radialGradient")) - fillType = state->getGradientFillType (xml, *path, opacity); - } - }; - - GetFillTypeOp op = { this, &path, opacity }; + GetFillTypeOp op = { this, &path, opacity, FillType() }; if (topLevelXml.applyOperationToChildWithID (urlID, op)) return op.fillType; diff --git a/source/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.h b/source/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.h index 2c015de83..3e2258feb 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.h +++ b/source/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.h @@ -179,7 +179,7 @@ public: /** Returns one of the files in the list. @param index should be less than getNumFiles(). If this is out-of-range, the - return value will be File::nonexistent + return value will be a default File() object @see getNumFiles, getFileInfo */ File getFile (int index) const; diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp b/source/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp index afe498a22..6c6e57f6f 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp +++ b/source/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp @@ -26,7 +26,7 @@ FileBrowserComponent::FileBrowserComponent (int flags_, const File& initialFileOrDirectory, const FileFilter* fileFilter_, FilePreviewComponent* previewComp_) - : FileFilter (String::empty), + : FileFilter (String()), fileFilter (fileFilter_), flags (flags_), previewComp (previewComp_), @@ -44,7 +44,7 @@ FileBrowserComponent::FileBrowserComponent (int flags_, String filename; - if (initialFileOrDirectory == File::nonexistent) + if (initialFileOrDirectory == File()) { currentRoot = File::getCurrentWorkingDirectory(); } @@ -391,7 +391,7 @@ void FileBrowserComponent::fileDoubleClicked (const File& f) setRoot (f); if ((flags & canSelectDirectories) != 0 && (flags & doNotClearFileNameOnRootChange) == 0) - filenameBox.setText (String::empty); + filenameBox.setText (String()); } else { @@ -535,8 +535,8 @@ void FileBrowserComponent::getDefaultRoots (StringArray& rootNames, StringArray& rootNames.add (name); } - rootPaths.add (String::empty); - rootNames.add (String::empty); + rootPaths.add (String()); + rootNames.add (String()); rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName()); rootNames.add (TRANS("Documents")); @@ -559,8 +559,8 @@ void FileBrowserComponent::getDefaultRoots (StringArray& rootNames, StringArray& rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName()); rootNames.add (TRANS("Desktop")); - rootPaths.add (String::empty); - rootNames.add (String::empty); + rootPaths.add (String()); + rootNames.add (String()); Array volumes; File vol ("/Volumes"); diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h b/source/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h index 284ee95b4..058a36de4 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h +++ b/source/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h @@ -59,7 +59,7 @@ public: canSelectFiles = 4, /**< specifies that the user can select files (can be used in conjunction with canSelectDirectories). */ canSelectDirectories = 8, /**< specifies that the user can select directories (can be used in - conjuction with canSelectFiles). */ + conjunction with canSelectFiles). */ canSelectMultipleItems = 16, /**< specifies that the user can select multiple items. */ useTreeView = 32, /**< specifies that a tree-view should be shown instead of a file list. */ filenameBoxIsReadOnly = 64, /**< specifies that the user can't type directly into the filename box. */ @@ -74,7 +74,7 @@ public: specify the component's behaviour. The flags must contain either openMode or saveMode, and canSelectFiles and/or canSelectDirectories. @param initialFileOrDirectory The file or directory that should be selected when the component begins. - If this is File::nonexistent, a default directory will be chosen. + If this is File(), a default directory will be chosen. @param fileFilter an optional filter to use to determine which files are shown. If this is nullptr then all files are displayed. Note that a pointer is kept internally to this object, so make sure that it is not deleted diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp b/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp index 97c3f3252..7a6eb3605 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp +++ b/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp @@ -113,13 +113,15 @@ bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previ } else { - WildcardFileFilter wildcard (selectsFiles ? filters : String::empty, - selectsDirectories ? "*" : String::empty, - String::empty); + ignoreUnused (selectMultiple); + + WildcardFileFilter wildcard (selectsFiles ? filters : String(), + selectsDirectories ? "*" : String(), + String()); FileBrowserComponent browserComponent (flags, startingFile, &wildcard, previewComp); - FileChooserDialogBox box (title, String::empty, + FileChooserDialogBox box (title, String(), browserComponent, warnAboutOverwrite, browserComponent.findColour (AlertWindow::backgroundColourId)); diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.h b/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.h index f7399751b..e6a35d50a 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.h +++ b/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.h @@ -64,8 +64,8 @@ public: tell the user what's going on @param initialFileOrDirectory the file or directory that should be selected when the dialog box opens. If this parameter is - set to File::nonexistent, a sensible default - directory will be used instead. + set to File(), a sensible default directory will + be used instead. @param filePatternsAllowed a set of file patterns to specify which files can be selected - each pattern should be separated by a comma or semi-colon, e.g. "*" or @@ -82,8 +82,8 @@ public: @see browseForFileToOpen, browseForFileToSave, browseForDirectory */ FileChooser (const String& dialogBoxTitle, - const File& initialFileOrDirectory = File::nonexistent, - const String& filePatternsAllowed = String::empty, + const File& initialFileOrDirectory = File(), + const String& filePatternsAllowed = String(), bool useOSNativeDialogBox = true, bool treatFilePackagesAsDirectories = false); diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp b/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp index 52fa00795..ef851cbf9 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp +++ b/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp @@ -238,7 +238,7 @@ void FileChooserDialogBox::createNewFolder() TRANS("Please enter the name for the folder"), AlertWindow::NoIcon, this); - aw->addTextEditor ("Folder Name", String::empty, String::empty, false); + aw->addTextEditor ("Folder Name", String(), String(), false); aw->addButton (TRANS("Create Folder"), 1, KeyPress (KeyPress::returnKey)); aw->addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey)); diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h b/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h index 2f1a993cf..f0e82bb44 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h +++ b/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h @@ -37,10 +37,10 @@ @code { - WildcardFileFilter wildcardFilter ("*.foo", String::empty, "Foo files"); + WildcardFileFilter wildcardFilter ("*.foo", String(), "Foo files"); FileBrowserComponent browser (FileBrowserComponent::canSelectFiles, - File::nonexistent, + File(), &wildcardFilter, nullptr); diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp b/source/modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp index f75e80e68..2333dcc82 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp +++ b/source/modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp @@ -27,7 +27,7 @@ Image juce_createIconForFile (const File& file); //============================================================================== FileListComponent::FileListComponent (DirectoryContentsList& listToShow) - : ListBox (String::empty, nullptr), + : ListBox (String(), nullptr), DirectoryContentsDisplayComponent (listToShow) { setModel (this); @@ -153,13 +153,13 @@ public: file = newFile; fileSize = newFileSize; modTime = newModTime; - icon = Image::null; + icon = Image(); isDirectory = fileInfo != nullptr && fileInfo->isDirectory; repaint(); } - if (file != File::nonexistent && icon.isNull() && ! isDirectory) + if (file != File() && icon.isNull() && ! isDirectory) { updateIcon (true); diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FilePreviewComponent.h b/source/modules/juce_gui_basics/filebrowser/juce_FilePreviewComponent.h index d09d56387..a1c342b9a 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FilePreviewComponent.h +++ b/source/modules/juce_gui_basics/filebrowser/juce_FilePreviewComponent.h @@ -51,7 +51,7 @@ public: /** Called to indicate that the user's currently selected file has changed. @param newSelectedFile the newly selected file or directory, which may be - File::nonexistent if none is selected. + a defualt File() object if none is selected. */ virtual void selectedFileChanged (const File& newSelectedFile) = 0; diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp b/source/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp index 3239085fc..6c9364ef5 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp +++ b/source/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp @@ -26,8 +26,8 @@ FileSearchPathListComponent::FileSearchPathListComponent() : addButton ("+"), removeButton ("-"), changeButton (TRANS ("change...")), - upButton (String::empty, DrawableButton::ImageOnButtonBackground), - downButton (String::empty, DrawableButton::ImageOnButtonBackground) + upButton (String(), DrawableButton::ImageOnButtonBackground), + downButton (String(), DrawableButton::ImageOnButtonBackground) { listBox.setModel (this); addAndMakeVisible (listBox); @@ -222,10 +222,10 @@ void FileSearchPathListComponent::buttonClicked (Button* button) { File start (defaultBrowseTarget); - if (start == File::nonexistent) + if (start == File()) start = path [0]; - if (start == File::nonexistent) + if (start == File()) start = File::getCurrentWorkingDirectory(); #if JUCE_MODAL_LOOPS_PERMITTED diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp b/source/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp index 178a10b3d..6565ba461 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp +++ b/source/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp @@ -172,7 +172,7 @@ public: void paintItem (Graphics& g, int width, int height) override { - if (file != File::nonexistent) + if (file != File()) { updateIcon (true); @@ -285,7 +285,7 @@ File FileTreeComponent::getSelectedFile (const int index) const if (const FileListTreeItem* const item = dynamic_cast (getSelectedItem (index))) return item->file; - return File::nonexistent; + return File(); } void FileTreeComponent::deselectAllFiles() diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h b/source/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h index 833556fd8..7f45f67d7 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h +++ b/source/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h @@ -54,23 +54,23 @@ public: /** Returns the number of files the user has got selected. @see getSelectedFile */ - int getNumSelectedFiles() const { return TreeView::getNumSelectedItems(); } + int getNumSelectedFiles() const override { return TreeView::getNumSelectedItems(); } /** Returns one of the files that the user has currently selected. The index should be in the range 0 to (getNumSelectedFiles() - 1). @see getNumSelectedFiles */ - File getSelectedFile (int index = 0) const; + File getSelectedFile (int index = 0) const override; /** Deselects any files that are currently selected. */ - void deselectAllFiles(); + void deselectAllFiles() override; /** Scrolls the list to the top. */ - void scrollToTop(); + void scrollToTop() override; /** If the specified file is in the list, it will become the only selected item (and if the file isn't in the list, all other items will be deselected). */ - void setSelectedFile (const File&); + void setSelectedFile (const File&) override; /** Updates the files in the list. */ void refresh(); diff --git a/source/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp b/source/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp index 2a69425e4..6cfc09b70 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp +++ b/source/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp @@ -105,8 +105,8 @@ void FilenameComponent::setDefaultBrowseTarget (const File& newDefaultDirectory) File FilenameComponent::getLocationToBrowse() { - return getCurrentFile() == File::nonexistent ? defaultBrowseFile - : getCurrentFile(); + return getCurrentFile() == File() ? defaultBrowseFile + : getCurrentFile(); } void FilenameComponent::buttonClicked (Button*) diff --git a/source/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.cpp b/source/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.cpp index d67908979..1b6265a59 100644 --- a/source/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.cpp +++ b/source/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.cpp @@ -57,7 +57,7 @@ void ImagePreviewComponent::timerCallback() { stopTimer(); - currentThumbnail = Image::null; + currentThumbnail = Image(); currentDetails.clear(); repaint(); diff --git a/source/modules/juce_gui_basics/juce_gui_basics.cpp b/source/modules/juce_gui_basics/juce_gui_basics.cpp index b65ae4250..25d0b80fb 100644 --- a/source/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/source/modules/juce_gui_basics/juce_gui_basics.cpp @@ -31,6 +31,8 @@ #error "Incorrect use of JUCE cpp file" #endif +#include "AppConfig.h" + #define NS_FORMAT_FUNCTION(F,A) // To avoid spurious warnings from GCC #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 @@ -48,11 +50,7 @@ #import #if JUCE_SUPPORT_CARBON - #define Point CarbonDummyPointName - #define Component CarbonDummyCompName #import // still needed for SetSystemUIMode() - #undef Point - #undef Component #endif //============================================================================== @@ -257,6 +255,11 @@ extern bool juce_areThereAnyAlwaysOnTopWindows(); #include "misc/juce_BubbleComponent.cpp" #include "misc/juce_DropShadower.cpp" +// these classes are C++11-only +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS && JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS && JUCE_COMPILER_SUPPORTS_LAMBDAS + #include "layout/juce_FlexBox.cpp" +#endif + #if JUCE_IOS || JUCE_WINDOWS #include "native/juce_MultiTouchMapper.h" #endif diff --git a/source/modules/juce_gui_basics/juce_gui_basics.h b/source/modules/juce_gui_basics/juce_gui_basics.h index cf53565eb..a5883f6ae 100644 --- a/source/modules/juce_gui_basics/juce_gui_basics.h +++ b/source/modules/juce_gui_basics/juce_gui_basics.h @@ -22,11 +22,38 @@ ============================================================================== */ +/******************************************************************************* + The block below describes the properties of this module, and is read by + the Projucer to automatically generate project code that uses it. + For details about the syntax and how to create or use a module, see the + JUCE Module Format.txt file. + + + BEGIN_JUCE_MODULE_DECLARATION + + ID: juce_gui_basics + vendor: juce + version: 4.3.0 + name: JUCE GUI core classes + description: Basic user-interface components and related classes. + website: http://www.juce.com/juce + license: GPL/Commercial + + dependencies: juce_events juce_graphics juce_data_structures + OSXFrameworks: Cocoa Carbon QuartzCore + iOSFrameworks: UIKit + linuxPackages: x11 xinerama xext + + END_JUCE_MODULE_DECLARATION + +*******************************************************************************/ + + #ifndef JUCE_GUI_BASICS_H_INCLUDED #define JUCE_GUI_BASICS_H_INCLUDED -#include "../juce_graphics/juce_graphics.h" -#include "../juce_data_structures/juce_data_structures.h" +#include +#include //============================================================================== /** Config: JUCE_ENABLE_REPAINT_DEBUGGING @@ -50,7 +77,7 @@ /** JUCE_USE_XINERAMA: Enables Xinerama multi-monitor support (Linux only). Unless you specifically want to disable this, it's best to leave this option turned on. This will be used as a fallback if JUCE_USE_XRANDR not set or libxrandr cannot be found. - Note that your users do not need to have Xrandr installed for your JUCE app to run, as + Note that your users do not need to have Xinerama installed for your JUCE app to run, as the availability of Xinerama is queried during runtime. */ #ifndef JUCE_USE_XINERAMA @@ -126,6 +153,7 @@ class BubbleComponent; class KeyPressMappingSet; class ApplicationCommandManagerListener; class DrawableButton; +class FlexBox; #include "mouse/juce_MouseCursor.h" #include "mouse/juce_MouseListener.h" @@ -254,6 +282,12 @@ class DrawableButton; #include "lookandfeel/juce_LookAndFeel_V1.h" #include "lookandfeel/juce_LookAndFeel_V3.h" +// these classes are C++11-only +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS && JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS && JUCE_COMPILER_SUPPORTS_LAMBDAS +#include "layout/juce_FlexItem.h" +#include "layout/juce_FlexBox.h" +#endif + } #endif // JUCE_GUI_BASICS_H_INCLUDED diff --git a/source/modules/juce_gui_basics/keyboard/juce_KeyPress.cpp b/source/modules/juce_gui_basics/keyboard/juce_KeyPress.cpp index 29e2f4eff..22fc2c249 100644 --- a/source/modules/juce_gui_basics/keyboard/juce_KeyPress.cpp +++ b/source/modules/juce_gui_basics/keyboard/juce_KeyPress.cpp @@ -250,7 +250,7 @@ String KeyPress::getTextDescription() const { // some keyboard layouts use a shift-key to get the slash, but in those cases, we // want to store it as being a slash, not shift+whatever. - if (textCharacter == '/') + if (textCharacter == '/' && keyCode != numberPadDivide) return "/"; if (mods.isCtrlDown()) desc << "ctrl + "; diff --git a/source/modules/juce_gui_basics/layout/juce_FlexBox.cpp b/source/modules/juce_gui_basics/layout/juce_FlexBox.cpp new file mode 100644 index 000000000..6d53a1832 --- /dev/null +++ b/source/modules/juce_gui_basics/layout/juce_FlexBox.cpp @@ -0,0 +1,836 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +struct FlexBoxLayoutCalculation +{ + using Coord = double; + + FlexBoxLayoutCalculation (const FlexBox& fb, Coord w, Coord h) + : owner (fb), parentWidth (w), parentHeight (h), numItems (owner.items.size()), + isRowDirection (fb.flexDirection == FlexBox::Direction::row + || fb.flexDirection == FlexBox::Direction::rowReverse), + containerLineLength (isRowDirection ? parentWidth : parentHeight) + { + lineItems.calloc ((size_t) (numItems * numItems)); + lineInfo.calloc ((size_t) numItems); + } + + struct ItemWithState + { + ItemWithState (FlexItem& source) noexcept : item (&source) {} + + FlexItem* item; + Coord lockedWidth = 0, lockedHeight = 0; + Coord lockedMarginLeft = 0, lockedMarginRight = 0, lockedMarginTop = 0, lockedMarginBottom = 0; + Coord preferredWidth = 0, preferredHeight = 0; + bool locked = false; + + void resetItemLockedSize() noexcept + { + lockedWidth = preferredWidth; + lockedHeight = preferredHeight; + lockedMarginLeft = getValueOrZeroIfAuto (item->margin.left); + lockedMarginRight = getValueOrZeroIfAuto (item->margin.right); + lockedMarginTop = getValueOrZeroIfAuto (item->margin.top); + lockedMarginBottom = getValueOrZeroIfAuto (item->margin.bottom); + } + + void setWidthChecked (Coord newWidth) noexcept + { + if (isAssigned (item->maxWidth)) newWidth = jmin (newWidth, static_cast (item->maxWidth)); + if (isAssigned (item->minWidth)) newWidth = jmax (newWidth, static_cast (item->minWidth)); + + lockedWidth = newWidth; + } + + void setHeightChecked (Coord newHeight) noexcept + { + if (isAssigned (item->maxHeight)) newHeight = jmin (newHeight, (Coord) item->maxHeight); + if (isAssigned (item->minHeight)) newHeight = jmax (newHeight, (Coord) item->minHeight); + + lockedHeight = newHeight; + } + }; + + struct RowInfo + { + int numItems; + Coord crossSize, lineY, totalLength; + }; + + const FlexBox& owner; + const Coord parentWidth, parentHeight; + const int numItems; + const bool isRowDirection; + const Coord containerLineLength; + + int numberOfRows = 1; + Coord containerCrossLength = 0; + + HeapBlock lineItems; + HeapBlock lineInfo; + Array itemStates; + + ItemWithState& getItem (int x, int y) const noexcept { return *lineItems[y * numItems + x]; } + + static bool isAuto (Coord value) noexcept { return value == FlexItem::autoValue; } + static bool isAssigned (Coord value) noexcept { return value != FlexItem::notAssigned; } + static Coord getValueOrZeroIfAuto (Coord value) noexcept { return isAuto (value) ? Coord() : value; } + + //============================================================================== + void createStates() + { + itemStates.ensureStorageAllocated (numItems); + + for (auto& item : owner.items) + itemStates.add (item); + + itemStates.sort (*this, true); + + for (auto& item : itemStates) + { + item.preferredWidth = getPreferredWidth (item); + item.preferredHeight = getPreferredHeight (item); + } + } + + void initialiseItems() noexcept + { + if (owner.flexWrap == FlexBox::Wrap::noWrap) // for single-line, all items go in line 1 + { + lineInfo[0].numItems = numItems; + int i = 0; + + for (auto& item : itemStates) + { + item.resetItemLockedSize(); + lineItems[i++] = &item; + } + } + else // if multi-line, group the flexbox items into multiple lines + { + auto currentLength = containerLineLength; + int column = 0, row = 0; + bool firstRow = true; + + for (auto& item : itemStates) + { + item.resetItemLockedSize(); + + const auto flexitemLength = getItemLength (item); + + if (flexitemLength > currentLength) + { + if (! firstRow) + row++; + + if (row >= numItems) + break; + + column = 0; + currentLength = containerLineLength; + numberOfRows = jmax (numberOfRows, row + 1); + } + + currentLength -= flexitemLength; + lineItems[row * numItems + column] = &item; + ++column; + lineInfo[row].numItems = jmax (lineInfo[row].numItems, column); + firstRow = false; + } + } + } + + void resolveFlexibleLengths() noexcept + { + for (int row = 0; row < numberOfRows; ++row) + { + resetRowItems (row); + + for (int maxLoops = numItems; --maxLoops >= 0;) + { + resetUnlockedRowItems (row); + + if (layoutRowItems (row)) + break; + } + } + } + + void resolveAutoMarginsOnMainAxis() noexcept + { + for (int row = 0; row < numberOfRows; ++row) + { + Coord allFlexGrow = 0; + const auto numColumns = lineInfo[row].numItems; + const auto remainingLength = containerLineLength - lineInfo[row].totalLength; + + for (int column = 0; column < numColumns; ++column) + { + auto& item = getItem (column, row); + + if (isRowDirection) + { + if (isAuto (item.item->margin.left)) ++allFlexGrow; + if (isAuto (item.item->margin.right)) ++allFlexGrow; + } + else + { + if (isAuto (item.item->margin.top)) ++allFlexGrow; + if (isAuto (item.item->margin.bottom)) ++allFlexGrow; + } + } + + auto changeUnitWidth = remainingLength / allFlexGrow; + + if (changeUnitWidth > 0) + { + for (int column = 0; column < numColumns; ++column) + { + auto& item = getItem (column, row); + + if (isRowDirection) + { + if (isAuto (item.item->margin.left)) item.lockedMarginLeft = changeUnitWidth; + if (isAuto (item.item->margin.right)) item.lockedMarginRight = changeUnitWidth; + } + else + { + if (isAuto (item.item->margin.top)) item.lockedMarginTop = changeUnitWidth; + if (isAuto (item.item->margin.bottom)) item.lockedMarginBottom = changeUnitWidth; + } + } + } + } + } + + void calculateCrossSizesByLine() noexcept + { + for (int row = 0; row < numberOfRows; ++row) + { + Coord maxSize = 0; + const auto numColumns = lineInfo[row].numItems; + + for (int column = 0; column < numColumns; ++column) + { + auto& item = getItem (column, row); + + maxSize = jmax (maxSize, isRowDirection ? item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom + : item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight); + } + + lineInfo[row].crossSize = maxSize; + } + } + + void calculateCrossSizeOfAllItems() noexcept + { + for (int row = 0; row < numberOfRows; ++row) + { + const auto numColumns = lineInfo[row].numItems; + + for (int column = 0; column < numColumns; ++column) + { + auto& item = getItem (column, row); + + if (isAssigned (item.item->maxHeight) && item.lockedHeight > item.item->maxHeight) + item.lockedHeight = item.item->maxHeight; + + if (isAssigned (item.item->maxWidth) && item.lockedWidth > item.item->maxWidth) + item.lockedWidth = item.item->maxWidth; + } + } + } + + void alignLinesPerAlignContent() noexcept + { + containerCrossLength = isRowDirection ? parentHeight : parentWidth; + + if (owner.alignContent == FlexBox::AlignContent::flexStart) + { + for (int row = 0; row < numberOfRows; ++row) + for (int row2 = row; row2 < numberOfRows; ++row2) + lineInfo[row].lineY = row == 0 ? 0 : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize; + } + else if (owner.alignContent == FlexBox::AlignContent::flexEnd) + { + for (int row = 0; row < numberOfRows; ++row) + { + Coord crossHeights = 0; + + for (int row2 = row; row2 < numberOfRows; ++row2) + crossHeights += lineInfo[row2].crossSize; + + lineInfo[row].lineY = containerCrossLength - crossHeights; + } + } + else + { + Coord totalHeight = 0; + + for (int row = 0; row < numberOfRows; ++row) + totalHeight += lineInfo[row].crossSize; + + if (owner.alignContent == FlexBox::AlignContent::stretch) + { + const auto difference = jmax (Coord(), (containerCrossLength - totalHeight) / numberOfRows); + + for (int row = 0; row < numberOfRows; ++row) + { + lineInfo[row].crossSize += difference; + lineInfo[row].lineY = row == 0 ? 0 : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize; + } + } + else if (owner.alignContent == FlexBox::AlignContent::center) + { + const auto additionalength = (containerCrossLength - totalHeight) / 2; + + for (int row = 0; row < numberOfRows; ++row) + lineInfo[row].lineY = row == 0 ? additionalength : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize; + } + else if (owner.alignContent == FlexBox::AlignContent::spaceBetween) + { + const auto additionalength = numberOfRows <= 1 ? Coord() : jmax (Coord(), (containerCrossLength - totalHeight) + / static_cast (numberOfRows - 1)); + lineInfo[0].lineY = 0; + + for (int row = 1; row < numberOfRows; ++row) + lineInfo[row].lineY += additionalength + lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize; + } + else if (owner.alignContent == FlexBox::AlignContent::spaceAround) + { + const auto additionalength = numberOfRows <= 1 ? Coord() : jmax (Coord(), (containerCrossLength - totalHeight) + / static_cast (2 + (2 * (numberOfRows - 1)))); + + lineInfo[0].lineY = additionalength; + + for (int row = 1; row < numberOfRows; ++row) + lineInfo[row].lineY += (2 * additionalength) + lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize; + } + } + } + + void resolveAutoMarginsOnCrossAxis() noexcept + { + for (int row = 0; row < numberOfRows; ++row) + { + const auto numColumns = lineInfo[row].numItems; + const auto crossSizeForLine = lineInfo[row].crossSize; + + for (int column = 0; column < numColumns; ++column) + { + auto& item = getItem (column, row); + + if (isRowDirection) + { + if (isAuto (item.item->margin.top) && isAuto (item.item->margin.bottom)) + item.lockedMarginTop = (crossSizeForLine - item.lockedHeight) / 2; + else if (isAuto (item.item->margin.top)) + item.lockedMarginTop = crossSizeForLine - item.lockedHeight - item.item->margin.bottom; + } + else if (isAuto (item.item->margin.left) && isAuto (item.item->margin.right)) + { + item.lockedMarginLeft = jmax (Coord(), (crossSizeForLine - item.lockedWidth) / 2); + } + else if (isAuto (item.item->margin.top)) + { + item.lockedMarginLeft = jmax (Coord(), crossSizeForLine - item.lockedHeight - item.item->margin.bottom); + } + } + } + } + + void alignItemsInCrossAxisInLinesPerAlignItems() noexcept + { + for (int row = 0; row < numberOfRows; ++row) + { + const auto numColumns = lineInfo[row].numItems; + const auto lineSize = lineInfo[row].crossSize; + + for (int column = 0; column < numColumns; ++column) + { + auto& item = getItem (column, row); + + if (item.item->alignSelf == FlexItem::AlignSelf::autoAlign) + { + if (owner.alignItems == FlexBox::AlignItems::stretch) + { + item.lockedMarginTop = item.item->margin.top; + + if (isRowDirection) + item.setHeightChecked (lineSize - item.item->margin.top - item.item->margin.bottom); + } + else if (owner.alignItems == FlexBox::AlignItems::flexStart) + { + item.lockedMarginTop = item.item->margin.top; + } + else if (owner.alignItems == FlexBox::AlignItems::flexEnd) + { + item.lockedMarginTop = lineSize - item.lockedHeight - item.item->margin.bottom; + } + else if (owner.alignItems == FlexBox::AlignItems::center) + { + item.lockedMarginTop = (lineSize - item.lockedHeight - item.item->margin.top - item.item->margin.bottom) / 2; + } + } + } + } + } + + void alignLinesPerAlignSelf() noexcept + { + for (int row = 0; row < numberOfRows; ++row) + { + const auto numColumns = lineInfo[row].numItems; + const auto lineSize = lineInfo[row].crossSize; + + for (int column = 0; column < numColumns; ++column) + { + auto& item = getItem (column, row); + + if (! isAuto (item.item->margin.top)) + { + if (item.item->alignSelf == FlexItem::AlignSelf::flexStart) + { + if (isRowDirection) + item.lockedMarginTop = item.item->margin.top; + else + item.lockedMarginLeft = item.item->margin.left; + } + else if (item.item->alignSelf == FlexItem::AlignSelf::flexEnd) + { + if (isRowDirection) + item.lockedMarginTop = lineSize - item.lockedHeight - item.item->margin.bottom; + else + item.lockedMarginLeft = lineSize - item.lockedWidth - item.item->margin.right; + } + else if (item.item->alignSelf == FlexItem::AlignSelf::center) + { + if (isRowDirection) + item.lockedMarginTop = item.item->margin.top + (lineSize - item.lockedHeight - item.item->margin.top - item.item->margin.bottom) / 2; + else + item.lockedMarginLeft = item.item->margin.left + (lineSize - item.lockedWidth - item.item->margin.left - item.item->margin.right) / 2; + } + else if (item.item->alignSelf == FlexItem::AlignSelf::stretch) + { + item.lockedMarginTop = item.item->margin.top; + item.lockedMarginLeft = item.item->margin.left; + + if (isRowDirection) + item.setHeightChecked (isAssigned (item.item->height) ? getPreferredHeight (item) + : lineSize - item.item->margin.top - item.item->margin.bottom); + else + item.setWidthChecked (isAssigned (item.item->width) ? getPreferredWidth (item) + : lineSize - item.item->margin.left - item.item->margin.right); + } + } + } + } + } + + void alignItemsByJustifyContent() noexcept + { + Coord additionalMarginRight = 0, additionalMarginLeft = 0; + + recalculateTotalItemLengthPerLineArray(); + + for (int row = 0; row < numberOfRows; ++row) + { + const auto numColumns = lineInfo[row].numItems; + Coord x = 0; + + if (owner.justifyContent == FlexBox::JustifyContent::flexEnd) + { + x = containerLineLength - lineInfo[row].totalLength; + } + else if (owner.justifyContent == FlexBox::JustifyContent::center) + { + x = (containerLineLength - lineInfo[row].totalLength) / 2; + } + else if (owner.justifyContent == FlexBox::JustifyContent::spaceBetween) + { + additionalMarginRight + = jmax (Coord(), (containerLineLength - lineInfo[row].totalLength) / jmax (1, numColumns - 1)); + } + else if (owner.justifyContent == FlexBox::JustifyContent::spaceAround) + { + additionalMarginLeft = additionalMarginRight + = jmax (Coord(), (containerLineLength - lineInfo[row].totalLength) / jmax (1, 2 * numColumns)); + } + + for (int column = 0; column < numColumns; ++column) + { + auto& item = getItem (column, row); + + if (isRowDirection) + { + item.lockedMarginLeft += additionalMarginLeft; + item.lockedMarginRight += additionalMarginRight; + item.item->currentBounds.setPosition ((float) (x + item.lockedMarginLeft), (float) item.lockedMarginTop); + x += item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight; + } + else + { + item.lockedMarginTop += additionalMarginLeft; + item.lockedMarginBottom += additionalMarginRight; + item.item->currentBounds.setPosition ((float) item.lockedMarginLeft, (float) (x + item.lockedMarginTop)); + x += item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom; + } + } + } + } + + void layoutAllItems() noexcept + { + for (int row = 0; row < numberOfRows; ++row) + { + const auto lineY = lineInfo[row].lineY; + const auto numColumns = lineInfo[row].numItems; + + for (int column = 0; column < numColumns; ++column) + { + auto& item = getItem (column, row); + + if (isRowDirection) + item.item->currentBounds.setY ((float) (lineY + item.lockedMarginTop)); + else + item.item->currentBounds.setX ((float) (lineY + item.lockedMarginLeft)); + + item.item->currentBounds.setSize ((float) item.lockedWidth, + (float) item.lockedHeight); + } + } + + reverseLocations(); + reverseWrap(); + } + + static int compareElements (const ItemWithState& i1, const ItemWithState& i2) noexcept + { + return i1.item->order < i2.item->order ? -1 : (i2.item->order < i1.item->order ? 1 : 0); + } + +private: + void resetRowItems (const int row) noexcept + { + const auto numColumns = lineInfo[row].numItems; + + for (int column = 0; column < numColumns; ++column) + resetItem (getItem (column, row)); + } + + void resetUnlockedRowItems (const int row) noexcept + { + const auto numColumns = lineInfo[row].numItems; + + for (int column = 0; column < numColumns; ++column) + { + auto& item = getItem (column, row); + + if (! item.locked) + resetItem (item); + } + } + + void resetItem (ItemWithState& item) noexcept + { + item.locked = false; + item.lockedWidth = getPreferredWidth (item); + item.lockedHeight = getPreferredHeight (item); + } + + bool layoutRowItems (const int row) noexcept + { + const auto numColumns = lineInfo[row].numItems; + auto flexContainerLength = containerLineLength; + Coord totalItemsLength = 0, totalFlexGrow = 0, totalFlexShrink = 0; + + for (int column = 0; column < numColumns; ++column) + { + const auto& item = getItem (column, row); + + if (item.locked) + { + flexContainerLength -= getItemLength (item); + } + else + { + totalItemsLength += getItemLength (item); + totalFlexGrow += item.item->flexGrow; + totalFlexShrink += item.item->flexShrink; + } + } + + Coord changeUnit = 0; + const auto difference = flexContainerLength - totalItemsLength; + const bool positiveFlexibility = difference > 0; + + if (positiveFlexibility) + { + if (totalFlexGrow != 0) + changeUnit = difference / totalFlexGrow; + } + else + { + if (totalFlexShrink != 0) + changeUnit = difference / totalFlexShrink; + } + + bool ok = true; + + for (int column = 0; column < numColumns; ++column) + { + auto& item = getItem (column, row); + + if (! item.locked) + if (! addToItemLength (item, (positiveFlexibility ? item.item->flexGrow + : item.item->flexShrink) * changeUnit, row)) + ok = false; + } + + return ok; + } + + void recalculateTotalItemLengthPerLineArray() noexcept + { + for (int row = 0; row < numberOfRows; ++row) + { + lineInfo[row].totalLength = 0; + const auto numColumns = lineInfo[row].numItems; + + for (int column = 0; column < numColumns; ++column) + { + const auto& item = getItem (column, row); + + lineInfo[row].totalLength += isRowDirection ? item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight + : item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom; + } + } + } + + void reverseLocations() noexcept + { + if (owner.flexDirection == FlexBox::Direction::rowReverse) + { + for (auto& item : owner.items) + item.currentBounds.setX ((float) (containerLineLength - item.currentBounds.getRight())); + } + else if (owner.flexDirection == FlexBox::Direction::columnReverse) + { + for (auto& item : owner.items) + item.currentBounds.setY ((float) (containerLineLength - item.currentBounds.getBottom())); + } + } + + void reverseWrap() noexcept + { + if (owner.flexWrap == FlexBox::Wrap::wrapReverse) + { + if (isRowDirection) + { + for (auto& item : owner.items) + item.currentBounds.setY ((float) (containerCrossLength - item.currentBounds.getBottom())); + } + else + { + for (auto& item : owner.items) + item.currentBounds.setX ((float) (containerCrossLength - item.currentBounds.getRight())); + } + } + } + + Coord getItemLength (const ItemWithState& item) const noexcept + { + return isRowDirection ? item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight + : item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom; + } + + Coord getItemCrossSize (const ItemWithState& item) const noexcept + { + return isRowDirection ? item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom + : item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight; + } + + bool addToItemLength (ItemWithState& item, const Coord length, int row) const noexcept + { + bool ok = false; + + if (isRowDirection) + { + const auto prefWidth = getPreferredWidth (item); + + if (isAssigned (item.item->maxWidth) && item.item->maxWidth < prefWidth + length) + { + item.lockedWidth = item.item->maxWidth; + item.locked = true; + } + else if (isAssigned (prefWidth) && item.item->minWidth > prefWidth + length) + { + item.lockedWidth = item.item->minWidth; + item.locked = true; + } + else + { + ok = true; + item.lockedWidth = prefWidth + length; + } + + lineInfo[row].totalLength += item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight; + } + else + { + const auto prefHeight = getPreferredHeight (item); + + if (isAssigned (item.item->maxHeight) && item.item->maxHeight < prefHeight + length) + { + item.lockedHeight = item.item->maxHeight; + item.locked = true; + } + else if (isAssigned (prefHeight) && item.item->minHeight > prefHeight + length) + { + item.lockedHeight = item.item->minHeight; + item.locked = true; + } + else + { + ok = true; + item.lockedHeight = prefHeight + length; + } + + lineInfo[row].totalLength += item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom; + } + + return ok; + } + + Coord getPreferredWidth (const ItemWithState& itemWithState) const noexcept + { + const auto& item = *itemWithState.item; + auto preferredWidth = (item.flexBasis > 0 && isRowDirection) + ? item.flexBasis + : (isAssigned (item.width) ? item.width : item.minWidth); + + if (isAssigned (item.minWidth) && preferredWidth < item.minWidth) return item.minWidth; + if (isAssigned (item.maxWidth) && preferredWidth > item.maxWidth) return item.maxWidth; + + return preferredWidth; + } + + Coord getPreferredHeight (const ItemWithState& itemWithState) const noexcept + { + const auto& item = *itemWithState.item; + auto preferredHeight = (item.flexBasis > 0 && ! isRowDirection) + ? item.flexBasis + : (isAssigned (item.height) ? item.height : item.minHeight); + + if (isAssigned (item.minHeight) && preferredHeight < item.minHeight) return item.minHeight; + if (isAssigned (item.maxHeight) && preferredHeight > item.maxHeight) return item.maxHeight; + + return preferredHeight; + } +}; + +//============================================================================== +FlexBox::FlexBox() noexcept {} +FlexBox::~FlexBox() noexcept {} + +FlexBox::FlexBox (JustifyContent jc) noexcept : justifyContent (jc) {} + +FlexBox::FlexBox (Direction d, Wrap w, AlignContent ac, AlignItems ai, JustifyContent jc) noexcept + : flexDirection (d), flexWrap (w), alignContent (ac), alignItems (ai), justifyContent (jc) +{} + +void FlexBox::performLayout (Rectangle targetArea) +{ + if (! items.isEmpty()) + { + FlexBoxLayoutCalculation layout (*this, targetArea.getWidth(), targetArea.getHeight()); + + layout.createStates(); + layout.initialiseItems(); + layout.resolveFlexibleLengths(); + layout.resolveAutoMarginsOnMainAxis(); + layout.calculateCrossSizesByLine(); + layout.calculateCrossSizeOfAllItems(); + layout.alignLinesPerAlignContent(); + layout.resolveAutoMarginsOnCrossAxis(); + layout.alignItemsInCrossAxisInLinesPerAlignItems(); + layout.alignLinesPerAlignSelf(); + layout.alignItemsByJustifyContent(); + layout.layoutAllItems(); + + for (auto& item : items) + { + item.currentBounds += targetArea.getPosition(); + + if (auto comp = item.associatedComponent) + comp->setBounds (item.currentBounds.getSmallestIntegerContainer()); + + if (auto box = item.associatedFlexBox) + box->performLayout (item.currentBounds); + } + } +} + +void FlexBox::performLayout (Rectangle targetArea) +{ + performLayout (targetArea.toFloat()); +} + +//============================================================================== +FlexItem::FlexItem() noexcept {} +FlexItem::FlexItem (float w, float h) noexcept : currentBounds (w, h), minWidth (w), minHeight (h) {} +FlexItem::FlexItem (float w, float h, Component& c) noexcept : FlexItem (w, h) { associatedComponent = &c; } +FlexItem::FlexItem (float w, float h, FlexBox& fb) noexcept : FlexItem (w, h) { associatedFlexBox = &fb; } +FlexItem::FlexItem (Component& c) noexcept : associatedComponent (&c) {} +FlexItem::FlexItem (FlexBox& fb) noexcept : associatedFlexBox (&fb) {} + +FlexItem::Margin::Margin() noexcept : left(), right(), top(), bottom() {} +FlexItem::Margin::Margin (float v) noexcept : left (v), right (v), top (v), bottom (v) {} + +//============================================================================== +FlexItem FlexItem::withFlex (float newFlexGrow) const noexcept +{ + auto fi = *this; + fi.flexGrow = newFlexGrow; + return fi; +} + +FlexItem FlexItem::withFlex (float newFlexGrow, float newFlexShrink) const noexcept +{ + auto fi = withFlex (newFlexGrow); + fi.flexShrink = newFlexShrink; + return fi; +} + +FlexItem FlexItem::withFlex (float newFlexGrow, float newFlexShrink, float newFlexBasis) const noexcept +{ + auto fi = withFlex (newFlexGrow, newFlexShrink); + fi.flexBasis = newFlexBasis; + return fi; +} + +FlexItem FlexItem::withWidth (float newWidth) const noexcept { auto fi = *this; fi.width = newWidth; return fi; } +FlexItem FlexItem::withHeight (float newHeight) const noexcept { auto fi = *this; fi.height = newHeight; return fi; } +FlexItem FlexItem::withMargin (Margin m) const noexcept { auto fi = *this; fi.margin = m; return fi; } diff --git a/source/modules/juce_gui_basics/layout/juce_FlexBox.h b/source/modules/juce_gui_basics/layout/juce_FlexBox.h new file mode 100644 index 000000000..7057192d3 --- /dev/null +++ b/source/modules/juce_gui_basics/layout/juce_FlexBox.h @@ -0,0 +1,102 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + + +/** + Represents a FlexBox container, which contains and manages the layout of a set + of FlexItem objects. + + To use this class, set its parameters appropriately (you can search online for + more help on exactly how the FlexBox protocol works!), then add your sub-items + to the items array, and call performLayout(). + + @see FlexItem +*/ +class JUCE_API FlexBox +{ +public: + /** Possible values for the flexDirection property. */ + enum class Direction { row, rowReverse, column, columnReverse }; + /** Possible values for the flexWrap property. */ + enum class Wrap { noWrap, wrap, wrapReverse }; + /** Possible values for the alignContent property. */ + enum class AlignContent { stretch, flexStart, flexEnd, center, spaceBetween, spaceAround }; + /** Possible values for the alignItems property. */ + enum class AlignItems { stretch, flexStart, flexEnd, center }; + /** Possible values for the justifyContent property. */ + enum class JustifyContent { flexStart, flexEnd, center, spaceBetween, spaceAround }; + + //============================================================================== + /** Creates an empty FlexBox container with default parameters. */ + FlexBox() noexcept; + + /** Creates an empty FlexBox container with these parameters. */ + FlexBox (Direction, Wrap, AlignContent, AlignItems, JustifyContent) noexcept; + + /** Creates an empty FlexBox container with the given content-justification mode. */ + FlexBox (JustifyContent) noexcept; + + /** Destructor. */ + ~FlexBox() noexcept; + + //============================================================================== + /** Lays-out the box's items within the given rectangle. */ + void performLayout (Rectangle targetArea); + + /** Lays-out the box's items within the given rectangle. */ + void performLayout (Rectangle targetArea); + + //============================================================================== + /** Specifies how flex items are placed in the flex container, and defines the + direction of the main axis. + */ + Direction flexDirection = Direction::row; + + /** Specifies whether items are forced into a single line or can be wrapped onto multiple lines. + If wrapping is allowed, this property also controls the direction in which lines are stacked. + */ + Wrap flexWrap = Wrap::noWrap; + + /** Specifies how a flex container's lines are placed within the flex container when + there is extra space on the cross-axis. + This property has no effect on single line layouts. + */ + AlignContent alignContent = AlignContent::stretch; + + /** Specifies the alignment of flex items along the cross-axis of each line. */ + AlignItems alignItems = AlignItems::stretch; + + /** Defines how the container distributes space between and around items along the main-axis. + The alignment is done after the lengths and auto margins are applied, so that if there is at + least one flexible element, with flex-grow different from 0, it will have no effect as there + won't be any available space. + */ + JustifyContent justifyContent = JustifyContent::flexStart; + + /** The set of items to lay-out. */ + Array items; + +private: + JUCE_LEAK_DETECTOR (FlexBox) +}; diff --git a/source/modules/juce_gui_basics/layout/juce_FlexItem.h b/source/modules/juce_gui_basics/layout/juce_FlexItem.h new file mode 100644 index 000000000..839031055 --- /dev/null +++ b/source/modules/juce_gui_basics/layout/juce_FlexItem.h @@ -0,0 +1,142 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + + +/** + Describes the properties of an item inside a FlexBox container. + + @see FlexBox +*/ +class JUCE_API FlexItem +{ +public: + //============================================================================== + /** Creates an item with default parameters, and zero size. */ + FlexItem() noexcept; + + /** Creates an item with the given size. */ + FlexItem (float width, float height) noexcept; + + /** Creates an item with the given size and target component. */ + FlexItem (float width, float height, Component& targetComponent) noexcept; + + /** Creates an item that represents an embedded FlexBox with a given size. */ + FlexItem (float width, float height, FlexBox& flexBoxToControl) noexcept; + + /** Creates an item with a given target component. */ + FlexItem (Component& componentToControl) noexcept; + + /** Creates an item that represents an embedded FlexBox. */ + FlexItem (FlexBox& flexBoxToControl) noexcept; + + //============================================================================== + /** The item's current bounds. */ + Rectangle currentBounds; + + /** If this is non-null, it represents a Component whose bounds are controlled by this item. */ + Component* associatedComponent = nullptr; + + /** If this is non-null, it represents a FlexBox whose bounds are controlled by this item. */ + FlexBox* associatedFlexBox = nullptr; + + /** Determines the order used to lay out items in their flex container. + Elements are laid out in ascending order of thus order value. Elements with the same order value + are laid out in the order in which they appear in the array. + */ + int order = 0; + + /** Specifies the flex grow factor of this item. + This indicates the amount of space inside the flex container the item should take up. + */ + float flexGrow = 0.0f; + + /** Specifies the flex shrink factor of the item. + This indicates the rate at which the item shrinks if there is insufficient space in + the container. + */ + float flexShrink = 1.0f; + + /** Specifies the flex-basis of the item. + This is the initial main size of a flex item in the direction of flow. It determines the size + of the content-box unless specified otherwise using box-sizing. + */ + float flexBasis = 0.0f; + + /** Possible value for the alignSelf property */ + enum class AlignSelf { autoAlign, flexStart, flexEnd, center, stretch }; + + /** This is the aligh-self property of the item. + This determines the alignment of the item along the corss-axis (perpendicular to the direction + of flow). + */ + AlignSelf alignSelf = AlignSelf::stretch; + + //============================================================================== + /** This constant can be used for sizes to indicate that 'auto' mode should be used. */ + static const int autoValue = -2; + /** This constant can be used for sizes to indicate that no value has been set. */ + static const int notAssigned = -1; + + float width = (float) notAssigned; /**< The item's width. */ + float minWidth = 0.0f; /**< The item's minimum width */ + float maxWidth = (float) notAssigned; /**< The item's maximum width */ + + float height = (float) notAssigned; /**< The item's height */ + float minHeight = 0.0f; /**< The item's minimum height */ + float maxHeight = (float) notAssigned; /**< The item's maximum height */ + + /** Represents a margin. */ + struct Margin + { + Margin() noexcept; /**< Creates a margin of size zero. */ + Margin (float size) noexcept; /**< Creates a margin with this size on all sides. */ + + float left; /**< Left margin size */ + float right; /**< Right margin size */ + float top; /**< Top margin size */ + float bottom; /**< Bottom margin size */ + }; + + /** The margin to leave around this item. */ + Margin margin; + + //============================================================================== + /** Returns a copy of this object with a new flex-grow value. */ + FlexItem withFlex (float newFlexGrow) const noexcept; + + /** Returns a copy of this object with new flex-grow and flex-shrink values. */ + FlexItem withFlex (float newFlexGrow, float newFlexShrink) const noexcept; + + /** Returns a copy of this object with new flex-grow, flex-shrink and flex-basis values. */ + FlexItem withFlex (float newFlexGrow, float newFlexShrink, float newFlexBasis) const noexcept; + + /** Returns a copy of this object with a new width. */ + FlexItem withWidth (float newWidth) const noexcept; + + /** Returns a copy of this object with a new height. */ + FlexItem withHeight (float newHeight) const noexcept; + + /** Returns a copy of this object with a new margin. */ + FlexItem withMargin (Margin) const noexcept; +}; diff --git a/source/modules/juce_gui_basics/layout/juce_GroupComponent.h b/source/modules/juce_gui_basics/layout/juce_GroupComponent.h index 02ba492ce..8ad22e939 100644 --- a/source/modules/juce_gui_basics/layout/juce_GroupComponent.h +++ b/source/modules/juce_gui_basics/layout/juce_GroupComponent.h @@ -41,8 +41,8 @@ public: @param componentName the name to give the component @param labelText the text to show at the top of the outline */ - GroupComponent (const String& componentName = String::empty, - const String& labelText = String::empty); + GroupComponent (const String& componentName = String(), + const String& labelText = String()); /** Destructor. */ ~GroupComponent(); diff --git a/source/modules/juce_gui_basics/layout/juce_MultiDocumentPanel.cpp b/source/modules/juce_gui_basics/layout/juce_MultiDocumentPanel.cpp index 69c40bb9f..b81a3f1a7 100644 --- a/source/modules/juce_gui_basics/layout/juce_MultiDocumentPanel.cpp +++ b/source/modules/juce_gui_basics/layout/juce_MultiDocumentPanel.cpp @@ -23,7 +23,7 @@ */ MultiDocumentPanelWindow::MultiDocumentPanelWindow (Colour backgroundColour) - : DocumentWindow (String::empty, backgroundColour, + : DocumentWindow (String(), backgroundColour, DocumentWindow::maximiseButton | DocumentWindow::closeButton, false) { } diff --git a/source/modules/juce_gui_basics/layout/juce_ResizableBorderComponent.h b/source/modules/juce_gui_basics/layout/juce_ResizableBorderComponent.h index bd7b57840..ffafc987a 100644 --- a/source/modules/juce_gui_basics/layout/juce_ResizableBorderComponent.h +++ b/source/modules/juce_gui_basics/layout/juce_ResizableBorderComponent.h @@ -57,9 +57,9 @@ public: Remember that when the target component is resized, it'll need to move and resize this component to keep it in place, as this won't happen automatically. - If the constrainer parameter is non-zero, then this object will be used to enforce - limits on the size and position that the component can be stretched to. Make sure - that the constrainer isn't deleted while still in use by this object. + If the constrainer parameter is not a nullptr, then this object will be used to + enforce limits on the size and position that the component can be stretched to. + Make sure that the constrainer isn't deleted while still in use by this object. @see ComponentBoundsConstrainer */ diff --git a/source/modules/juce_gui_basics/layout/juce_ResizableEdgeComponent.h b/source/modules/juce_gui_basics/layout/juce_ResizableEdgeComponent.h index 23c3689ec..9ebab6688 100644 --- a/source/modules/juce_gui_basics/layout/juce_ResizableEdgeComponent.h +++ b/source/modules/juce_gui_basics/layout/juce_ResizableEdgeComponent.h @@ -60,9 +60,9 @@ public: Remember that when the target component is resized, it'll need to move and resize this component to keep it in place, as this won't happen automatically. - If the constrainer parameter is non-zero, then this object will be used to enforce - limits on the size and position that the component can be stretched to. Make sure - that the constrainer isn't deleted while still in use by this object. + If the constrainer parameter is not a nullptr, then this object will be used to + enforce limits on the size and position that the component can be stretched to. + Make sure that the constrainer isn't deleted while still in use by this object. @see ComponentBoundsConstrainer */ diff --git a/source/modules/juce_gui_basics/layout/juce_TabbedButtonBar.cpp b/source/modules/juce_gui_basics/layout/juce_TabbedButtonBar.cpp index 04907cff9..ee9d20290 100644 --- a/source/modules/juce_gui_basics/layout/juce_TabbedButtonBar.cpp +++ b/source/modules/juce_gui_basics/layout/juce_TabbedButtonBar.cpp @@ -321,7 +321,7 @@ int TabbedButtonBar::getNumTabs() const String TabbedButtonBar::getCurrentTabName() const { TabInfo* tab = tabs [currentTabIndex]; - return tab == nullptr ? String::empty : tab->name; + return tab == nullptr ? String() : tab->name; } StringArray TabbedButtonBar::getTabNames() const diff --git a/source/modules/juce_gui_basics/layout/juce_Viewport.cpp b/source/modules/juce_gui_basics/layout/juce_Viewport.cpp index b6ac2ca97..edb0ba250 100644 --- a/source/modules/juce_gui_basics/layout/juce_Viewport.cpp +++ b/source/modules/juce_gui_basics/layout/juce_Viewport.cpp @@ -54,6 +54,7 @@ Viewport::Viewport (const String& name) Viewport::~Viewport() { + setScrollOnDragEnabled (false); deleteOrRemoveContentComp(); } @@ -109,8 +110,13 @@ int Viewport::getMaximumVisibleHeight() const { return contentHolder.getHeight Point Viewport::viewportPosToCompPos (Point pos) const { jassert (contentComp != nullptr); - return Point (jmax (jmin (0, contentHolder.getWidth() - contentComp->getWidth()), jmin (0, -(pos.x))), - jmax (jmin (0, contentHolder.getHeight() - contentComp->getHeight()), jmin (0, -(pos.y)))); + + Rectangle contentBounds = contentHolder.getLocalArea (contentComp, contentComp->getLocalBounds()); + Point p (jmax (jmin (0, contentHolder.getWidth() - contentBounds.getWidth()), jmin (0, -(pos.x))), + jmax (jmin (0, contentHolder.getHeight() - contentBounds.getHeight()), jmin (0, -(pos.y)))); + + + return p.transformedBy (contentComp->getTransform().inverted()); } void Viewport::setViewPosition (const int xPixelsOffset, const int yPixelsOffset) @@ -180,6 +186,104 @@ void Viewport::componentMovedOrResized (Component&, bool, bool) updateVisibleArea(); } +//============================================================================== +typedef AnimatedPosition ViewportDragPosition; + +struct Viewport::DragToScrollListener : private MouseListener, + private ViewportDragPosition::Listener +{ + DragToScrollListener (Viewport& v) + : viewport (v), numTouches (0), isDragging (false) + { + viewport.contentHolder.addMouseListener (this, true); + offsetX.addListener (this); + offsetY.addListener (this); + } + + ~DragToScrollListener() + { + viewport.contentHolder.removeMouseListener (this); + } + + void positionChanged (ViewportDragPosition&, double) override + { + viewport.setViewPosition (originalViewPos - Point ((int) offsetX.getPosition(), + (int) offsetY.getPosition())); + } + + void mouseDown (const MouseEvent&) override + { + ++numTouches; + } + + void mouseDrag (const MouseEvent& e) override + { + if (numTouches == 1) + { + Point totalOffset = e.getOffsetFromDragStart().toFloat(); + + if (! isDragging && totalOffset.getDistanceFromOrigin() > 8.0f) + { + isDragging = true; + + originalViewPos = viewport.getViewPosition(); + offsetX.setPosition (0.0); + offsetX.beginDrag(); + offsetY.setPosition (0.0); + offsetY.beginDrag(); + } + + if (isDragging) + { + offsetX.drag (totalOffset.x); + offsetY.drag (totalOffset.y); + } + } + } + + void mouseUp (const MouseEvent&) override + { + if (--numTouches == 0) + { + offsetX.endDrag(); + offsetY.endDrag(); + isDragging = false; + } + + jassert (numTouches >= 0); + } + + Viewport& viewport; + ViewportDragPosition offsetX, offsetY; + Point originalViewPos; + int numTouches; + bool isDragging; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DragToScrollListener) +}; + +void Viewport::setScrollOnDragEnabled (bool shouldScrollOnDrag) +{ + if (isScrollOnDragEnabled() != shouldScrollOnDrag) + { + if (shouldScrollOnDrag) + dragToScrollListener = new DragToScrollListener (*this); + else + dragToScrollListener = nullptr; + } +} + +bool Viewport::isScrollOnDragEnabled() const noexcept +{ + return dragToScrollListener != nullptr; +} + +bool Viewport::isCurrentlyScrollingOnDrag() const noexcept +{ + return dragToScrollListener != nullptr && dragToScrollListener->isDragging; +} + +//============================================================================== void Viewport::lookAndFeelChanged() { if (! customScrollBarThickness) diff --git a/source/modules/juce_gui_basics/layout/juce_Viewport.h b/source/modules/juce_gui_basics/layout/juce_Viewport.h index 50b01c43b..ee73e2770 100644 --- a/source/modules/juce_gui_basics/layout/juce_Viewport.h +++ b/source/modules/juce_gui_basics/layout/juce_Viewport.h @@ -50,7 +50,7 @@ public: The viewport is initially empty - use the setViewedComponent() method to add a child component for it to manage. */ - explicit Viewport (const String& componentName = String::empty); + explicit Viewport (const String& componentName = String()); /** Destructor. */ ~Viewport(); @@ -241,6 +241,17 @@ public: ScrollBar* getHorizontalScrollBar() noexcept { return &horizontalScrollBar; } + /** Enables or disables drag-to-scroll functionality in the viewport. */ + void setScrollOnDragEnabled (bool shouldScrollOnDrag); + + /** Returns true if drag-to-scroll functionality is enabled. */ + bool isScrollOnDragEnabled() const noexcept; + + /** Returns true if the user is currently dragging-to-scroll. + @see setScrollOnDragEnabled + */ + bool isCurrentlyScrollingOnDrag() const noexcept; + //============================================================================== /** @internal */ void resized() override; @@ -271,6 +282,11 @@ private: Component contentHolder; ScrollBar verticalScrollBar, horizontalScrollBar; + struct DragToScrollListener; + friend struct DragToScrollListener; + friend struct ContainerDeletePolicy; + ScopedPointer dragToScrollListener; + Point viewportPosToCompPos (Point) const; void updateVisibleArea(); diff --git a/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel.h b/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel.h index 3de56b309..8332a9aba 100644 --- a/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel.h +++ b/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel.h @@ -133,7 +133,7 @@ public: returned. If none has been set, it will just return Colours::black. The colour IDs for various purposes are stored as enums in the components that - they are relevent to - for an example, see Slider::ColourIds, + they are relevant to - for an example, see Slider::ColourIds, Label::ColourIds, TextEditor::ColourIds, TreeView::ColourIds, etc. If you're looking up a colour for use in drawing a component, it's usually diff --git a/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp b/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp index af92bd810..2442ef955 100644 --- a/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp +++ b/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp @@ -69,6 +69,8 @@ LookAndFeel_V2::LookAndFeel_V2() TextButton::textColourOffId, 0xff000000, ToggleButton::textColourId, 0xff000000, + ToggleButton::tickColourId, 0xff000000, + ToggleButton::tickDisabledColourId, 0xff808080, TextEditor::backgroundColourId, 0xffffffff, TextEditor::textColourId, 0xff000000, @@ -91,6 +93,8 @@ LookAndFeel_V2::LookAndFeel_V2() TreeView::backgroundColourId, 0x00000000, TreeView::dragAndDropIndicatorColourId, 0x80ff0000, TreeView::selectedItemBackgroundColourId, 0x00000000, + TreeView::oddItemsColourId, 0x00000000, + TreeView::evenItemsColourId, 0x00000000, PopupMenu::backgroundColourId, 0xffffffff, PopupMenu::textColourId, 0xff000000, @@ -265,13 +269,12 @@ void LookAndFeel_V2::drawButtonText (Graphics& g, TextButton& button, bool /*isM const int fontHeight = roundToInt (font.getHeight() * 0.6f); const int leftIndent = jmin (fontHeight, 2 + cornerSize / (button.isConnectedOnLeft() ? 4 : 2)); const int rightIndent = jmin (fontHeight, 2 + cornerSize / (button.isConnectedOnRight() ? 4 : 2)); + const int textWidth = button.getWidth() - leftIndent - rightIndent; - g.drawFittedText (button.getButtonText(), - leftIndent, - yIndent, - button.getWidth() - leftIndent - rightIndent, - button.getHeight() - yIndent * 2, - Justification::centred, 2); + if (textWidth > 0) + g.drawFittedText (button.getButtonText(), + leftIndent, yIndent, textWidth, button.getHeight() - yIndent * 2, + Justification::centred, 2); } void LookAndFeel_V2::drawTickBox (Graphics& g, Component& component, @@ -296,10 +299,11 @@ void LookAndFeel_V2::drawTickBox (Graphics& g, Component& component, tick.lineTo (3.0f, 6.0f); tick.lineTo (6.0f, 0.0f); - g.setColour (isEnabled ? Colours::black : Colours::grey); + g.setColour (component.findColour (isEnabled ? ToggleButton::tickColourId + : ToggleButton::tickDisabledColourId)); const AffineTransform trans (AffineTransform::scale (w / 9.0f, h / 9.0f) - .translated (x, y)); + .translated (x, y)); g.strokePath (tick, PathStrokeType (2.5f), trans); } @@ -1073,6 +1077,13 @@ void LookAndFeel_V2::drawMenuBarItem (Graphics& g, int width, int height, g.drawFittedText (itemText, 0, 0, width, height, Justification::centred, 1); } +Component* LookAndFeel_V2::getParentComponentForMenuOptions (const PopupMenu::Options& options) +{ + return options.getParentComponent(); +} + +void LookAndFeel_V2::preparePopupMenuWindow (Component&) {} + //============================================================================== void LookAndFeel_V2::fillTextEditorBackground (Graphics& g, int /*width*/, int /*height*/, TextEditor& textEditor) { @@ -1167,7 +1178,7 @@ Font LookAndFeel_V2::getComboBoxFont (ComboBox& box) Label* LookAndFeel_V2::createComboBoxTextBox (ComboBox&) { - return new Label (String::empty, String::empty); + return new Label (String(), String()); } void LookAndFeel_V2::positionComboBoxText (ComboBox& box, Label& label) @@ -1445,13 +1456,13 @@ void LookAndFeel_V2::drawRotarySlider (Graphics& g, int x, int y, int width, int Button* LookAndFeel_V2::createSliderButton (Slider&, const bool isIncrement) { - return new TextButton (isIncrement ? "+" : "-", String::empty); + return new TextButton (isIncrement ? "+" : "-", String()); } class LookAndFeel_V2::SliderLabelComp : public Label { public: - SliderLabelComp() : Label (String::empty, String::empty) {} + SliderLabelComp() : Label (String(), String()) {} void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) {} }; @@ -1719,6 +1730,9 @@ void LookAndFeel_V2::drawDocumentWindowTitleBar (DocumentWindow& window, Graphic int w, int h, int titleSpaceX, int titleSpaceW, const Image* icon, bool drawTitleTextOnLeft) { + if (w * h == 0) + return; + const bool isActive = window.isActiveWindow(); g.setGradientFill (ColourGradient (window.getBackgroundColour(), diff --git a/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h b/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h index aea453953..2468edeb1 100644 --- a/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h +++ b/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h @@ -162,6 +162,7 @@ public: void getIdealPopupMenuItemSize (const String& text, bool isSeparator, int standardMenuItemHeight, int& idealWidth, int& idealHeight) override; int getMenuWindowFlags() override; + void preparePopupMenuWindow (Component&) override; void drawMenuBarBackground (Graphics&, int width, int height, bool isMouseOverBar, MenuBarComponent&) override; int getMenuBarItemWidth (MenuBarComponent&, int itemIndex, const String& itemText) override; @@ -173,6 +174,8 @@ public: bool isMouseOverItem, bool isMenuOpen, bool isMouseOverBar, MenuBarComponent&) override; + Component* getParentComponentForMenuOptions (const PopupMenu::Options& options) override; + //============================================================================== void drawComboBox (Graphics&, int width, int height, bool isButtonDown, int buttonX, int buttonY, int buttonW, int buttonH, diff --git a/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp b/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp index 72f093325..774830c0a 100644 --- a/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp +++ b/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp @@ -28,6 +28,7 @@ LookAndFeel_V3::LookAndFeel_V3() const Colour textButtonColour (0xffeeeeff); setColour (TextButton::buttonColourId, textButtonColour); + setColour (TextButton::buttonOnColourId, Colour (0xff000000)); setColour (ComboBox::buttonColourId, textButtonColour); setColour (TextEditor::outlineColourId, Colours::transparentBlack); setColour (TabbedButtonBar::tabOutlineColourId, Colour (0x66000000)); diff --git a/source/modules/juce_gui_basics/menus/juce_MenuBarComponent.cpp b/source/modules/juce_gui_basics/menus/juce_MenuBarComponent.cpp index 8d746c4fc..b979ba481 100644 --- a/source/modules/juce_gui_basics/menus/juce_MenuBarComponent.cpp +++ b/source/modules/juce_gui_basics/menus/juce_MenuBarComponent.cpp @@ -143,6 +143,11 @@ void MenuBarComponent::setOpenItem (int index) { if (currentPopupIndex != index) { + if (currentPopupIndex < 0 && index >= 0) + model->handleMenuBarActivate (true); + else if (currentPopupIndex >= 0 && index < 0) + model->handleMenuBarActivate (false); + repaintMenuItem (currentPopupIndex); currentPopupIndex = index; repaintMenuItem (currentPopupIndex); diff --git a/source/modules/juce_gui_basics/menus/juce_MenuBarModel.cpp b/source/modules/juce_gui_basics/menus/juce_MenuBarModel.cpp index d93642ffa..ccfcbc49b 100644 --- a/source/modules/juce_gui_basics/menus/juce_MenuBarModel.cpp +++ b/source/modules/juce_gui_basics/menus/juce_MenuBarModel.cpp @@ -82,3 +82,12 @@ void MenuBarModel::applicationCommandListChanged() { menuItemsChanged(); } + +void MenuBarModel::handleMenuBarActivate (bool isActive) +{ + menuBarActivated (isActive); + listeners.call (&MenuBarModel::Listener::menuBarActivated, this, isActive); +} + +void MenuBarModel::menuBarActivated (bool) {} +void MenuBarModel::Listener::menuBarActivated (MenuBarModel*, bool) {} diff --git a/source/modules/juce_gui_basics/menus/juce_MenuBarModel.h b/source/modules/juce_gui_basics/menus/juce_MenuBarModel.h index 0fc06e7f9..4267661f6 100644 --- a/source/modules/juce_gui_basics/menus/juce_MenuBarModel.h +++ b/source/modules/juce_gui_basics/menus/juce_MenuBarModel.h @@ -88,6 +88,10 @@ public: */ virtual void menuCommandInvoked (MenuBarModel* menuBarModel, const ApplicationCommandTarget::InvocationInfo& info) = 0; + + /** Called when the menu bar is first activated or when the user finished interacting + with the menu bar. */ + virtual void menuBarActivated (MenuBarModel* menuBarModel, bool isActive); }; /** Registers a listener for callbacks when the menu items in this model change. @@ -126,6 +130,12 @@ public: virtual void menuItemSelected (int menuItemID, int topLevelMenuIndex) = 0; + /** This is called when the user starts/stops navigating the maenu bar. + + @param isActive true when the user starts navigating the menu bar + */ + virtual void menuBarActivated (bool isActive); + //============================================================================== #if JUCE_MAC || DOXYGEN /** OSX ONLY - Sets the model that is currently being shown as the main @@ -147,7 +157,7 @@ public: */ static void setMacMainMenu (MenuBarModel* newMenuBarModel, const PopupMenu* extraAppleMenuItems = nullptr, - const String& recentItemsMenuName = String::empty); + const String& recentItemsMenuName = String()); /** OSX ONLY - Returns the menu model that is currently being shown as the main menu bar. @@ -167,7 +177,8 @@ public: void applicationCommandListChanged() override; /** @internal */ void handleAsyncUpdate() override; - + /** @internal */ + void handleMenuBarActivate (bool isActive); private: ApplicationCommandManager* manager; ListenerList listeners; diff --git a/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp b/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp index 2418a5281..248cfd47b 100644 --- a/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp +++ b/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp @@ -22,120 +22,67 @@ ============================================================================== */ -//============================================================================== namespace PopupMenuSettings { const int scrollZone = 24; const int borderSize = 2; - const int timerInterval = 50; const int dismissCommandId = 0x6287345f; - const int sectionHeaderID = 0x4734a34f; static bool menuWasHiddenBecauseOfAppChange = false; } -class PopupMenu::Item +//============================================================================== +struct PopupMenu::HelperClasses { -public: - Item() : itemID (0), isActive (true), isSeparator (true), isTicked (false), - usesColour (false), commandManager (nullptr) - {} - - Item (const int itemId, - const String& name, - const bool active, - const bool ticked, - Drawable* drawable, - const Colour colour, - const bool useColour, - CustomComponent* const custom, - const PopupMenu* const sub, - ApplicationCommandManager* const manager) - - : itemID (itemId), text (name), textColour (colour), - isActive (active), isSeparator (false), isTicked (ticked), - usesColour (useColour), iconDrawable (drawable), - customComp (custom), subMenu (createCopyIfNotNull (sub)), commandManager (manager) - { - if (commandManager != nullptr && itemID != 0) - { - String shortcutKey; - const Array keyPresses (commandManager->getKeyMappings() - ->getKeyPressesAssignedToCommand (itemID)); - - for (int i = 0; i < keyPresses.size(); ++i) - { - const String key (keyPresses.getReference(i).getTextDescriptionWithIcons()); - - if (shortcutKey.isNotEmpty()) - shortcutKey << ", "; - - if (key.length() == 1 && key[0] < 128) - shortcutKey << "shortcut: '" << key << '\''; - else - shortcutKey << key; - } +class MouseSourceState; +class MenuWindow; - shortcutKey = shortcutKey.trim(); +static bool canBeTriggered (const PopupMenu::Item& item) noexcept { return item.isEnabled && item.itemID != 0 && ! item.isSectionHeader; } +static bool hasActiveSubMenu (const PopupMenu::Item& item) noexcept { return item.isEnabled && item.subMenu != nullptr && item.subMenu->items.size() > 0; } +static const Colour* getColour (const PopupMenu::Item& item) noexcept { return item.colour != Colour (0x00000000) ? &item.colour : nullptr; } +static bool hasSubMenu (const PopupMenu::Item& item) noexcept { return item.subMenu != nullptr && (item.itemID == 0 || item.subMenu->getNumItems() > 0); } - if (shortcutKey.isNotEmpty()) - text << "" << shortcutKey; - } +//============================================================================== +struct HeaderItemComponent : public PopupMenu::CustomComponent +{ + HeaderItemComponent (const String& name) : PopupMenu::CustomComponent (false) + { + setName (name); } - Item (const Item& other) - : itemID (other.itemID), - text (other.text), - textColour (other.textColour), - isActive (other.isActive), - isSeparator (other.isSeparator), - isTicked (other.isTicked), - usesColour (other.usesColour), - iconDrawable (other.iconDrawable != nullptr ? other.iconDrawable->createCopy() : nullptr), - customComp (other.customComp), - subMenu (createCopyIfNotNull (other.subMenu.get())), - commandManager (other.commandManager) - {} - - bool canBeTriggered() const noexcept { return isActive && itemID != 0 && itemID != PopupMenuSettings::sectionHeaderID; } - bool hasActiveSubMenu() const noexcept { return isActive && subMenu != nullptr && subMenu->items.size() > 0; } - - //============================================================================== - const int itemID; - String text; - const Colour textColour; - const bool isActive, isSeparator, isTicked, usesColour; - ScopedPointer iconDrawable; - ReferenceCountedObjectPtr customComp; - ScopedPointer subMenu; - ApplicationCommandManager* const commandManager; + void paint (Graphics& g) override + { + getLookAndFeel().drawPopupMenuSectionHeader (g, getLocalBounds(), getName()); + } -private: - Item& operator= (const Item&); + void getIdealSize (int& idealWidth, int& idealHeight) override + { + getLookAndFeel().getIdealPopupMenuItemSize (getName(), false, -1, idealWidth, idealHeight); + idealHeight += idealHeight / 2; + idealWidth += idealWidth / 4; + } - JUCE_LEAK_DETECTOR (Item) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HeaderItemComponent) }; - //============================================================================== -struct PopupMenu::HelperClasses +struct ItemComponent : public Component { - -class MouseSourceState; -class MenuWindow; - -//============================================================================== -class ItemComponent : public Component -{ -public: - ItemComponent (const PopupMenu::Item& info, int standardItemHeight, MenuWindow& parent) - : itemInfo (info), + ItemComponent (const PopupMenu::Item& i, int standardItemHeight, MenuWindow& parent) + : item (i), + customComp (i.customComponent), isHighlighted (false) { - addAndMakeVisible (itemInfo.customComp); + if (item.isSectionHeader) + customComp = new HeaderItemComponent (item.text); + + addAndMakeVisible (customComp); + parent.addAndMakeVisible (this); + updateShortcutKeyDescription(); + int itemW = 80; int itemH = 16; getIdealSize (itemW, itemH, standardItemHeight); @@ -146,45 +93,33 @@ public: ~ItemComponent() { - removeChildComponent (itemInfo.customComp); + removeChildComponent (customComp); } void getIdealSize (int& idealWidth, int& idealHeight, const int standardItemHeight) { - if (itemInfo.customComp != nullptr) - itemInfo.customComp->getIdealSize (idealWidth, idealHeight); + if (customComp != nullptr) + customComp->getIdealSize (idealWidth, idealHeight); else - getLookAndFeel().getIdealPopupMenuItemSize (itemInfo.text, - itemInfo.isSeparator, + getLookAndFeel().getIdealPopupMenuItemSize (getTextForMeasurement(), + item.isSeparator, standardItemHeight, idealWidth, idealHeight); } void paint (Graphics& g) override { - if (itemInfo.customComp == nullptr) - { - String mainText (itemInfo.text); - String endText; - const int endIndex = mainText.indexOf (""); - - if (endIndex >= 0) - { - endText = mainText.substring (endIndex + 5).trim(); - mainText = mainText.substring (0, endIndex); - } - - getLookAndFeel() - .drawPopupMenuItem (g, getLocalBounds(), - itemInfo.isSeparator, - itemInfo.isActive, - isHighlighted, - itemInfo.isTicked, - itemInfo.subMenu != nullptr && (itemInfo.itemID == 0 || itemInfo.subMenu->getNumItems() > 0), - mainText, endText, - itemInfo.iconDrawable, - itemInfo.usesColour ? &(itemInfo.textColour) : nullptr); - } + if (customComp == nullptr) + getLookAndFeel().drawPopupMenuItem (g, getLocalBounds(), + item.isSeparator, + item.isEnabled, + isHighlighted, + item.isTicked, + hasSubMenu (item), + item.text, + item.shortcutKeyDescription, + item.image, + getColour (item)); } void resized() override @@ -195,24 +130,59 @@ public: void setHighlighted (bool shouldBeHighlighted) { - shouldBeHighlighted = shouldBeHighlighted && itemInfo.isActive; + shouldBeHighlighted = shouldBeHighlighted && item.isEnabled; if (isHighlighted != shouldBeHighlighted) { isHighlighted = shouldBeHighlighted; - if (itemInfo.customComp != nullptr) - itemInfo.customComp->setHighlighted (shouldBeHighlighted); + if (customComp != nullptr) + customComp->setHighlighted (shouldBeHighlighted); repaint(); } } - PopupMenu::Item itemInfo; + PopupMenu::Item item; private: + // NB: we use a copy of the one from the item info in case we're using our own section comp + ReferenceCountedObjectPtr customComp; bool isHighlighted; + void updateShortcutKeyDescription() + { + if (item.commandManager != nullptr + && item.itemID != 0 + && item.shortcutKeyDescription.isEmpty()) + { + String shortcutKey; + const Array keyPresses (item.commandManager->getKeyMappings() + ->getKeyPressesAssignedToCommand (item.itemID)); + + for (int i = 0; i < keyPresses.size(); ++i) + { + const String key (keyPresses.getReference(i).getTextDescriptionWithIcons()); + + if (shortcutKey.isNotEmpty()) + shortcutKey << ", "; + + if (key.length() == 1 && key[0] < 128) + shortcutKey << "shortcut: '" << key << '\''; + else + shortcutKey << key; + } + + item.shortcutKeyDescription = shortcutKey.trim(); + } + } + + String getTextForMeasurement() const + { + return item.shortcutKeyDescription.isNotEmpty() ? item.text + " " + item.shortcutKeyDescription + : item.text; + } + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemComponent) }; @@ -230,6 +200,7 @@ public: options (opts), managerOfChosenCommand (manager), componentAttachedTo (options.targetComponent), + parentComponent (nullptr), hasBeenOver (false), needsToScroll (false), dismissOnMouseUp (shouldDismissOnMouseUp), @@ -250,7 +221,11 @@ public: setLookAndFeel (parent != nullptr ? &(parent->getLookAndFeel()) : menu.lookAndFeel.get()); - setOpaque (getLookAndFeel().findColour (PopupMenu::backgroundColourId).isOpaque() + LookAndFeel& lf = getLookAndFeel(); + + parentComponent = lf.getParentComponentForMenuOptions (options); + + setOpaque (lf.findColour (PopupMenu::backgroundColourId).isOpaque() || ! Desktop::canUseSemiTransparentWindows()); for (int i = 0; i < menu.items.size(); ++i) @@ -267,18 +242,32 @@ public: if (options.visibleItemID != 0) { - const int y = options.targetArea.getY() - windowPos.getY(); + const Point targetPosition = + (parentComponent != nullptr ? parentComponent->getLocalPoint (nullptr, options.targetArea.getTopLeft()) + : options.targetArea.getTopLeft()); + + const int y = targetPosition.getY() - windowPos.getY(); ensureItemIsVisible (options.visibleItemID, isPositiveAndBelow (y, windowPos.getHeight()) ? y : -1); } resizeToBestWindowPos(); - addToDesktop (ComponentPeer::windowIsTemporary - | ComponentPeer::windowIgnoresKeyPresses - | getLookAndFeel().getMenuWindowFlags()); - getActiveWindows().add (this); - Desktop::getInstance().addGlobalMouseListener (this); + if (parentComponent != nullptr) + { + parentComponent->addChildComponent (this); + } + else + { + addToDesktop (ComponentPeer::windowIsTemporary + | ComponentPeer::windowIgnoresKeyPresses + | lf.getMenuWindowFlags()); + + getActiveWindows().add (this); + Desktop::getInstance().addGlobalMouseListener (this); + } + + lf.preparePopupMenuWindow (*this); } ~MenuWindow() @@ -300,10 +289,13 @@ public: void paintOverChildren (Graphics& g) override { + LookAndFeel& lf = getLookAndFeel(); + + if (parentComponent != nullptr) + lf.drawResizableFrame (g, getWidth(), getHeight(), BorderSize (PopupMenuSettings::borderSize)); + if (canScroll()) { - LookAndFeel& lf = getLookAndFeel(); - if (isTopScrollZoneActive()) lf.drawPopupMenuUpDownArrow (g, getWidth(), PopupMenuSettings::scrollZone, true); @@ -333,13 +325,25 @@ public: *managerOfChosenCommand = item->commandManager; } - exitModalState (item != nullptr ? item->itemID : 0); + exitModalState (getResultItemID (item)); if (makeInvisible && (deletionChecker != nullptr)) setVisible (false); } } + static int getResultItemID (const PopupMenu::Item* item) + { + if (item == nullptr) + return 0; + + if (CustomCallback* cc = item->customCallback) + if (! cc->menuItemTriggered()) + return 0; + + return item->itemID; + } + void dismissMenu (const PopupMenu::Item* const item) { if (parent != nullptr) @@ -439,7 +443,7 @@ public: { // we want to dismiss the menu, but if we do it synchronously, then // the mouse-click will be allowed to pass through. That's good, except - // when the user clicks on the button that orginally popped the menu up, + // when the user clicks on the button that originally popped the menu up, // as they'll expect the menu to go away, and in fact it'll just // come back. So only dismiss synchronously if they're not on the original // comp that we're attached to. @@ -583,27 +587,43 @@ public: } //============================================================================== - void calculateWindowPos (const Rectangle& target, const bool alignToRectangle) + Rectangle getParentArea (Point targetPoint) { - const Rectangle mon (Desktop::getInstance().getDisplays() - .getDisplayContaining (target.getCentre()) - #if JUCE_MAC - .userArea); - #else - .totalArea); // on windows, don't stop the menu overlapping the taskbar - #endif + Rectangle parentArea (Desktop::getInstance().getDisplays() + .getDisplayContaining (targetPoint) + #if JUCE_MAC + .userArea); + #else + .totalArea); // on windows, don't stop the menu overlapping the taskbar + #endif - const int maxMenuHeight = mon.getHeight() - 24; + if (parentComponent == nullptr) + return parentArea; + + return parentComponent->getLocalArea (nullptr, + parentComponent->getScreenBounds() + .reduced (PopupMenuSettings::borderSize) + .getIntersection (parentArea)); + } + + void calculateWindowPos (Rectangle target, const bool alignToRectangle) + { + const Rectangle parentArea = getParentArea (target.getCentre()); + + if (parentComponent != nullptr) + target = parentComponent->getLocalArea (nullptr, target).getIntersection (parentArea); + + const int maxMenuHeight = parentArea.getHeight() - 24; int x, y, widthToUse, heightToUse; - layoutMenuItems (mon.getWidth() - 24, maxMenuHeight, widthToUse, heightToUse); + layoutMenuItems (parentArea.getWidth() - 24, maxMenuHeight, widthToUse, heightToUse); if (alignToRectangle) { x = target.getX(); - const int spaceUnder = mon.getHeight() - (target.getBottom() - mon.getY()); - const int spaceOver = target.getY() - mon.getY(); + const int spaceUnder = parentArea.getHeight() - (target.getBottom() - parentArea.getY()); + const int spaceOver = target.getY() - parentArea.getY(); if (heightToUse < spaceUnder - 30 || spaceUnder >= spaceOver) y = target.getBottom(); @@ -612,7 +632,7 @@ public: } else { - bool tendTowardsRight = target.getCentreX() < mon.getCentreX(); + bool tendTowardsRight = target.getCentreX() < parentArea.getCentreX(); if (parent != nullptr) { @@ -621,19 +641,19 @@ public: const bool parentGoingRight = (parent->getX() + parent->getWidth() / 2 > parent->parent->getX() + parent->parent->getWidth() / 2); - if (parentGoingRight && target.getRight() + widthToUse < mon.getRight() - 4) + if (parentGoingRight && target.getRight() + widthToUse < parentArea.getRight() - 4) tendTowardsRight = true; else if ((! parentGoingRight) && target.getX() > widthToUse + 4) tendTowardsRight = false; } - else if (target.getRight() + widthToUse < mon.getRight() - 32) + else if (target.getRight() + widthToUse < parentArea.getRight() - 32) { tendTowardsRight = true; } } - const int biggestSpace = jmax (mon.getRight() - target.getRight(), - target.getX() - mon.getX()) - 32; + const int biggestSpace = jmax (parentArea.getRight() - target.getRight(), + target.getX() - parentArea.getX()) - 32; if (biggestSpace < widthToUse) { @@ -642,21 +662,21 @@ public: if (numColumns > 1) layoutMenuItems (biggestSpace - 4, maxMenuHeight, widthToUse, heightToUse); - tendTowardsRight = (mon.getRight() - target.getRight()) >= (target.getX() - mon.getX()); + tendTowardsRight = (parentArea.getRight() - target.getRight()) >= (target.getX() - parentArea.getX()); } if (tendTowardsRight) - x = jmin (mon.getRight() - widthToUse - 4, target.getRight()); + x = jmin (parentArea.getRight() - widthToUse - 4, target.getRight()); else - x = jmax (mon.getX() + 4, target.getX() - widthToUse); + x = jmax (parentArea.getX() + 4, target.getX() - widthToUse); y = target.getY(); - if (target.getCentreY() > mon.getCentreY()) - y = jmax (mon.getY(), target.getBottom() - heightToUse); + if (target.getCentreY() > parentArea.getCentreY()) + y = jmax (parentArea.getY(), target.getBottom() - heightToUse); } - x = jmax (mon.getX() + 1, jmin (mon.getRight() - (widthToUse + 6), x)); - y = jmax (mon.getY() + 1, jmin (mon.getBottom() - (heightToUse + 6), y)); + x = jmax (parentArea.getX() + 1, jmin (parentArea.getRight() - (widthToUse + 6), x)); + y = jmax (parentArea.getY() + 1, jmin (parentArea.getBottom() - (heightToUse + 6), y)); windowPos.setBounds (x, y, widthToUse, heightToUse); @@ -727,9 +747,12 @@ public: childNum += numChildren; } - if (totalW < options.minWidth) + // width must never be larger than the screen + const int minWidth = jmin (maxMenuW, options.minWidth); + + if (totalW < minWidth) { - totalW = options.minWidth; + totalW = minWidth; for (int col = 0; col < numColumns; ++col) columnWidths.set (0, totalW / numColumns); @@ -744,43 +767,42 @@ public: for (int i = items.size(); --i >= 0;) { - ItemComponent* const m = items.getUnchecked(i); - - if (m != nullptr - && m->itemInfo.itemID == itemID - && windowPos.getHeight() > PopupMenuSettings::scrollZone * 4) + if (ItemComponent* const m = items.getUnchecked(i)) { - const int currentY = m->getY(); - - if (wantedY > 0 || currentY < 0 || m->getBottom() > windowPos.getHeight()) + if (m->item.itemID == itemID + && windowPos.getHeight() > PopupMenuSettings::scrollZone * 4) { - if (wantedY < 0) - wantedY = jlimit (PopupMenuSettings::scrollZone, - jmax (PopupMenuSettings::scrollZone, - windowPos.getHeight() - (PopupMenuSettings::scrollZone + m->getHeight())), - currentY); + const int currentY = m->getY(); - const Rectangle mon (Desktop::getInstance().getDisplays() - .getDisplayContaining (windowPos.getPosition()).userArea); + if (wantedY > 0 || currentY < 0 || m->getBottom() > windowPos.getHeight()) + { + if (wantedY < 0) + wantedY = jlimit (PopupMenuSettings::scrollZone, + jmax (PopupMenuSettings::scrollZone, + windowPos.getHeight() - (PopupMenuSettings::scrollZone + m->getHeight())), + currentY); - int deltaY = wantedY - currentY; + const Rectangle parantArea = getParentArea (windowPos.getPosition()); - windowPos.setSize (jmin (windowPos.getWidth(), mon.getWidth()), - jmin (windowPos.getHeight(), mon.getHeight())); + int deltaY = wantedY - currentY; - const int newY = jlimit (mon.getY(), - mon.getBottom() - windowPos.getHeight(), - windowPos.getY() + deltaY); + windowPos.setSize (jmin (windowPos.getWidth(), parantArea.getWidth()), + jmin (windowPos.getHeight(), parantArea.getHeight())); - deltaY -= newY - windowPos.getY(); + const int newY = jlimit (parantArea.getY(), + parantArea.getBottom() - windowPos.getHeight(), + windowPos.getY() + deltaY); - childYOffset -= deltaY; - windowPos.setPosition (windowPos.getX(), newY); + deltaY -= newY - windowPos.getY(); - updateYPositions(); - } + childYOffset -= deltaY; + windowPos.setPosition (windowPos.getX(), newY); - break; + updateYPositions(); + } + + break; + } } } } @@ -877,9 +899,9 @@ public: activeSubMenu = nullptr; if (childComp != nullptr - && childComp->itemInfo.hasActiveSubMenu()) + && hasActiveSubMenu (childComp->item)) { - activeSubMenu = new HelperClasses::MenuWindow (*(childComp->itemInfo.subMenu), this, + activeSubMenu = new HelperClasses::MenuWindow (*(childComp->item.subMenu), this, options.withTargetScreenArea (childComp->getScreenBounds()) .withMinimumWidth (0) .withTargetComponent (nullptr), @@ -897,11 +919,11 @@ public: void triggerCurrentlyHighlightedItem() { if (currentChild != nullptr - && currentChild->itemInfo.canBeTriggered() - && (currentChild->itemInfo.customComp == nullptr - || currentChild->itemInfo.customComp->isTriggeredAutomatically())) + && canBeTriggered (currentChild->item) + && (currentChild->item.customComponent == nullptr + || currentChild->item.customComponent->isTriggeredAutomatically())) { - dismissMenu (¤tChild->itemInfo); + dismissMenu (¤tChild->item); } } @@ -917,7 +939,7 @@ public: if (ItemComponent* mic = items.getUnchecked ((start + items.size()) % items.size())) { - if (mic->itemInfo.canBeTriggered() || mic->itemInfo.hasActiveSubMenu()) + if (canBeTriggered (mic->item) || hasActiveSubMenu (mic->item)) { setCurrentlyHighlightedChild (mic); break; @@ -944,6 +966,7 @@ public: OwnedArray items; ApplicationCommandManager** managerOfChosenCommand; WeakReference componentAttachedTo; + Component* parentComponent; Rectangle windowPos; bool hasBeenOver, needsToScroll; bool dismissOnMouseUp, hideOnExit, disableMouseMoves, hasAnyJuceCompHadFocus; @@ -973,7 +996,7 @@ public: if (! window.windowIsStillValid()) return; - startTimer (PopupMenuSettings::timerInterval); + startTimerHz (20); handleMousePosition (e.getScreenPosition()); } @@ -1175,9 +1198,8 @@ private: }; //============================================================================== -class NormalComponentWrapper : public PopupMenu::CustomComponent +struct NormalComponentWrapper : public PopupMenu::CustomComponent { -public: NormalComponentWrapper (Component* const comp, const int w, const int h, const bool triggerMenuItemAutomaticallyWhenClicked) : PopupMenu::CustomComponent (triggerMenuItemAutomaticallyWhenClicked), @@ -1198,38 +1220,11 @@ public: child->setBounds (getLocalBounds()); } -private: const int width, height; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NormalComponentWrapper) }; -//============================================================================== -class HeaderItemComponent : public PopupMenu::CustomComponent -{ -public: - HeaderItemComponent (const String& name) - : PopupMenu::CustomComponent (false) - { - setName (name); - } - - void paint (Graphics& g) override - { - getLookAndFeel().drawPopupMenuSectionHeader (g, getLocalBounds(), getName()); - } - - void getIdealSize (int& idealWidth, int& idealHeight) override - { - getLookAndFeel().getIdealPopupMenuItemSize (getName(), false, -1, idealWidth, idealHeight); - idealHeight += idealHeight / 2; - idealWidth += idealWidth / 4; - } - -private: - JUCE_LEAK_DETECTOR (HeaderItemComponent) -}; - }; //============================================================================== @@ -1282,14 +1277,72 @@ void PopupMenu::clear() items.clear(); } -void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked) +//============================================================================== +PopupMenu::Item::Item() noexcept + : itemID (0), + commandManager (nullptr), + colour (0x00000000), + isEnabled (true), + isTicked (false), + isSeparator (false), + isSectionHeader (false) +{ +} + +PopupMenu::Item::Item (const Item& other) + : text (other.text), + itemID (other.itemID), + subMenu (createCopyIfNotNull (other.subMenu.get())), + image (other.image != nullptr ? other.image->createCopy() : nullptr), + customComponent (other.customComponent), + customCallback (other.customCallback), + commandManager (other.commandManager), + shortcutKeyDescription (other.shortcutKeyDescription), + colour (other.colour), + isEnabled (other.isEnabled), + isTicked (other.isTicked), + isSeparator (other.isSeparator), + isSectionHeader (other.isSectionHeader) +{ +} + +PopupMenu::Item& PopupMenu::Item::operator= (const Item& other) +{ + text = other.text; + itemID = other.itemID; + subMenu = createCopyIfNotNull (other.subMenu.get()); + image = (other.image != nullptr ? other.image->createCopy() : nullptr); + customComponent = other.customComponent; + customCallback = other.customCallback; + commandManager = other.commandManager; + shortcutKeyDescription = other.shortcutKeyDescription; + colour = other.colour; + isEnabled = other.isEnabled; + isTicked = other.isTicked; + isSeparator = other.isSeparator; + isSectionHeader = other.isSectionHeader; + return *this; +} + +void PopupMenu::addItem (const Item& newItem) { - jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user - // didn't pick anything, so you shouldn't use it as the id - // for an item.. + // An ID of 0 is used as a return value to indicate that the user + // didn't pick anything, so you shouldn't use it as the ID for an item.. + jassert (newItem.itemID != 0 + || newItem.isSeparator || newItem.isSectionHeader + || newItem.subMenu != nullptr); - items.add (new Item (itemResultID, itemText, isActive, isTicked, nullptr, - Colours::black, false, nullptr, nullptr, nullptr)); + items.add (new Item (newItem)); +} + +void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked) +{ + Item i; + i.text = itemText; + i.itemID = itemResultID; + i.isEnabled = isActive; + i.isTicked = isTicked; + addItem (i); } static Drawable* createDrawableFromImage (const Image& im) @@ -1306,23 +1359,18 @@ static Drawable* createDrawableFromImage (const Image& im) void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked, const Image& iconToUse) { - jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user - // didn't pick anything, so you shouldn't use it as the id - // for an item.. - - - items.add (new Item (itemResultID, itemText, isActive, isTicked, createDrawableFromImage (iconToUse), - Colours::black, false, nullptr, nullptr, nullptr)); + addItem (itemResultID, itemText, isActive, isTicked, createDrawableFromImage (iconToUse)); } void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked, Drawable* iconToUse) { - jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user - // didn't pick anything, so you shouldn't use it as the id - // for an item.. - - items.add (new Item (itemResultID, itemText, isActive, isTicked, iconToUse, - Colours::black, false, nullptr, nullptr, nullptr)); + Item i; + i.text = itemText; + i.itemID = itemResultID; + i.isEnabled = isActive; + i.isTicked = isTicked; + i.image = iconToUse; + addItem (i); } void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager, @@ -1337,53 +1385,59 @@ void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager, ApplicationCommandInfo info (*registeredInfo); ApplicationCommandTarget* const target = commandManager->getTargetForCommand (commandID, info); - items.add (new Item (commandID, - displayName.isNotEmpty() ? displayName - : info.shortName, - target != nullptr && (info.flags & ApplicationCommandInfo::isDisabled) == 0, - (info.flags & ApplicationCommandInfo::isTicked) != 0, - iconToUse, - Colours::black, - false, - nullptr, nullptr, - commandManager)); + Item i; + i.text = displayName.isNotEmpty() ? displayName : info.shortName; + i.itemID = (int) commandID; + i.commandManager = commandManager; + i.isEnabled = target != nullptr && (info.flags & ApplicationCommandInfo::isDisabled) == 0; + i.isTicked = (info.flags & ApplicationCommandInfo::isTicked) != 0; + i.image = iconToUse; + addItem (i); } } void PopupMenu::addColouredItem (int itemResultID, const String& itemText, Colour itemTextColour, bool isActive, bool isTicked, Drawable* iconToUse) { - jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user - // didn't pick anything, so you shouldn't use it as the id - // for an item.. - - items.add (new Item (itemResultID, itemText, isActive, isTicked, iconToUse, - itemTextColour, true, nullptr, nullptr, nullptr)); + Item i; + i.text = itemText; + i.itemID = itemResultID; + i.colour = itemTextColour; + i.isEnabled = isActive; + i.isTicked = isTicked; + i.image = iconToUse; + addItem (i); } void PopupMenu::addColouredItem (int itemResultID, const String& itemText, Colour itemTextColour, bool isActive, bool isTicked, const Image& iconToUse) { - addColouredItem (itemResultID, itemText, itemTextColour, isActive, isTicked, createDrawableFromImage (iconToUse)); + Item i; + i.text = itemText; + i.itemID = itemResultID; + i.colour = itemTextColour; + i.isEnabled = isActive; + i.isTicked = isTicked; + i.image = createDrawableFromImage (iconToUse); + addItem (i); } -void PopupMenu::addCustomItem (int itemID, CustomComponent* cc, const PopupMenu* subMenu) +void PopupMenu::addCustomItem (int itemResultID, CustomComponent* cc, const PopupMenu* subMenu) { - jassert (itemID != 0); // 0 is used as a return value to indicate that the user - // didn't pick anything, so you shouldn't use it as the id - // for an item.. - - items.add (new Item (itemID, String::empty, true, false, nullptr, - Colours::black, false, cc, subMenu, nullptr)); + Item i; + i.itemID = itemResultID; + i.customComponent = cc; + i.subMenu = createCopyIfNotNull (subMenu); + addItem (i); } void PopupMenu::addCustomItem (int itemResultID, Component* customComponent, int idealWidth, int idealHeight, bool triggerMenuItemAutomaticallyWhenClicked, const PopupMenu* subMenu) { - items.add (new Item (itemResultID, String::empty, true, false, nullptr, Colours::black, false, - new HelperClasses::NormalComponentWrapper (customComponent, idealWidth, idealHeight, - triggerMenuItemAutomaticallyWhenClicked), - subMenu, nullptr)); + addCustomItem (itemResultID, + new HelperClasses::NormalComponentWrapper (customComponent, idealWidth, idealHeight, + triggerMenuItemAutomaticallyWhenClicked), + subMenu); } void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, bool isActive) @@ -1400,24 +1454,38 @@ void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, bool isActive, Drawable* iconToUse, bool isTicked, int itemResultID) { - items.add (new Item (itemResultID, subMenuName, isActive && (itemResultID != 0 || subMenu.getNumItems() > 0), isTicked, - iconToUse, Colours::black, false, nullptr, &subMenu, nullptr)); + Item i; + i.text = subMenuName; + i.itemID = itemResultID; + i.subMenu = new PopupMenu (subMenu); + i.isEnabled = isActive && (itemResultID != 0 || subMenu.getNumItems() > 0); + i.isTicked = isTicked; + i.image = iconToUse; + addItem (i); } void PopupMenu::addSeparator() { if (items.size() > 0 && ! items.getLast()->isSeparator) - items.add (new Item()); + { + Item i; + i.isSeparator = true; + addItem (i); + } } void PopupMenu::addSectionHeader (const String& title) { - addCustomItem (PopupMenuSettings::sectionHeaderID, new HelperClasses::HeaderItemComponent (title)); + Item i; + i.text = title; + i.isSectionHeader = true; + addItem (i); } //============================================================================== PopupMenu::Options::Options() : targetComponent (nullptr), + parentComponent (nullptr), visibleItemID (0), minWidth (0), maxColumns (0), @@ -1472,6 +1540,13 @@ PopupMenu::Options PopupMenu::Options::withItemThatMustBeVisible (int idOfItemTo return o; } +PopupMenu::Options PopupMenu::Options::withParentComponent (Component* parent) const noexcept +{ + Options o (*this); + o.parentComponent = parent; + return o; +} + Component* PopupMenu::createWindow (const Options& options, ApplicationCommandManager** managerOfChosenCommand) const { @@ -1486,9 +1561,8 @@ Component* PopupMenu::createWindow (const Options& options, //============================================================================== // This invokes any command manager commands and deletes the menu window when it is dismissed -class PopupMenuCompletionCallback : public ModalComponentManager::Callback +struct PopupMenuCompletionCallback : public ModalComponentManager::Callback { -public: PopupMenuCompletionCallback() : managerOfChosenCommand (nullptr), prevFocused (Component::getCurrentlyFocusedComponent()), @@ -1497,7 +1571,7 @@ public: PopupMenuSettings::menuWasHiddenBecauseOfAppChange = false; } - void modalStateFinished (int result) + void modalStateFinished (int result) override { if (managerOfChosenCommand != nullptr && result != 0) { @@ -1524,7 +1598,6 @@ public: ScopedPointer component; WeakReference prevFocused, prevTopLevel; -private: JUCE_DECLARE_NON_COPYABLE (PopupMenuCompletionCallback) }; @@ -1651,10 +1724,8 @@ bool PopupMenu::containsCommandItem (const int commandID) const const Item& mi = *items.getUnchecked (i); if ((mi.itemID == commandID && mi.commandManager != nullptr) - || (mi.subMenu != nullptr && mi.subMenu->containsCommandItem (commandID))) - { + || (mi.subMenu != nullptr && mi.subMenu->containsCommandItem (commandID))) return true; - } } return false; @@ -1671,7 +1742,7 @@ bool PopupMenu::containsAnyActiveItems() const noexcept if (mi.subMenu->containsAnyActiveItems()) return true; } - else if (mi.isActive) + else if (mi.isEnabled) { return true; } @@ -1708,7 +1779,7 @@ void PopupMenu::CustomComponent::triggerMenuItem() { if (HelperClasses::MenuWindow* const pmw = mic->findParentComponentOfClass()) { - pmw->dismissMenu (&mic->itemInfo); + pmw->dismissMenu (&mic->item); } else { @@ -1725,53 +1796,25 @@ void PopupMenu::CustomComponent::triggerMenuItem() } //============================================================================== -PopupMenu::MenuItemIterator::MenuItemIterator (const PopupMenu& m) - : subMenu (nullptr), - itemId (0), - isSeparator (false), - isTicked (false), - isEnabled (false), - isCustomComponent (false), - isSectionHeader (false), - customColour (nullptr), - menu (m), - index (0) -{ -} +PopupMenu::CustomCallback::CustomCallback() {} +PopupMenu::CustomCallback::~CustomCallback() {} -PopupMenu::MenuItemIterator::~MenuItemIterator() -{ -} +//============================================================================== +PopupMenu::MenuItemIterator::MenuItemIterator (const PopupMenu& m) : menu (m), index (0) {} +PopupMenu::MenuItemIterator::~MenuItemIterator() {} bool PopupMenu::MenuItemIterator::next() { if (index >= menu.items.size()) return false; - const Item* const item = menu.items.getUnchecked (index); - ++index; - - if (item->isSeparator && index >= menu.items.size()) // (avoid showing a separator at the end) - return false; + const Item* const item = menu.items.getUnchecked (index++); - itemName = item->customComp != nullptr ? item->customComp->getName() : item->text; - subMenu = item->subMenu; - itemId = item->itemID; - isSeparator = item->isSeparator; - isTicked = item->isTicked; - isEnabled = item->isActive; - isSectionHeader = dynamic_cast (static_cast (item->customComp)) != nullptr; - isCustomComponent = (! isSectionHeader) && item->customComp != nullptr; - customColour = item->usesColour ? &(item->textColour) : nullptr; - icon = item->iconDrawable; - commandManager = item->commandManager; - - return true; + return ! (item->isSeparator && index >= menu.items.size()); // (avoid showing a separator at the end) } -void PopupMenu::MenuItemIterator::addItemTo (PopupMenu& targetMenu) +const PopupMenu::Item& PopupMenu::MenuItemIterator::getItem() const noexcept { - targetMenu.items.add (new Item (itemId, itemName, isEnabled, isTicked, icon != nullptr ? icon->createCopy() : nullptr, - customColour != nullptr ? *customColour : Colours::black, - customColour != nullptr, nullptr, subMenu, commandManager)); + jassert (isPositiveAndBelow (index - 1, menu.items.size())); + return *menu.items.getUnchecked (index - 1); } diff --git a/source/modules/juce_gui_basics/menus/juce_PopupMenu.h b/source/modules/juce_gui_basics/menus/juce_PopupMenu.h index f24072310..76b18a2e7 100644 --- a/source/modules/juce_gui_basics/menus/juce_PopupMenu.h +++ b/source/modules/juce_gui_basics/menus/juce_PopupMenu.h @@ -81,6 +81,7 @@ private: public: class CustomComponent; + class CustomCallback; //============================================================================== /** Creates an empty popup menu. */ @@ -104,6 +105,74 @@ public: /** Resets the menu, removing all its items. */ void clear(); + /** Describes a popup menu item. */ + struct JUCE_API Item + { + /** Creates a null item. + You'll need to set some fields after creating an Item before you + can add it to a PopupMenu + */ + Item() noexcept; + + /** Creates a copy of an item. */ + Item (const Item&); + + /** Creates a copy of an item. */ + Item& operator= (const Item&); + + /** The menu item's name. */ + String text; + + /** The menu item's ID. This can not be 0 if you want the item to be triggerable! */ + int itemID; + + /** A sub-menu, or nullptr if there isn't one. */ + ScopedPointer subMenu; + + /** A drawable to use as an icon, or nullptr if there isn't one. */ + ScopedPointer image; + + /** A custom component for the item to display, or nullptr if there isn't one. */ + ReferenceCountedObjectPtr customComponent; + + /** A custom callback for the item to use, or nullptr if there isn't one. */ + ReferenceCountedObjectPtr customCallback; + + /** A command manager to use to automatically invoke the command, or nullptr if none is specified. */ + ApplicationCommandManager* commandManager; + + /** An optional string describing the shortcut key for this item. + This is only used for displaying at the right-hand edge of a menu item - the + menu won't attempt to actually catch or process the key. If you supply a + commandManager parameter then the menu will attempt to fill-in this field + automatically. + */ + String shortcutKeyDescription; + + /** A colour to use to draw the menu text. + By default this is transparent black, which means that the LookAndFeel should choose the colour. + */ + Colour colour; + + /** True if this menu item is enabled. */ + bool isEnabled; + + /** True if this menu item should have a tick mark next to it. */ + bool isTicked; + + /** True if this menu item is a separator line. */ + bool isSeparator; + + /** True if this menu item is a section header. */ + bool isSectionHeader; + }; + + /** Adds an item to the menu. + You can call this method for full control over the item that is added, or use the other + addItem helper methods if you want to pass arguments rather than creating an Item object. + */ + void addItem (const Item& newItem); + /** Appends a new text item for this menu to show. @param itemResultID the number that will be returned from the show() method @@ -173,7 +242,7 @@ public: */ void addCommandItem (ApplicationCommandManager* commandManager, CommandID commandID, - const String& displayName = String::empty, + const String& displayName = String(), Drawable* iconToUse = nullptr); @@ -274,7 +343,6 @@ public: int itemResultID = 0); /** Appends a separator to the menu, to help break it up into sections. - The menu class is smart enough not to display separators at the top or bottom of the menu, and it will replace mutliple adjacent separators with a single one, so your code can be quite free and easy about adding these, and it'll @@ -283,14 +351,12 @@ public: void addSeparator(); /** Adds a non-clickable text item to the menu. - This is a bold-font items which can be used as a header to separate the items into named groups. */ void addSectionHeader (const String& title); /** Returns the number of items that the menu currently contains. - (This doesn't count separators). */ int getNumItems() const noexcept; @@ -318,18 +384,31 @@ public: public: Options(); + //============================================================================== Options withTargetComponent (Component* targetComponent) const noexcept; Options withTargetScreenArea (const Rectangle& targetArea) const noexcept; Options withMinimumWidth (int minWidth) const noexcept; Options withMaximumNumColumns (int maxNumColumns) const noexcept; Options withStandardItemHeight (int standardHeight) const noexcept; Options withItemThatMustBeVisible (int idOfItemToBeVisible) const noexcept; + Options withParentComponent (Component* parentComponent) const noexcept; + + //============================================================================== + Component* getParentComponent() const noexcept { return parentComponent; } + Component* getTargetComponent() const noexcept { return targetComponent; } + Rectangle getTargetScreenArea() const noexcept { return targetArea; } + int getMinimumWidth() const noexcept { return minWidth; } + int getMaximumNumColumns() const noexcept { return maxColumns; } + int getStandardItemHeight() const noexcept { return standardHeight; } + int getItemThatMustBeVisible() const noexcept { return visibleItemID; } private: + //============================================================================== friend class PopupMenu; friend class PopupMenu::Window; Rectangle targetArea; Component* targetComponent; + Component* parentComponent; int visibleItemID, minWidth, maxColumns, standardHeight; }; @@ -360,12 +439,12 @@ public: in zero. @param standardItemHeight if this is non-zero, it will be used as the standard height for menu items (apart from custom items) - @param callback if this is non-zero, the menu will be launched asynchronously, - returning immediately, and the callback will receive a - call when the menu is either dismissed or has an item - selected. This object will be owned and deleted by the - system, so make sure that it works safely and that any - pointers that it uses are safely within scope. + @param callback if this is not a nullptr, the menu will be launched + asynchronously, returning immediately, and the callback + will receive a call when the menu is either dismissed or + has an item selected. This object will be owned and + deleted by the system, so make sure that it works safely + and that any pointers that it uses are safely within scope. @see showAt */ int show (int itemIDThatMustBeVisible = 0, @@ -486,21 +565,10 @@ public: */ bool next(); - /** Adds an item to the target menu which has all the properties of this item. */ - void addItemTo (PopupMenu& targetMenu); - - //============================================================================== - String itemName; - const PopupMenu* subMenu; - int itemId; - bool isSeparator; - bool isTicked; - bool isEnabled; - bool isCustomComponent; - bool isSectionHeader; - const Colour* customColour; - const Drawable* icon; - ApplicationCommandManager* commandManager; + /** Returns a reference to the description of the current item. + It is only valid to call this after next() has returned true! + */ + const Item& getItem() const noexcept; private: //============================================================================== @@ -561,6 +629,26 @@ public: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomComponent) }; + //============================================================================== + /** A user-defined callback that can be used for specific items in a popup menu. + @see PopupMenu::Item::customCallback + */ + class JUCE_API CustomCallback : public SingleThreadedReferenceCountedObject + { + public: + CustomCallback(); + ~CustomCallback(); + + /** Callback to indicate this item has been triggered. + @returns true if the itemID should be sent to the exitModalState method, or + false if it should send 0, indicating no further action should be taken + */ + virtual bool menuItemTriggered() = 0; + + private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomCallback) + }; + //============================================================================== /** This abstract base class is implemented by LookAndFeel classes to provide menu drawing functionality. @@ -617,11 +705,14 @@ public: bool isMenuOpen, bool isMouseOverBar, MenuBarComponent&) = 0; + + virtual Component* getParentComponentForMenuOptions (const PopupMenu::Options& options) = 0; + + virtual void preparePopupMenuWindow (Component& newWindow) = 0; }; private: //============================================================================== - JUCE_PUBLIC_IN_DLL_BUILD (class Item) JUCE_PUBLIC_IN_DLL_BUILD (struct HelperClasses) friend struct HelperClasses; friend class MenuBarComponent; diff --git a/source/modules/juce_gui_basics/misc/juce_DropShadower.cpp b/source/modules/juce_gui_basics/misc/juce_DropShadower.cpp index 9510deae4..d46f1372d 100644 --- a/source/modules/juce_gui_basics/misc/juce_DropShadower.cpp +++ b/source/modules/juce_gui_basics/misc/juce_DropShadower.cpp @@ -31,7 +31,7 @@ public: setVisible (true); setInterceptsMouseClicks (false, false); - if (! comp->isOnDesktop()) + if (comp->isOnDesktop()) { setSize (1, 1); // to keep the OS happy by not having zero-size windows addToDesktop (ComponentPeer::windowIgnoresMouseClicks diff --git a/source/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h b/source/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h index 0b050237a..faf9b6b76 100644 --- a/source/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h +++ b/source/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h @@ -86,7 +86,7 @@ public: */ void startDragging (const var& sourceDescription, Component* sourceComponent, - Image dragImage = Image::null, + Image dragImage = Image(), bool allowDraggingToOtherJuceWindows = false, const Point* imageOffsetFromMouse = nullptr); diff --git a/source/modules/juce_gui_basics/mouse/juce_LassoComponent.h b/source/modules/juce_gui_basics/mouse/juce_LassoComponent.h index 347cd4cf1..65e2f16e6 100644 --- a/source/modules/juce_gui_basics/mouse/juce_LassoComponent.h +++ b/source/modules/juce_gui_basics/mouse/juce_LassoComponent.h @@ -44,7 +44,7 @@ public: /** Returns the set of items that lie within a given lassoable region. - Your implementation of this method must find all the relevent items that lie + Your implementation of this method must find all the relevant items that lie within the given rectangle. and add them to the itemsFound array. The coordinates are relative to the top-left of the lasso component's parent diff --git a/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp b/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp index 3f83081a4..bcc4e8488 100644 --- a/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp +++ b/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp @@ -146,7 +146,7 @@ public: void sendMouseUp (Component& comp, Point screenPos, Time time, const ModifierKeys oldMods) { JUCE_MOUSE_EVENT_DBG ("up") - comp.internalMouseUp (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time, oldMods); + comp.internalMouseUp (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time, oldMods, pressure); } void sendMouseWheel (Component& comp, Point screenPos, Time time, const MouseWheelDetails& wheel) diff --git a/source/modules/juce_gui_basics/mouse/juce_SelectedItemSet.h b/source/modules/juce_gui_basics/mouse/juce_SelectedItemSet.h index 764191b85..6535e8295 100644 --- a/source/modules/juce_gui_basics/mouse/juce_SelectedItemSet.h +++ b/source/modules/juce_gui_basics/mouse/juce_SelectedItemSet.h @@ -75,7 +75,7 @@ public: for (int i = selectedItems.size(); --i >= 0;) if (! other.isSelected (selectedItems.getReference (i))) - itemDeselected (selectedItems.remove (i)); + itemDeselected (selectedItems.removeAndReturn (i)); for (SelectableItemType* i = other.selectedItems.begin(), *e = other.selectedItems.end(); i != e; ++i) { @@ -235,7 +235,7 @@ public: if (i >= 0) { changed(); - itemDeselected (selectedItems.remove (i)); + itemDeselected (selectedItems.removeAndReturn (i)); } } @@ -248,7 +248,7 @@ public: for (int i = selectedItems.size(); --i >= 0;) { - itemDeselected (selectedItems.remove (i)); + itemDeselected (selectedItems.removeAndReturn (i)); i = jmin (i, selectedItems.size()); } } diff --git a/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp b/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp index 79f091e4f..09bc0674d 100644 --- a/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp +++ b/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp @@ -572,9 +572,8 @@ private: int sizeAllocated; float scale; - class PreallocatedImage : public ImagePixelData + struct PreallocatedImage : public ImagePixelData { - public: PreallocatedImage (const int width_, const int height_, jint* data_, bool hasAlpha_) : ImagePixelData (Image::ARGB, width_, height_), data (data_), hasAlpha (hasAlpha_) { @@ -607,7 +606,7 @@ private: bm.data = (uint8*) (data + x + y * width); } - ImagePixelData* clone() + ImagePixelData::Ptr clone() { PreallocatedImage* s = new PreallocatedImage (width, height, 0, hasAlpha); s->allocatedData.malloc (sizeof (jint) * width * height); diff --git a/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm b/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm index b97105d83..afdc29562 100644 --- a/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm +++ b/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm @@ -48,6 +48,20 @@ namespace Orientations return Desktop::upright; } + static UIInterfaceOrientation convertFromJuce (Desktop::DisplayOrientation orientation) + { + switch (orientation) + { + case Desktop::upright: return UIInterfaceOrientationPortrait; + case Desktop::upsideDown: return UIInterfaceOrientationPortraitUpsideDown; + case Desktop::rotatedClockwise: return UIInterfaceOrientationLandscapeLeft; + case Desktop::rotatedAntiClockwise: return UIInterfaceOrientationLandscapeRight; + default: jassertfalse; // unknown orientation! + } + + return UIInterfaceOrientationPortrait; + } + static CGAffineTransform getCGTransformFor (const Desktop::DisplayOrientation orientation) noexcept { if (isUsingOldRotationMethod()) @@ -211,7 +225,7 @@ public: static Rectangle rotatedScreenPosToReal (const Rectangle& r) { - if (isUsingOldRotationMethod()) + if (! SystemStats::isRunningInAppExtensionSandbox() && isUsingOldRotationMethod()) { const Rectangle screen (convertToRectInt ([UIScreen mainScreen].bounds)); @@ -241,7 +255,7 @@ public: static Rectangle realScreenPosToRotated (const Rectangle& r) { - if (isUsingOldRotationMethod()) + if (! SystemStats::isRunningInAppExtensionSandbox() && isUsingOldRotationMethod()) { const Rectangle screen (convertToRectInt ([UIScreen mainScreen].bounds)); @@ -363,28 +377,29 @@ static bool isKioskModeView (JuceUIViewController* c) - (void) viewDidLoad { sendScreenBoundsUpdate (self); + [super viewDidLoad]; } - (void) viewWillAppear: (BOOL) animated { - ignoreUnused (animated); - [self viewDidLoad]; + sendScreenBoundsUpdate (self); + [super viewWillAppear:animated]; } - (void) viewDidAppear: (BOOL) animated { - ignoreUnused (animated); - [self viewDidLoad]; + sendScreenBoundsUpdate (self); + [super viewDidAppear:animated]; } - (void) viewWillLayoutSubviews { - [self viewDidLoad]; + sendScreenBoundsUpdate (self); } - (void) viewDidLayoutSubviews { - [self viewDidLoad]; + sendScreenBoundsUpdate (self); } @end @@ -469,7 +484,7 @@ static bool isKioskModeView (JuceUIViewController* c) if (owner != nullptr) owner->viewFocusLoss(); - return true; + return [super resignFirstResponder]; } - (BOOL) canBecomeFirstResponder @@ -786,6 +801,7 @@ static float getMaximumTouchForce (UITouch* touch) noexcept return (float) touch.maximumPossibleForce; #endif + ignoreUnused (touch); return 0.0f; } @@ -796,6 +812,7 @@ static float getTouchForce (UITouch* touch) noexcept return (float) touch.force; #endif + ignoreUnused (touch); return 0.0f; } @@ -997,7 +1014,10 @@ void UIViewComponentPeer::drawRect (CGRect r) CGContextClearRect (cg, CGContextGetClipBoundingBox (cg)); CGContextConcatCTM (cg, CGAffineTransformMake (1, 0, 0, -1, 0, getComponent().getHeight())); - CoreGraphicsContext g (cg, getComponent().getHeight(), static_cast ([UIScreen mainScreen].scale)); + + // NB the CTM on iOS already includes a factor for the display scale, so + // we'll tell the context that the scale is 1.0 to avoid it using it twice + CoreGraphicsContext g (cg, getComponent().getHeight(), 1.0f); insideDrawRect = true; handlePaint (g); @@ -1023,7 +1043,30 @@ void Desktop::setKioskComponent (Component* kioskModeComp, bool enableOrDisable, } } -void Desktop::allowedOrientationsChanged() {} +void Desktop::allowedOrientationsChanged() +{ + // if the current orientation isn't allowed anymore then switch orientations + if (! isOrientationEnabled (getCurrentOrientation())) + { + DisplayOrientation orientations[] = { upright, upsideDown, rotatedClockwise, rotatedAntiClockwise }; + + const int n = sizeof (orientations) / sizeof (DisplayOrientation); + int i; + + for (i = 0; i < n; ++i) + if (isOrientationEnabled (orientations[i])) + break; + + + // you need to support at least one orientation + jassert (i < n); + i = jmin (n - 1, i); + + NSNumber *value = [NSNumber numberWithInt:Orientations::convertFromJuce (orientations[i])]; + [[UIDevice currentDevice] setValue:value forKey:@"orientation"]; + [value release]; + } +} //============================================================================== void UIViewComponentPeer::repaint (const Rectangle& area) diff --git a/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm b/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm index 07bdc3f9b..466c4ecf4 100644 --- a/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm +++ b/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm @@ -39,6 +39,7 @@ Array appBecomingInactiveCallbacks; { } +@property (strong, nonatomic) UIWindow *window; - (void) applicationDidFinishLaunching: (UIApplication*) application; - (void) applicationWillTerminate: (UIApplication*) application; - (void) applicationDidEnterBackground: (UIApplication*) application; @@ -117,8 +118,7 @@ int juce_iOSMain (int argc, const char* argv[]) //============================================================================== void LookAndFeel::playAlertSound() { - //xxx - //AudioServicesPlaySystemSound (); + // TODO } //============================================================================== @@ -334,11 +334,15 @@ bool DragAndDropContainer::performExternalDragDropOfText (const String&) //============================================================================== void Desktop::setScreenSaverEnabled (const bool isEnabled) { - [[UIApplication sharedApplication] setIdleTimerDisabled: ! isEnabled]; + if (! SystemStats::isRunningInAppExtensionSandbox()) + [[UIApplication sharedApplication] setIdleTimerDisabled: ! isEnabled]; } bool Desktop::isScreenSaverEnabled() { + if (SystemStats::isRunningInAppExtensionSandbox()) + return true; + return ! [[UIApplication sharedApplication] isIdleTimerDisabled]; } @@ -351,7 +355,7 @@ bool juce_areThereAnyAlwaysOnTopWindows() //============================================================================== Image juce_createIconForFile (const File&) { - return Image::null; + return Image(); } //============================================================================== @@ -394,7 +398,10 @@ double Desktop::getDefaultMasterScale() Desktop::DisplayOrientation Desktop::getCurrentOrientation() const { - return Orientations::convertToJuce ([[UIApplication sharedApplication] statusBarOrientation]); + UIInterfaceOrientation orientation = SystemStats::isRunningInAppExtensionSandbox() ? UIInterfaceOrientationPortrait + : [[UIApplication sharedApplication] statusBarOrientation]; + + return Orientations::convertToJuce (orientation); } void Desktop::Displays::findDisplays (float masterScale) diff --git a/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp b/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp index 180a0bfaa..05b23f6fc 100644 --- a/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp +++ b/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp @@ -171,8 +171,6 @@ void FileChooser::showPlatformDialog (Array& results, else addZenityArgs (args, separator, title, file, filters, isDirectory, isSave, selectMultipleFiles); - args.add ("2>/dev/null"); // (to avoid logging info ending up in the results) - ChildProcess child; if (child.start (args, ChildProcess::wantStdOut)) diff --git a/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp b/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp index 0bc8e1b87..f0282890f 100644 --- a/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp +++ b/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp @@ -92,7 +92,7 @@ struct Atoms static Atom getIfExists (const char* name) { return XInternAtom (display, name, True); } static Atom getCreating (const char* name) { return XInternAtom (display, name, False); } - static String getName (const Atom& atom) + static String getName (const Atom atom) { if (atom == None) return "None"; @@ -100,7 +100,7 @@ struct Atoms return String (XGetAtomName (display, atom)); } - static bool isMimeTypeFile (const Atom& atom) { return getName (atom).equalsIgnoreCase ("text/uri-list"); } + static bool isMimeTypeFile (const Atom atom) { return getName (atom).equalsIgnoreCase ("text/uri-list"); } }; const unsigned long Atoms::DndVersion = 3; @@ -635,7 +635,7 @@ public: sendDataChangeMessage(); } - ImagePixelData* clone() override + ImagePixelData::Ptr clone() override { jassertfalse; return nullptr; @@ -1214,7 +1214,10 @@ private: e.dpi = ((static_cast (crtc->width) * 25.4 * 0.5) / static_cast (output->mm_width)) + ((static_cast (crtc->height) * 25.4 * 0.5) / static_cast (output->mm_height)); - e.scale = masterScale * getScaleForDisplay (output->name, e); + double scale = getScaleForDisplay (output->name, e); + scale = (scale <= 0.1 ? 1.0 : scale); + + e.scale = masterScale * scale; infos.add (e); } @@ -2308,7 +2311,7 @@ public: // if we have opengl contexts then just repaint them all // regardless if this is really necessary - repaintOpenGLContexts (); + repaintOpenGLContexts(); if (exposeEvent.window != windowH) { @@ -2568,7 +2571,7 @@ private: else if (Time::getApproximateMillisecondCounter() > lastTimeImageUsed + 3000) { stopTimer(); - image = Image::null; + image = Image(); } } @@ -2950,7 +2953,7 @@ private: swa.border_pixel = 0; swa.background_pixmap = None; swa.colormap = colormap; - swa.override_redirect = (styleFlags & windowIsTemporary) ? True : False; + swa.override_redirect = ((styleFlags & windowIsTemporary) != 0) ? True : False; swa.event_mask = getAllEventsMask(); windowH = XCreateWindow (display, parentToAddTo != 0 ? parentToAddTo : root, @@ -3510,7 +3513,7 @@ private: if (Atoms::isMimeTypeFile (dragAndDropCurrentMimeType)) { for (int i = 0; i < lines.size(); ++i) - dragInfo.files.add (URL::removeEscapeChars (lines[i].replace ("file://", String::empty, true))); + dragInfo.files.add (URL::removeEscapeChars (lines[i].replace ("file://", String(), true))); dragInfo.files.trim(); dragInfo.files.removeEmptyStrings(); @@ -3959,9 +3962,8 @@ void* CustomMouseCursorInfo::create() const hotspotX = (hotspotX * (int) cursorW) / (int) imageW; hotspotY = (hotspotY * (int) cursorH) / (int) imageH; - g.drawImageWithin (image, 0, 0, (int) imageW, (int) imageH, - RectanglePlacement::xLeft | RectanglePlacement::yTop | RectanglePlacement::onlyReduceInSize, - false); + g.drawImage (image, Rectangle ((float) imageW, (float) imageH), + RectanglePlacement::xLeft | RectanglePlacement::yTop | RectanglePlacement::onlyReduceInSize); } else { @@ -4080,7 +4082,7 @@ void MouseCursor::showInAllWindows() const //============================================================================== Image juce_createIconForFile (const File& /* file */) { - return Image::null; + return Image(); } //============================================================================== @@ -4157,7 +4159,7 @@ void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (AlertWindow::AlertIcon Component* associatedComponent, ModalComponentManager::Callback* callback) { - AlertWindow::showMessageBoxAsync (iconType, title, message, String::empty, associatedComponent, callback); + AlertWindow::showMessageBoxAsync (iconType, title, message, String(), associatedComponent, callback); } bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (AlertWindow::AlertIconType iconType, @@ -4165,7 +4167,7 @@ bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (AlertWindow::AlertIconType Component* associatedComponent, ModalComponentManager::Callback* callback) { - return AlertWindow::showOkCancelBox (iconType, title, message, String::empty, String::empty, + return AlertWindow::showOkCancelBox (iconType, title, message, String(), String(), associatedComponent, callback); } @@ -4175,7 +4177,7 @@ int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconTy ModalComponentManager::Callback* callback) { return AlertWindow::showYesNoCancelBox (iconType, title, message, - String::empty, String::empty, String::empty, + String(), String(), String(), associatedComponent, callback); } diff --git a/source/modules/juce_gui_basics/native/juce_mac_FileChooser.mm b/source/modules/juce_gui_basics/native/juce_mac_FileChooser.mm index 42cfd93ad..69f4a64b0 100644 --- a/source/modules/juce_gui_basics/native/juce_mac_FileChooser.mm +++ b/source/modules/juce_gui_basics/native/juce_mac_FileChooser.mm @@ -151,7 +151,7 @@ void FileChooser::showPlatformDialog (Array& results, tempMenu = new TemporaryMainMenuWithStandardCommands(); StringArray* filters = new StringArray(); - filters->addTokens (filter.replaceCharacters (",:", ";;"), ";", String::empty); + filters->addTokens (filter.replaceCharacters (",:", ";;"), ";", String()); filters->trim(); filters->removeEmptyStrings(); diff --git a/source/modules/juce_gui_basics/native/juce_mac_MainMenu.mm b/source/modules/juce_gui_basics/native/juce_mac_MainMenu.mm index 8ca3b614f..10de90b73 100644 --- a/source/modules/juce_gui_basics/native/juce_mac_MainMenu.mm +++ b/source/modules/juce_gui_basics/native/juce_mac_MainMenu.mm @@ -39,7 +39,7 @@ public: ~JuceMainMenuHandler() { - setMenu (nullptr, nullptr, String::empty); + setMenu (nullptr, nullptr, String()); jassert (instance == this); instance = nullptr; @@ -69,23 +69,20 @@ public: extraAppleMenuItems = createCopyIfNotNull (newExtraAppleMenuItems); } - void addTopLevelMenu (NSMenu* parent, const PopupMenu& child, - const String& name, const int menuId, const int tag) + void addTopLevelMenu (NSMenu* parent, const PopupMenu& child, const String& name, int menuId, int topLevelIndex) { NSMenuItem* item = [parent addItemWithTitle: juceStringToNS (name) action: nil keyEquivalent: nsEmptyString()]; - [item setTag: tag]; - NSMenu* sub = createMenu (child, name, menuId, tag, true); + NSMenu* sub = createMenu (child, name, menuId, topLevelIndex, true); [parent setSubmenu: sub forItem: item]; [sub setAutoenablesItems: false]; [sub release]; } - void updateTopLevelMenu (NSMenuItem* parentItem, const PopupMenu& menuToCopy, - const String& name, const int menuId, const int tag) + void updateTopLevelMenu (NSMenuItem* parentItem, const PopupMenu& menuToCopy, const String& name, int menuId, int topLevelIndex) { // Note: This method used to update the contents of the existing menu in-place, but that caused // weird side-effects which messed-up keyboard focus when switching between windows. By creating @@ -93,11 +90,10 @@ public: NSMenu* menu = [[NSMenu alloc] initWithTitle: juceStringToNS (name)]; for (PopupMenu::MenuItemIterator iter (menuToCopy); iter.next();) - addMenuItem (iter, menu, menuId, tag); + addMenuItem (iter, menu, menuId, topLevelIndex); [menu setAutoenablesItems: false]; [menu update]; - [parentItem setTag: tag]; [parentItem setSubmenu: menu]; [menu release]; } @@ -138,8 +134,10 @@ public: void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info) { - if (NSMenuItem* item = findMenuItem ([NSApp mainMenu], info)) - flashMenuBar ([item menu]); + if ((info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) == 0 + && info.invocationMethod != ApplicationCommandTarget::InvocationInfo::fromKeyPress) + if (NSMenuItem* item = findMenuItemWithTag ([NSApp mainMenu], info.commandID)) + flashMenuBar ([item menu]); } void updateMenus (NSMenu* menu) @@ -156,7 +154,7 @@ public: (new AsyncMenuUpdater())->post(); } - void invoke (const int commandId, ApplicationCommandManager* const commandManager, const int topLevelIndex) const + void invoke (int commandId, ApplicationCommandManager* commandManager, int topLevelIndex) const { if (currentModel != nullptr) { @@ -172,7 +170,7 @@ public: } } - void invokeDirectly (const int commandId, const int topLevelIndex) + void invokeDirectly (int commandId, int topLevelIndex) { if (currentModel != nullptr) currentModel->menuItemSelected (commandId, topLevelIndex); @@ -181,16 +179,17 @@ public: void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo, const int topLevelMenuId, const int topLevelIndex) { - NSString* text = juceStringToNS (iter.itemName.upToFirstOccurrenceOf ("", false, true)); + const PopupMenu::Item& i = iter.getItem(); + NSString* text = juceStringToNS (i.text); if (text == nil) text = nsEmptyString(); - if (iter.isSeparator) + if (i.isSeparator) { [menuToAddTo addItem: [NSMenuItem separatorItem]]; } - else if (iter.isSectionHeader) + else if (i.isSectionHeader) { NSMenuItem* item = [menuToAddTo addItemWithTitle: text action: nil @@ -198,9 +197,9 @@ public: [item setEnabled: false]; } - else if (iter.subMenu != nullptr) + else if (i.subMenu != nullptr) { - if (iter.itemName == recentItemsMenuName) + if (i.text == recentItemsMenuName) { if (recent == nullptr) recent = new RecentFilesMenuItem(); @@ -219,10 +218,10 @@ public: action: nil keyEquivalent: nsEmptyString()]; - [item setTag: iter.itemId]; - [item setEnabled: iter.isEnabled]; + [item setTag: i.itemID]; + [item setEnabled: i.isEnabled]; - NSMenu* sub = createMenu (*iter.subMenu, iter.itemName, topLevelMenuId, topLevelIndex, false); + NSMenu* sub = createMenu (*i.subMenu, i.text, topLevelMenuId, topLevelIndex, false); [menuToAddTo setSubmenu: sub forItem: item]; [sub release]; } @@ -232,19 +231,19 @@ public: action: @selector (menuItemInvoked:) keyEquivalent: nsEmptyString()]; - [item setTag: iter.itemId]; - [item setEnabled: iter.isEnabled]; - [item setState: iter.isTicked ? NSOnState : NSOffState]; + [item setTag: i.itemID]; + [item setEnabled: i.isEnabled]; + [item setState: i.isTicked ? NSOnState : NSOffState]; [item setTarget: (id) callback]; - NSMutableArray* info = [NSMutableArray arrayWithObject: [NSNumber numberWithUnsignedLongLong: (pointer_sized_uint) (void*) iter.commandManager]]; + NSMutableArray* info = [NSMutableArray arrayWithObject: [NSNumber numberWithUnsignedLongLong: (pointer_sized_uint) (void*) i.commandManager]]; [info addObject: [NSNumber numberWithInt: topLevelIndex]]; [item setRepresentedObject: info]; - if (iter.commandManager != nullptr) + if (i.commandManager != nullptr) { - const Array keyPresses (iter.commandManager->getKeyMappings() - ->getKeyPressesAssignedToCommand (iter.itemId)); + const Array keyPresses (i.commandManager->getKeyMappings() + ->getKeyPressesAssignedToCommand (i.itemID)); if (keyPresses.size() > 0) { @@ -355,16 +354,16 @@ private: ScopedPointer recent; //============================================================================== - static NSMenuItem* findMenuItem (NSMenu* const menu, const ApplicationCommandTarget::InvocationInfo& info) + static NSMenuItem* findMenuItemWithTag (NSMenu* const menu, int tag) { for (NSInteger i = [menu numberOfItems]; --i >= 0;) { NSMenuItem* m = [menu itemAtIndex: i]; - if ([m tag] == info.commandID) + if ([m tag] == tag) return m; if (NSMenu* sub = [m submenu]) - if (NSMenuItem* found = findMenuItem (sub, info)) + if (NSMenuItem* found = findMenuItemWithTag (sub, tag)) return found; } @@ -420,9 +419,8 @@ private: return m; } - class AsyncMenuUpdater : public CallbackMessage + struct AsyncMenuUpdater : public CallbackMessage { - public: AsyncMenuUpdater() {} void messageCallback() override @@ -431,14 +429,12 @@ private: instance->menuBarItemsChanged (nullptr); } - private: JUCE_DECLARE_NON_COPYABLE (AsyncMenuUpdater) }; - class AsyncCommandInvoker : public CallbackMessage + struct AsyncCommandInvoker : public CallbackMessage { - public: - AsyncCommandInvoker (const int commandId_, const int topLevelIndex_) + AsyncCommandInvoker (int commandId_, int topLevelIndex_) : commandId (commandId_), topLevelIndex (topLevelIndex_) {} @@ -448,9 +444,7 @@ private: instance->invokeDirectly (commandId, topLevelIndex); } - private: const int commandId, topLevelIndex; - JUCE_DECLARE_NON_COPYABLE (AsyncCommandInvoker) }; @@ -580,9 +574,8 @@ private: // This override is also important because it stops the base class // calling ModalComponentManager::bringToFront, which can get // recursive when file dialogs are involved - class SilentDummyModalComp : public Component + struct SilentDummyModalComp : public Component { - public: SilentDummyModalComp() {} void inputAttemptWhenModal() override {} }; @@ -727,6 +720,9 @@ static void mainMenuTrackingChanged (bool isTracking) { menuHandler->isOpen = isTracking; + if (MenuBarModel* model = menuHandler->currentModel) + model->handleMenuBarActivate (isTracking); + if (menuHandler->defferedUpdateRequested && ! isTracking) { menuHandler->defferedUpdateRequested = false; diff --git a/source/modules/juce_gui_basics/native/juce_mac_MouseCursor.mm b/source/modules/juce_gui_basics/native/juce_mac_MouseCursor.mm index 09a63d8af..434bcf2cc 100644 --- a/source/modules/juce_gui_basics/native/juce_mac_MouseCursor.mm +++ b/source/modules/juce_gui_basics/native/juce_mac_MouseCursor.mm @@ -27,25 +27,24 @@ //============================================================================== namespace MouseCursorHelpers { - NSImage* createNSImage (const Image&); - NSImage* createNSImage (const Image& image) + NSImage* createNSImage (const Image&, float scaleFactor = 1.f); + NSImage* createNSImage (const Image& image, float scaleFactor) { JUCE_AUTORELEASEPOOL { NSImage* im = [[NSImage alloc] init]; - [im setSize: NSMakeSize (image.getWidth(), image.getHeight())]; - [im lockFocus]; + const NSSize requiredSize = NSMakeSize (image.getWidth() / scaleFactor, image.getHeight() / scaleFactor); + [im setSize: requiredSize]; CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB(); - CGImageRef imageRef = juce_createCoreGraphicsImage (image, colourSpace, false); + CGImageRef imageRef = juce_createCoreGraphicsImage (image, colourSpace, true); CGColorSpaceRelease (colourSpace); - CGContextRef cg = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort]; - CGContextDrawImage (cg, convertToCGRect (image.getBounds()), imageRef); - + NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage: imageRef]; + [imageRep setSize: requiredSize]; + [im addRepresentation: imageRep]; + [imageRep release]; CGImageRelease (imageRef); - [im unlockFocus]; - return im; } } @@ -92,6 +91,8 @@ namespace MouseCursorHelpers } } + [originalImage release]; + NSDictionary* info = [NSDictionary dictionaryWithContentsOfFile: juceStringToNS (cursorPath + "/info.plist")]; const float hotspotX = (float) [[info valueForKey: nsStringLiteral ("hotx")] doubleValue]; @@ -104,7 +105,7 @@ namespace MouseCursorHelpers void* CustomMouseCursorInfo::create() const { - return MouseCursorHelpers::fromNSImage (MouseCursorHelpers::createNSImage (image), + return MouseCursorHelpers::fromNSImage (MouseCursorHelpers::createNSImage (image, scaleFactor), NSMakePoint (hotspot.x, hotspot.y)); } diff --git a/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index d786ac61a..e98a7b0be 100644 --- a/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -58,6 +58,10 @@ static NSRect flippedScreenRect (NSRect r) noexcept return r; } +#if JUCE_MODULE_AVAILABLE_juce_opengl +void componentPeerAboutToBeRemovedFromScreen (ComponentPeer&); +#endif + //============================================================================== class NSViewComponentPeer : public ComponentPeer, private AsyncUpdater @@ -69,14 +73,16 @@ public: view (nil), isSharedWindow (viewToAttachTo != nil), fullScreen (false), - insideDrawRect (false), #if USE_COREGRAPHICS_RENDERING usingCoreGraphics (true), #else usingCoreGraphics (false), #endif isZooming (false), + isFirstLiveResize (false), textWasInserted (false), + isStretchingTop (false), isStretchingLeft (false), + isStretchingBottom (false), isStretchingRight (false), notificationCenter (nil) { appFocusChangeCallback = appFocusChanged; @@ -236,9 +242,9 @@ public: void setRepresentedFile (const File& file) override { if (! isSharedWindow) - [window setRepresentedFilename: juceStringToNS (file != File::nonexistent + [window setRepresentedFilename: juceStringToNS (file != File() ? file.getFullPathName() - : String::empty)]; + : String())]; } void setBounds (const Rectangle& newBounds, bool isNowFullScreen) override @@ -394,15 +400,19 @@ public: && isPositiveAndBelow (localPos.getY(), (int) viewFrame.size.height))) return false; - if (NSWindow* const viewWindow = [view window]) + if (! SystemStats::isRunningInAppExtensionSandbox()) { - const NSRect windowFrame = [viewWindow frame]; - const NSPoint windowPoint = [view convertPoint: NSMakePoint (localPos.x, viewFrame.size.height - localPos.y) toView: nil]; - const NSPoint screenPoint = NSMakePoint (windowFrame.origin.x + windowPoint.x, - windowFrame.origin.y + windowPoint.y); + if (NSWindow* const viewWindow = [view window]) + { + const NSRect windowFrame = [viewWindow frame]; + const NSPoint windowPoint = [view convertPoint: NSMakePoint (localPos.x, viewFrame.size.height - localPos.y) toView: nil]; + const NSPoint screenPoint = NSMakePoint (windowFrame.origin.x + windowPoint.x, + windowFrame.origin.y + windowPoint.y); - if (! isWindowAtPoint (viewWindow, screenPoint)) - return false; + if (! isWindowAtPoint (viewWindow, screenPoint)) + return false; + + } } NSView* v = [view hitTest: NSMakePoint (viewFrame.origin.x + localPos.getX(), @@ -612,7 +622,6 @@ public: wheel.isSmooth = false; wheel.isInertial = false; - #if ! JUCE_PPC @try { #if defined (MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 @@ -641,7 +650,6 @@ public: } @catch (...) {} - #endif if (wheel.deltaX == 0 && wheel.deltaY == 0) { @@ -668,6 +676,16 @@ public: void redirectPaste (NSObject*) { handleKeyPress (KeyPress ('v', ModifierKeys (ModifierKeys::commandModifier), 'v')); } void redirectCut (NSObject*) { handleKeyPress (KeyPress ('x', ModifierKeys (ModifierKeys::commandModifier), 'x')); } + void redirectWillMoveToWindow (NSWindow* newWindow) + { + #if JUCE_MODULE_AVAILABLE_juce_opengl + if ([view window] == window && newWindow == nullptr) + componentPeerAboutToBeRemovedFromScreen (*this); + #else + ignoreUnused (newWindow); + #endif + } + void sendMouseEvent (NSEvent* ev) { updateModifiers (ev); @@ -793,6 +811,37 @@ public: displayScale = (float) screen.backingScaleFactor; #endif + #if USE_COREGRAPHICS_RENDERING && JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS + // This option invokes a separate paint call for each rectangle of the clip region. + // It's a long story, but this is a basically a workaround for a CGContext not having + // a way of finding whether a rectangle falls within its clip region + if (usingCoreGraphics) + { + const NSRect* rects = nullptr; + NSInteger numRects = 0; + [view getRectsBeingDrawn: &rects count: &numRects]; + + if (numRects > 1) + { + for (int i = 0; i < numRects; ++i) + { + NSRect rect = rects[i]; + CGContextSaveGState (cg); + CGContextClipToRect (cg, CGRectMake (rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)); + drawRect (cg, rect, displayScale); + CGContextRestoreGState (cg); + } + + return; + } + } + #endif + + drawRect (cg, r, displayScale); + } + + void drawRect (CGContextRef cg, NSRect r, float displayScale) + { #if USE_COREGRAPHICS_RENDERING if (usingCoreGraphics) { @@ -842,9 +891,16 @@ public: void handleAsyncUpdate() override { + #if JucePlugin_Build_AAX || JucePlugin_Build_RTAS || JucePlugin_Build_AUv3 || JucePlugin_Build_AU || JucePlugin_Build_VST3 || JucePlugin_Build_VST + const bool shouldThrottle = true; + #else + const bool shouldThrottle = areAnyWindowsInLiveResize(); + #endif + // When windows are being resized, artificially throttling high-frequency repaints helps - // to stop the event queue getting clogged, and keeps everything working smoothly - if (areAnyWindowsInLiveResize() + // to stop the event queue getting clogged, and keeps everything working smoothly. + // For some reason Logic also needs this throttling to recored parameter events correctly. + if (shouldThrottle && Time::getCurrentTime() < lastRepaintTime + RelativeTime::milliseconds (1000 / 30)) { triggerAsyncUpdate(); @@ -854,6 +910,7 @@ public: for (const Rectangle* i = deferredRepaints.begin(), *e = deferredRepaints.end(); i != e; ++i) [view setNeedsDisplayInRect: makeNSRect (*i)]; + lastRepaintTime = Time::getCurrentTime(); deferredRepaints.clear(); } @@ -871,10 +928,7 @@ public: void invokePaint (LowLevelGraphicsContext& context) { - lastRepaintTime = Time::getCurrentTime(); - insideDrawRect = true; handlePaint (context); - insideDrawRect = false; } void performAnyPendingRepaintsNow() override @@ -956,7 +1010,10 @@ public: void liveResizingStart() { if (constrainer != nullptr) + { constrainer->resizeStart(); + isFirstLiveResize = true; + } } void liveResizingEnd() @@ -969,30 +1026,34 @@ public: { if (constrainer != nullptr && ! isKioskMode()) { - Rectangle pos (convertToRectInt (flippedScreenRect (r))); - Rectangle original (convertToRectInt (flippedScreenRect ([window frame]))); + const float scale = getComponent().getDesktopScaleFactor(); + + Rectangle pos = ScalingHelpers::unscaledScreenPosToScaled (scale, convertToRectInt (flippedScreenRect (r))); + Rectangle original = ScalingHelpers::unscaledScreenPosToScaled (scale, convertToRectInt (flippedScreenRect ([window frame]))); const Rectangle screenBounds (Desktop::getInstance().getDisplays().getTotalBounds (true)); #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6 - if ([window inLiveResize]) + const bool inLiveResize = [window inLiveResize]; #else - if ([window respondsToSelector: @selector (inLiveResize)] - && [window performSelector: @selector (inLiveResize)]) + const bool inLiveResize = [window respondsToSelector: @selector (inLiveResize)] + && [window performSelector: @selector (inLiveResize)]; #endif + + if (! inLiveResize || isFirstLiveResize) { - constrainer->checkBounds (pos, original, screenBounds, - false, false, true, true); - } - else - { - constrainer->checkBounds (pos, original, screenBounds, - pos.getY() != original.getY() && pos.getBottom() == original.getBottom(), - pos.getX() != original.getX() && pos.getRight() == original.getRight(), - pos.getY() == original.getY() && pos.getBottom() != original.getBottom(), - pos.getX() == original.getX() && pos.getRight() != original.getRight()); + isFirstLiveResize = false; + + isStretchingTop = (pos.getY() != original.getY() && pos.getBottom() == original.getBottom()); + isStretchingLeft = (pos.getX() != original.getX() && pos.getRight() == original.getRight()); + isStretchingBottom = (pos.getY() == original.getY() && pos.getBottom() != original.getBottom()); + isStretchingRight = (pos.getX() == original.getX() && pos.getRight() != original.getRight()); } + constrainer->checkBounds (pos, original, screenBounds, + isStretchingTop, isStretchingLeft, isStretchingBottom, isStretchingRight); + + pos = ScalingHelpers::scaledScreenPosToUnscaled (scale, pos); r = flippedScreenRect (makeNSRect (pos)); } @@ -1283,8 +1344,9 @@ public: //============================================================================== NSWindow* window; NSView* view; - bool isSharedWindow, fullScreen, insideDrawRect; - bool usingCoreGraphics, isZooming, textWasInserted; + bool isSharedWindow, fullScreen; + bool usingCoreGraphics, isZooming, isFirstLiveResize, textWasInserted; + bool isStretchingTop, isStretchingLeft, isStretchingBottom, isStretchingRight; String stringBeingComposed; NSNotificationCenter* notificationCenter; @@ -1488,6 +1550,8 @@ struct JuceNSViewClass : public ObjCClass addMethod (@selector (copy:), copy, "v@:@"); addMethod (@selector (cut:), cut, "v@:@"); + addMethod (@selector (viewWillMoveToWindow:), willMoveToWindow, "v@:@"); + addProtocol (@protocol (NSTextInput)); registerClass(); @@ -1537,6 +1601,11 @@ private: static void paste (id self, SEL, NSObject* s) { if (NSViewComponentPeer* p = getOwner (self)) p->redirectPaste (s); } static void cut (id self, SEL, NSObject* s) { if (NSViewComponentPeer* p = getOwner (self)) p->redirectCut (s); } + static void willMoveToWindow (id self, SEL, NSWindow* window) + { + if (NSViewComponentPeer* p = getOwner (self)) p->redirectWillMoveToWindow (window); + } + static BOOL acceptsFirstMouse (id, SEL, NSEvent*) { return YES; } static BOOL wantsDefaultClipping (id, SEL) { return YES; } // (this is the default, but may want to customise it in future) static BOOL worksWhenModal (id self, SEL) { if (NSViewComponentPeer* p = getOwner (self)) return p->worksWhenModal(); return NO; }; @@ -2064,7 +2133,7 @@ const int KeyPress::F7Key = NSF7FunctionKey; const int KeyPress::F8Key = NSF8FunctionKey; const int KeyPress::F9Key = NSF9FunctionKey; const int KeyPress::F10Key = NSF10FunctionKey; -const int KeyPress::F11Key = NSF1FunctionKey; +const int KeyPress::F11Key = NSF11FunctionKey; const int KeyPress::F12Key = NSF12FunctionKey; const int KeyPress::F13Key = NSF13FunctionKey; const int KeyPress::F14Key = NSF14FunctionKey; diff --git a/source/modules/juce_gui_basics/native/juce_mac_Windowing.mm b/source/modules/juce_gui_basics/native/juce_mac_Windowing.mm index b39229065..1ed7aea73 100644 --- a/source/modules/juce_gui_basics/native/juce_mac_Windowing.mm +++ b/source/modules/juce_gui_basics/native/juce_mac_Windowing.mm @@ -483,13 +483,15 @@ String SystemClipboard::getTextFromClipboard() void Process::setDockIconVisible (bool isVisible) { - #if defined (MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) - [NSApp setActivationPolicy: isVisible ? NSApplicationActivationPolicyRegular - : NSApplicationActivationPolicyProhibited]; - #else - ignoreUnused (isVisible); - jassertfalse; // sorry, not available in 10.5! - #endif + ProcessSerialNumber psn { 0, kCurrentProcess }; + ProcessApplicationTransformState state = isVisible + ? kProcessTransformToForegroundApplication + : kProcessTransformToUIElementApplication; + + OSStatus err = TransformProcessType (&psn, state); + + jassert (err == 0); + ignoreUnused (err); } bool Desktop::isOSXDarkModeActive() diff --git a/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp b/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp index ad11d4063..a6076a422 100644 --- a/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp +++ b/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp @@ -147,7 +147,7 @@ void FileChooser::showPlatformDialog (Array& results, const String& title_ // use a modal window as the parent for this dialog box // to block input from other app windows - Component parentWindow (String::empty); + Component parentWindow; const Rectangle mainMon (Desktop::getInstance().getDisplays().getMainDisplay().userArea); parentWindow.setBounds (mainMon.getX() + mainMon.getWidth() / 4, mainMon.getY() + mainMon.getHeight() / 4, diff --git a/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index 4dd23d379..253dfec96 100644 --- a/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -39,6 +39,10 @@ #define WM_APPCOMMAND 0x0319 #endif +#if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client + #include +#endif + extern void juce_repeatLastProcessPriority(); extern void juce_checkCurrentlyFocusedTopLevelWindow(); // in juce_TopLevelWindow.cpp extern bool juce_isRunningInWine(); @@ -381,7 +385,7 @@ public: sendDataChangeMessage(); } - ImagePixelData* clone() override + ImagePixelData::Ptr clone() override { WindowsBitmapImage* im = new WindowsBitmapImage (pixelFormat, width, height, false); @@ -531,7 +535,7 @@ namespace IconConverters } } - return Image::null; + return Image(); } HICON createHICONFromImage (const Image& image, const BOOL isIcon, int hotspotX, int hotspotY) @@ -561,6 +565,9 @@ namespace IconConverters //============================================================================== class HWNDComponentPeer : public ComponentPeer + #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client + , public ModifierKeyReceiver + #endif { public: enum RenderingEngineType @@ -585,6 +592,9 @@ public: currentWindowIcon (0), dropTarget (nullptr), updateLayeredWindowAlpha (255) + #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client + , modProvider (nullptr) + #endif { callFunctionIfNotLocked (&createWindowCallback, this); @@ -772,6 +782,9 @@ public: if (isFullScreen() != shouldBeFullScreen) { + if (constrainer != nullptr) + constrainer->resizeStart(); + fullScreen = shouldBeFullScreen; const WeakReference deletionChecker (&component); @@ -795,6 +808,9 @@ public: if (deletionChecker != nullptr) handleMovedOrResized(); + + if (constrainer != nullptr) + constrainer->resizeEnd(); } } @@ -858,7 +874,7 @@ public: if (! makeActive) { - // in this case a broughttofront call won't have occured, so do it now.. + // in this case a broughttofront call won't have occurred, so do it now.. handleBroughtToFront(); } } @@ -957,7 +973,12 @@ public: if (isKeyDown (VK_SHIFT)) keyMods |= ModifierKeys::shiftModifier; if (isKeyDown (VK_CONTROL)) keyMods |= ModifierKeys::ctrlModifier; if (isKeyDown (VK_MENU)) keyMods |= ModifierKeys::altModifier; - if (isKeyDown (VK_RMENU)) keyMods &= ~(ModifierKeys::ctrlModifier | ModifierKeys::altModifier); + + // workaround: Windows maps AltGr to left-Ctrl + right-Alt. + if (isKeyDown (VK_RMENU) && !isKeyDown (VK_RCONTROL)) + { + keyMods = (keyMods & ~ModifierKeys::ctrlModifier) | ModifierKeys::altModifier; + } currentModifiers = currentModifiers.withOnlyMouseButtons().withFlags (keyMods); } @@ -1040,8 +1061,9 @@ public: Point getMousePos (const POINTL& mousePos) const { - return owner.globalToLocal (Point (static_cast (mousePos.x), - static_cast (mousePos.y))); + return owner.globalToLocal (ScalingHelpers::unscaledScreenPosToScaled (owner.getComponent().getDesktopScaleFactor(), + Point (static_cast (mousePos.x), + static_cast (mousePos.y)))); } template @@ -1106,14 +1128,6 @@ public: ownerInfo->dragInfo.clear(); - DroppedData textData (dataObject, CF_UNICODETEXT); - - if (SUCCEEDED (textData.error)) - { - ownerInfo->dragInfo.text = String (CharPointer_UTF16 ((const WCHAR*) textData.data), - CharPointer_UTF16 ((const WCHAR*) addBytesToPointer (textData.data, textData.dataSize))); - } - else { DroppedData fileData (dataObject, CF_HDROP); @@ -1126,14 +1140,21 @@ public: ownerInfo->parseFileList (static_cast (names), fileData.dataSize); else ownerInfo->parseFileList (static_cast (names), fileData.dataSize); - } - else - { - return fileData.error; + + return S_OK; } } - return S_OK; + DroppedData textData (dataObject, CF_UNICODETEXT); + + if (SUCCEEDED (textData.error)) + { + ownerInfo->dragInfo.text = String (CharPointer_UTF16 ((const WCHAR*) textData.data), + CharPointer_UTF16 ((const WCHAR*) addBytesToPointer (textData.data, textData.dataSize))); + return S_OK; + } + + return textData.error; } JUCE_DECLARE_NON_COPYABLE (JuceDropTarget) @@ -1165,6 +1186,9 @@ private: JuceDropTarget* dropTarget; uint8 updateLayeredWindowAlpha; MultiTouchMapper currentTouches; + #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client + ModifierKeyProvider* modProvider; + #endif //============================================================================== class TemporaryImage : public Timer @@ -1186,7 +1210,7 @@ private: void timerCallback() override { stopTimer(); - image = Image::null; + image = Image(); } private: @@ -1726,7 +1750,7 @@ private: if (registerTouchWindow == nullptr) return false; - // Relevent info about touch/pen detection flags: + // Relevant info about touch/pen detection flags: // https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx // http://www.petertissen.de/?p=4 @@ -1751,6 +1775,11 @@ private: updateKeyModifiers(); + #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client + if (modProvider != nullptr) + currentModifiers = currentModifiers.withFlags (modProvider->getWin32Modifiers()); + #endif + TRACKMOUSEEVENT tme; tme.cbSize = sizeof (tme); tme.dwFlags = TME_LEAVE; @@ -1793,6 +1822,12 @@ private: if (isValidPeer (this)) { updateModifiersFromWParam (wParam); + + #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client + if (modProvider != nullptr) + currentModifiers = currentModifiers.withFlags (modProvider->getWin32Modifiers()); + #endif + isDragging = true; doMouseEvent (position, MouseInputSource::invalidPressure); @@ -1806,6 +1841,12 @@ private: return; updateModifiersFromWParam (wParam); + + #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client + if (modProvider != nullptr) + currentModifiers = currentModifiers.withFlags (modProvider->getWin32Modifiers()); + #endif + const bool wasDragging = isDragging; isDragging = false; @@ -1936,6 +1977,7 @@ private: bool handleTouchInput (const TOUCHINPUT& touch, const bool isDown, const bool isUp) { bool isCancel = false; + const int touchIndex = currentTouches.getIndexOfTouch (touch.dwID); const int64 time = getMouseEventTime(); const Point pos (globalToLocal (Point (touch.x / 100.0f, @@ -2391,6 +2433,19 @@ private: { } + //============================================================================== + #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client + void setModifierKeyProvider (ModifierKeyProvider* provider) override + { + modProvider = provider; + } + + void removeModifierKeyProvider() override + { + modProvider = nullptr; + } + #endif + //============================================================================== public: static LRESULT CALLBACK windowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam) @@ -2653,6 +2708,16 @@ private: break; case SC_KEYMENU: + #if ! JUCE_WINDOWS_ALT_KEY_TRIGGERS_MENU + // This test prevents a press of the ALT key from triggering the ancient top-left window menu. + // By default we suppress this behaviour because it's unlikely that more than a tiny subset of + // our users will actually want it, and it causes problems if you're trying to use the ALT key + // as a modifier for mouse actions. If you really need the old behaviour, then just define + // JUCE_WINDOWS_ALT_KEY_TRIGGERS_MENU=1 in your app. + if ((lParam >> 16) <= 0) // Values above zero indicate that a mouse-click triggered the menu + return 0; + #endif + // (NB mustn't call sendInputAttemptWhenModalMessage() here because of very obscure // situations that can arise if a modal loop is started from an alt-key keypress). if (hasTitleBar() && h == GetCapture()) diff --git a/source/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp b/source/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp index 99eccbd9c..d13fa0e4b 100644 --- a/source/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp +++ b/source/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp @@ -238,7 +238,7 @@ void PropertyPanel::addProperties (const Array& newPropertie if (isEmpty()) repaint(); - propertyHolderComponent->insertSection (-1, new SectionComponent (String::empty, newProperties, true)); + propertyHolderComponent->insertSection (-1, new SectionComponent (String(), newProperties, true)); updatePropHolderLayout(); } diff --git a/source/modules/juce_gui_basics/properties/juce_PropertyPanel.h b/source/modules/juce_gui_basics/properties/juce_PropertyPanel.h index 43615b950..aca85988e 100644 --- a/source/modules/juce_gui_basics/properties/juce_PropertyPanel.h +++ b/source/modules/juce_gui_basics/properties/juce_PropertyPanel.h @@ -149,6 +149,10 @@ public: */ const String& getMessageWhenEmpty() const noexcept; + //============================================================================== + /** Returns the PropertyPanel's internal Viewport. */ + Viewport& getViewport() noexcept { return viewport; } + //============================================================================== /** @internal */ void paint (Graphics&) override; diff --git a/source/modules/juce_gui_basics/properties/juce_SliderPropertyComponent.cpp b/source/modules/juce_gui_basics/properties/juce_SliderPropertyComponent.cpp index 75c4581d7..46ee985ad 100644 --- a/source/modules/juce_gui_basics/properties/juce_SliderPropertyComponent.cpp +++ b/source/modules/juce_gui_basics/properties/juce_SliderPropertyComponent.cpp @@ -26,13 +26,14 @@ SliderPropertyComponent::SliderPropertyComponent (const String& name, const double rangeMin, const double rangeMax, const double interval, - const double skewFactor) + const double skewFactor, + bool symmetricSkew) : PropertyComponent (name) { addAndMakeVisible (slider); slider.setRange (rangeMin, rangeMax, interval); - slider.setSkewFactor (skewFactor); + slider.setSkewFactor (skewFactor, symmetricSkew); slider.setSliderStyle (Slider::LinearBar); slider.addListener (this); @@ -43,13 +44,14 @@ SliderPropertyComponent::SliderPropertyComponent (const Value& valueToControl, const double rangeMin, const double rangeMax, const double interval, - const double skewFactor) + const double skewFactor, + bool symmetricSkew) : PropertyComponent (name) { addAndMakeVisible (slider); slider.setRange (rangeMin, rangeMax, interval); - slider.setSkewFactor (skewFactor); + slider.setSkewFactor (skewFactor, symmetricSkew); slider.setSliderStyle (Slider::LinearBar); slider.getValueObject().referTo (valueToControl); diff --git a/source/modules/juce_gui_basics/properties/juce_SliderPropertyComponent.h b/source/modules/juce_gui_basics/properties/juce_SliderPropertyComponent.h index 35983df68..d45e448b8 100644 --- a/source/modules/juce_gui_basics/properties/juce_SliderPropertyComponent.h +++ b/source/modules/juce_gui_basics/properties/juce_SliderPropertyComponent.h @@ -48,7 +48,8 @@ protected: double rangeMin, double rangeMax, double interval, - double skewFactor = 1.0); + double skewFactor = 1.0, + bool symmetricSkew = false); public: //============================================================================== @@ -68,7 +69,8 @@ public: double rangeMin, double rangeMax, double interval, - double skewFactor = 1.0); + double skewFactor = 1.0, + bool symmetricSkew = false); /** Destructor. */ ~SliderPropertyComponent(); diff --git a/source/modules/juce_gui_basics/properties/juce_TextPropertyComponent.cpp b/source/modules/juce_gui_basics/properties/juce_TextPropertyComponent.cpp index f765cd0a0..5a52f9c61 100644 --- a/source/modules/juce_gui_basics/properties/juce_TextPropertyComponent.cpp +++ b/source/modules/juce_gui_basics/properties/juce_TextPropertyComponent.cpp @@ -27,7 +27,7 @@ class TextPropertyComponent::LabelComp : public Label, { public: LabelComp (TextPropertyComponent& tpc, const int charLimit, const bool multiline) - : Label (String::empty, String::empty), + : Label (String(), String()), owner (tpc), maxChars (charLimit), isMultiline (multiline) diff --git a/source/modules/juce_gui_basics/properties/juce_TextPropertyComponent.h b/source/modules/juce_gui_basics/properties/juce_TextPropertyComponent.h index 9e36f74b7..de9d467dd 100644 --- a/source/modules/juce_gui_basics/properties/juce_TextPropertyComponent.h +++ b/source/modules/juce_gui_basics/properties/juce_TextPropertyComponent.h @@ -38,8 +38,10 @@ protected: //============================================================================== /** Creates a text property component. - The maxNumChars is used to set the length of string allowable, and isMultiLine - sets whether the text editor allows carriage returns. + @param propertyName The name of the property + @param maxNumChars If not zero, then this specifies the maximum allowable length of + the string. If zero, then the string will have no length limit. + @param isMultiLine isMultiLine sets whether the text editor allows carriage returns. @see TextEditor */ @@ -50,8 +52,11 @@ protected: public: /** Creates a text property component. - The maxNumChars is used to set the length of string allowable, and isMultiLine - sets whether the text editor allows carriage returns. + @param valueToControl The Value that is controlled by the TextPropertyCOmponent + @param propertyName The name of the property + @param maxNumChars If not zero, then this specifies the maximum allowable length of + the string. If zero, then the string will have no length limit. + @param isMultiLine isMultiLine sets whether the text editor allows carriage returns. @see TextEditor */ @@ -136,7 +141,7 @@ private: }; #ifndef DOXYGEN - /** This typedef is just for compatibility with old code and VC6 - newer code should use Button::Listener instead. */ + /** This typedef is just for compatibility with old code and VC6 - newer code should use TextPropertyComponent::Listener instead. */ typedef TextPropertyComponent::Listener TextPropertyComponentListener; #endif diff --git a/source/modules/juce_gui_basics/widgets/juce_ComboBox.cpp b/source/modules/juce_gui_basics/widgets/juce_ComboBox.cpp index 68e1c2389..13fc1644c 100644 --- a/source/modules/juce_gui_basics/widgets/juce_ComboBox.cpp +++ b/source/modules/juce_gui_basics/widgets/juce_ComboBox.cpp @@ -46,7 +46,8 @@ ComboBox::ComboBox (const String& name) menuActive (false), scrollWheelEnabled (false), mouseWheelAccumulator (0), - noChoicesMessage (TRANS("(no choices)")) + noChoicesMessage (TRANS("(no choices)")), + labelEditableState (editableUnknown) { setRepaintsOnMouseActivity (true); lookAndFeelChanged(); @@ -66,7 +67,9 @@ void ComboBox::setEditableText (const bool isEditable) if (label->isEditableOnSingleClick() != isEditable || label->isEditableOnDoubleClick() != isEditable) { label->setEditable (isEditable, isEditable, false); - setWantsKeyboardFocus (! isEditable); + labelEditableState = (isEditable ? labelIsEditable : labelIsNotEditable); + + setWantsKeyboardFocus (labelEditableState == labelIsNotEditable); resized(); } } @@ -109,7 +112,7 @@ void ComboBox::addItem (const String& newItemText, const int newItemId) if (separatorPending) { separatorPending = false; - items.add (new ItemInfo (String::empty, 0, false, false)); + items.add (new ItemInfo (String(), 0, false, false)); } items.add (new ItemInfo (newItemText, newItemId, true, false)); @@ -137,7 +140,7 @@ void ComboBox::addSectionHeading (const String& headingName) if (separatorPending) { separatorPending = false; - items.add (new ItemInfo (String::empty, 0, false, false)); + items.add (new ItemInfo (String(), 0, false, false)); } items.add (new ItemInfo (headingName, 0, true, true)); @@ -216,7 +219,7 @@ String ComboBox::getItemText (const int index) const if (const ItemInfo* const item = getItemForIndex (index)) return item->name; - return String::empty; + return String(); } int ComboBox::getItemId (const int index) const noexcept @@ -271,7 +274,7 @@ int ComboBox::getSelectedId() const noexcept void ComboBox::setSelectedId (const int newItemId, const NotificationType notification) { const ItemInfo* const item = getItemForId (newItemId); - const String newItemText (item != nullptr ? item->name : String::empty); + const String newItemText (item != nullptr ? item->name : String()); if (lastCurrentId != newItemId || label->getText() != newItemText) { @@ -437,7 +440,14 @@ void ComboBox::lookAndFeelChanged() } addAndMakeVisible (label); - setWantsKeyboardFocus (! label->isEditable()); + + EditableState newEditableState = (label->isEditable() ? labelIsEditable : labelIsNotEditable); + + if (newEditableState != labelEditableState) + { + labelEditableState = newEditableState; + setWantsKeyboardFocus (labelEditableState == labelIsNotEditable); + } label->addListener (this); label->addMouseListener (this, false); diff --git a/source/modules/juce_gui_basics/widgets/juce_ComboBox.h b/source/modules/juce_gui_basics/widgets/juce_ComboBox.h index 515e5d2e5..ca05da6c9 100644 --- a/source/modules/juce_gui_basics/widgets/juce_ComboBox.h +++ b/source/modules/juce_gui_basics/widgets/juce_ComboBox.h @@ -57,7 +57,7 @@ public: @param componentName the name to set for the component (see Component::setName()) */ - explicit ComboBox (const String& componentName = String::empty); + explicit ComboBox (const String& componentName = String()); /** Destructor. */ ~ComboBox(); @@ -427,6 +427,13 @@ private: bool isEnabled : 1, isHeading : 1; }; + enum EditableState + { + editableUnknown, + labelIsNotEditable, + labelIsEditable + }; + OwnedArray items; Value currentId; int lastCurrentId; @@ -435,6 +442,7 @@ private: ListenerList listeners; ScopedPointer