Browse Source

Update juce

tags/2018-04-16
falkTX 9 years ago
parent
commit
c9ab28b9f8
29 changed files with 231 additions and 404 deletions
  1. +34
    -14
      libs/juce/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp
  2. +1
    -1
      libs/juce/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp
  3. +1
    -1
      libs/juce/source/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp
  4. +9
    -5
      libs/juce/source/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.mm
  5. +18
    -12
      libs/juce/source/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp
  6. +0
    -273
      libs/juce/source/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.mm
  7. +1
    -1
      libs/juce/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h
  8. +2
    -2
      libs/juce/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm
  9. +1
    -1
      libs/juce/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp
  10. +1
    -1
      libs/juce/source/modules/juce_audio_utils/gui/juce_AudioThumbnail.h
  11. +1
    -1
      libs/juce/source/modules/juce_audio_utils/gui/juce_AudioThumbnailBase.h
  12. +28
    -25
      libs/juce/source/modules/juce_audio_utils/gui/juce_MidiKeyboardComponent.cpp
  13. +5
    -2
      libs/juce/source/modules/juce_audio_utils/gui/juce_MidiKeyboardComponent.h
  14. +2
    -2
      libs/juce/source/modules/juce_core/containers/juce_Variant.cpp
  15. +8
    -11
      libs/juce/source/modules/juce_core/maths/juce_Expression.cpp
  16. +1
    -1
      libs/juce/source/modules/juce_core/memory/juce_Atomic.h
  17. +1
    -1
      libs/juce/source/modules/juce_core/native/juce_win32_SystemStats.cpp
  18. +21
    -5
      libs/juce/source/modules/juce_core/network/juce_MACAddress.cpp
  19. +13
    -4
      libs/juce/source/modules/juce_core/network/juce_MACAddress.h
  20. +10
    -5
      libs/juce/source/modules/juce_core/streams/juce_MemoryInputStream.cpp
  21. +4
    -0
      libs/juce/source/modules/juce_data_structures/values/juce_Value.h
  22. +19
    -9
      libs/juce/source/modules/juce_graphics/native/juce_mac_Fonts.mm
  23. +2
    -2
      libs/juce/source/modules/juce_gui_basics/layout/juce_ResizableCornerComponent.h
  24. +1
    -1
      libs/juce/source/modules/juce_gui_basics/menus/juce_MenuBarModel.h
  25. +40
    -17
      libs/juce/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp
  26. +1
    -1
      libs/juce/source/modules/juce_gui_basics/widgets/juce_Label.cpp
  27. +3
    -3
      libs/juce/source/modules/juce_gui_basics/windows/juce_AlertWindow.cpp
  28. +2
    -2
      libs/juce/source/modules/juce_gui_extra/code_editor/juce_CPlusPlusCodeTokeniserFunctions.h
  29. +1
    -1
      libs/juce/source/modules/juce_tracktion_marketplace/marketplace/juce_OnlineUnlockForm.cpp

+ 34
- 14
libs/juce/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp View File

@@ -397,11 +397,9 @@ public:
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, nullptr, &size, &sr)))
sampleRate = sr;
const double currentRate = getNominalSampleRate();
if (currentRate > 0)
sampleRate = currentRate;
UInt32 framesPerBuf = (UInt32) bufferSize;
size = sizeof (framesPerBuf);
@@ -525,6 +523,30 @@ public:
}
}
double getNominalSampleRate() const
{
AudioObjectPropertyAddress pa;
pa.mSelector = kAudioDevicePropertyNominalSampleRate;
pa.mScope = kAudioObjectPropertyScopeGlobal;
pa.mElement = kAudioObjectPropertyElementMaster;
Float64 sr = 0;
UInt32 size = (UInt32) sizeof (sr);
return OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &sr)) ? (double) sr : 0.0;
}
bool setNominalSampleRate (double newSampleRate) const
{
if (std::abs (getNominalSampleRate() - newSampleRate) < 1.0)
return true;
AudioObjectPropertyAddress pa;
pa.mSelector = kAudioDevicePropertyNominalSampleRate;
pa.mScope = kAudioObjectPropertyScopeGlobal;
pa.mElement = kAudioObjectPropertyElementMaster;
Float64 sr = newSampleRate;
return OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (sr), &sr));
}
//==============================================================================
String reopen (const BigInteger& inputChannels,
const BigInteger& outputChannels,
@@ -549,25 +571,23 @@ public:
numInputChans = activeInputChans.countNumberOfSetBits();
numOutputChans = activeOutputChans.countNumberOfSetBits();
// set sample rate
AudioObjectPropertyAddress pa;
pa.mSelector = kAudioDevicePropertyNominalSampleRate;
pa.mScope = kAudioObjectPropertyScopeGlobal;
pa.mElement = kAudioObjectPropertyElementMaster;
Float64 sr = newSampleRate;
if (! OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (sr), &sr)))
if (! setNominalSampleRate (newSampleRate))
{
updateDetailsFromDevice();
error = "Couldn't change sample rate";
}
else
{
// change buffer size
UInt32 framesPerBuf = (UInt32) bufferSizeSamples;
AudioObjectPropertyAddress pa;
pa.mSelector = kAudioDevicePropertyBufferFrameSize;
pa.mScope = kAudioObjectPropertyScopeGlobal;
pa.mElement = kAudioObjectPropertyElementMaster;
UInt32 framesPerBuf = (UInt32) bufferSizeSamples;
if (! OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (framesPerBuf), &framesPerBuf)))
{
updateDetailsFromDevice();
error = "Couldn't change buffer size";
}
else


+ 1
- 1
libs/juce/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp View File

@@ -643,7 +643,7 @@ public:
if (metadataValues.size() > 0)
{
// The meta data should have been santised for the AIFF format.
// The meta data should have been sanitised for the AIFF format.
// If it was originally sourced from a WAV file the MetaDataSource
// key should be removed (or set to "AIFF") once this has been done
jassert (metadataValues.getValue ("MetaDataSource", "None") != "WAV");


+ 1
- 1
libs/juce/source/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp View File

@@ -1024,7 +1024,7 @@ public:
if (metadataValues.size() > 0)
{
// The meta data should have been santised for the WAV format.
// The meta data should have been sanitised for the WAV format.
// If it was originally sourced from an AIFF file the MetaDataSource
// key should be removed (or set to "WAV") once this has been done
jassert (metadataValues.getValue ("MetaDataSource", "None") != "AIFF");


+ 9
- 5
libs/juce/source/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.mm View File

@@ -37,6 +37,9 @@
#include "../utility/juce_FakeMouseMoveGenerator.h"
#include "../utility/juce_CarbonVisibility.h"
#undef Component
#undef Point
//==============================================================================
namespace juce
{
@@ -103,6 +106,7 @@ void* attachComponentToWindowRefVST (Component* comp, void* parentWindowOrView,
WindowAttributes attributes;
GetWindowAttributes ((WindowRef) parentWindowOrView, &attributes);
if ((attributes & kWindowCompositingAttribute) != 0)
{
HIViewRef root = HIViewGetRoot ((WindowRef) parentWindowOrView);
@@ -160,7 +164,7 @@ void* attachComponentToWindowRefVST (Component* comp, void* parentWindowOrView,
}
#endif
(void) isNSView;
ignoreUnused (isNSView);
NSView* parentView = [(NSView*) parentWindowOrView retain];
#if JucePlugin_EditorRequiresKeyboardFocus
@@ -233,7 +237,7 @@ void detachComponentFromWindowRefVST (Component* comp, void* window, bool isNSVi
}
#endif
(void) isNSView;
ignoreUnused (isNSView);
comp->removeFromDesktop();
[(id) window release];
}
@@ -261,7 +265,7 @@ void setNativeHostWindowSizeVST (void* window, Component* component, int newWidt
}
#endif
(void) isNSView;
ignoreUnused (isNSView);
if (NSView* hostView = (NSView*) window)
{
@@ -280,7 +284,7 @@ void setNativeHostWindowSizeVST (void* window, Component* component, int newWidt
void checkWindowVisibilityVST (void* window, Component* comp, bool isNSView);
void checkWindowVisibilityVST (void* window, Component* comp, bool isNSView)
{
(void) window; (void) comp; (void) isNSView;
ignoreUnused (window, comp, isNSView);
#if ! JUCE_64BIT
if (! isNSView)
@@ -301,7 +305,7 @@ bool forwardCurrentKeyEventToHostVST (Component* comp, bool isNSView)
}
#endif
(void) comp; (void) isNSView;
ignoreUnused (comp, isNSView);
return false;
}


+ 18
- 12
libs/juce/source/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp View File

@@ -62,10 +62,15 @@ using namespace Steinberg;
//==============================================================================
#if JUCE_MAC
extern void initialiseMacVST3();
extern void* attachComponentToWindowRefVST3 (Component*, void* parent, bool isNSView);
extern void detachComponentFromWindowRefVST3 (Component*, void* window, bool isNSView);
extern void setNativeHostWindowSizeVST3 (void* window, Component*, int newWidth, int newHeight, bool isNSView);
extern void initialiseMacVST();
#if ! JUCE_64BIT
extern void updateEditorCompBoundsVST (Component*);
#endif
extern void* attachComponentToWindowRefVST (Component*, void* parentWindowOrView, bool isNSView);
extern void detachComponentFromWindowRefVST (Component*, void* nsWindow, bool isNSView);
extern void setNativeHostWindowSizeVST (void* window, Component*, int newWidth, int newHeight, bool isNSView);
#endif
//==============================================================================
@@ -250,7 +255,7 @@ public:
toString128 (info.title, "Bypass");
toString128 (info.shortTitle, "Bypass");
toString128 (info.units, "");
info.stepCount = 2;
info.stepCount = 1;
info.defaultNormalizedValue = 0.0f;
info.unitId = Vst::kRootUnitId;
info.flags = Vst::ParameterInfo::kIsBypass;
@@ -561,7 +566,7 @@ private:
component->setVisible (true);
#else
isNSView = (strcmp (type, kPlatformTypeNSView) == 0);
macHostWindow = juce::attachComponentToWindowRefVST3 (component, parent, isNSView);
macHostWindow = juce::attachComponentToWindowRefVST (component, parent, isNSView);
#endif
component->resizeHostWindow();
@@ -580,7 +585,7 @@ private:
#else
if (macHostWindow != nullptr)
{
juce::detachComponentFromWindowRefVST3 (component, macHostWindow, isNSView);
juce::detachComponentFromWindowRefVST (component, macHostWindow, isNSView);
macHostWindow = nullptr;
}
#endif
@@ -692,7 +697,7 @@ private:
setSize (w, h);
#else
if (owner.macHostWindow != nullptr && ! getHostType().isWavelab())
juce::setNativeHostWindowSizeVST3 (owner.macHostWindow, this, w, h, owner.isNSView);
juce::setNativeHostWindowSizeVST (owner.macHostWindow, this, w, h, owner.isNSView);
#endif
if (owner.plugFrame != nullptr)
@@ -1007,13 +1012,13 @@ public:
void setStateInformation (const void* data, int sizeAsInt)
{
size_t size = static_cast<size_t> (sizeAsInt);
int64 size = sizeAsInt;
// Check if this data was written with a newer JUCE version
// and if it has the JUCE private data magic code at the end
const size_t jucePrivDataIdentifierSize = std::strlen (kJucePrivateDataIdentifier);
if (size >= (jucePrivDataIdentifierSize + sizeof (int64)))
if (size >= jucePrivDataIdentifierSize + sizeof (int64))
{
const char* buffer = static_cast<const char*> (data);
@@ -1039,7 +1044,8 @@ public:
}
}
pluginInstance->setStateInformation (data, static_cast<int> (size));
if (size >= 0)
pluginInstance->setStateInformation (data, static_cast<int> (size));
}
//==============================================================================
@@ -1778,7 +1784,7 @@ DEF_CLASS_IID (JuceAudioProcessor)
bool initModule()
{
#if JUCE_MAC
initialiseMacVST3();
initialiseMacVST();
#endif
return true;


+ 0
- 273
libs/juce/source/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.mm View File

@@ -1,273 +0,0 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2015 - ROLI Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
// Your project must contain an AppConfig.h file with your project-specific settings in it,
// and your header search path must make it accessible to the module's files.
#include "AppConfig.h"
#include "../utility/juce_CheckSettingMacros.h"
#if JucePlugin_Build_VST3
#define JUCE_MAC_WINDOW_VISIBITY_BODGE 1
#include "../utility/juce_IncludeSystemHeaders.h"
#include "../utility/juce_IncludeModuleHeaders.h"
#include "../utility/juce_FakeMouseMoveGenerator.h"
#include "../utility/juce_CarbonVisibility.h"
#undef Component
#undef Point
//==============================================================================
namespace juce
{
void initialiseMacVST3()
{
#if ! JUCE_64BIT
NSApplicationLoad();
#endif
}
#if ! JUCE_64BIT
static void updateComponentPosVST3 (Component* const comp)
{
DBG ("updateComponentPos()");
HIViewRef dummyView = (HIViewRef) (void*) (pointer_sized_int)
comp->getProperties() ["dummyViewRef"].toString().getHexValue64();
HIRect r;
HIViewGetFrame (dummyView, &r);
HIViewRef root;
HIViewFindByID (HIViewGetRoot (HIViewGetWindow (dummyView)), kHIViewWindowContentID, &root);
HIViewConvertRect (&r, HIViewGetSuperview (dummyView), root);
Rect windowPos;
GetWindowBounds (HIViewGetWindow (dummyView), kWindowContentRgn, &windowPos);
comp->setTopLeftPosition ((int) (windowPos.left + r.origin.x),
(int) (windowPos.top + r.origin.y));
}
static pascal OSStatus viewBoundsChangedEventVST3 (EventHandlerCallRef, EventRef, void* user)
{
updateComponentPosVST3 ((Component*) user);
return noErr;
}
#endif
void* attachComponentToWindowRefVST3 (Component* comp, void* windowRef, bool isHIView)
{
DBG ("attachComponentToWindowRef()");
JUCE_AUTORELEASEPOOL
{
#if JUCE_64BIT
(void) isHIView;
NSView* parentView = (NSView*) windowRef;
#if JucePlugin_EditorRequiresKeyboardFocus
comp->addToDesktop (0, parentView);
#else
comp->addToDesktop (ComponentPeer::windowIgnoresKeyPresses, parentView);
#endif
// (this workaround is because Wavelab provides a zero-size parent view..)
if ([parentView frame].size.height == 0)
[((NSView*) comp->getWindowHandle()) setFrameOrigin: NSZeroPoint];
comp->setVisible (true);
comp->toFront (false);
[[parentView window] setAcceptsMouseMovedEvents: YES];
return parentView;
#else
//treat NSView like 64bit
if (! isHIView)
{
NSView* parentView = (NSView*) windowRef;
#if JucePlugin_EditorRequiresKeyboardFocus
comp->addToDesktop (0, parentView);
#else
comp->addToDesktop (ComponentPeer::windowIgnoresKeyPresses, parentView);
#endif
// (this workaround is because Wavelab provides a zero-size parent view..)
if ([parentView frame].size.height == 0)
[((NSView*) comp->getWindowHandle()) setFrameOrigin: NSZeroPoint];
comp->setVisible (true);
comp->toFront (false);
[[parentView window] setAcceptsMouseMovedEvents: YES];
return parentView;
}
NSWindow* hostWindow = [[NSWindow alloc] initWithWindowRef: windowRef];
[hostWindow retain];
[hostWindow setCanHide: YES];
[hostWindow setReleasedWhenClosed: YES];
HIViewRef parentView = nullptr;
WindowAttributes attributes;
GetWindowAttributes ((WindowRef) windowRef, &attributes);
if ((attributes & kWindowCompositingAttribute) != 0)
{
HIViewRef root = HIViewGetRoot ((WindowRef) windowRef);
HIViewFindByID (root, kHIViewWindowContentID, &parentView);
if (parentView == nullptr)
parentView = root;
}
else
{
GetRootControl ((WindowRef) windowRef, (ControlRef*) &parentView);
if (parentView == nullptr)
CreateRootControl ((WindowRef) windowRef, (ControlRef*) &parentView);
}
// It seems that the only way to successfully position our overlaid window is by putting a dummy
// HIView into the host's carbon window, and then catching events to see when it gets repositioned
HIViewRef dummyView = 0;
HIImageViewCreate (0, &dummyView);
HIRect r = { {0, 0}, { (float) comp->getWidth(), (float) comp->getHeight()} };
HIViewSetFrame (dummyView, &r);
HIViewAddSubview (parentView, dummyView);
comp->getProperties().set ("dummyViewRef", String::toHexString ((pointer_sized_int) (void*) dummyView));
EventHandlerRef ref;
const EventTypeSpec kControlBoundsChangedEvent = { kEventClassControl, kEventControlBoundsChanged };
InstallEventHandler (GetControlEventTarget (dummyView), NewEventHandlerUPP (viewBoundsChangedEventVST3), 1, &kControlBoundsChangedEvent, (void*) comp, &ref);
comp->getProperties().set ("boundsEventRef", String::toHexString ((pointer_sized_int) (void*) ref));
updateComponentPosVST3 (comp);
#if ! JucePlugin_EditorRequiresKeyboardFocus
comp->addToDesktop (ComponentPeer::windowIsTemporary | ComponentPeer::windowIgnoresKeyPresses);
#else
comp->addToDesktop (ComponentPeer::windowIsTemporary);
#endif
comp->setVisible (true);
comp->toFront (false);
NSView* pluginView = (NSView*) comp->getWindowHandle();
NSWindow* pluginWindow = [pluginView window];
[pluginWindow setExcludedFromWindowsMenu: YES];
[pluginWindow setCanHide: YES];
[hostWindow addChildWindow: pluginWindow
ordered: NSWindowAbove];
[hostWindow orderFront: nil];
[pluginWindow orderFront: nil];
attachWindowHidingHooks (comp, (WindowRef) windowRef, hostWindow);
return hostWindow;
#endif
}
}
void detachComponentFromWindowRefVST3 (Component* comp, void* nsWindow, bool isHIView)
{
JUCE_AUTORELEASEPOOL
{
#if ! JUCE_64BIT
if (isHIView)
{
JUCE_AUTORELEASEPOOL
{
EventHandlerRef ref = (EventHandlerRef) (void*) (pointer_sized_int)
comp->getProperties() ["boundsEventRef"].toString().getHexValue64();
RemoveEventHandler (ref);
removeWindowHidingHooks (comp);
HIViewRef dummyView = (HIViewRef) (void*) (pointer_sized_int)
comp->getProperties() ["dummyViewRef"].toString().getHexValue64();
if (HIViewIsValid (dummyView))
CFRelease (dummyView);
NSWindow* hostWindow = (NSWindow*) nsWindow;
NSView* pluginView = (NSView*) comp->getWindowHandle();
NSWindow* pluginWindow = [pluginView window];
[hostWindow removeChildWindow: pluginWindow];
comp->removeFromDesktop();
[hostWindow release];
}
// The event loop needs to be run between closing the window and deleting the plugin,
// presumably to let the cocoa objects get tidied up. Leaving out this line causes crashes
// in Live and Reaper when you delete the plugin with its window open.
// (Doing it this way rather than using a single longer timout means that we can guarantee
// how many messages will be dispatched, which seems to be vital in Reaper)
for (int i = 20; --i >= 0;)
MessageManager::getInstance()->runDispatchLoopUntil (1);
return;
}
#endif
(void) nsWindow; (void) isHIView;
comp->removeFromDesktop();
}
}
void setNativeHostWindowSizeVST3 (void* nsWindow, Component* component, int newWidth, int newHeight, bool isHIView)
{
JUCE_AUTORELEASEPOOL
{
#if ! JUCE_64BIT
if (isHIView)
{
if (HIViewRef dummyView = (HIViewRef) (void*) (pointer_sized_int)
component->getProperties() ["dummyViewRef"].toString().getHexValue64())
{
HIRect frameRect;
HIViewGetFrame (dummyView, &frameRect);
frameRect.size.width = newWidth;
frameRect.size.height = newHeight;
HIViewSetFrame (dummyView, &frameRect);
}
return;
}
#endif
(void) nsWindow; (void) isHIView;
component->setSize (newWidth, newHeight);
}
}
} // (juce namespace)
#endif

+ 1
- 1
libs/juce/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h View File

@@ -42,7 +42,7 @@ public:
bool fileMightContainThisPluginType (const String& fileOrIdentifier) override;
String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override;
bool pluginNeedsRescanning (const PluginDescription&) override;
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive);
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive) override;
bool doesPluginStillExist (const PluginDescription&) override;
FileSearchPath getDefaultLocationsToSearch() override;
bool canScanForPlugins() const override { return true; }


+ 2
- 2
libs/juce/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm View File

@@ -840,9 +840,9 @@ public:
if (audioUnit != nullptr)
{
UInt32 paramListSize = 0;
UInt32 dummy = 0, paramListSize = 0;
AudioUnitGetProperty (audioUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global,
0, 0, &paramListSize);
0, &dummy, &paramListSize);
if (paramListSize > 0)
{


+ 1
- 1
libs/juce/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp View File

@@ -1145,7 +1145,7 @@ public:
}
//==============================================================================
int getNumParameters() { return effect != nullptr ? effect->numParams : 0; }
int getNumParameters() override { return effect != nullptr ? effect->numParams : 0; }
float getParameter (int index) override
{


+ 1
- 1
libs/juce/source/modules/juce_audio_utils/gui/juce_AudioThumbnail.h View File

@@ -78,7 +78,7 @@ public:
setSource (new FileInputSource (file))
@endcode
You can pass a zero in here to clear the thumbnail.
You can pass a nullptr in here to clear the thumbnail.
The source that is passed in will be deleted by this object when it is no longer needed.
@returns true if the source could be opened as a valid audio file, false if this failed for
some reason.


+ 1
- 1
libs/juce/source/modules/juce_audio_utils/gui/juce_AudioThumbnailBase.h View File

@@ -56,7 +56,7 @@ public:
setSource (new FileInputSource (file))
@endcode
You can pass a zero in here to clear the thumbnail.
You can pass a nullptr in here to clear the thumbnail.
The source that is passed in will be deleted by this object when it is no longer needed.
@returns true if the source could be opened as a valid audio file, false if this failed for
some reason.


+ 28
- 25
libs/juce/source/modules/juce_audio_utils/gui/juce_MidiKeyboardComponent.cpp View File

@@ -230,21 +230,35 @@ void MidiKeyboardComponent::getKeyPos (int midiNoteNumber, int& x, int& w) const
x -= xOffset + rx;
}
Rectangle<int> MidiKeyboardComponent::getWhiteNotePos (int noteNum) const
Rectangle<int> MidiKeyboardComponent::getRectangleForKey (const int note) const
{
jassert (note >= rangeStart && note <= rangeEnd);
int x, w;
getKeyPos (noteNum, x, w);
Rectangle<int> pos;
getKeyPos (note, x, w);
switch (orientation)
if (MidiMessage::isMidiNoteBlack (note))
{
case horizontalKeyboard: pos.setBounds (x, 0, w, getHeight()); break;
case verticalKeyboardFacingLeft: pos.setBounds (0, x, getWidth(), w); break;
case verticalKeyboardFacingRight: pos.setBounds (0, getHeight() - x - w, getWidth(), w); break;
default: break;
switch (orientation)
{
case horizontalKeyboard: return Rectangle<int> (x, 0, w, blackNoteLength);
case verticalKeyboardFacingLeft: return Rectangle<int> (getWidth() - blackNoteLength, x, blackNoteLength, w);
case verticalKeyboardFacingRight: return Rectangle<int> (0, getHeight() - x - w, blackNoteLength, w);
default: jassertfalse; break;
}
}
else
{
switch (orientation)
{
case horizontalKeyboard: return Rectangle<int> (x, 0, w, getHeight());
case verticalKeyboardFacingLeft: return Rectangle<int> (0, x, getWidth(), w);
case verticalKeyboardFacingRight: return Rectangle<int> (0, getHeight() - x - w, getWidth(), w);
default: jassertfalse; break;
}
}
return pos;
return Rectangle<int>();
}
int MidiKeyboardComponent::getKeyStartPosition (const int midiNoteNumber) const
@@ -339,7 +353,7 @@ int MidiKeyboardComponent::remappedXYToNote (Point<int> pos, float& mousePositio
void MidiKeyboardComponent::repaintNote (const int noteNum)
{
if (noteNum >= rangeStart && noteNum <= rangeEnd)
repaint (getWhiteNotePos (noteNum));
repaint (getRectangleForKey (noteNum));
}
void MidiKeyboardComponent::paint (Graphics& g)
@@ -349,9 +363,7 @@ void MidiKeyboardComponent::paint (Graphics& g)
const Colour lineColour (findColour (keySeparatorLineColourId));
const Colour textColour (findColour (textLabelColourId));
int octave;
for (octave = 0; octave < 128; octave += 12)
for (int octave = 0; octave < 128; octave += 12)
{
for (int white = 0; white < 7; ++white)
{
@@ -359,7 +371,7 @@ void MidiKeyboardComponent::paint (Graphics& g)
if (noteNum >= rangeStart && noteNum <= rangeEnd)
{
const Rectangle<int> pos (getWhiteNotePos (noteNum));
Rectangle<int> pos = getRectangleForKey (noteNum);
drawWhiteNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
state.isNoteOnForChannels (midiInChannelMask, noteNum),
@@ -416,7 +428,7 @@ void MidiKeyboardComponent::paint (Graphics& g)
const Colour blackNoteColour (findColour (blackNoteColourId));
for (octave = 0; octave < 128; octave += 12)
for (int octave = 0; octave < 128; octave += 12)
{
for (int black = 0; black < 5; ++black)
{
@@ -424,16 +436,7 @@ void MidiKeyboardComponent::paint (Graphics& g)
if (noteNum >= rangeStart && noteNum <= rangeEnd)
{
getKeyPos (noteNum, x, w);
Rectangle<int> pos;
switch (orientation)
{
case horizontalKeyboard: pos.setBounds (x, 0, w, blackNoteLength); break;
case verticalKeyboardFacingLeft: pos.setBounds (width - blackNoteLength, x, blackNoteLength, w); break;
case verticalKeyboardFacingRight: pos.setBounds (0, height - x - w, blackNoteLength, w); break;
default: break;
}
Rectangle<int> pos = getRectangleForKey (noteNum);
drawBlackNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
state.isNoteOnForChannels (midiInChannelMask, noteNum),


+ 5
- 2
libs/juce/source/modules/juce_audio_utils/gui/juce_MidiKeyboardComponent.h View File

@@ -366,6 +366,10 @@ protected:
virtual void getKeyPosition (int midiNoteNumber, float keyWidth,
int& x, int& w) const;
/** Returns the rectangle for a given key if within the displayable range */
Rectangle<int> getRectangleForKey (int midiNoteNumber) const;
private:
//==============================================================================
friend class MidiKeyboardUpDownButton;
@@ -400,9 +404,8 @@ private:
void resetAnyKeysInUse();
void updateNoteUnderMouse (Point<int>, bool isDown, int fingerNum);
void updateNoteUnderMouse (const MouseEvent&, bool isDown);
void repaintNote (const int midiNoteNumber);
void repaintNote (int midiNoteNumber);
void setLowestVisibleKeyFloat (float noteNumber);
Rectangle<int> getWhiteNotePos (int noteNumber) const;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardComponent)
};


+ 2
- 2
libs/juce/source/modules/juce_core/containers/juce_Variant.cpp View File

@@ -49,7 +49,7 @@ public:
virtual int toInt (const ValueUnion&) const noexcept { return 0; }
virtual int64 toInt64 (const ValueUnion&) const noexcept { return 0; }
virtual double toDouble (const ValueUnion&) const noexcept { return 0; }
virtual String toString (const ValueUnion&) const { return String::empty; }
virtual String toString (const ValueUnion&) const { return String(); }
virtual bool toBool (const ValueUnion&) const noexcept { return false; }
virtual ReferenceCountedObject* toObject (const ValueUnion&) const noexcept { return nullptr; }
virtual Array<var>* toArray (const ValueUnion&) const noexcept { return nullptr; }
@@ -274,7 +274,7 @@ public:
}
String toString (const ValueUnion& data) const override { return "Object 0x" + String::toHexString ((int) (pointer_sized_int) data.objectValue); }
bool toBool (const ValueUnion& data) const noexcept override { return data.objectValue != 0; }
bool toBool (const ValueUnion& data) const noexcept override { return data.objectValue != nullptr; }
ReferenceCountedObject* toObject (const ValueUnion& data) const noexcept override { return data.objectValue; }
bool isObject() const noexcept override { return true; }


+ 8
- 11
libs/juce/source/modules/juce_core/maths/juce_Expression.cpp View File

@@ -992,7 +992,8 @@ double Expression::evaluate() const
double Expression::evaluate (const Expression::Scope& scope) const
{
return term->resolve (scope, 0)->toDouble();
String err;
return evaluate (scope, err);
}
double Expression::evaluate (const Scope& scope, String& evaluationError) const
@@ -1038,20 +1039,16 @@ Expression Expression::adjustedToGiveNewResult (const double targetValue, const
jassert (termToAdjust != nullptr);
const Term* const parent = Helpers::findDestinationFor (newTerm, termToAdjust);
if (parent == nullptr)
if (const Term* parent = Helpers::findDestinationFor (newTerm, termToAdjust))
{
termToAdjust->value = targetValue;
if (const Helpers::TermPtr reverseTerm = parent->createTermToEvaluateInput (scope, termToAdjust, targetValue, newTerm))
termToAdjust->value = Expression (reverseTerm).evaluate (scope);
else
return Expression (targetValue);
}
else
{
const Helpers::TermPtr reverseTerm (parent->createTermToEvaluateInput (scope, termToAdjust, targetValue, newTerm));
if (reverseTerm == nullptr)
return Expression (targetValue);
termToAdjust->value = reverseTerm->resolve (scope, 0)->toDouble();
termToAdjust->value = targetValue;
}
return Expression (newTerm.release());


+ 1
- 1
libs/juce/source/modules/juce_core/memory/juce_Atomic.h View File

@@ -207,7 +207,7 @@ private:
#endif
//==============================================================================
#elif (JUCE_GCC || JUCE_CLANG) && ! JUCE_WINDOWS
#elif (JUCE_GCC || JUCE_CLANG) && ! JUCE_MSVC
#define JUCE_ATOMICS_GCC 1 // GCC with intrinsics
#if JUCE_IOS || JUCE_ANDROID // (64-bit ops will compile but not link on these mobile OSes)


+ 1
- 1
libs/juce/source/modules/juce_core/native/juce_win32_SystemStats.cpp View File

@@ -429,7 +429,7 @@ String SystemStats::getFullUserName()
String SystemStats::getComputerName()
{
TCHAR text [MAX_COMPUTERNAME_LENGTH + 1] = { 0 };
TCHAR text[128] = { 0 };
DWORD len = (DWORD) numElementsInArray (text) - 1;
GetComputerName (text, &len);
return String (text, len);


+ 21
- 5
libs/juce/source/modules/juce_core/network/juce_MACAddress.cpp View File

@@ -26,28 +26,44 @@
==============================================================================
*/
MACAddress::MACAddress()
MACAddress::MACAddress() noexcept
{
zeromem (address, sizeof (address));
}
MACAddress::MACAddress (const MACAddress& other)
MACAddress::MACAddress (const MACAddress& other) noexcept
{
memcpy (address, other.address, sizeof (address));
}
MACAddress& MACAddress::operator= (const MACAddress& other)
MACAddress& MACAddress::operator= (const MACAddress& other) noexcept
{
memcpy (address, other.address, sizeof (address));
return *this;
}
MACAddress::MACAddress (const uint8 bytes[6])
MACAddress::MACAddress (const uint8 bytes[6]) noexcept
{
memcpy (address, bytes, sizeof (address));
}
MACAddress::MACAddress (StringRef addressString)
{
MemoryBlock hex;
hex.loadFromHexString (addressString);
if (hex.getSize() == sizeof (address))
memcpy (address, hex.getData(), sizeof (address));
else
zeromem (address, sizeof (address));
}
String MACAddress::toString() const
{
return toString ("-");
}
String MACAddress::toString (StringRef separator) const
{
String s;
@@ -56,7 +72,7 @@ String MACAddress::toString() const
s << String::toHexString ((int) address[i]).paddedLeft ('0', 2);
if (i < sizeof (address) - 1)
s << '-';
s << separator;
}
return s;


+ 13
- 4
libs/juce/source/modules/juce_core/network/juce_MACAddress.h View File

@@ -43,16 +43,22 @@ public:
//==============================================================================
/** Creates a null address (00-00-00-00-00-00). */
MACAddress();
MACAddress() noexcept;
/** Creates a copy of another address. */
MACAddress (const MACAddress&);
MACAddress (const MACAddress&) noexcept;
/** Creates a copy of another address. */
MACAddress& operator= (const MACAddress&);
MACAddress& operator= (const MACAddress&) noexcept;
/** Creates an address from 6 bytes. */
explicit MACAddress (const uint8 bytes[6]);
explicit MACAddress (const uint8 bytes[6]) noexcept;
/** Creates an address from a hex string.
If the string isn't a 6-byte hex value, this will just default-initialise
the object.
*/
explicit MACAddress (StringRef address);
/** Returns a pointer to the 6 bytes that make up this address. */
const uint8* getBytes() const noexcept { return address; }
@@ -60,6 +66,9 @@ public:
/** Returns a dash-separated string in the form "11-22-33-44-55-66" */
String toString() const;
/** Returns a hex string of this address, using a custom separator between each byte. */
String toString (StringRef separator) const;
/** Returns the address in the lower 6 bytes of an int64.
This uses a little-endian arrangement, with the first byte of the address being


+ 10
- 5
libs/juce/source/modules/juce_core/streams/juce_MemoryInputStream.cpp View File

@@ -67,13 +67,18 @@ int MemoryInputStream::read (void* const buffer, const int howMany)
{
jassert (buffer != nullptr && howMany >= 0);
const int num = jmin (howMany, (int) (dataSize - position));
if (num <= 0)
if (howMany <= 0 || position >= dataSize)
return 0;
memcpy (buffer, addBytesToPointer (data, position), (size_t) num);
position += (unsigned int) num;
return num;
const size_t num = jmin ((size_t) howMany, dataSize - position);
if (num > 0)
{
memcpy (buffer, addBytesToPointer (data, position), num);
position += num;
}
return (int) num;
}
bool MemoryInputStream::isExhausted()


+ 4
- 0
libs/juce/source/modules/juce_data_structures/values/juce_Value.h View File

@@ -40,6 +40,10 @@
When you create a Value with its default constructor, it acts as a wrapper around a
simple var object, but by creating a Value that refers to a custom subclass of ValueSource,
you can map the Value onto any kind of underlying data.
Important note! The Value class is not thread-safe! If you're accessing one from
multiple threads, then you'll need to use your own synchronisation around any code
that accesses it.
*/
class JUCE_API Value
{


+ 19
- 9
libs/juce/source/modules/juce_graphics/native/juce_mac_Fonts.mm View File

@@ -222,20 +222,21 @@ namespace CoreTextTypeLayout
CFStringRef cfText = text.getText().toCFString();
CFMutableAttributedStringRef attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0);
CFAttributedStringReplaceString (attribString, CFRangeMake(0, 0), cfText);
CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText);
CFRelease (cfText);
const int numCharacterAttributes = text.getNumAttributes();
const CFIndex attribStringLen = CFAttributedStringGetLength (attribString);
for (int i = 0; i < numCharacterAttributes; ++i)
{
const AttributedString::Attribute& attr = *text.getAttribute (i);
const int rangeStart = attr.range.getStart();
if (attr.range.getStart() > CFAttributedStringGetLength (attribString))
if (rangeStart >= attribStringLen)
continue;
Range<int> range (attr.range);
range.setEnd (jmin (range.getEnd(), (int) CFAttributedStringGetLength (attribString)));
CFRange range = CFRangeMake (rangeStart, jmin (attr.range.getEnd(), (int) attribStringLen) - rangeStart);
if (const Font* const f = attr.getFont())
{
@@ -243,8 +244,19 @@ namespace CoreTextTypeLayout
{
ctFontRef = getFontWithPointSize (ctFontRef, f->getHeight() * getHeightToPointsFactor (ctFontRef));
CFAttributedStringSetAttribute (attribString, CFRangeMake (range.getStart(), range.getLength()),
kCTFontAttributeName, ctFontRef);
CFAttributedStringSetAttribute (attribString, range, kCTFontAttributeName, ctFontRef);
float extraKerning = f->getExtraKerningFactor();
if (extraKerning != 0.0f)
{
extraKerning *= f->getHeight();
CFNumberRef numberRef = CFNumberCreate (0, kCFNumberFloatType, &extraKerning);
CFAttributedStringSetAttribute (attribString, range, kCTKernAttributeName, numberRef);
CFRelease (numberRef);
}
CFRelease (ctFontRef);
}
}
@@ -264,9 +276,7 @@ namespace CoreTextTypeLayout
col->getFloatAlpha());
#endif
CFAttributedStringSetAttribute (attribString,
CFRangeMake (range.getStart(), range.getLength()),
kCTForegroundColorAttributeName, colour);
CFAttributedStringSetAttribute (attribString, range, kCTForegroundColorAttributeName, colour);
CGColorRelease (colour);
}
}


+ 2
- 2
libs/juce/source/modules/juce_gui_basics/layout/juce_ResizableCornerComponent.h View File

@@ -51,10 +51,10 @@ public:
Remember that when the target component is resized, it'll need to move and
resize this component to keep it in place, as this won't happen automatically.
If the constrainer parameter is non-zero, then this object will be used to enforce
If a constrainer object is provided, then this object will be used to enforce
limits on the size and position that the component can be stretched to. Make sure
that the constrainer isn't deleted while still in use by this object. If you
pass a zero in here, no limits will be put on the sizes it can be stretched to.
pass a nullptr in here, no limits will be put on the sizes it can be stretched to.
@see ComponentBoundsConstrainer
*/


+ 1
- 1
libs/juce/source/modules/juce_gui_basics/menus/juce_MenuBarModel.h View File

@@ -131,7 +131,7 @@ public:
/** OSX ONLY - Sets the model that is currently being shown as the main
menu bar at the top of the screen on the Mac.
You can pass 0 to stop the current model being displayed. Be careful
You can pass nullptr to stop the current model being displayed. Be careful
not to delete a model while it is being used.
An optional extra menu can be specified, containing items to add to the top of


+ 40
- 17
libs/juce/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp View File

@@ -39,6 +39,14 @@
#define WM_APPCOMMAND 0x0319
#endif
#ifndef MI_WP_SIGNATURE
#define MI_WP_SIGNATURE 0xFF515700
#endif
#ifndef SIGNATURE_MASK
#define SIGNATURE_MASK 0xFFFFFF00
#endif
extern void juce_repeatLastProcessPriority();
extern void juce_checkCurrentlyFocusedTopLevelWindow(); // in juce_TopLevelWindow.cpp
extern bool juce_isRunningInWine();
@@ -1706,8 +1714,22 @@ private:
return 1000 / 60; // Throttling the incoming mouse-events seems to still be needed in XP..
}
bool isTouchEvent() noexcept
{
if (registerTouchWindow == nullptr)
return false;
LPARAM dw = GetMessageExtraInfo();
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx
return (dw & SIGNATURE_MASK) == MI_WP_SIGNATURE;
}
void doMouseMove (Point<float> position)
{
// this will be handled by WM_TOUCH
if (isTouchEvent())
return;
if (! isMouseOver)
{
isMouseOver = true;
@@ -1744,6 +1766,10 @@ private:
void doMouseDown (Point<float> position, const WPARAM wParam)
{
// this will be handled by WM_TOUCH
if (isTouchEvent())
return;
if (GetCapture() != hwnd)
SetCapture (hwnd);
@@ -1760,6 +1786,10 @@ private:
void doMouseUp (Point<float> position, const WPARAM wParam)
{
// this will be handled by WM_TOUCH
if (isTouchEvent())
return;
updateModifiersFromWParam (wParam);
const bool wasDragging = isDragging;
isDragging = false;
@@ -1879,8 +1909,7 @@ private:
const DWORD flags = inputInfo[i].dwFlags;
if ((flags & (TOUCHEVENTF_DOWN | TOUCHEVENTF_MOVE | TOUCHEVENTF_UP)) != 0)
if (! handleTouchInput (inputInfo[i], (flags & TOUCHEVENTF_PRIMARY) != 0,
(flags & TOUCHEVENTF_DOWN) != 0, (flags & TOUCHEVENTF_UP) != 0))
if (! handleTouchInput (inputInfo[i], (flags & TOUCHEVENTF_DOWN) != 0, (flags & TOUCHEVENTF_UP) != 0))
return 0; // abandon method if this window was deleted by the callback
}
}
@@ -1889,7 +1918,7 @@ private:
return 0;
}
bool handleTouchInput (const TOUCHINPUT& touch, const bool isPrimary, const bool isDown, const bool isUp)
bool handleTouchInput (const TOUCHINPUT& touch, const bool isDown, const bool isUp)
{
bool isCancel = false;
const int touchIndex = currentTouches.getIndexOfTouch (touch.dwID);
@@ -1903,13 +1932,10 @@ private:
currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
modsToSend = currentModifiers;
if (! isPrimary)
{
// this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before.
handleMouseEvent (touchIndex, pos.toFloat(), modsToSend.withoutMouseButtons(), time);
if (! isValidPeer (this)) // (in case this component was deleted by the event)
return false;
}
// this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before.
handleMouseEvent (touchIndex, pos.toFloat(), modsToSend.withoutMouseButtons(), time);
if (! isValidPeer (this)) // (in case this component was deleted by the event)
return false;
}
else if (isUp)
{
@@ -1930,14 +1956,11 @@ private:
currentModifiers = currentModifiers.withoutMouseButtons();
}
if (! isPrimary)
{
handleMouseEvent (touchIndex, pos.toFloat(), modsToSend, time);
if (! isValidPeer (this)) // (in case this component was deleted by the event)
return false;
}
handleMouseEvent (touchIndex, pos.toFloat(), modsToSend, time);
if (! isValidPeer (this)) // (in case this component was deleted by the event)
return false;
if ((isUp || isCancel) && ! isPrimary)
if (isUp || isCancel)
{
handleMouseEvent (touchIndex, Point<float> (-10.0f, -10.0f), currentModifiers, time);
if (! isValidPeer (this))


+ 1
- 1
libs/juce/source/modules/juce_gui_basics/widgets/juce_Label.cpp View File

@@ -309,7 +309,7 @@ TextEditor* Label::createEditorComponent()
copyColourIfSpecified (*this, *ed, textWhenEditingColourId, TextEditor::textColourId);
copyColourIfSpecified (*this, *ed, backgroundWhenEditingColourId, TextEditor::backgroundColourId);
copyColourIfSpecified (*this, *ed, outlineWhenEditingColourId, TextEditor::outlineColourId);
copyColourIfSpecified (*this, *ed, outlineWhenEditingColourId, TextEditor::focusedOutlineColourId);
return ed;
}


+ 3
- 3
libs/juce/source/modules/juce_gui_basics/windows/juce_AlertWindow.cpp View File

@@ -89,7 +89,7 @@ void AlertWindow::addButton (const String& name,
const KeyPress& shortcutKey1,
const KeyPress& shortcutKey2)
{
TextButton* const b = new TextButton (name, String::empty);
TextButton* const b = new TextButton (name, String());
buttons.add (b);
b->setWantsKeyboardFocus (true);
@@ -143,9 +143,9 @@ void AlertWindow::addTextEditor (const String& name,
ed->setColour (TextEditor::outlineColourId, findColour (ComboBox::outlineColourId));
ed->setFont (getLookAndFeel().getAlertWindowMessageFont());
addAndMakeVisible (ed);
ed->setText (initialContents);
ed->setCaretPosition (initialContents.length());
addAndMakeVisible (ed);
textboxNames.add (onScreenLabel);
updateLayout (false);
@@ -165,7 +165,7 @@ String AlertWindow::getTextEditorContents (const String& nameOfTextEditor) const
if (TextEditor* const t = getTextEditor (nameOfTextEditor))
return t->getText();
return String::empty;
return String();
}


+ 2
- 2
libs/juce/source/modules/juce_gui_extra/code_editor/juce_CPlusPlusCodeTokeniserFunctions.h View File

@@ -66,14 +66,14 @@ struct CppTokeniserFunctions
static const char* const keywords7Char[] =
{ "nullptr", "alignas", "alignof", "default", "mutable", "private",
"typedef", "virtual", "wchar_t", nullptr };
"typedef", "virtual", "wchar_t", "__cdecl", "_Pragma", "uint8_t", nullptr };
static const char* const keywordsOther[] =
{ "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 };
"@protected", "@property", "@synthesize", "__fastcall", "__stdcall", nullptr };
const char* const* k;


+ 1
- 1
libs/juce/source/modules/juce_tracktion_marketplace/marketplace/juce_OnlineUnlockForm.cpp View File

@@ -234,7 +234,7 @@ void OnlineUnlockForm::showBubbleMessage (const String& text, Component& target)
addChildComponent (bubble);
AttributedString attString;
attString.append (text, Font (15.0f));
attString.append (text, Font (16.0f));
bubble->showAt (getLocalArea (&target, target.getLocalBounds()),
attString, 500, // numMillisecondsBeforeRemoving


Loading…
Cancel
Save