diff --git a/.github/workflows/merge_upstream.yaml b/.github/workflows/merge_upstream.yaml new file mode 100644 index 000000000000..b6763eeab8cc --- /dev/null +++ b/.github/workflows/merge_upstream.yaml @@ -0,0 +1,23 @@ +name: Merge Upstream +on: + schedule: + - cron: '0 * * * *' + push: + workflow_dispatch: + +jobs: + build: + name: Merge Upstream + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + token: ${{ secrets.TOKEN }} + - run: | + git fetch --prune --unshallow + - name: "Run script" + run: | + ./.github/workflows/update + env: + GH_EMAIL: ${{ secrets.GH_EMAIL }} + GH_NAME: ${{ secrets.GH_NAME }} diff --git a/.github/workflows/update b/.github/workflows/update new file mode 100755 index 000000000000..cc83df00c37c --- /dev/null +++ b/.github/workflows/update @@ -0,0 +1,32 @@ +#!/bin/sh -e + +# Use braces to load the entire script in to memory +{ + echo ** Move to repo + cd $RUNNER_WORKSPACE/JUCE + pwd + + git config --global user.email "$GH_NAME" + git config --global user.name "$GH_EMAIL" + + echo ** Updating tracktion/JUCE from juce-framework/JUCE + + git checkout develop + + echo ** Setup remote + git remote -v + git remote add upstream https://github.com/juce-framework/JUCE.git + git remote -v + + echo ** Fetch remote + git fetch upstream + + echo ** Update develop + git merge upstream/develop + git push + + echo ** Update tracktion/develop + git checkout tracktion/develop + git merge develop + git push +} \ No newline at end of file diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp index 262f0a00c06a..39af1383f86c 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp @@ -910,6 +910,7 @@ class JuceVSTWrapper final : public AudioProcessorListener, case Vst2::effGetNumMidiInputChannels: return handleGetNumMidiInputChannels(); case Vst2::effGetNumMidiOutputChannels: return handleGetNumMidiOutputChannels(); case Vst2::effEditIdle: return handleEditIdle(); + case Vst2::effGetMidiKeyName: return handleGetMidiKeyName (args); default: return 0; } } @@ -2047,6 +2048,22 @@ class JuceVSTWrapper final : public AudioProcessorListener, return 0; } + pointer_sized_int handleGetMidiKeyName (VstOpCodeArguments args) + { + if (processor != nullptr) + { + String name; + Vst2::MidiKeyName* keyName = (Vst2::MidiKeyName*)args.ptr; + + if (processor->hasNameForMidiNoteNumber (keyName->thisKeyNumber, args.index, name)) + { + name.copyToUTF8 (keyName->keyName, sizeof (keyName->keyName)); + return 1; + } + } + return 0; + } + //============================================================================== ScopedJuceInitialiser_GUI libraryInitialiser; diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp index 39dc1c50b9bf..8c9731db7016 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp @@ -497,9 +497,29 @@ class JuceAudioProcessor final : public Vst::IUnitInfo return kResultFalse; } + tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID, Steinberg::int32) override + { + String name; + for (int i = 0; i < 127; i++) + if (audioProcessor->hasNameForMidiNoteNumber (i, 1, name) == kResultTrue) + return kResultTrue; + + return kResultFalse; + } + + tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID, Steinberg::int32, Steinberg::int16 midiNote, Vst::String128 name) override + { + String nameOut; + if (audioProcessor->hasNameForMidiNoteNumber (midiNote, 1, nameOut)) + { + toString128 (name, nameOut); + return kResultTrue; + } + + return kResultFalse; + } + tresult PLUGIN_API getProgramInfo (Vst::ProgramListID, Steinberg::int32, Vst::CString, Vst::String128) override { return kNotImplemented; } - tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID, Steinberg::int32) override { return kNotImplemented; } - tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID, Steinberg::int32, Steinberg::int16, Vst::String128) override { return kNotImplemented; } tresult PLUGIN_API selectUnit (Vst::UnitID) override { return kNotImplemented; } tresult PLUGIN_API setUnitProgramData (Steinberg::int32, Steinberg::int32, IBStream*) override { return kNotImplemented; } Vst::UnitID PLUGIN_API getSelectedUnit() override { return Vst::kRootUnitId; } diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 107506a3e98c..a210e34d3b57 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -2863,6 +2863,34 @@ class VST3PluginInstance final : public AudioPluginInstance return result; } + //============================================================================== + bool hasNameForMidiNoteNumber (int note, int /*midiChannel*/, juce::String& noteName) override + { + if (unitInfo != nullptr) + { + const int programListCount = unitInfo->getProgramListCount(); + + if (programListCount > 0) + { + Vst::ProgramListInfo programListInfo {}; + + if (unitInfo->getProgramListInfo (0, programListInfo) == kResultOk) + { + if (unitInfo->hasProgramPitchNames (programListInfo.id, 0) == kResultTrue) + { + Vst::String128 name; + if (unitInfo->getProgramPitchName (programListInfo.id, 0, (Steinberg::int16) note, name) == kResultTrue) + { + noteName = toString (name) ; + return true; + } + } + } + } + } + return false; + } + //============================================================================== void updateTrackProperties (const TrackProperties& properties) override { diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index d08e11f5daeb..22b69afff74c 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -1333,6 +1333,21 @@ struct VSTPluginInstance final : public AudioPluginInstance, int pluginCanDo (const char* text) const { return (int) dispatch (Vst2::effCanDo, 0, 0, (void*) text, 0); } + bool hasNameForMidiNoteNumber (int note, int midiChannel, juce::String& noteName) override + { + Vst2::MidiKeyName keyName; + memset (&keyName, 0, sizeof (keyName)); + keyName.thisProgramIndex = getCurrentProgram(); + keyName.thisKeyNumber = note; + + if (dispatch (Vst2::effGetMidiKeyName, midiChannel, 0, &keyName, 0.0f)) + { + noteName = String::createStringFromData (keyName.keyName, sizeof (keyName.keyName)); + return true; + } + return false; + } + //============================================================================== void prepareToPlay (double rate, int samplesPerBlockExpected) override { @@ -2942,6 +2957,11 @@ struct VSTPluginWindow final : public AudioProcessorEditor, if (safeThis != nullptr) safeThis->componentMovedOrResized (true, true); }); + Timer::callAfterDelay (250, [safeThis = SafePointer { this }] + { + if (safeThis != nullptr) + safeThis->componentMovedOrResized (true, true); + }); #else componentMovedOrResized (true, true); #endif diff --git a/modules/juce_audio_processors/juce_audio_processors.cpp b/modules/juce_audio_processors/juce_audio_processors.cpp index 71a64c38f5af..4558af1359b0 100644 --- a/modules/juce_audio_processors/juce_audio_processors.cpp +++ b/modules/juce_audio_processors/juce_audio_processors.cpp @@ -176,6 +176,10 @@ struct NSViewComponentWithParent : public NSViewComponent, addMethod (@selector (clipsToBounds), [] (id, SEL) { return YES; }); JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + addMethod (@selector (clipsToBounds), [] (id, SEL) { return YES; }); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + registerClass(); } }; diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index 078c9387fb90..61cec61325b6 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -919,6 +919,11 @@ void AudioProcessor::setCurrentProgramStateInformation (const void* data, int si //============================================================================== void AudioProcessor::updateTrackProperties (const AudioProcessor::TrackProperties&) {} +bool AudioProcessor::hasNameForMidiNoteNumber (int, int, juce::String&) +{ + return false; +} + //============================================================================== // magic number to identify memory blocks that we've stored as XML const uint32 magicXmlNumber = 0x21324356; diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index aa9995c2e4f1..7fe12fdb9b08 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -1294,6 +1294,9 @@ class JUCE_API AudioProcessor : private AAXClientExtensions */ virtual void updateTrackProperties (const TrackProperties& properties); + /** Gets the name of a MIDI note, if available */ + virtual bool hasNameForMidiNoteNumber (int note, int midiChannel, juce::String& name); + //============================================================================== /** Helper function that just converts an xml element into a binary blob. diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp index 0d3104c1ab5c..2b3a40c3f56e 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp @@ -176,7 +176,8 @@ void AudioProcessorEditor::editorResized (bool wasResized) // obliterate it! If you want to scale the whole of your UI use Desktop::setGlobalScaleFactor(), // or, for applying other transforms, consider putting the component you want to transform // in a child of the editor and transform that instead. - jassert (getTransform() == hostScaleTransform); + if (findParentComponentOfClass() == nullptr) + jassert (getTransform() == hostScaleTransform); if (wasResized) { diff --git a/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp b/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp index 2aaaaeb7a11e..7478906103b9 100644 --- a/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp +++ b/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp @@ -296,28 +296,31 @@ PopupMenu PluginListComponent::createOptionsMenu() for (auto format : formatManager.getFormats()) if (format->canScanForPlugins()) - menu.addItem (PopupMenu::Item ("Remove all " + format->getName() + " plug-ins") + menu.addItem (PopupMenu::Item ("Remove all " + format->getName() + " plugins") .setEnabled (! list.getTypesForFormat (*format).isEmpty()) .setAction ([this, format] { - for (auto& pd : list.getTypesForFormat (*format)) - list.removeType (pd); + auto title = "Remove all " + format->getName() + " plugins?"; + auto message = "Are you sure you want to remove all " + format->getName() + " plugins?"; + if (AlertWindow::showOkCancelBox (AlertWindow::QuestionIcon, title, message, TRANS("Yes"), TRANS("No"), nullptr, nullptr)) + for (auto& pd : list.getTypesForFormat (*format)) + list.removeType (pd); })); menu.addSeparator(); - menu.addItem (PopupMenu::Item (TRANS ("Remove selected plug-in from list")) + menu.addItem (PopupMenu::Item (TRANS("Remove selected plugin from list")) .setEnabled (table.getNumSelectedRows() > 0) .setAction ([this] { removeSelectedPlugins(); })); - menu.addItem (PopupMenu::Item (TRANS ("Remove any plug-ins whose files no longer exist")) + menu.addItem (PopupMenu::Item (TRANS("Remove any plugins whose files no longer exist")) .setAction ([this] { removeMissingPlugins(); })); menu.addSeparator(); auto selectedRow = table.getSelectedRow(); - menu.addItem (PopupMenu::Item (TRANS ("Show folder containing selected plug-in")) + menu.addItem (PopupMenu::Item (TRANS("Show folder containing selected plugin")) .setEnabled (canShowFolderForPlugin (list, selectedRow)) .setAction ([this, selectedRow] { showFolderForPlugin (list, selectedRow); })); @@ -325,7 +328,7 @@ PopupMenu PluginListComponent::createOptionsMenu() for (auto format : formatManager.getFormats()) if (format->canScanForPlugins()) - menu.addItem (PopupMenu::Item ("Scan for new or updated " + format->getName() + " plug-ins") + menu.addItem (PopupMenu::Item ("Scan for new or updated " + format->getName() + " plugins") .setAction ([this, format] { scanFor (*format); })); return menu; @@ -337,10 +340,10 @@ PopupMenu PluginListComponent::createMenuForRow (int rowNumber) if (rowNumber >= 0 && rowNumber < tableModel->getNumRows()) { - menu.addItem (PopupMenu::Item (TRANS ("Remove plug-in from list")) + menu.addItem (PopupMenu::Item (TRANS("Remove plugin from list")) .setAction ([this, rowNumber] { removePluginItem (rowNumber); })); - menu.addItem (PopupMenu::Item (TRANS ("Show folder containing plug-in")) + menu.addItem (PopupMenu::Item (TRANS("Show folder containing plugin")) .setEnabled (canShowFolderForPlugin (list, rowNumber)) .setAction ([this, rowNumber] { showFolderForPlugin (list, rowNumber); })); } @@ -637,8 +640,8 @@ void PluginListComponent::scanFor (AudioPluginFormat& format) void PluginListComponent::scanFor (AudioPluginFormat& format, const StringArray& filesOrIdentifiersToScan) { currentScanner.reset (new Scanner (*this, format, filesOrIdentifiersToScan, propertiesToUse, allowAsync, numThreads, - dialogTitle.isNotEmpty() ? dialogTitle : TRANS ("Scanning for plug-ins..."), - dialogText.isNotEmpty() ? dialogText : TRANS ("Searching for all possible plug-in files..."))); + dialogTitle.isNotEmpty() ? dialogTitle : TRANS("Scanning for plugins..."), + dialogText.isNotEmpty() ? dialogText : TRANS("Searching for all possible plugin files..."))); } bool PluginListComponent::isScanning() const noexcept diff --git a/modules/juce_graphics/geometry/juce_AffineTransform.cpp b/modules/juce_graphics/geometry/juce_AffineTransform.cpp index c4d475e801df..ef0fbb483509 100644 --- a/modules/juce_graphics/geometry/juce_AffineTransform.cpp +++ b/modules/juce_graphics/geometry/juce_AffineTransform.cpp @@ -179,6 +179,12 @@ AffineTransform AffineTransform::verticalFlip (float height) noexcept 0.0f, -1.0f, height }; } +AffineTransform AffineTransform::horizontalFlip (float width) noexcept +{ + return { -1.0f, 0.0f, width, + 0.0f, 1.0f, 0.0f }; +} + AffineTransform AffineTransform::inverted() const noexcept { double determinant = getDeterminant(); diff --git a/modules/juce_graphics/geometry/juce_AffineTransform.h b/modules/juce_graphics/geometry/juce_AffineTransform.h index bd61c2a90e9d..7a75af827ec0 100644 --- a/modules/juce_graphics/geometry/juce_AffineTransform.h +++ b/modules/juce_graphics/geometry/juce_AffineTransform.h @@ -206,6 +206,10 @@ class JUCE_API AffineTransform final */ static AffineTransform verticalFlip (float height) noexcept; + /** Returns a transform that will flip coordinates horizontally within a window of the given width. + */ + static AffineTransform horizontalFlip (float width) noexcept; + /** Returns a matrix which is the inverse operation of this one. Some matrices don't have an inverse - in this case, the method will just return diff --git a/modules/juce_gui_basics/components/juce_Component.h b/modules/juce_gui_basics/components/juce_Component.h index d2fcb8ec5595..d20b40c3237f 100644 --- a/modules/juce_gui_basics/components/juce_Component.h +++ b/modules/juce_gui_basics/components/juce_Component.h @@ -2334,6 +2334,9 @@ class JUCE_API Component : public MouseListener /** If the component is valid, this deletes it and sets this pointer to null. */ void deleteAndZero() { delete getComponent(); } + bool operator== (const SafePointer& component) const noexcept { return weakRef == component; } + bool operator!= (const SafePointer& component) const noexcept { return weakRef != component; } + bool operator== (ComponentType* component) const noexcept { return weakRef == component; } bool operator!= (ComponentType* component) const noexcept { return weakRef != component; } diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index 52b65d4174ff..810560983e0a 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -2742,7 +2742,7 @@ class HWNDComponentPeer final : public ComponentPeer, static void* toFrontCallback1 (void* h) { - BringWindowToTop ((HWND) h); + SetForegroundWindow ((HWND) h); return nullptr; }