From 7bb4a809aaae2fa1023ea311ac654f8dd6e0090d Mon Sep 17 00:00:00 2001 From: asb2m10 Date: Wed, 12 Feb 2014 19:55:38 -0500 Subject: [PATCH] Updated to JUCE 3.0.1 --- Builds/MacOSX/Dexed.xcodeproj/project.pbxproj | 14 +- .../UserInterfaceState.xcuserstate | Bin 89515 -> 89527 bytes Builds/VisualStudio2012/Dexed.vcxproj | 12 + Builds/VisualStudio2012/Dexed.vcxproj.filters | 18 + Builds/VisualStudio2013/Dexed.vcxproj | 12 + Builds/VisualStudio2013/Dexed.vcxproj.filters | 18 + JuceLibraryCode/AppConfig.h | 4 + .../buffers/juce_FloatVectorOperations.cpp | 448 +++++-- .../buffers/juce_FloatVectorOperations.h | 7 +- .../juce_audio_basics/juce_audio_basics.cpp | 5 + .../juce_audio_basics/juce_module_info | 2 +- .../midi/juce_MidiMessage.cpp | 138 ++- .../synthesisers/juce_Synthesiser.cpp | 20 + .../synthesisers/juce_Synthesiser.h | 36 +- .../audio_io/juce_AudioDeviceManager.cpp | 32 +- .../audio_io/juce_AudioDeviceManager.h | 2 +- .../audio_io/juce_AudioIODevice.h | 46 +- .../juce_audio_devices/juce_module_info | 2 +- .../native/juce_android_Audio.cpp | 59 +- .../native/juce_android_OpenSL.cpp | 57 +- .../native/juce_ios_Audio.cpp | 56 +- .../native/juce_linux_ALSA.cpp | 71 +- .../native/juce_linux_JackAudio.cpp | 62 +- .../native/juce_mac_CoreAudio.cpp | 1033 +++++++++++++---- .../native/juce_win32_ASIO.cpp | 58 +- .../native/juce_win32_DirectSound.cpp | 57 +- .../native/juce_win32_WASAPI.cpp | 52 +- .../sources/juce_AudioSourcePlayer.h | 3 +- .../sources/juce_AudioTransportSource.cpp | 40 +- .../sources/juce_AudioTransportSource.h | 10 +- .../codecs/flac/libFLAC/stream_decoder.c | 16 +- .../codecs/flac/libFLAC/stream_encoder.c | 2 +- .../codecs/juce_AiffAudioFormat.cpp | 2 +- .../codecs/juce_LAMEEncoderAudioFormat.cpp | 10 +- .../codecs/juce_MP3AudioFormat.cpp | 9 +- .../juce_audio_formats/juce_module_info | 2 +- .../AU/juce_AU_Wrapper.mm | 11 +- .../RTAS/juce_RTAS_Wrapper.cpp | 2 +- .../Standalone/juce_StandaloneFilterWindow.h | 2 +- .../VST/juce_VST_Wrapper.cpp | 32 +- .../juce_audio_plugin_client/juce_module_info | 2 +- .../utility/juce_PluginUtilities.cpp | 4 +- .../juce_AudioUnitPluginFormat.mm | 28 +- .../format_types/juce_LADSPAPluginFormat.cpp | 12 +- .../format_types/juce_VST3PluginFormat.cpp | 276 +++-- .../format_types/juce_VST3PluginFormat.h | 4 +- .../format_types/juce_VSTMidiEventList.h | 4 +- .../format_types/juce_VSTPluginFormat.cpp | 15 +- .../juce_audio_processors/juce_module_info | 2 +- .../processors/juce_AudioProcessor.cpp | 4 +- .../processors/juce_AudioProcessorGraph.cpp | 12 +- .../processors/juce_AudioProcessorGraph.h | 6 +- .../juce_GenericAudioProcessorEditor.cpp | 4 +- .../scanning/juce_KnownPluginList.cpp | 19 +- .../scanning/juce_KnownPluginList.h | 7 +- .../scanning/juce_PluginDirectoryScanner.cpp | 2 +- .../scanning/juce_PluginListComponent.cpp | 243 ++-- .../scanning/juce_PluginListComponent.h | 36 +- .../gui/juce_AudioDeviceSelectorComponent.cpp | 45 +- .../modules/juce_audio_utils/juce_module_info | 2 +- .../juce_core/containers/juce_OwnedArray.h | 5 +- .../juce_core/containers/juce_PropertySet.cpp | 4 +- .../juce_core/containers/juce_PropertySet.h | 6 +- .../modules/juce_core/files/juce_File.cpp | 12 +- .../modules/juce_core/files/juce_File.h | 2 +- .../juce_core/files/juce_TemporaryFile.cpp | 4 +- .../juce_core/files/juce_TemporaryFile.h | 2 +- .../juce_core/javascript/juce_JSON.cpp | 14 +- .../juce_core/javascript/juce_Javascript.cpp | 5 +- .../juce_core/javascript/juce_Javascript.h | 1 - .../modules/juce_core/juce_module_info | 2 +- .../juce_core/maths/juce_BigInteger.cpp | 2 +- .../juce_core/maths/juce_Expression.cpp | 4 +- .../modules/juce_core/misc/juce_Uuid.cpp | 1 - .../juce_core/native/juce_android_Files.cpp | 2 +- .../juce_core/native/juce_linux_Files.cpp | 6 +- .../juce_core/native/juce_linux_Network.cpp | 6 +- .../native/juce_linux_SystemStats.cpp | 8 +- .../juce_core/native/juce_mac_Files.mm | 4 +- .../juce_core/native/juce_mac_Strings.mm | 2 +- .../juce_core/native/juce_mac_SystemStats.mm | 8 +- .../juce_core/native/juce_posix_SharedCode.h | 2 +- .../juce_core/native/juce_win32_Files.cpp | 4 +- .../juce_core/network/juce_IPAddress.cpp | 2 +- .../modules/juce_core/network/juce_Socket.cpp | 4 +- .../modules/juce_core/network/juce_Socket.h | 2 +- .../modules/juce_core/network/juce_URL.cpp | 8 +- .../modules/juce_core/network/juce_URL.h | 2 +- .../juce_core/system/juce_StandardHeader.h | 2 +- .../juce_core/text/juce_LocalisedStrings.cpp | 17 +- .../juce_core/text/juce_LocalisedStrings.h | 7 + .../modules/juce_core/text/juce_String.cpp | 9 +- .../modules/juce_core/text/juce_String.h | 3 + .../juce_core/text/juce_StringArray.cpp | 2 +- .../juce_core/text/juce_StringPairArray.cpp | 5 + .../juce_core/text/juce_StringPairArray.h | 2 + .../juce_core/text/juce_StringPool.cpp | 6 +- .../juce_core/threads/juce_ThreadPool.h | 2 +- .../time/juce_PerformanceCounter.cpp | 2 +- .../juce_core/time/juce_PerformanceCounter.h | 2 +- .../juce_core/xml/juce_XmlDocument.cpp | 2 +- .../modules/juce_core/zip/juce_ZipFile.cpp | 2 +- .../app_properties/juce_PropertiesFile.cpp | 10 +- .../app_properties/juce_PropertiesFile.h | 2 +- .../juce_data_structures/juce_module_info | 2 +- .../undomanager/juce_UndoManager.cpp | 4 +- .../undomanager/juce_UndoManager.h | 4 +- .../values/juce_ValueTree.cpp | 22 +- .../values/juce_ValueTree.h | 2 +- .../juce_InterprocessConnection.cpp | 2 +- .../modules/juce_events/juce_module_info | 2 +- .../messages/juce_ApplicationBase.cpp | 2 +- .../messages/juce_MessageManager.cpp | 41 +- .../native/juce_mac_MessageManager.mm | 27 +- .../contexts/juce_GraphicsContext.cpp | 18 +- .../fonts/juce_AttributedString.cpp | 2 +- .../fonts/juce_CustomTypeface.cpp | 4 +- .../juce_graphics/fonts/juce_Typeface.h | 6 + .../juce_graphics/geometry/juce_Rectangle.h | 14 +- .../modules/juce_graphics/juce_module_info | 2 +- .../native/juce_android_Fonts.cpp | 8 +- .../native/juce_freetype_Fonts.cpp | 76 +- .../juce_graphics/native/juce_linux_Fonts.cpp | 7 +- .../juce_graphics/native/juce_mac_Fonts.mm | 82 +- .../native/juce_win32_DirectWriteTypeface.cpp | 47 +- .../juce_graphics/native/juce_win32_Fonts.cpp | 164 ++- .../application/juce_Application.cpp | 1 - .../juce_gui_basics/buttons/juce_Button.cpp | 2 +- .../juce_ApplicationCommandManager.cpp | 4 +- .../components/juce_Component.cpp | 39 +- .../components/juce_Component.h | 33 +- .../juce_gui_basics/drawables/juce_Drawable.h | 2 +- .../drawables/juce_SVGParser.cpp | 8 +- .../juce_DirectoryContentsList.cpp | 2 +- .../filebrowser/juce_FileBrowserComponent.cpp | 6 +- .../filebrowser/juce_FileChooserDialogBox.cpp | 8 +- .../juce_FileSearchPathListComponent.cpp | 12 +- .../filebrowser/juce_FilenameComponent.cpp | 2 +- .../juce_ImagePreviewComponent.cpp | 2 +- .../modules/juce_gui_basics/juce_module_info | 2 +- .../keyboard/juce_KeyboardFocusTraverser.h | 6 +- .../layout/juce_ComponentAnimator.cpp | 1 + .../juce_gui_basics/layout/juce_Viewport.cpp | 6 +- .../lookandfeel/juce_LookAndFeel_V1.h | 2 +- .../lookandfeel/juce_LookAndFeel_V2.cpp | 78 +- .../lookandfeel/juce_LookAndFeel_V2.h | 6 +- .../lookandfeel/juce_LookAndFeel_V3.cpp | 30 +- .../lookandfeel/juce_LookAndFeel_V3.h | 5 +- .../juce_gui_basics/menus/juce_PopupMenu.cpp | 108 +- .../juce_gui_basics/menus/juce_PopupMenu.h | 88 +- .../mouse/juce_DragAndDropContainer.cpp | 9 + .../mouse/juce_DragAndDropContainer.h | 2 +- .../native/juce_ios_Windowing.mm | 6 +- .../native/juce_mac_MouseCursor.mm | 19 +- .../native/juce_mac_NSViewComponentPeer.mm | 6 +- .../native/juce_mac_Windowing.mm | 2 +- .../native/juce_win32_FileChooser.cpp | 2 +- .../juce_BooleanPropertyComponent.cpp | 4 +- .../juce_ButtonPropertyComponent.cpp | 2 +- .../juce_ChoicePropertyComponent.cpp | 2 +- .../properties/juce_PropertyPanel.cpp | 2 +- .../properties/juce_PropertyPanel.h | 4 +- .../juce_SliderPropertyComponent.cpp | 4 +- .../juce_gui_basics/widgets/juce_ListBox.cpp | 2 +- .../juce_gui_basics/widgets/juce_Slider.cpp | 9 +- .../widgets/juce_TableHeaderComponent.cpp | 2 +- .../widgets/juce_TableListBox.cpp | 2 +- .../widgets/juce_TableListBox.h | 28 +- .../juce_gui_basics/widgets/juce_Toolbar.cpp | 37 +- .../widgets/juce_ToolbarItemPalette.cpp | 4 +- .../juce_gui_basics/widgets/juce_TreeView.cpp | 2 +- .../windows/juce_CallOutBox.cpp | 2 +- .../windows/juce_ComponentPeer.h | 2 +- .../windows/juce_ThreadWithProgressWindow.cpp | 6 +- .../windows/juce_ThreadWithProgressWindow.h | 4 +- .../windows/juce_TooltipWindow.cpp | 2 +- .../juce_CPlusPlusCodeTokeniserFunctions.h | 161 ++- .../code_editor/juce_CodeDocument.cpp | 5 +- .../code_editor/juce_CodeEditorComponent.cpp | 137 ++- .../code_editor/juce_CodeEditorComponent.h | 8 +- .../code_editor/juce_LuaCodeTokeniser.cpp | 233 ++++ .../code_editor/juce_LuaCodeTokeniser.h | 63 + .../code_editor/juce_XMLCodeTokeniser.cpp | 166 +++ .../code_editor/juce_XMLCodeTokeniser.h | 62 + .../modules/juce_gui_extra/juce_gui_extra.cpp | 8 + .../modules/juce_gui_extra/juce_gui_extra.h | 13 + .../modules/juce_gui_extra/juce_module_info | 2 +- .../misc/juce_ColourSelector.cpp | 4 +- .../misc/juce_KeyMappingEditorComponent.cpp | 4 +- .../misc/juce_LiveConstantEditor.cpp | 466 ++++++++ .../misc/juce_LiveConstantEditor.h | 298 +++++ .../misc/juce_WebBrowserComponent.h | 5 + .../juce_android_WebBrowserComponent.cpp | 9 +- .../native/juce_linux_WebBrowserComponent.cpp | 9 +- .../native/juce_mac_WebBrowserComponent.mm | 15 +- .../native/juce_win32_WebBrowserComponent.cpp | 19 +- 196 files changed, 4592 insertions(+), 1640 deletions(-) create mode 100644 JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.cpp create mode 100644 JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.h create mode 100644 JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp create mode 100644 JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.h create mode 100644 JuceLibraryCode/modules/juce_gui_extra/misc/juce_LiveConstantEditor.cpp create mode 100644 JuceLibraryCode/modules/juce_gui_extra/misc/juce_LiveConstantEditor.h diff --git a/Builds/MacOSX/Dexed.xcodeproj/project.pbxproj b/Builds/MacOSX/Dexed.xcodeproj/project.pbxproj index d6d8d51..ba6efd2 100644 --- a/Builds/MacOSX/Dexed.xcodeproj/project.pbxproj +++ b/Builds/MacOSX/Dexed.xcodeproj/project.pbxproj @@ -86,6 +86,7 @@ 0707C87B401DC983E3FF4263 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_MathsFunctions.h"; path = "../../JuceLibraryCode/modules/juce_core/maths/juce_MathsFunctions.h"; sourceTree = "SOURCE_ROOT"; }; 0733471B6DA02299D2C9590A = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_StringArray.cpp"; path = "../../JuceLibraryCode/modules/juce_core/text/juce_StringArray.cpp"; sourceTree = "SOURCE_ROOT"; }; 075BB641199B15A84856DE6E = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_SliderPropertyComponent.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_basics/properties/juce_SliderPropertyComponent.cpp"; sourceTree = "SOURCE_ROOT"; }; + 076C4F22CCC47AFEAC2D0C68 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_LuaCodeTokeniser.h"; path = "../../JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.h"; sourceTree = "SOURCE_ROOT"; }; 07CB27E064E2A82C2B1D3832 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_AbstractFifo.h"; path = "../../JuceLibraryCode/modules/juce_core/containers/juce_AbstractFifo.h"; sourceTree = "SOURCE_ROOT"; }; 0A3CCDBDD4238A7F9CB559BD = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_FilenameComponent.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h"; sourceTree = "SOURCE_ROOT"; }; 0A55490419140DC6D2FBB1DA = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ComboBox.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_basics/widgets/juce_ComboBox.cpp"; sourceTree = "SOURCE_ROOT"; }; @@ -129,6 +130,7 @@ 17C3024C2A1EE03BC0ED9C96 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "juce_mac_Windowing.mm"; path = "../../JuceLibraryCode/modules/juce_gui_basics/native/juce_mac_Windowing.mm"; sourceTree = "SOURCE_ROOT"; }; 17ED00953353016B7E7492B0 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ApplicationCommandInfo.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandInfo.cpp"; sourceTree = "SOURCE_ROOT"; }; 18AD1662D33E576F96C30A6A = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_TopLevelWindow.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_basics/windows/juce_TopLevelWindow.cpp"; sourceTree = "SOURCE_ROOT"; }; + 18D52C793029AFCC92C77A75 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_LiveConstantEditor.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_extra/misc/juce_LiveConstantEditor.cpp"; sourceTree = "SOURCE_ROOT"; }; 1914E45DEAEBD1592581F1FD = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_AudioPluginFormatManager.h"; path = "../../JuceLibraryCode/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h"; sourceTree = "SOURCE_ROOT"; }; 195B617C1FF754108A97F645 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_KeyPressMappingSet.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/commands/juce_KeyPressMappingSet.h"; sourceTree = "SOURCE_ROOT"; }; 19B56FF22ED0090A8D7A3E04 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "juce_gui_basics.mm"; path = "../../JuceLibraryCode/modules/juce_gui_basics/juce_gui_basics.mm"; sourceTree = "SOURCE_ROOT"; }; @@ -294,6 +296,7 @@ 45F6575A7BDB9134EAEA7633 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ActiveXControlComponent.h"; path = "../../JuceLibraryCode/modules/juce_gui_extra/embedding/juce_ActiveXControlComponent.h"; sourceTree = "SOURCE_ROOT"; }; 462B768DFC2129F54233D51D = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_StretchableLayoutResizerBar.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/layout/juce_StretchableLayoutResizerBar.h"; sourceTree = "SOURCE_ROOT"; }; 46908987EEFC3623A53A95C2 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_LagrangeInterpolator.cpp"; path = "../../JuceLibraryCode/modules/juce_audio_basics/effects/juce_LagrangeInterpolator.cpp"; sourceTree = "SOURCE_ROOT"; }; + 46969E6B78BC89383358DCDA = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_XMLCodeTokeniser.h"; path = "../../JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.h"; sourceTree = "SOURCE_ROOT"; }; 46B8FB88F2949700DD70A821 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_WildcardFileFilter.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_WildcardFileFilter.h"; sourceTree = "SOURCE_ROOT"; }; 46C20298CCB469481F5C8D36 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_MouseInactivityDetector.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/mouse/juce_MouseInactivityDetector.h"; sourceTree = "SOURCE_ROOT"; }; 4710CA869326390AF0CE2A0B = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ResizableEdgeComponent.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableEdgeComponent.h"; sourceTree = "SOURCE_ROOT"; }; @@ -398,6 +401,7 @@ 6D256DC39965C982ADAAFA81 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_FillType.h"; path = "../../JuceLibraryCode/modules/juce_graphics/colour/juce_FillType.h"; sourceTree = "SOURCE_ROOT"; }; 6DC26B61BA1638AFAF142A6C = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "juce_gui_extra.mm"; path = "../../JuceLibraryCode/modules/juce_gui_extra/juce_gui_extra.mm"; sourceTree = "SOURCE_ROOT"; }; 6DCB546B40A962FCA3C74DB9 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_ColourGradient.h"; path = "../../JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.h"; sourceTree = "SOURCE_ROOT"; }; + 6DEFC761C7F27A8ED88790B4 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_XMLCodeTokeniser.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp"; sourceTree = "SOURCE_ROOT"; }; 6E2A781F28B3F735F4FAB2A2 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_KeyMappingEditorComponent.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_extra/misc/juce_KeyMappingEditorComponent.cpp"; sourceTree = "SOURCE_ROOT"; }; 6E84E300D2ECD62ED1ED3478 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_FileInputSource.h"; path = "../../JuceLibraryCode/modules/juce_core/streams/juce_FileInputSource.h"; sourceTree = "SOURCE_ROOT"; }; 6E8FC799E88893F8CD15BB71 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_LADSPAPluginFormat.cpp"; path = "../../JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp"; sourceTree = "SOURCE_ROOT"; }; @@ -838,6 +842,7 @@ F957420DFF4D2354671B4116 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_RectanglePlacement.cpp"; path = "../../JuceLibraryCode/modules/juce_graphics/placement/juce_RectanglePlacement.cpp"; sourceTree = "SOURCE_ROOT"; }; F9B29C9F01195D5A979AB5C9 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_SVGParser.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_basics/drawables/juce_SVGParser.cpp"; sourceTree = "SOURCE_ROOT"; }; F9FA29A5FF9C9921D785A1C0 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_LookAndFeel_V1.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V1.cpp"; sourceTree = "SOURCE_ROOT"; }; + FA0406B777C1CB9C71F86BC1 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_LuaCodeTokeniser.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.cpp"; sourceTree = "SOURCE_ROOT"; }; FA726CE9275EF0E84BBEA666 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_LinkedListPointer.h"; path = "../../JuceLibraryCode/modules/juce_core/containers/juce_LinkedListPointer.h"; sourceTree = "SOURCE_ROOT"; }; FA98BC06299525310A31107B = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Label.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/widgets/juce_Label.h"; sourceTree = "SOURCE_ROOT"; }; FAC5045BEAA6C0B1AC904BED = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_TextDragAndDropTarget.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/mouse/juce_TextDragAndDropTarget.h"; sourceTree = "SOURCE_ROOT"; }; @@ -847,6 +852,7 @@ FC452B781AEE181BEF7F948E = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ComponentPeer.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp"; sourceTree = "SOURCE_ROOT"; }; FC856709502EE15E8D3F448B = { isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; FCABD85F0480D4972896F379 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ReverbAudioSource.cpp"; path = "../../JuceLibraryCode/modules/juce_audio_basics/sources/juce_ReverbAudioSource.cpp"; sourceTree = "SOURCE_ROOT"; }; + FCBA692E842A80D9618CA467 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_LiveConstantEditor.h"; path = "../../JuceLibraryCode/modules/juce_gui_extra/misc/juce_LiveConstantEditor.h"; sourceTree = "SOURCE_ROOT"; }; FD2285710D78FDBC856ADF13 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_AudioProcessorPlayer.h"; path = "../../JuceLibraryCode/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h"; sourceTree = "SOURCE_ROOT"; }; FD333147C1339A81B846EC52 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_RecentlyOpenedFilesList.h"; path = "../../JuceLibraryCode/modules/juce_gui_extra/misc/juce_RecentlyOpenedFilesList.h"; sourceTree = "SOURCE_ROOT"; }; FD799268DEA0EC4CDFD4DA10 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_NewLine.h"; path = "../../JuceLibraryCode/modules/juce_core/text/juce_NewLine.h"; sourceTree = "SOURCE_ROOT"; }; @@ -1741,7 +1747,11 @@ 5DE3DC6998A92F718C9683FA, 71DFB6F3C44390C0FD109073, 7E68BB771E88E0A2A323D365, - 588255FD68989F1A03FDF31C ); name = "code_editor"; sourceTree = ""; }; + 588255FD68989F1A03FDF31C, + FA0406B777C1CB9C71F86BC1, + 076C4F22CCC47AFEAC2D0C68, + 6DEFC761C7F27A8ED88790B4, + 46969E6B78BC89383358DCDA ); name = "code_editor"; sourceTree = ""; }; 3BAE6D34CB0FFE32A18C6008 = { isa = PBXGroup; children = ( BAA44451865610A98B3A69AE, 346937AF08405CC63D570161 ); name = documents; sourceTree = ""; }; @@ -1757,6 +1767,8 @@ 4F977F1C295B0D355391AAD3, 6E2A781F28B3F735F4FAB2A2, A00DC4E59356AF5F1D9C02D1, + 18D52C793029AFCC92C77A75, + FCBA692E842A80D9618CA467, 5BFC8D75FFE4E8DEE50B3B1A, A062855D9DD17397012BC224, AE8321756C03700EB12FF98A, diff --git a/Builds/MacOSX/Dexed.xcodeproj/project.xcworkspace/xcuserdata/asb2m10.xcuserdatad/UserInterfaceState.xcuserstate b/Builds/MacOSX/Dexed.xcodeproj/project.xcworkspace/xcuserdata/asb2m10.xcuserdatad/UserInterfaceState.xcuserstate index c41b940546b99e30e6d602e61604e0b0fed36378..4bb9c08aa7d208dfa37e9b190a12c90135394dd9 100644 GIT binary patch literal 89527 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 diff --git a/Builds/VisualStudio2012/Dexed.vcxproj b/Builds/VisualStudio2012/Dexed.vcxproj index 9ccec7a..e01b429 100644 --- a/Builds/VisualStudio2012/Dexed.vcxproj +++ b/Builds/VisualStudio2012/Dexed.vcxproj @@ -1036,6 +1036,12 @@ true + + true + + + true + true @@ -1048,6 +1054,9 @@ true + + true + true @@ -1502,6 +1511,8 @@ + + @@ -1510,6 +1521,7 @@ + diff --git a/Builds/VisualStudio2012/Dexed.vcxproj.filters b/Builds/VisualStudio2012/Dexed.vcxproj.filters index 3569e1b..f2259fd 100644 --- a/Builds/VisualStudio2012/Dexed.vcxproj.filters +++ b/Builds/VisualStudio2012/Dexed.vcxproj.filters @@ -1270,6 +1270,12 @@ Juce Modules\juce_gui_extra\code_editor + + Juce Modules\juce_gui_extra\code_editor + + + Juce Modules\juce_gui_extra\code_editor + Juce Modules\juce_gui_extra\documents @@ -1282,6 +1288,9 @@ Juce Modules\juce_gui_extra\misc + + Juce Modules\juce_gui_extra\misc + Juce Modules\juce_gui_extra\misc @@ -2556,6 +2565,12 @@ Juce Modules\juce_gui_extra\code_editor + + Juce Modules\juce_gui_extra\code_editor + + + Juce Modules\juce_gui_extra\code_editor + Juce Modules\juce_gui_extra\documents @@ -2580,6 +2595,9 @@ Juce Modules\juce_gui_extra\misc + + Juce Modules\juce_gui_extra\misc + Juce Modules\juce_gui_extra\misc diff --git a/Builds/VisualStudio2013/Dexed.vcxproj b/Builds/VisualStudio2013/Dexed.vcxproj index 0dc5f34..c74725b 100644 --- a/Builds/VisualStudio2013/Dexed.vcxproj +++ b/Builds/VisualStudio2013/Dexed.vcxproj @@ -1036,6 +1036,12 @@ true + + true + + + true + true @@ -1048,6 +1054,9 @@ true + + true + true @@ -1502,6 +1511,8 @@ + + @@ -1510,6 +1521,7 @@ + diff --git a/Builds/VisualStudio2013/Dexed.vcxproj.filters b/Builds/VisualStudio2013/Dexed.vcxproj.filters index ce5b2ed..56f2fec 100644 --- a/Builds/VisualStudio2013/Dexed.vcxproj.filters +++ b/Builds/VisualStudio2013/Dexed.vcxproj.filters @@ -1270,6 +1270,12 @@ Juce Modules\juce_gui_extra\code_editor + + Juce Modules\juce_gui_extra\code_editor + + + Juce Modules\juce_gui_extra\code_editor + Juce Modules\juce_gui_extra\documents @@ -1282,6 +1288,9 @@ Juce Modules\juce_gui_extra\misc + + Juce Modules\juce_gui_extra\misc + Juce Modules\juce_gui_extra\misc @@ -2556,6 +2565,12 @@ Juce Modules\juce_gui_extra\code_editor + + Juce Modules\juce_gui_extra\code_editor + + + Juce Modules\juce_gui_extra\code_editor + Juce Modules\juce_gui_extra\documents @@ -2580,6 +2595,9 @@ Juce Modules\juce_gui_extra\misc + + Juce Modules\juce_gui_extra\misc + Juce Modules\juce_gui_extra\misc diff --git a/JuceLibraryCode/AppConfig.h b/JuceLibraryCode/AppConfig.h index efdaf74..635bb80 100644 --- a/JuceLibraryCode/AppConfig.h +++ b/JuceLibraryCode/AppConfig.h @@ -168,6 +168,10 @@ //#define JUCE_WEB_BROWSER #endif +#ifndef JUCE_ENABLE_LIVE_CONSTANT_EDITOR + //#define JUCE_ENABLE_LIVE_CONSTANT_EDITOR +#endif + //============================================================================== // Audio plugin settings.. diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp index 449e9ae..9dda414 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp @@ -22,10 +22,13 @@ ============================================================================== */ -#if JUCE_USE_SSE_INTRINSICS - namespace FloatVectorHelpers { + + #define JUCE_INCREMENT_SRC_DEST dest += 4; src += 4; + #define JUCE_INCREMENT_DEST dest += 4; + + #if JUCE_USE_SSE_INTRINSICS static bool sse2Present = false; static bool isSSE2Available() noexcept @@ -44,7 +47,6 @@ namespace FloatVectorHelpers static inline float findMinimumOrMaximum (const float* src, int num, const bool isMinimum) noexcept { - #if JUCE_USE_SSE_INTRINSICS const int numLongOps = num / 4; if (numLongOps > 1 && FloatVectorHelpers::isSSE2Available()) @@ -90,66 +92,142 @@ namespace FloatVectorHelpers return localVal; } - #endif return isMinimum ? juce::findMinimum (src, num) : juce::findMaximum (src, num); } -} -#define JUCE_BEGIN_SSE_OP \ - if (FloatVectorHelpers::isSSE2Available()) \ - { \ + #define JUCE_BEGIN_SSE_OP \ + if (FloatVectorHelpers::isSSE2Available()) \ + { \ + const int numLongOps = num / 4; + + #define JUCE_FINISH_SSE_OP(normalOp) \ + num &= 3; \ + if (num == 0) return; \ + } \ + for (int i = 0; i < num; ++i) normalOp; + + #define JUCE_SSE_LOOP(sseOp, srcLoad, dstLoad, dstStore, locals, increment) \ + for (int i = 0; i < numLongOps; ++i) \ + { \ + locals (srcLoad, dstLoad); \ + dstStore (dest, sseOp); \ + increment; \ + } + + #define JUCE_LOAD_NONE(srcLoad, dstLoad) + #define JUCE_LOAD_DEST(srcLoad, dstLoad) const __m128 d = dstLoad (dest); + #define JUCE_LOAD_SRC(srcLoad, dstLoad) const __m128 s = srcLoad (src); + #define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const __m128 d = dstLoad (dest); const __m128 s = srcLoad (src); + + #define JUCE_PERFORM_SSE_OP_DEST(normalOp, sseOp, locals) \ + JUCE_BEGIN_SSE_OP \ + if (FloatVectorHelpers::isAligned (dest)) JUCE_SSE_LOOP (sseOp, dummy, _mm_load_ps, _mm_store_ps, locals, JUCE_INCREMENT_DEST) \ + else JUCE_SSE_LOOP (sseOp, dummy, _mm_loadu_ps, _mm_storeu_ps, locals, JUCE_INCREMENT_DEST) \ + JUCE_FINISH_SSE_OP (normalOp) + + #define JUCE_PERFORM_SSE_OP_SRC_DEST(normalOp, sseOp, locals, increment) \ + JUCE_BEGIN_SSE_OP \ + if (FloatVectorHelpers::isAligned (dest)) \ + { \ + if (FloatVectorHelpers::isAligned (src)) JUCE_SSE_LOOP (sseOp, _mm_load_ps, _mm_load_ps, _mm_store_ps, locals, increment) \ + else JUCE_SSE_LOOP (sseOp, _mm_loadu_ps, _mm_load_ps, _mm_store_ps, locals, increment) \ + }\ + else \ + { \ + if (FloatVectorHelpers::isAligned (src)) JUCE_SSE_LOOP (sseOp, _mm_load_ps, _mm_loadu_ps, _mm_storeu_ps, locals, increment) \ + else JUCE_SSE_LOOP (sseOp, _mm_loadu_ps, _mm_loadu_ps, _mm_storeu_ps, locals, increment) \ + } \ + JUCE_FINISH_SSE_OP (normalOp) + + + //============================================================================== + #elif JUCE_USE_ARM_NEON + + static inline float findMinimumOrMaximum (const float* src, int num, const bool isMinimum) noexcept + { + const int numLongOps = num / 4; + + if (numLongOps > 1) + { + float32x4_t val; + + #define JUCE_MINIMUMMAXIMUM_NEON_LOOP(loadOp, minMaxOp) \ + val = loadOp (src); \ + src += 4; \ + for (int i = 1; i < numLongOps; ++i) \ + { \ + const float32x4_t s = loadOp (src); \ + val = minMaxOp (val, s); \ + src += 4; \ + } + + if (isMinimum) { JUCE_MINIMUMMAXIMUM_NEON_LOOP (vld1q_f32, vminq_f32) } + else { JUCE_MINIMUMMAXIMUM_NEON_LOOP (vld1q_f32, vmaxq_f32) } + + float localVal; + + { + float vals[4]; + vst1q_f32 (vals, val); + + localVal = isMinimum ? jmin (vals[0], vals[1], vals[2], vals[3]) + : jmax (vals[0], vals[1], vals[2], vals[3]); + } + + num &= 3; + + for (int i = 0; i < num; ++i) + localVal = isMinimum ? jmin (localVal, src[i]) + : jmax (localVal, src[i]); + + return localVal; + } + + return isMinimum ? juce::findMinimum (src, num) + : juce::findMaximum (src, num); + } + + #define JUCE_BEGIN_NEON_OP \ const int numLongOps = num / 4; -#define JUCE_FINISH_SSE_OP(normalOp) \ + #define JUCE_FINISH_NEON_OP(normalOp) \ num &= 3; \ if (num == 0) return; \ - } \ - for (int i = 0; i < num; ++i) normalOp; - -#define JUCE_SSE_LOOP(sseOp, srcLoad, dstLoad, dstStore, locals, increment) \ - for (int i = 0; i < numLongOps; ++i) \ - { \ - locals (srcLoad, dstLoad); \ - dstStore (dest, sseOp); \ - increment; \ - } + for (int i = 0; i < num; ++i) normalOp; + + #define JUCE_NEON_LOOP(neonOp, srcLoad, dstLoad, dstStore, locals, increment) \ + for (int i = 0; i < numLongOps; ++i) \ + { \ + locals (srcLoad, dstLoad); \ + dstStore (dest, neonOp); \ + increment; \ + } -#define JUCE_INCREMENT_SRC_DEST dest += 4; src += 4; -#define JUCE_INCREMENT_DEST dest += 4; - -#define JUCE_LOAD_NONE(srcLoad, dstLoad) -#define JUCE_LOAD_DEST(srcLoad, dstLoad) const __m128 d = dstLoad (dest); -#define JUCE_LOAD_SRC(srcLoad, dstLoad) const __m128 s = srcLoad (src); -#define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const __m128 d = dstLoad (dest); const __m128 s = srcLoad (src); - -#define JUCE_PERFORM_SSE_OP_DEST(normalOp, sseOp, locals) \ - JUCE_BEGIN_SSE_OP \ - if (FloatVectorHelpers::isAligned (dest)) JUCE_SSE_LOOP (sseOp, dummy, _mm_load_ps, _mm_store_ps, locals, JUCE_INCREMENT_DEST) \ - else JUCE_SSE_LOOP (sseOp, dummy, _mm_loadu_ps, _mm_storeu_ps, locals, JUCE_INCREMENT_DEST) \ - JUCE_FINISH_SSE_OP (normalOp) - -#define JUCE_PERFORM_SSE_OP_SRC_DEST(normalOp, sseOp, locals, increment) \ - JUCE_BEGIN_SSE_OP \ - if (FloatVectorHelpers::isAligned (dest)) \ - { \ - if (FloatVectorHelpers::isAligned (src)) JUCE_SSE_LOOP (sseOp, _mm_load_ps, _mm_load_ps, _mm_store_ps, locals, increment) \ - else JUCE_SSE_LOOP (sseOp, _mm_loadu_ps, _mm_load_ps, _mm_store_ps, locals, increment) \ - }\ - else \ - { \ - if (FloatVectorHelpers::isAligned (src)) JUCE_SSE_LOOP (sseOp, _mm_load_ps, _mm_loadu_ps, _mm_storeu_ps, locals, increment) \ - else JUCE_SSE_LOOP (sseOp, _mm_loadu_ps, _mm_loadu_ps, _mm_storeu_ps, locals, increment) \ - } \ - JUCE_FINISH_SSE_OP (normalOp) - - -#else - #define JUCE_PERFORM_SSE_OP_DEST(normalOp, unused1, unused2) for (int i = 0; i < num; ++i) normalOp; - #define JUCE_PERFORM_SSE_OP_SRC_DEST(normalOp, sseOp, locals, increment) for (int i = 0; i < num; ++i) normalOp; -#endif + #define JUCE_LOAD_NONE(srcLoad, dstLoad) + #define JUCE_LOAD_DEST(srcLoad, dstLoad) const float32x4_t d = dstLoad (dest); + #define JUCE_LOAD_SRC(srcLoad, dstLoad) const float32x4_t s = srcLoad (src); + #define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const float32x4_t d = dstLoad (dest); const float32x4_t s = srcLoad (src); + + #define JUCE_PERFORM_NEON_OP_DEST(normalOp, neonOp, locals) \ + JUCE_BEGIN_NEON_OP \ + JUCE_NEON_LOOP (neonOp, dummy, vld1q_f32, vst1q_f32, locals, JUCE_INCREMENT_DEST) \ + JUCE_FINISH_NEON_OP (normalOp) + + #define JUCE_PERFORM_NEON_OP_SRC_DEST(normalOp, neonOp, locals) \ + JUCE_BEGIN_NEON_OP \ + JUCE_NEON_LOOP (neonOp, vld1q_f32, vld1q_f32, vst1q_f32, locals, JUCE_INCREMENT_SRC_DEST) \ + JUCE_FINISH_NEON_OP (normalOp) + + //============================================================================== + #else + #define JUCE_PERFORM_SSE_OP_DEST(normalOp, unused1, unused2) for (int i = 0; i < num; ++i) normalOp; + #define JUCE_PERFORM_SSE_OP_SRC_DEST(normalOp, sseOp, locals, increment) for (int i = 0; i < num; ++i) normalOp; + #endif +} +//============================================================================== void JUCE_CALLTYPE FloatVectorOperations::clear (float* dest, int num) noexcept { #if JUCE_USE_VDSP_FRAMEWORK @@ -163,11 +241,13 @@ void JUCE_CALLTYPE FloatVectorOperations::fill (float* dest, float valueToFill, { #if JUCE_USE_VDSP_FRAMEWORK vDSP_vfill (&valueToFill, dest, 1, (size_t) num); + #elif JUCE_USE_ARM_NEON + const float32x4_t val = vld1q_dup_f32 (&valueToFill); + JUCE_PERFORM_NEON_OP_DEST (dest[i] = valueToFill, val, JUCE_LOAD_NONE) #else #if JUCE_USE_SSE_INTRINSICS const __m128 val = _mm_load1_ps (&valueToFill); #endif - JUCE_PERFORM_SSE_OP_DEST (dest[i] = valueToFill, val, JUCE_LOAD_NONE) #endif } @@ -181,58 +261,78 @@ void JUCE_CALLTYPE FloatVectorOperations::copyWithMultiply (float* dest, const f { #if JUCE_USE_VDSP_FRAMEWORK vDSP_vsmul (src, 1, &multiplier, dest, 1, num); + #elif JUCE_USE_ARM_NEON + JUCE_PERFORM_NEON_OP_SRC_DEST (dest[i] += src[i], vmulq_n_f32(s, multiplier), JUCE_LOAD_SRC) #else #if JUCE_USE_SSE_INTRINSICS const __m128 mult = _mm_load1_ps (&multiplier); #endif - - JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] = src[i] * multiplier, - _mm_mul_ps (mult, s), + JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] = src[i] * multiplier, _mm_mul_ps (mult, s), JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST) #endif } +void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float amount, int num) noexcept +{ + #if JUCE_USE_ARM_NEON + const float32x4_t amountToAdd = vld1q_dup_f32(&amount); + JUCE_PERFORM_NEON_OP_DEST (dest[i] += amount, vaddq_f32 (d, amountToAdd), JUCE_LOAD_DEST) + #else + #if JUCE_USE_SSE_INTRINSICS + const __m128 amountToAdd = _mm_load1_ps (&amount); + #endif + JUCE_PERFORM_SSE_OP_DEST (dest[i] += amount, _mm_add_ps (d, amountToAdd), JUCE_LOAD_DEST) + #endif +} + void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src, int num) noexcept { #if JUCE_USE_VDSP_FRAMEWORK vDSP_vadd (src, 1, dest, 1, dest, 1, num); + #elif JUCE_USE_ARM_NEON + JUCE_PERFORM_NEON_OP_SRC_DEST (dest[i] += src[i], vaddq_f32 (d, s), JUCE_LOAD_SRC_DEST) #else - JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] += src[i], - _mm_add_ps (d, s), - JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST) + JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] += src[i], _mm_add_ps (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST) #endif } -void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float amount, int num) noexcept +void JUCE_CALLTYPE FloatVectorOperations::subtract (float* dest, const float* src, int num) noexcept { - #if JUCE_USE_SSE_INTRINSICS - const __m128 amountToAdd = _mm_load1_ps (&amount); + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vsub (src, 1, dest, 1, dest, 1, num); + #elif JUCE_USE_ARM_NEON + JUCE_PERFORM_NEON_OP_SRC_DEST (dest[i] -= src[i], vsubq_f32 (d, s), JUCE_LOAD_SRC_DEST) + #else + JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] -= src[i], _mm_sub_ps (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST) #endif - - JUCE_PERFORM_SSE_OP_DEST (dest[i] += amount, - _mm_add_ps (d, amountToAdd), - JUCE_LOAD_DEST) } void JUCE_CALLTYPE FloatVectorOperations::addWithMultiply (float* dest, const float* src, float multiplier, int num) noexcept { - #if JUCE_USE_SSE_INTRINSICS - const __m128 mult = _mm_load1_ps (&multiplier); + #if JUCE_USE_ARM_NEON + JUCE_PERFORM_NEON_OP_SRC_DEST (dest[i] += src[i] * multiplier, + vmlaq_n_f32 (d, s, multiplier), + JUCE_LOAD_SRC_DEST) + #else + #if JUCE_USE_SSE_INTRINSICS + const __m128 mult = _mm_load1_ps (&multiplier); + #endif + + JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] += src[i] * multiplier, + _mm_add_ps (d, _mm_mul_ps (mult, s)), + JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST) #endif - JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] += src[i] * multiplier, - _mm_add_ps (d, _mm_mul_ps (mult, s)), - JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST) } void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, const float* src, int num) noexcept { #if JUCE_USE_VDSP_FRAMEWORK vDSP_vmul (src, 1, dest, 1, dest, 1, num); + #elif JUCE_USE_ARM_NEON + JUCE_PERFORM_NEON_OP_SRC_DEST (dest[i] *= src[i], vmulq_f32 (d, s), JUCE_LOAD_SRC_DEST) #else - JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] *= src[i], - _mm_mul_ps (d, s), - JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST) + JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] *= src[i], _mm_mul_ps (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST) #endif } @@ -240,14 +340,13 @@ void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, float multiplie { #if JUCE_USE_VDSP_FRAMEWORK vDSP_vsmul (dest, 1, &multiplier, dest, 1, num); + #elif JUCE_USE_ARM_NEON + JUCE_PERFORM_NEON_OP_DEST (dest[i] *= multiplier, vmulq_n_f32 (d, multiplier), JUCE_LOAD_DEST) #else #if JUCE_USE_SSE_INTRINSICS const __m128 mult = _mm_load1_ps (&multiplier); #endif - - JUCE_PERFORM_SSE_OP_DEST (dest[i] *= multiplier, - _mm_mul_ps (d, mult), - JUCE_LOAD_DEST) + JUCE_PERFORM_SSE_OP_DEST (dest[i] *= multiplier, _mm_mul_ps (d, mult), JUCE_LOAD_DEST) #endif } @@ -262,13 +361,19 @@ void FloatVectorOperations::negate (float* dest, const float* src, int num) noex void JUCE_CALLTYPE FloatVectorOperations::convertFixedToFloat (float* dest, const int* src, float multiplier, int num) noexcept { - #if JUCE_USE_SSE_INTRINSICS - const __m128 mult = _mm_load1_ps (&multiplier); - #endif + #if JUCE_USE_ARM_NEON + JUCE_PERFORM_NEON_OP_SRC_DEST (dest[i] = src[i] * multiplier, + vmulq_n_f32 (vcvtq_f32_s32 (vld1q_s32 (src)), multiplier), + JUCE_LOAD_NONE) + #else + #if JUCE_USE_SSE_INTRINSICS + const __m128 mult = _mm_load1_ps (&multiplier); + #endif - JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] = src[i] * multiplier, - _mm_mul_ps (mult, _mm_cvtepi32_ps (_mm_loadu_si128 ((const __m128i*) src))), - JUCE_LOAD_NONE, JUCE_INCREMENT_SRC_DEST) + JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] = src[i] * multiplier, + _mm_mul_ps (mult, _mm_cvtepi32_ps (_mm_loadu_si128 ((const __m128i*) src))), + JUCE_LOAD_NONE, JUCE_INCREMENT_SRC_DEST) + #endif } void JUCE_CALLTYPE FloatVectorOperations::findMinAndMax (const float* src, int num, float& minResult, float& maxResult) noexcept @@ -315,6 +420,51 @@ void JUCE_CALLTYPE FloatVectorOperations::findMinAndMax (const float* src, int n localMax = jmax (localMax, s); } + minResult = localMin; + maxResult = localMax; + return; + } + #elif JUCE_USE_ARM_NEON + const int numLongOps = num / 4; + + if (numLongOps > 1) + { + float32x4_t mn, mx; + + #define JUCE_MINMAX_NEON_LOOP(loadOp) \ + mn = loadOp (src); \ + mx = mn; \ + src += 4; \ + for (int i = 1; i < numLongOps; ++i) \ + { \ + const float32x4_t s = loadOp (src); \ + mn = vminq_f32 (mn, s); \ + mx = vmaxq_f32 (mx, s); \ + src += 4; \ + } + + JUCE_MINMAX_NEON_LOOP (vld1q_f32); + + float localMin, localMax; + + { + float mns[4], mxs[4]; + vst1q_f32 (mns, mn); + vst1q_f32 (mxs, mx); + + localMin = jmin (mns[0], mns[1], mns[2], mns[3]); + localMax = jmax (mxs[0], mxs[1], mxs[2], mxs[3]); + } + + num &= 3; + + for (int i = 0; i < num; ++i) + { + const float s = src[i]; + localMin = jmin (localMin, s); + localMax = jmax (localMax, s); + } + minResult = localMin; maxResult = localMax; return; @@ -326,7 +476,7 @@ void JUCE_CALLTYPE FloatVectorOperations::findMinAndMax (const float* src, int n float JUCE_CALLTYPE FloatVectorOperations::findMinimum (const float* src, int num) noexcept { - #if JUCE_USE_SSE_INTRINSICS + #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON return FloatVectorHelpers::findMinimumOrMaximum (src, num, true); #else return juce::findMinimum (src, num); @@ -335,7 +485,7 @@ float JUCE_CALLTYPE FloatVectorOperations::findMinimum (const float* src, int nu float JUCE_CALLTYPE FloatVectorOperations::findMaximum (const float* src, int num) noexcept { - #if JUCE_USE_SSE_INTRINSICS + #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON return FloatVectorHelpers::findMinimumOrMaximum (src, num, false); #else return juce::findMaximum (src, num); @@ -350,3 +500,129 @@ void JUCE_CALLTYPE FloatVectorOperations::enableFlushToZeroMode (bool shouldEnab #endif (void) shouldEnable; } + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class FloatVectorOperationsTests : public UnitTest +{ +public: + FloatVectorOperationsTests() : UnitTest ("FloatVectorOperations") {} + + void runTest() + { + beginTest ("FloatVectorOperations"); + + for (int i = 100; --i >= 0;) + { + const int num = getRandom().nextInt (500) + 1; + + HeapBlock buffer1 (num + 16), buffer2 (num + 16); + HeapBlock buffer3 (num + 16); + + #if JUCE_ARM + float* const data1 = buffer1; + float* const data2 = buffer2; + int* const int1 = buffer3; + #else + float* const data1 = addBytesToPointer (buffer1.getData(), getRandom().nextInt (16)); + float* const data2 = addBytesToPointer (buffer2.getData(), getRandom().nextInt (16)); + int* const int1 = addBytesToPointer (buffer3.getData(), getRandom().nextInt (16)); + #endif + + fillRandomly (data1, num); + fillRandomly (data2, num); + + float mn1, mx1, mn2, mx2; + FloatVectorOperations::findMinAndMax (data1, num, mn1, mx1); + juce::findMinAndMax (data1, num, mn2, mx2); + expect (mn1 == mn2); + expect (mx1 == mx2); + + expect (FloatVectorOperations::findMinimum (data1, num) == juce::findMinimum (data1, num)); + expect (FloatVectorOperations::findMaximum (data1, num) == juce::findMaximum (data1, num)); + + expect (FloatVectorOperations::findMinimum (data2, num) == juce::findMinimum (data2, num)); + expect (FloatVectorOperations::findMaximum (data2, num) == juce::findMaximum (data2, num)); + + FloatVectorOperations::clear (data1, num); + expect (areAllValuesEqual (data1, num, 0)); + + FloatVectorOperations::fill (data1, 2.0f, num); + expect (areAllValuesEqual (data1, num, 2.0f)); + + FloatVectorOperations::add (data1, 2.0f, num); + expect (areAllValuesEqual (data1, num, 4.0f)); + + FloatVectorOperations::copy (data2, data1, num); + expect (areAllValuesEqual (data2, num, 4.0f)); + + FloatVectorOperations::add (data2, data1, num); + expect (areAllValuesEqual (data2, num, 8.0f)); + + FloatVectorOperations::copyWithMultiply (data2, data1, 4.0f, num); + expect (areAllValuesEqual (data2, num, 16.0f)); + + FloatVectorOperations::addWithMultiply (data2, data1, 4.0f, num); + expect (areAllValuesEqual (data2, num, 32.0f)); + + FloatVectorOperations::multiply (data1, 2.0f, num); + expect (areAllValuesEqual (data1, num, 8.0f)); + + FloatVectorOperations::multiply (data1, data2, num); + expect (areAllValuesEqual (data1, num, 256.0f)); + + FloatVectorOperations::negate (data2, data1, num); + expect (areAllValuesEqual (data2, num, -256.0f)); + + FloatVectorOperations::subtract (data1, data2, num); + expect (areAllValuesEqual (data1, num, 512.0f)); + + fillRandomly (int1, num); + FloatVectorOperations::convertFixedToFloat (data1, int1, 2.0f, num); + convertFixed (data2, int1, 2.0f, num); + expect (buffersMatch (data1, data2, num)); + } + } + + void fillRandomly (float* d, int num) + { + while (--num >= 0) + *d++ = getRandom().nextFloat() * 1000.0f; + } + + void fillRandomly (int* d, int num) + { + while (--num >= 0) + *d++ = getRandom().nextInt(); + } + + static void convertFixed (float* d, const int* s, float multiplier, int num) + { + while (--num >= 0) + *d++ = *s++ * multiplier; + } + + static bool areAllValuesEqual (const float* d, int num, float target) + { + while (--num >= 0) + if (*d++ != target) + return false; + + return true; + } + + static bool buffersMatch (const float* d1, const float* d2, int num) + { + while (--num >= 0) + if (std::abs (*d1++ - *d2++) > std::numeric_limits::epsilon()) + return false; + + return true; + } +}; + +static FloatVectorOperationsTests vectorOpTests; + +#endif diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h index 6268ab9..6ffcfab 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h @@ -47,11 +47,14 @@ public: /** Copies a vector of floats, multiplying each value by a given multiplier */ static void JUCE_CALLTYPE copyWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; + /** Adds a fixed value to the destination values. */ + static void JUCE_CALLTYPE add (float* dest, float 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; - /** Adds a fixed value to the destination values. */ - static void JUCE_CALLTYPE add (float* dest, float amount, int numValues) noexcept; + /** Subtracts the source values from the destination values. */ + static void JUCE_CALLTYPE subtract (float* dest, const float* src, int numValues) noexcept; /** Multiplies each source value by the given multiplier, then adds it to the destination value. */ static void JUCE_CALLTYPE addWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; diff --git a/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.cpp b/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.cpp index 06ed681..ee7d3cf 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.cpp @@ -58,6 +58,11 @@ #undef JUCE_USE_VDSP_FRAMEWORK #endif +#if __ARM_NEON__ && ! (JUCE_USE_VDSP_FRAMEWORK || defined (JUCE_USE_ARM_NEON)) + #define JUCE_USE_ARM_NEON 1 + #include +#endif + namespace juce { diff --git a/JuceLibraryCode/modules/juce_audio_basics/juce_module_info b/JuceLibraryCode/modules/juce_audio_basics/juce_module_info index 455a820..acdd765 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/juce_module_info +++ b/JuceLibraryCode/modules/juce_audio_basics/juce_module_info @@ -1,7 +1,7 @@ { "id": "juce_audio_basics", "name": "JUCE audio and midi data classes", - "version": "3.0.0", + "version": "3.0.1", "description": "Classes for audio buffer manipulation, midi message handling, synthesis, etc", "website": "http://www.juce.com/juce", "license": "GPL/Commercial", diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.cpp index aa866ee..c111404 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.cpp @@ -945,29 +945,38 @@ const char* MidiMessage::getGMInstrumentName (const int n) { static const char* names[] = { - "Acoustic Grand Piano", "Bright Acoustic Piano", "Electric Grand Piano", "Honky-tonk Piano", - "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavinet", "Celesta", "Glockenspiel", - "Music Box", "Vibraphone", "Marimba", "Xylophone", "Tubular Bells", "Dulcimer", "Drawbar Organ", - "Percussive Organ", "Rock Organ", "Church Organ", "Reed Organ", "Accordion", "Harmonica", - "Tango Accordion", "Acoustic Guitar (nylon)", "Acoustic Guitar (steel)", "Electric Guitar (jazz)", - "Electric Guitar (clean)", "Electric Guitar (mute)", "Overdriven Guitar", "Distortion Guitar", - "Guitar Harmonics", "Acoustic Bass", "Electric Bass (finger)", "Electric Bass (pick)", - "Fretless Bass", "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2", "Violin", - "Viola", "Cello", "Contrabass", "Tremolo Strings", "Pizzicato Strings", "Orchestral Harp", - "Timpani", "String Ensemble 1", "String Ensemble 2", "SynthStrings 1", "SynthStrings 2", - "Choir Aahs", "Voice Oohs", "Synth Voice", "Orchestra Hit", "Trumpet", "Trombone", "Tuba", - "Muted Trumpet", "French Horn", "Brass Section", "SynthBrass 1", "SynthBrass 2", "Soprano Sax", - "Alto Sax", "Tenor Sax", "Baritone Sax", "Oboe", "English Horn", "Bassoon", "Clarinet", - "Piccolo", "Flute", "Recorder", "Pan Flute", "Blown Bottle", "Shakuhachi", "Whistle", - "Ocarina", "Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (calliope)", "Lead 4 (chiff)", - "Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)", "Lead 8 (bass+lead)", "Pad 1 (new age)", - "Pad 2 (warm)", "Pad 3 (polysynth)", "Pad 4 (choir)", "Pad 5 (bowed)", "Pad 6 (metallic)", - "Pad 7 (halo)", "Pad 8 (sweep)", "FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)", - "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)", "FX 8 (sci-fi)", - "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba", "Bag pipe", "Fiddle", "Shanai", "Tinkle Bell", - "Agogo", "Steel Drums", "Woodblock", "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", - "Guitar Fret Noise", "Breath Noise", "Seashore", "Bird Tweet", "Telephone Ring", "Helicopter", - "Applause", "Gunshot" + NEEDS_TRANS("Acoustic Grand Piano"), NEEDS_TRANS("Bright Acoustic Piano"), NEEDS_TRANS("Electric Grand Piano"), NEEDS_TRANS("Honky-tonk Piano"), + NEEDS_TRANS("Electric Piano 1"), NEEDS_TRANS("Electric Piano 2"), NEEDS_TRANS("Harpsichord"), NEEDS_TRANS("Clavinet"), + NEEDS_TRANS("Celesta"), NEEDS_TRANS("Glockenspiel"), NEEDS_TRANS("Music Box"), NEEDS_TRANS("Vibraphone"), + NEEDS_TRANS("Marimba"), NEEDS_TRANS("Xylophone"), NEEDS_TRANS("Tubular Bells"), NEEDS_TRANS("Dulcimer"), + NEEDS_TRANS("Drawbar Organ"), NEEDS_TRANS("Percussive Organ"), NEEDS_TRANS("Rock Organ"), NEEDS_TRANS("Church Organ"), + NEEDS_TRANS("Reed Organ"), NEEDS_TRANS("Accordion"), NEEDS_TRANS("Harmonica"), NEEDS_TRANS("Tango Accordion"), + NEEDS_TRANS("Acoustic Guitar (nylon)"), NEEDS_TRANS("Acoustic Guitar (steel)"), NEEDS_TRANS("Electric Guitar (jazz)"), NEEDS_TRANS("Electric Guitar (clean)"), + NEEDS_TRANS("Electric Guitar (mute)"), NEEDS_TRANS("Overdriven Guitar"), NEEDS_TRANS("Distortion Guitar"), NEEDS_TRANS("Guitar Harmonics"), + NEEDS_TRANS("Acoustic Bass"), NEEDS_TRANS("Electric Bass (finger)"), NEEDS_TRANS("Electric Bass (pick)"), NEEDS_TRANS("Fretless Bass"), + NEEDS_TRANS("Slap Bass 1"), NEEDS_TRANS("Slap Bass 2"), NEEDS_TRANS("Synth Bass 1"), NEEDS_TRANS("Synth Bass 2"), + NEEDS_TRANS("Violin"), NEEDS_TRANS("Viola"), NEEDS_TRANS("Cello"), NEEDS_TRANS("Contrabass"), + NEEDS_TRANS("Tremolo Strings"), NEEDS_TRANS("Pizzicato Strings"), NEEDS_TRANS("Orchestral Harp"), NEEDS_TRANS("Timpani"), + NEEDS_TRANS("String Ensemble 1"), NEEDS_TRANS("String Ensemble 2"), NEEDS_TRANS("SynthStrings 1"), NEEDS_TRANS("SynthStrings 2"), + NEEDS_TRANS("Choir Aahs"), NEEDS_TRANS("Voice Oohs"), NEEDS_TRANS("Synth Voice"), NEEDS_TRANS("Orchestra Hit"), + NEEDS_TRANS("Trumpet"), NEEDS_TRANS("Trombone"), NEEDS_TRANS("Tuba"), NEEDS_TRANS("Muted Trumpet"), + NEEDS_TRANS("French Horn"), NEEDS_TRANS("Brass Section"), NEEDS_TRANS("SynthBrass 1"), NEEDS_TRANS("SynthBrass 2"), + NEEDS_TRANS("Soprano Sax"), NEEDS_TRANS("Alto Sax"), NEEDS_TRANS("Tenor Sax"), NEEDS_TRANS("Baritone Sax"), + NEEDS_TRANS("Oboe"), NEEDS_TRANS("English Horn"), NEEDS_TRANS("Bassoon"), NEEDS_TRANS("Clarinet"), + NEEDS_TRANS("Piccolo"), NEEDS_TRANS("Flute"), NEEDS_TRANS("Recorder"), NEEDS_TRANS("Pan Flute"), + NEEDS_TRANS("Blown Bottle"), NEEDS_TRANS("Shakuhachi"), NEEDS_TRANS("Whistle"), NEEDS_TRANS("Ocarina"), + NEEDS_TRANS("Lead 1 (square)"), NEEDS_TRANS("Lead 2 (sawtooth)"), NEEDS_TRANS("Lead 3 (calliope)"), NEEDS_TRANS("Lead 4 (chiff)"), + NEEDS_TRANS("Lead 5 (charang)"), NEEDS_TRANS("Lead 6 (voice)"), NEEDS_TRANS("Lead 7 (fifths)"), NEEDS_TRANS("Lead 8 (bass+lead)"), + NEEDS_TRANS("Pad 1 (new age)"), NEEDS_TRANS("Pad 2 (warm)"), NEEDS_TRANS("Pad 3 (polysynth)"), NEEDS_TRANS("Pad 4 (choir)"), + NEEDS_TRANS("Pad 5 (bowed)"), NEEDS_TRANS("Pad 6 (metallic)"), NEEDS_TRANS("Pad 7 (halo)"), NEEDS_TRANS("Pad 8 (sweep)"), + NEEDS_TRANS("FX 1 (rain)"), NEEDS_TRANS("FX 2 (soundtrack)"), NEEDS_TRANS("FX 3 (crystal)"), NEEDS_TRANS("FX 4 (atmosphere)"), + NEEDS_TRANS("FX 5 (brightness)"), NEEDS_TRANS("FX 6 (goblins)"), NEEDS_TRANS("FX 7 (echoes)"), NEEDS_TRANS("FX 8 (sci-fi)"), + NEEDS_TRANS("Sitar"), NEEDS_TRANS("Banjo"), NEEDS_TRANS("Shamisen"), NEEDS_TRANS("Koto"), + NEEDS_TRANS("Kalimba"), NEEDS_TRANS("Bag pipe"), NEEDS_TRANS("Fiddle"), NEEDS_TRANS("Shanai"), + NEEDS_TRANS("Tinkle Bell"), NEEDS_TRANS("Agogo"), NEEDS_TRANS("Steel Drums"), NEEDS_TRANS("Woodblock"), + NEEDS_TRANS("Taiko Drum"), NEEDS_TRANS("Melodic Tom"), NEEDS_TRANS("Synth Drum"), NEEDS_TRANS("Reverse Cymbal"), + NEEDS_TRANS("Guitar Fret Noise"), NEEDS_TRANS("Breath Noise"), NEEDS_TRANS("Seashore"), NEEDS_TRANS("Bird Tweet"), + NEEDS_TRANS("Telephone Ring"), NEEDS_TRANS("Helicopter"), NEEDS_TRANS("Applause"), NEEDS_TRANS("Gunshot") }; return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr; @@ -977,10 +986,10 @@ const char* MidiMessage::getGMInstrumentBankName (const int n) { static const char* names[] = { - "Piano", "Chromatic Percussion", "Organ", "Guitar", - "Bass", "Strings", "Ensemble", "Brass", - "Reed", "Pipe", "Synth Lead", "Synth Pad", - "Synth Effects", "Ethnic", "Percussive", "Sound Effects" + NEEDS_TRANS("Piano"), NEEDS_TRANS("Chromatic Percussion"), NEEDS_TRANS("Organ"), NEEDS_TRANS("Guitar"), + NEEDS_TRANS("Bass"), NEEDS_TRANS("Strings"), NEEDS_TRANS("Ensemble"), NEEDS_TRANS("Brass"), + NEEDS_TRANS("Reed"), NEEDS_TRANS("Pipe"), NEEDS_TRANS("Synth Lead"), NEEDS_TRANS("Synth Pad"), + NEEDS_TRANS("Synth Effects"), NEEDS_TRANS("Ethnic"), NEEDS_TRANS("Percussive"), NEEDS_TRANS("Sound Effects") }; return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr; @@ -990,15 +999,18 @@ const char* MidiMessage::getRhythmInstrumentName (const int n) { static const char* names[] = { - "Acoustic Bass Drum", "Bass Drum 1", "Side Stick", "Acoustic Snare", - "Hand Clap", "Electric Snare", "Low Floor Tom", "Closed Hi-Hat", "High Floor Tom", - "Pedal Hi-Hat", "Low Tom", "Open Hi-Hat", "Low-Mid Tom", "Hi-Mid Tom", "Crash Cymbal 1", - "High Tom", "Ride Cymbal 1", "Chinese Cymbal", "Ride Bell", "Tambourine", "Splash Cymbal", - "Cowbell", "Crash Cymbal 2", "Vibraslap", "Ride Cymbal 2", "Hi Bongo", "Low Bongo", - "Mute Hi Conga", "Open Hi Conga", "Low Conga", "High Timbale", "Low Timbale", "High Agogo", - "Low Agogo", "Cabasa", "Maracas", "Short Whistle", "Long Whistle", "Short Guiro", - "Long Guiro", "Claves", "Hi Wood Block", "Low Wood Block", "Mute Cuica", "Open Cuica", - "Mute Triangle", "Open Triangle" + NEEDS_TRANS("Acoustic Bass Drum"), NEEDS_TRANS("Bass Drum 1"), NEEDS_TRANS("Side Stick"), NEEDS_TRANS("Acoustic Snare"), + NEEDS_TRANS("Hand Clap"), NEEDS_TRANS("Electric Snare"), NEEDS_TRANS("Low Floor Tom"), NEEDS_TRANS("Closed Hi-Hat"), + NEEDS_TRANS("High Floor Tom"), NEEDS_TRANS("Pedal Hi-Hat"), NEEDS_TRANS("Low Tom"), NEEDS_TRANS("Open Hi-Hat"), + NEEDS_TRANS("Low-Mid Tom"), NEEDS_TRANS("Hi-Mid Tom"), NEEDS_TRANS("Crash Cymbal 1"), NEEDS_TRANS("High Tom"), + NEEDS_TRANS("Ride Cymbal 1"), NEEDS_TRANS("Chinese Cymbal"), NEEDS_TRANS("Ride Bell"), NEEDS_TRANS("Tambourine"), + NEEDS_TRANS("Splash Cymbal"), NEEDS_TRANS("Cowbell"), NEEDS_TRANS("Crash Cymbal 2"), NEEDS_TRANS("Vibraslap"), + NEEDS_TRANS("Ride Cymbal 2"), NEEDS_TRANS("Hi Bongo"), NEEDS_TRANS("Low Bongo"), NEEDS_TRANS("Mute Hi Conga"), + NEEDS_TRANS("Open Hi Conga"), NEEDS_TRANS("Low Conga"), NEEDS_TRANS("High Timbale"), NEEDS_TRANS("Low Timbale"), + NEEDS_TRANS("High Agogo"), NEEDS_TRANS("Low Agogo"), NEEDS_TRANS("Cabasa"), NEEDS_TRANS("Maracas"), + NEEDS_TRANS("Short Whistle"), NEEDS_TRANS("Long Whistle"), NEEDS_TRANS("Short Guiro"), NEEDS_TRANS("Long Guiro"), + NEEDS_TRANS("Claves"), NEEDS_TRANS("Hi Wood Block"), NEEDS_TRANS("Low Wood Block"), NEEDS_TRANS("Mute Cuica"), + NEEDS_TRANS("Open Cuica"), NEEDS_TRANS("Mute Triangle"), NEEDS_TRANS("Open Triangle") }; return (n >= 35 && n <= 81) ? names [n - 35] : nullptr; @@ -1008,28 +1020,38 @@ const char* MidiMessage::getControllerName (const int n) { static const char* names[] = { - "Bank Select", "Modulation Wheel (coarse)", "Breath controller (coarse)", - 0, "Foot Pedal (coarse)", "Portamento Time (coarse)", - "Data Entry (coarse)", "Volume (coarse)", "Balance (coarse)", - 0, "Pan position (coarse)", "Expression (coarse)", "Effect Control 1 (coarse)", - "Effect Control 2 (coarse)", 0, 0, "General Purpose Slider 1", "General Purpose Slider 2", - "General Purpose Slider 3", "General Purpose Slider 4", 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, "Bank Select (fine)", "Modulation Wheel (fine)", "Breath controller (fine)", - 0, "Foot Pedal (fine)", "Portamento Time (fine)", "Data Entry (fine)", "Volume (fine)", - "Balance (fine)", 0, "Pan position (fine)", "Expression (fine)", "Effect Control 1 (fine)", - "Effect Control 2 (fine)", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - "Hold Pedal (on/off)", "Portamento (on/off)", "Sustenuto Pedal (on/off)", "Soft Pedal (on/off)", - "Legato Pedal (on/off)", "Hold 2 Pedal (on/off)", "Sound Variation", "Sound Timbre", - "Sound Release Time", "Sound Attack Time", "Sound Brightness", "Sound Control 6", - "Sound Control 7", "Sound Control 8", "Sound Control 9", "Sound Control 10", - "General Purpose Button 1 (on/off)", "General Purpose Button 2 (on/off)", - "General Purpose Button 3 (on/off)", "General Purpose Button 4 (on/off)", - 0, 0, 0, 0, 0, 0, 0, "Reverb Level", "Tremolo Level", "Chorus Level", "Celeste Level", - "Phaser Level", "Data Button increment", "Data Button decrement", "Non-registered Parameter (fine)", - "Non-registered Parameter (coarse)", "Registered Parameter (fine)", "Registered Parameter (coarse)", - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "All Sound Off", "All Controllers Off", - "Local Keyboard (on/off)", "All Notes Off", "Omni Mode Off", "Omni Mode On", "Mono Operation", - "Poly Operation" + NEEDS_TRANS("Bank Select"), NEEDS_TRANS("Modulation Wheel (coarse)"), NEEDS_TRANS("Breath controller (coarse)"), + nullptr, + NEEDS_TRANS("Foot Pedal (coarse)"), NEEDS_TRANS("Portamento Time (coarse)"), NEEDS_TRANS("Data Entry (coarse)"), + NEEDS_TRANS("Volume (coarse)"), NEEDS_TRANS("Balance (coarse)"), + nullptr, + NEEDS_TRANS("Pan position (coarse)"), NEEDS_TRANS("Expression (coarse)"), NEEDS_TRANS("Effect Control 1 (coarse)"), + NEEDS_TRANS("Effect Control 2 (coarse)"), + nullptr, nullptr, + NEEDS_TRANS("General Purpose Slider 1"), NEEDS_TRANS("General Purpose Slider 2"), + NEEDS_TRANS("General Purpose Slider 3"), NEEDS_TRANS("General Purpose Slider 4"), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + NEEDS_TRANS("Bank Select (fine)"), NEEDS_TRANS("Modulation Wheel (fine)"), NEEDS_TRANS("Breath controller (fine)"), + nullptr, + NEEDS_TRANS("Foot Pedal (fine)"), NEEDS_TRANS("Portamento Time (fine)"), NEEDS_TRANS("Data Entry (fine)"), NEEDS_TRANS("Volume (fine)"), + NEEDS_TRANS("Balance (fine)"), nullptr, NEEDS_TRANS("Pan position (fine)"), NEEDS_TRANS("Expression (fine)"), + NEEDS_TRANS("Effect Control 1 (fine)"), NEEDS_TRANS("Effect Control 2 (fine)"), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + NEEDS_TRANS("Hold Pedal (on/off)"), NEEDS_TRANS("Portamento (on/off)"), NEEDS_TRANS("Sustenuto Pedal (on/off)"), NEEDS_TRANS("Soft Pedal (on/off)"), + NEEDS_TRANS("Legato Pedal (on/off)"), NEEDS_TRANS("Hold 2 Pedal (on/off)"), NEEDS_TRANS("Sound Variation"), NEEDS_TRANS("Sound Timbre"), + NEEDS_TRANS("Sound Release Time"), NEEDS_TRANS("Sound Attack Time"), NEEDS_TRANS("Sound Brightness"), NEEDS_TRANS("Sound Control 6"), + NEEDS_TRANS("Sound Control 7"), NEEDS_TRANS("Sound Control 8"), NEEDS_TRANS("Sound Control 9"), NEEDS_TRANS("Sound Control 10"), + NEEDS_TRANS("General Purpose Button 1 (on/off)"), NEEDS_TRANS("General Purpose Button 2 (on/off)"), + NEEDS_TRANS("General Purpose Button 3 (on/off)"), NEEDS_TRANS("General Purpose Button 4 (on/off)"), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + NEEDS_TRANS("Reverb Level"), NEEDS_TRANS("Tremolo Level"), NEEDS_TRANS("Chorus Level"), NEEDS_TRANS("Celeste Level"), + NEEDS_TRANS("Phaser Level"), NEEDS_TRANS("Data Button increment"), NEEDS_TRANS("Data Button decrement"), NEEDS_TRANS("Non-registered Parameter (fine)"), + NEEDS_TRANS("Non-registered Parameter (coarse)"), NEEDS_TRANS("Registered Parameter (fine)"), NEEDS_TRANS("Registered Parameter (coarse)"), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + NEEDS_TRANS("All Sound Off"), NEEDS_TRANS("All Controllers Off"), NEEDS_TRANS("Local Keyboard (on/off)"), NEEDS_TRANS("All Notes Off"), + NEEDS_TRANS("Omni Mode Off"), NEEDS_TRANS("Omni Mode On"), NEEDS_TRANS("Mono Operation"), NEEDS_TRANS("Poly Operation") }; return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr; diff --git a/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp b/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp index 6af687e..1aa248b 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp @@ -56,6 +56,8 @@ void SynthesiserVoice::clearCurrentNote() currentlyPlayingSound = nullptr; } +void SynthesiserVoice::aftertouchChanged (int) {} + //============================================================================== Synthesiser::Synthesiser() : sampleRate (0), @@ -191,6 +193,10 @@ void Synthesiser::handleMidiEvent (const MidiMessage& m) handlePitchWheel (channel, wheelPos); } + else if (m.isAftertouch()) + { + handleAftertouch (m.getChannel(), m.getNoteNumber(), m.getAfterTouchValue()); + } else if (m.isController()) { handleController (m.getChannel(), m.getControllerNumber(), m.getControllerValue()); @@ -338,6 +344,20 @@ void Synthesiser::handleController (const int midiChannel, } } +void Synthesiser::handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue) +{ + const ScopedLock sl (lock); + + for (int i = voices.size(); --i >= 0;) + { + SynthesiserVoice* const voice = voices.getUnchecked (i); + + if (voice->getCurrentlyPlayingNote() == midiNoteNumber + && (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))) + voice->aftertouchChanged (aftertouchValue); + } +} + void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) { jassert (midiChannel > 0 && midiChannel <= 16); diff --git a/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h b/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h index eb443cf..7202b4b 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h +++ b/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h @@ -116,7 +116,6 @@ public: virtual bool canPlaySound (SynthesiserSound*) = 0; /** Called to start a new note. - This will be called during the rendering callback, so must be fast and thread-safe. */ virtual void startNote (int midiNoteNumber, @@ -142,12 +141,17 @@ public: /** Called to let the voice know that the pitch wheel has been moved. This will be called during the rendering callback, so must be fast and thread-safe. */ - virtual void pitchWheelMoved (int newValue) = 0; + virtual void pitchWheelMoved (int newPitchWheelValue) = 0; /** Called to let the voice know that a midi controller has been moved. This will be called during the rendering callback, so must be fast and thread-safe. */ - virtual void controllerMoved (int controllerNumber, int newValue) = 0; + virtual void controllerMoved (int controllerNumber, int newControllerValue) = 0; + + /** Called to let the voice know that the aftertouch has changed. + This will be called during the rendering callback, so must be fast and thread-safe. + */ + virtual void aftertouchChanged (int newAftertouchValue); //============================================================================== /** Renders the next block of data for this voice. @@ -186,6 +190,14 @@ public: */ void setCurrentPlaybackSampleRate (double newRate); + /** Returns true if the key that triggered this voice is still held down. + Note that the voice may still be playing after the key was released (e.g because the + sostenuto pedal is down). + */ + bool isKeyDown() const noexcept { return keyIsDown; } + + /** Returns true if the sostenuto pedal is currently active for this voice. */ + bool isSostenutoPedalDown() const noexcept { return sostenutoPedalDown; } protected: //============================================================================== @@ -218,8 +230,7 @@ private: int currentlyPlayingNote; uint32 noteOnTime; SynthesiserSound::Ptr currentlyPlayingSound; - bool keyIsDown; // the voice may still be playing when the key is not down (i.e. sustain pedal) - bool sostenutoPedalDown; + bool keyIsDown, sostenutoPedalDown; JUCE_LEAK_DETECTOR (SynthesiserVoice) }; @@ -400,6 +411,21 @@ public: int controllerNumber, int controllerValue); + /** Sends an aftertouch message. + + This will send an aftertouch message to any voices that are playing sounds on + the given midi channel and note number. + + This method will be called automatically according to the midi data passed into + renderNextBlock(), but may be called explicitly too. + + @param midiChannel the midi channel, from 1 to 16 inclusive + @param midiNoteNumber the midi note number, 0 to 127 + @param aftertouchValue the aftertouch value, between 0 and 127, + as returned by MidiMessage::getAftertouchValue() + */ + virtual void handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue); + /** Handles a sustain pedal event. */ virtual void handleSustainPedal (int midiChannel, bool isDown); diff --git a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp index 9d2041d..aeff3c6 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -361,8 +361,8 @@ void AudioDeviceManager::getAudioDeviceSetup (AudioDeviceSetup& setup) void AudioDeviceManager::deleteCurrentDevice() { currentAudioDevice = nullptr; - currentSetup.inputDeviceName = String::empty; - currentSetup.outputDeviceName = String::empty; + currentSetup.inputDeviceName.clear(); + currentSetup.outputDeviceName.clear(); } void AudioDeviceManager::setCurrentAudioDeviceType (const String& type, @@ -408,15 +408,15 @@ String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup jassert (&newSetup != ¤tSetup); // this will have no effect if (newSetup == currentSetup && currentAudioDevice != nullptr) - return String::empty; + return String(); if (! (newSetup == currentSetup)) sendChangeMessage(); stopDevice(); - const String newInputDeviceName (numInputChansNeeded == 0 ? String::empty : newSetup.inputDeviceName); - const String newOutputDeviceName (numOutputChansNeeded == 0 ? String::empty : newSetup.outputDeviceName); + const String newInputDeviceName (numInputChansNeeded == 0 ? String() : newSetup.inputDeviceName); + const String newOutputDeviceName (numOutputChansNeeded == 0 ? String() : newSetup.outputDeviceName); String error; AudioIODeviceType* type = getCurrentDeviceTypeObject(); @@ -428,7 +428,7 @@ String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup if (treatAsChosenDevice) updateXml(); - return String::empty; + return String(); } if (currentSetup.inputDeviceName != newInputDeviceName @@ -524,16 +524,16 @@ double AudioDeviceManager::chooseBestSampleRate (double rate) const { jassert (currentAudioDevice != nullptr); - if (rate > 0) - for (int i = currentAudioDevice->getNumSampleRates(); --i >= 0;) - if (currentAudioDevice->getSampleRate (i) == rate) - return rate; + const Array rates (currentAudioDevice->getAvailableSampleRates()); + + if (rate > 0 && rates.contains (rate)) + return rate; double lowestAbove44 = 0.0; - for (int i = currentAudioDevice->getNumSampleRates(); --i >= 0;) + for (int i = rates.size(); --i >= 0;) { - const double sr = currentAudioDevice->getSampleRate (i); + const double sr = rates[i]; if (sr >= 44100.0 && (lowestAbove44 < 1.0 || sr < lowestAbove44)) lowestAbove44 = sr; @@ -542,17 +542,15 @@ double AudioDeviceManager::chooseBestSampleRate (double rate) const if (lowestAbove44 > 0.0) return lowestAbove44; - return currentAudioDevice->getSampleRate (0); + return rates[0]; } int AudioDeviceManager::chooseBestBufferSize (int bufferSize) const { jassert (currentAudioDevice != nullptr); - if (bufferSize > 0) - for (int i = currentAudioDevice->getNumBufferSizesAvailable(); --i >= 0;) - if (currentAudioDevice->getBufferSizeSamples(i) == bufferSize) - return bufferSize; + if (bufferSize > 0 && currentAudioDevice->getAvailableBufferSizes().contains (bufferSize)) + return bufferSize; return currentAudioDevice->getDefaultBufferSize(); } diff --git a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h index 14432bd..d821593 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h +++ b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h @@ -190,7 +190,7 @@ public: int numOutputChannelsNeeded, const XmlElement* savedState, bool selectDefaultDeviceOnFailure, - const String& preferredDefaultDeviceName = String::empty, + const String& preferredDefaultDeviceName = String(), const AudioDeviceSetup* preferredSetupOptions = 0); /** Returns some XML representing the current state of the manager. diff --git a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h index 7028072..6817ae6 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h +++ b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h @@ -157,47 +157,19 @@ public: virtual StringArray getInputChannelNames() = 0; //============================================================================== - /** Returns the number of sample-rates this device supports. - - To find out which rates are available on this device, use this method to - find out how many there are, and getSampleRate() to get the rates. - - @see getSampleRate - */ - virtual int getNumSampleRates() = 0; - - /** Returns one of the sample-rates this device supports. - - To find out which rates are available on this device, use getNumSampleRates() to - find out how many there are, and getSampleRate() to get the individual rates. - - The sample rate is set by the open() method. - - (Note that for DirectSound some rates might not work, depending on combinations - of i/o channels that are being opened). - - @see getNumSampleRates + /** Returns the set of sample-rates this device supports. + @see getCurrentSampleRate */ - virtual double getSampleRate (int index) = 0; - - /** Returns the number of sizes of buffer that are available. + virtual Array getAvailableSampleRates() = 0; - @see getBufferSizeSamples, getDefaultBufferSize + /** Returns the set of buffer sizes that are available. + @see getCurrentBufferSizeSamples, getDefaultBufferSize */ - virtual int getNumBufferSizesAvailable() = 0; - - /** Returns one of the possible buffer-sizes. - - @param index the index of the buffer-size to use, from 0 to getNumBufferSizesAvailable() - 1 - @returns a number of samples - @see getNumBufferSizesAvailable, getDefaultBufferSize - */ - virtual int getBufferSizeSamples (int index) = 0; + virtual Array getAvailableBufferSizes() = 0; /** Returns the default buffer-size to use. - @returns a number of samples - @see getNumBufferSizesAvailable, getBufferSizeSamples + @see getAvailableBufferSizes */ virtual int getDefaultBufferSize() = 0; @@ -209,9 +181,9 @@ public: @param outputChannels a BigInteger in which a set bit indicates that the corresponding output channel should be enabled @param sampleRate the sample rate to try to use - to find out which rates are - available, see getNumSampleRates() and getSampleRate() + available, see getAvailableSampleRates() @param bufferSizeSamples the size of i/o buffer to use - to find out the available buffer - sizes, see getNumBufferSizesAvailable() and getBufferSizeSamples() + sizes, see getAvailableBufferSizes() @returns an error description if there's a problem, or an empty string if it succeeds in opening the device @see close diff --git a/JuceLibraryCode/modules/juce_audio_devices/juce_module_info b/JuceLibraryCode/modules/juce_audio_devices/juce_module_info index bb5e193..5afbb4e 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/juce_module_info +++ b/JuceLibraryCode/modules/juce_audio_devices/juce_module_info @@ -1,7 +1,7 @@ { "id": "juce_audio_devices", "name": "JUCE audio and midi I/O device classes", - "version": "3.0.0", + "version": "3.0.1", "description": "Classes to play and record from audio and midi i/o devices.", "website": "http://www.juce.com/juce", "license": "GPL/Commercial", diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Audio.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Audio.cpp index e21f946..2e3cd7e 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Audio.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Audio.cpp @@ -105,7 +105,7 @@ public: close(); } - StringArray getOutputChannelNames() + StringArray getOutputChannelNames() override { StringArray s; s.add ("Left"); @@ -113,7 +113,7 @@ public: return s; } - StringArray getInputChannelNames() + StringArray getInputChannelNames() override { StringArray s; @@ -130,36 +130,43 @@ public: return s; } - int getNumSampleRates() { return 1;} - double getSampleRate (int index) { return sampleRate; } - - int getDefaultBufferSize() { return 2048; } - int getNumBufferSizesAvailable() { return 50; } + Array getAvailableSampleRates() override + { + Array r; + r.add ((double) sampleRate); + return r; + } - int getBufferSizeSamples (int index) + Array getAvailableBufferSizes() override { + Array b; int n = 16; - for (int i = 0; i < index; ++i) + + for (int i = 0; i < 50; ++i) + { + b.add (n); n += n < 64 ? 16 : (n < 512 ? 32 : (n < 1024 ? 64 : (n < 2048 ? 128 : 256))); + } - return n; + return b; } + int getDefaultBufferSize() override { return 2048; } String open (const BigInteger& inputChannels, const BigInteger& outputChannels, double requestedSampleRate, - int bufferSize) + int bufferSize) override { close(); if (sampleRate != (int) requestedSampleRate) return "Sample rate not allowed"; - lastError = String::empty; + lastError.clear(); int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; numDeviceInputChannels = 0; @@ -227,7 +234,7 @@ public: return lastError; } - void close() + void close() override { if (isRunning) { @@ -237,18 +244,18 @@ public: } } - int getOutputLatencyInSamples() { return (minBufferSizeOut * 3) / 4; } - int getInputLatencyInSamples() { return (minBufferSizeIn * 3) / 4; } - bool isOpen() { return isRunning; } - int getCurrentBufferSizeSamples() { return actualBufferSize; } - int getCurrentBitDepth() { return 16; } - double getCurrentSampleRate() { return sampleRate; } - BigInteger getActiveOutputChannels() const { return activeOutputChans; } - BigInteger getActiveInputChannels() const { return activeInputChans; } - String getLastError() { return lastError; } - bool isPlaying() { return isRunning && callback != 0; } - - void start (AudioIODeviceCallback* newCallback) + int getOutputLatencyInSamples() override { return (minBufferSizeOut * 3) / 4; } + int getInputLatencyInSamples() override { return (minBufferSizeIn * 3) / 4; } + bool isOpen() override { return isRunning; } + int getCurrentBufferSizeSamples() override { return actualBufferSize; } + int getCurrentBitDepth() override { return 16; } + double getCurrentSampleRate() override { return sampleRate; } + BigInteger getActiveOutputChannels() const override { return activeOutputChans; } + BigInteger getActiveInputChannels() const override { return activeInputChans; } + String getLastError() override { return lastError; } + bool isPlaying() override { return isRunning && callback != 0; } + + void start (AudioIODeviceCallback* newCallback) override { if (isRunning && callback != newCallback) { @@ -260,7 +267,7 @@ public: } } - void stop() + void stop() override { if (isRunning) { diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_OpenSL.cpp index d15629b..eb60dcc 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_OpenSL.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_OpenSL.cpp @@ -30,9 +30,6 @@ bool isOpenSLAvailable() return library.open ("libOpenSLES.so"); } -const unsigned short openSLRates[] = { 8000, 16000, 32000, 44100, 48000 }; -const unsigned short openSLBufferSizes[] = { 256, 512, 768, 1024, 1280, 1600 }; // must all be multiples of the block size - //============================================================================== class OpenSLAudioIODevice : public AudioIODevice, public Thread @@ -66,7 +63,7 @@ public: bool openedOk() const { return engine.outputMixObject != nullptr; } - StringArray getOutputChannelNames() + StringArray getOutputChannelNames() override { StringArray s; s.add ("Left"); @@ -74,38 +71,33 @@ public: return s; } - StringArray getInputChannelNames() + StringArray getInputChannelNames() override { StringArray s; s.add ("Audio Input"); return s; } - int getNumSampleRates() { return numElementsInArray (openSLRates); } - - double getSampleRate (int index) + Array getAvailableSampleRates() override { - jassert (index >= 0 && index < getNumSampleRates()); - return (int) openSLRates [index]; + static const double rates[] = { 8000.0, 16000.0, 32000.0, 44100.0, 48000.0 }; + return Array (rates, numElementsInArray (rates)); } - int getDefaultBufferSize() { return 1024; } - int getNumBufferSizesAvailable() { return numElementsInArray (openSLBufferSizes); } - - int getBufferSizeSamples (int index) + Array getAvailableBufferSizes() override { - jassert (index >= 0 && index < getNumBufferSizesAvailable()); - return (int) openSLBufferSizes [index]; + static const int sizes[] = { 256, 512, 768, 1024, 1280, 1600 }; // must all be multiples of the block size + return Array (sizes, numElementsInArray (sizes)); } String open (const BigInteger& inputChannels, const BigInteger& outputChannels, double requestedSampleRate, - int bufferSize) + int bufferSize) override { close(); - lastError = String::empty; + lastError.clear(); sampleRate = (int) requestedSampleRate; int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; @@ -133,7 +125,7 @@ public: return lastError; } - void close() + void close() override { stop(); stopThread (6000); @@ -142,18 +134,19 @@ public: player = nullptr; } - int getOutputLatencyInSamples() { return outputLatency; } - int getInputLatencyInSamples() { return inputLatency; } - bool isOpen() { return deviceOpen; } - int getCurrentBufferSizeSamples() { return actualBufferSize; } - int getCurrentBitDepth() { return 16; } - double getCurrentSampleRate() { return sampleRate; } - BigInteger getActiveOutputChannels() const { return activeOutputChans; } - BigInteger getActiveInputChannels() const { return activeInputChans; } - String getLastError() { return lastError; } - bool isPlaying() { return callback != nullptr; } - - void start (AudioIODeviceCallback* newCallback) + int getDefaultBufferSize() override { return 1024; } + int getOutputLatencyInSamples() override { return outputLatency; } + int getInputLatencyInSamples() override { return inputLatency; } + bool isOpen() override { return deviceOpen; } + int getCurrentBufferSizeSamples() override { return actualBufferSize; } + int getCurrentBitDepth() override { return 16; } + double getCurrentSampleRate() override { return sampleRate; } + BigInteger getActiveOutputChannels() const override { return activeOutputChans; } + BigInteger getActiveInputChannels() const override { return activeInputChans; } + String getLastError() override { return lastError; } + bool isPlaying() override { return callback != nullptr; } + + void start (AudioIODeviceCallback* newCallback) override { stop(); @@ -166,7 +159,7 @@ public: } } - void stop() + void stop() override { if (AudioIODeviceCallback* const oldCallback = setCallback (nullptr)) oldCallback->audioDeviceStopped(); diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_ios_Audio.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_ios_Audio.cpp index adbb1dc..a5a1b31 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_ios_Audio.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_ios_Audio.cpp @@ -48,7 +48,7 @@ public: close(); } - StringArray getOutputChannelNames() + StringArray getOutputChannelNames() override { StringArray s; s.add ("Left"); @@ -56,7 +56,7 @@ public: return s; } - StringArray getInputChannelNames() + StringArray getInputChannelNames() override { StringArray s; if (audioInputIsAvailable) @@ -67,20 +67,27 @@ public: return s; } - int getNumSampleRates() { return jmax (1, sampleRates.size()); } - double getSampleRate (int index) { return sampleRates.size() > 0 ? sampleRates [index] : sampleRate; } + Array getAvailableSampleRates() override { return sampleRates; } - int getNumBufferSizesAvailable() { return 6; } - int getBufferSizeSamples (int index) { return 1 << (jlimit (0, 5, index) + 6); } - int getDefaultBufferSize() { return 1024; } + Array getAvailableBufferSizes() override + { + Array r; + + for (int i = 6; i < 12; ++i) + r.add (1 << i); + + return r; + } + + int getDefaultBufferSize() override { return 1024; } String open (const BigInteger& inputChannelsWanted, const BigInteger& outputChannelsWanted, - double targetSampleRate, int bufferSize) + double targetSampleRate, int bufferSize) override { close(); - lastError = String::empty; + lastError.clear(); preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; // xxx set up channel mapping @@ -127,7 +134,7 @@ public: return lastError; } - void close() + void close() override { if (isRunning) { @@ -146,19 +153,19 @@ public: } } - bool isOpen() { return isRunning; } + bool isOpen() override { return isRunning; } - int getCurrentBufferSizeSamples() { return actualBufferSize; } - double getCurrentSampleRate() { return sampleRate; } - int getCurrentBitDepth() { return 16; } + int getCurrentBufferSizeSamples() override { return actualBufferSize; } + double getCurrentSampleRate() override { return sampleRate; } + int getCurrentBitDepth() override { return 16; } - BigInteger getActiveOutputChannels() const { return activeOutputChans; } - BigInteger getActiveInputChannels() const { return activeInputChans; } + BigInteger getActiveOutputChannels() const override { return activeOutputChans; } + BigInteger getActiveInputChannels() const override { return activeInputChans; } - int getOutputLatencyInSamples() { return 0; } //xxx - int getInputLatencyInSamples() { return 0; } //xxx + int getOutputLatencyInSamples() override { return 0; } //xxx + int getInputLatencyInSamples() override { return 0; } //xxx - void start (AudioIODeviceCallback* newCallback) + void start (AudioIODeviceCallback* newCallback) override { if (isRunning && callback != newCallback) { @@ -170,7 +177,7 @@ public: } } - void stop() + void stop() override { if (isRunning) { @@ -187,8 +194,8 @@ public: } } - bool isPlaying() { return isRunning && callback != nullptr; } - String getLastError() { return lastError; } + bool isPlaying() override { return isRunning && callback != nullptr; } + String getLastError() override { return lastError; } private: //================================================================================================== @@ -436,6 +443,11 @@ private: isRunning = true; AudioSessionSetActive (true); AudioOutputUnitStart (audioUnit); + + const ScopedLock sl (callbackLock); + + if (callback != nullptr) + callback->audioDeviceError ("iOS audio session resumed"); } } diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_ALSA.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_ALSA.cpp index 7d47f05..9bc9a26 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_ALSA.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_ALSA.cpp @@ -47,7 +47,7 @@ namespace #define JUCE_ALSA_FAILED(x) failed (x) -static void getDeviceSampleRates (snd_pcm_t* handle, Array & rates) +static void getDeviceSampleRates (snd_pcm_t* handle, Array& rates) { const int ratesToTry[] = { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 0 }; @@ -59,7 +59,7 @@ static void getDeviceSampleRates (snd_pcm_t* handle, Array & rates) if (snd_pcm_hw_params_any (handle, hwParams) >= 0 && snd_pcm_hw_params_test_rate (handle, hwParams, ratesToTry[i], 0) == 0) { - rates.addIfNotAlreadyThere (ratesToTry[i]); + rates.addIfNotAlreadyThere ((double) ratesToTry[i]); } } } @@ -91,7 +91,7 @@ static void getDeviceProperties (const String& deviceID, unsigned int& maxChansOut, unsigned int& minChansIn, unsigned int& maxChansIn, - Array & rates, + Array& rates, bool testOutput, bool testInput) { @@ -482,7 +482,7 @@ public: { close(); - error = String::empty; + error.clear(); sampleRate = newSampleRate; bufferSize = newBufferSize; @@ -722,7 +722,7 @@ public: int bufferSize, outputLatency, inputLatency; BigInteger currentInputChans, currentOutputChans; - Array sampleRates; + Array sampleRates; StringArray channelNamesOut, channelNamesIn; AudioIODeviceCallback* callback; @@ -798,31 +798,34 @@ public: close(); } - StringArray getOutputChannelNames() { return internal.channelNamesOut; } - StringArray getInputChannelNames() { return internal.channelNamesIn; } + StringArray getOutputChannelNames() override { return internal.channelNamesOut; } + StringArray getInputChannelNames() override { return internal.channelNamesIn; } - int getNumSampleRates() { return internal.sampleRates.size(); } - double getSampleRate (int index) { return internal.sampleRates [index]; } + Array getAvailableSampleRates() override { return internal.sampleRates; } - int getDefaultBufferSize() { return 512; } - int getNumBufferSizesAvailable() { return 50; } - - int getBufferSizeSamples (int index) + Array getAvailableBufferSizes() override { + Array r; int n = 16; - for (int i = 0; i < index; ++i) + + for (int i = 0; i < 50; ++i) + { + r.add (n); n += n < 64 ? 16 : (n < 512 ? 32 : (n < 1024 ? 64 : (n < 2048 ? 128 : 256))); + } - return n; + return r; } + int getDefaultBufferSize() override { return 512; } + String open (const BigInteger& inputChannels, const BigInteger& outputChannels, double sampleRate, - int bufferSizeSamples) + int bufferSizeSamples) override { close(); @@ -831,11 +834,13 @@ public: if (sampleRate <= 0) { - for (int i = 0; i < getNumSampleRates(); ++i) + for (int i = 0; i < internal.sampleRates.size(); ++i) { - if (getSampleRate (i) >= 44100) + double rate = internal.sampleRates[i]; + + if (rate >= 44100) { - sampleRate = getSampleRate (i); + sampleRate = rate; break; } } @@ -848,28 +853,28 @@ public: return internal.error; } - void close() + void close() override { stop(); internal.close(); isOpen_ = false; } - bool isOpen() { return isOpen_; } - bool isPlaying() { return isStarted && internal.error.isEmpty(); } - String getLastError() { return internal.error; } + bool isOpen() override { return isOpen_; } + bool isPlaying() override { return isStarted && internal.error.isEmpty(); } + String getLastError() override { return internal.error; } - int getCurrentBufferSizeSamples() { return internal.bufferSize; } - double getCurrentSampleRate() { return internal.sampleRate; } - int getCurrentBitDepth() { return internal.getBitDepth(); } + int getCurrentBufferSizeSamples() override { return internal.bufferSize; } + double getCurrentSampleRate() override { return internal.sampleRate; } + int getCurrentBitDepth() override { return internal.getBitDepth(); } - BigInteger getActiveOutputChannels() const { return internal.currentOutputChans; } - BigInteger getActiveInputChannels() const { return internal.currentInputChans; } + BigInteger getActiveOutputChannels() const override { return internal.currentOutputChans; } + BigInteger getActiveInputChannels() const override { return internal.currentInputChans; } - int getOutputLatencyInSamples() { return internal.outputLatency; } - int getInputLatencyInSamples() { return internal.inputLatency; } + int getOutputLatencyInSamples() override { return internal.outputLatency; } + int getInputLatencyInSamples() override { return internal.inputLatency; } - void start (AudioIODeviceCallback* callback) + void start (AudioIODeviceCallback* callback) override { if (! isOpen_) callback = nullptr; @@ -882,7 +887,7 @@ public: isStarted = (callback != nullptr); } - void stop() + void stop() override { AudioIODeviceCallback* const oldCallback = internal.callback; @@ -1002,7 +1007,7 @@ private: { unsigned int minChansOut = 0, maxChansOut = 0; unsigned int minChansIn = 0, maxChansIn = 0; - Array rates; + Array rates; bool isInput = inputName.isNotEmpty(), isOutput = outputName.isNotEmpty(); getDeviceProperties (id, minChansOut, maxChansOut, minChansIn, maxChansIn, rates, isOutput, isInput); diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp index 08962a0..2e57d10 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp @@ -218,16 +218,36 @@ public: return names; } - StringArray getOutputChannelNames() { return getChannelNames (false); } - StringArray getInputChannelNames() { return getChannelNames (true); } - int getNumSampleRates() { return client != nullptr ? 1 : 0; } - double getSampleRate (int /*index*/) { return client != nullptr ? juce::jack_get_sample_rate (client) : 0; } - int getNumBufferSizesAvailable() { return client != nullptr ? 1 : 0; } - int getBufferSizeSamples (int /*index*/) { return getDefaultBufferSize(); } - int getDefaultBufferSize() { return client != nullptr ? juce::jack_get_buffer_size (client) : 0; } + StringArray getOutputChannelNames() override { return getChannelNames (false); } + StringArray getInputChannelNames() override { return getChannelNames (true); } + + Array getAvailableSampleRates() override + { + Array rates; + + if (client != nullptr) + rates.add (juce::jack_get_sample_rate (client)); + + return rates; + } + + Array getAvailableBufferSizes() override + { + Array sizes; + + if (client != nullptr) + sizes.add (juce::jack_get_buffer_size (client)); + + return sizes; + } + + int getDefaultBufferSize() override { return getCurrentBufferSizeSamples(); } + int getCurrentBufferSizeSamples() override { return client != nullptr ? juce::jack_get_buffer_size (client) : 0; } + double getCurrentSampleRate() override { return client != nullptr ? juce::jack_get_sample_rate (client) : 0; } + String open (const BigInteger& inputChannels, const BigInteger& outputChannels, - double /* sampleRate */, int /* bufferSizeSamples */) + double /* sampleRate */, int /* bufferSizeSamples */) override { if (client == nullptr) { @@ -235,7 +255,7 @@ public: return lastError; } - lastError = String::empty; + lastError.clear(); close(); juce::jack_set_process_callback (client, processCallback, this); @@ -273,7 +293,7 @@ public: return lastError; } - void close() + void close() override { stop(); @@ -288,7 +308,7 @@ public: deviceIsOpen = false; } - void start (AudioIODeviceCallback* newCallback) + void start (AudioIODeviceCallback* newCallback) override { if (deviceIsOpen && newCallback != callback) { @@ -307,22 +327,20 @@ public: } } - void stop() + void stop() override { start (nullptr); } - bool isOpen() { return deviceIsOpen; } - bool isPlaying() { return callback != nullptr; } - int getCurrentBufferSizeSamples() { return getBufferSizeSamples (0); } - double getCurrentSampleRate() { return getSampleRate (0); } - int getCurrentBitDepth() { return 32; } - String getLastError() { return lastError; } + bool isOpen() override { return deviceIsOpen; } + bool isPlaying() override { return callback != nullptr; } + int getCurrentBitDepth() override { return 32; } + String getLastError() override { return lastError; } - BigInteger getActiveOutputChannels() const { return activeOutputChannels; } - BigInteger getActiveInputChannels() const { return activeInputChannels; } + BigInteger getActiveOutputChannels() const override { return activeOutputChannels; } + BigInteger getActiveInputChannels() const override { return activeInputChannels; } - int getOutputLatencyInSamples() + int getOutputLatencyInSamples() override { int latency = 0; @@ -332,7 +350,7 @@ public: return latency; } - int getInputLatencyInSamples() + int getInputLatencyInSamples() override { int latency = 0; diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp index 60b82b7..799207c 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp @@ -23,7 +23,7 @@ */ #if JUCE_COREAUDIO_LOGGING_ENABLED - #define JUCE_COREAUDIOLOG(a) Logger::writeToLog (a) + #define JUCE_COREAUDIOLOG(a) { String camsg ("CoreAudio: "); camsg << a; Logger::writeToLog (camsg); } #else #define JUCE_COREAUDIOLOG(a) #endif @@ -140,7 +140,7 @@ class CoreAudioIODevice; class CoreAudioInternal : private Timer { public: - CoreAudioInternal (CoreAudioIODevice& d, AudioDeviceID id, bool isSlave) + CoreAudioInternal (CoreAudioIODevice& d, AudioDeviceID id) : owner (d), inputLatency (0), outputLatency (0), @@ -148,7 +148,6 @@ public: #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 audioProcID (0), #endif - isSlaveDevice (isSlave), deviceID (id), started (false), sampleRate (0), @@ -216,12 +215,12 @@ public: pa.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; pa.mElement = kAudioObjectPropertyElementMaster; - if (OK (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, 0, &size))) + if (OK (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, nullptr, &size))) { HeapBlock bufList; bufList.calloc (size, 1); - if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, bufList))) + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, bufList))) { const int numStreams = (int) bufList->mNumberBuffers; @@ -232,17 +231,27 @@ public: for (unsigned int j = 0; j < b.mNumberChannels; ++j) { String name; + NSString* nameNSString = nil; + size = sizeof (nameNSString); + + #if JUCE_CLANG + // Very irritating that AudioDeviceGetProperty is marked as deprecated, since + // there seems to be no replacement way of getting the channel names. + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + #endif + if (AudioDeviceGetProperty (deviceID, chanNum + 1, input, kAudioDevicePropertyChannelNameCFString, + &size, &nameNSString) == noErr) { - char channelName [256] = { 0 }; - UInt32 nameSize = sizeof (channelName); - UInt32 channelNum = (UInt32) chanNum + 1; - pa.mSelector = kAudioDevicePropertyChannelName; - - if (AudioObjectGetPropertyData (deviceID, &pa, sizeof (channelNum), &channelNum, &nameSize, channelName) == noErr) - name = String::fromUTF8 (channelName, (int) nameSize); + name = nsStringToJuce (nameNSString); + [nameNSString release]; } + #if JUCE_CLANG + #pragma clang diagnostic pop + #endif + if ((input ? activeInputChans : activeOutputChans) [chanNum]) { CallbackDetailsForChannel info = { i, (int) j, (int) b.mNumberChannels }; @@ -265,7 +274,6 @@ public: Array getSampleRatesFromDevice() const { Array newSampleRates; - String rates; AudioObjectPropertyAddress pa; pa.mScope = kAudioObjectPropertyScopeWildcard; @@ -273,12 +281,12 @@ public: pa.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; UInt32 size = 0; - if (OK (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, 0, &size))) + if (OK (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, nullptr, &size))) { HeapBlock ranges; ranges.calloc (size, 1); - if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, ranges))) + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, ranges))) { static const double possibleRates[] = { 44100.0, 48000.0, 88200.0, 96000.0, 176400.0, 192000.0 }; @@ -289,7 +297,6 @@ public: if (possibleRates[i] >= ranges[j].mMinimum - 2 && possibleRates[i] <= ranges[j].mMaximum + 2) { newSampleRates.add (possibleRates[i]); - rates << possibleRates[i] << ' '; break; } } @@ -298,12 +305,8 @@ public: } if (newSampleRates.size() == 0 && sampleRate > 0) - { newSampleRates.add (sampleRate); - rates << sampleRate; - } - JUCE_COREAUDIOLOG ("rates: " + rates); return newSampleRates; } @@ -317,12 +320,12 @@ public: pa.mSelector = kAudioDevicePropertyBufferFrameSizeRange; UInt32 size = 0; - if (OK (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, 0, &size))) + if (OK (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, nullptr, &size))) { HeapBlock ranges; ranges.calloc (size, 1); - if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, ranges))) + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, ranges))) { newBufferSizes.add ((int) (ranges[0].mMinimum + 15) & ~15); @@ -357,7 +360,7 @@ public: pa.mElement = kAudioObjectPropertyElementMaster; pa.mSelector = kAudioDevicePropertyLatency; pa.mScope = scope; - AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, &lat); + AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &lat); return (int) lat; } @@ -377,26 +380,25 @@ public: UInt32 isAlive; UInt32 size = sizeof (isAlive); pa.mSelector = kAudioDevicePropertyDeviceIsAlive; - if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, &isAlive)) && isAlive == 0) + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &isAlive)) && isAlive == 0) return; Float64 sr; size = sizeof (sr); pa.mSelector = kAudioDevicePropertyNominalSampleRate; - if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, &sr))) + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &sr))) sampleRate = sr; UInt32 framesPerBuf = bufferSize; size = sizeof (framesPerBuf); pa.mSelector = kAudioDevicePropertyBufferFrameSize; - AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, &framesPerBuf); + AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &framesPerBuf); Array newBufferSizes (getBufferSizesFromDevice()); Array newSampleRates (getSampleRatesFromDevice()); inputLatency = getLatencyFromDevice (kAudioDevicePropertyScopeInput); outputLatency = getLatencyFromDevice (kAudioDevicePropertyScopeOutput); - JUCE_COREAUDIOLOG ("lat: " + String (inputLatency) + " " + String (outputLatency)); Array newInChans, newOutChans; StringArray newInNames (getChannelInfo (true, newInChans)); @@ -442,11 +444,8 @@ public: pa.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; pa.mElement = kAudioObjectPropertyElementMaster; - if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &transSize, &avt))) - { - DBG (buffer); + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &transSize, &avt))) s.add (buffer); - } } return s; @@ -465,7 +464,7 @@ public: if (deviceID != 0) { - if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, ¤tSourceID))) + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, ¤tSourceID))) { HeapBlock types; const int num = getAllDataSourcesForDevice (deviceID, types); @@ -508,11 +507,9 @@ public: //============================================================================== String reopen (const BigInteger& inputChannels, const BigInteger& outputChannels, - double newSampleRate, - int bufferSizeSamples) + double newSampleRate, int bufferSizeSamples) { String error; - JUCE_COREAUDIOLOG ("CoreAudio reopen"); callbacksAllowed = false; stopTimer(); @@ -566,11 +563,6 @@ public: error = "Device has no available sample-rates"; else if (bufferSizes.size() == 0) error = "Device has no available buffer-sizes"; - else if (inputDevice != 0) - error = inputDevice->reopen (inputChannels, - outputChannels, - newSampleRate, - bufferSizeSamples); } } @@ -578,7 +570,7 @@ public: return error; } - bool start (AudioIODeviceCallback* cb) + bool start() { if (! started) { @@ -609,13 +601,13 @@ public: } } - if (started) - { - const ScopedLock sl (callbackLock); - callback = cb; - } + return started; + } - return started && (inputDevice == nullptr || inputDevice->start (cb)); + void setCallback (AudioIODeviceCallback* cb) + { + const ScopedLock sl (callbackLock); + callback = cb; } void stop (bool leaveInterruptRunning) @@ -655,7 +647,7 @@ public: pa.mScope = kAudioObjectPropertyScopeWildcard; pa.mElement = kAudioObjectPropertyElementMaster; - OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, &running)); + OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &running)); if (running == 0) break; @@ -663,9 +655,6 @@ public: const ScopedLock sl (callbackLock); } - - if (inputDevice != nullptr) - inputDevice->stop (leaveInterruptRunning); } double getSampleRate() const { return sampleRate; } @@ -678,77 +667,34 @@ public: if (callback != nullptr) { - if (inputDevice == 0) + for (int i = numInputChans; --i >= 0;) { - for (int i = numInputChans; --i >= 0;) - { - const CallbackDetailsForChannel& info = inputChannelInfo.getReference(i); - float* dest = tempInputBuffers [i]; - const float* src = ((const float*) inInputData->mBuffers[info.streamNum].mData) - + info.dataOffsetSamples; - const int stride = info.dataStrideSamples; + const CallbackDetailsForChannel& info = inputChannelInfo.getReference(i); + float* dest = tempInputBuffers [i]; + const float* src = ((const float*) inInputData->mBuffers[info.streamNum].mData) + + info.dataOffsetSamples; + const int stride = info.dataStrideSamples; - if (stride != 0) // if this is zero, info is invalid + if (stride != 0) // if this is zero, info is invalid + { + for (int j = bufferSize; --j >= 0;) { - for (int j = bufferSize; --j >= 0;) - { - *dest++ = *src; - src += stride; - } + *dest++ = *src; + src += stride; } } } - if (! isSlaveDevice) - { - if (inputDevice == 0) - { - callback->audioDeviceIOCallback (const_cast (tempInputBuffers.getData()), - numInputChans, - tempOutputBuffers, - numOutputChans, - bufferSize); - } - else - { - jassert (inputDevice->bufferSize == bufferSize); - - // Sometimes the two linked devices seem to get their callbacks in - // parallel, so we need to lock both devices to stop the input data being - // changed while inside our callback.. - const ScopedLock sl2 (inputDevice->callbackLock); - - callback->audioDeviceIOCallback (const_cast (inputDevice->tempInputBuffers.getData()), - inputDevice->numInputChans, - tempOutputBuffers, - numOutputChans, - bufferSize); - } + callback->audioDeviceIOCallback (const_cast (tempInputBuffers.getData()), + numInputChans, + tempOutputBuffers, + numOutputChans, + bufferSize); - for (int i = numOutputChans; --i >= 0;) - { - const CallbackDetailsForChannel& info = outputChannelInfo.getReference(i); - const float* src = tempOutputBuffers [i]; - float* dest = ((float*) outOutputData->mBuffers[info.streamNum].mData) - + info.dataOffsetSamples; - const int stride = info.dataStrideSamples; - - if (stride != 0) // if this is zero, info is invalid - { - for (int j = bufferSize; --j >= 0;) - { - *dest = *src++; - dest += stride; - } - } - } - } - } - else - { - for (int i = jmin (numOutputChans, outputChannelInfo.size()); --i >= 0;) + for (int i = numOutputChans; --i >= 0;) { const CallbackDetailsForChannel& info = outputChannelInfo.getReference(i); + const float* src = tempOutputBuffers [i]; float* dest = ((float*) outOutputData->mBuffers[info.streamNum].mData) + info.dataOffsetSamples; const int stride = info.dataStrideSamples; @@ -757,12 +703,18 @@ public: { for (int j = bufferSize; --j >= 0;) { - *dest = 0.0f; + *dest = *src++; dest += stride; } } } } + else + { + for (UInt32 i = 0; i < outOutputData->mNumberBuffers; ++i) + zeromem (outOutputData->mBuffers[i].mData, + outOutputData->mBuffers[i].mDataByteSize); + } } // called by callbacks @@ -774,9 +726,9 @@ public: void timerCallback() override { - stopTimer(); - JUCE_COREAUDIOLOG ("CoreAudio device changed callback"); + JUCE_COREAUDIOLOG ("Device changed"); + stopTimer(); const double oldSampleRate = sampleRate; const int oldBufferSize = bufferSize; updateDetailsFromDevice(); @@ -790,28 +742,25 @@ public: int inputLatency, outputLatency; BigInteger activeInputChans, activeOutputChans; StringArray inChanNames, outChanNames; - Array sampleRates; - Array bufferSizes; + Array sampleRates; + Array bufferSizes; AudioIODeviceCallback* callback; #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 AudioDeviceIOProcID audioProcID; #endif - ScopedPointer inputDevice; - bool isSlaveDevice; - private: CriticalSection callbackLock; AudioDeviceID deviceID; bool started; double sampleRate; int bufferSize; - HeapBlock audioBuffer; + HeapBlock audioBuffer; int numInputChans, numOutputChans; bool callbacksAllowed; Array inputChannelInfo, outputChannelInfo; - HeapBlock tempInputBuffers, tempOutputBuffers; + HeapBlock tempInputBuffers, tempOutputBuffers; //============================================================================== static OSStatus audioIOProc (AudioDeviceID /*inDevice*/, @@ -822,13 +771,13 @@ private: const AudioTimeStamp* /*inOutputTime*/, void* device) { - static_cast (device)->audioCallback (inInputData, outOutputData); + static_cast (device)->audioCallback (inInputData, outOutputData); return noErr; } static OSStatus deviceListenerProc (AudioDeviceID /*inDevice*/, UInt32 /*inLine*/, const AudioObjectPropertyAddress* pa, void* inClientData) { - CoreAudioInternal* const intern = static_cast (inClientData); + CoreAudioInternal* const intern = static_cast (inClientData); switch (pa->mSelector) { @@ -854,7 +803,7 @@ private: } //============================================================================== - static int getAllDataSourcesForDevice (AudioDeviceID deviceID, HeapBlock & types) + static int getAllDataSourcesForDevice (AudioDeviceID deviceID, HeapBlock& types) { AudioObjectPropertyAddress pa; pa.mSelector = kAudioDevicePropertyDataSources; @@ -863,11 +812,11 @@ private: UInt32 size = 0; if (deviceID != 0 - && AudioObjectGetPropertyDataSize (deviceID, &pa, 0, 0, &size) == noErr) + && AudioObjectGetPropertyDataSize (deviceID, &pa, 0, nullptr, &size) == noErr) { types.calloc (size, 1); - if (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, types) == noErr) + if (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, types) == noErr) return size / (int) sizeof (OSType); } @@ -897,10 +846,8 @@ class CoreAudioIODevice : public AudioIODevice { public: CoreAudioIODevice (const String& deviceName, - AudioDeviceID inputDeviceId, - const int inputIndex_, - AudioDeviceID outputDeviceId, - const int outputIndex_) + AudioDeviceID inputDeviceId, const int inputIndex_, + AudioDeviceID outputDeviceId, const int outputIndex_) : AudioIODevice (deviceName, "CoreAudio"), inputIndex (inputIndex_), outputIndex (outputIndex_), @@ -912,15 +859,11 @@ public: if (outputDeviceId == 0 || outputDeviceId == inputDeviceId) { jassert (inputDeviceId != 0); - - device = new CoreAudioInternal (*this, inputDeviceId, false); + device = new CoreAudioInternal (*this, inputDeviceId); } else { - device = new CoreAudioInternal (*this, outputDeviceId, false); - - if (inputDeviceId != 0) - device->inputDevice = new CoreAudioInternal (*this, inputDeviceId, true); + device = new CoreAudioInternal (*this, outputDeviceId); } internal = device; @@ -928,8 +871,8 @@ public: AudioObjectPropertyAddress pa; pa.mSelector = kAudioObjectPropertySelectorWildcard; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementWildcard; + pa.mScope = kAudioObjectPropertyScopeWildcard; + pa.mElement = kAudioObjectPropertyElementWildcard; AudioObjectAddPropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, internal); } @@ -946,37 +889,24 @@ public: AudioObjectRemovePropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, internal); } - StringArray getOutputChannelNames() - { - return internal->outChanNames; - } - - StringArray getInputChannelNames() - { - if (internal->inputDevice != 0) - return internal->inputDevice->inChanNames; - - return internal->inChanNames; - } - - bool isOpen() { return isOpen_; } + StringArray getOutputChannelNames() override { return internal->outChanNames; } + StringArray getInputChannelNames() override { return internal->inChanNames; } - int getNumSampleRates() { return internal->sampleRates.size(); } - double getSampleRate (int index) { return internal->sampleRates [index]; } - double getCurrentSampleRate() { return internal->getSampleRate(); } + bool isOpen() override { return isOpen_; } - int getCurrentBitDepth() { return 32; } // no way to find out, so just assume it's high.. + Array getAvailableSampleRates() override { return internal->sampleRates; } + Array getAvailableBufferSizes() override { return internal->bufferSizes; } - int getNumBufferSizesAvailable() { return internal->bufferSizes.size(); } - int getBufferSizeSamples (int index) { return internal->bufferSizes [index]; } - int getCurrentBufferSizeSamples() { return internal->getBufferSize(); } + double getCurrentSampleRate() override { return internal->getSampleRate(); } + int getCurrentBitDepth() override { return 32; } // no way to find out, so just assume it's high.. + int getCurrentBufferSizeSamples() override { return internal->getBufferSize(); } - int getDefaultBufferSize() + int getDefaultBufferSize() override { int best = 0; - for (int i = 0; best < 512 && i < getNumBufferSizesAvailable(); ++i) - best = getBufferSizeSamples(i); + for (int i = 0; best < 512 && i < internal->bufferSizes.size(); ++i) + best = internal->bufferSizes.getUnchecked(i); if (best == 0) best = 512; @@ -986,8 +916,7 @@ public: String open (const BigInteger& inputChannels, const BigInteger& outputChannels, - double sampleRate, - int bufferSizeSamples) + double sampleRate, int bufferSizeSamples) override { isOpen_ = true; @@ -995,11 +924,15 @@ public: bufferSizeSamples = getDefaultBufferSize(); lastError = internal->reopen (inputChannels, outputChannels, sampleRate, bufferSizeSamples); + + JUCE_COREAUDIOLOG ("Opened: " << getName()); + JUCE_COREAUDIOLOG ("Latencies: " << getInputLatencyInSamples() << ' ' << getOutputLatencyInSamples()); + isOpen_ = lastError.isEmpty(); return lastError; } - void close() + void close() override { isOpen_ = false; internal->stop (false); @@ -1007,54 +940,43 @@ public: void restart() { + JUCE_COREAUDIOLOG ("Restarting"); AudioIODeviceCallback* oldCallback = internal->callback; stop(); - - if (oldCallback != nullptr) - start (oldCallback); - } - - BigInteger getActiveOutputChannels() const - { - return internal->activeOutputChans; + start (oldCallback); } - BigInteger getActiveInputChannels() const - { - BigInteger chans (internal->activeInputChans); - - if (internal->inputDevice != nullptr) - chans |= internal->inputDevice->activeInputChans; + BigInteger getActiveOutputChannels() const override { return internal->activeOutputChans; } + BigInteger getActiveInputChannels() const override { return internal->activeInputChans; } - return chans; - } - - int getOutputLatencyInSamples() + int getOutputLatencyInSamples() override { // this seems like a good guess at getting the latency right - comparing // this with a round-trip measurement, it gets it to within a few millisecs // for the built-in mac soundcard - return internal->outputLatency + internal->getBufferSize() * 2; + return internal->outputLatency; } - int getInputLatencyInSamples() + int getInputLatencyInSamples() override { - return internal->inputLatency + internal->getBufferSize() * 2; + return internal->inputLatency; } - void start (AudioIODeviceCallback* callback) + void start (AudioIODeviceCallback* callback) override { if (! isStarted) { if (callback != nullptr) callback->audioDeviceAboutToStart (this); - isStarted = true; - internal->start (callback); + isStarted = internal->start(); + + if (isStarted) + internal->setCallback (callback); } } - void stop() + void stop() override { if (isStarted) { @@ -1068,7 +990,7 @@ public: } } - bool isPlaying() + bool isPlaying() override { if (internal->callback == nullptr) isStarted = false; @@ -1076,7 +998,7 @@ public: return isStarted; } - String getLastError() + String getLastError() override { return lastError; } @@ -1090,12 +1012,10 @@ private: static OSStatus hardwareListenerProc (AudioDeviceID /*inDevice*/, UInt32 /*inLine*/, const AudioObjectPropertyAddress* pa, void* inClientData) { - CoreAudioInternal* const intern = static_cast (inClientData); - switch (pa->mSelector) { case kAudioHardwarePropertyDevices: - intern->deviceDetailsChanged(); + static_cast (inClientData)->deviceDetailsChanged(); break; case kAudioHardwarePropertyDefaultOutputDevice: @@ -1111,10 +1031,646 @@ private: }; //============================================================================== -class CoreAudioIODeviceType : public AudioIODeviceType +class AudioIODeviceCombiner : public AudioIODevice, + private Thread { public: + AudioIODeviceCombiner (const String& deviceName) + : AudioIODevice (deviceName, "CoreAudio"), + Thread (deviceName), callback (nullptr), + currentSampleRate (0), currentBufferSize (0), active (false), + fifos (1, 1) + { + } + + ~AudioIODeviceCombiner() + { + close(); + devices.clear(); + } + + void addDevice (AudioIODevice* device) + { + jassert (device != nullptr); + jassert (! isOpen()); + jassert (! device->isOpen()); + devices.add (new DeviceWrapper (*this, device)); + } + + Array getDevices() const + { + Array devs; + + for (int i = 0; i < devices.size(); ++i) + devs.add (devices.getUnchecked(i)->device); + + return devs; + } + + StringArray getOutputChannelNames() override + { + StringArray names; + + for (int i = 0; i < devices.size(); ++i) + names.addArray (devices.getUnchecked(i)->device->getOutputChannelNames()); + + + names.appendNumbersToDuplicates (false, true); + return names; + } + + StringArray getInputChannelNames() override + { + StringArray names; + + for (int i = 0; i < devices.size(); ++i) + names.addArray (devices.getUnchecked(i)->device->getInputChannelNames()); + + names.appendNumbersToDuplicates (false, true); + return names; + } + + Array getAvailableSampleRates() override + { + Array commonRates; + + for (int i = 0; i < devices.size(); ++i) + { + Array rates (devices.getUnchecked(i)->device->getAvailableSampleRates()); + + if (i == 0) + commonRates = rates; + else + commonRates.removeValuesNotIn (rates); + } + + return commonRates; + } + + Array getAvailableBufferSizes() override + { + Array commonSizes; + + for (int i = 0; i < devices.size(); ++i) + { + Array sizes (devices.getUnchecked(i)->device->getAvailableBufferSizes()); + + if (i == 0) + commonSizes = sizes; + else + commonSizes.removeValuesNotIn (sizes); + } + + return commonSizes; + } + + bool isOpen() override { return active; } + bool isPlaying() override { return callback != nullptr; } + double getCurrentSampleRate() override { return currentSampleRate; } + int getCurrentBufferSizeSamples() override { return currentBufferSize; } + + int getCurrentBitDepth() override + { + int depth = 32; + + for (int i = 0; i < devices.size(); ++i) + depth = jmin (depth, devices.getUnchecked(i)->device->getCurrentBitDepth()); + + return depth; + } + + int getDefaultBufferSize() override + { + int size = 0; + + for (int i = 0; i < devices.size(); ++i) + size = jmax (size, devices.getUnchecked(i)->device->getDefaultBufferSize()); + + return size; + } + + String open (const BigInteger& inputChannels, + const BigInteger& outputChannels, + double sampleRate, int bufferSize) override + { + close(); + active = true; + + if (bufferSize <= 0) + bufferSize = getDefaultBufferSize(); + + if (sampleRate <= 0) + { + Array rates (getAvailableSampleRates()); + + for (int i = 0; i < rates.size() && sampleRate < 44100.0; ++i) + sampleRate = rates.getUnchecked(i); + } + + currentSampleRate = sampleRate; + currentBufferSize = bufferSize; + + const int fifoSize = bufferSize * 3 + 1; + int totalInputChanIndex = 0, totalOutputChanIndex = 0; + int chanIndex = 0; + + for (int i = 0; i < devices.size(); ++i) + { + DeviceWrapper& d = *devices.getUnchecked(i); + + BigInteger ins (inputChannels >> totalInputChanIndex); + BigInteger outs (outputChannels >> totalOutputChanIndex); + + int numIns = d.device->getInputChannelNames().size(); + int numOuts = d.device->getOutputChannelNames().size(); + + totalInputChanIndex += numIns; + totalOutputChanIndex += numOuts; + + String err = d.open (ins, outs, sampleRate, bufferSize, + chanIndex, fifoSize); + + if (err.isNotEmpty()) + { + close(); + lastError = err; + return err; + } + + chanIndex += d.numInputChans + d.numOutputChans; + } + + fifos.setSize (chanIndex, fifoSize); + fifos.clear(); + startThread (9); + + return String(); + } + + void close() override + { + stop(); + stopThread (10000); + fifos.clear(); + active = false; + + for (int i = 0; i < devices.size(); ++i) + devices.getUnchecked(i)->close(); + } + + BigInteger getActiveOutputChannels() const override + { + BigInteger chans; + int start = 0; + + for (int i = 0; i < devices.size(); ++i) + { + const int numChans = devices.getUnchecked(i)->device->getOutputChannelNames().size(); + chans |= (devices.getUnchecked(i)->device->getActiveOutputChannels() << start); + start += numChans; + } + + return chans; + } + + BigInteger getActiveInputChannels() const override + { + BigInteger chans; + int start = 0; + + for (int i = 0; i < devices.size(); ++i) + { + const int numChans = devices.getUnchecked(i)->device->getInputChannelNames().size(); + chans |= (devices.getUnchecked(i)->device->getActiveInputChannels() << start); + start += numChans; + } + + return chans; + } + + int getOutputLatencyInSamples() override + { + int lat = 0; + + for (int i = 0; i < devices.size(); ++i) + lat = jmax (lat, devices.getUnchecked(i)->device->getOutputLatencyInSamples()); + + return lat + currentBufferSize * 2; + } + + int getInputLatencyInSamples() override + { + int lat = 0; + + for (int i = 0; i < devices.size(); ++i) + lat = jmax (lat, devices.getUnchecked(i)->device->getInputLatencyInSamples()); + + return lat + currentBufferSize * 2; + } + + void start (AudioIODeviceCallback* newCallback) override + { + if (callback != newCallback) + { + stop(); + fifos.clear(); + + for (int i = 0; i < devices.size(); ++i) + devices.getUnchecked(i)->start(); + + if (newCallback != nullptr) + newCallback->audioDeviceAboutToStart (this); + + const ScopedLock sl (callbackLock); + callback = newCallback; + } + } + + void stop() override + { + AudioIODeviceCallback* lastCallback = nullptr; + + { + const ScopedLock sl (callbackLock); + std::swap (callback, lastCallback); + } + + for (int i = 0; i < devices.size(); ++i) + devices.getUnchecked(i)->device->stop(); + + if (lastCallback != nullptr) + lastCallback->audioDeviceStopped(); + } + + String getLastError() override + { + return lastError; + } + +private: + CriticalSection callbackLock; + AudioIODeviceCallback* callback; + double currentSampleRate; + int currentBufferSize; + bool active; + String lastError; + + AudioSampleBuffer fifos; + + void run() override + { + const int numSamples = currentBufferSize; + + AudioSampleBuffer buffer (fifos.getNumChannels(), numSamples); + buffer.clear(); + + Array inputChans, outputChans; + + for (int i = 0; i < devices.size(); ++i) + { + DeviceWrapper& d = *devices.getUnchecked(i); + + for (int j = 0; j < d.numInputChans; ++j) inputChans.add (buffer.getSampleData (d.inputIndex + j)); + for (int j = 0; j < d.numOutputChans; ++j) outputChans.add (buffer.getSampleData (d.outputIndex + j)); + } + + const int numInputChans = inputChans.size(); + const int numOutputChans = outputChans.size(); + + inputChans.add (nullptr); + outputChans.add (nullptr); + + const int blockSizeMs = jmax (1, (int) (1000 * numSamples / currentSampleRate)); + + jassert (numInputChans + numOutputChans == buffer.getNumChannels()); + + while (! threadShouldExit()) + { + readInput (buffer, numSamples, blockSizeMs); + + bool didCallback = true; + + { + const ScopedLock sl (callbackLock); + + if (callback != nullptr) + callback->audioDeviceIOCallback ((const float**) inputChans.getRawDataPointer(), numInputChans, + outputChans.getRawDataPointer(), numOutputChans, numSamples); + else + didCallback = false; + } + + if (didCallback) + { + pushOutputData (buffer, numSamples, blockSizeMs); + } + else + { + for (int i = 0; i < numOutputChans; ++i) + FloatVectorOperations::clear (outputChans[i], numSamples); + + reset(); + } + } + } + + void reset() + { + for (int i = 0; i < devices.size(); ++i) + devices.getUnchecked(i)->reset(); + } + + void underrun() + { + } + + void readInput (AudioSampleBuffer& buffer, const int numSamples, const int blockSizeMs) + { + for (int i = 0; i < devices.size(); ++i) + { + DeviceWrapper& d = *devices.getUnchecked(i); + d.done = (d.numInputChans == 0); + } + + for (int tries = 3;;) + { + bool anyRemaining = false; + + for (int i = 0; i < devices.size(); ++i) + { + DeviceWrapper& d = *devices.getUnchecked(i); + + if (! d.done) + { + if (d.isInputReady (numSamples)) + { + d.readInput (buffer, numSamples); + d.done = true; + } + else + anyRemaining = true; + } + } + + if (! anyRemaining) + return; + + if (--tries == 0) + break; + + wait (blockSizeMs); + } + + for (int j = 0; j < devices.size(); ++j) + { + DeviceWrapper& d = *devices.getUnchecked(j); + + if (! d.done) + for (int i = 0; i < d.numInputChans; ++i) + buffer.clear (d.inputIndex + i, 0, numSamples); + } + } + + void pushOutputData (AudioSampleBuffer& buffer, const int numSamples, const int blockSizeMs) + { + for (int i = 0; i < devices.size(); ++i) + { + DeviceWrapper& d = *devices.getUnchecked(i); + d.done = (d.numOutputChans == 0); + } + + for (int tries = 3;;) + { + bool anyRemaining = false; + + for (int i = 0; i < devices.size(); ++i) + { + DeviceWrapper& d = *devices.getUnchecked(i); + + if (! d.done) + { + if (d.isOutputReady (numSamples)) + { + d.pushOutputData (buffer, numSamples); + d.done = true; + } + else + anyRemaining = true; + } + } + + if ((! anyRemaining) || --tries == 0) + return; + + wait (blockSizeMs); + } + } + //============================================================================== + struct DeviceWrapper : private AudioIODeviceCallback + { + DeviceWrapper (AudioIODeviceCombiner& cd, AudioIODevice* d) + : owner (cd), device (d), inputIndex (0), outputIndex (0), + inputFifo (32), outputFifo (32), done (false) + { + } + + ~DeviceWrapper() + { + close(); + } + + String open (const BigInteger& inputChannels, const BigInteger& outputChannels, + double sampleRate, int bufferSize, + int channelIndex, + int fifoSize) + { + inputFifo.setTotalSize (fifoSize); + outputFifo.setTotalSize (fifoSize); + inputFifo.reset(); + outputFifo.reset(); + + String err (device->open (inputChannels, outputChannels, sampleRate, bufferSize)); + + numInputChans = device->getActiveInputChannels().countNumberOfSetBits(); + numOutputChans = device->getActiveOutputChannels().countNumberOfSetBits(); + + inputIndex = channelIndex; + outputIndex = channelIndex + numInputChans; + + return err; + } + + void close() + { + device->close(); + } + + void start() + { + reset(); + device->start (this); + } + + void reset() + { + inputFifo.reset(); + outputFifo.reset(); + } + + bool isInputReady (int numSamples) const noexcept + { + return numInputChans == 0 || inputFifo.getNumReady() >= numSamples; + } + + void readInput (AudioSampleBuffer& destBuffer, int numSamples) + { + if (numInputChans == 0) + return; + + int start1, size1, start2, size2; + inputFifo.prepareToRead (numSamples, start1, size1, start2, size2); + + for (int i = 0; i < numInputChans; ++i) + { + const int index = inputIndex + i; + float* const dest = destBuffer.getSampleData (index); + const float* const src = owner.fifos.getSampleData (index); + + if (size1 > 0) FloatVectorOperations::copy (dest, src + start1, size1); + if (size2 > 0) FloatVectorOperations::copy (dest + size1, src + start2, size2); + } + + inputFifo.finishedRead (size1 + size2); + } + + bool isOutputReady (int numSamples) const noexcept + { + return numOutputChans == 0 || outputFifo.getFreeSpace() >= numSamples; + } + + void pushOutputData (AudioSampleBuffer& srcBuffer, int numSamples) + { + if (numOutputChans == 0) + return; + + int start1, size1, start2, size2; + outputFifo.prepareToWrite (numSamples, start1, size1, start2, size2); + + for (int i = 0; i < numOutputChans; ++i) + { + const int index = outputIndex + i; + float* const dest = owner.fifos.getSampleData (index); + const float* const src = srcBuffer.getSampleData (index); + + if (size1 > 0) FloatVectorOperations::copy (dest + start1, src, size1); + if (size2 > 0) FloatVectorOperations::copy (dest + start2, src + size1, size2); + } + + outputFifo.finishedWrite (size1 + size2); + } + + void audioDeviceIOCallback (const float** inputChannelData, int numInputChannels, + float** outputChannelData, int numOutputChannels, + int numSamples) override + { + AudioSampleBuffer& buf = owner.fifos; + + if (numInputChannels > 0) + { + int start1, size1, start2, size2; + inputFifo.prepareToWrite (numSamples, start1, size1, start2, size2); + + if (size1 + size2 < numSamples) + { + inputFifo.reset(); + inputFifo.prepareToWrite (numSamples, start1, size1, start2, size2); + } + + for (int i = 0; i < numInputChannels; ++i) + { + float* const dest = buf.getSampleData (inputIndex + i); + const float* const src = inputChannelData[i]; + + if (size1 > 0) FloatVectorOperations::copy (dest + start1, src, size1); + if (size2 > 0) FloatVectorOperations::copy (dest + start2, src + size1, size2); + } + + inputFifo.finishedWrite (size1 + size2); + + if (numSamples > size1 + size2) + { + for (int i = 0; i < numInputChans; ++i) + buf.clear (inputIndex + i, size1 + size2, numSamples - (size1 + size2)); + + owner.underrun(); + } + } + + if (numOutputChannels > 0) + { + int start1, size1, start2, size2; + outputFifo.prepareToRead (numSamples, start1, size1, start2, size2); + + if (size1 + size2 < numSamples) + { + Thread::sleep (1); + outputFifo.prepareToRead (numSamples, start1, size1, start2, size2); + } + + for (int i = 0; i < numOutputChannels; ++i) + { + float* const dest = outputChannelData[i]; + const float* const src = buf.getSampleData (outputIndex + i); + + if (size1 > 0) FloatVectorOperations::copy (dest, src + start1, size1); + if (size2 > 0) FloatVectorOperations::copy (dest + size1, src + start2, size2); + } + + outputFifo.finishedRead (size1 + size2); + + if (numSamples > size1 + size2) + { + for (int i = 0; i < numOutputChannels; ++i) + FloatVectorOperations::clear (outputChannelData[i] + (size1 + size2), numSamples - (size1 + size2)); + + owner.underrun(); + } + } + + owner.notify(); + } + + void audioDeviceAboutToStart (AudioIODevice*) override {} + void audioDeviceStopped() override {} + + void audioDeviceError (const String& errorMessage) override + { + const ScopedLock sl (owner.callbackLock); + + if (owner.callback != nullptr) + owner.callback->audioDeviceError (errorMessage); + } + + AudioIODeviceCombiner& owner; + ScopedPointer device; + int inputIndex, numInputChans, outputIndex, numOutputChans; + AbstractFifo inputFifo, outputFifo; + bool done; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DeviceWrapper) + }; + + OwnedArray devices; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioIODeviceCombiner) +}; + + +//============================================================================== +class CoreAudioIODeviceType : public AudioIODeviceType +{ +public: CoreAudioIODeviceType() : AudioIODeviceType ("CoreAudio"), hasScanned (false) @@ -1154,12 +1710,12 @@ public: pa.mScope = kAudioObjectPropertyScopeWildcard; pa.mElement = kAudioObjectPropertyElementMaster; - if (AudioObjectGetPropertyDataSize (kAudioObjectSystemObject, &pa, 0, 0, &size) == noErr) + if (AudioObjectGetPropertyDataSize (kAudioObjectSystemObject, &pa, 0, nullptr, &size) == noErr) { - HeapBlock devs; + HeapBlock devs; devs.calloc (size, 1); - if (AudioObjectGetPropertyData (kAudioObjectSystemObject, &pa, 0, 0, &size, devs) == noErr) + if (AudioObjectGetPropertyData (kAudioObjectSystemObject, &pa, 0, nullptr, &size, devs) == noErr) { const int num = size / (int) sizeof (AudioDeviceID); for (int i = 0; i < num; ++i) @@ -1168,7 +1724,7 @@ public: size = sizeof (name); pa.mSelector = kAudioDevicePropertyDeviceName; - if (AudioObjectGetPropertyData (devs[i], &pa, 0, 0, &size, name) == noErr) + if (AudioObjectGetPropertyData (devs[i], &pa, 0, nullptr, &size, name) == noErr) { const String nameString (String::fromUTF8 (name, (int) strlen (name))); const int numIns = getNumChannels (devs[i], true); @@ -1213,11 +1769,12 @@ public: // get the built-in mic rather than the built-in output with no inputs.. AudioObjectPropertyAddress pa; - pa.mSelector = forInput ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementMaster; + pa.mSelector = forInput ? kAudioHardwarePropertyDefaultInputDevice + : kAudioHardwarePropertyDefaultOutputDevice; + pa.mScope = kAudioObjectPropertyScopeWildcard; + pa.mElement = kAudioObjectPropertyElementMaster; - if (AudioObjectGetPropertyData (kAudioObjectSystemObject, &pa, 0, 0, &size, &deviceID) == noErr) + if (AudioObjectGetPropertyData (kAudioObjectSystemObject, &pa, 0, nullptr, &size, &deviceID) == noErr) { if (forInput) { @@ -1240,12 +1797,24 @@ public: { jassert (hasScanned); // need to call scanForDevices() before doing this - CoreAudioIODevice* const d = dynamic_cast (device); - if (d == nullptr) - return -1; + if (CoreAudioIODevice* const d = dynamic_cast (device)) + return asInput ? d->inputIndex + : d->outputIndex; - return asInput ? d->inputIndex - : d->outputIndex; + if (AudioIODeviceCombiner* const d = dynamic_cast (device)) + { + const Array devs (d->getDevices()); + + for (int i = 0; i < devs.size(); ++i) + { + const int index = getIndexOfDevice (devs.getUnchecked(i), asInput); + + if (index >= 0) + return index; + } + } + + return -1; } bool hasSeparateInputsAndOutputs() const { return true; } @@ -1255,27 +1824,41 @@ public: { jassert (hasScanned); // need to call scanForDevices() before doing this - const int inputIndex = inputDeviceNames.indexOf (inputDeviceName); + const int inputIndex = inputDeviceNames.indexOf (inputDeviceName); const int outputIndex = outputDeviceNames.indexOf (outputDeviceName); - String deviceName (outputDeviceName); - if (deviceName.isEmpty()) - deviceName = inputDeviceName; + AudioDeviceID inputDeviceID = inputIds [inputIndex]; + AudioDeviceID outputDeviceID = outputIds [outputIndex]; + + if (inputDeviceID == 0 && outputDeviceID == 0) + return nullptr; + + String combinedName (outputDeviceName.isEmpty() ? inputDeviceName : outputDeviceName); + + if (inputDeviceID == outputDeviceID) + return new CoreAudioIODevice (combinedName, inputDeviceID, inputIndex, outputDeviceID, outputIndex); + + ScopedPointer in, out; + + if (inputDeviceID != 0) + in = new CoreAudioIODevice (inputDeviceName, inputDeviceID, inputIndex, 0, -1); + + if (outputDeviceID != 0) + out = new CoreAudioIODevice (outputDeviceName, 0, -1, outputDeviceID, outputIndex); - if (index >= 0) - return new CoreAudioIODevice (deviceName, - inputIds [inputIndex], - inputIndex, - outputIds [outputIndex], - outputIndex); + if (in == nullptr) return out.release(); + if (out == nullptr) return in.release(); - return nullptr; + ScopedPointer combo (new AudioIODeviceCombiner (combinedName)); + combo->addDevice (in.release()); + combo->addDevice (out.release()); + return combo.release(); } //============================================================================== private: StringArray inputDeviceNames, outputDeviceNames; - Array inputIds, outputIds; + Array inputIds, outputIds; bool hasScanned; @@ -1289,12 +1872,12 @@ private: pa.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; pa.mElement = kAudioObjectPropertyElementMaster; - if (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, 0, &size) == noErr) + if (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, nullptr, &size) == noErr) { HeapBlock bufList; bufList.calloc (size, 1); - if (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, bufList) == noErr) + if (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, bufList) == noErr) { const int numStreams = (int) bufList->mNumberBuffers; @@ -1317,7 +1900,7 @@ private: static OSStatus hardwareListenerProc (AudioDeviceID, UInt32, const AudioObjectPropertyAddress*, void* clientData) { - static_cast (clientData)->audioDeviceListChanged(); + static_cast (clientData)->audioDeviceListChanged(); return noErr; } diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_ASIO.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_ASIO.cpp index dc46c69..1b520dd 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_ASIO.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_ASIO.cpp @@ -366,13 +366,13 @@ public: { // find a list of sample rates.. const int possibleSampleRates[] = { 44100, 48000, 88200, 96000, 176400, 192000 }; - Array newRates; + Array newRates; if (asioObject != nullptr) { for (int index = 0; index < numElementsInArray (possibleSampleRates); ++index) if (asioObject->canSampleRate ((double) possibleSampleRates[index]) == 0) - newRates.add (possibleSampleRates[index]); + newRates.add ((double) possibleSampleRates[index]); } if (newRates.size() == 0) @@ -398,19 +398,16 @@ public: } } - StringArray getOutputChannelNames() { return outputChannelNames; } - StringArray getInputChannelNames() { return inputChannelNames; } + StringArray getOutputChannelNames() override { return outputChannelNames; } + StringArray getInputChannelNames() override { return inputChannelNames; } - int getNumSampleRates() { return sampleRates.size(); } - double getSampleRate (int index) { return sampleRates [index]; } - - int getNumBufferSizesAvailable() { return bufferSizes.size(); } - int getBufferSizeSamples (int index) { return bufferSizes [index]; } - int getDefaultBufferSize() { return preferredSize; } + Array getAvailableSampleRates() override { return sampleRates; } + Array getAvailableBufferSizes() override { return bufferSizes; } + int getDefaultBufferSize() override { return preferredSize; } String open (const BigInteger& inputChannels, const BigInteger& outputChannels, - double sr, int bufferSizeSamples) + double sr, int bufferSizeSamples) override { if (isOpen()) close(); @@ -436,7 +433,7 @@ public: bufferSizeSamples = readBufferSizes (bufferSizeSamples); - int sampleRate = roundToInt (sr); + double sampleRate = sr; currentSampleRate = sampleRate; currentBlockSizeSamples = bufferSizeSamples; currentChansOut.clear(); @@ -451,12 +448,12 @@ public: jassert (sampleRate != 0); if (sampleRate == 0) - sampleRate = 44100; + sampleRate = 44100.0; updateClockSources(); currentSampleRate = getSampleRate(); - error = String::empty; + error.clear(); buffersCreated = false; setSampleRate (sampleRate); @@ -615,9 +612,9 @@ public: return error; } - void close() + void close() override { - error = String::empty; + error.clear(); stopTimer(); stop(); @@ -643,20 +640,20 @@ public: } } - bool isOpen() { return deviceIsOpen || insideControlPanelModalLoop; } - bool isPlaying() { return asioObject != nullptr && currentCallback != nullptr; } + bool isOpen() override { return deviceIsOpen || insideControlPanelModalLoop; } + bool isPlaying() override { return asioObject != nullptr && currentCallback != nullptr; } - int getCurrentBufferSizeSamples() { return currentBlockSizeSamples; } - double getCurrentSampleRate() { return currentSampleRate; } - int getCurrentBitDepth() { return currentBitDepth; } + int getCurrentBufferSizeSamples() override { return currentBlockSizeSamples; } + double getCurrentSampleRate() override { return currentSampleRate; } + int getCurrentBitDepth() override { return currentBitDepth; } - BigInteger getActiveOutputChannels() const { return currentChansOut; } - BigInteger getActiveInputChannels() const { return currentChansIn; } + BigInteger getActiveOutputChannels() const override { return currentChansOut; } + BigInteger getActiveInputChannels() const override { return currentChansIn; } - int getOutputLatencyInSamples() { return outputLatency + currentBlockSizeSamples / 4; } - int getInputLatencyInSamples() { return inputLatency + currentBlockSizeSamples / 4; } + int getOutputLatencyInSamples() override { return outputLatency + currentBlockSizeSamples / 4; } + int getInputLatencyInSamples() override { return inputLatency + currentBlockSizeSamples / 4; } - void start (AudioIODeviceCallback* callback) + void start (AudioIODeviceCallback* callback) override { if (callback != nullptr) { @@ -667,7 +664,7 @@ public: } } - void stop() + void stop() override { AudioIODeviceCallback* const lastCallback = currentCallback; @@ -764,7 +761,8 @@ private: long totalNumInputChans, totalNumOutputChans; StringArray inputChannelNames, outputChannelNames; - Array sampleRates, bufferSizes; + Array sampleRates; + Array bufferSizes; long inputLatency, outputLatency; long minSize, maxSize, preferredSize, granularity; ASIOClockSource clocks[32]; @@ -951,7 +949,7 @@ private: return cr; } - void setSampleRate (int newRate) + void setSampleRate (double newRate) { if (currentSampleRate != newRate) { @@ -1181,7 +1179,7 @@ private: numActiveOutputChans = 0; currentCallback = nullptr; - error = String::empty; + error.clear(); if (getName().isEmpty()) return error; diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp index 2546f99..3eb0757 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp @@ -755,7 +755,7 @@ public: String open (const BigInteger& inputChannels, const BigInteger& outputChannels, - double sampleRate, int bufferSizeSamples) + double sampleRate, int bufferSizeSamples) override { lastError = openDevice (inputChannels, outputChannels, sampleRate, bufferSizeSamples); isOpen_ = lastError.isEmpty(); @@ -763,7 +763,7 @@ public: return lastError; } - void close() + void close() override { stop(); @@ -774,38 +774,41 @@ public: } } - bool isOpen() { return isOpen_ && isThreadRunning(); } - int getCurrentBufferSizeSamples() { return bufferSizeSamples; } - double getCurrentSampleRate() { return sampleRate; } - BigInteger getActiveOutputChannels() const { return enabledOutputs; } - BigInteger getActiveInputChannels() const { return enabledInputs; } - int getOutputLatencyInSamples() { return (int) (getCurrentBufferSizeSamples() * 1.5); } - int getInputLatencyInSamples() { return getOutputLatencyInSamples(); } - StringArray getOutputChannelNames() { return outChannels; } - StringArray getInputChannelNames() { return inChannels; } - - int getNumSampleRates() { return 4; } - int getDefaultBufferSize() { return 2560; } - int getNumBufferSizesAvailable() { return 50; } - - double getSampleRate (int index) + bool isOpen() override { return isOpen_ && isThreadRunning(); } + int getCurrentBufferSizeSamples() override { return bufferSizeSamples; } + double getCurrentSampleRate() override { return sampleRate; } + BigInteger getActiveOutputChannels() const override { return enabledOutputs; } + BigInteger getActiveInputChannels() const override { return enabledInputs; } + int getOutputLatencyInSamples() override { return (int) (getCurrentBufferSizeSamples() * 1.5); } + int getInputLatencyInSamples() override { return getOutputLatencyInSamples(); } + StringArray getOutputChannelNames() override { return outChannels; } + StringArray getInputChannelNames() override { return inChannels; } + + Array getAvailableSampleRates() override { - const double samps[] = { 44100.0, 48000.0, 88200.0, 96000.0 }; - return samps [jlimit (0, 3, index)]; + static const double rates[] = { 44100.0, 48000.0, 88200.0, 96000.0 }; + return Array (rates, numElementsInArray (rates)); } - int getBufferSizeSamples (int index) + Array getAvailableBufferSizes() override { + Array r; int n = 64; - for (int i = 0; i < index; ++i) + + for (int i = 0; i < 50; ++i) + { + r.add (n); n += (n < 512) ? 32 : ((n < 1024) ? 64 : ((n < 2048) ? 128 : 256)); + } - return n; + return r; } - int getCurrentBitDepth() + int getDefaultBufferSize() override { return 2560; } + + int getCurrentBitDepth() override { int bits = 256; @@ -821,7 +824,7 @@ public: return bits; } - void start (AudioIODeviceCallback* call) + void start (AudioIODeviceCallback* call) override { if (isOpen_ && call != nullptr && ! isStarted) { @@ -840,7 +843,7 @@ public: } } - void stop() + void stop() override { if (isStarted) { @@ -856,8 +859,8 @@ public: } } - bool isPlaying() { return isStarted && isOpen_ && isThreadRunning(); } - String getLastError() { return lastError; } + bool isPlaying() override { return isStarted && isOpen_ && isThreadRunning(); } + String getLastError() override { return lastError; } //============================================================================== StringArray inChannels, outChannels; diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp index 7482ec1..b60cdd4 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp @@ -882,7 +882,7 @@ public: return false; } - StringArray getOutputChannelNames() + StringArray getOutputChannelNames() override { StringArray outChannels; @@ -893,7 +893,7 @@ public: return outChannels; } - StringArray getInputChannelNames() + StringArray getInputChannelNames() override { StringArray inChannels; @@ -904,31 +904,29 @@ public: return inChannels; } - int getNumSampleRates() { return sampleRates.size(); } - double getSampleRate (int index) { return sampleRates [index]; } - int getNumBufferSizesAvailable() { return bufferSizes.size(); } - int getBufferSizeSamples (int index) { return bufferSizes [index]; } - int getDefaultBufferSize() { return defaultBufferSize; } + Array getAvailableSampleRates() override { return sampleRates; } + Array getAvailableBufferSizes() override { return bufferSizes; } + int getDefaultBufferSize() override { return defaultBufferSize; } - int getCurrentBufferSizeSamples() { return currentBufferSizeSamples; } - double getCurrentSampleRate() { return currentSampleRate; } - int getCurrentBitDepth() { return 32; } - int getOutputLatencyInSamples() { return latencyOut; } - int getInputLatencyInSamples() { return latencyIn; } - BigInteger getActiveOutputChannels() const { return outputDevice != nullptr ? outputDevice->channels : BigInteger(); } - BigInteger getActiveInputChannels() const { return inputDevice != nullptr ? inputDevice->channels : BigInteger(); } - String getLastError() { return lastError; } + int getCurrentBufferSizeSamples() override { return currentBufferSizeSamples; } + double getCurrentSampleRate() override { return currentSampleRate; } + int getCurrentBitDepth() override { return 32; } + int getOutputLatencyInSamples() override { return latencyOut; } + int getInputLatencyInSamples() override { return latencyIn; } + BigInteger getActiveOutputChannels() const override { return outputDevice != nullptr ? outputDevice->channels : BigInteger(); } + BigInteger getActiveInputChannels() const override { return inputDevice != nullptr ? inputDevice->channels : BigInteger(); } + String getLastError() override { return lastError; } String open (const BigInteger& inputChannels, const BigInteger& outputChannels, - double sampleRate, int bufferSizeSamples) + double sampleRate, int bufferSizeSamples) override { close(); - lastError = String::empty; + lastError.clear(); if (sampleRates.size() == 0 && inputDevice != nullptr && outputDevice != nullptr) { - lastError = "The input and output devices don't share a common sample rate!"; + lastError = TRANS("The input and output devices don't share a common sample rate!"); return lastError; } @@ -939,14 +937,14 @@ public: if (inputDevice != nullptr && ! inputDevice->open (currentSampleRate, inputChannels)) { - lastError = "Couldn't open the input device!"; + lastError = TRANS("Couldn't open the input device!"); return lastError; } if (outputDevice != nullptr && ! outputDevice->open (currentSampleRate, outputChannels)) { close(); - lastError = "Couldn't open the output device!"; + lastError = TRANS("Couldn't open the output device!"); return lastError; } @@ -963,7 +961,7 @@ public: if (! check (inputDevice->client->Start())) { close(); - lastError = "Couldn't start the input device!"; + lastError = TRANS("Couldn't start the input device!"); return lastError; } } @@ -975,7 +973,7 @@ public: if (! check (outputDevice->client->Start())) { close(); - lastError = "Couldn't start the output device!"; + lastError = TRANS("Couldn't start the output device!"); return lastError; } } @@ -984,7 +982,7 @@ public: return lastError; } - void close() + void close() override { stop(); signalThreadShouldExit(); @@ -1000,10 +998,10 @@ public: isOpen_ = false; } - bool isOpen() { return isOpen_ && isThreadRunning(); } - bool isPlaying() { return isStarted && isOpen_ && isThreadRunning(); } + bool isOpen() override { return isOpen_ && isThreadRunning(); } + bool isPlaying() override { return isStarted && isOpen_ && isThreadRunning(); } - void start (AudioIODeviceCallback* call) + void start (AudioIODeviceCallback* call) override { if (isOpen_ && call != nullptr && ! isStarted) { @@ -1022,7 +1020,7 @@ public: } } - void stop() + void stop() override { if (isStarted) { diff --git a/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioSourcePlayer.h b/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioSourcePlayer.h index badd651..0a3751c 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioSourcePlayer.h +++ b/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioSourcePlayer.h @@ -65,8 +65,7 @@ public: void setSource (AudioSource* newSource); /** Returns the source that's playing. - - May return 0 if there's no source. + May return nullptr if there's no source. */ AudioSource* getCurrentSource() const noexcept { return source; } diff --git a/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioTransportSource.cpp b/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioTransportSource.cpp index 624e9a6..e923b46 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioTransportSource.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioTransportSource.cpp @@ -44,15 +44,12 @@ AudioTransportSource::AudioTransportSource() AudioTransportSource::~AudioTransportSource() { setSource (nullptr); - releaseMasterResources(); } void AudioTransportSource::setSource (PositionableAudioSource* const newSource, - int readAheadBufferSize_, - TimeSliceThread* readAheadThread, - double sourceSampleRateToCorrectFor, - int maxNumChannels) + int readAheadSize, TimeSliceThread* readAheadThread, + double sourceSampleRateToCorrectFor, int maxNumChannels) { if (source == newSource) { @@ -62,7 +59,7 @@ void AudioTransportSource::setSource (PositionableAudioSource* const newSource, setSource (nullptr, 0, nullptr); // deselect and reselect to avoid releasing resources wrongly } - readAheadBufferSize = readAheadBufferSize_; + readAheadBufferSize = readAheadSize; sourceSampleRate = sourceSampleRateToCorrectFor; ResamplingAudioSource* newResamplerSource = nullptr; @@ -70,15 +67,15 @@ void AudioTransportSource::setSource (PositionableAudioSource* const newSource, PositionableAudioSource* newPositionableSource = nullptr; AudioSource* newMasterSource = nullptr; - ScopedPointer oldResamplerSource (resamplerSource); - ScopedPointer oldBufferingSource (bufferingSource); + ScopedPointer oldResamplerSource (resamplerSource); + ScopedPointer oldBufferingSource (bufferingSource); AudioSource* oldMasterSource = masterSource; if (newSource != nullptr) { newPositionableSource = newSource; - if (readAheadBufferSize_ > 0) + if (readAheadSize > 0) { // If you want to use a read-ahead buffer, you must also provide a TimeSliceThread // for it to use! @@ -86,7 +83,7 @@ void AudioTransportSource::setSource (PositionableAudioSource* const newSource, newPositionableSource = newBufferingSource = new BufferingAudioSource (newPositionableSource, *readAheadThread, - false, readAheadBufferSize_, maxNumChannels); + false, readAheadSize, maxNumChannels); } newPositionableSource->setNextReadPosition (0); @@ -115,6 +112,7 @@ void AudioTransportSource::setSource (PositionableAudioSource* const newSource, masterSource = newMasterSource; positionableSource = newPositionableSource; + inputStreamEOF = false; playing = false; } @@ -170,7 +168,10 @@ double AudioTransportSource::getCurrentPosition() const double AudioTransportSource::getLengthInSeconds() const { - return getTotalLength() / sampleRate; + if (sampleRate > 0.0) + return getTotalLength() / sampleRate; + + return 0.0; } void AudioTransportSource::setNextReadPosition (int64 newPosition) @@ -181,6 +182,7 @@ void AudioTransportSource::setNextReadPosition (int64 newPosition) newPosition = (int64) (newPosition * sourceSampleRate / sampleRate); positionableSource->setNextReadPosition (newPosition); + inputStreamEOF = false; } } @@ -189,7 +191,6 @@ int64 AudioTransportSource::getNextReadPosition() const if (positionableSource != nullptr) { const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; - return (int64) (positionableSource->getNextReadPosition() * ratio); } @@ -203,7 +204,6 @@ int64 AudioTransportSource::getTotalLength() const if (positionableSource != nullptr) { const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; - return (int64) (positionableSource->getTotalLength() * ratio); } @@ -213,9 +213,7 @@ int64 AudioTransportSource::getTotalLength() const bool AudioTransportSource::isLooping() const { const ScopedLock sl (callbackLock); - - return positionableSource != nullptr - && positionableSource->isLooping(); + return positionableSource != nullptr && positionableSource->isLooping(); } void AudioTransportSource::setGain (const float newGain) noexcept @@ -236,6 +234,7 @@ void AudioTransportSource::prepareToPlay (int samplesPerBlockExpected, double ne if (resamplerSource != nullptr && sourceSampleRate > 0) resamplerSource->setResamplingRatio (sourceSampleRate / sampleRate); + inputStreamEOF = false; isPrepared = true; } @@ -258,8 +257,6 @@ void AudioTransportSource::getNextAudioBlock (const AudioSourceChannelInfo& info { const ScopedLock sl (callbackLock); - inputStreamEOF = false; - if (masterSource != nullptr && ! stopped) { masterSource->getNextAudioBlock (info); @@ -275,7 +272,7 @@ void AudioTransportSource::getNextAudioBlock (const AudioSourceChannelInfo& info } if (positionableSource->getNextReadPosition() > positionableSource->getTotalLength() + 1 - && ! positionableSource->isLooping()) + && ! positionableSource->isLooping()) { playing = false; inputStreamEOF = true; @@ -285,10 +282,7 @@ void AudioTransportSource::getNextAudioBlock (const AudioSourceChannelInfo& info stopped = ! playing; for (int i = info.buffer->getNumChannels(); --i >= 0;) - { - info.buffer->applyGainRamp (i, info.startSample, info.numSamples, - lastGain, gain); - } + info.buffer->applyGainRamp (i, info.startSample, info.numSamples, lastGain, gain); } else { diff --git a/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioTransportSource.h b/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioTransportSource.h index e63fa0d..e60dee0 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioTransportSource.h +++ b/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioTransportSource.h @@ -45,7 +45,6 @@ class JUCE_API AudioTransportSource : public PositionableAudioSource, public: //============================================================================== /** Creates an AudioTransportSource. - After creating one of these, use the setSource() method to select an input source. */ AudioTransportSource(); @@ -94,7 +93,6 @@ public: void setPosition (double newPosition); /** Returns the position that the next data block will be read from - This is a time in seconds. */ double getCurrentPosition() const; @@ -102,8 +100,7 @@ public: /** Returns the stream's length in seconds. */ double getLengthInSeconds() const; - /** Returns true if the player has stopped because its input stream ran out of data. - */ + /** Returns true if the player has stopped because its input stream ran out of data. */ bool hasStreamFinished() const noexcept { return inputStreamEOF; } //============================================================================== @@ -126,19 +123,16 @@ public: //============================================================================== /** Changes the gain to apply to the output. - @param newGain a factor by which to multiply the outgoing samples, so 1.0 = 0dB, 0.5 = -6dB, 2.0 = 6dB, etc. */ void setGain (float newGain) noexcept; /** Returns the current gain setting. - @see setGain */ float getGain() const noexcept { return gain; } - //============================================================================== /** Implementation of the AudioSource method. */ void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; @@ -175,7 +169,7 @@ private: bool volatile playing, stopped; double sampleRate, sourceSampleRate; int blockSize, readAheadBufferSize; - bool isPrepared, inputStreamEOF; + bool volatile isPrepared, inputStreamEOF; void releaseMasterResources(); diff --git a/JuceLibraryCode/modules/juce_audio_formats/codecs/flac/libFLAC/stream_decoder.c b/JuceLibraryCode/modules/juce_audio_formats/codecs/flac/libFLAC/stream_decoder.c index b268fe0..04e1766 100644 --- a/JuceLibraryCode/modules/juce_audio_formats/codecs/flac/libFLAC/stream_decoder.c +++ b/JuceLibraryCode/modules/juce_audio_formats/codecs/flac/libFLAC/stream_decoder.c @@ -1348,12 +1348,12 @@ FLAC__bool has_id_filtered_(FLAC__StreamDecoder *decoder, FLAC__byte *id) FLAC__bool find_metadata_(FLAC__StreamDecoder *decoder) { FLAC__uint32 x; - unsigned i, id; + unsigned i, id_; FLAC__bool first = true; FLAC__ASSERT(FLAC__bitreader_is_consumed_byte_aligned(decoder->private_->input)); - for(i = id = 0; i < 4; ) { + for(i = id_ = 0; i < 4; ) { if(decoder->private_->cached) { x = (FLAC__uint32)decoder->private_->lookahead; decoder->private_->cached = false; @@ -1365,19 +1365,19 @@ FLAC__bool find_metadata_(FLAC__StreamDecoder *decoder) if(x == FLAC__STREAM_SYNC_STRING[i]) { first = true; i++; - id = 0; + id_ = 0; continue; } - if(x == ID3V2_TAG_[id]) { - id++; + if(x == ID3V2_TAG_[id_]) { + id_++; i = 0; - if(id == 3) { + if(id_ == 3) { if(!skip_id3v2_tag_(decoder)) return false; /* skip_id3v2_tag_ sets the state for us */ } continue; } - id = 0; + id_ = 0; if(x == 0xff) { /* MAGIC NUMBER for the first 8 frame sync bits */ decoder->private_->header_warmup[0] = (FLAC__byte)x; if(!FLAC__bitreader_read_raw_uint32(decoder->private_->input, &x, 8)) @@ -3369,4 +3369,4 @@ FLAC__bool file_eof_callback_(const FLAC__StreamDecoder *decoder, void *client_d return feof(decoder->private_->file)? true : false; } -#endif \ No newline at end of file +#endif diff --git a/JuceLibraryCode/modules/juce_audio_formats/codecs/flac/libFLAC/stream_encoder.c b/JuceLibraryCode/modules/juce_audio_formats/codecs/flac/libFLAC/stream_encoder.c index f2507ca..20e98da 100644 --- a/JuceLibraryCode/modules/juce_audio_formats/codecs/flac/libFLAC/stream_encoder.c +++ b/JuceLibraryCode/modules/juce_audio_formats/codecs/flac/libFLAC/stream_encoder.c @@ -4333,4 +4333,4 @@ FILE *get_binary_stdout_(void) return stdout; } -#endif \ No newline at end of file +#endif diff --git a/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp b/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp index 86f9976..d7edd45 100644 --- a/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp +++ b/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp @@ -343,7 +343,7 @@ namespace AiffFileHelpers out.writeIntBigEndian (values.getValue (prefix + "TimeStamp", "0").getIntValue()); out.writeShortBigEndian ((short) values.getValue (prefix + "Identifier", "0").getIntValue()); - const String comment (values.getValue (prefix + "Text", String::empty)); + const String comment (values.getValue (prefix + "Text", String())); const size_t commentLength = jmin (comment.getNumBytesAsUTF8(), (size_t) 65534); out.writeShortBigEndian ((short) commentLength + 1); diff --git a/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.cpp b/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.cpp index c5c337f..5d30d44 100644 --- a/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.cpp +++ b/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_LAMEEncoderAudioFormat.cpp @@ -28,7 +28,7 @@ class LAMEEncoderAudioFormat::Writer : public AudioFormatWriter { public: Writer (OutputStream* destStream, const String& formatName, - const File& lameApp, int vbr, int cbr, + const File& appFile, int vbr, int cbr, double sampleRate, unsigned int numberOfChannels, unsigned int bitsPerSample, const StringPairArray& metadata) : AudioFormatWriter (destStream, formatName, sampleRate, @@ -43,7 +43,7 @@ public: writer = wavFormat.createWriterFor (out, sampleRate, numChannels, bitsPerSample, metadata, 0); - args.add (lameApp.getFullPathName()); + args.add (appFile.getFullPathName()); args.add ("--quiet"); @@ -72,7 +72,7 @@ public: void addMetadataArg (const StringPairArray& metadata, const char* key, const char* lameFlag) { - const String value (metadata.getValue (key, String::empty)); + const String value (metadata.getValue (key, String())); if (value.isNotEmpty()) { @@ -103,11 +103,11 @@ private: ScopedPointer writer; StringArray args; - bool runLameChildProcess (const TemporaryFile& tempMP3, const StringArray& args) const + bool runLameChildProcess (const TemporaryFile& tempMP3, const StringArray& processArgs) const { ChildProcess cp; - if (cp.start (args)) + if (cp.start (processArgs)) { const String childOutput (cp.readAllProcessOutput()); DBG (childOutput); (void) childOutput; diff --git a/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp b/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp index 226b374..8a7033a 100644 --- a/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp +++ b/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp @@ -3095,7 +3095,14 @@ private: const int64 streamSize = stream.stream.getTotalLength(); if (streamSize > 0) - numFrames = (streamSize - streamStartPos) / (stream.frame.frameSize); + { + const int bytesPerFrame = stream.frame.frameSize + 4; + + if (bytesPerFrame == 417 || bytesPerFrame == 418) + numFrames = roundToInt ((streamSize - streamStartPos) / 417.95918); // more accurate for 128k + else + numFrames = (streamSize - streamStartPos) / bytesPerFrame; + } } return numFrames * 1152; diff --git a/JuceLibraryCode/modules/juce_audio_formats/juce_module_info b/JuceLibraryCode/modules/juce_audio_formats/juce_module_info index e749b5c..b37705e 100644 --- a/JuceLibraryCode/modules/juce_audio_formats/juce_module_info +++ b/JuceLibraryCode/modules/juce_audio_formats/juce_module_info @@ -1,7 +1,7 @@ { "id": "juce_audio_formats", "name": "JUCE audio file format codecs", - "version": "3.0.0", + "version": "3.0.1", "description": "Classes for reading and writing various audio file formats.", "website": "http://www.juce.com/juce", "license": "GPL/Commercial", diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/JuceLibraryCode/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index 0ebcf9e..b1d9ec2 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -945,9 +945,10 @@ public: (juce::uint8) inData2 }; incomingEvents.addEvent (data, 3, (int) inStartFrame); - #endif - return noErr; + #else + return kAudioUnitErr_PropertyNotInUse; + #endif } OSStatus HandleSysEx (const UInt8* inData, UInt32 inLength) override @@ -955,8 +956,10 @@ public: #if JucePlugin_WantsMidiInput const ScopedLock sl (incomingMidiLock); incomingEvents.addEvent (inData, (int) inLength, 0); - #endif return noErr; + #else + return kAudioUnitErr_PropertyNotInUse; + #endif } //============================================================================== @@ -1369,7 +1372,7 @@ private: JUCE_AUTORELEASEPOOL { jassert (ed != nullptr); - addAndMakeVisible (&editor); + addAndMakeVisible (editor); setOpaque (true); setVisible (true); setBroughtToFrontOnMouseClick (true); diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp index 04077c1..10dcb68 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp @@ -861,7 +861,7 @@ private: void GetNameOfLength (char* name, int maxLength, OSType inControllerType) const { // Pro-tools expects all your parameters to have valid names! - jassert (juceFilter->getParameterName (index).isNotEmpty()); + jassert (juceFilter->getParameterName (index, maxLength).isNotEmpty()); juceFilter->getParameterName (index, maxLength).copyToUTF8 (name, (size_t) maxLength + 1); } diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h b/JuceLibraryCode/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h index d0613cc..6dfcf87 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h @@ -54,7 +54,7 @@ public: { setTitleBarButtonsRequired (DocumentWindow::minimiseButton | DocumentWindow::closeButton, false); - Component::addAndMakeVisible (&optionsButton); + Component::addAndMakeVisible (optionsButton); optionsButton.addListener (this); optionsButton.setTriggeredOnMouseDown (true); diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp index 3f0012f..b38206d 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -40,13 +40,6 @@ #undef STRICT #define STRICT 1 #include - - #ifdef __MINGW32__ - struct MOUSEHOOKSTRUCTEX : public MOUSEHOOKSTRUCT - { - DWORD mouseData; - }; - #endif #elif defined (LINUX) #include #include @@ -62,12 +55,6 @@ #endif //============================================================================== -/* These files come with the Steinberg VST SDK - to get them, you'll need to - visit the Steinberg website and jump through some hoops to sign up as a - VST developer. - - Then, you'll need to make sure your include path contains your "vstsdk2.4" directory. -*/ #ifndef _MSC_VER #define __cdecl #endif @@ -80,7 +67,15 @@ #pragma clang diagnostic ignored "-Wdeprecated-writable-strings" #endif -// VSTSDK V2.4 includes.. +/* These files come with the Steinberg VST SDK - to get them, you'll need to + visit the Steinberg website and agree to whatever is currently required to + get them. The best version to get is the VST3 SDK, which also contains + the older VST2.4 files. + + Then, you'll need to make sure your include path contains your "VST SDK3" + directory (or whatever you've named it on your machine). The introjucer has + a special box for setting this path. +*/ #include #include #include @@ -181,7 +176,10 @@ namespace { if (nCode >= 0 && wParam == WM_MOUSEWHEEL) { - const MOUSEHOOKSTRUCTEX& hs = *(MOUSEHOOKSTRUCTEX*) lParam; + // using a local copy of this struct to support old mingw libraries + struct MOUSEHOOKSTRUCTEX_ : public MOUSEHOOKSTRUCT { DWORD mouseData; }; + + const MOUSEHOOKSTRUCTEX_& hs = *(MOUSEHOOKSTRUCTEX_*) lParam; if (Component* const comp = Desktop::getInstance().findComponentAt (Point (hs.pt.x, hs.pt.y))) if (comp->getWindowHandle() != 0) @@ -835,7 +833,7 @@ public: if (filter != nullptr) { jassert (isPositiveAndBelow (index, filter->getNumParameters())); - filter->getParameterText (index).copyToUTF8 (text, 24); // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. + filter->getParameterText (index, 24).copyToUTF8 (text, 24); // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. } } @@ -844,7 +842,7 @@ public: if (filter != nullptr) { jassert (isPositiveAndBelow (index, filter->getNumParameters())); - filter->getParameterName (index).copyToUTF8 (text, 16); // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. + filter->getParameterName (index, 16).copyToUTF8 (text, 16); // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. } } diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_module_info b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_module_info index adcf0d3..12bd247 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_module_info +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_module_info @@ -1,7 +1,7 @@ { "id": "juce_audio_plugin_client", "name": "JUCE audio plugin wrapper classes", - "version": "3.0.0", + "version": "3.0.1", "description": "Classes for building VST, RTAS and AU plugins.", "website": "http://www.juce.com/juce", "license": "GPL/Commercial", diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_PluginUtilities.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_PluginUtilities.cpp index e4684d6..82e7e47 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_PluginUtilities.cpp +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_PluginUtilities.cpp @@ -22,7 +22,7 @@ ============================================================================== */ -#if _MSC_VER +#if _MSC_VER || JUCE_MINGW #include #endif @@ -33,7 +33,7 @@ #include "../utility/juce_CheckSettingMacros.h" #include "juce_IncludeModuleHeaders.h" -#if _MSC_VER +#if _MSC_VER || JUCE_MINGW #if JucePlugin_Build_RTAS extern "C" BOOL WINAPI DllMainRTAS (HINSTANCE, DWORD, LPVOID); diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index b6f849f..375b2e7 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -58,7 +58,9 @@ namespace juce namespace AudioUnitFormatHelpers { - static int insideCallback = 0; + #if JUCE_DEBUG + static ThreadLocalValue insideCallback; + #endif String osTypeToString (OSType type) { @@ -136,7 +138,7 @@ namespace AudioUnitFormatHelpers fileOrIdentifier.lastIndexOfChar ('/')) + 1)); StringArray tokens; - tokens.addTokens (s, ",", String::empty); + tokens.addTokens (s, ",", String()); tokens.removeEmptyStrings(); if (tokens.size() == 3) @@ -290,7 +292,9 @@ public: { using namespace AudioUnitFormatHelpers; - ++insideCallback; + #if JUCE_DEBUG + ++*insideCallback; + #endif JUCE_AU_LOG ("Opening AU: " + fileOrIdentifier); @@ -306,14 +310,20 @@ public: } } - --insideCallback; + #if JUCE_DEBUG + --*insideCallback; + #endif } ~AudioUnitPluginInstance() { const ScopedLock sl (lock); - jassert (AudioUnitFormatHelpers::insideCallback == 0); + #if JUCE_DEBUG + // this indicates that some kind of recursive call is getting triggered that's + // deleting this plugin while it's still under construction. + jassert (AudioUnitFormatHelpers::insideCallback.get() == 0); + #endif if (eventListenerRef != 0) { @@ -570,7 +580,7 @@ public: if (isPositiveAndBelow (index, getNumInputChannels())) return "Input " + String (index + 1); - return String::empty; + return String(); } const String getOutputChannelName (int index) const override @@ -578,7 +588,7 @@ public: if (isPositiveAndBelow (index, getNumOutputChannels())) return "Output " + String (index + 1); - return String::empty; + return String(); } bool isInputChannelStereoPair (int index) const override { return isPositiveAndBelow (index, getNumInputChannels()); } @@ -652,7 +662,7 @@ public: if (const ParamInfo* p = parameters[index]) return p->name; - return String::empty; + return String(); } const String getParameterText (int index) override { return String (getParameter (index)); } @@ -1293,7 +1303,7 @@ public: : AudioProcessorEditor (&p), plugin (p) { - addAndMakeVisible (&wrapper); + addAndMakeVisible (wrapper); setOpaque (true); setVisible (true); diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp index c7f794b..d528a27 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp @@ -222,7 +222,7 @@ public: desc.lastFileModTime = module->file.getLastModificationTime(); desc.pluginFormatName = "LADSPA"; desc.category = getCategory(); - desc.manufacturerName = plugin != nullptr ? String (plugin->Maker) : String::empty; + desc.manufacturerName = plugin != nullptr ? String (plugin->Maker) : String(); desc.version = getVersion(); desc.numInputChannels = getNumInputChannels(); desc.numOutputChannels = getNumOutputChannels(); @@ -338,7 +338,7 @@ public: if (isPositiveAndBelow (index, getNumInputChannels())) return String (plugin->PortNames [inputs [index]]).trim(); - return String::empty; + return String(); } const String getOutputChannelName (const int index) const @@ -346,7 +346,7 @@ public: if (isPositiveAndBelow (index, getNumInputChannels())) return String (plugin->PortNames [outputs [index]]).trim(); - return String::empty; + return String(); } //============================================================================== @@ -390,7 +390,7 @@ public: return String (plugin->PortNames [parameters [index]]).trim(); } - return String::empty; + return String(); } const String getParameterText (int index) @@ -407,7 +407,7 @@ public: return String (parameterValues[index].scaled, 4); } - return String::empty; + return String(); } //============================================================================== @@ -424,7 +424,7 @@ public: const String getProgramName (int index) { // XXX - return String::empty; + return String(); } void changeProgramName (int index, const String& newName) diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 572f423..d162906 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -40,11 +40,17 @@ #pragma clang diagnostic ignored "-Wunused-parameter" #pragma clang diagnostic ignored "-Wconversion" #pragma clang diagnostic ignored "-Woverloaded-virtual" + #pragma clang diagnostic ignored "-Wshadow" #endif -// Got an include error here? If so, you'll need to install the VST3 SDK somewhere, -// and use the introjucer or your IDE to add it to your include path. The introjucer -// has a special box for specifying this path for each export target. +/* 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 "VST SDK3" + directory (or whatever you've named it on your machine). The introjucer has + a special box for setting this path. +*/ #include #include #include @@ -85,11 +91,12 @@ #undef DBPRT3 #undef DBPRT4 #undef DBPRT5 +#undef calloc #undef free #undef malloc +#undef realloc #undef NEW #undef NEWVEC -#undef realloc #undef VERIFY #undef VERIFY_IS #undef VERIFY_NOT @@ -98,6 +105,7 @@ #undef SINGLE_CREATE_FUNC #undef _META_CLASS #undef _META_CLASS_IFACE +#undef _META_CLASS_SINGLE #undef META_CLASS #undef META_CLASS_IFACE #undef META_CLASS_SINGLE @@ -306,8 +314,8 @@ static void activateAllBussesOfType (Vst::IComponent* component, } //============================================================================== -/** Assigns an AudioSampleBuffer's channels to an AudioBusBuffers' */ -static void associateBufferTo (Vst::AudioBusBuffers& vstBuffers, const AudioSampleBuffer& buffer) noexcept +/** Assigns a complete AudioSampleBuffer's channels to an AudioBusBuffers' */ +static void associateWholeBufferTo (Vst::AudioBusBuffers& vstBuffers, const AudioSampleBuffer& buffer) noexcept { vstBuffers.channelBuffers32 = buffer.getArrayOfChannels(); vstBuffers.numChannels = buffer.getNumChannels(); @@ -566,30 +574,36 @@ public: for (Steinberg::int32 i = 0; i < eventList.getEventCount(); ++i) { - Event event; + Event e; - if (eventList.getEvent (i, event) == kResultOk) + if (eventList.getEvent (i, e) == kResultOk) { - switch (event.type) + switch (e.type) { case Event::kNoteOnEvent: - result.addEvent (MidiMessage::noteOn (event.noteOn.channel + 1, event.noteOn.pitch, (uint8) (event.noteOn.velocity * 127.0f)), - event.sampleOffset); + result.addEvent (MidiMessage::noteOn (createSafeChannel (e.noteOn.channel), + createSafeNote (e.noteOn.pitch), + (uint8) denormaliseToMidiValue (e.noteOn.velocity)), + e.sampleOffset); break; case Event::kNoteOffEvent: - result.addEvent (MidiMessage::noteOff (event.noteOff.channel + 1, event.noteOff.pitch, (uint8) (event.noteOff.velocity * 127.0f)), - event.sampleOffset); + result.addEvent (MidiMessage::noteOff (createSafeChannel (e.noteOff.channel), + createSafeNote (e.noteOff.pitch), + (uint8) denormaliseToMidiValue (e.noteOff.velocity)), + e.sampleOffset); break; case Event::kPolyPressureEvent: - result.addEvent (MidiMessage::aftertouchChange (event.polyPressure.channel + 1, event.polyPressure.pitch, (int) (event.polyPressure.pressure * 127.0f)), - event.sampleOffset); + result.addEvent (MidiMessage::aftertouchChange (createSafeChannel (e.polyPressure.channel), + createSafeNote (e.polyPressure.pitch), + denormaliseToMidiValue (e.polyPressure.pressure)), + e.sampleOffset); break; case Event::kDataEvent: - result.addEvent (MidiMessage::createSysExMessage (event.data.bytes, event.data.size), - event.sampleOffset); + result.addEvent (MidiMessage::createSysExMessage (e.data.bytes, e.data.size), + e.sampleOffset); break; default: @@ -607,42 +621,58 @@ public: MidiMessage msg; int midiEventPosition = 0; + enum { maxNumEvents = 2048 }; // Steinberg's Host Checker states no more than 2048 events are allowed at once + int numEvents = 0; + while (iterator.getNextEvent (msg, midiEventPosition)) { - Event event = { 0 }; + if (++numEvents > maxNumEvents) + break; + + Event e = { 0 }; if (msg.isNoteOn()) { - event.type = Event::kNoteOnEvent; - event.noteOn.channel = (Steinberg::int16) msg.getChannel() - 1; - event.noteOn.pitch = (Steinberg::int16) msg.getNoteNumber(); - event.noteOn.velocity = msg.getVelocity() / 127.0f; + e.type = Event::kNoteOnEvent; + e.noteOn.channel = createSafeChannel (msg.getChannel()); + e.noteOn.pitch = createSafeNote (msg.getNoteNumber()); + e.noteOn.velocity = normaliseMidiValue (msg.getVelocity()); + e.noteOn.length = 0; + e.noteOn.tuning = 0.0f; + e.noteOn.noteId = -1; } else if (msg.isNoteOff()) { - event.type = Event::kNoteOffEvent; - event.noteOff.channel = (Steinberg::int16) msg.getChannel() - 1; - event.noteOff.pitch = (Steinberg::int16) msg.getNoteNumber(); - event.noteOff.velocity = msg.getVelocity() / 127.0f; + e.type = Event::kNoteOffEvent; + e.noteOff.channel = createSafeChannel (msg.getChannel()); + e.noteOff.pitch = createSafeNote (msg.getNoteNumber()); + e.noteOff.velocity = normaliseMidiValue (msg.getVelocity()); + e.noteOff.tuning = 0.0f; + e.noteOff.noteId = -1; } else if (msg.isSysEx()) { - event.type = Event::kDataEvent; - event.data.bytes = msg.getSysExData(); - event.data.size = msg.getSysExDataSize(); + e.type = Event::kDataEvent; + e.data.bytes = msg.getSysExData(); + e.data.size = msg.getSysExDataSize(); + e.data.type = DataEvent::kMidiSysEx; } else if (msg.isAftertouch()) { - event.type = Event::kPolyPressureEvent; - event.polyPressure.channel = (Steinberg::int16) msg.getChannel() - 1; - event.polyPressure.pitch = (Steinberg::int16) msg.getNoteNumber(); - event.polyPressure.pressure = msg.getAfterTouchValue() / 127.0f; + e.type = Event::kPolyPressureEvent; + e.polyPressure.channel = createSafeChannel (msg.getChannel()); + e.polyPressure.pitch = createSafeNote (msg.getNoteNumber()); + e.polyPressure.pressure = normaliseMidiValue (msg.getAfterTouchValue()); + } + else + { + continue; } - event.busIndex = 0; - event.sampleOffset = midiEventPosition; + e.busIndex = 0; + e.sampleOffset = midiEventPosition; - result.addEvent (event); + result.addEvent (e); } } @@ -650,6 +680,15 @@ private: Array events; Atomic refCount; + static Steinberg::int16 createSafeChannel (int channel) noexcept { return (Steinberg::int16) jlimit (0, 15, channel - 1); } + static int createSafeChannel (Steinberg::int16 channel) noexcept { return (int) jlimit (1, 16, channel + 1); } + + static Steinberg::int16 createSafeNote (int note) noexcept { return (Steinberg::int16) jlimit (0, 127, note); } + static int createSafeNote (Steinberg::int16 note) noexcept { return jlimit (0, 127, (int) note); } + + static float normaliseMidiValue (int value) noexcept { return jlimit (0.0f, 1.0f, (float) value / 127.0f); } + static int denormaliseToMidiValue (float value) noexcept { return roundToInt (jlimit (0.0f, 127.0f, value * 127.0f)); } + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiEventList) }; @@ -1448,16 +1487,16 @@ private: } //============================================================================== - bool open (const File& file, const PluginDescription& description) + bool open (const File& f, const PluginDescription& description) { - dllHandle = new DLLHandle (file.getFullPathName()); + dllHandle = new DLLHandle (f.getFullPathName()); ComSmartPtr pluginFactory (dllHandle->getPluginFactory()); ComSmartPtr host (new VST3HostContext (nullptr)); MatchingDescriptionFinder finder (host, pluginFactory, description); - const Result result (finder.findDescriptionsAndPerform (file)); + const Result result (finder.findDescriptionsAndPerform (f)); if (result.getErrorMessage() == MatchingDescriptionFinder::getSuccessString()) { @@ -1506,14 +1545,8 @@ public: #endif } - Steinberg::uint32 PLUGIN_API addRef() override { return 1; } - Steinberg::uint32 PLUGIN_API release() override { return 1; } - - tresult PLUGIN_API queryInterface (const TUID, void** obj) override - { - *obj = nullptr; - return kNotImplemented; - } + JUCE_DECLARE_VST3_COM_REF_METHODS + JUCE_DECLARE_VST3_COM_QUERY_METHODS void paint (Graphics& g) override { @@ -1602,13 +1635,15 @@ public: private: //============================================================================== + Atomic refCount; ComSmartPtr view; #if JUCE_WINDOWS - struct ChildComponent : public Component + class ChildComponent : public Component { + public: ChildComponent() {} - void paint (Graphics& g) { g.fillAll (Colours::cornflowerblue); } + void paint (Graphics& g) override { g.fillAll (Colours::cornflowerblue); } using Component::createNewPeer; @@ -1652,7 +1687,7 @@ private: pluginHandle = (HandleFormat) peer->getNativeHandle(); #elif JUCE_MAC dummyComponent.setBounds (getBounds().withZeroOrigin()); - addAndMakeVisible (&dummyComponent); + addAndMakeVisible (dummyComponent); pluginHandle = [[NSView alloc] init]; dummyComponent.setView (pluginHandle); #endif @@ -1676,9 +1711,9 @@ class VST3PluginInstance : public AudioPluginInstance public: VST3PluginInstance (const VST3ModuleHandle::Ptr& handle) : module (handle), - result (1, 1), numInputAudioBusses (0), numOutputAudioBusses (0), + resultBuffer (1, 1), inputParameterChanges (new ParameterChangeList()), outputParameterChanges (new ParameterChangeList()), midiInputs (new MidiEventList()), @@ -1761,6 +1796,62 @@ public: return module != nullptr ? module->name : String::empty; } + typedef Array > BusMap; + + /** Assigns a series of AudioSampleBuffer's channels to an AudioBusBuffers' + + @warning For speed, does not check the channel count and offsets + according to the AudioSampleBuffer + */ + void associateBufferTo (Vst::AudioBusBuffers& vstBuffers, + BusMap& busMap, + const AudioSampleBuffer& buffer, + int numChannels, int channelStartOffset, + int sampleOffset = 0) noexcept + { + const int channelEnd = numChannels + channelStartOffset; + jassert (channelEnd >= 0 && channelEnd <= buffer.getNumChannels()); + + busMap.add (Array()); + Array& chans = busMap.getReference (busMap.size() - 1); + + for (int i = channelStartOffset; i < channelEnd; ++i) + chans.add (buffer.getSampleData (i, sampleOffset)); + + vstBuffers.channelBuffers32 = chans.getRawDataPointer(); + vstBuffers.numChannels = numChannels; + vstBuffers.silenceFlags = 0; + } + + void mapAudioSampleBufferToBusses (Array& result, + AudioSampleBuffer& source, + int numBusses, bool isInput) + { + result.clearQuick(); + + BusMap& busMapToUse = isInput ? inputBusMap : outputBusMap; + busMapToUse.clearQuick(); + + int channelIndexOffset = 0; + + for (int i = 0; i < numBusses; ++i) + { + Vst::SpeakerArrangement arrangement = 0; + processor->getBusArrangement (isInput ? Vst::kInput : Vst::kOutput, + (Steinberg::int32) i, arrangement); + + const int numChansForBus = BigInteger ((int64) arrangement).countNumberOfSetBits(); + + result.add (Vst::AudioBusBuffers()); + + associateBufferTo (result.getReference (i), busMapToUse, source, + BigInteger ((int64) arrangement).countNumberOfSetBits(), + channelIndexOffset); + + channelIndexOffset += numChansForBus; + } + } + void prepareToPlay (double sampleRate, int estimatedSamplesPerBlock) override { using namespace Vst; @@ -1777,6 +1868,8 @@ public: setup.sampleRate = sampleRate; setup.processMode = isNonRealtime() ? kOffline : kRealtime; + resultBuffer.setSize (numOutputs, estimatedSamplesPerBlock, false, true, true); + warnOnFailure (processor->setupProcessing (setup)); if (! isComponentInitialised) @@ -1787,28 +1880,28 @@ public: warnOnFailure (component->setActive (true)); warnOnFailure (processor->setProcessing (true)); - result.setSize (numOutputs, estimatedSamplesPerBlock, false, true, true); - Array inArrangements, outArrangements; fillWithCorrespondingSpeakerArrangements (inArrangements, numInputs); fillWithCorrespondingSpeakerArrangements (outArrangements, numOutputs); - warnOnFailure (processor->setBusArrangements (inArrangements.getRawDataPointer(), - getNumSingleDirectionBussesFor (component, true, true), - outArrangements.getRawDataPointer(), - getNumSingleDirectionBussesFor (component, false, true))); + warnOnFailure (processor->setBusArrangements (inArrangements.getRawDataPointer(), numInputAudioBusses, + outArrangements.getRawDataPointer(), numOutputAudioBusses)); } void releaseResources() override { - result.setSize (1, 1, false, true, true); + JUCE_TRY + { + resultBuffer.setSize (1, 1, false, true, true); - if (processor != nullptr) - processor->setProcessing (false); + if (processor != nullptr) + processor->setProcessing (false); - if (component != nullptr) - component->setActive (false); + if (component != nullptr) + component->setActive (false); + } + JUCE_CATCH_ALL_ASSERT } void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override @@ -1823,8 +1916,8 @@ public: ProcessData data; data.processMode = isNonRealtime() ? kOffline : kRealtime; data.symbolicSampleSize = kSample32; - data.numInputs = 1; // Number of busses, not channels! - data.numOutputs = 1; // Number of busses, not channels! + data.numInputs = numInputAudioBusses; + data.numOutputs = numOutputAudioBusses; data.inputParameterChanges = inputParameterChanges; data.outputParameterChanges = outputParameterChanges; data.numSamples = (Steinberg::int32) numSamples; @@ -2057,14 +2150,17 @@ private: mutable ComSmartPtr view; - AudioSampleBuffer result; - Vst::AudioBusBuffers inputs, outputs; - - // The number of IO busses MUST match that of the plugin's, as very poorly specified by the Steinberg SDK + /** The number of IO busses 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; + AudioSampleBuffer resultBuffer; + BusMap inputBusMap, outputBusMap; + Array inputBusses, outputBusses; //============================================================================== - template + template static void appendStateFrom (XmlElement& head, ComSmartPtr& object, const String& identifier) { if (object != nullptr) @@ -2133,10 +2229,10 @@ private: { jassert (numClasses >= 0); // The plugin must provide at least an IComponent and IEditController! - for (Steinberg::int32 i = 0; i < numClasses; ++i) + for (Steinberg::int32 j = 0; j < numClasses; ++j) { info = new PClassInfo(); - factory->getClassInfo (i, info); + factory->getClassInfo (j, info); if (std::strcmp (info->category, kVstAudioEffectClass) != 0) continue; @@ -2153,7 +2249,7 @@ private: if (pf2.loadFrom (factory)) { info2 = new PClassInfo2(); - pf2->getClassInfo2 (i, info2); + pf2->getClassInfo2 (j, info2); } else { @@ -2164,7 +2260,7 @@ private: { pf3->setHostContext (host->getFUnknown()); infoW = new PClassInfoW(); - pf3->getClassInfoUnicode (i, infoW); + pf3->getClassInfoUnicode (j, infoW); } else { @@ -2251,7 +2347,8 @@ private: Steinberg::MemoryStream stream; if (component->getState (&stream) == kResultTrue) - warnOnFailure (editController->setComponentState (&stream)); + if (stream.seek (0, Steinberg::IBStream::kIBSeekSet, nullptr) == kResultTrue) + warnOnFailure (editController->setComponentState (&stream)); } void grabInformationObjects() @@ -2304,6 +2401,7 @@ private: component->getBusInfo (forAudio ? Vst::kAudio : Vst::kEvent, forInput ? Vst::kInput : Vst::kOutput, (Steinberg::int32) index, busInfo); + return busInfo; } @@ -2322,23 +2420,15 @@ private: } //============================================================================== - struct AudioBusBuffersWrapper - { - AudioBusBuffersWrapper() {} - ~AudioBusBuffersWrapper() {} - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioBusBuffersWrapper) - }; - void associateTo (Vst::ProcessData& destination, AudioSampleBuffer& buffer) { - result.clear(); + resultBuffer.clear(); - associateBufferTo (inputs, buffer); - associateBufferTo (outputs, result); + mapAudioSampleBufferToBusses (inputBusses, buffer, numInputAudioBusses, true); + mapAudioSampleBufferToBusses (outputBusses, resultBuffer, numOutputAudioBusses, false); - destination.inputs = &inputs; - destination.outputs = &outputs; + destination.inputs = inputBusses.getRawDataPointer(); + destination.outputs = outputBusses.getRawDataPointer(); } void associateTo (Vst::ProcessData& destination, MidiBuffer& midiBuffer) @@ -2360,22 +2450,22 @@ private: Vst::ParameterInfo getParameterInfoForIndex (int index) const { - Vst::ParameterInfo info = { 0 }; + Vst::ParameterInfo paramInfo = { 0 }; if (processor != nullptr) - editController->getParameterInfo (index, info); + editController->getParameterInfo (index, paramInfo); - return info; + return paramInfo; } Vst::ProgramListInfo getProgramListInfo (int index) const { - Vst::ProgramListInfo info = { 0 }; + Vst::ProgramListInfo paramInfo = { 0 }; if (unitInfo != nullptr) - unitInfo->getProgramListInfo (index, info); + unitInfo->getProgramListInfo (index, paramInfo); - return info; + return paramInfo; } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginInstance) diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h index c637eba..308ff5a 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h @@ -68,5 +68,5 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginFormat) }; -#endif //JUCE_PLUGINHOST_VST3 -#endif //JUCE_VST3PLUGINFORMAT_H_INCLUDED \ No newline at end of file +#endif // JUCE_PLUGINHOST_VST3 +#endif // JUCE_VST3PLUGINFORMAT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h index 3337272..f442eab 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h @@ -22,12 +22,11 @@ ============================================================================== */ -#ifdef __aeffect__ +#ifdef __aeffect__ // NB: this must come first, *before* the header-guard. #ifndef JUCE_VSTMIDIEVENTLIST_H_INCLUDED #define JUCE_VSTMIDIEVENTLIST_H_INCLUDED - //============================================================================== /** Holds a set of VSTMidiEvent objects and makes it easy to add events to the list. @@ -184,6 +183,5 @@ private: } }; - #endif // JUCE_VSTMIDIEVENTLIST_H_INCLUDED #endif diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index e0cc66c..dec1a37 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -1831,17 +1831,22 @@ private: int versionBits[32]; int n = 0; - while (v != 0) + for (int vv = v; vv != 0; vv /= 10) + versionBits [n++] = vv % 10; + + if (n > 4) // if the number ends up silly, it's probably encoded as hex instead of decimal.. { - versionBits [n++] = v % 10; - v /= 10; - } + n = 0; - s << 'V'; + for (int vv = v; vv != 0; vv >>= 8) + versionBits [n++] = vv & 255; + } while (n > 1 && versionBits [n - 1] == 0) --n; + s << 'V'; + while (n > 0) { s << versionBits [--n]; diff --git a/JuceLibraryCode/modules/juce_audio_processors/juce_module_info b/JuceLibraryCode/modules/juce_audio_processors/juce_module_info index e598809..34124f6 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/juce_module_info +++ b/JuceLibraryCode/modules/juce_audio_processors/juce_module_info @@ -1,7 +1,7 @@ { "id": "juce_audio_processors", "name": "JUCE audio plugin hosting classes", - "version": "3.0.0", + "version": "3.0.1", "description": "Classes for loading and playing VST, AU, or internally-generated audio processors.", "website": "http://www.juce.com/juce", "license": "GPL/Commercial", diff --git a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index 8f4f43a..501fedf 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -201,7 +201,7 @@ void AudioProcessor::updateHostDisplay() l->audioProcessorChanged (this); } -String AudioProcessor::getParameterLabel (int) const { return String::empty; } +String AudioProcessor::getParameterLabel (int) const { return String(); } bool AudioProcessor::isParameterAutomatable (int) const { return true; } bool AudioProcessor::isMetaParameter (int) const { return false; } @@ -266,7 +266,7 @@ void AudioProcessor::copyXmlToBinary (const XmlElement& xml, juce::MemoryBlock& MemoryOutputStream out (destData, false); out.writeInt (magicXmlNumber); out.writeInt (0); - xml.writeToStream (out, String::empty, true, false); + xml.writeToStream (out, String(), true, false); out.writeByte (0); } diff --git a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index e965b22..c1f1929 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -1367,7 +1367,7 @@ const String AudioProcessorGraph::AudioGraphIOProcessor::getName() const default: break; } - return String::empty; + return String(); } void AudioProcessorGraph::AudioGraphIOProcessor::fillInPluginDescription (PluginDescription& d) const @@ -1469,7 +1469,7 @@ const String AudioProcessorGraph::AudioGraphIOProcessor::getInputChannelName (in default: break; } - return String::empty; + return String(); } const String AudioProcessorGraph::AudioGraphIOProcessor::getOutputChannelName (int channelIndex) const @@ -1481,7 +1481,7 @@ const String AudioProcessorGraph::AudioGraphIOProcessor::getOutputChannelName (i default: break; } - return String::empty; + return String(); } bool AudioProcessorGraph::AudioGraphIOProcessor::isInputChannelStereoPair (int /*index*/) const @@ -1501,17 +1501,17 @@ bool AudioProcessorGraph::AudioGraphIOProcessor::hasEditor() const AudioProcessorEditor* AudioProcessorGraph::AudioGraphIOProcessor::createEditor() { return nullptr; } int AudioProcessorGraph::AudioGraphIOProcessor::getNumParameters() { return 0; } -const String AudioProcessorGraph::AudioGraphIOProcessor::getParameterName (int) { return String::empty; } +const String AudioProcessorGraph::AudioGraphIOProcessor::getParameterName (int) { return String(); } float AudioProcessorGraph::AudioGraphIOProcessor::getParameter (int) { return 0.0f; } -const String AudioProcessorGraph::AudioGraphIOProcessor::getParameterText (int) { return String::empty; } +const String AudioProcessorGraph::AudioGraphIOProcessor::getParameterText (int) { return String(); } void AudioProcessorGraph::AudioGraphIOProcessor::setParameter (int, float) { } int AudioProcessorGraph::AudioGraphIOProcessor::getNumPrograms() { return 0; } int AudioProcessorGraph::AudioGraphIOProcessor::getCurrentProgram() { return 0; } void AudioProcessorGraph::AudioGraphIOProcessor::setCurrentProgram (int) { } -const String AudioProcessorGraph::AudioGraphIOProcessor::getProgramName (int) { return String::empty; } +const String AudioProcessorGraph::AudioGraphIOProcessor::getProgramName (int) { return String(); } void AudioProcessorGraph::AudioGraphIOProcessor::changeProgramName (int, const String&) {} void AudioProcessorGraph::AudioGraphIOProcessor::getStateInformation (juce::MemoryBlock&) {} diff --git a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h index bf22670..7deb9b5 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h +++ b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h @@ -372,15 +372,15 @@ public: AudioProcessorEditor* createEditor() { return nullptr; } int getNumParameters() { return 0; } - const String getParameterName (int) { return String::empty; } + const String getParameterName (int) { return String(); } float getParameter (int) { return 0; } - const String getParameterText (int) { return String::empty; } + const String getParameterText (int) { return String(); } void setParameter (int, float) { } int getNumPrograms() { return 0; } int getCurrentProgram() { return 0; } void setCurrentProgram (int) { } - const String getProgramName (int) { return String::empty; } + const String getProgramName (int) { return String(); } void changeProgramName (int, const String&) { } void getStateInformation (juce::MemoryBlock&); diff --git a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp index 1b584e8..4b9da9b 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp @@ -35,7 +35,7 @@ public: slider (p, index_) { startTimer (100); - addAndMakeVisible (&slider); + addAndMakeVisible (slider); owner.addListener (this); } @@ -123,7 +123,7 @@ GenericAudioProcessorEditor::GenericAudioProcessorEditor (AudioProcessor* const jassert (p != nullptr); setOpaque (true); - addAndMakeVisible (&panel); + addAndMakeVisible (panel); Array params; diff --git a/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp b/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp index ed58205..2ea3c98 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp @@ -241,7 +241,8 @@ void KnownPluginList::clearBlacklistedFiles() //============================================================================== struct PluginSorter { - PluginSorter (KnownPluginList::SortMethod sortMethod) noexcept : method (sortMethod) {} + PluginSorter (KnownPluginList::SortMethod sortMethod, bool forwards) noexcept + : method (sortMethod), direction (forwards ? 1 : -1) {} int compareElements (const PluginDescription* const first, const PluginDescription* const second) const @@ -252,6 +253,7 @@ struct PluginSorter { case KnownPluginList::sortByCategory: diff = first->category.compareLexicographically (second->category); break; case KnownPluginList::sortByManufacturer: diff = first->manufacturerName.compareLexicographically (second->manufacturerName); break; + case KnownPluginList::sortByFormat: diff = first->pluginFormatName.compare (second->pluginFormatName); break; case KnownPluginList::sortByFileSystemLocation: diff = lastPathPart (first->fileOrIdentifier).compare (lastPathPart (second->fileOrIdentifier)); break; default: break; } @@ -259,7 +261,7 @@ struct PluginSorter if (diff == 0) diff = first->name.compareLexicographically (second->name); - return diff; + return diff * direction; } private: @@ -268,14 +270,17 @@ private: return path.replaceCharacter ('\\', '/').upToLastOccurrenceOf ("/", false, false); } - KnownPluginList::SortMethod method; + const KnownPluginList::SortMethod method; + const int direction; + + JUCE_DECLARE_NON_COPYABLE (PluginSorter) }; -void KnownPluginList::sort (const SortMethod method) +void KnownPluginList::sort (const SortMethod method, bool forwards) { if (method != defaultOrder) { - PluginSorter sorter (method); + PluginSorter sorter (method, forwards); types.sort (sorter, true); sendChangeMessage(); @@ -477,7 +482,7 @@ KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortM Array sorted; { - PluginSorter sorter (sortMethod); + PluginSorter sorter (sortMethod, true); for (int i = 0; i < types.size(); ++i) sorted.addSorted (sorter, types.getUnchecked(i)); @@ -485,7 +490,7 @@ KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortM PluginTree* tree = new PluginTree(); - if (sortMethod == sortByCategory || sortMethod == sortByManufacturer) + if (sortMethod == sortByCategory || sortMethod == sortByManufacturer || sortMethod == sortByFormat) { PluginTreeUtils::buildTreeByCategory (*tree, sorted, sortMethod); } diff --git a/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_KnownPluginList.h b/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_KnownPluginList.h index b66c7d6..5f60643 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_KnownPluginList.h +++ b/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_KnownPluginList.h @@ -132,6 +132,7 @@ public: sortAlphabetically, sortByCategory, sortByManufacturer, + sortByFormat, sortByFileSystemLocation }; @@ -143,7 +144,7 @@ public: Use getIndexChosenByMenu() to find out the type that was chosen. */ - void addToMenu (PopupMenu& menu, const SortMethod sortMethod) const; + void addToMenu (PopupMenu& menu, SortMethod sortMethod) 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. @@ -153,7 +154,7 @@ public: //============================================================================== /** Sorts the list. */ - void sort (const SortMethod method); + void sort (SortMethod method, bool forwards); //============================================================================== /** Creates some XML that can be used to store the state of this list. */ @@ -195,7 +196,7 @@ public: private: //============================================================================== - OwnedArray types; + OwnedArray types; StringArray blacklist; ScopedPointer scanner; CriticalSection scanLock; diff --git a/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp b/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp index 24eb6d2..5502f00 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp @@ -120,7 +120,7 @@ bool PluginDirectoryScanner::skipNextFile() void PluginDirectoryScanner::setDeadMansPedalFile (const StringArray& newContents) { - if (deadMansPedalFile != File::nonexistent) + if (deadMansPedalFile.getFullPathName().isNotEmpty()) deadMansPedalFile.replaceWithText (newContents.joinIntoString ("\n"), true, true); } diff --git a/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp b/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp index 570263c..8112277 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp @@ -22,10 +22,116 @@ ============================================================================== */ -PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, - KnownPluginList& listToEdit, - const File& deadMansPedal, - PropertiesFile* const props) +class PluginListComponent::TableModel : public TableListBoxModel +{ +public: + TableModel (PluginListComponent& c, KnownPluginList& l) : owner (c), list (l) {} + + int getNumRows() override + { + return list.getNumTypes() + list.getBlacklistedFiles().size(); + } + + void paintRowBackground (Graphics& g, int /*rowNumber*/, int /*width*/, int /*height*/, bool rowIsSelected) override + { + if (rowIsSelected) + g.fillAll (owner.findColour (TextEditor::highlightColourId)); + } + + enum + { + nameCol = 1, + typeCol = 2, + categoryCol = 3, + manufacturerCol = 4, + descCol = 5 + }; + + void paintCell (Graphics& g, int row, int columnId, int width, int height, bool /*rowIsSelected*/) override + { + String text; + bool isBlacklisted = row >= list.getNumTypes(); + + if (isBlacklisted) + { + if (columnId == nameCol) + text = list.getBlacklistedFiles() [row - list.getNumTypes()]; + else if (columnId == descCol) + text = TRANS("Deactivated after failing to initialise correctly"); + } + else if (const PluginDescription* const desc = list.getType (row)) + { + switch (columnId) + { + case nameCol: text = desc->name; break; + case typeCol: text = desc->pluginFormatName; break; + case categoryCol: text = desc->category.isNotEmpty() ? desc->category : "-"; break; + case manufacturerCol: text = desc->manufacturerName; break; + case descCol: text = getPluginDescription (*desc); break; + + default: jassertfalse; break; + } + } + + if (text.isNotEmpty()) + { + g.setColour (isBlacklisted ? Colours::red + : columnId == nameCol ? Colours::black + : Colours::grey); + g.setFont (Font (height * 0.7f, Font::bold)); + g.drawFittedText (text, 4, 0, width - 6, height, Justification::centredLeft, 1, 0.9f); + } + } + + void deleteKeyPressed (int lastRowSelected) override + { + removePluginItem (list, lastRowSelected); + } + + void sortOrderChanged (int newSortColumnId, bool isForwards) override + { + switch (newSortColumnId) + { + case nameCol: list.sort (KnownPluginList::sortAlphabetically, isForwards); break; + case typeCol: list.sort (KnownPluginList::sortByFormat, isForwards); break; + case categoryCol: list.sort (KnownPluginList::sortByCategory, isForwards); break; + case manufacturerCol: list.sort (KnownPluginList::sortByManufacturer, isForwards); break; + case descCol: break; + + default: jassertfalse; break; + } + } + + static void removePluginItem (KnownPluginList& list, int index) + { + if (index < list.getNumTypes()) + list.removeType (index); + else + list.removeFromBlacklist (list.getBlacklistedFiles() [index - list.getNumTypes()]); + } + + static String getPluginDescription (const PluginDescription& desc) + { + StringArray items; + + if (desc.descriptiveName != desc.name) + items.add (desc.descriptiveName); + + items.add (desc.version); + + items.removeEmptyStrings(); + return items.joinIntoString (" - "); + } + + PluginListComponent& owner; + KnownPluginList& list; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableModel) +}; + +//============================================================================== +PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, KnownPluginList& listToEdit, + const File& deadMansPedal, PropertiesFile* const props) : formatManager (manager), list (listToEdit), deadMansPedalFile (deadMansPedal), @@ -33,10 +139,22 @@ PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, propertiesToUse (props), numThreads (0) { - listBox.setModel (this); - addAndMakeVisible (&listBox); + tableModel = new TableModel (*this, listToEdit); + + TableHeaderComponent& header = table.getHeader(); + + header.addColumn (TRANS("Name"), TableModel::nameCol, 200, 100, 700, TableHeaderComponent::defaultFlags | TableHeaderComponent::sortedForwards); + header.addColumn (TRANS("Format"), TableModel::typeCol, 80, 80, 80, TableHeaderComponent::notResizable); + header.addColumn (TRANS("Category"), TableModel::categoryCol, 100, 100, 200); + header.addColumn (TRANS("Manufacturer"), TableModel::manufacturerCol, 200, 100, 300); + header.addColumn (TRANS("Description"), TableModel::descCol, 300, 100, 500, TableHeaderComponent::notSortable); + + table.setHeaderHeight (22); + table.setRowHeight (20); + table.setModel (tableModel); + addAndMakeVisible (table); - addAndMakeVisible (&optionsButton); + addAndMakeVisible (optionsButton); optionsButton.addListener (this); optionsButton.setTriggeredOnMouseDown (true); @@ -66,9 +184,13 @@ void PluginListComponent::setNumberOfThreadsForScanning (int num) void PluginListComponent::resized() { - listBox.setBounds (0, 0, getWidth(), getHeight() - 30); + Rectangle r (getLocalBounds().reduced (2)); + + optionsButton.setBounds (r.removeFromBottom (24)); optionsButton.changeWidthToFitText (24); - optionsButton.setTopLeftPosition (0, getHeight() - 28); + + r.removeFromBottom (3); + table.setBounds (r); } void PluginListComponent::changeListenerCallback (ChangeBroadcaster*) @@ -78,89 +200,22 @@ void PluginListComponent::changeListenerCallback (ChangeBroadcaster*) void PluginListComponent::updateList() { - listBox.updateContent(); - listBox.repaint(); -} - -int PluginListComponent::getNumRows() -{ - return list.getNumTypes() + list.getBlacklistedFiles().size(); -} - -void PluginListComponent::paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) -{ - if (rowIsSelected) - g.fillAll (findColour (TextEditor::highlightColourId)); - - String name, desc; - bool isBlacklisted = false; - - if (row >= list.getNumTypes()) - { - isBlacklisted = true; - name = list.getBlacklistedFiles() [row - list.getNumTypes()]; - desc = TRANS("Deactivated after failing to initialise correctly"); - } - else if (const PluginDescription* const pd = list.getType (row)) - { - name = pd->name; - - desc << pd->pluginFormatName - << (pd->isInstrument ? " instrument" : " effect") - << " - " << pd->numInputChannels << (pd->numInputChannels == 1 ? " in" : " ins") - << " / " << pd->numOutputChannels << (pd->numOutputChannels == 1 ? " out" : " outs"); - - if (pd->manufacturerName.isNotEmpty()) desc << " - " << pd->manufacturerName; - if (pd->version.isNotEmpty()) desc << " - " << pd->version; - if (pd->category.isNotEmpty()) desc << " - category: '" << pd->category << '\''; - } - - if (name.isNotEmpty()) - { - GlyphArrangement ga; - ga.addCurtailedLineOfText (Font (height * 0.7f, Font::bold), - name, 8.0f, height * 0.8f, width - 10.0f, true); - - g.setColour (isBlacklisted ? Colours::red : Colours::black); - ga.draw (g); - - const Rectangle bb (ga.getBoundingBox (0, -1, false)); - - ga.clear(); - ga.addCurtailedLineOfText (Font (height * 0.6f), desc, - jmax (bb.getRight() + 10.0f, width / 3.0f), height * 0.8f, - width - bb.getRight() - 12.0f, true); - - g.setColour (isBlacklisted ? Colours::red : Colours::grey); - ga.draw (g); - } -} - -static void removePluginItem (KnownPluginList& list, int index) -{ - if (index < list.getNumTypes()) - list.removeType (index); - else - list.removeFromBlacklist (list.getBlacklistedFiles() [index - list.getNumTypes()]); -} - -void PluginListComponent::deleteKeyPressed (int lastRowSelected) -{ - removePluginItem (list, lastRowSelected); + table.updateContent(); + table.repaint(); } void PluginListComponent::removeSelected() { - const SparseSet selected (listBox.getSelectedRows()); + const SparseSet selected (table.getSelectedRows()); for (int i = list.getNumTypes(); --i >= 0;) if (selected.contains (i)) - removePluginItem (list, i); + TableModel::removePluginItem (list, i); } bool PluginListComponent::canShowSelectedFolder() const { - if (const PluginDescription* const desc = list.getType (listBox.getSelectedRow())) + if (const PluginDescription* const desc = list.getType (table.getSelectedRow())) return File::createFileWithoutCheckingPath (desc->fileOrIdentifier).exists(); return false; @@ -169,7 +224,7 @@ bool PluginListComponent::canShowSelectedFolder() const void PluginListComponent::showSelectedFolder() { if (canShowSelectedFolder()) - if (const PluginDescription* const desc = list.getType (listBox.getSelectedRow())) + if (const PluginDescription* const desc = list.getType (table.getSelectedRow())) File (desc->fileOrIdentifier).getParentDirectory().startAsProcess(); } @@ -192,12 +247,9 @@ void PluginListComponent::optionsMenuCallback (int result) { case 0: break; case 1: list.clear(); break; - case 2: list.sort (KnownPluginList::sortAlphabetically); break; - case 3: list.sort (KnownPluginList::sortByCategory); break; - case 4: list.sort (KnownPluginList::sortByManufacturer); break; - case 5: removeSelected(); break; - case 6: showSelectedFolder(); break; - case 7: removeMissingPlugins(); break; + case 2: removeSelected(); break; + case 3: showSelectedFolder(); break; + case 4: removeMissingPlugins(); break; default: if (AudioPluginFormat* format = formatManager.getFormat (result - 10)) @@ -213,13 +265,9 @@ void PluginListComponent::buttonClicked (Button* button) { PopupMenu menu; menu.addItem (1, TRANS("Clear list")); - menu.addItem (5, TRANS("Remove selected plug-in from list"), listBox.getNumSelectedRows() > 0); - menu.addItem (6, TRANS("Show folder containing selected plug-in"), canShowSelectedFolder()); - menu.addItem (7, TRANS("Remove any plug-ins whose files no longer exist")); - menu.addSeparator(); - menu.addItem (2, TRANS("Sort alphabetically")); - menu.addItem (3, TRANS("Sort by category")); - menu.addItem (4, TRANS("Sort by manufacturer")); + menu.addItem (2, TRANS("Remove selected plug-in from list"), table.getNumSelectedRows() > 0); + menu.addItem (3, TRANS("Show folder containing selected plug-in"), canShowSelectedFolder()); + menu.addItem (4, TRANS("Remove any plug-ins whose files no longer exist")); menu.addSeparator(); for (int i = 0; i < formatManager.getNumFormats(); ++i) @@ -262,10 +310,7 @@ void PluginListComponent::setLastSearchPath (PropertiesFile& properties, AudioPl class PluginListComponent::Scanner : private Timer { public: - Scanner (PluginListComponent& plc, - AudioPluginFormat& format, - PropertiesFile* properties, - int threads) + Scanner (PluginListComponent& plc, AudioPluginFormat& format, PropertiesFile* properties, int threads) : owner (plc), formatToScan (format), propertiesToUse (properties), pathChooserWindow (TRANS("Select folders to scan..."), String::empty, AlertWindow::NoIcon), progressWindow (TRANS("Scanning for plug-ins..."), diff --git a/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.h b/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.h index a3cf675..97751a8 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.h +++ b/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.h @@ -33,7 +33,6 @@ */ class JUCE_API PluginListComponent : public Component, public FileDragAndDropTarget, - private ListBoxModel, private ChangeListener, private ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug) { @@ -53,7 +52,7 @@ public: /** Destructor. */ ~PluginListComponent(); - /** Changes the text in the panel's button. */ + /** Changes the text in the panel's options button. */ void setOptionsButtonText (const String& newText); /** Sets how many threads to simultaneously scan for plugins. @@ -62,42 +61,32 @@ public: void setNumberOfThreadsForScanning (int numThreads); /** Returns the last search path stored in a given properties file for the specified format. */ - static FileSearchPath getLastSearchPath (PropertiesFile& properties, AudioPluginFormat& format); + static FileSearchPath getLastSearchPath (PropertiesFile&, AudioPluginFormat&); /** Stores a search path in a properties file for the given format. */ - static void setLastSearchPath (PropertiesFile& properties, AudioPluginFormat& format, - const FileSearchPath& newPath); + static void setLastSearchPath (PropertiesFile&, AudioPluginFormat&, const FileSearchPath&); /** Triggers an asynchronous scan for the given format. */ - void scanFor (AudioPluginFormat& format); + void scanFor (AudioPluginFormat&); /** Returns true if there's currently a scan in progress. */ bool isScanning() const noexcept; - //============================================================================== - /** @internal */ - void resized() override; - /** @internal */ - bool isInterestedInFileDrag (const StringArray&) override; - /** @internal */ - void filesDropped (const StringArray&, int, int) override; - /** @internal */ - int getNumRows() override; - /** @internal */ - void paintListBoxItem (int row, Graphics&, int width, int height, bool rowIsSelected) override; - /** @internal */ - void deleteKeyPressed (int lastRowSelected) override; - private: //============================================================================== AudioPluginFormatManager& formatManager; KnownPluginList& list; File deadMansPedalFile; - ListBox listBox; + TableListBox table; TextButton optionsButton; PropertiesFile* propertiesToUse; int numThreads; + class TableModel; + friend class TableModel; + friend struct ContainerDeletePolicy; + ScopedPointer tableModel; + class Scanner; friend class Scanner; friend struct ContainerDeletePolicy; @@ -107,11 +96,14 @@ private: static void optionsMenuStaticCallback (int, PluginListComponent*); void optionsMenuCallback (int); void updateList(); - void removeSelected(); void showSelectedFolder(); bool canShowSelectedFolder() const; + void removeSelected(); void removeMissingPlugins(); + void resized() override; + bool isInterestedInFileDrag (const StringArray&) override; + void filesDropped (const StringArray&, int, int) override; void buttonClicked (Button*) override; void changeListenerCallback (ChangeBroadcaster*) override; diff --git a/JuceLibraryCode/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp b/JuceLibraryCode/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp index ebff477..db22b5d 100644 --- a/JuceLibraryCode/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp +++ b/JuceLibraryCode/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp @@ -352,7 +352,7 @@ public: if (error.isNotEmpty()) AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, - TRANS ("Error when trying to open audio device!"), + TRANS("Error when trying to open audio device!"), error); } @@ -410,7 +410,7 @@ public: addAndMakeVisible (outputChanList = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioOutputType, TRANS ("(no audio output channels found)"))); - outputChanLabel = new Label (String::empty, TRANS ("active output channels:")); + outputChanLabel = new Label (String::empty, TRANS("Active output channels:")); outputChanLabel->attachToComponent (outputChanList, true); } @@ -429,8 +429,8 @@ public: { addAndMakeVisible (inputChanList = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioInputType, - TRANS ("(no audio input channels found)"))); - inputChanLabel = new Label (String::empty, TRANS ("active input channels:")); + TRANS("(no audio input channels found)"))); + inputChanLabel = new Label (String::empty, TRANS("Active input channels:")); inputChanLabel->attachToComponent (inputChanList, true); } @@ -525,8 +525,8 @@ private: if (currentDevice != nullptr && currentDevice->hasControlPanel()) { - addAndMakeVisible (showUIButton = new TextButton (TRANS ("show this device's control panel"), - TRANS ("opens the device's own control panel"))); + addAndMakeVisible (showUIButton = new TextButton (TRANS ("Show this device's control panel"), + TRANS ("Opens the device's own control panel"))); showUIButton->addListener (this); } @@ -544,13 +544,13 @@ private: addAndMakeVisible (outputDeviceDropDown); outputDeviceLabel = new Label (String::empty, - type.hasSeparateInputsAndOutputs() ? TRANS ("output:") - : TRANS ("device:")); + type.hasSeparateInputsAndOutputs() ? TRANS("Output:") + : TRANS("Device:")); outputDeviceLabel->attachToComponent (outputDeviceDropDown, true); if (setup.maxNumOutputChannels > 0) { - addAndMakeVisible (testButton = new TextButton (TRANS ("Test"))); + addAndMakeVisible (testButton = new TextButton (TRANS("Test"))); testButton->addListener (this); } } @@ -571,7 +571,7 @@ private: inputDeviceDropDown->addListener (this); addAndMakeVisible (inputDeviceDropDown); - inputDeviceLabel = new Label (String::empty, TRANS ("input:")); + inputDeviceLabel = new Label (String::empty, TRANS("Input:")); inputDeviceLabel->attachToComponent (inputDeviceDropDown, true); addAndMakeVisible (inputLevelMeter @@ -590,7 +590,7 @@ private: { addAndMakeVisible (sampleRateDropDown = new ComboBox (String::empty)); - sampleRateLabel = new Label (String::empty, TRANS ("sample rate:")); + sampleRateLabel = new Label (String::empty, TRANS("Sample rate:")); sampleRateLabel->attachToComponent (sampleRateDropDown, true); } else @@ -599,11 +599,11 @@ private: sampleRateDropDown->removeListener (this); } - const int numRates = currentDevice->getNumSampleRates(); + const Array rates (currentDevice->getAvailableSampleRates()); - for (int i = 0; i < numRates; ++i) + for (int i = 0; i < rates.size(); ++i) { - const int rate = roundToInt (currentDevice->getSampleRate (i)); + const int rate = roundToInt (rates[i]); sampleRateDropDown->addItem (String (rate) + " Hz", rate); } @@ -617,7 +617,7 @@ private: { addAndMakeVisible (bufferSizeDropDown = new ComboBox (String::empty)); - bufferSizeLabel = new Label (String::empty, TRANS ("audio buffer size:")); + bufferSizeLabel = new Label (String::empty, TRANS("Audio buffer size:")); bufferSizeLabel->attachToComponent (bufferSizeDropDown, true); } else @@ -626,19 +626,16 @@ private: bufferSizeDropDown->removeListener (this); } - const int numBufferSizes = currentDevice->getNumBufferSizesAvailable(); + const Array bufferSizes (currentDevice->getAvailableBufferSizes()); + double currentRate = currentDevice->getCurrentSampleRate(); if (currentRate == 0) currentRate = 48000.0; - for (int i = 0; i < numBufferSizes; ++i) + for (int i = 0; i < bufferSizes.size(); ++i) { - const int bs = currentDevice->getBufferSizeSamples (i); - bufferSizeDropDown->addItem (String (bs) - + " samples (" - + String (bs * 1000.0 / currentRate, 1) - + " ms)", - bs); + const int bs = bufferSizes[i]; + bufferSizeDropDown->addItem (String (bs) + " samples (" + String (bs * 1000.0 / currentRate, 1) + " ms)", bs); } bufferSizeDropDown->setSelectedId (currentDevice->getCurrentBufferSizeSamples(), dontSendNotification); @@ -936,7 +933,7 @@ AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& addAndMakeVisible (deviceTypeDropDown); deviceTypeDropDown->addListener (this); - deviceTypeDropDownLabel = new Label (String::empty, TRANS ("Audio device type:")); + deviceTypeDropDownLabel = new Label (String::empty, TRANS("Audio device type:")); deviceTypeDropDownLabel->setJustificationType (Justification::centredRight); deviceTypeDropDownLabel->attachToComponent (deviceTypeDropDown, true); } diff --git a/JuceLibraryCode/modules/juce_audio_utils/juce_module_info b/JuceLibraryCode/modules/juce_audio_utils/juce_module_info index 383c091..59269d2 100644 --- a/JuceLibraryCode/modules/juce_audio_utils/juce_module_info +++ b/JuceLibraryCode/modules/juce_audio_utils/juce_module_info @@ -1,7 +1,7 @@ { "id": "juce_audio_utils", "name": "JUCE extra audio utility classes", - "version": "3.0.0", + "version": "3.0.1", "description": "Classes for audio-related GUI and miscellaneous tasks.", "website": "http://www.juce.com/juce", "license": "GPL/Commercial", diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_OwnedArray.h b/JuceLibraryCode/modules/juce_core/containers/juce_OwnedArray.h index c68bf46..a116df8 100644 --- a/JuceLibraryCode/modules/juce_core/containers/juce_OwnedArray.h +++ b/JuceLibraryCode/modules/juce_core/containers/juce_OwnedArray.h @@ -505,10 +505,7 @@ public: jassert (numElementsToAdd <= 0 || data.elements != nullptr); while (--numElementsToAdd >= 0) - { - data.elements [numUsed] = new ObjectClass (*arrayToAddFrom.getUnchecked (startIndex++)); - ++numUsed; - } + data.elements [numUsed++] = createCopyIfNotNull (arrayToAddFrom.getUnchecked (startIndex++)); } /** Inserts a new object into the array assuming that the array is sorted. diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_PropertySet.cpp b/JuceLibraryCode/modules/juce_core/containers/juce_PropertySet.cpp index 735dc60..6b02baf 100644 --- a/JuceLibraryCode/modules/juce_core/containers/juce_PropertySet.cpp +++ b/JuceLibraryCode/modules/juce_core/containers/juce_PropertySet.cpp @@ -155,8 +155,8 @@ void PropertySet::removeValue (StringRef keyName) void PropertySet::setValue (const String& keyName, const XmlElement* const xml) { - setValue (keyName, xml == nullptr ? var::null - : var (xml->createDocument (String::empty, true))); + setValue (keyName, xml == nullptr ? var() + : var (xml->createDocument ("", true))); } bool PropertySet::containsKey (StringRef keyName) const noexcept diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_PropertySet.h b/JuceLibraryCode/modules/juce_core/containers/juce_PropertySet.h index 8d3f429..2a4ecb1 100644 --- a/JuceLibraryCode/modules/juce_core/containers/juce_PropertySet.h +++ b/JuceLibraryCode/modules/juce_core/containers/juce_PropertySet.h @@ -69,7 +69,7 @@ public: @param keyName the name of the property to retrieve @param defaultReturnValue a value to return if the named property doesn't actually exist */ - String getValue (StringRef keyName, const String& defaultReturnValue = String::empty) const noexcept; + String getValue (StringRef keyName, const String& defaultReturnValue = String()) const noexcept; /** Returns one of the properties as an integer. @@ -109,8 +109,8 @@ public: /** Returns one of the properties as an XML element. - The result will a new XMLElement object that the caller must delete. If may return 0 if the - key isn't found, or if the entry contains an string that isn't valid XML. + The result will a new XMLElement object that the caller must delete. If may return nullptr + if the key isn't found, or if the entry contains an string that isn't valid XML. If the value isn't found in this set, then this will look for it in a fallback property set (if you've specified one with the setFallbackPropertySet() method), diff --git a/JuceLibraryCode/modules/juce_core/files/juce_File.cpp b/JuceLibraryCode/modules/juce_core/files/juce_File.cpp index b69b5a7..eff4af4 100644 --- a/JuceLibraryCode/modules/juce_core/files/juce_File.cpp +++ b/JuceLibraryCode/modules/juce_core/files/juce_File.cpp @@ -75,7 +75,7 @@ const File File::nonexistent; String File::parseAbsolutePath (const String& p) { if (p.isEmpty()) - return String::empty; + return String(); #if JUCE_WINDOWS // Windows.. @@ -322,7 +322,7 @@ String File::getFileNameWithoutExtension() const bool File::isAChildOf (const File& potentialParent) const { - if (potentialParent == File::nonexistent) + if (potentialParent.fullPath.isEmpty()) return false; const String ourPath (getPathUpToLastSlash()); @@ -482,11 +482,11 @@ bool File::loadFileAsData (MemoryBlock& destBlock) const String File::loadFileAsString() const { if (! existsAsFile()) - return String::empty; + return String(); FileInputStream in (*this); return in.openedOk() ? in.readEntireStreamAsString() - : String::empty; + : String(); } void File::readLines (StringArray& destLines) const @@ -598,7 +598,7 @@ String File::getFileExtension() const if (indexOfDot > fullPath.lastIndexOfChar (separator)) return fullPath.substring (indexOfDot); - return String::empty; + return String(); } bool File::hasFileExtension (StringRef possibleSuffix) const @@ -629,7 +629,7 @@ bool File::hasFileExtension (StringRef possibleSuffix) const File File::withFileExtension (StringRef newExtension) const { if (fullPath.isEmpty()) - return File::nonexistent; + return File(); String filePart (getFileName()); diff --git a/JuceLibraryCode/modules/juce_core/files/juce_File.h b/JuceLibraryCode/modules/juce_core/files/juce_File.h index 6e4e654..cfcba5e 100644 --- a/JuceLibraryCode/modules/juce_core/files/juce_File.h +++ b/JuceLibraryCode/modules/juce_core/files/juce_File.h @@ -741,7 +741,7 @@ public: @see revealToUser */ - bool startAsProcess (const String& parameters = String::empty) const; + bool startAsProcess (const String& parameters = String()) const; /** Opens Finder, Explorer, or whatever the OS uses, to show the user this file's location. @see startAsProcess diff --git a/JuceLibraryCode/modules/juce_core/files/juce_TemporaryFile.cpp b/JuceLibraryCode/modules/juce_core/files/juce_TemporaryFile.cpp index d5789b6..50475d5 100644 --- a/JuceLibraryCode/modules/juce_core/files/juce_TemporaryFile.cpp +++ b/JuceLibraryCode/modules/juce_core/files/juce_TemporaryFile.cpp @@ -50,7 +50,7 @@ TemporaryFile::TemporaryFile (const File& target, const int optionFlags) targetFile (target) { // If you use this constructor, you need to give it a valid target file! - jassert (targetFile != File::nonexistent); + jassert (targetFile != File()); } TemporaryFile::TemporaryFile (const File& target, const File& temporary) @@ -79,7 +79,7 @@ bool TemporaryFile::overwriteTargetFileWithTemporary() const { // This method only works if you created this object with the constructor // that takes a target file! - jassert (targetFile != File::nonexistent); + jassert (targetFile != File()); if (temporaryFile.exists()) { diff --git a/JuceLibraryCode/modules/juce_core/files/juce_TemporaryFile.h b/JuceLibraryCode/modules/juce_core/files/juce_TemporaryFile.h index ef48903..04561a7 100644 --- a/JuceLibraryCode/modules/juce_core/files/juce_TemporaryFile.h +++ b/JuceLibraryCode/modules/juce_core/files/juce_TemporaryFile.h @@ -89,7 +89,7 @@ public: The file will not be created until you write to it. And remember that when this object is deleted, the file will also be deleted! */ - TemporaryFile (const String& suffix = String::empty, + TemporaryFile (const String& suffix = String(), int optionFlags = 0); /** Creates a temporary file in the same directory as a specified file. diff --git a/JuceLibraryCode/modules/juce_core/javascript/juce_JSON.cpp b/JuceLibraryCode/modules/juce_core/javascript/juce_JSON.cpp index 8945fcc..8aa8192 100644 --- a/JuceLibraryCode/modules/juce_core/javascript/juce_JSON.cpp +++ b/JuceLibraryCode/modules/juce_core/javascript/juce_JSON.cpp @@ -35,7 +35,7 @@ public: switch (t.getAndAdvance()) { - case 0: result = var::null; return Result::ok(); + case 0: result = var(); return Result::ok(); case '{': return parseObject (t, result); case '[': return parseArray (t, result); } @@ -148,7 +148,7 @@ private: if (t2.getAndAdvance() == 'u' && t2.getAndAdvance() == 'l' && t2.getAndAdvance() == 'l') { t = t2; - result = var::null; + result = var(); return Result::ok(); } break; @@ -254,7 +254,7 @@ private: if (c2 != ':') return createFail ("Expected ':', but found", &oldT); - resultProperties.set (propertyName, var::null); + resultProperties.set (propertyName, var()); var* propertyValue = resultProperties.getVarPointer (propertyName); Result r2 (parseAny (t, *propertyValue)); @@ -300,7 +300,7 @@ private: return createFail ("Unexpected end-of-input in array declaration"); t = oldT; - destArray->add (var::null); + destArray->add (var()); Result r (parseAny (t, destArray->getReference (destArray->size() - 1))); if (r.failed()) @@ -514,7 +514,7 @@ var JSON::parse (const String& text) var result; if (! JSONParser::parseObjectOrArray (text.getCharPointer(), result)) - result = var::null; + result = var(); return result; } @@ -610,7 +610,7 @@ public: { switch (r.nextInt (depth > 3 ? 6 : 8)) { - case 0: return var::null; + case 0: return var(); case 1: return r.nextInt(); case 2: return r.nextInt64(); case 3: return r.nextBool(); @@ -638,7 +638,7 @@ public: } default: - return var::null; + return var(); } } diff --git a/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.cpp b/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.cpp index 1e269d0..00dc44b 100644 --- a/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.cpp +++ b/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.cpp @@ -298,11 +298,10 @@ struct JavascriptEngine::RootObject : public DynamicObject if (r == returnWasHit) return r; if (r == breakWasHit) break; - if (r == continueWasHit) continue; iterator->perform (s, nullptr); - if (isDoLoop && ! condition->getResult (s)) + if (isDoLoop && r != continueWasHit && ! condition->getResult (s)) break; } @@ -1244,7 +1243,7 @@ struct JavascriptEngine::RootObject : public DynamicObject if (matchIf (TokenTypes::openParen)) return parseSuffixes (matchCloseParen (parseExpression())); if (matchIf (TokenTypes::true_)) return parseSuffixes (new LiteralValue (location, (int) 1)); if (matchIf (TokenTypes::false_)) return parseSuffixes (new LiteralValue (location, (int) 0)); - if (matchIf (TokenTypes::null_)) return parseSuffixes (new LiteralValue (location, var::null)); + if (matchIf (TokenTypes::null_)) return parseSuffixes (new LiteralValue (location, var())); if (matchIf (TokenTypes::undefined)) return parseSuffixes (new Expression (location)); if (currentType == TokenTypes::literal) diff --git a/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.h b/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.h index e080b18..42368a4 100644 --- a/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.h +++ b/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.h @@ -26,7 +26,6 @@ ============================================================================== */ - /** A simple javascript interpreter! diff --git a/JuceLibraryCode/modules/juce_core/juce_module_info b/JuceLibraryCode/modules/juce_core/juce_module_info index a3a1ee2..d6cb11d 100644 --- a/JuceLibraryCode/modules/juce_core/juce_module_info +++ b/JuceLibraryCode/modules/juce_core/juce_module_info @@ -1,7 +1,7 @@ { "id": "juce_core", "name": "JUCE core classes", - "version": "3.0.0", + "version": "3.0.1", "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": "ISC Permissive", diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_BigInteger.cpp b/JuceLibraryCode/modules/juce_core/maths/juce_BigInteger.cpp index ba37420..ba19414 100644 --- a/JuceLibraryCode/modules/juce_core/maths/juce_BigInteger.cpp +++ b/JuceLibraryCode/modules/juce_core/maths/juce_BigInteger.cpp @@ -951,7 +951,7 @@ String BigInteger::toString (const int base, const int minimumNumCharacters) con else { jassertfalse; // can't do the specified base! - return String::empty; + return String(); } s = s.paddedLeft ('0', minimumNumCharacters); diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_Expression.cpp b/JuceLibraryCode/modules/juce_core/maths/juce_Expression.cpp index 41b144a..9734446 100644 --- a/JuceLibraryCode/modules/juce_core/maths/juce_Expression.cpp +++ b/JuceLibraryCode/modules/juce_core/maths/juce_Expression.cpp @@ -53,7 +53,7 @@ public: virtual String getName() const { jassertfalse; // You shouldn't call this for an expression that's not actually a function! - return String::empty; + return String(); } virtual void renameSymbol (const Symbol& oldSymbol, const String& newName, const Scope& scope, int recursionDepth) @@ -1181,5 +1181,5 @@ void Expression::Scope::visitRelativeScope (const String& scopeName, Visitor&) c String Expression::Scope::getScopeUID() const { - return String::empty; + return String(); } diff --git a/JuceLibraryCode/modules/juce_core/misc/juce_Uuid.cpp b/JuceLibraryCode/modules/juce_core/misc/juce_Uuid.cpp index bffb38d..eb74964 100644 --- a/JuceLibraryCode/modules/juce_core/misc/juce_Uuid.cpp +++ b/JuceLibraryCode/modules/juce_core/misc/juce_Uuid.cpp @@ -26,7 +26,6 @@ ============================================================================== */ - Uuid::Uuid() { Random r; diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_Files.cpp b/JuceLibraryCode/modules/juce_core/native/juce_android_Files.cpp index 1818043..45903b1 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_android_Files.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_android_Files.cpp @@ -80,7 +80,7 @@ File File::getSpecialLocation (const SpecialLocationType type) break; } - return File::nonexistent; + return File(); } bool File::moveToTrash() const diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_Files.cpp b/JuceLibraryCode/modules/juce_core/native/juce_linux_Files.cpp index 436480d..d0dd54d 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_linux_Files.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_Files.cpp @@ -72,7 +72,7 @@ bool File::isOnRemovableDrive() const String File::getVersion() const { - return String::empty; // xxx not yet implemented + return String(); // xxx not yet implemented } //============================================================================== @@ -115,7 +115,7 @@ File File::getSpecialLocation (const SpecialLocationType type) if (struct passwd* const pw = getpwuid (getuid())) return File (CharPointer_UTF8 (pw->pw_dir)); - return File::nonexistent; + return File(); } case userDocumentsDirectory: return resolveXDGFolder ("XDG_DOCUMENTS_DIR", "~"); @@ -163,7 +163,7 @@ File File::getSpecialLocation (const SpecialLocationType type) break; } - return File::nonexistent; + return File(); } //============================================================================== diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_Network.cpp b/JuceLibraryCode/modules/juce_core/native/juce_linux_Network.cpp index a946273..a052b2c 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_linux_Network.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_Network.cpp @@ -309,7 +309,7 @@ private: { char c = 0; if (read (&c, 1) != 1) - return String::empty; + return String(); buffer.writeByte (c); @@ -324,7 +324,7 @@ private: if (header.startsWithIgnoreCase ("HTTP/")) return header; - return String::empty; + return String(); } static void writeValueIfNotPresent (MemoryOutputStream& dest, const String& headers, const String& key, const String& value) @@ -432,7 +432,7 @@ private: if (lines[i].startsWithIgnoreCase (itemName)) return lines[i].substring (itemName.length()).trim(); - return String::empty; + return String(); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp index 91cecbd..a2575a1 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp @@ -44,7 +44,7 @@ String SystemStats::getOperatingSystemName() String SystemStats::getDeviceDescription() { - return String::empty; + return String(); } bool SystemStats::isOperatingSystem64Bit() @@ -69,7 +69,7 @@ namespace LinuxStatsHelpers if (lines[i].startsWithIgnoreCase (key)) return lines[i].fromFirstOccurrenceOf (":", false, false).trim(); - return String::empty; + return String(); } } @@ -107,7 +107,7 @@ String SystemStats::getLogonName() if (struct passwd* const pw = getpwuid (getuid())) return CharPointer_UTF8 (pw->pw_name); - return String::empty; + return String(); } String SystemStats::getFullUserName() @@ -121,7 +121,7 @@ String SystemStats::getComputerName() if (gethostname (name, sizeof (name) - 1) == 0) return name; - return String::empty; + return String(); } static String getLocaleValue (nl_item key) diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm b/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm index e4d4f96..0a00e92 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm +++ b/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm @@ -257,7 +257,7 @@ File File::getSpecialLocation (const SpecialLocationType type) return File (resultPath.convertToPrecomposedUnicode()); } - return File::nonexistent; + return File(); } //============================================================================== @@ -271,7 +271,7 @@ String File::getVersion() const return nsStringToJuce (name); } - return String::empty; + return String(); } //============================================================================== diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_Strings.mm b/JuceLibraryCode/modules/juce_core/native/juce_mac_Strings.mm index ac81900..862eb53 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_mac_Strings.mm +++ b/JuceLibraryCode/modules/juce_core/native/juce_mac_Strings.mm @@ -29,7 +29,7 @@ String String::fromCFString (CFStringRef cfString) { if (cfString == 0) - return String::empty; + return String(); CFRange range = { 0, CFStringGetLength (cfString) }; HeapBlock u ((size_t) range.length + 1); diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_SystemStats.mm b/JuceLibraryCode/modules/juce_core/native/juce_mac_SystemStats.mm index ecccb2d..45967ee 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_mac_SystemStats.mm +++ b/JuceLibraryCode/modules/juce_core/native/juce_mac_SystemStats.mm @@ -124,7 +124,7 @@ SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() return iOS; #else StringArray parts; - parts.addTokens (getOSXVersion(), ".", String::empty); + parts.addTokens (getOSXVersion(), ".", String()); jassert (parts[0].getIntValue() == 10); const int major = parts[1].getIntValue(); @@ -148,7 +148,7 @@ String SystemStats::getDeviceDescription() #if JUCE_IOS return nsStringToJuce ([[UIDevice currentDevice] model]); #else - return String::empty; + return String(); #endif } @@ -182,7 +182,7 @@ String SystemStats::getCpuVendor() return String (reinterpret_cast (vendor), 12); #else - return String::empty; + return String(); #endif } @@ -218,7 +218,7 @@ String SystemStats::getComputerName() if (gethostname (name, sizeof (name) - 1) == 0) return String (name).upToLastOccurrenceOf (".local", false, true); - return String::empty; + return String(); } static String getLocaleValue (CFStringRef key) diff --git a/JuceLibraryCode/modules/juce_core/native/juce_posix_SharedCode.h b/JuceLibraryCode/modules/juce_core/native/juce_posix_SharedCode.h index 243a194..46d2382 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_posix_SharedCode.h +++ b/JuceLibraryCode/modules/juce_core/native/juce_posix_SharedCode.h @@ -636,7 +636,7 @@ String File::getVolumeLabel() const } #endif - return String::empty; + return String(); } int File::getVolumeSerialNumber() const diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_Files.cpp b/JuceLibraryCode/modules/juce_core/native/juce_win32_Files.cpp index 866d2d3..6f0b3f4 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_win32_Files.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_win32_Files.cpp @@ -93,7 +93,7 @@ namespace WindowsFileHelpers if (SHGetSpecialFolderPath (0, path, type, FALSE)) return File (String (path)); - return File::nonexistent; + return File(); } File getModuleFileName (HINSTANCE moduleHandle) @@ -542,7 +542,7 @@ File JUCE_CALLTYPE File::getSpecialLocation (const SpecialLocationType type) default: jassertfalse; // unknown type? - return File::nonexistent; + return File(); } return WindowsFileHelpers::getSpecialFolderPath (csidlType); diff --git a/JuceLibraryCode/modules/juce_core/network/juce_IPAddress.cpp b/JuceLibraryCode/modules/juce_core/network/juce_IPAddress.cpp index ced207d..6c4d087 100644 --- a/JuceLibraryCode/modules/juce_core/network/juce_IPAddress.cpp +++ b/JuceLibraryCode/modules/juce_core/network/juce_IPAddress.cpp @@ -55,7 +55,7 @@ IPAddress::IPAddress (uint32 n) noexcept IPAddress::IPAddress (const String& adr) { StringArray tokens; - tokens.addTokens (adr, ".", String::empty); + tokens.addTokens (adr, ".", String()); for (int i = 0; i < 4; ++i) address[i] = (uint8) tokens[i].getIntValue(); diff --git a/JuceLibraryCode/modules/juce_core/network/juce_Socket.cpp b/JuceLibraryCode/modules/juce_core/network/juce_Socket.cpp index c902d41..2d5d1fe 100644 --- a/JuceLibraryCode/modules/juce_core/network/juce_Socket.cpp +++ b/JuceLibraryCode/modules/juce_core/network/juce_Socket.cpp @@ -386,7 +386,7 @@ void StreamingSocket::close() ::close (handle); #endif - hostName = String::empty; + hostName.clear(); portNumber = 0; handle = -1; isListener = false; @@ -506,7 +506,7 @@ void DatagramSocket::close() ::close (handle); #endif - hostName = String::empty; + hostName.clear(); portNumber = 0; handle = -1; } diff --git a/JuceLibraryCode/modules/juce_core/network/juce_Socket.h b/JuceLibraryCode/modules/juce_core/network/juce_Socket.h index 616d2db..73795c9 100644 --- a/JuceLibraryCode/modules/juce_core/network/juce_Socket.h +++ b/JuceLibraryCode/modules/juce_core/network/juce_Socket.h @@ -146,7 +146,7 @@ public: @see waitForNextConnection */ - bool createListener (int portNumber, const String& localHostName = String::empty); + bool createListener (int portNumber, const String& localHostName = String()); /** When in "listener" mode, this waits for a connection and spawns it as a new socket. diff --git a/JuceLibraryCode/modules/juce_core/network/juce_URL.cpp b/JuceLibraryCode/modules/juce_core/network/juce_URL.cpp index b9db52a..cf9f2cf 100644 --- a/JuceLibraryCode/modules/juce_core/network/juce_URL.cpp +++ b/JuceLibraryCode/modules/juce_core/network/juce_URL.cpp @@ -181,7 +181,7 @@ namespace URLHelpers << "\"; filename=\"" << file.getFileName() << "\"\r\n"; const String mimeType (url.getMimeTypesOfUploadFiles() - .getValue (paramName, String::empty)); + .getValue (paramName, String())); if (mimeType.isNotEmpty()) data << "Content-Type: " << mimeType << "\r\n"; @@ -253,7 +253,7 @@ String URL::getSubPath() const { const int startOfPath = URLHelpers::findStartOfPath (url); - return startOfPath <= 0 ? String::empty + return startOfPath <= 0 ? String() : url.substring (startOfPath); } @@ -363,7 +363,7 @@ String URL::readEntireTextStream (const bool usePostCommand) const if (in != nullptr) return in->readEntireStreamAsString(); - return String::empty; + return String(); } XmlElement* URL::readEntireXmlStream (const bool usePostCommand) const @@ -470,5 +470,5 @@ bool URL::launchInDefaultBrowser() const if (u.containsChar ('@') && ! u.containsChar (':')) u = "mailto:" + u; - return Process::openDocument (u, String::empty); + return Process::openDocument (u, String()); } diff --git a/JuceLibraryCode/modules/juce_core/network/juce_URL.h b/JuceLibraryCode/modules/juce_core/network/juce_URL.h index 462ec88..28d3aac 100644 --- a/JuceLibraryCode/modules/juce_core/network/juce_URL.h +++ b/JuceLibraryCode/modules/juce_core/network/juce_URL.h @@ -251,7 +251,7 @@ public: InputStream* createInputStream (bool usePostCommand, OpenStreamProgressCallback* progressCallback = nullptr, void* progressCallbackContext = nullptr, - String extraHeaders = String::empty, + String extraHeaders = String(), int connectionTimeOutMs = 0, StringPairArray* responseHeaders = nullptr) const; diff --git a/JuceLibraryCode/modules/juce_core/system/juce_StandardHeader.h b/JuceLibraryCode/modules/juce_core/system/juce_StandardHeader.h index a899843..12e332a 100644 --- a/JuceLibraryCode/modules/juce_core/system/juce_StandardHeader.h +++ b/JuceLibraryCode/modules/juce_core/system/juce_StandardHeader.h @@ -36,7 +36,7 @@ */ #define JUCE_MAJOR_VERSION 3 #define JUCE_MINOR_VERSION 0 -#define JUCE_BUILDNUMBER 0 +#define JUCE_BUILDNUMBER 1 /** Current Juce version number. diff --git a/JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.cpp b/JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.cpp index f2098a0..1d70e4e 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.cpp +++ b/JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.cpp @@ -43,11 +43,17 @@ LocalisedStrings::~LocalisedStrings() //============================================================================== String LocalisedStrings::translate (const String& text) const { + if (fallback != nullptr && ! translations.containsKey (text)) + return fallback->translate (text); + return translations.getValue (text, text); } String LocalisedStrings::translate (const String& text, const String& resultIfNotFound) const { + if (fallback != nullptr && ! translations.containsKey (text)) + return fallback->translate (text, resultIfNotFound); + return translations.getValue (text, resultIfNotFound); } @@ -73,7 +79,7 @@ namespace SpinLock currentMappingsLock; ScopedPointer currentMappings; - int findCloseQuote (const String& text, int startPos) + static int findCloseQuote (const String& text, int startPos) { juce_wchar lastChar = 0; String::CharPointerType t (text.getCharPointer() + startPos); @@ -92,7 +98,7 @@ namespace return startPos; } - String unescapeString (const String& s) + static String unescapeString (const String& s) { return s.replace ("\\\"", "\"") .replace ("\\\'", "\'") @@ -141,6 +147,8 @@ void LocalisedStrings::loadFromText (const String& fileContents, bool ignoreCase countryCodes.removeEmptyStrings(); } } + + translations.minimiseStorageOverheads(); } void LocalisedStrings::addStrings (const LocalisedStrings& other) @@ -151,6 +159,11 @@ void LocalisedStrings::addStrings (const LocalisedStrings& other) translations.addArray (other.translations); } +void LocalisedStrings::setFallback (LocalisedStrings* f) +{ + fallback = f; +} + //============================================================================== void LocalisedStrings::setCurrentMappings (LocalisedStrings* newTranslations) { diff --git a/JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.h b/JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.h index 69e9df9..acf1da8 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.h +++ b/JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.h @@ -183,11 +183,18 @@ public: */ void addStrings (const LocalisedStrings&); + /** Gives this object a set of strings to use as a fallback if a string isn't found. + The object that is passed-in will be owned and deleted by this object + when no longer needed. It can be nullptr to clear the existing fallback object. + */ + void setFallback (LocalisedStrings* fallbackStrings); + private: //============================================================================== String languageName; StringArray countryCodes; StringPairArray translations; + ScopedPointer fallback; void loadFromText (const String&, bool ignoreCase); diff --git a/JuceLibraryCode/modules/juce_core/text/juce_String.cpp b/JuceLibraryCode/modules/juce_core/text/juce_String.cpp index d2137de..d9f8fea 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_String.cpp +++ b/JuceLibraryCode/modules/juce_core/text/juce_String.cpp @@ -259,6 +259,12 @@ void String::swapWith (String& other) noexcept std::swap (text, other.text); } +void String::clear() noexcept +{ + StringHolder::release (text); + text = &(emptyString.text); +} + String& String::operator= (const String& other) noexcept { StringHolder::retain (other.text); @@ -419,7 +425,8 @@ namespace NumberToStringConverters { explicit StackArrayStream (char* d) { - imbue (std::locale::classic()); + static const std::locale classicLocale (std::locale::classic()); + imbue (classicLocale); setp (d, d + charsNeededForDouble); } diff --git a/JuceLibraryCode/modules/juce_core/text/juce_String.h b/JuceLibraryCode/modules/juce_core/text/juce_String.h index c760239..febe6ee 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_String.h +++ b/JuceLibraryCode/modules/juce_core/text/juce_String.h @@ -307,6 +307,9 @@ public: */ inline bool isNotEmpty() const noexcept { return text[0] != 0; } + /** Resets this string to be empty. */ + void clear() noexcept; + /** Case-insensitive comparison with another string. */ bool equalsIgnoreCase (const String& other) const noexcept; diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringArray.cpp b/JuceLibraryCode/modules/juce_core/text/juce_StringArray.cpp index 4925023..e26d38d 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_StringArray.cpp +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringArray.cpp @@ -293,7 +293,7 @@ String StringArray::joinIntoString (StringRef separator, int start, int numberTo start = 0; if (start >= last) - return String::empty; + return String(); if (start == last - 1) return strings.getReference (start); diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.cpp b/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.cpp index 7912cd9..3275d2d 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.cpp +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.cpp @@ -78,6 +78,11 @@ String StringPairArray::getValue (StringRef key, const String& defaultReturnValu return defaultReturnValue; } +bool StringPairArray::containsKey (StringRef key) const noexcept +{ + return keys.contains (key); +} + void StringPairArray::set (const String& key, const String& value) { const int i = keys.indexOf (key, ignoreCase); diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.h b/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.h index add4a60..e1c774d 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.h +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.h @@ -85,6 +85,8 @@ public: */ String getValue (StringRef, const String& defaultReturnValue) const; + /** Returns true if the given key exists. */ + bool containsKey (StringRef key) const noexcept; /** Returns a list of all keys in the array. */ const StringArray& getAllKeys() const noexcept { return keys; } diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringPool.cpp b/JuceLibraryCode/modules/juce_core/text/juce_StringPool.cpp index 88ea9a0..6841c47 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_StringPool.cpp +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringPool.cpp @@ -81,7 +81,7 @@ namespace StringPoolHelpers String::CharPointerType StringPool::getPooledString (const String& s) { if (s.isEmpty()) - return String::empty.getCharPointer(); + return String().getCharPointer(); return StringPoolHelpers::getPooledStringFromArray (strings, s, lock); } @@ -89,7 +89,7 @@ String::CharPointerType StringPool::getPooledString (const String& s) String::CharPointerType StringPool::getPooledString (const char* const s) { if (s == nullptr || *s == 0) - return String::empty.getCharPointer(); + return String().getCharPointer(); return StringPoolHelpers::getPooledStringFromArray (strings, s, lock); } @@ -97,7 +97,7 @@ String::CharPointerType StringPool::getPooledString (const char* const s) String::CharPointerType StringPool::getPooledString (const wchar_t* const s) { if (s == nullptr || *s == 0) - return String::empty.getCharPointer(); + return String().getCharPointer(); return StringPoolHelpers::getPooledStringFromArray (strings, s, lock); } diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.h b/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.h index c93d436..ed820e8 100644 --- a/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.h +++ b/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.h @@ -247,7 +247,7 @@ public: /** Returns one of the jobs in the queue. Note that this can be a very volatile list as jobs might be continuously getting shifted - around in the list, and this method may return 0 if the index is currently out-of-range. + around in the list, and this method may return nullptr if the index is currently out-of-range. */ ThreadPoolJob* getJob (int index) const; diff --git a/JuceLibraryCode/modules/juce_core/time/juce_PerformanceCounter.cpp b/JuceLibraryCode/modules/juce_core/time/juce_PerformanceCounter.cpp index a071665..b22d5e4 100644 --- a/JuceLibraryCode/modules/juce_core/time/juce_PerformanceCounter.cpp +++ b/JuceLibraryCode/modules/juce_core/time/juce_PerformanceCounter.cpp @@ -28,7 +28,7 @@ static void appendToFile (const File& f, const String& s) { - if (f != File::nonexistent) + if (f.getFullPathName().isNotEmpty()) { FileOutputStream out (f); diff --git a/JuceLibraryCode/modules/juce_core/time/juce_PerformanceCounter.h b/JuceLibraryCode/modules/juce_core/time/juce_PerformanceCounter.h index 81db856..aac50d2 100644 --- a/JuceLibraryCode/modules/juce_core/time/juce_PerformanceCounter.h +++ b/JuceLibraryCode/modules/juce_core/time/juce_PerformanceCounter.h @@ -65,7 +65,7 @@ public: */ PerformanceCounter (const String& counterName, int runsPerPrintout = 100, - const File& loggingFile = File::nonexistent); + const File& loggingFile = File()); /** Destructor. */ ~PerformanceCounter(); diff --git a/JuceLibraryCode/modules/juce_core/xml/juce_XmlDocument.cpp b/JuceLibraryCode/modules/juce_core/xml/juce_XmlDocument.cpp index b95a8a6..6243d3e 100644 --- a/JuceLibraryCode/modules/juce_core/xml/juce_XmlDocument.cpp +++ b/JuceLibraryCode/modules/juce_core/xml/juce_XmlDocument.cpp @@ -210,7 +210,7 @@ XmlElement* XmlDocument::parseDocumentElement (String::CharPointerType textToPar } else { - lastError = String::empty; + lastError.clear(); ScopedPointer result (readNextElement (! onlyReadOuterDocumentElement)); diff --git a/JuceLibraryCode/modules/juce_core/zip/juce_ZipFile.cpp b/JuceLibraryCode/modules/juce_core/zip/juce_ZipFile.cpp index dba7fe7..0f257e2 100644 --- a/JuceLibraryCode/modules/juce_core/zip/juce_ZipFile.cpp +++ b/JuceLibraryCode/modules/juce_core/zip/juce_ZipFile.cpp @@ -541,7 +541,7 @@ private: void writeFlagsAndSizes (OutputStream& target) const { target.writeShort (10); // version needed - target.writeShort (0); // flags + target.writeShort ((short) (1 << 11)); // this flag indicates UTF-8 filename encoding target.writeShort (compressionLevel > 0 ? (short) 8 : (short) 0); writeTimeAndDate (target, fileTime); target.writeInt ((int) checksum); diff --git a/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp b/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp index 78c9736..3c1c0d4 100644 --- a/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp +++ b/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp @@ -90,8 +90,8 @@ File PropertiesFile::Options::getDefaultFile() const File dir (File::getSpecialLocation (commonToAllUsers ? File::commonApplicationDataDirectory : File::userApplicationDataDirectory)); - if (dir == File::nonexistent) - return File::nonexistent; + if (dir == File()) + return File(); dir = dir.getChildFile (folderName.isNotEmpty() ? folderName : applicationName); @@ -165,7 +165,7 @@ bool PropertiesFile::save() stopTimer(); if (options.doNotSave - || file == File::nonexistent + || file == File() || file.isDirectory() || ! file.getParentDirectory().createDirectory()) return false; @@ -195,7 +195,7 @@ bool PropertiesFile::loadAsXml() { getAllProperties().set (name, e->getFirstChildElement() != nullptr - ? e->getFirstChildElement()->createDocument (String::empty, true) + ? e->getFirstChildElement()->createDocument ("", true) : e->getStringAttribute (PropertyFileConstants::valueAttribute)); } } @@ -234,7 +234,7 @@ bool PropertiesFile::saveAsXml() if (pl != nullptr && ! pl->isLocked()) return false; // locking failure.. - if (doc.writeToFile (file, String::empty)) + if (doc.writeToFile (file, String())) { needsWriting = false; return true; diff --git a/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_PropertiesFile.h b/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_PropertiesFile.h index 0e82b13..fd09c8c 100644 --- a/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_PropertiesFile.h +++ b/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_PropertiesFile.h @@ -144,7 +144,7 @@ public: C:\\Documents and Settings\\username\\Application Data\\[folderName]\\[applicationName].[filenameSuffix] On Linux it'll return - ~/.[folderName]/[applicationName].[filenameSuffix] + ~/[folderName]/[applicationName].[filenameSuffix] If the folderName variable is empty, it'll use the app name for this (or omit the folder name on the Mac). diff --git a/JuceLibraryCode/modules/juce_data_structures/juce_module_info b/JuceLibraryCode/modules/juce_data_structures/juce_module_info index bd92088..7ba39c4 100644 --- a/JuceLibraryCode/modules/juce_data_structures/juce_module_info +++ b/JuceLibraryCode/modules/juce_data_structures/juce_module_info @@ -1,7 +1,7 @@ { "id": "juce_data_structures", "name": "JUCE data model helper classes", - "version": "3.0.0", + "version": "3.0.1", "description": "Classes for undo/redo management, and smart data structures.", "website": "http://www.juce.com/juce", "license": "GPL/Commercial", diff --git a/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoManager.cpp b/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoManager.cpp index 328a052..3557b90 100644 --- a/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoManager.cpp +++ b/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoManager.cpp @@ -235,7 +235,7 @@ String UndoManager::getUndoDescription() const if (const ActionSet* const s = getCurrentSet()) return s->name; - return String::empty; + return String(); } String UndoManager::getRedoDescription() const @@ -243,7 +243,7 @@ String UndoManager::getRedoDescription() const if (const ActionSet* const s = getNextSet()) return s->name; - return String::empty; + return String(); } Time UndoManager::getTimeOfUndoTransaction() const diff --git a/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoManager.h b/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoManager.h index 6948263..eee9cbc 100644 --- a/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoManager.h +++ b/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoManager.h @@ -107,7 +107,7 @@ public: @see beginNewTransaction */ bool perform (UndoableAction* action, - const String& actionName = String::empty); + const String& actionName = String()); /** Starts a new group of actions that together will be treated as a single transaction. @@ -118,7 +118,7 @@ public: @param actionName a description of the transaction that is about to be performed */ - void beginNewTransaction (const String& actionName = String::empty); + void beginNewTransaction (const String& actionName = String()); /** Changes the name stored for the current transaction. diff --git a/JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.cpp b/JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.cpp index 2c4a94c..a9e3893 100644 --- a/JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.cpp +++ b/JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.cpp @@ -168,7 +168,7 @@ public: } else { - undoManager->perform (new SetPropertyAction (this, name, newValue, var::null, true, false)); + undoManager->perform (new SetPropertyAction (this, name, newValue, var(), true, false)); } } } @@ -188,7 +188,7 @@ public: else { if (properties.contains (name)) - undoManager->perform (new SetPropertyAction (this, name, var::null, properties [name], false, true)); + undoManager->perform (new SetPropertyAction (this, name, var(), properties [name], false, true)); } } @@ -206,7 +206,7 @@ public: else { for (int i = properties.size(); --i >= 0;) - undoManager->perform (new SetPropertyAction (this, properties.getName(i), var::null, + undoManager->perform (new SetPropertyAction (this, properties.getName(i), var(), properties.getValueAt(i), false, true)); } } @@ -230,7 +230,7 @@ public: return ValueTree (s); } - return ValueTree::invalid; + return ValueTree(); } ValueTree getOrCreateChildWithName (const Identifier typeToMatch, UndoManager* undoManager) @@ -257,7 +257,7 @@ public: return ValueTree (s); } - return ValueTree::invalid; + return ValueTree(); } bool isAChildOf (const SharedObject* const possibleParent) const noexcept @@ -444,7 +444,7 @@ public: } else { - output.writeString (String::empty); + output.writeString (String()); output.writeCompressedInt (0); output.writeCompressedInt (0); } @@ -846,17 +846,17 @@ ValueTree ValueTree::getChild (int index) const ValueTree ValueTree::getChildWithName (const Identifier type) const { - return object != nullptr ? object->getChildWithName (type) : ValueTree::invalid; + return object != nullptr ? object->getChildWithName (type) : ValueTree(); } ValueTree ValueTree::getOrCreateChildWithName (const Identifier type, UndoManager* undoManager) { - return object != nullptr ? object->getOrCreateChildWithName (type, undoManager) : ValueTree::invalid; + return object != nullptr ? object->getOrCreateChildWithName (type, undoManager) : ValueTree(); } ValueTree ValueTree::getChildWithProperty (const Identifier propertyName, const var& propertyValue) const { - return object != nullptr ? object->getChildWithProperty (propertyName, propertyValue) : ValueTree::invalid; + return object != nullptr ? object->getChildWithProperty (propertyName, propertyValue) : ValueTree(); } bool ValueTree::isAChildOf (const ValueTree& possibleParent) const @@ -962,7 +962,7 @@ ValueTree ValueTree::fromXml (const XmlElement& xml) String ValueTree::toXmlString() const { const ScopedPointer xml (createXml()); - return xml != nullptr ? xml->createDocument (String::empty) : String::empty; + return xml != nullptr ? xml->createDocument ("") : String(); } //============================================================================== @@ -976,7 +976,7 @@ ValueTree ValueTree::readFromStream (InputStream& input) const String type (input.readString()); if (type.isEmpty()) - return ValueTree::invalid; + return ValueTree(); ValueTree v (type); diff --git a/JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.h b/JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.h index 44fd13f..311d722 100644 --- a/JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.h +++ b/JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.h @@ -317,7 +317,7 @@ public: //============================================================================== /** Creates an XmlElement that holds a complete image of this node and all its children. - If this node is invalid, this may return 0. Otherwise, the XML that is produced can + If this node is invalid, this may return nullptr. Otherwise, the XML that is produced can be used to recreate a similar node by calling fromXml() @see fromXml */ diff --git a/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.cpp b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.cpp index a76ff4d..e22ff5b 100644 --- a/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.cpp +++ b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.cpp @@ -141,7 +141,7 @@ String InterprocessConnection::getConnectedHostName() const return "localhost"; } - return String::empty; + return String(); } //============================================================================== diff --git a/JuceLibraryCode/modules/juce_events/juce_module_info b/JuceLibraryCode/modules/juce_events/juce_module_info index 3bd4805..cb3c7f2 100644 --- a/JuceLibraryCode/modules/juce_events/juce_module_info +++ b/JuceLibraryCode/modules/juce_events/juce_module_info @@ -1,7 +1,7 @@ { "id": "juce_events", "name": "JUCE message and event handling classes", - "version": "3.0.0", + "version": "3.0.1", "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", diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_ApplicationBase.cpp b/JuceLibraryCode/modules/juce_events/messages/juce_ApplicationBase.cpp index a5610fa..507823d 100644 --- a/JuceLibraryCode/modules/juce_events/messages/juce_ApplicationBase.cpp +++ b/JuceLibraryCode/modules/juce_events/messages/juce_ApplicationBase.cpp @@ -134,7 +134,7 @@ struct JUCEApplicationBase::MultipleInstanceHandler {}; #if JUCE_ANDROID StringArray JUCEApplicationBase::getCommandLineParameterArray() { return StringArray(); } -String JUCEApplicationBase::getCommandLineParameters() { return String::empty; } +String JUCEApplicationBase::getCommandLineParameters() { return String(); } #else diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.cpp b/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.cpp index 925c27a..2f38a1c 100644 --- a/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.cpp +++ b/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.cpp @@ -22,21 +22,6 @@ ============================================================================== */ -class MessageManager::QuitMessage : public MessageManager::MessageBase -{ -public: - QuitMessage() {} - - void messageCallback() override - { - if (MessageManager* const mm = MessageManager::instance) - mm->quitMessageReceived = true; - } - - JUCE_DECLARE_NON_COPYABLE (QuitMessage) -}; - -//============================================================================== MessageManager::MessageManager() noexcept : quitMessagePosted (false), quitMessageReceived (false), @@ -96,12 +81,6 @@ void MessageManager::runDispatchLoop() runDispatchLoopUntil (-1); } -void MessageManager::stopDispatchLoop() -{ - (new QuitMessage())->post(); - quitMessagePosted = true; -} - bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) { jassert (isThisTheMessageThread()); // must only be called by the message thread @@ -124,6 +103,26 @@ bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) return ! quitMessageReceived; } +class MessageManager::QuitMessage : public MessageManager::MessageBase +{ +public: + QuitMessage() {} + + void messageCallback() override + { + if (MessageManager* const mm = MessageManager::instance) + mm->quitMessageReceived = true; + } + + JUCE_DECLARE_NON_COPYABLE (QuitMessage) +}; + +void MessageManager::stopDispatchLoop() +{ + (new QuitMessage())->post(); + quitMessagePosted = true; +} + #endif //============================================================================== diff --git a/JuceLibraryCode/modules/juce_events/native/juce_mac_MessageManager.mm b/JuceLibraryCode/modules/juce_events/native/juce_mac_MessageManager.mm index b8d83af..3aed8a2 100644 --- a/JuceLibraryCode/modules/juce_events/native/juce_mac_MessageManager.mm +++ b/JuceLibraryCode/modules/juce_events/native/juce_mac_MessageManager.mm @@ -237,15 +237,32 @@ void MessageManager::runDispatchLoop() } } -void MessageManager::stopDispatchLoop() +static void shutdownNSApp() { - jassert (isThisTheMessageThread()); // must only be called by the message thread + [NSApp stop: nil]; + [NSApp activateIgnoringOtherApps: YES]; // (if the app is inactive, it sits there and ignores the quit request until the next time it gets activated) + [NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1]; +} +void MessageManager::stopDispatchLoop() +{ quitMessagePosted = true; + #if ! JUCE_PROJUCER_LIVE_BUILD - [NSApp stop: nil]; - [NSApp activateIgnoringOtherApps: YES]; // (if the app is inactive, it sits there and ignores the quit request until the next time it gets activated) - [NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1]; + if (isThisTheMessageThread()) + { + shutdownNSApp(); + } + else + { + struct QuitCallback : public CallbackMessage + { + QuitCallback() {} + void messageCallback() override { shutdownNSApp(); } + }; + + (new QuitCallback())->post(); + } #endif } diff --git a/JuceLibraryCode/modules/juce_graphics/contexts/juce_GraphicsContext.cpp b/JuceLibraryCode/modules/juce_graphics/contexts/juce_GraphicsContext.cpp index 71ea342..e43cf79 100644 --- a/JuceLibraryCode/modules/juce_graphics/contexts/juce_GraphicsContext.cpp +++ b/JuceLibraryCode/modules/juce_graphics/contexts/juce_GraphicsContext.cpp @@ -232,17 +232,25 @@ Font Graphics::getCurrentFont() const void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY, Justification justification) const { - if (text.isNotEmpty() - && startX < context.getClipBounds().getRight()) + if (text.isNotEmpty()) { - GlyphArrangement arr; - arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY); - // Don't pass any vertical placement flags to this method - they'll be ignored. jassert (justification.getOnlyVerticalFlags() == 0); const int flags = justification.getOnlyHorizontalFlags(); + if (flags == Justification::right) + { + if (startX < context.getClipBounds().getX()) + return; + } + else if (flags == Justification::left) + if (startX > context.getClipBounds().getRight()) + return; + + GlyphArrangement arr; + arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY); + if (flags != Justification::left) { float w = arr.getBoundingBox (0, -1, true).getWidth(); diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_AttributedString.cpp b/JuceLibraryCode/modules/juce_graphics/fonts/juce_AttributedString.cpp index d5999c8..43b6318 100644 --- a/JuceLibraryCode/modules/juce_graphics/fonts/juce_AttributedString.cpp +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_AttributedString.cpp @@ -166,7 +166,7 @@ void AttributedString::append (const AttributedString& other) void AttributedString::clear() { - text = String::empty; + text.clear(); attributes.clear(); } diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_CustomTypeface.cpp b/JuceLibraryCode/modules/juce_graphics/fonts/juce_CustomTypeface.cpp index 1ab1591..c9d6e8d 100644 --- a/JuceLibraryCode/modules/juce_graphics/fonts/juce_CustomTypeface.cpp +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_CustomTypeface.cpp @@ -99,13 +99,13 @@ namespace CustomTypefaceHelpers //============================================================================== CustomTypeface::CustomTypeface() - : Typeface (String::empty, String::empty) + : Typeface (String(), String()) { clear(); } CustomTypeface::CustomTypeface (InputStream& serialisedTypefaceStream) - : Typeface (String::empty, String::empty) + : Typeface (String(), String()) { clear(); diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.h b/JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.h index 4560bb3..9c2ae31 100644 --- a/JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.h +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.h @@ -64,6 +64,12 @@ public: /** Creates a new system typeface. */ static Ptr createSystemTypefaceFor (const Font& font); + /** Attempts to create a font from some raw font file data (e.g. a TTF or OTF file image). + The system will take its own internal copy of the data, so you can free the block once + this method has returned. + */ + static Ptr createSystemTypefaceFor (const void* fontFileData, size_t fontFileDataSize); + //============================================================================== /** Destructor. */ virtual ~Typeface(); diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Rectangle.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Rectangle.h index 4c38837..1fc9bd6 100644 --- a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Rectangle.h +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Rectangle.h @@ -190,13 +190,17 @@ public: Rectangle withZeroOrigin() const noexcept { return Rectangle (w, h); } /** Returns a rectangle which has the same position and height as this one, but with a different width. */ - Rectangle withWidth (ValueType newWidth) const noexcept { return Rectangle (pos.x, pos.y, newWidth, h); } + Rectangle withWidth (ValueType newWidth) const noexcept { return Rectangle (pos.x, pos.y, newWidth, h); } /** Returns a rectangle which has the same position and width as this one, but with a different height. */ - Rectangle withHeight (ValueType newHeight) const noexcept { return Rectangle (pos.x, pos.y, w, newHeight); } + Rectangle withHeight (ValueType newHeight) const noexcept { return Rectangle (pos.x, pos.y, w, newHeight); } - /** Returns a rectangle with the same position as this one, but a new size. */ - Rectangle withSize (ValueType newWidth, const ValueType newHeight) const noexcept { return Rectangle (pos.x, pos.y, newWidth, newHeight); } + /** Returns a rectangle with the same top-left position as this one, but a new size. */ + Rectangle withSize (ValueType newWidth, ValueType newHeight) const noexcept { return Rectangle (pos.x, pos.y, newWidth, newHeight); } + + /** Returns a rectangle with the same centre position as this one, but a new size. */ + Rectangle withSizeKeepingCentre (ValueType newWidth, ValueType newHeight) const noexcept { return Rectangle (pos.x + (w - newWidth) / (ValueType) 2, + pos.y + (h - newHeight) / (ValueType) 2, newWidth, newHeight); } /** Moves the x position, adjusting the width so that the right-hand edge remains in the same place. If the x is moved to be on the right of the current right-hand edge, the width will be set to zero. @@ -865,7 +869,7 @@ public: static Rectangle fromString (StringRef stringVersion) { StringArray toks; - toks.addTokens (stringVersion.text.findEndOfWhitespace(), ",; \t\r\n", String::empty); + toks.addTokens (stringVersion.text.findEndOfWhitespace(), ",; \t\r\n", ""); return Rectangle (parseIntAfterSpace (toks[0]), parseIntAfterSpace (toks[1]), diff --git a/JuceLibraryCode/modules/juce_graphics/juce_module_info b/JuceLibraryCode/modules/juce_graphics/juce_module_info index 2b0fcdc..6c6e850 100644 --- a/JuceLibraryCode/modules/juce_graphics/juce_module_info +++ b/JuceLibraryCode/modules/juce_graphics/juce_module_info @@ -1,7 +1,7 @@ { "id": "juce_graphics", "name": "JUCE graphics classes", - "version": "3.0.0", + "version": "3.0.1", "description": "Classes for 2D vector graphics, image loading/saving, font handling, etc.", "website": "http://www.juce.com/juce", "license": "GPL/Commercial", diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_android_Fonts.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_android_Fonts.cpp index cbd3119..837dd6b 100644 --- a/JuceLibraryCode/modules/juce_graphics/native/juce_android_Fonts.cpp +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_android_Fonts.cpp @@ -291,7 +291,7 @@ private: file = getFontFile (family, "Regular"); if (! file.exists()) - file = getFontFile (family, String::empty); + file = getFontFile (family, String()); return file; } @@ -315,6 +315,12 @@ Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) return new AndroidTypeface (font); } +Typeface::Ptr Typeface::createSystemTypefaceFor (const void*, size_t) +{ + jassertfalse; // not yet implemented! + return nullptr; +} + void Typeface::scanFolderForFonts (const File&) { jassertfalse; // not available unless using FreeType diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_freetype_Fonts.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_freetype_Fonts.cpp index 5b3f0f2..43a6436 100644 --- a/JuceLibraryCode/modules/juce_graphics/native/juce_freetype_Fonts.cpp +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_freetype_Fonts.cpp @@ -56,6 +56,14 @@ struct FTFaceWrapper : public ReferenceCountedObject face = 0; } + FTFaceWrapper (const FTLibWrapper::Ptr& ftLib, const void* data, size_t dataSize, int faceIndex) + : face (0), library (ftLib), savedFaceData (data, dataSize) + { + if (FT_New_Memory_Face (ftLib->library, (const FT_Byte*) savedFaceData.getData(), + (FT_Long) savedFaceData.getSize(), faceIndex, &face) != 0) + face = 0; + } + ~FTFaceWrapper() { if (face != 0) @@ -64,8 +72,9 @@ struct FTFaceWrapper : public ReferenceCountedObject FT_Face face; FTLibWrapper::Ptr library; + MemoryBlock savedFaceData; - typedef ReferenceCountedObjectPtr Ptr; + typedef ReferenceCountedObjectPtr Ptr; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTFaceWrapper) }; @@ -106,24 +115,34 @@ public: }; //============================================================================== + static FTFaceWrapper::Ptr selectUnicodeCharmap (FTFaceWrapper* face) + { + if (face != nullptr) + if (FT_Select_Charmap (face->face, ft_encoding_unicode) != 0) + FT_Set_Charmap (face->face, face->face->charmaps[0]); + + return face; + } + + FTFaceWrapper::Ptr createFace (const void* data, size_t dataSize, int index) + { + return selectUnicodeCharmap (new FTFaceWrapper (library, data, dataSize, index)); + } + + FTFaceWrapper::Ptr createFace (const File& file, int index) + { + return selectUnicodeCharmap (new FTFaceWrapper (library, file, index)); + } + FTFaceWrapper::Ptr createFace (const String& fontName, const String& fontStyle) { const KnownTypeface* ftFace = matchTypeface (fontName, fontStyle); if (ftFace == nullptr) ftFace = matchTypeface (fontName, "Regular"); - if (ftFace == nullptr) ftFace = matchTypeface (fontName, String::empty); + if (ftFace == nullptr) ftFace = matchTypeface (fontName, String()); if (ftFace != nullptr) - { - if (FTFaceWrapper::Ptr face = new FTFaceWrapper (library, ftFace->file, ftFace->faceIndex)) - { - // If there isn't a unicode charmap then select the first one. - if (FT_Select_Charmap (face->face, ft_encoding_unicode) != 0) - FT_Set_Charmap (face->face, face->face->charmaps[0]); - - return face; - } - } + return createFace (ftFace->file, ftFace->faceIndex); return nullptr; } @@ -272,20 +291,27 @@ class FreeTypeTypeface : public CustomTypeface { public: FreeTypeTypeface (const Font& font) - : faceWrapper (FTTypefaceList::getInstance() - ->createFace (font.getTypefaceName(), font.getTypefaceStyle())) + : faceWrapper (FTTypefaceList::getInstance()->createFace (font.getTypefaceName(), + font.getTypefaceStyle())) { if (faceWrapper != nullptr) - { - setCharacteristics (font.getTypefaceName(), - font.getTypefaceStyle(), - faceWrapper->face->ascender / (float) (faceWrapper->face->ascender - faceWrapper->face->descender), - L' '); - } - else - { - DBG ("Failed to create typeface: " << font.toString()); - } + initialiseCharacteristics (font.getTypefaceName(), + font.getTypefaceStyle()); + } + + FreeTypeTypeface (const void* data, size_t dataSize) + : faceWrapper (FTTypefaceList::getInstance()->createFace (data, dataSize, 0)) + { + if (faceWrapper != nullptr) + initialiseCharacteristics (faceWrapper->face->family_name, + faceWrapper->face->style_name); + } + + void initialiseCharacteristics (const String& name, const String& style) + { + setCharacteristics (name, style, + faceWrapper->face->ascender / (float) (faceWrapper->face->ascender - faceWrapper->face->descender), + L' '); } bool loadGlyphIfPossible (const juce_wchar character) @@ -295,7 +321,7 @@ public: FT_Face face = faceWrapper->face; const unsigned int glyphIndex = FT_Get_Char_Index (face, character); - if (FT_Load_Glyph (face, glyphIndex, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM) == 0 + if (FT_Load_Glyph (face, glyphIndex, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM | FT_LOAD_NO_HINTING) == 0 && face->glyph->format == ft_glyph_format_outline) { const float scale = 1.0f / (float) (face->ascender - face->descender); diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_linux_Fonts.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_linux_Fonts.cpp index cf54d62..0869ec7 100644 --- a/JuceLibraryCode/modules/juce_graphics/native/juce_linux_Fonts.cpp +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_linux_Fonts.cpp @@ -43,7 +43,7 @@ StringArray FTTypefaceList::getDefaultFontDirectories() { if (e->getStringAttribute ("prefix") == "xdg") { - String xdgDataHome (SystemStats::getEnvironmentVariable ("XDG_DATA_HOME", String::empty)); + String xdgDataHome (SystemStats::getEnvironmentVariable ("XDG_DATA_HOME", String())); if (xdgDataHome.trimStart().isEmpty()) xdgDataHome = "~/.local/share"; @@ -69,6 +69,11 @@ Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) return new FreeTypeTypeface (font); } +Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize) +{ + return new FreeTypeTypeface (data, dataSize); +} + void Typeface::scanFolderForFonts (const File& folder) { FTTypefaceList::getInstance()->scanFontPaths (StringArray (folder.getFullPathName())); diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_Fonts.mm b/JuceLibraryCode/modules/juce_graphics/native/juce_mac_Fonts.mm index 18eca77..2dfa209 100644 --- a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_Fonts.mm +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_mac_Fonts.mm @@ -496,28 +496,73 @@ public: if (ctFontRef != nullptr) { - const float ctAscent = std::abs ((float) CTFontGetAscent (ctFontRef)); - const float ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef)); - const float ctTotalHeight = ctAscent + ctDescent; + fontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr); + initialiseMetrics(); + } + } - ascent = ctAscent / ctTotalHeight; - unitsToHeightScaleFactor = 1.0f / ctTotalHeight; - pathTransform = AffineTransform::identity.scale (unitsToHeightScaleFactor); + OSXTypeface (const void* data, size_t dataSize) + : Typeface (String(), String()), + fontRef (nullptr), + ctFontRef (nullptr), + fontHeightToPointsFactor (1.0f), + renderingTransform (CGAffineTransformIdentity), + attributedStringAtts (nullptr), + ascent (0.0f), + unitsToHeightScaleFactor (0.0f) + { + CFDataRef cfData = CFDataCreate (kCFAllocatorDefault, (const UInt8*) data, (CFIndex) dataSize); + CGDataProviderRef provider = CGDataProviderCreateWithCFData (cfData); + CFRelease (cfData); - fontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr); - fontHeightToPointsFactor = referenceFontSize / ctTotalHeight; + fontRef = CGFontCreateWithDataProvider (provider); + CGDataProviderRelease (provider); - const short zero = 0; - CFNumberRef numberRef = CFNumberCreate (0, kCFNumberShortType, &zero); + if (fontRef != nullptr) + { + ctFontRef = CTFontCreateWithGraphicsFont (fontRef, referenceFontSize, nullptr, nullptr); - CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName }; - CFTypeRef values[] = { ctFontRef, numberRef }; - attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), - &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - CFRelease (numberRef); + if (ctFontRef != nullptr) + { + if (CFStringRef fontName = CTFontCopyName (ctFontRef, kCTFontFamilyNameKey)) + { + name = String::fromCFString (fontName); + CFRelease (fontName); + } + + if (CFStringRef fontStyle = CTFontCopyName (ctFontRef, kCTFontStyleNameKey)) + { + style = String::fromCFString (fontStyle); + CFRelease (fontStyle); + } + + initialiseMetrics(); + } } } + void initialiseMetrics() + { + const float ctAscent = std::abs ((float) CTFontGetAscent (ctFontRef)); + const float ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef)); + const float ctTotalHeight = ctAscent + ctDescent; + + ascent = ctAscent / ctTotalHeight; + unitsToHeightScaleFactor = 1.0f / ctTotalHeight; + pathTransform = AffineTransform::identity.scale (unitsToHeightScaleFactor); + + fontHeightToPointsFactor = referenceFontSize / ctTotalHeight; + + const short zero = 0; + CFNumberRef numberRef = CFNumberCreate (0, kCFNumberShortType, &zero); + + CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName }; + CFTypeRef values[] = { ctFontRef, numberRef }; + attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFRelease (numberRef); + } + ~OSXTypeface() { if (attributedStringAtts != nullptr) @@ -730,7 +775,7 @@ StringArray Font::findAllTypefaceStyles (const String& family) CTFontDescriptorRef ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes); CFRelease (fontDescAttributes); - CFArrayRef fontFamilyArray = CFArrayCreate(kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks); + CFArrayRef fontFamilyArray = CFArrayCreate (kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks); CFRelease (ctFontDescRef); CTFontCollectionRef fontCollectionRef = CTFontCollectionCreateWithFontDescriptors (fontFamilyArray, nullptr); @@ -1202,6 +1247,11 @@ Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) return new OSXTypeface (font); } +Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize) +{ + return new OSXTypeface (data, dataSize); +} + void Typeface::scanFolderForFonts (const File&) { jassertfalse; // not implemented on this platform diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp index 3d92634..8cd8ddc 100644 --- a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp @@ -163,30 +163,37 @@ public: break; } + jassert (dwFont != nullptr); hr = dwFont->CreateFontFace (dwFontFace.resetAndGetPointerAddress()); - DWRITE_FONT_METRICS dwFontMetrics; - dwFontFace->GetMetrics (&dwFontMetrics); - - // All Font Metrics are in design units so we need to get designUnitsPerEm value - // to get the metrics into Em/Design Independent Pixels - designUnitsPerEm = dwFontMetrics.designUnitsPerEm; - - ascent = std::abs ((float) dwFontMetrics.ascent); - const float totalSize = ascent + std::abs ((float) dwFontMetrics.descent); - ascent /= totalSize; - unitsToHeightScaleFactor = designUnitsPerEm / totalSize; - - HDC tempDC = GetDC (0); - heightToPointsFactor = (72.0f / GetDeviceCaps (tempDC, LOGPIXELSY)) * unitsToHeightScaleFactor; - ReleaseDC (0, tempDC); - - const float pathAscent = (1024.0f * dwFontMetrics.ascent) / designUnitsPerEm; - const float pathDescent = (1024.0f * dwFontMetrics.descent) / designUnitsPerEm; - const float pathScale = 1.0f / (std::abs (pathAscent) + std::abs (pathDescent)); - pathTransform = AffineTransform::scale (pathScale); + if (dwFontFace != nullptr) + { + DWRITE_FONT_METRICS dwFontMetrics; + dwFontFace->GetMetrics (&dwFontMetrics); + + // All Font Metrics are in design units so we need to get designUnitsPerEm value + // to get the metrics into Em/Design Independent Pixels + designUnitsPerEm = dwFontMetrics.designUnitsPerEm; + + ascent = std::abs ((float) dwFontMetrics.ascent); + const float totalSize = ascent + std::abs ((float) dwFontMetrics.descent); + ascent /= totalSize; + unitsToHeightScaleFactor = designUnitsPerEm / totalSize; + + HDC tempDC = GetDC (0); + float dpi = (GetDeviceCaps (tempDC, LOGPIXELSX) + GetDeviceCaps (tempDC, LOGPIXELSY)) / 2.0f; + heightToPointsFactor = (dpi / GetDeviceCaps (tempDC, LOGPIXELSY)) * unitsToHeightScaleFactor; + ReleaseDC (0, tempDC); + + const float pathAscent = (1024.0f * dwFontMetrics.ascent) / designUnitsPerEm; + const float pathDescent = (1024.0f * dwFontMetrics.descent) / designUnitsPerEm; + const float pathScale = 1.0f / (std::abs (pathAscent) + std::abs (pathDescent)); + pathTransform = AffineTransform::scale (pathScale); + } } + bool loadedOk() const noexcept { return dwFontFace != nullptr; } + float getAscent() const { return ascent; } float getDescent() const { return 1.0f - ascent; } float getHeightToPointsFactor() const { return heightToPointsFactor; } diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_Fonts.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_win32_Fonts.cpp index b8455eb..a5448f3 100644 --- a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_Fonts.cpp +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_win32_Fonts.cpp @@ -22,6 +22,115 @@ ============================================================================== */ +/* This is some quick-and-dirty code to extract the typeface name from a lump of TTF file data. + It's needed because although win32 will happily load a TTF file from in-memory data, it won't + tell you the name of the damned font that it just loaded.. and in order to actually use the font, + you need to know its name!! Anyway, this awful hack seems to work for most fonts. +*/ +namespace TTFNameExtractor +{ + struct OffsetTable + { + uint32 version; + uint16 numTables, searchRange, entrySelector, rangeShift; + }; + + struct TableDirectory + { + char tag[4]; + uint32 checkSum, offset, length; + }; + + struct NamingTable + { + uint16 formatSelector; + uint16 numberOfNameRecords; + uint16 offsetStartOfStringStorage; + }; + + struct NameRecord + { + uint16 platformID, encodingID, languageID; + uint16 nameID, stringLength, offsetFromStorageArea; + }; + + static String parseNameRecord (MemoryInputStream& input, const NameRecord& nameRecord, + const int64 directoryOffset, const int64 offsetOfStringStorage) + { + String result; + const int64 oldPos = input.getPosition(); + input.setPosition (directoryOffset + offsetOfStringStorage + ByteOrder::swapIfLittleEndian (nameRecord.offsetFromStorageArea)); + const int stringLength = (int) ByteOrder::swapIfLittleEndian (nameRecord.stringLength); + const int platformID = ByteOrder::swapIfLittleEndian (nameRecord.platformID); + + if (platformID == 0 || platformID == 3) + { + const int numChars = stringLength / 2 + 1; + HeapBlock buffer; + buffer.calloc (numChars + 1); + input.read (buffer, stringLength); + + for (int i = 0; i < numChars; ++i) + buffer[i] = ByteOrder::swapIfLittleEndian (buffer[i]); + + static_jassert (sizeof (CharPointer_UTF16::CharType) == sizeof (uint16)); + result = CharPointer_UTF16 ((CharPointer_UTF16::CharType*) buffer.getData()); + } + else + { + HeapBlock buffer; + buffer.calloc (stringLength + 1); + input.read (buffer, stringLength); + result = CharPointer_UTF8 (buffer.getData()); + } + + input.setPosition (oldPos); + return result; + } + + static String parseNameTable (MemoryInputStream& input, int64 directoryOffset) + { + input.setPosition (directoryOffset); + + NamingTable namingTable = { 0 }; + input.read (&namingTable, sizeof (namingTable)); + + for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (namingTable.numberOfNameRecords); ++i) + { + NameRecord nameRecord = { 0 }; + input.read (&nameRecord, sizeof (nameRecord)); + + if (ByteOrder::swapIfLittleEndian (nameRecord.nameID) == 4) + { + const String result (parseNameRecord (input, nameRecord, directoryOffset, + ByteOrder::swapIfLittleEndian (namingTable.offsetStartOfStringStorage))); + + if (result.isNotEmpty()) + return result; + } + } + + return String(); + } + + static String getTypefaceNameFromFile (MemoryInputStream& input) + { + OffsetTable offsetTable = { 0 }; + input.read (&offsetTable, sizeof (offsetTable)); + + for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (offsetTable.numTables); ++i) + { + TableDirectory tableDirectory = { 0 }; + input.read (&tableDirectory, sizeof (tableDirectory)); + + if (String (tableDirectory.tag, sizeof (tableDirectory.tag)).equalsIgnoreCase ("name")) + return parseNameTable (input, ByteOrder::swapIfLittleEndian (tableDirectory.offset)); + } + + return String(); + } +} + namespace FontEnumerators { static int CALLBACK fontEnum2 (ENUMLOGFONTEXW* lpelfe, NEWTEXTMETRICEXW*, int type, LPARAM lParam) @@ -204,23 +313,29 @@ class WindowsTypeface : public Typeface { public: WindowsTypeface (const Font& font) - : Typeface (font.getTypefaceName(), - font.getTypefaceStyle()), - fontH (0), - previousFontH (0), - dc (CreateCompatibleDC (0)), + : Typeface (font.getTypefaceName(), font.getTypefaceStyle()), + fontH (0), previousFontH (0), + dc (CreateCompatibleDC (0)), memoryFont (0), ascent (1.0f), heightToPointsFactor (1.0f), defaultGlyph (-1) { loadFont(); + } - if (GetTextMetrics (dc, &tm)) - { - heightToPointsFactor = (72.0f / GetDeviceCaps (dc, LOGPIXELSY)) * heightInPoints / (float) tm.tmHeight; - ascent = tm.tmAscent / (float) tm.tmHeight; - defaultGlyph = getGlyphForChar (dc, tm.tmDefaultChar); - createKerningPairs (dc, (float) tm.tmHeight); - } + WindowsTypeface (const void* data, size_t dataSize) + : Typeface (String(), String()), + fontH (0), previousFontH (0), + dc (CreateCompatibleDC (0)), memoryFont (0), + ascent (1.0f), heightToPointsFactor (1.0f), + defaultGlyph (-1) + { + DWORD numInstalled = 0; + memoryFont = AddFontMemResourceEx (const_cast (data), (DWORD) dataSize, + nullptr, &numInstalled); + + MemoryInputStream m (data, dataSize, false); + name = TTFNameExtractor::getTypefaceNameFromFile (m); + loadFont(); } ~WindowsTypeface() @@ -230,6 +345,9 @@ public: if (fontH != 0) DeleteObject (fontH); + + if (memoryFont != 0) + RemoveFontMemResourceEx (memoryFont); } float getAscent() const { return ascent; } @@ -353,6 +471,7 @@ private: HGDIOBJ previousFontH; HDC dc; TEXTMETRIC tm; + HANDLE memoryFont; float ascent, heightToPointsFactor; int defaultGlyph, heightInPoints; @@ -411,6 +530,15 @@ private: } } } + + if (GetTextMetrics (dc, &tm)) + { + float dpi = (GetDeviceCaps (dc, LOGPIXELSX) + GetDeviceCaps (dc, LOGPIXELSY)) / 2.0f; + heightToPointsFactor = (dpi / GetDeviceCaps (dc, LOGPIXELSY)) * heightInPoints / (float) tm.tmHeight; + ascent = tm.tmAscent / (float) tm.tmHeight; + defaultGlyph = getGlyphForChar (dc, tm.tmDefaultChar); + createKerningPairs (dc, (float) tm.tmHeight); + } } void createKerningPairs (HDC dc, const float height) @@ -493,12 +621,22 @@ Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) const Direct2DFactories& factories = Direct2DFactories::getInstance(); if (factories.systemFonts != nullptr) - return new WindowsDirectWriteTypeface (font, factories.systemFonts); + { + ScopedPointer wtf (new WindowsDirectWriteTypeface (font, factories.systemFonts)); + + if (wtf->loadedOk()) + return wtf.release(); + } #endif return new WindowsTypeface (font); } +Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize) +{ + return new WindowsTypeface (data, dataSize); +} + void Typeface::scanFolderForFonts (const File&) { jassertfalse; // not implemented on this platform diff --git a/JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.cpp b/JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.cpp index 93b54be..e96ff68 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.cpp @@ -22,7 +22,6 @@ ============================================================================== */ - JUCEApplication::JUCEApplication() {} JUCEApplication::~JUCEApplication() {} diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.cpp b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.cpp index 502f4a9..ac93013 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.cpp @@ -186,7 +186,7 @@ void Button::setToggleState (const bool shouldBeOn, const NotificationType notif // async callbacks aren't possible here jassert (notification != sendNotificationAsync); - sendClickMessage (ModifierKeys()); + sendClickMessage (ModifierKeys::getCurrentModifiers()); if (deletionWatcher == nullptr) return; diff --git a/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandManager.cpp b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandManager.cpp index ad88cd0..4f4ff46 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandManager.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandManager.cpp @@ -127,7 +127,7 @@ String ApplicationCommandManager::getNameOfCommand (const CommandID commandID) c if (const ApplicationCommandInfo* const ci = getCommandForID (commandID)) return ci->shortName; - return String::empty; + return String(); } String ApplicationCommandManager::getDescriptionOfCommand (const CommandID commandID) const noexcept @@ -136,7 +136,7 @@ String ApplicationCommandManager::getDescriptionOfCommand (const CommandID comma return ci->description.isNotEmpty() ? ci->description : ci->shortName; - return String::empty; + return String(); } StringArray ApplicationCommandManager::getCommandCategories() const diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.cpp b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.cpp index de4244e..414dc36 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.cpp @@ -1443,25 +1443,25 @@ Component* Component::getComponentAt (const int x, const int y) } //============================================================================== -void Component::addChildComponent (Component* const child, int zOrder) +void Component::addChildComponent (Component& child, int zOrder) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. CHECK_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN - if (child != nullptr && child->parentComponent != this) + if (child.parentComponent != this) { - if (child->parentComponent != nullptr) - child->parentComponent->removeChildComponent (child); + if (child.parentComponent != nullptr) + child.parentComponent->removeChildComponent (&child); else - child->removeFromDesktop(); + child.removeFromDesktop(); - child->parentComponent = this; + child.parentComponent = this; - if (child->isVisible()) - child->repaintParent(); + if (child.isVisible()) + child.repaintParent(); - if (! child->isAlwaysOnTop()) + if (! child.isAlwaysOnTop()) { if (zOrder < 0 || zOrder > childComponentList.size()) zOrder = childComponentList.size(); @@ -1475,20 +1475,29 @@ void Component::addChildComponent (Component* const child, int zOrder) } } - childComponentList.insert (zOrder, child); + childComponentList.insert (zOrder, &child); - child->internalHierarchyChanged(); + child.internalHierarchyChanged(); internalChildrenChanged(); } } +void Component::addAndMakeVisible (Component& child, int zOrder) +{ + child.setVisible (true); + addChildComponent (child, zOrder); +} + +void Component::addChildComponent (Component* const child, int zOrder) +{ + if (child != nullptr) + addChildComponent (*child, zOrder); +} + void Component::addAndMakeVisible (Component* const child, int zOrder) { if (child != nullptr) - { - child->setVisible (true); - addChildComponent (child, zOrder); - } + addAndMakeVisible (*child, zOrder); } void Component::addChildAndSetID (Component* const child, const String& childID) diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.h b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.h index e3e4e31..00ef6ec 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.h +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.h @@ -178,7 +178,7 @@ public: object that it is using. Otherwise, it will return the window of its top-level parent component. - This may return 0 if there isn't a desktop component. + This may return nullptr if there isn't a desktop component. @see addToDesktop, isOnDesktop */ @@ -703,13 +703,38 @@ public: */ void addChildComponent (Component* child, int zOrder = -1); - /** Adds a child component to this one, and also makes the child visible if it isn't. + /** Adds a child component to this one. + + Adding a child component does not mean that the component will own or delete the child - it's + your responsibility to delete the component. Note that it's safe to delete a component + without first removing it from its parent - doing so will automatically remove it and + send out the appropriate notifications before the deletion completes. + + If the child is already a child of this component, then no action will be taken, and its + z-order will be left unchanged. + + @param child the new component to add. If the component passed-in is already + the child of another component, it'll first be removed from it current parent. + @param zOrder The index in the child-list at which this component should be inserted. + A value of -1 will insert it in front of the others, 0 is the back. + @see removeChildComponent, addAndMakeVisible, addChildAndSetID, getChild, ComponentListener::componentChildrenChanged + */ + void addChildComponent (Component& child, int zOrder = -1); + + /** Adds a child component to this one, and also makes the child visible if it isn't already. - Quite a useful function, this is just the same as calling setVisible (true) on the child - and then addChildComponent(). See addChildComponent() for more details. + This is the same as calling setVisible (true) on the child and then addChildComponent(). + See addChildComponent() for more details. */ void addAndMakeVisible (Component* child, int zOrder = -1); + /** Adds a child component to this one, and also makes the child visible if it isn't already. + + This is the same as calling setVisible (true) on the child and then addChildComponent(). + See addChildComponent() for more details. + */ + void addAndMakeVisible (Component& child, int zOrder = -1); + /** Adds a child component to this one, makes it visible, and sets its component ID. @see addAndMakeVisible, addChildComponent */ diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_Drawable.h b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_Drawable.h index da67b22..6a59c59 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_Drawable.h +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_Drawable.h @@ -142,7 +142,7 @@ public: into a Drawable tree. The object returned must be deleted by the caller. If something goes wrong - while parsing, it may return 0. + while parsing, it may return nullptr. SVG is a pretty large and complex spec, and this doesn't aim to be a full implementation, but it can return the basic vector objects. diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_SVGParser.cpp b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_SVGParser.cpp index fa19b2e..198bfdb 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_SVGParser.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_SVGParser.cpp @@ -944,7 +944,7 @@ private: } String getStyleAttribute (const XmlPath& xml, const String& attributeName, - const String& defaultValue = String::empty) const + const String& defaultValue = String()) const { if (xml->hasAttribute (attributeName)) return xml->getStringAttribute (attributeName, defaultValue); @@ -953,7 +953,7 @@ private: if (styleAtt.isNotEmpty()) { - const String value (getAttributeFromStyleList (styleAtt, attributeName, String::empty)); + const String value (getAttributeFromStyleList (styleAtt, attributeName, String())); if (value.isNotEmpty()) return value; @@ -991,7 +991,7 @@ private: if (xml.parent != nullptr) return getInheritedAttribute (*xml.parent, attributeName); - return String::empty; + return String(); } //============================================================================== @@ -1144,7 +1144,7 @@ private: StringArray tokens; tokens.addTokens (t.fromFirstOccurrenceOf ("(", false, false) .upToFirstOccurrenceOf (")", false, false), - ", ", String::empty); + ", ", ""); tokens.removeEmptyStrings (true); diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.cpp index f3e62c7..97e9482 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.cpp @@ -136,7 +136,7 @@ File DirectoryContentsList::getFile (const int index) const if (const FileInfo* const info = files [index]) return root.getChildFile (info->filename); - return File::nonexistent; + return File(); } bool DirectoryContentsList::contains (const File& targetFile) const diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp index 33c485d..4a2f023 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp @@ -84,19 +84,19 @@ FileBrowserComponent::FileBrowserComponent (int flags_, fileListComponent->addListener (this); - addAndMakeVisible (¤tPathBox); + addAndMakeVisible (currentPathBox); currentPathBox.setEditableText (true); resetRecentPaths(); currentPathBox.addListener (this); - addAndMakeVisible (&filenameBox); + addAndMakeVisible (filenameBox); filenameBox.setMultiLine (false); filenameBox.setSelectAllWhenFocused (true); filenameBox.setText (filename, false); filenameBox.addListener (this); filenameBox.setReadOnly ((flags & (filenameBoxIsReadOnly | canSelectMultipleItems)) != 0); - addAndMakeVisible (&fileLabel); + addAndMakeVisible (fileLabel); fileLabel.attachToComponent (&filenameBox, true); addAndMakeVisible (goUpButton = getLookAndFeel().createFileBrowserGoUpButton()); diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp index 745f8ee..adf1a74 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp @@ -33,15 +33,15 @@ public: newFolderButton (TRANS ("New Folder")), instructions (desc) { - addAndMakeVisible (&chooserComponent); + addAndMakeVisible (chooserComponent); - addAndMakeVisible (&okButton); + addAndMakeVisible (okButton); okButton.addShortcut (KeyPress (KeyPress::returnKey)); - addAndMakeVisible (&cancelButton); + addAndMakeVisible (cancelButton); cancelButton.addShortcut (KeyPress (KeyPress::escapeKey)); - addChildComponent (&newFolderButton); + addChildComponent (newFolderButton); setInterceptsMouseClicks (false, true); } diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp index 2840480..a2cad46 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp @@ -30,23 +30,23 @@ FileSearchPathListComponent::FileSearchPathListComponent() downButton (String::empty, DrawableButton::ImageOnButtonBackground) { listBox.setModel (this); - addAndMakeVisible (&listBox); + addAndMakeVisible (listBox); listBox.setColour (ListBox::backgroundColourId, Colours::black.withAlpha (0.02f)); listBox.setColour (ListBox::outlineColourId, Colours::black.withAlpha (0.1f)); listBox.setOutlineThickness (1); - addAndMakeVisible (&addButton); + addAndMakeVisible (addButton); addButton.addListener (this); addButton.setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight | Button::ConnectedOnBottom | Button::ConnectedOnTop); - addAndMakeVisible (&removeButton); + addAndMakeVisible (removeButton); removeButton.addListener (this); removeButton.setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight | Button::ConnectedOnBottom | Button::ConnectedOnTop); - addAndMakeVisible (&changeButton); + addAndMakeVisible (changeButton); changeButton.addListener (this); - addAndMakeVisible (&upButton); + addAndMakeVisible (upButton); upButton.addListener (this); { @@ -59,7 +59,7 @@ FileSearchPathListComponent::FileSearchPathListComponent() upButton.setImages (&arrowImage); } - addAndMakeVisible (&downButton); + addAndMakeVisible (downButton); downButton.addListener (this); { diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp index 84c5c64..e4f18bc 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp @@ -38,7 +38,7 @@ FilenameComponent::FilenameComponent (const String& name, wildcard (fileBrowserWildcard), enforcedSuffix (suffix) { - addAndMakeVisible (&filenameBox); + addAndMakeVisible (filenameBox); filenameBox.setEditableText (canEditFilename); filenameBox.addListener (this); filenameBox.setTextWhenNothingSelected (textWhenNothingSelected); diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.cpp index 8089683..537aae3 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.cpp @@ -58,7 +58,7 @@ void ImagePreviewComponent::timerCallback() stopTimer(); currentThumbnail = Image::null; - currentDetails = String::empty; + currentDetails.clear(); repaint(); ScopedPointer in (fileToLoad.createInputStream()); diff --git a/JuceLibraryCode/modules/juce_gui_basics/juce_module_info b/JuceLibraryCode/modules/juce_gui_basics/juce_module_info index e3ef339..a66035f 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/juce_module_info +++ b/JuceLibraryCode/modules/juce_gui_basics/juce_module_info @@ -1,7 +1,7 @@ { "id": "juce_gui_basics", "name": "JUCE GUI core classes", - "version": "3.0.0", + "version": "3.0.1", "description": "Basic user-interface components and related classes.", "website": "http://www.juce.com/juce", "license": "GPL/Commercial", diff --git a/JuceLibraryCode/modules/juce_gui_basics/keyboard/juce_KeyboardFocusTraverser.h b/JuceLibraryCode/modules/juce_gui_basics/keyboard/juce_KeyboardFocusTraverser.h index a22311d..94ef5f7 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/keyboard/juce_KeyboardFocusTraverser.h +++ b/JuceLibraryCode/modules/juce_gui_basics/keyboard/juce_KeyboardFocusTraverser.h @@ -60,7 +60,7 @@ public: The default implementation will return the next component which is to the right of or below this one. - This may return 0 if there's no suitable candidate. + This may return nullptr if there's no suitable candidate. */ virtual Component* getNextComponent (Component* current); @@ -70,7 +70,7 @@ public: The default implementation will return the next component which is to the left of or above this one. - This may return 0 if there's no suitable candidate. + This may return nullptr if there's no suitable candidate. */ virtual Component* getPreviousComponent (Component* current); @@ -80,7 +80,7 @@ public: The default implementation will just return the foremost child component that wants focus. - This may return 0 if there's no suitable candidate. + This may return nullptr if there's no suitable candidate. */ virtual Component* getDefaultComponent (Component* parentComponent); }; diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ComponentAnimator.cpp b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ComponentAnimator.cpp index efc952d..b7d052c 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ComponentAnimator.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ComponentAnimator.cpp @@ -137,6 +137,7 @@ public: public: ProxyComponent (Component& c) { + setWantsKeyboardFocus (false); setBounds (c.getBounds()); setTransform (c.getTransform()); setAlpha (c.getAlpha()); diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_Viewport.cpp b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_Viewport.cpp index d026740..d4d0e72 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_Viewport.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_Viewport.cpp @@ -34,11 +34,11 @@ Viewport::Viewport (const String& name) horizontalScrollBar (false) { // content holder is used to clip the contents so they don't overlap the scrollbars - addAndMakeVisible (&contentHolder); + addAndMakeVisible (contentHolder); contentHolder.setInterceptsMouseClicks (false, true); - addChildComponent (&verticalScrollBar); - addChildComponent (&horizontalScrollBar); + addChildComponent (verticalScrollBar); + addChildComponent (horizontalScrollBar); verticalScrollBar.addListener (this); horizontalScrollBar.addListener (this); diff --git a/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V1.h b/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V1.h index 8b977bc..bc2b6db 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V1.h +++ b/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V1.h @@ -98,4 +98,4 @@ private: }; -#endif // JUCE_LOOKANDFEEL_H_INCLUDED +#endif // JUCE_LOOKANDFEEL_V1_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp b/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp index 9b3555f..bf6ebe8 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp @@ -819,17 +819,17 @@ int LookAndFeel_V2::getTreeViewIndentSize (TreeView&) //============================================================================== void LookAndFeel_V2::drawBubble (Graphics& g, BubbleComponent& comp, - const Point& tip, const Rectangle& body) + const Point& tip, const Rectangle& body) { Path p; - p.addBubble (body, body.getUnion (Rectangle (tip.x, tip.y, 1.0f, 1.0f)), + p.addBubble (body.reduced (0.5f), body.getUnion (Rectangle (tip.x, tip.y, 1.0f, 1.0f)), tip, 5.0f, jmin (15.0f, body.getWidth() * 0.2f, body.getHeight() * 0.2f)); g.setColour (comp.findColour (BubbleComponent::backgroundColourId)); g.fillPath (p); g.setColour (comp.findColour (BubbleComponent::outlineColourId)); - g.strokePath (p, PathStrokeType (1.33f)); + g.strokePath (p, PathStrokeType (1.0f)); } @@ -900,24 +900,23 @@ void LookAndFeel_V2::drawPopupMenuUpDownArrow (Graphics& g, int width, int heigh g.fillPath (p); } -void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, int width, int height, +void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, const Rectangle& area, const bool isSeparator, const bool isActive, const bool isHighlighted, const bool isTicked, const bool hasSubMenu, const String& text, const String& shortcutKeyText, - Image* image, const Colour* const textColourToUse) + const Drawable* icon, const Colour* const textColourToUse) { - const float halfH = height * 0.5f; - if (isSeparator) { - const float separatorIndent = 5.5f; + Rectangle r (area.reduced (5, 0)); + r.removeFromTop (r.getHeight() / 2 - 1); g.setColour (Colour (0x33000000)); - g.drawLine (separatorIndent, halfH, width - separatorIndent, halfH); + g.fillRect (r.removeFromTop (1)); g.setColour (Colour (0x66ffffff)); - g.drawLine (separatorIndent, halfH + 1.0f, width - separatorIndent, halfH + 1.0f); + g.fillRect (r.removeFromTop (1)); } else { @@ -926,10 +925,12 @@ void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, int width, int height, if (textColourToUse != nullptr) textColour = *textColourToUse; + Rectangle r (area.reduced (1)); + if (isHighlighted) { g.setColour (findColour (PopupMenu::highlightedBackgroundColourId)); - g.fillRect (1, 1, width - 2, height - 2); + g.fillRect (r); g.setColour (findColour (PopupMenu::highlightedTextColourId)); } @@ -943,51 +944,31 @@ void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, int width, int height, Font font (getPopupMenuFont()); - if (font.getHeight() > height / 1.3f) - font.setHeight (height / 1.3f); + const float maxFontHeight = area.getHeight() / 1.3f; + + if (font.getHeight() > maxFontHeight) + font.setHeight (maxFontHeight); g.setFont (font); - const int leftBorder = (height * 5) / 4; - const int rightBorder = 4; + Rectangle iconArea (r.removeFromLeft ((r.getHeight() * 5) / 4).reduced (3).toFloat()); - if (image != nullptr) + if (icon != nullptr) { - g.drawImageWithin (*image, - 2, 1, leftBorder - 4, height - 2, - RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, false); + icon->drawWithin (g, iconArea, RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, 1.0f); } else if (isTicked) { const Path tick (getTickShape (1.0f)); - const float th = font.getAscent(); - const float ty = halfH - th * 0.5f; - - g.fillPath (tick, tick.getTransformToScaleToFit (2.0f, ty, (float) (leftBorder - 4), - th, true)); - } - - g.drawFittedText (text, - leftBorder, 0, width - (leftBorder + rightBorder), height, - Justification::centredLeft, 1); - - if (shortcutKeyText.isNotEmpty()) - { - Font f2 (font); - f2.setHeight (f2.getHeight() * 0.75f); - f2.setHorizontalScale (0.95f); - g.setFont (f2); - - g.drawText (shortcutKeyText, - leftBorder, 0, width - (leftBorder + rightBorder + 4), height, - Justification::centredRight, - true); + g.fillPath (tick, tick.getTransformToScaleToFit (iconArea, true)); } if (hasSubMenu) { const float arrowH = 0.6f * getPopupMenuFont().getAscent(); - const float x = width - height * 0.6f; + + const float x = (float) r.removeFromRight ((int) arrowH).getX(); + const float halfH = (float) r.getCentreY(); Path p; p.addTriangle (x, halfH - arrowH * 0.5f, @@ -996,6 +977,19 @@ void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, int width, int height, g.fillPath (p); } + + r.removeFromRight (3); + g.drawFittedText (text, r, Justification::centredLeft, 1); + + if (shortcutKeyText.isNotEmpty()) + { + Font f2 (font); + f2.setHeight (f2.getHeight() * 0.75f); + f2.setHorizontalScale (0.95f); + g.setFont (f2); + + g.drawText (shortcutKeyText, r, Justification::centredRight, true); + } } } diff --git a/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h b/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h index 430c90d..8079e5a 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h +++ b/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h @@ -133,10 +133,10 @@ public: //============================================================================== void drawPopupMenuBackground (Graphics&, int width, int height) override; - void drawPopupMenuItem (Graphics&, int width, int height, + void drawPopupMenuItem (Graphics&, const Rectangle& area, bool isSeparator, bool isActive, bool isHighlighted, bool isTicked, bool hasSubMenu, const String& text, const String& shortcutKeyText, - Image* image, const Colour* textColour) override; + const Drawable* icon, const Colour* textColour) override; Font getPopupMenuFont() override; @@ -344,4 +344,4 @@ private: }; -#endif // JUCE_LOOKANDFEEL_H_INCLUDED +#endif // JUCE_LOOKANDFEEL_V2_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp b/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp index cffef3d..2007252 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp @@ -34,7 +34,7 @@ LookAndFeel_V3::LookAndFeel_V3() setColour (TabbedComponent::outlineColourId, Colour (0xff999999)); setColour (Slider::trackColourId, Colour (0xbbffffff)); setColour (Slider::thumbColourId, Colour (0xffddddff)); - + setColour (BubbleComponent::backgroundColourId, Colour (0xeeeeeedd)); setColour (ScrollBar::thumbColourId, Colour::greyLevel (0.8f).contrasting().withAlpha (0.13f)); } @@ -190,7 +190,6 @@ void LookAndFeel_V3::drawTabButton (TabBarButton& button, Graphics& g, bool isMo if (button.getToggleState()) { g.setColour (bkg); - g.fillRect (activeArea); } else { @@ -207,9 +206,10 @@ void LookAndFeel_V3::drawTabButton (TabBarButton& button, Graphics& g, bool isMo g.setGradientFill (ColourGradient (bkg.brighter (0.2f), (float) p1.x, (float) p1.y, bkg.darker (0.1f), (float) p2.x, (float) p2.y, false)); - g.fillRect (activeArea); } + g.fillRect (activeArea); + g.setColour (button.findColour (TabbedButtonBar::tabOutlineColourId)); Rectangle r (activeArea); @@ -604,3 +604,27 @@ Button* LookAndFeel_V3::createDocumentWindowButton (int buttonType) jassertfalse; return nullptr; } + +Path LookAndFeel_V3::getTickShape (const float height) +{ + static const unsigned char pathData[] + = { 110,109,32,210,202,64,126,183,148,64,108,39,244,247,64,245,76,124,64,108,178,131,27,65,246,76,252,64,108,175,242,4,65,246,76,252, + 64,108,236,5,68,65,0,0,160,180,108,240,150,90,65,21,136,52,63,108,48,59,16,65,0,0,32,65,108,32,210,202,64,126,183,148,64, 99,101,0,0 }; + + Path p; + p.loadPathFromData (pathData, sizeof (pathData)); + p.scaleToFit (0, 0, height * 2.0f, height, true); + return p; +} + +Path LookAndFeel_V3::getCrossShape (const float height) +{ + static const unsigned char pathData[] + = { 110,109,88,57,198,65,29,90,171,65,108,63,53,154,65,8,172,126,65,108,76,55,198,65,215,163,38,65,108,141,151,175,65,82,184,242,64,108,117,147,131,65,90,100,81,65,108,184,30,47,65,82,184,242,64,108,59,223,1,65,215,163,38,65,108,84,227,89,65,8,172,126,65, + 108,35,219,1,65,29,90,171,65,108,209,34,47,65,231,251,193,65,108,117,147,131,65,207,247,149,65,108,129,149,175,65,231,251,193,65,99,101,0,0 }; + + Path p; + p.loadPathFromData (pathData, sizeof (pathData)); + p.scaleToFit (0, 0, height * 2.0f, height, true); + return p; +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.h b/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.h index 412331e..ad97552 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.h +++ b/JuceLibraryCode/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.h @@ -82,6 +82,9 @@ public: void drawConcertinaPanelHeader (Graphics&, const Rectangle& area, bool isMouseOver, bool isMouseDown, ConcertinaPanel&, Component&) override; + Path getTickShape (float height) override; + Path getCrossShape (float height) override; + static void createTabTextLayout (const TabBarButton& button, float length, float depth, Colour colour, TextLayout&); private: @@ -90,4 +93,4 @@ private: }; -#endif // JUCE_LOOKANDFEEL_H_INCLUDED +#endif // JUCE_LOOKANDFEEL_V3_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/menus/juce_PopupMenu.cpp b/JuceLibraryCode/modules/juce_gui_basics/menus/juce_PopupMenu.cpp index 605bcd2..abfc922 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/menus/juce_PopupMenu.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/menus/juce_PopupMenu.cpp @@ -45,7 +45,7 @@ public: const String& name, const bool active, const bool ticked, - const Image& im, + Drawable* drawable, const Colour colour, const bool useColour, CustomComponent* const custom, @@ -54,8 +54,8 @@ public: : itemID (itemId), text (name), textColour (colour), isActive (active), isSeparator (false), isTicked (ticked), - usesColour (useColour), image (im), customComp (custom), - subMenu (createCopyIfNotNull (sub)), commandManager (manager) + usesColour (useColour), iconDrawable (drawable), + customComp (custom), subMenu (createCopyIfNotNull (sub)), commandManager (manager) { if (commandManager != nullptr && itemID != 0) { @@ -92,7 +92,7 @@ public: isSeparator (other.isSeparator), isTicked (other.isTicked), usesColour (other.usesColour), - image (other.image), + iconDrawable (other.iconDrawable != nullptr ? other.iconDrawable->createCopy() : nullptr), customComp (other.customComp), subMenu (createCopyIfNotNull (other.subMenu.get())), commandManager (other.commandManager) @@ -106,7 +106,7 @@ public: String text; const Colour textColour; const bool isActive, isSeparator, isTicked, usesColour; - Image image; + ScopedPointer iconDrawable; ReferenceCountedObjectPtr customComp; ScopedPointer subMenu; ApplicationCommandManager* const commandManager; @@ -175,14 +175,14 @@ public: } getLookAndFeel() - .drawPopupMenuItem (g, getWidth(), getHeight(), + .drawPopupMenuItem (g, getLocalBounds(), itemInfo.isSeparator, itemInfo.isActive, isHighlighted, itemInfo.isTicked, itemInfo.subMenu != nullptr && (itemInfo.itemID == 0 || itemInfo.subMenu->getNumItems() > 0), mainText, endText, - itemInfo.image.isValid() ? &itemInfo.image : nullptr, + itemInfo.iconDrawable, itemInfo.usesColour ? &(itemInfo.textColour) : nullptr); } } @@ -1288,8 +1288,40 @@ void PopupMenu::clear() items.clear(); } -void PopupMenu::addItem (const int itemResultID, const String& itemText, - const bool isActive, const bool isTicked, const Image& iconToUse) +void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked) +{ + 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, nullptr, + Colours::black, false, nullptr, nullptr, nullptr)); +} + +static Drawable* createDrawableFromImage (const Image& im) +{ + if (im.isValid()) + { + DrawableImage* d = new DrawableImage(); + d->setImage (im); + return d; + } + + return nullptr; +} + +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)); +} + +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 @@ -1315,7 +1347,7 @@ void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager, : info.shortName, target != nullptr && (info.flags & ApplicationCommandInfo::isDisabled) == 0, (info.flags & ApplicationCommandInfo::isTicked) != 0, - Image::null, + nullptr, Colours::black, false, nullptr, nullptr, @@ -1323,50 +1355,49 @@ void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager, } } -void PopupMenu::addColouredItem (const int itemResultID, - const String& itemText, - Colour itemTextColour, - const bool isActive, - const bool isTicked, - const Image& iconToUse) +void PopupMenu::addColouredItem (int itemResultID, const String& itemText, Colour itemTextColour, + 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, iconToUse, + items.add (new Item (itemResultID, itemText, isActive, isTicked, createDrawableFromImage (iconToUse), itemTextColour, true, nullptr, nullptr, nullptr)); } -void PopupMenu::addCustomItem (const int itemID, CustomComponent* const cc, const PopupMenu* subMenu) +void PopupMenu::addCustomItem (int itemID, 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, Image::null, + items.add (new Item (itemID, String::empty, true, false, nullptr, Colours::black, false, cc, subMenu, nullptr)); } -void PopupMenu::addCustomItem (const int itemResultID, - Component* customComponent, - int idealWidth, int idealHeight, - const bool triggerMenuItemAutomaticallyWhenClicked, - const PopupMenu* subMenu) +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, Image::null, - Colours::black, false, + items.add (new Item (itemResultID, String::empty, true, false, nullptr, Colours::black, false, new HelperClasses::NormalComponentWrapper (customComponent, idealWidth, idealHeight, triggerMenuItemAutomaticallyWhenClicked), subMenu, nullptr)); } -void PopupMenu::addSubMenu (const String& subMenuName, - const PopupMenu& subMenu, - const bool isActive, - const Image& iconToUse, - const bool isTicked, - const int itemResultID) +void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, bool isActive) +{ + addSubMenu (subMenuName, subMenu, isActive, nullptr, false, 0); +} + +void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, bool isActive, + const Image& iconToUse, bool isTicked, int itemResultID) +{ + addSubMenu (subMenuName, subMenu, isActive, createDrawableFromImage (iconToUse), isTicked, itemResultID); +} + +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)); @@ -1673,7 +1704,7 @@ void PopupMenu::CustomComponent::triggerMenuItem() { if (HelperClasses::ItemComponent* const mic = dynamic_cast (getParentComponent())) { - if (HelperClasses::MenuWindow* const pmw = dynamic_cast (mic->getParentComponent())) + if (HelperClasses::MenuWindow* const pmw = dynamic_cast (mic->getParentComponent())) { pmw->dismissMenu (&mic->itemInfo); } @@ -1727,10 +1758,10 @@ bool PopupMenu::MenuItemIterator::next() isSeparator = item->isSeparator; isTicked = item->isTicked; isEnabled = item->isActive; - isSectionHeader = dynamic_cast (static_cast (item->customComp)) != nullptr; + isSectionHeader = dynamic_cast (static_cast (item->customComp)) != nullptr; isCustomComponent = (! isSectionHeader) && item->customComp != nullptr; customColour = item->usesColour ? &(item->textColour) : nullptr; - customImage = item->image; + icon = item->iconDrawable; commandManager = item->commandManager; return true; @@ -1738,8 +1769,7 @@ bool PopupMenu::MenuItemIterator::next() void PopupMenu::MenuItemIterator::addItemTo (PopupMenu& targetMenu) { - targetMenu.items.add (new Item (itemId, itemName, isEnabled, isTicked, customImage, - customColour != nullptr ? *customColour : Colours::black, customColour != nullptr, - nullptr, - subMenu, commandManager)); + targetMenu.items.add (new Item (itemId, itemName, isEnabled, isTicked, icon != nullptr ? icon->createCopy() : nullptr, + customColour != nullptr ? *customColour : Colours::black, + customColour != nullptr, nullptr, subMenu, commandManager)); } diff --git a/JuceLibraryCode/modules/juce_gui_basics/menus/juce_PopupMenu.h b/JuceLibraryCode/modules/juce_gui_basics/menus/juce_PopupMenu.h index e4622d6..82545a3 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/menus/juce_PopupMenu.h +++ b/JuceLibraryCode/modules/juce_gui_basics/menus/juce_PopupMenu.h @@ -111,18 +111,52 @@ public: @param itemText the text to show. @param isEnabled if false, the item will be shown 'greyed-out' and can't be picked @param isTicked if true, the item will be shown with a tick next to it - @param iconToUse if this is non-zero, it should be an image that will be - displayed to the left of the item. This method will take its - own copy of the image passed-in, so there's no need to keep - it hanging around. @see addSeparator, addColouredItem, addCustomItem, addSubMenu */ void addItem (int itemResultID, const String& itemText, bool isEnabled = true, - bool isTicked = false, - const Image& iconToUse = Image::null); + bool isTicked = false); + + /** Appends a new item with an icon. + + @param itemResultID the number that will be returned from the show() method + if the user picks this item. The value should never be + zero, because that's used to indicate that the user didn't + select anything. + @param itemText the text to show. + @param isEnabled if false, the item will be shown 'greyed-out' and can't be picked + @param isTicked if true, the item will be shown with a tick next to it + @param iconToUse if this is a valid image, it will be displayed to the left of the item. + + @see addSeparator, addColouredItem, addCustomItem, addSubMenu + */ + void addItem (int itemResultID, + const String& itemText, + bool isEnabled, + bool isTicked, + const Image& iconToUse); + + /** Appends a new item with an icon. + + @param itemResultID the number that will be returned from the show() method + if the user picks this item. The value should never be + zero, because that's used to indicate that the user didn't + select anything. + @param itemText the text to show. + @param isEnabled if false, the item will be shown 'greyed-out' and can't be picked + @param isTicked if true, the item will be shown with a tick next to it + @param iconToUse a Drawable object to use as the icon to the left of the item. + The menu will take ownership of this drawable object and will + delete it later when no longer needed + @see addSeparator, addColouredItem, addCustomItem, addSubMenu + */ + void addItem (int itemResultID, + const String& itemText, + bool isEnabled, + bool isTicked, + Drawable* iconToUse); /** Adds an item that represents one of the commands in a command manager object. @@ -177,8 +211,35 @@ public: */ void addSubMenu (const String& subMenuName, const PopupMenu& subMenu, - bool isEnabled = true, - const Image& iconToUse = Image::null, + bool isEnabled = true); + + /** Appends a sub-menu with an icon. + + If the menu that's passed in is empty, it will appear as an inactive item. + If the itemResultID argument is non-zero, then the sub-menu item itself can be + clicked to trigger it as a command. + */ + void addSubMenu (const String& subMenuName, + const PopupMenu& subMenu, + bool isEnabled, + const Image& iconToUse, + bool isTicked = false, + int itemResultID = 0); + + /** Appends a sub-menu with an icon. + + If the menu that's passed in is empty, it will appear as an inactive item. + If the itemResultID argument is non-zero, then the sub-menu item itself can be + clicked to trigger it as a command. + + The iconToUse parameter is a Drawable object to use as the icon to the left of + the item. The menu will take ownership of this drawable object and will delete it + later when no longer needed + */ + void addSubMenu (const String& subMenuName, + const PopupMenu& subMenu, + bool isEnabled, + Drawable* iconToUse, bool isTicked = false, int itemResultID = 0); @@ -408,7 +469,7 @@ public: bool isCustomComponent; bool isSectionHeader; const Colour* customColour; - Image customImage; + const Drawable* icon; ApplicationCommandManager* commandManager; private: @@ -493,12 +554,12 @@ public: virtual void drawPopupMenuBackground (Graphics&, int width, int height) = 0; /** Draws one of the items in a popup menu. */ - virtual void drawPopupMenuItem (Graphics&, int width, int height, + virtual void drawPopupMenuItem (Graphics&, const Rectangle& area, bool isSeparator, bool isActive, bool isHighlighted, bool isTicked, bool hasSubMenu, const String& text, const String& shortcutKeyText, - Image* icon, + const Drawable* icon, const Colour* textColour) = 0; /** Returns the size and style of font to use in popup menus. */ @@ -549,6 +610,11 @@ private: Component* createWindow (const Options&, ApplicationCommandManager**) const; int showWithOptionalCallback (const Options&, ModalComponentManager::Callback*, bool); + #if JUCE_CATCH_DEPRECATED_CODE_MISUSE + // These methods have new implementations now - see its new definition + int drawPopupMenuItem (Graphics&, int, int, bool, bool, bool, bool, bool, const String&, const String&, Image*, const Colour*) { return 0; } + #endif + JUCE_LEAK_DETECTOR (PopupMenu) }; diff --git a/JuceLibraryCode/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp b/JuceLibraryCode/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp index 44d52f1..0806461 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp @@ -154,10 +154,14 @@ public: else if (now > lastTimeOverTarget + RelativeTime::milliseconds (700)) checkForExternalDrag (details, screenPos); } + + forceMouseCursorUpdate(); } void timerCallback() override { + forceMouseCursorUpdate(); + if (sourceDetails.sourceComponent == nullptr) { delete this; @@ -201,6 +205,11 @@ private: bool hasCheckedForExternalDrag; Time lastTimeOverTarget; + void forceMouseCursorUpdate() + { + Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); + } + DragAndDropTarget* getCurrentlyOver() const noexcept { return dynamic_cast (currentlyOverComp.get()); diff --git a/JuceLibraryCode/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h b/JuceLibraryCode/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h index 7d0fed0..9843753 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h +++ b/JuceLibraryCode/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h @@ -110,7 +110,7 @@ public: It's useful when a component wants to call startDragging but doesn't know the DragAndDropContainer it should to use. - Obviously this may return 0 if it doesn't find a suitable component. + Obviously this may return nullptr if it doesn't find a suitable component. */ static DragAndDropContainer* findParentDragContainerFor (Component* childComponent); diff --git a/JuceLibraryCode/modules/juce_gui_basics/native/juce_ios_Windowing.mm b/JuceLibraryCode/modules/juce_gui_basics/native/juce_ios_Windowing.mm index 3852983..dff4c25 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/native/juce_ios_Windowing.mm +++ b/JuceLibraryCode/modules/juce_gui_basics/native/juce_ios_Windowing.mm @@ -110,7 +110,7 @@ public: iOSMessageBox (const String& title, const String& message, NSString* button1, NSString* button2, NSString* button3, ModalComponentManager::Callback* cb, const bool async) - : result (0), delegate (nil), alert (nil), + : result (0), resultReceived (false), delegate (nil), alert (nil), callback (cb), isYesNo (button3 != nil), isAsync (async) { delegate = [[JuceAlertBoxDelegate alloc] init]; @@ -137,7 +137,7 @@ public: JUCE_AUTORELEASEPOOL { - while (! alert.hidden && alert.superview != nil) + while (! (alert.hidden || resultReceived)) [[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; } @@ -147,6 +147,7 @@ public: void buttonClicked (const int buttonIndex) noexcept { result = buttonIndex; + resultReceived = true; if (callback != nullptr) callback->modalStateFinished (result); @@ -157,6 +158,7 @@ public: private: int result; + bool resultReceived; JuceAlertBoxDelegate* delegate; UIAlertView* alert; ScopedPointer callback; diff --git a/JuceLibraryCode/modules/juce_gui_basics/native/juce_mac_MouseCursor.mm b/JuceLibraryCode/modules/juce_gui_basics/native/juce_mac_MouseCursor.mm index 4739f2e..4811607 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/native/juce_mac_MouseCursor.mm +++ b/JuceLibraryCode/modules/juce_gui_basics/native/juce_mac_MouseCursor.mm @@ -52,15 +52,20 @@ namespace MouseCursorHelpers static void* fromWebKitFile (const char* filename, float hx, float hy) { - FileInputStream fileStream (File ("/System/Library/Frameworks/WebKit.framework/Frameworks/WebCore.framework/Resources").getChildFile (filename)); - BufferedInputStream buf (fileStream, 4096); + FileInputStream fileStream (File ("/System/Library/Frameworks/WebKit.framework/Frameworks/WebCore.framework/Resources") + .getChildFile (filename)); - PNGImageFormat pngFormat; - Image im (pngFormat.decodeImage (buf)); + if (fileStream.openedOk()) + { + BufferedInputStream buf (fileStream, 4096); + + PNGImageFormat pngFormat; + Image im (pngFormat.decodeImage (buf)); - if (im.isValid()) - return CustomMouseCursorInfo (im, (int) (hx * im.getWidth()), - (int) (hy * im.getHeight())).create(); + if (im.isValid()) + return CustomMouseCursorInfo (im, (int) (hx * im.getWidth()), + (int) (hy * im.getHeight())).create(); + } return nullptr; } diff --git a/JuceLibraryCode/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/JuceLibraryCode/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index 7796380..54bce81 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/JuceLibraryCode/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -1452,7 +1452,7 @@ private: if (target != nullptr) [(NSView*) self interpretKeyEvents: [NSArray arrayWithObject: ev]]; else - owner->stringBeingComposed = String::empty; + owner->stringBeingComposed.clear(); if ((! owner->textWasInserted) && (owner == nullptr || ! owner->redirectKeyDown (ev))) { @@ -1490,7 +1490,7 @@ private: } } - owner->stringBeingComposed = String::empty; + owner->stringBeingComposed.clear(); } } @@ -1525,7 +1525,7 @@ private: owner->textWasInserted = true; } - owner->stringBeingComposed = String::empty; + owner->stringBeingComposed.clear(); } } } diff --git a/JuceLibraryCode/modules/juce_gui_basics/native/juce_mac_Windowing.mm b/JuceLibraryCode/modules/juce_gui_basics/native/juce_mac_Windowing.mm index 709993e..7744d03 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/native/juce_mac_Windowing.mm +++ b/JuceLibraryCode/modules/juce_gui_basics/native/juce_mac_Windowing.mm @@ -439,7 +439,7 @@ String SystemClipboard::getTextFromClipboard() { NSString* text = [[NSPasteboard generalPasteboard] stringForType: NSStringPboardType]; - return text == nil ? String::empty + return text == nil ? String() : nsStringToJuce (text); } diff --git a/JuceLibraryCode/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp b/JuceLibraryCode/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp index da839eb..930bf55 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp @@ -183,7 +183,7 @@ void FileChooser::showPlatformDialog (Array& results, const String& title_ if (! SHGetPathFromIDListW (list, files)) { files[0] = 0; - info.returnedString = String::empty; + info.returnedString.clear(); } LPMALLOC al; diff --git a/JuceLibraryCode/modules/juce_gui_basics/properties/juce_BooleanPropertyComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/properties/juce_BooleanPropertyComponent.cpp index adf1ecf..6f0d458 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/properties/juce_BooleanPropertyComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/properties/juce_BooleanPropertyComponent.cpp @@ -29,7 +29,7 @@ BooleanPropertyComponent::BooleanPropertyComponent (const String& name, onText (buttonTextWhenTrue), offText (buttonTextWhenFalse) { - addAndMakeVisible (&button); + addAndMakeVisible (button); button.setClickingTogglesState (false); button.addListener (this); } @@ -41,7 +41,7 @@ BooleanPropertyComponent::BooleanPropertyComponent (const Value& valueToControl, onText (buttonText), offText (buttonText) { - addAndMakeVisible (&button); + addAndMakeVisible (button); button.setClickingTogglesState (false); button.setButtonText (buttonText); button.getToggleStateValue().referTo (valueToControl); diff --git a/JuceLibraryCode/modules/juce_gui_basics/properties/juce_ButtonPropertyComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/properties/juce_ButtonPropertyComponent.cpp index 1459dd3..cf03191 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/properties/juce_ButtonPropertyComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/properties/juce_ButtonPropertyComponent.cpp @@ -26,7 +26,7 @@ ButtonPropertyComponent::ButtonPropertyComponent (const String& name, const bool triggerOnMouseDown) : PropertyComponent (name) { - addAndMakeVisible (&button); + addAndMakeVisible (button); button.setTriggeredOnMouseDown (triggerOnMouseDown); button.addListener (this); } diff --git a/JuceLibraryCode/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.cpp index 9168e43..66b5481 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.cpp @@ -96,7 +96,7 @@ ChoicePropertyComponent::~ChoicePropertyComponent() //============================================================================== void ChoicePropertyComponent::createComboBox() { - addAndMakeVisible (&comboBox); + addAndMakeVisible (comboBox); for (int i = 0; i < choices.size(); ++i) { diff --git a/JuceLibraryCode/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp b/JuceLibraryCode/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp index fd1c21d..dc93d7c 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp @@ -193,7 +193,7 @@ void PropertyPanel::init() { messageWhenEmpty = TRANS("(nothing selected)"); - addAndMakeVisible (&viewport); + addAndMakeVisible (viewport); viewport.setViewedComponent (propertyHolderComponent = new PropertyHolderComponent()); viewport.setFocusContainer (true); } diff --git a/JuceLibraryCode/modules/juce_gui_basics/properties/juce_PropertyPanel.h b/JuceLibraryCode/modules/juce_gui_basics/properties/juce_PropertyPanel.h index 2ec47f6..3bd6115 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/properties/juce_PropertyPanel.h +++ b/JuceLibraryCode/modules/juce_gui_basics/properties/juce_PropertyPanel.h @@ -63,7 +63,7 @@ public: These properties are added without them being inside a named section. If you want them to be kept together in a collapsible section, use addSection() instead. */ - void addProperties (const Array & newPropertyComponents); + void addProperties (const Array& newPropertyComponents); /** Adds a set of properties to the panel. @@ -76,7 +76,7 @@ public: To add properies without them being in a section, use addProperties(). */ void addSection (const String& sectionTitle, - const Array & newPropertyComponents, + const Array& newPropertyComponents, bool shouldSectionInitiallyBeOpen = true); /** Calls the refresh() method of all PropertyComponents in the panel */ diff --git a/JuceLibraryCode/modules/juce_gui_basics/properties/juce_SliderPropertyComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/properties/juce_SliderPropertyComponent.cpp index def6b12..d1190ae 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/properties/juce_SliderPropertyComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/properties/juce_SliderPropertyComponent.cpp @@ -29,7 +29,7 @@ SliderPropertyComponent::SliderPropertyComponent (const String& name, const double skewFactor) : PropertyComponent (name) { - addAndMakeVisible (&slider); + addAndMakeVisible (slider); slider.setRange (rangeMin, rangeMax, interval); slider.setSkewFactor (skewFactor); @@ -46,7 +46,7 @@ SliderPropertyComponent::SliderPropertyComponent (const Value& valueToControl, const double skewFactor) : PropertyComponent (name) { - addAndMakeVisible (&slider); + addAndMakeVisible (slider); slider.setRange (rangeMin, rangeMax, interval); slider.setSkewFactor (skewFactor); diff --git a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_ListBox.cpp b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_ListBox.cpp index acc378b..5c2dddb 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_ListBox.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_ListBox.cpp @@ -952,6 +952,6 @@ void ListBoxModel::selectedRowsChanged (int) {} void ListBoxModel::deleteKeyPressed (int) {} void ListBoxModel::returnKeyPressed (int) {} void ListBoxModel::listWasScrolled() {} -var ListBoxModel::getDragSourceDescription (const SparseSet&) { return var::null; } +var ListBoxModel::getDragSourceDescription (const SparseSet&) { return var(); } String ListBoxModel::getTooltipForRow (int) { return String::empty; } MouseCursor ListBoxModel::getMouseCursorForRow (int) { return MouseCursor::NormalCursor; } diff --git a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_Slider.cpp b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_Slider.cpp index 29c15db..c59f9fc 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_Slider.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_Slider.cpp @@ -128,7 +128,7 @@ public: if (newInt != 0) { - int v = abs ((int) (newInt * 10000000)); + int v = std::abs (roundToInt (newInt * 10000000)); while ((v % 10) == 0) { @@ -408,7 +408,12 @@ public: void updateText() { if (valueBox != nullptr) - valueBox->setText (owner.getTextFromValue (currentValue.getValue()), dontSendNotification); + { + String newValue (owner.getTextFromValue (currentValue.getValue())); + + if (newValue != valueBox->getText()) + valueBox->setText (newValue, dontSendNotification); + } } double constrainedValue (double value) const diff --git a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp index 4c43a35..9ee0f16 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp @@ -440,7 +440,7 @@ String TableHeaderComponent::toString() const e->setAttribute ("width", ci->width); } - return doc.createDocument (String::empty, true, false); + return doc.createDocument ("", true, false); } void TableHeaderComponent::restoreFromString (const String& storedVersion) diff --git a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TableListBox.cpp b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TableListBox.cpp index 0b49d1c..3e2df4d 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TableListBox.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TableListBox.cpp @@ -466,7 +466,7 @@ void TableListBoxModel::returnKeyPressed (int) {} void TableListBoxModel::listWasScrolled() {} String TableListBoxModel::getCellTooltip (int /*rowNumber*/, int /*columnId*/) { return String::empty; } -var TableListBoxModel::getDragSourceDescription (const SparseSet&) { return var::null; } +var TableListBoxModel::getDragSourceDescription (const SparseSet&) { return var(); } Component* TableListBoxModel::refreshComponentForCell (int, int, bool, Component* existingComponentToUpdate) { diff --git a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TableListBox.h b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TableListBox.h index 883aca1..9f83e86 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TableListBox.h +++ b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TableListBox.h @@ -278,7 +278,7 @@ public: /** Returns the component that currently represents a given cell. If the component for this cell is off-screen or if the position is out-of-range, - this may return 0. + this may return nullptr. @see getCellPosition */ Component* getCellComponent (int columnId, int rowNumber) const; @@ -291,31 +291,31 @@ public: //============================================================================== /** @internal */ - int getNumRows(); + int getNumRows() override; /** @internal */ - void paintListBoxItem (int, Graphics&, int, int, bool); + void paintListBoxItem (int, Graphics&, int, int, bool) override; /** @internal */ - Component* refreshComponentForRow (int rowNumber, bool isRowSelected, Component* existingComponentToUpdate); + Component* refreshComponentForRow (int rowNumber, bool isRowSelected, Component* existingComponentToUpdate) override; /** @internal */ - void selectedRowsChanged (int lastRowSelected); + void selectedRowsChanged (int lastRowSelected) override; /** @internal */ - void deleteKeyPressed (int currentSelectedRow); + void deleteKeyPressed (int currentSelectedRow) override; /** @internal */ - void returnKeyPressed (int currentSelectedRow); + void returnKeyPressed (int currentSelectedRow) override; /** @internal */ - void backgroundClicked(); + void backgroundClicked() override; /** @internal */ - void listWasScrolled(); + void listWasScrolled() override; /** @internal */ - void tableColumnsChanged (TableHeaderComponent*); + void tableColumnsChanged (TableHeaderComponent*) override; /** @internal */ - void tableColumnsResized (TableHeaderComponent*); + void tableColumnsResized (TableHeaderComponent*) override; /** @internal */ - void tableSortOrderChanged (TableHeaderComponent*); + void tableSortOrderChanged (TableHeaderComponent*) override; /** @internal */ - void tableColumnDraggingChanged (TableHeaderComponent*, int); + void tableColumnDraggingChanged (TableHeaderComponent*, int) override; /** @internal */ - void resized(); + void resized() override; private: diff --git a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_Toolbar.cpp b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_Toolbar.cpp index d16b712..fc7b4a2 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_Toolbar.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_Toolbar.cpp @@ -228,7 +228,7 @@ public: private: Component::SafePointer owner; const int height; - Array oldIndexes; + Array oldIndexes; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MissingItemsComponent) }; @@ -285,7 +285,7 @@ void Toolbar::addItemInternal (ToolbarItemFactory& factory, if (ToolbarItemComponent* const tc = createItem (factory, itemId)) { #if JUCE_DEBUG - Array allowedIds; + Array allowedIds; factory.getAllToolbarItemIds (allowedIds); // If your factory can create an item for a given ID, it must also return @@ -308,7 +308,7 @@ void Toolbar::addItem (ToolbarItemFactory& factory, void Toolbar::addDefaultItems (ToolbarItemFactory& factoryToUse) { - Array ids; + Array ids; factoryToUse.getDefaultItemSet (ids); clear(); @@ -675,32 +675,27 @@ public: void positionNearBar() { const Rectangle screenSize (toolbar.getParentMonitorArea()); - const int tbx = toolbar.getScreenX(); - const int tby = toolbar.getScreenY(); + Point pos (toolbar.getScreenPosition()); const int gap = 8; - int x, y; - if (toolbar.isVertical()) { - y = tby; - - if (tbx > screenSize.getCentreX()) - x = tbx - getWidth() - gap; + if (pos.x > screenSize.getCentreX()) + pos.x -= getWidth() - gap; else - x = tbx + toolbar.getWidth() + gap; + pos.x += toolbar.getWidth() + gap; } else { - x = tbx + (toolbar.getWidth() - getWidth()) / 2; + pos.x += (toolbar.getWidth() - getWidth()) / 2; - if (tby > screenSize.getCentreY()) - y = tby - getHeight() - gap; + if (pos.y > screenSize.getCentreY()) + pos.y -= getHeight() - gap; else - y = tby + toolbar.getHeight() + gap; + pos.y += toolbar.getHeight() + gap; } - setTopLeftPosition (x, y); + setTopLeftPosition (pos); } private: @@ -718,13 +713,13 @@ private: + TRANS ("Items on the toolbar can also be dragged around to change their order, or dragged off the edge to delete them.")), defaultButton (TRANS ("Restore to default set of items")) { - addAndMakeVisible (&palette); + addAndMakeVisible (palette); if ((optionFlags & (Toolbar::allowIconsOnlyChoice | Toolbar::allowIconsWithTextChoice | Toolbar::allowTextOnlyChoice)) != 0) { - addAndMakeVisible (&styleBox); + addAndMakeVisible (styleBox); styleBox.setEditableText (false); if ((optionFlags & Toolbar::allowIconsOnlyChoice) != 0) styleBox.addItem (TRANS("Show icons only"), 1); @@ -746,11 +741,11 @@ private: if ((optionFlags & Toolbar::showResetToDefaultsButton) != 0) { - addAndMakeVisible (&defaultButton); + addAndMakeVisible (defaultButton); defaultButton.addListener (this); } - addAndMakeVisible (&instructions); + addAndMakeVisible (instructions); instructions.setFont (Font (13.0f)); setSize (500, 300); diff --git a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_ToolbarItemPalette.cpp b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_ToolbarItemPalette.cpp index 8f3bb5d..a4b6d52 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_ToolbarItemPalette.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_ToolbarItemPalette.cpp @@ -28,13 +28,13 @@ ToolbarItemPalette::ToolbarItemPalette (ToolbarItemFactory& tbf, Toolbar& bar) Component* const itemHolder = new Component(); viewport.setViewedComponent (itemHolder); - Array allIds; + Array allIds; factory.getAllToolbarItemIds (allIds); for (int i = 0; i < allIds.size(); ++i) addComponent (allIds.getUnchecked (i), -1); - addAndMakeVisible (&viewport); + addAndMakeVisible (viewport); } ToolbarItemPalette::~ToolbarItemPalette() diff --git a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TreeView.cpp b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TreeView.cpp index 12c95af..097ea89 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TreeView.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TreeView.cpp @@ -1371,7 +1371,7 @@ String TreeViewItem::getTooltip() var TreeViewItem::getDragSourceDescription() { - return var::null; + return var(); } bool TreeViewItem::isInterestedInFileDrag (const StringArray&) diff --git a/JuceLibraryCode/modules/juce_gui_basics/windows/juce_CallOutBox.cpp b/JuceLibraryCode/modules/juce_gui_basics/windows/juce_CallOutBox.cpp index 742bda9..1276f43 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/windows/juce_CallOutBox.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/windows/juce_CallOutBox.cpp @@ -25,7 +25,7 @@ CallOutBox::CallOutBox (Component& c, const Rectangle& area, Component* const parent) : borderSpace (20), arrowSize (16.0f), content (c) { - addAndMakeVisible (&content); + addAndMakeVisible (content); if (parent != nullptr) { diff --git a/JuceLibraryCode/modules/juce_gui_basics/windows/juce_ComponentPeer.h b/JuceLibraryCode/modules/juce_gui_basics/windows/juce_ComponentPeer.h index 4a4b515..2dc2d3f 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/windows/juce_ComponentPeer.h +++ b/JuceLibraryCode/modules/juce_gui_basics/windows/juce_ComponentPeer.h @@ -310,7 +310,7 @@ public: Point position; bool isEmpty() const noexcept { return files.size() == 0 && text.isEmpty(); } - void clear() noexcept { files.clear(); text = String::empty; } + void clear() noexcept { files.clear(); text.clear(); } }; bool handleDragMove (const DragInfo&); diff --git a/JuceLibraryCode/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.cpp b/JuceLibraryCode/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.cpp index 9a3a027..82d12aa 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.cpp @@ -33,8 +33,10 @@ ThreadWithProgressWindow::ThreadWithProgressWindow (const String& title, timeOutMsWhenCancelling (timeOutMsWhenCancelling_) { alertWindow = LookAndFeel::getDefaultLookAndFeel() - .createAlertWindow (title, String::empty, cancelButtonText, - String::empty, String::empty, + .createAlertWindow (title, String(), + cancelButtonText.isEmpty() ? TRANS("Cancel") + : cancelButtonText, + String(), String(), AlertWindow::NoIcon, hasCancelButton ? 1 : 0, componentToCentreAround); diff --git a/JuceLibraryCode/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.h b/JuceLibraryCode/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.h index 862d6d0..7bf8522 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.h +++ b/JuceLibraryCode/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.h @@ -96,7 +96,7 @@ public: the thread to stop before killing it forcibly (see Thread::stopThread() ) @param cancelButtonText the text that should be shown in the cancel button - (if it has one) + (if it has one). Leave this empty for the default "Cancel" @param componentToCentreAround if this is non-null, the window will be positioned so that it's centred around this component. */ @@ -104,7 +104,7 @@ public: bool hasProgressBar, bool hasCancelButton, int timeOutMsWhenCancelling = 10000, - const String& cancelButtonText = "Cancel", + const String& cancelButtonText = String(), Component* componentToCentreAround = nullptr); /** Destructor. */ diff --git a/JuceLibraryCode/modules/juce_gui_basics/windows/juce_TooltipWindow.cpp b/JuceLibraryCode/modules/juce_gui_basics/windows/juce_TooltipWindow.cpp index 6634efc..cc1ff7a 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/windows/juce_TooltipWindow.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/windows/juce_TooltipWindow.cpp @@ -115,7 +115,7 @@ String TooltipWindow::getTipFor (Component* const c) void TooltipWindow::hideTip() { - tipShowing = String::empty; + tipShowing.clear(); removeFromDesktop(); setVisible (false); } diff --git a/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CPlusPlusCodeTokeniserFunctions.h b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CPlusPlusCodeTokeniserFunctions.h index ec18615..a3a09f2 100644 --- a/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CPlusPlusCodeTokeniserFunctions.h +++ b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CPlusPlusCodeTokeniserFunctions.h @@ -46,33 +46,34 @@ struct CppTokeniserFunctions static bool isReservedKeyword (String::CharPointerType token, const int tokenLength) noexcept { static const char* const keywords2Char[] = - { "if", "do", "or", "id", 0 }; + { "if", "do", "or", nullptr }; static const char* const keywords3Char[] = - { "for", "int", "new", "try", "xor", "and", "asm", "not", 0 }; + { "for", "int", "new", "try", "xor", "and", "asm", "not", nullptr }; static const char* const keywords4Char[] = { "bool", "void", "this", "true", "long", "else", "char", - "enum", "case", "goto", "auto", 0 }; + "enum", "case", "goto", "auto", nullptr }; static const char* const keywords5Char[] = - { "while", "bitor", "break", "catch", "class", "compl", "const", "false", - "float", "short", "throw", "union", "using", "or_eq", 0 }; + { "float", "const", "while", "break", "false", "catch", "class", "bitor", + "compl", "or_eq", "short", "throw", "union", "using", "final", nullptr }; static const char* const keywords6Char[] = - { "return", "struct", "and_eq", "bitand", "delete", "double", "extern", - "friend", "inline", "not_eq", "public", "sizeof", "static", "signed", - "switch", "typeid", "wchar_t", "xor_eq", 0}; + { "return", "and_eq", "bitand", "delete", "double", "export", "extern", + "friend", "inline", "not_eq", "public", "signed", "sizeof", "static", + "struct", "switch", "typeid", "xor_eq", nullptr }; static const char* const keywords7Char[] = - { "default", "mutable", "private", "typedef", "nullptr", "virtual", 0 }; + { "nullptr", "alignas", "alignof", "default", "mutable", "private", + "typedef", "virtual", "wchar_t", nullptr }; static const char* const keywordsOther[] = - { "noexcept", "const_cast", "continue", "explicit", "namespace", - "operator", "protected", "register", "reinterpret_cast", "static_cast", - "template", "typename", "unsigned", "volatile", "constexpr", - "@implementation", "@interface", "@end", "@synthesize", "@dynamic", "@public", - "@private", "@property", "@protected", "@class", 0 }; + { "char16_t", "char32_t", "const_cast", "constexpr", "continue", "decltype", "dynamic_cast", + "explicit", "namespace", "noexcept", "operator", "protected", "register", "reinterpret_cast", + "static_assert", "static_cast", "template", "thread_local", "typename", "unsigned", "volatile", + "@class", "@dynamic", "@end", "@implementation", "@interface", "@public", "@private", + "@protected", "@property", "@synthesize", nullptr }; const char* const* k; @@ -100,7 +101,7 @@ struct CppTokeniserFunctions return false; } - template + template static int parseIdentifier (Iterator& source) noexcept { int tokenLength = 0; @@ -128,7 +129,7 @@ struct CppTokeniserFunctions return CPlusPlusCodeTokeniser::tokenType_identifier; } - template + template static bool skipNumberSuffix (Iterator& source) { const juce_wchar c = source.peekNextChar(); @@ -148,7 +149,7 @@ struct CppTokeniserFunctions || (c >= 'A' && c <= 'F'); } - template + template static bool parseHexLiteral (Iterator& source) noexcept { if (source.peekNextChar() == '-') @@ -179,7 +180,7 @@ struct CppTokeniserFunctions return c >= '0' && c <= '7'; } - template + template static bool parseOctalLiteral (Iterator& source) noexcept { if (source.peekNextChar() == '-') @@ -202,7 +203,7 @@ struct CppTokeniserFunctions return c >= '0' && c <= '9'; } - template + template static bool parseDecimalLiteral (Iterator& source) noexcept { if (source.peekNextChar() == '-') @@ -221,7 +222,7 @@ struct CppTokeniserFunctions return skipNumberSuffix (source); } - template + template static bool parseFloatLiteral (Iterator& source) noexcept { if (source.peekNextChar() == '-') @@ -282,7 +283,7 @@ struct CppTokeniserFunctions return true; } - template + template static int parseNumber (Iterator& source) { const Iterator original (source); @@ -302,7 +303,7 @@ struct CppTokeniserFunctions return CPlusPlusCodeTokeniser::tokenType_error; } - template + template static void skipQuotedString (Iterator& source) noexcept { const juce_wchar quote = source.nextChar(); @@ -319,7 +320,7 @@ struct CppTokeniserFunctions } } - template + template static void skipComment (Iterator& source) noexcept { bool lastWasStar = false; @@ -335,7 +336,7 @@ struct CppTokeniserFunctions } } - template + template static void skipPreprocessorLine (Iterator& source) noexcept { bool lastWasBackslash = false; @@ -378,14 +379,14 @@ struct CppTokeniserFunctions } } - template + template static void skipIfNextCharMatches (Iterator& source, const juce_wchar c) noexcept { if (source.peekNextChar() == c) source.skip(); } - template + template static void skipIfNextCharMatches (Iterator& source, const juce_wchar c1, const juce_wchar c2) noexcept { const juce_wchar c = source.peekNextChar(); @@ -394,10 +395,9 @@ struct CppTokeniserFunctions source.skip(); } - template + template static int readNextToken (Iterator& source) { - int result = CPlusPlusCodeTokeniser::tokenType_error; source.skipWhitespace(); const juce_wchar firstChar = source.peekNextChar(); @@ -405,135 +405,116 @@ struct CppTokeniserFunctions switch (firstChar) { case 0: - source.skip(); break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': case '.': - result = parseNumber (source); + { + int result = parseNumber (source); if (result == CPlusPlusCodeTokeniser::tokenType_error) { source.skip(); if (firstChar == '.') - result = CPlusPlusCodeTokeniser::tokenType_punctuation; + return CPlusPlusCodeTokeniser::tokenType_punctuation; } - break; + return result; + } case ',': case ';': case ':': source.skip(); - result = CPlusPlusCodeTokeniser::tokenType_punctuation; - break; + return CPlusPlusCodeTokeniser::tokenType_punctuation; - case '(': - case ')': - case '{': - case '}': - case '[': - case ']': + case '(': case ')': + case '{': case '}': + case '[': case ']': source.skip(); - result = CPlusPlusCodeTokeniser::tokenType_bracket; - break; + return CPlusPlusCodeTokeniser::tokenType_bracket; case '"': case '\'': skipQuotedString (source); - result = CPlusPlusCodeTokeniser::tokenType_string; - break; + return CPlusPlusCodeTokeniser::tokenType_string; case '+': - result = CPlusPlusCodeTokeniser::tokenType_operator; source.skip(); skipIfNextCharMatches (source, '+', '='); - break; + return CPlusPlusCodeTokeniser::tokenType_operator; case '-': + { source.skip(); - result = parseNumber (source); + int result = parseNumber (source); if (result == CPlusPlusCodeTokeniser::tokenType_error) { - result = CPlusPlusCodeTokeniser::tokenType_operator; skipIfNextCharMatches (source, '-', '='); + return CPlusPlusCodeTokeniser::tokenType_operator; } - break; - case '*': - case '%': - case '=': - case '!': - result = CPlusPlusCodeTokeniser::tokenType_operator; + return result; + } + + case '*': case '%': + case '=': case '!': source.skip(); skipIfNextCharMatches (source, '='); - break; + return CPlusPlusCodeTokeniser::tokenType_operator; case '/': - result = CPlusPlusCodeTokeniser::tokenType_operator; + { source.skip(); + juce_wchar nextChar = source.peekNextChar(); - if (source.peekNextChar() == '=') + if (nextChar == '/') { - source.skip(); - } - else if (source.peekNextChar() == '/') - { - result = CPlusPlusCodeTokeniser::tokenType_comment; source.skipToEndOfLine(); + return CPlusPlusCodeTokeniser::tokenType_comment; } - else if (source.peekNextChar() == '*') + + if (nextChar == '*') { source.skip(); - result = CPlusPlusCodeTokeniser::tokenType_comment; skipComment (source); + return CPlusPlusCodeTokeniser::tokenType_comment; } - break; + if (nextChar == '=') + source.skip(); + + return CPlusPlusCodeTokeniser::tokenType_operator; + } case '?': case '~': source.skip(); - result = CPlusPlusCodeTokeniser::tokenType_operator; - break; + return CPlusPlusCodeTokeniser::tokenType_operator; - case '<': - case '>': - case '|': - case '&': - case '^': + case '<': case '>': + case '|': case '&': case '^': source.skip(); - result = CPlusPlusCodeTokeniser::tokenType_operator; skipIfNextCharMatches (source, firstChar); skipIfNextCharMatches (source, '='); - break; + return CPlusPlusCodeTokeniser::tokenType_operator; case '#': - result = CPlusPlusCodeTokeniser::tokenType_preprocessor; skipPreprocessorLine (source); - break; + return CPlusPlusCodeTokeniser::tokenType_preprocessor; default: if (isIdentifierStart (firstChar)) - result = parseIdentifier (source); - else - source.skip(); + return parseIdentifier (source); + source.skip(); break; } - return result; + return CPlusPlusCodeTokeniser::tokenType_error; } /** A class that can be passed to the CppTokeniserFunctions functions in order to diff --git a/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CodeDocument.cpp b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CodeDocument.cpp index beb5d87..b4b9a9f 100644 --- a/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CodeDocument.cpp +++ b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CodeDocument.cpp @@ -577,9 +577,8 @@ void CodeDocument::insertText (const int insertIndex, const String& text) void CodeDocument::replaceSection (const int start, const int end, const String& newText) { - insertText (start, newText); - const int newTextLen = newText.length(); - deleteSection (start + newTextLen, end + newTextLen); + insertText (end, newText); + deleteSection (start, end); } void CodeDocument::applyChanges (const String& newContent) diff --git a/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp index 70b0a8f..cd10127 100644 --- a/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp @@ -340,8 +340,10 @@ CodeEditorComponent::CodeEditorComponent (CodeDocument& doc, CodeTokeniser* cons columnsOnScreen (0), scrollbarThickness (16), columnToTryToMaintain (-1), + readOnly (false), useSpacesForTabs (false), showLineNumbers (false), + shouldFollowDocumentChanges (false), xOffset (0), caretPos (doc, 0, 0), selectionStart (doc, 0, 0), @@ -363,10 +365,10 @@ CodeEditorComponent::CodeEditorComponent (CodeDocument& doc, CodeTokeniser* cons addAndMakeVisible (caret = getLookAndFeel().createCaretComponent (this)); - addAndMakeVisible (&verticalScrollBar); + addAndMakeVisible (verticalScrollBar); verticalScrollBar.setSingleStepSize (1.0); - addAndMakeVisible (&horizontalScrollBar); + addAndMakeVisible (horizontalScrollBar); horizontalScrollBar.setSingleStepSize (1.0); Font f (12.0f); @@ -434,6 +436,11 @@ void CodeEditorComponent::setLineNumbersShown (const bool shouldBeShown) } } +void CodeEditorComponent::setReadOnly (bool b) noexcept +{ + readOnly = b; +} + //============================================================================== void CodeEditorComponent::resized() { @@ -559,9 +566,10 @@ void CodeEditorComponent::codeDocumentChanged (const int startIndex, const int e && affectedTextStart.getPosition() <= selectionEnd.getPosition()) deselectAll(); - if (caretPos.getPosition() > affectedTextEnd.getPosition() - || caretPos.getPosition() < affectedTextStart.getPosition()) - moveCaretTo (affectedTextStart, false); + if (shouldFollowDocumentChanges) + if (caretPos.getPosition() > affectedTextEnd.getPosition() + || caretPos.getPosition() < affectedTextStart.getPosition()) + moveCaretTo (affectedTextStart, false); updateScrollBars(); } @@ -743,37 +751,43 @@ void CodeEditorComponent::insertTextAtCaret (const String& newText) void CodeEditorComponent::insertText (const String& newText) { - document.deleteSection (selectionStart, selectionEnd); + if (! readOnly) + { + document.deleteSection (selectionStart, selectionEnd); - if (newText.isNotEmpty()) - document.insertText (caretPos, newText); + if (newText.isNotEmpty()) + document.insertText (caretPos, newText); - scrollToKeepCaretOnScreen(); + scrollToKeepCaretOnScreen(); + } } void CodeEditorComponent::insertTabAtCaret() { - if (CharacterFunctions::isWhitespace (caretPos.getCharacter()) - && caretPos.getLineNumber() == caretPos.movedBy (1).getLineNumber()) + if (! readOnly) { - moveCaretTo (document.findWordBreakAfter (caretPos), false); - } + if (CharacterFunctions::isWhitespace (caretPos.getCharacter()) + && caretPos.getLineNumber() == caretPos.movedBy (1).getLineNumber()) + { + moveCaretTo (document.findWordBreakAfter (caretPos), false); + } - if (useSpacesForTabs) - { - const int caretCol = indexToColumn (caretPos.getLineNumber(), caretPos.getIndexInLine()); - const int spacesNeeded = spacesPerTab - (caretCol % spacesPerTab); - insertTextAtCaret (String::repeatedString (" ", spacesNeeded)); - } - else - { - insertTextAtCaret ("\t"); + if (useSpacesForTabs) + { + const int caretCol = indexToColumn (caretPos.getLineNumber(), caretPos.getIndexInLine()); + const int spacesNeeded = spacesPerTab - (caretCol % spacesPerTab); + insertTextAtCaret (String::repeatedString (" ", spacesNeeded)); + } + else + { + insertTextAtCaret ("\t"); + } } } bool CodeEditorComponent::deleteWhitespaceBackwardsToTabStop() { - if (getHighlightedRegion().isEmpty()) + if (getHighlightedRegion().isEmpty() && ! readOnly) { for (;;) { @@ -802,43 +816,46 @@ void CodeEditorComponent::unindentSelection() { indentSelectedLines (-spacesPe void CodeEditorComponent::indentSelectedLines (const int spacesToAdd) { - newTransaction(); - - CodeDocument::Position oldSelectionStart (selectionStart), oldSelectionEnd (selectionEnd), oldCaret (caretPos); - oldSelectionStart.setPositionMaintained (true); - oldSelectionEnd.setPositionMaintained (true); - oldCaret.setPositionMaintained (true); + if (! readOnly) + { + newTransaction(); - const int lineStart = selectionStart.getLineNumber(); - int lineEnd = selectionEnd.getLineNumber(); + CodeDocument::Position oldSelectionStart (selectionStart), oldSelectionEnd (selectionEnd), oldCaret (caretPos); + oldSelectionStart.setPositionMaintained (true); + oldSelectionEnd.setPositionMaintained (true); + oldCaret.setPositionMaintained (true); - if (lineEnd > lineStart && selectionEnd.getIndexInLine() == 0) - --lineEnd; + const int lineStart = selectionStart.getLineNumber(); + int lineEnd = selectionEnd.getLineNumber(); - for (int line = lineStart; line <= lineEnd; ++line) - { - const String lineText (document.getLine (line)); - const int nonWhitespaceStart = CodeEditorHelpers::findFirstNonWhitespaceChar (lineText); + if (lineEnd > lineStart && selectionEnd.getIndexInLine() == 0) + --lineEnd; - if (nonWhitespaceStart > 0 || lineText.trimStart().isNotEmpty()) + for (int line = lineStart; line <= lineEnd; ++line) { - const CodeDocument::Position wsStart (document, line, 0); - const CodeDocument::Position wsEnd (document, line, nonWhitespaceStart); - - const int numLeadingSpaces = indexToColumn (line, wsEnd.getIndexInLine()); - const int newNumLeadingSpaces = jmax (0, numLeadingSpaces + spacesToAdd); + const String lineText (document.getLine (line)); + const int nonWhitespaceStart = CodeEditorHelpers::findFirstNonWhitespaceChar (lineText); - if (newNumLeadingSpaces != numLeadingSpaces) + if (nonWhitespaceStart > 0 || lineText.trimStart().isNotEmpty()) { - document.deleteSection (wsStart, wsEnd); - document.insertText (wsStart, getTabString (newNumLeadingSpaces)); + const CodeDocument::Position wsStart (document, line, 0); + const CodeDocument::Position wsEnd (document, line, nonWhitespaceStart); + + const int numLeadingSpaces = indexToColumn (line, wsEnd.getIndexInLine()); + const int newNumLeadingSpaces = jmax (0, numLeadingSpaces + spacesToAdd); + + if (newNumLeadingSpaces != numLeadingSpaces) + { + document.deleteSection (wsStart, wsEnd); + document.insertText (wsStart, getTabString (newNumLeadingSpaces)); + } } } - } - selectionStart = oldSelectionStart; - selectionEnd = oldSelectionEnd; - caretPos = oldCaret; + selectionStart = oldSelectionStart; + selectionEnd = oldSelectionEnd; + caretPos = oldCaret; + } } void CodeEditorComponent::cut() @@ -1114,6 +1131,10 @@ void CodeEditorComponent::selectRegion (const CodeDocument::Position& start, //============================================================================== bool CodeEditorComponent::undo() { + if (readOnly) + return false; + + ScopedValueSetter svs (shouldFollowDocumentChanges, true, false); document.undo(); scrollToKeepCaretOnScreen(); return true; @@ -1121,6 +1142,10 @@ bool CodeEditorComponent::undo() bool CodeEditorComponent::redo() { + if (readOnly) + return false; + + ScopedValueSetter svs (shouldFollowDocumentChanges, true, false); document.redo(); scrollToKeepCaretOnScreen(); return true; @@ -1165,6 +1190,9 @@ bool CodeEditorComponent::keyPressed (const KeyPress& key) { if (! TextEditorKeyMapper::invokeKeyFunction (*this, key)) { + if (readOnly) + return false; + if (key == KeyPress::tabKey || key.getTextCharacter() == '\t') handleTabKey(); else if (key == KeyPress::returnKey) handleReturnKey(); else if (key == KeyPress::escapeKey) handleEscapeKey(); @@ -1220,7 +1248,7 @@ void CodeEditorComponent::getCommandInfo (const CommandID commandID, Application { case StandardApplicationCommandIDs::cut: result.setInfo (TRANS ("Cut"), TRANS ("Copies the currently selected text to the clipboard and deletes it."), "Editing", 0); - result.setActive (anythingSelected); + result.setActive (anythingSelected && ! readOnly); result.defaultKeypresses.add (KeyPress ('x', ModifierKeys::commandModifier, 0)); break; @@ -1232,12 +1260,13 @@ void CodeEditorComponent::getCommandInfo (const CommandID commandID, Application case StandardApplicationCommandIDs::paste: result.setInfo (TRANS ("Paste"), TRANS ("Inserts text from the clipboard."), "Editing", 0); + result.setActive (! readOnly); result.defaultKeypresses.add (KeyPress ('v', ModifierKeys::commandModifier, 0)); break; case StandardApplicationCommandIDs::del: result.setInfo (TRANS ("Delete"), TRANS ("Deletes any selected text."), "Editing", 0); - result.setActive (anythingSelected); + result.setActive (anythingSelected && ! readOnly); break; case StandardApplicationCommandIDs::selectAll: @@ -1248,13 +1277,13 @@ void CodeEditorComponent::getCommandInfo (const CommandID commandID, Application case StandardApplicationCommandIDs::undo: result.setInfo (TRANS ("Undo"), TRANS ("Undo"), "Editing", 0); result.defaultKeypresses.add (KeyPress ('z', ModifierKeys::commandModifier, 0)); - result.setActive (document.getUndoManager().canUndo()); + result.setActive (document.getUndoManager().canUndo() && ! readOnly); break; case StandardApplicationCommandIDs::redo: result.setInfo (TRANS ("Redo"), TRANS ("Redo"), "Editing", 0); result.defaultKeypresses.add (KeyPress ('z', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0)); - result.setActive (document.getUndoManager().canRedo()); + result.setActive (document.getUndoManager().canRedo() && ! readOnly); break; default: diff --git a/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h index d772a46..bb3fb16 100644 --- a/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h +++ b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h @@ -204,6 +204,12 @@ public: /** Returns the font that the editor is using. */ const Font& getFont() const noexcept { return font; } + /** Makes the editor read-only. */ + void setReadOnly (bool shouldBeReadOnly) noexcept; + + /** Returns true if the editor is set to be read-only. */ + bool isReadOnly() const noexcept { return readOnly; } + //============================================================================== struct JUCE_API ColourScheme { @@ -352,7 +358,7 @@ private: float charWidth; int lineHeight, linesOnScreen, columnsOnScreen; int scrollbarThickness, columnToTryToMaintain; - bool useSpacesForTabs, showLineNumbers; + bool readOnly, useSpacesForTabs, showLineNumbers, shouldFollowDocumentChanges; double xOffset; CodeDocument::Position caretPos, selectionStart, selectionEnd; diff --git a/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.cpp b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.cpp new file mode 100644 index 0000000..a58b2bd --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.cpp @@ -0,0 +1,233 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software 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 LuaTokeniserFunctions +{ + static bool isReservedKeyword (String::CharPointerType token, const int tokenLength) noexcept + { + static const char* const keywords2Char[] = + { "if", "or", "in", "do", nullptr }; + + static const char* const keywords3Char[] = + { "and", "end", "for", "nil", "not", nullptr }; + + static const char* const keywords4Char[] = + { "then", "true", "else", nullptr }; + + static const char* const keywords5Char[] = + { "false", "local", "until", "while", "break", nullptr }; + + static const char* const keywords6Char[] = + { "repeat", "return", "elseif", nullptr}; + + static const char* const keywordsOther[] = + { "function", "@interface", "@end", "@synthesize", "@dynamic", "@public", + "@private", "@property", "@protected", "@class", nullptr }; + + const char* const* k; + + switch (tokenLength) + { + case 2: k = keywords2Char; break; + case 3: k = keywords3Char; break; + case 4: k = keywords4Char; break; + case 5: k = keywords5Char; break; + case 6: k = keywords6Char; break; + + default: + if (tokenLength < 2 || tokenLength > 16) + return false; + + k = keywordsOther; + break; + } + + for (int i = 0; k[i] != 0; ++i) + if (token.compare (CharPointer_ASCII (k[i])) == 0) + return true; + + return false; + } + + template + static int parseIdentifier (Iterator& source) noexcept + { + int tokenLength = 0; + String::CharPointerType::CharType possibleIdentifier [100]; + String::CharPointerType possible (possibleIdentifier); + + while (CppTokeniserFunctions::isIdentifierBody (source.peekNextChar())) + { + const juce_wchar c = source.nextChar(); + + if (tokenLength < 20) + possible.write (c); + + ++tokenLength; + } + + if (tokenLength > 1 && tokenLength <= 16) + { + possible.writeNull(); + + if (isReservedKeyword (String::CharPointerType (possibleIdentifier), tokenLength)) + return LuaTokeniser::tokenType_keyword; + } + + return LuaTokeniser::tokenType_identifier; + } + + template + static int readNextToken (Iterator& source) + { + source.skipWhitespace(); + + const juce_wchar firstChar = source.peekNextChar(); + + switch (firstChar) + { + case 0: + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '.': + { + int result = CppTokeniserFunctions::parseNumber (source); + + if (result == LuaTokeniser::tokenType_error) + { + source.skip(); + + if (firstChar == '.') + return LuaTokeniser::tokenType_punctuation; + } + + return result; + } + + case ',': + case ';': + case ':': + source.skip(); + return LuaTokeniser::tokenType_punctuation; + + case '(': case ')': + case '{': case '}': + case '[': case ']': + source.skip(); + return LuaTokeniser::tokenType_bracket; + + case '"': + case '\'': + CppTokeniserFunctions::skipQuotedString (source); + return LuaTokeniser::tokenType_string; + + case '+': + source.skip(); + CppTokeniserFunctions::skipIfNextCharMatches (source, '+', '='); + return LuaTokeniser::tokenType_operator; + + case '-': + { + source.skip(); + int result = CppTokeniserFunctions::parseNumber (source); + + if (source.peekNextChar() == '-') + { + source.skipToEndOfLine(); + return LuaTokeniser::tokenType_comment; + } + + if (result == LuaTokeniser::tokenType_error) + { + CppTokeniserFunctions::skipIfNextCharMatches (source, '-', '='); + return LuaTokeniser::tokenType_operator; + } + + return result; + } + + case '*': case '%': + case '=': case '!': + source.skip(); + CppTokeniserFunctions::skipIfNextCharMatches (source, '='); + return LuaTokeniser::tokenType_operator; + + case '?': + case '~': + source.skip(); + return LuaTokeniser::tokenType_operator; + + case '<': case '>': + case '|': case '&': case '^': + source.skip(); + CppTokeniserFunctions::skipIfNextCharMatches (source, firstChar); + CppTokeniserFunctions::skipIfNextCharMatches (source, '='); + return LuaTokeniser::tokenType_operator; + + default: + if (CppTokeniserFunctions::isIdentifierStart (firstChar)) + return parseIdentifier (source); + + source.skip(); + break; + } + + return LuaTokeniser::tokenType_error; + } +}; + +//============================================================================== +LuaTokeniser::LuaTokeniser() {} +LuaTokeniser::~LuaTokeniser() {} + +int LuaTokeniser::readNextToken (CodeDocument::Iterator& source) +{ + return LuaTokeniserFunctions::readNextToken (source); +} + +CodeEditorComponent::ColourScheme LuaTokeniser::getDefaultColourScheme() +{ + static const CodeEditorComponent::ColourScheme::TokenType types[] = + { + { "Error", Colour (0xffcc0000) }, + { "Comment", Colour (0xff3c3c3c) }, + { "Keyword", Colour (0xff0000cc) }, + { "Operator", Colour (0xff225500) }, + { "Identifier", Colour (0xff000000) }, + { "Integer", Colour (0xff880000) }, + { "Float", Colour (0xff885500) }, + { "String", Colour (0xff990099) }, + { "Bracket", Colour (0xff000055) }, + { "Punctuation", Colour (0xff004400) } + }; + + CodeEditorComponent::ColourScheme cs; + + for (unsigned int i = 0; i < sizeof (types) / sizeof (types[0]); ++i) // (NB: numElementsInArray doesn't work here in GCC4.2) + cs.set (types[i].name, types[i].colour); + + return cs; +} diff --git a/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.h b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.h new file mode 100644 index 0000000..0cee34b --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.h @@ -0,0 +1,63 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software 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_LUACODETOKENISER_H_INCLUDED +#define JUCE_LUACODETOKENISER_H_INCLUDED + + +//============================================================================== +/** +*/ +class JUCE_API LuaTokeniser : public CodeTokeniser +{ +public: + //============================================================================== + LuaTokeniser(); + ~LuaTokeniser(); + + //============================================================================== + int readNextToken (CodeDocument::Iterator&) override; + CodeEditorComponent::ColourScheme getDefaultColourScheme() override; + + /** The token values returned by this tokeniser. */ + enum TokenType + { + tokenType_error = 0, + tokenType_comment, + tokenType_keyword, + tokenType_operator, + tokenType_identifier, + tokenType_integer, + tokenType_float, + tokenType_string, + tokenType_bracket, + tokenType_punctuation + }; + +private: + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LuaTokeniser) +}; + +#endif // JUCE_LUACODETOKENISER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp new file mode 100644 index 0000000..ecaba96 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp @@ -0,0 +1,166 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software 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. + + ============================================================================== +*/ + +XmlTokeniser::XmlTokeniser() {} +XmlTokeniser::~XmlTokeniser() {} + +CodeEditorComponent::ColourScheme XmlTokeniser::getDefaultColourScheme() +{ + struct Type + { + const char* name; + uint32 colour; + }; + + const Type types[] = + { + { "Error", 0xffcc0000 }, + { "Comment", 0xff00aa00 }, + { "Keyword", 0xff0000cc }, + { "Operator", 0xff225500 }, + { "Identifier", 0xff000000 }, + { "String", 0xff990099 }, + { "Bracket", 0xff000055 }, + { "Punctuation", 0xff004400 }, + { "Preprocessor Text", 0xff660000 } + }; + + CodeEditorComponent::ColourScheme cs; + + for (unsigned int i = 0; i < sizeof (types) / sizeof (types[0]); ++i) // (NB: numElementsInArray doesn't work here in GCC4.2) + cs.set (types[i].name, Colour (types[i].colour)); + + return cs; +}; + +template +static void skipToEndOfXmlDTD (Iterator& source) noexcept +{ + bool lastWasQuestionMark = false; + + for (;;) + { + const juce_wchar c = source.nextChar(); + + if (c == 0 || (c == '>' && lastWasQuestionMark)) + break; + + lastWasQuestionMark = (c == '?'); + } +} + +template +static void skipToEndOfXmlComment (Iterator& source) noexcept +{ + juce_wchar last[2] = { 0 }; + + for (;;) + { + const juce_wchar c = source.nextChar(); + + if (c == 0 || (c == '>' && last[0] == '-' && last[1] == '-')) + break; + + last[1] = last[0]; + last[0] = c; + } +} + +int XmlTokeniser::readNextToken (CodeDocument::Iterator& source) +{ + source.skipWhitespace(); + const juce_wchar firstChar = source.peekNextChar(); + + switch (firstChar) + { + case 0: break; + + case '"': + case '\'': + CppTokeniserFunctions::skipQuotedString (source); + return tokenType_string; + + case '<': + { + source.skip(); + source.skipWhitespace(); + const juce_wchar nextChar = source.peekNextChar(); + + if (nextChar == '?') + { + source.skip(); + skipToEndOfXmlDTD (source); + return tokenType_preprocessor; + } + + if (nextChar == '!') + { + source.skip(); + + if (source.peekNextChar() == '-') + { + source.skip(); + + if (source.peekNextChar() == '-') + { + skipToEndOfXmlComment (source); + return tokenType_comment; + } + } + } + + CppTokeniserFunctions::skipIfNextCharMatches (source, '/'); + CppTokeniserFunctions::parseIdentifier (source); + source.skipWhitespace(); + CppTokeniserFunctions::skipIfNextCharMatches (source, '/'); + source.skipWhitespace(); + CppTokeniserFunctions::skipIfNextCharMatches (source, '>'); + return tokenType_keyword; + } + + case '>': + source.skip(); + return tokenType_keyword; + + case '/': + source.skip(); + source.skipWhitespace(); + CppTokeniserFunctions::skipIfNextCharMatches (source, '>'); + return tokenType_keyword; + + case '=': + case ':': + source.skip(); + return tokenType_operator; + + default: + if (CppTokeniserFunctions::isIdentifierStart (firstChar)) + CppTokeniserFunctions::parseIdentifier (source); + + source.skip(); + break; + }; + + return tokenType_identifier; +} diff --git a/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.h b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.h new file mode 100644 index 0000000..3bf5860 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.h @@ -0,0 +1,62 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software 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_XMLCODETOKENISER_H_INCLUDED +#define JUCE_XMLCODETOKENISER_H_INCLUDED + + +//============================================================================== +/** +*/ +class JUCE_API XmlTokeniser : public CodeTokeniser +{ +public: + //============================================================================== + XmlTokeniser(); + ~XmlTokeniser(); + + //============================================================================== + int readNextToken (CodeDocument::Iterator&) override; + CodeEditorComponent::ColourScheme getDefaultColourScheme() override; + + /** The token values returned by this tokeniser. */ + enum TokenType + { + tokenType_error = 0, + tokenType_comment, + tokenType_keyword, + tokenType_operator, + tokenType_identifier, + tokenType_string, + tokenType_bracket, + tokenType_punctuation, + tokenType_preprocessor + }; + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlTokeniser) +}; + + +#endif // JUCE_XMLCODETOKENISER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_extra/juce_gui_extra.cpp b/JuceLibraryCode/modules/juce_gui_extra/juce_gui_extra.cpp index 3b2eb94..a9fab23 100644 --- a/JuceLibraryCode/modules/juce_gui_extra/juce_gui_extra.cpp +++ b/JuceLibraryCode/modules/juce_gui_extra/juce_gui_extra.cpp @@ -86,6 +86,8 @@ namespace juce #include "code_editor/juce_CodeDocument.cpp" #include "code_editor/juce_CodeEditorComponent.cpp" #include "code_editor/juce_CPlusPlusCodeTokeniser.cpp" +#include "code_editor/juce_XMLCodeTokeniser.cpp" +#include "code_editor/juce_LuaCodeTokeniser.cpp" #include "misc/juce_BubbleMessageComponent.cpp" #include "misc/juce_ColourSelector.cpp" #include "misc/juce_KeyMappingEditorComponent.cpp" @@ -93,6 +95,7 @@ namespace juce #include "misc/juce_RecentlyOpenedFilesList.cpp" #include "misc/juce_SplashScreen.cpp" #include "misc/juce_SystemTrayIconComponent.cpp" +#include "misc/juce_LiveConstantEditor.cpp" } @@ -144,4 +147,9 @@ namespace juce #endif #endif +#if JUCE_WEB_BROWSER + bool WebBrowserComponent::pageAboutToLoad (const String&) { return true; } + void WebBrowserComponent::pageFinishedLoading (const String&) {} + void WebBrowserComponent::windowCloseRequest() {} +#endif } diff --git a/JuceLibraryCode/modules/juce_gui_extra/juce_gui_extra.h b/JuceLibraryCode/modules/juce_gui_extra/juce_gui_extra.h index aead733..36532f1 100644 --- a/JuceLibraryCode/modules/juce_gui_extra/juce_gui_extra.h +++ b/JuceLibraryCode/modules/juce_gui_extra/juce_gui_extra.h @@ -37,6 +37,16 @@ #define JUCE_WEB_BROWSER 1 #endif +/** Config: JUCE_ENABLE_LIVE_CONSTANT_EDITOR + This lets you turn on the JUCE_ENABLE_LIVE_CONSTANT_EDITOR support. See the documentation + for that macro for more details. +*/ +#ifndef JUCE_ENABLE_LIVE_CONSTANT_EDITOR + #if JUCE_DEBUG + #define JUCE_ENABLE_LIVE_CONSTANT_EDITOR 1 + #endif +#endif + //============================================================================= namespace juce { @@ -47,6 +57,8 @@ namespace juce #include "code_editor/juce_CodeTokeniser.h" #include "code_editor/juce_CPlusPlusCodeTokeniser.h" #include "code_editor/juce_CPlusPlusCodeTokeniserFunctions.h" +#include "code_editor/juce_XMLCodeTokeniser.h" +#include "code_editor/juce_LuaCodeTokeniser.h" #include "embedding/juce_ActiveXControlComponent.h" #include "embedding/juce_NSViewComponent.h" #include "embedding/juce_UIViewComponent.h" @@ -59,6 +71,7 @@ namespace juce #include "misc/juce_SplashScreen.h" #include "misc/juce_SystemTrayIconComponent.h" #include "misc/juce_WebBrowserComponent.h" +#include "misc/juce_LiveConstantEditor.h" } diff --git a/JuceLibraryCode/modules/juce_gui_extra/juce_module_info b/JuceLibraryCode/modules/juce_gui_extra/juce_module_info index f84e0a3..5bbabdc 100644 --- a/JuceLibraryCode/modules/juce_gui_extra/juce_module_info +++ b/JuceLibraryCode/modules/juce_gui_extra/juce_module_info @@ -1,7 +1,7 @@ { "id": "juce_gui_extra", "name": "JUCE extended GUI classes", - "version": "3.0.0", + "version": "3.0.1", "description": "Miscellaneous GUI classes for specialised tasks.", "website": "http://www.juce.com/juce", "license": "GPL/Commercial", diff --git a/JuceLibraryCode/modules/juce_gui_extra/misc/juce_ColourSelector.cpp b/JuceLibraryCode/modules/juce_gui_extra/misc/juce_ColourSelector.cpp index 1343b21..b41379d 100644 --- a/JuceLibraryCode/modules/juce_gui_extra/misc/juce_ColourSelector.cpp +++ b/JuceLibraryCode/modules/juce_gui_extra/misc/juce_ColourSelector.cpp @@ -73,7 +73,7 @@ public: ColourSpaceView (ColourSelector& cs, float& hue, float& sat, float& val, const int edgeSize) : owner (cs), h (hue), s (sat), v (val), lastHue (0.0f), edge (edgeSize) { - addAndMakeVisible (&marker); + addAndMakeVisible (marker); setMouseCursor (MouseCursor::CrosshairCursor); } @@ -199,7 +199,7 @@ public: HueSelectorComp (ColourSelector& cs, float& hue, const int edgeSize) : owner (cs), h (hue), edge (edgeSize) { - addAndMakeVisible (&marker); + addAndMakeVisible (marker); } void paint (Graphics& g) override diff --git a/JuceLibraryCode/modules/juce_gui_extra/misc/juce_KeyMappingEditorComponent.cpp b/JuceLibraryCode/modules/juce_gui_extra/misc/juce_KeyMappingEditorComponent.cpp index 6299abf..bfded7a 100644 --- a/JuceLibraryCode/modules/juce_gui_extra/misc/juce_KeyMappingEditorComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_extra/misc/juce_KeyMappingEditorComponent.cpp @@ -403,11 +403,11 @@ KeyMappingEditorComponent::KeyMappingEditorComponent (KeyPressMappingSet& mappin if (showResetToDefaultButton) { - addAndMakeVisible (&resetButton); + addAndMakeVisible (resetButton); resetButton.addListener (treeItem); } - addAndMakeVisible (&tree); + addAndMakeVisible (tree); tree.setColour (TreeView::backgroundColourId, findColour (backgroundColourId)); tree.setRootItemVisible (false); tree.setDefaultOpenness (true); diff --git a/JuceLibraryCode/modules/juce_gui_extra/misc/juce_LiveConstantEditor.cpp b/JuceLibraryCode/modules/juce_gui_extra/misc/juce_LiveConstantEditor.cpp new file mode 100644 index 0000000..38c9ab9 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_extra/misc/juce_LiveConstantEditor.cpp @@ -0,0 +1,466 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software 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_ENABLE_LIVE_CONSTANT_EDITOR + +namespace LiveConstantEditor +{ + +//============================================================================== +class AllComponentRepainter : private Timer, + private DeletedAtShutdown +{ +public: + AllComponentRepainter() {} + + static AllComponentRepainter& getInstance() + { + static AllComponentRepainter* instance = new AllComponentRepainter(); + return *instance; + } + + void trigger() + { + if (! isTimerRunning()) + startTimer (100); + } + +private: + void timerCallback() override + { + stopTimer(); + + for (int i = TopLevelWindow::getNumTopLevelWindows(); --i >= 0;) + if (Component* c = TopLevelWindow::getTopLevelWindow(i)) + repaintAndResizeAllComps (c); + } + + static void repaintAndResizeAllComps (Component::SafePointer c) + { + if (c->isVisible()) + { + c->repaint(); + c->resized(); + + for (int i = c->getNumChildComponents(); --i >= 0;) + if (c != nullptr) + if (Component* child = c->getChildComponent(i)) + repaintAndResizeAllComps (child); + } + } +}; + +//============================================================================== +int64 parseInt (String s) +{ + s = s.retainCharacters ("0123456789abcdefABCDEFx"); + + if (s.startsWith ("0x")) + return s.substring(2).getHexValue64(); + + return s.getLargeIntValue(); +} + +double parseDouble (const String& s) +{ + return s.retainCharacters ("0123456789.eE-").getDoubleValue(); +} + +String intToString (int v, bool preferHex) { return preferHex ? "0x" + String::toHexString (v) : String (v); } +String intToString (int64 v, bool preferHex) { return preferHex ? "0x" + String::toHexString (v) : String (v); } + +//============================================================================== +LiveValueBase::LiveValueBase (const char* file, int line) + : sourceFile (file), sourceLine (line) +{ + name = File (sourceFile).getFileName() + " : " + String (sourceLine); +} + +LiveValueBase::~LiveValueBase() +{ +} + +//============================================================================== +LivePropertyEditorBase::LivePropertyEditorBase (LiveValueBase& v, CodeDocument& d) + : value (v), resetButton ("reset"), document (d), sourceEditor (document, &tokeniser), wasHex (false) +{ + setSize (600, 100); + + addAndMakeVisible (name); + addAndMakeVisible (resetButton); + addAndMakeVisible (valueEditor); + addAndMakeVisible (sourceEditor); + + findOriginalValueInCode(); + selectOriginalValue(); + + name.setFont (13.0f); + name.setText (v.name, dontSendNotification); + valueEditor.setText (v.getStringValue (wasHex), dontSendNotification); + valueEditor.addListener (this); + sourceEditor.setReadOnly (true); + resetButton.addListener (this); +} + +void LivePropertyEditorBase::paint (Graphics& g) +{ + g.setColour (Colours::white); + g.fillRect (getLocalBounds().removeFromBottom (1)); +} + +void LivePropertyEditorBase::resized() +{ + Rectangle r (getLocalBounds().reduced (0, 3).withTrimmedBottom (1)); + + Rectangle left (r.removeFromLeft (jmax (200, r.getWidth() / 3))); + + Rectangle top (left.removeFromTop (25)); + resetButton.setBounds (top.removeFromRight (35).reduced (0, 3)); + name.setBounds (top); + valueEditor.setBounds (left.removeFromTop (25)); + left.removeFromTop (2); + + if (customComp != nullptr) + customComp->setBounds (left); + + r.removeFromLeft (4); + sourceEditor.setBounds (r); +} + +void LivePropertyEditorBase::textEditorTextChanged (TextEditor&) +{ + applyNewValue (valueEditor.getText()); +} + +void LivePropertyEditorBase::buttonClicked (Button*) +{ + applyNewValue (value.getOriginalStringValue (wasHex)); +} + +void LivePropertyEditorBase::applyNewValue (const String& s) +{ + value.setStringValue (s); + + document.replaceSection (valueStart.getPosition(), valueEnd.getPosition(), value.getCodeValue (wasHex)); + document.clearUndoHistory(); + selectOriginalValue(); + + valueEditor.setText (s, dontSendNotification); + AllComponentRepainter::getInstance().trigger(); +} + +void LivePropertyEditorBase::selectOriginalValue() +{ + sourceEditor.selectRegion (valueStart, valueEnd); +} + +void LivePropertyEditorBase::findOriginalValueInCode() +{ + CodeDocument::Position pos (document, value.sourceLine, 0); + String line (pos.getLineText()); + String::CharPointerType p (line.getCharPointer()); + + p = CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT")); + + if (p.isEmpty()) + { + // Not sure how this would happen - some kind of mix-up between source code and line numbers.. + jassertfalse; + return; + } + + p += (int) (sizeof ("JUCE_LIVE_CONSTANT") - 1); + p = p.findEndOfWhitespace(); + + if (! CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT")).isEmpty()) + { + // Aargh! You've added two JUCE_LIVE_CONSTANT macros on the same line! + // They're identified by their line number, so you must make sure each + // one goes on a separate line! + jassertfalse; + } + + if (p.getAndAdvance() == '(') + { + String::CharPointerType start (p), end (p); + + int depth = 1; + + while (! end.isEmpty()) + { + const juce_wchar c = end.getAndAdvance(); + + if (c == '(') ++depth; + if (c == ')') --depth; + + if (depth == 0) + { + --end; + break; + } + } + + if (end > start) + { + valueStart = CodeDocument::Position (document, value.sourceLine, (int) (start - line.getCharPointer())); + valueEnd = CodeDocument::Position (document, value.sourceLine, (int) (end - line.getCharPointer())); + + valueStart.setPositionMaintained (true); + valueEnd.setPositionMaintained (true); + + wasHex = String (start, end).containsIgnoreCase ("0x"); + } + } +} + +//============================================================================== +class ValueListHolderComponent : public Component +{ +public: + ValueListHolderComponent (ValueList& l) : valueList (l) + { + setVisible (true); + } + + void addItem (int width, LiveValueBase& v, CodeDocument& doc) + { + addAndMakeVisible (editors.add (v.createPropertyComponent (doc))); + layout (width); + } + + void layout (int width) + { + setSize (width, editors.size() * itemHeight); + resized(); + } + + void resized() override + { + Rectangle r (getLocalBounds().reduced (2, 0)); + + for (int i = 0; i < editors.size(); ++i) + editors.getUnchecked(i)->setBounds (r.removeFromTop (itemHeight)); + } + + enum { itemHeight = 120 }; + + ValueList& valueList; + OwnedArray editors; +}; + +//============================================================================== +class ValueList::EditorWindow : public DocumentWindow, + private DeletedAtShutdown +{ +public: + EditorWindow (ValueList& list) + : DocumentWindow ("Live Values", Colours::lightgrey, DocumentWindow::closeButton) + { + setLookAndFeel (&lookAndFeel); + setUsingNativeTitleBar (true); + + viewport.setViewedComponent (new ValueListHolderComponent (list), true); + viewport.setSize (700, 600); + viewport.setScrollBarsShown (true, false); + + setContentNonOwned (&viewport, true); + setResizable (true, false); + setResizeLimits (500, 400, 10000, 10000); + centreWithSize (getWidth(), getHeight()); + setVisible (true); + } + + void closeButtonPressed() override + { + setVisible (false); + } + + void updateItems (ValueList& list) + { + if (ValueListHolderComponent* l = dynamic_cast (viewport.getViewedComponent())) + { + while (l->getNumChildComponents() < list.values.size()) + { + if (LiveValueBase* v = list.values [l->getNumChildComponents()]) + l->addItem (viewport.getMaximumVisibleWidth(), *v, list.getDocument (v->sourceFile)); + else + break; + } + + setVisible (true); + } + } + + void resized() override + { + DocumentWindow::resized(); + + if (ValueListHolderComponent* l = dynamic_cast (viewport.getViewedComponent())) + l->layout (viewport.getMaximumVisibleWidth()); + } + + Viewport viewport; + LookAndFeel_V3 lookAndFeel; +}; + +//============================================================================== +ValueList::ValueList() {} +ValueList::~ValueList() {} + +ValueList& ValueList::getInstance() +{ + static ValueList* i = new ValueList(); + return *i; +} + +void ValueList::addValue (LiveValueBase* v) +{ + values.add (v); + triggerAsyncUpdate(); +} + +void ValueList::handleAsyncUpdate() +{ + if (editorWindow == nullptr) + editorWindow = new EditorWindow (*this); + + editorWindow->updateItems (*this); +} + +CodeDocument& ValueList::getDocument (const File& file) +{ + const int index = documentFiles.indexOf (file.getFullPathName()); + + if (index >= 0) + return *documents.getUnchecked (index); + + CodeDocument* doc = documents.add (new CodeDocument()); + documentFiles.add (file); + doc->replaceAllContent (file.loadFileAsString()); + doc->clearUndoHistory(); + return *doc; +} + +//============================================================================== +struct ColourEditorComp : public Component, + private ChangeListener +{ + ColourEditorComp (LivePropertyEditorBase& e) : editor (e) + { + setMouseCursor (MouseCursor::PointingHandCursor); + } + + Colour getColour() const + { + return Colour ((int) parseInt (editor.value.getStringValue (false))); + } + + void paint (Graphics& g) override + { + g.fillCheckerBoard (getLocalBounds(), 6, 6, + Colour (0xffdddddd).overlaidWith (getColour()), + Colour (0xffffffff).overlaidWith (getColour())); + } + + void mouseDown (const MouseEvent&) override + { + ColourSelector* colourSelector = new ColourSelector(); + colourSelector->setName ("Colour"); + colourSelector->setCurrentColour (getColour()); + colourSelector->addChangeListener (this); + colourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack); + colourSelector->setSize (300, 400); + + CallOutBox::launchAsynchronously (colourSelector, getScreenBounds(), nullptr); + } + + void changeListenerCallback (ChangeBroadcaster* source) override + { + if (ColourSelector* cs = dynamic_cast (source)) + editor.applyNewValue (getAsString (cs->getCurrentColour(), true)); + + repaint(); + } + + LivePropertyEditorBase& editor; +}; + +Component* createColourEditor (LivePropertyEditorBase& editor) +{ + return new ColourEditorComp (editor); +} + +//============================================================================== +class SliderComp : public Component, + private Slider::Listener +{ +public: + SliderComp (LivePropertyEditorBase& e, bool useFloat) + : editor (e), isFloat (useFloat) + { + slider.setTextBoxStyle (Slider::NoTextBox, true, 0, 0); + addAndMakeVisible (slider); + updateRange(); + slider.addListener (this); + } + + void updateRange() + { + double v = isFloat ? parseDouble (editor.value.getStringValue (false)) + : (double) parseInt (editor.value.getStringValue (false)); + + double range = isFloat ? 10 : 100; + + slider.setRange (v - range, v + range); + slider.setValue (v, dontSendNotification); + } + +private: + LivePropertyEditorBase& editor; + Slider slider; + bool isFloat; + + void sliderValueChanged (Slider*) + { + editor.applyNewValue (isFloat ? getAsString ((double) slider.getValue(), editor.wasHex) + : getAsString ((int64) slider.getValue(), editor.wasHex)); + + } + + void sliderDragStarted (Slider*) {} + void sliderDragEnded (Slider*) { updateRange(); } + + void resized() + { + slider.setBounds (getLocalBounds().removeFromTop (25)); + } +}; + + +Component* createIntegerSlider (LivePropertyEditorBase& editor) { return new SliderComp (editor, false); } +Component* createFloatSlider (LivePropertyEditorBase& editor) { return new SliderComp (editor, true); } + +} + +#endif diff --git a/JuceLibraryCode/modules/juce_gui_extra/misc/juce_LiveConstantEditor.h b/JuceLibraryCode/modules/juce_gui_extra/misc/juce_LiveConstantEditor.h new file mode 100644 index 0000000..db0e728 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_extra/misc/juce_LiveConstantEditor.h @@ -0,0 +1,298 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software 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_LIVECONSTANTEDITOR_H_INCLUDED +#define JUCE_LIVECONSTANTEDITOR_H_INCLUDED + +#if JUCE_ENABLE_LIVE_CONSTANT_EDITOR && ! DOXYGEN + +//============================================================================== +/** You can safely ignore all the stuff in this namespace - it's a bunch of boilerplate + code used to implement the JUCE_LIVE_CONSTANT functionality. +*/ +namespace LiveConstantEditor +{ + int64 parseInt (String); + double parseDouble (const String&); + String intToString (int, bool preferHex); + String intToString (int64, bool preferHex); + + template + static void setFromString (Type& v, const String& s) { v = static_cast (s); } + inline void setFromString (char& v, const String& s) { v = (char) parseInt (s); } + inline void setFromString (unsigned char& v, const String& s) { v = (unsigned char) parseInt (s); } + inline void setFromString (short& v, const String& s) { v = (short) parseInt (s); } + inline void setFromString (unsigned short& v, const String& s) { v = (unsigned short) parseInt (s); } + inline void setFromString (int& v, const String& s) { v = (int) parseInt (s); } + inline void setFromString (unsigned int& v, const String& s) { v = (unsigned int) parseInt (s); } + inline void setFromString (long& v, const String& s) { v = (long) parseInt (s); } + inline void setFromString (unsigned long& v, const String& s) { v = (unsigned long) parseInt (s); } + inline void setFromString (int64& v, const String& s) { v = (int64) parseInt (s); } + inline void setFromString (uint64& v, const String& s) { v = (uint64) parseInt (s); } + inline void setFromString (double& v, const String& s) { v = parseDouble (s); } + inline void setFromString (float& v, const String& s) { v = (float) parseDouble (s); } + inline void setFromString (String& v, const String& s) { v = s; } + inline void setFromString (Colour& v, const String& s) { v = Colour ((int) parseInt (s)); } + + template + inline String getAsString (const Type& v, bool) { return String (v); } + inline String getAsString (char v, bool preferHex) { return intToString ((int) v, preferHex); } + inline String getAsString (unsigned char v, bool preferHex) { return intToString ((int) v, preferHex); } + inline String getAsString (short v, bool preferHex) { return intToString ((int) v, preferHex); } + inline String getAsString (unsigned short v, bool preferHex) { return intToString ((int) v, preferHex); } + inline String getAsString (int v, bool preferHex) { return intToString ((int) v, preferHex); } + inline String getAsString (unsigned int v, bool preferHex) { return intToString ((int) v, preferHex); } + inline String getAsString (int64 v, bool preferHex) { return intToString ((int64) v, preferHex); } + inline String getAsString (uint64 v, bool preferHex) { return intToString ((int64) v, preferHex); } + inline String getAsString (Colour v, bool) { return intToString ((int) v.getARGB(), true); } + + template + inline String getAsCode (Type& v, bool preferHex) { return getAsString (v, preferHex); } + inline String getAsCode (Colour v, bool) { return "Colour (0x" + String::toHexString ((int) v.getARGB()).paddedLeft ('0', 8) + ")"; } + inline String getAsCode (const String& v, bool) { return "\"" + v + "\""; } + inline String getAsCode (const char* v, bool) { return getAsCode (String (v), false); } + + template + inline const char* castToCharPointer (const Type&) { return ""; } + inline const char* castToCharPointer (const String& s) { return s.toRawUTF8(); } + + struct LivePropertyEditorBase; + + //============================================================================== + struct JUCE_API LiveValueBase + { + LiveValueBase (const char* file, int line); + virtual ~LiveValueBase(); + + virtual LivePropertyEditorBase* createPropertyComponent (CodeDocument&) = 0; + virtual String getStringValue (bool preferHex) const = 0; + virtual String getCodeValue (bool preferHex) const = 0; + virtual void setStringValue (const String&) = 0; + virtual String getOriginalStringValue (bool preferHex) const = 0; + + String name, sourceFile; + int sourceLine; + + JUCE_DECLARE_NON_COPYABLE (LiveValueBase) + }; + + //============================================================================== + struct JUCE_API LivePropertyEditorBase : public Component, + private TextEditor::Listener, + private ButtonListener + { + LivePropertyEditorBase (LiveValueBase&, CodeDocument&); + + void paint (Graphics&) override; + void resized() override; + void textEditorTextChanged (TextEditor&) override; + void buttonClicked (Button*) override; + + void applyNewValue (const String&); + void selectOriginalValue(); + void findOriginalValueInCode(); + + LiveValueBase& value; + Label name; + TextEditor valueEditor; + TextButton resetButton; + CodeDocument& document; + CPlusPlusCodeTokeniser tokeniser; + CodeEditorComponent sourceEditor; + CodeDocument::Position valueStart, valueEnd; + ScopedPointer customComp; + bool wasHex; + + JUCE_DECLARE_NON_COPYABLE (LivePropertyEditorBase) + }; + + //============================================================================== + Component* createColourEditor (LivePropertyEditorBase&); + Component* createIntegerSlider (LivePropertyEditorBase&); + Component* createFloatSlider (LivePropertyEditorBase&); + + template struct CustomEditor { static Component* create (LivePropertyEditorBase&) { return nullptr; } }; + template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createFloatSlider (e); } }; + template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createFloatSlider (e); } }; + template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createColourEditor (e); } }; + + template + struct LivePropertyEditor : public LivePropertyEditorBase + { + template + LivePropertyEditor (ValueType& v, CodeDocument& d) : LivePropertyEditorBase (v, d) + { + addAndMakeVisible (customComp = CustomEditor::create (*this)); + } + }; + + //============================================================================== + template + struct LiveValue : public LiveValueBase + { + LiveValue (const char* file, int line, const Type& initialValue) + : LiveValueBase (file, line), value (initialValue), originalValue (initialValue) + {} + + operator Type() const noexcept { return value; } + operator const char*() const { return castToCharPointer (value); } + + LivePropertyEditorBase* createPropertyComponent (CodeDocument& doc) override + { + return new LivePropertyEditor (*this, doc); + } + + String getStringValue (bool preferHex) const override { return getAsString (value, preferHex); } + String getCodeValue (bool preferHex) const override { return getAsCode (value, preferHex); } + String getOriginalStringValue (bool preferHex) const override { return getAsString (originalValue, preferHex); } + void setStringValue (const String& s) override { setFromString (value, s); } + + Type value, originalValue; + + JUCE_DECLARE_NON_COPYABLE (LiveValue) + }; + + //============================================================================== + class JUCE_API ValueList : private AsyncUpdater, + private DeletedAtShutdown + { + public: + ValueList(); + ~ValueList(); + + static ValueList& getInstance(); + + template + LiveValue& getValue (const char* file, int line, const Type& initialValue) + { + const ScopedLock sl (lock); + typedef LiveValue ValueType; + + for (int i = 0; i < values.size(); ++i) + { + LiveValueBase* v = values.getUnchecked(i); + + if (v->sourceLine == line && v->sourceFile == file) + return *static_cast (v); + } + + ValueType* v = new ValueType (file, line, initialValue); + addValue (v); + return *v; + } + + private: + OwnedArray values; + OwnedArray documents; + Array documentFiles; + class EditorWindow; + friend class EditorWindow; + friend struct ContainerDeletePolicy; + Component::SafePointer editorWindow; + CriticalSection lock; + + CodeDocument& getDocument (const File&); + void addValue (LiveValueBase*); + void handleAsyncUpdate() override; + }; + + template + inline LiveValue& getValue (const char* file, int line, const Type& initialValue) + { + return ValueList::getInstance().getValue (file, line, initialValue); + } + + inline LiveValue& getValue (const char* file, int line, const char* initialValue) + { + return getValue (file, line, String (initialValue)); + } +} + +#endif + +//============================================================================== +#if JUCE_ENABLE_LIVE_CONSTANT_EDITOR || DOXYGEN + /** + This macro wraps a primitive constant value in some cunning boilerplate code that allows + its value to be interactively tweaked in a popup window while your application is running. + + In a release build, this macro disappears and is replaced by only the constant that it + wraps, but if JUCE_ENABLE_LIVE_CONSTANT_EDITOR is enabled, it injects a class wrapper + that automatically pops-up a window containing an editor that allows the value to be + tweaked at run-time. The editor window will also force all visible components to be + resized and repainted whenever a value is changed, so that if you use this to wrap + a colour or layout parameter, you'll be able to immediately see the effects of changing it. + + The editor will also load the original source-file that contains each JUCE_LIVE_CONSTANT + macro, and will display a preview of the modified source code as you adjust the values. + + Things to note: + + - Only one of these per line! The __FILE__ and __LINE__ macros are used to identify + the value, so things will get confused if you have more than one per line + - Obviously because it needs to load the source code based on the __FILE__ macro, + it'll only work if the source files are stored locally in the same location as they + were when you compiled the program. + - It's only designed to cope with simple types: primitives, string literals, and + the Colour class, so if you try using it for other classes or complex expressions, + good luck! + - The editor window will get popped up whenever a new value is used for the first + time. You can close the window, but there's no way to get it back without restarting + the app! + + e.g. in this example the colours, font size, and text used in the paint method can + all be adjusted live: + @code + void MyComp::paint (Graphics& g) override + { + g.fillAll (JUCE_LIVE_CONSTANT (Colour (0xffddddff))); + + Colour fontColour = JUCE_LIVE_CONSTANT (Colour (0xff005500)); + float fontSize = JUCE_LIVE_CONSTANT (16.0f); + + g.setColour (fontColour); + g.setFont (fontSize); + + g.drawFittedText (JUCE_LIVE_CONSTANT ("Hello world!"), + getLocalBounds(), Justification::centred, 2); + } + @endcode + */ + #define JUCE_LIVE_CONSTANT(initialValue) \ + (LiveConstantEditor::getValue (__FILE__, __LINE__ - 1, initialValue)) +#else + #define JUCE_LIVE_CONSTANT(initialValue) \ + (initialValue) +#endif + + +#endif // JUCE_LIVECONSTANTEDITOR_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h b/JuceLibraryCode/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h index c6f5925..6afad60 100644 --- a/JuceLibraryCode/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h +++ b/JuceLibraryCode/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h @@ -93,6 +93,11 @@ public: /** This callback happens when the browser has finished loading a page. */ virtual void pageFinishedLoading (const String& url); + /** This callback occurs when a script or other activity in the browser asks for + the window to be closed. + */ + virtual void windowCloseRequest(); + //============================================================================== /** @internal */ void paint (Graphics&) override; diff --git a/JuceLibraryCode/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp b/JuceLibraryCode/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp index 971715d..dcf786d 100644 --- a/JuceLibraryCode/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp @@ -60,14 +60,14 @@ void WebBrowserComponent::stop() void WebBrowserComponent::goBack() { - lastURL = String::empty; + lastURL.clear(); blankPageShown = false; } void WebBrowserComponent::goForward() { - lastURL = String::empty; + lastURL.clear(); } @@ -90,7 +90,7 @@ void WebBrowserComponent::reloadLastURL() if (lastURL.isNotEmpty()) { goToURL (lastURL, &lastHeaders, &lastPostData); - lastURL = String::empty; + lastURL.clear(); } } @@ -107,6 +107,3 @@ void WebBrowserComponent::visibilityChanged() { checkWindowAssociation(); } - -bool WebBrowserComponent::pageAboutToLoad (const String&) { return true; } -void WebBrowserComponent::pageFinishedLoading (const String&) {} diff --git a/JuceLibraryCode/modules/juce_gui_extra/native/juce_linux_WebBrowserComponent.cpp b/JuceLibraryCode/modules/juce_gui_extra/native/juce_linux_WebBrowserComponent.cpp index d9212e6..1a36b74 100644 --- a/JuceLibraryCode/modules/juce_gui_extra/native/juce_linux_WebBrowserComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_extra/native/juce_linux_WebBrowserComponent.cpp @@ -65,14 +65,14 @@ void WebBrowserComponent::stop() void WebBrowserComponent::goBack() { - lastURL = String::empty; + lastURL.clear(); blankPageShown = false; } void WebBrowserComponent::goForward() { - lastURL = String::empty; + lastURL.clear(); } @@ -95,7 +95,7 @@ void WebBrowserComponent::reloadLastURL() if (lastURL.isNotEmpty()) { goToURL (lastURL, &lastHeaders, &lastPostData); - lastURL = String::empty; + lastURL.clear(); } } @@ -112,6 +112,3 @@ void WebBrowserComponent::visibilityChanged() { checkWindowAssociation(); } - -bool WebBrowserComponent::pageAboutToLoad (const String&) { return true; } -void WebBrowserComponent::pageFinishedLoading (const String&) {} diff --git a/JuceLibraryCode/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm b/JuceLibraryCode/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm index a7997bb..286ff80 100644 --- a/JuceLibraryCode/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm +++ b/JuceLibraryCode/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm @@ -33,6 +33,7 @@ struct DownloadClickDetectorClass : public ObjCClass addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:), decidePolicyForNavigationAction, "v@:@@@@@"); addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame, "v@:@@"); + addMethod (@selector (webView:willCloseFrame:), willCloseFrame, "v@:@@"); registerClass(); } @@ -60,6 +61,11 @@ private: getOwner (self)->pageFinishedLoading (nsStringToJuce ([url absoluteString])); } } + + static void willCloseFrame (id self, SEL, WebView*, WebFrame*) + { + getOwner (self)->windowCloseRequest(); + } }; #else @@ -279,14 +285,14 @@ void WebBrowserComponent::stop() void WebBrowserComponent::goBack() { - lastURL = String::empty; + lastURL.clear(); blankPageShown = false; browser->goBack(); } void WebBrowserComponent::goForward() { - lastURL = String::empty; + lastURL.clear(); browser->goForward(); } @@ -328,7 +334,7 @@ void WebBrowserComponent::reloadLastURL() if (lastURL.isNotEmpty()) { goToURL (lastURL, &lastHeaders, &lastPostData); - lastURL = String::empty; + lastURL.clear(); } } @@ -346,6 +352,3 @@ void WebBrowserComponent::visibilityChanged() { checkWindowAssociation(); } - -bool WebBrowserComponent::pageAboutToLoad (const String&) { return true; } -void WebBrowserComponent::pageFinishedLoading (const String&) {} diff --git a/JuceLibraryCode/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp b/JuceLibraryCode/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp index 3b40c50..76f4c1d 100644 --- a/JuceLibraryCode/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp @@ -158,6 +158,16 @@ private: owner.pageFinishedLoading (getStringFromVariant (pDispParams->rgvarg[0].pvarVal)); return S_OK; } + else if (dispIdMember == DISPID_WINDOWCLOSING) + { + owner.windowCloseRequest(); + + // setting this bool tells the browser to ignore the event - we'll handle it. + if (pDispParams->cArgs > 0 && pDispParams->rgvarg[0].vt == (VT_BYREF | VT_BOOL)) + *pDispParams->rgvarg[0].pboolVal = VARIANT_TRUE; + + return S_OK; + } return E_NOTIMPL; } @@ -225,7 +235,7 @@ void WebBrowserComponent::stop() void WebBrowserComponent::goBack() { - lastURL = String::empty; + lastURL.clear(); blankPageShown = false; if (browser->browser != nullptr) @@ -234,7 +244,7 @@ void WebBrowserComponent::goBack() void WebBrowserComponent::goForward() { - lastURL = String::empty; + lastURL.clear(); if (browser->browser != nullptr) browser->browser->GoForward(); @@ -287,7 +297,7 @@ void WebBrowserComponent::reloadLastURL() if (lastURL.isNotEmpty()) { goToURL (lastURL, &lastHeaders, &lastPostData); - lastURL = String::empty; + lastURL.clear(); } } @@ -305,6 +315,3 @@ void WebBrowserComponent::visibilityChanged() { checkWindowAssociation(); } - -bool WebBrowserComponent::pageAboutToLoad (const String&) { return true; } -void WebBrowserComponent::pageFinishedLoading (const String&) {}