diff --git a/.clang-format b/.clang-format
index e6a8760e2..3db7236c0 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,4 +1,5 @@
---
+Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
@@ -42,7 +43,6 @@ IndentWrappedFunctionNames: true
IndentPPDirectives: BeforeHash
KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: Signature
-Language: Cpp
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PackConstructorInitializers: Never
@@ -70,6 +70,7 @@ SpacesInSquareBrackets: false
Standard: "c++17"
TabWidth: 4
UseTab: Never
+WhitespaceSensitiveMacros: ['EM_ASM', 'EM_JS', 'EM_ASM_INT', 'EM_ASM_DOUBLE', 'EM_ASM_PTR', 'MAIN_THREAD_EM_ASM', 'MAIN_THREAD_EM_ASM_INT', 'MAIN_THREAD_EM_ASM_DOUBLE', 'MAIN_THREAD_EM_ASM_DOUBLE', 'MAIN_THREAD_ASYNC_EM_ASM']
---
Language: ObjC
BasedOnStyle: Chromium
@@ -84,3 +85,10 @@ PointerAlignment: Left
SpacesBeforeTrailingComments: 1
TabWidth: 4
UseTab: Never
+---
+Language: JavaScript
+BasedOnStyle: Chromium
+ColumnLimit: 0
+IndentWidth: 4
+TabWidth: 4
+UseTab: Never
diff --git a/README.md b/README.md
index fc69cb73b..e7ec4904b 100644
--- a/README.md
+++ b/README.md
@@ -3,11 +3,14 @@
UI courtesy from https://www.drywestdesign.com/:

-Example rive animations ([source code](./examples/render/source/main.cpp)):
-[](https://kunitoki.github.io/yup/demos/web_render_1/)
-[](https://kunitoki.github.io/yup/demos/web_render_2/)
-[](https://kunitoki.github.io/yup/demos/web_render_3/)
-
+
+
+
+
+
+
+
+Example Rive animation display ([source code](./examples/render/source/main.cpp)):
[Renderer Youtube Video](https://youtube.com/shorts/3XC4hyDlrVs)
[](https://github.com/kunitoki/yup/actions/workflows/build_macos.yml)
@@ -33,14 +36,28 @@ YUP brings a suite of powerful features, including:
## Supported Rendering Backends
| | **Windows** | **macOS** | **Linux** | **WASM** | **Android**(1) | **iOS**(1) |
|--------------------------|:------------------:|:------------------:|:------------------:|:------------------:|:-------------------------:|:---------------------:|
-| **OpenGL 4.2** | :white_check_mark: | | :white_check_mark: | | :white_check_mark: | |
-| **OpenGL ES2.0** | | | | :white_check_mark: | | |
-| **Metal** | | :white_check_mark: | | | | :white_check_mark: |
+| **OpenGL 4.2** | :white_check_mark: | | :white_check_mark: | | | |
+| **OpenGL ES2.0** | | | | :white_check_mark: | :construction: | |
+| **OpenGL ES3.0** | | | | | :construction: | |
+| **Metal** | | :white_check_mark: | | | | :construction: |
| **Direct3D 11** | :white_check_mark: | | | | | |
-| **Vulkan**(2) | | | | | | |
+| **Vulkan** | :construction: | :construction: | :construction: | | :construction: | |
+| **WebGPU** | | | | | | |
1. Platforms not fully supported by the windowing system
-2. Renderer currently work in progress
+
+## Supported Audio Backends
+| | **Windows** | **macOS** | **Linux** | **WASM** | **Android** | **iOS** |
+|--------------------------|:------------------:|:------------------:|:------------------:|:------------------:|:-------------------------:|:---------------------:|
+| **CoreAudio** | | :white_check_mark: | | | | :white_check_mark: |
+| **ASIO** | :white_check_mark: | | | | | |
+| **DirectSound** | :white_check_mark: | | | | | |
+| **WASAPI** | :white_check_mark: | | | | | |
+| **ALSA** | | | :white_check_mark: | | | |
+| **JACK** | | | :white_check_mark: | | | |
+| **Oboe** | | | | | :white_check_mark: | |
+| **OpenSL** | | | | | :white_check_mark: | |
+| **AudioWorklet** | | | | :white_check_mark: | | |
## Development
> [!IMPORTANT]
diff --git a/cmake/platforms/emscripten/mini-coi.js b/cmake/platforms/emscripten/mini-coi.js
new file mode 100644
index 000000000..9fbfc47cc
--- /dev/null
+++ b/cmake/platforms/emscripten/mini-coi.js
@@ -0,0 +1,28 @@
+/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
+/*! mini-coi - Andrea Giammarchi and contributors, licensed under MIT */
+(({ document: d, navigator: { serviceWorker: s } }) => {
+ if (d) {
+ const { currentScript: c } = d;
+ s.register(c.src, { scope: c.getAttribute('scope') || '.' }).then(r => {
+ r.addEventListener('updatefound', () => location.reload());
+ if (r.active && !s.controller) location.reload();
+ });
+ }
+ else {
+ addEventListener('install', () => skipWaiting());
+ addEventListener('activate', e => e.waitUntil(clients.claim()));
+ addEventListener('fetch', e => {
+ const { request: r } = e;
+ if (r.cache === 'only-if-cached' && r.mode !== 'same-origin') return;
+ e.respondWith(fetch(r).then(r => {
+ const { body, status, statusText } = r;
+ if (!status || status > 399) return r;
+ const h = new Headers(r.headers);
+ h.set('Cross-Origin-Opener-Policy', 'same-origin');
+ h.set('Cross-Origin-Embedder-Policy', 'require-corp');
+ h.set('Cross-Origin-Resource-Policy', 'cross-origin');
+ return new Response(body, { status, statusText, headers: h });
+ }));
+ });
+ }
+ })(self);
\ No newline at end of file
diff --git a/cmake/platforms/emscripten/shell.html b/cmake/platforms/emscripten/shell.html
index 680dec4b1..332f1fdef 100644
--- a/cmake/platforms/emscripten/shell.html
+++ b/cmake/platforms/emscripten/shell.html
@@ -2,9 +2,8 @@
+
-
-
YUP! On Emscripten
YUP! On Emscripten
Downloading...
Resize canvasLock/hide mouse pointer
\ No newline at end of file
diff --git a/docs/demos/web_render_4/mini-coi.js b/docs/demos/web_render_4/mini-coi.js
new file mode 100644
index 000000000..9fbfc47cc
--- /dev/null
+++ b/docs/demos/web_render_4/mini-coi.js
@@ -0,0 +1,28 @@
+/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
+/*! mini-coi - Andrea Giammarchi and contributors, licensed under MIT */
+(({ document: d, navigator: { serviceWorker: s } }) => {
+ if (d) {
+ const { currentScript: c } = d;
+ s.register(c.src, { scope: c.getAttribute('scope') || '.' }).then(r => {
+ r.addEventListener('updatefound', () => location.reload());
+ if (r.active && !s.controller) location.reload();
+ });
+ }
+ else {
+ addEventListener('install', () => skipWaiting());
+ addEventListener('activate', e => e.waitUntil(clients.claim()));
+ addEventListener('fetch', e => {
+ const { request: r } = e;
+ if (r.cache === 'only-if-cached' && r.mode !== 'same-origin') return;
+ e.respondWith(fetch(r).then(r => {
+ const { body, status, statusText } = r;
+ if (!status || status > 399) return r;
+ const h = new Headers(r.headers);
+ h.set('Cross-Origin-Opener-Policy', 'same-origin');
+ h.set('Cross-Origin-Embedder-Policy', 'require-corp');
+ h.set('Cross-Origin-Resource-Policy', 'cross-origin');
+ return new Response(body, { status, statusText, headers: h });
+ }));
+ });
+ }
+ })(self);
\ No newline at end of file
diff --git a/examples/graphics/CMakeLists.txt b/examples/graphics/CMakeLists.txt
index 03a2e012a..5749a1eae 100644
--- a/examples/graphics/CMakeLists.txt
+++ b/examples/graphics/CMakeLists.txt
@@ -23,10 +23,16 @@ cmake_minimum_required(VERSION 3.28)
set (target_name example_graphics)
set (target_version "1.0.0")
+set (link_options "")
+if ("${yup_platform}" MATCHES "^(emscripten)$")
+ set (link_options --preload-file ${CMAKE_CURRENT_LIST_DIR}/data/Roboto-Regular.ttf@data/Roboto-Regular.ttf)
+endif()
+
yup_standalone_app (
TARGET_NAME ${target_name}
TARGET_VERSION ${target_version}
TARGET_IDE_GROUP "Examples"
+ LINK_OPTIONS ${link_options}
MODULES
juce_core
juce_events
diff --git a/examples/graphics/source/main.cpp b/examples/graphics/source/main.cpp
index 501744c78..9e46e01e7 100644
--- a/examples/graphics/source/main.cpp
+++ b/examples/graphics/source/main.cpp
@@ -26,6 +26,51 @@
#include
#include
+#include // For sine wave generation
+
+//==============================================================================
+
+class SineWaveGenerator
+{
+public:
+ SineWaveGenerator()
+ : sampleRate (44100.0)
+ , currentAngle (0.0)
+ , angleDelta (0.0)
+ , amplitude (0.0)
+ {
+ }
+
+ void setFrequency (double frequency, double newSampleRate)
+ {
+ sampleRate = newSampleRate;
+ angleDelta = (juce::MathConstants::twoPi * frequency) / sampleRate;
+
+ amplitude.reset (sampleRate, 0.1);
+ }
+
+ void setAmplitude (float newAmplitude)
+ {
+ amplitude.setTargetValue (newAmplitude);
+ }
+
+ float getNextSample()
+ {
+ auto sample = std::sin (currentAngle) * amplitude.getNextValue();
+
+ currentAngle += angleDelta;
+ if (currentAngle >= juce::MathConstants::twoPi)
+ currentAngle -= juce::MathConstants::twoPi;
+
+ return static_cast (sample);
+ }
+
+private:
+ double sampleRate;
+ double currentAngle;
+ double angleDelta;
+ yup::SmoothedValue amplitude;
+};
//==============================================================================
@@ -47,28 +92,51 @@ class CustomWindow
return;
}
+ setTitle ("main");
+
+ // Load the font
+ yup::File fontFilePath =
#if JUCE_WASM
- yup::File dataPath = yup::File ("/data");
+ yup::File ("/data")
#else
- yup::File dataPath = yup::File (__FILE__).getParentDirectory().getSiblingFile ("data");
+ yup::File (__FILE__).getParentDirectory().getSiblingFile ("data")
#endif
-
- yup::File fontFilePath = dataPath.getChildFile ("Roboto-Regular.ttf");
+ .getChildFile ("Roboto-Regular.ttf");
if (auto result = font.loadFromFile (fontFilePath, factory); result.failed())
yup::Logger::outputDebugString (result.getErrorMessage());
- setTitle ("main");
+ // Initialize the audio device
+ deviceManager.addAudioCallback (this);
+ deviceManager.initialiseWithDefaultDevices (1, 0);
+
+ // Initialize sine wave generators
+ double sampleRate = deviceManager.getAudioDeviceSetup().sampleRate;
+ sineWaveGenerators.resize (totalRows * totalColumns);
+ for (size_t i = 0; i < sineWaveGenerators.size(); ++i)
+ {
+ sineWaveGenerators[i] = std::make_unique();
+ sineWaveGenerators[i]->setFrequency(440.0 * std::pow(1.1, i), sampleRate);
+ }
+
+ // Add sliders and buttons
for (int i = 0; i < totalRows * totalColumns; ++i)
- addAndMakeVisible (sliders.add (std::make_unique (yup::String (i), font)));
+ {
+ auto slider = sliders.add (std::make_unique (yup::String (i), font));
- //button = std::make_unique ("xyz", font);
- //addAndMakeVisible (*button);
+ slider->onValueChanged = [this, i](float value)
+ {
+ sineWaveGenerators[i]->setAmplitude (value);
+ };
- deviceManager.addAudioCallback (this);
- deviceManager.initialiseWithDefaultDevices (1, 0);
+ addAndMakeVisible (slider);
+ }
+
+ //button = std::make_unique ("Randomize", font);
+ //addAndMakeVisible (*button);
+ // Start the timer
startTimerHz (1);
}
@@ -174,6 +242,7 @@ class CustomWindow
int numSamples,
const yup::AudioIODeviceCallbackContext& context) override
{
+ /*
int copiedSamples = 0;
while (copiedSamples < numSamples)
{
@@ -184,6 +253,19 @@ class CustomWindow
readPos %= renderData.size();
}
+ */
+
+ for (int sample = 0; sample < numSamples; ++sample)
+ {
+ float mixedSample = 0.0f;
+
+ for (int i = 0; i < sineWaveGenerators.size(); ++i)
+ mixedSample += sineWaveGenerators[i]->getNextSample();
+
+ for (int channel = 0; channel < numOutputChannels; ++channel)
+ outputChannelData[channel][sample] =
+ mixedSample / static_cast (sineWaveGenerators.size());
+ }
//inputReady.signal();
//renderReady.wait();
@@ -191,7 +273,7 @@ class CustomWindow
void audioDeviceAboutToStart (yup::AudioIODevice* device) override
{
- DBG ("audioDeviceAboutToStart");
+ yup::Logger::outputDebugString ("audioDeviceAboutToStart");
inputData.resize (device->getDefaultBufferSize());
renderData.resize (device->getDefaultBufferSize());
@@ -200,7 +282,7 @@ class CustomWindow
void audioDeviceStopped() override
{
- DBG ("audioDeviceStopped");
+ yup::Logger::outputDebugString ("audioDeviceStopped");
}
private:
@@ -232,6 +314,8 @@ class CustomWindow
std::unique_ptr button;
+ std::vector> sineWaveGenerators;
+
yup::Font font;
yup::StyledText styleText;
};
@@ -244,7 +328,7 @@ struct Application : yup::YUPApplication
const yup::String getApplicationName() override
{
- return "yup graphics!";
+ return "yup! graphics";
}
const yup::String getApplicationVersion() override
diff --git a/examples/render/source/main.cpp b/examples/render/source/main.cpp
index 92b25d61e..2ce8ce695 100644
--- a/examples/render/source/main.cpp
+++ b/examples/render/source/main.cpp
@@ -188,7 +188,7 @@ struct Application : yup::YUPApplication
const yup::String getApplicationName() override
{
- return "yup!";
+ return "yup! render";
}
const yup::String getApplicationVersion() override
@@ -198,6 +198,8 @@ struct Application : yup::YUPApplication
void initialise (const yup::String& commandLineParameters) override
{
+ YUP_PROFILE_START();
+
yup::Logger::outputDebugString ("Starting app " + commandLineParameters);
window = std::make_unique();
@@ -210,6 +212,8 @@ struct Application : yup::YUPApplication
yup::Logger::outputDebugString ("Shutting down");
window.reset();
+
+ YUP_PROFILE_STOP();
}
private:
diff --git a/justfile b/justfile
index 99cfe206f..3f9578b48 100644
--- a/justfile
+++ b/justfile
@@ -31,8 +31,9 @@ ios:
emscripten:
emcc -v
emcmake cmake -G "Ninja Multi-Config" -B build
- cmake --build build
- python3 -m http.server -d build/examples/render/Debug
+ cmake --build build --config Debug
+ python3 -m http.server -d .
+ #python3 serve.py -p 8000 -d .
#run:
# @just make
diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp
index 98aa82404..78e5cac42 100644
--- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp
+++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp
@@ -268,6 +268,7 @@ void AudioDeviceManager::createAudioDeviceTypes (OwnedArray&
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Oboe());
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_OpenSLES());
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Android());
+ addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_AudioWorklet());
}
void AudioDeviceManager::addAudioDeviceType (std::unique_ptr newDeviceType)
diff --git a/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp b/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp
index e143e013d..ac3ec2bc6 100644
--- a/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp
+++ b/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp
@@ -219,4 +219,16 @@ AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe()
}
#endif
+#if JUCE_EMSCRIPTEN
+AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_AudioWorklet()
+{
+ return new AudioWorkletAudioIODeviceType();
+}
+#else
+AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_AudioWorklet()
+{
+ return nullptr;
+}
+#endif
+
} // namespace juce
diff --git a/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h b/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h
index 2d3d63c38..a60e2df77 100644
--- a/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h
+++ b/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h
@@ -183,6 +183,8 @@ class JUCE_API AudioIODeviceType
static AudioIODeviceType* createAudioIODeviceType_Oboe();
/** Creates a Bela device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_Bela();
+ /** Creates a AudioWorklet device type if it's available on this platform, or returns null. */
+ static AudioIODeviceType* createAudioIODeviceType_AudioWorklet();
#ifndef DOXYGEN
[[deprecated ("You should call the method which takes a WASAPIDeviceMode instead.")]] static AudioIODeviceType* createAudioIODeviceType_WASAPI (bool exclusiveMode);
diff --git a/modules/juce_audio_devices/juce_audio_devices.cpp b/modules/juce_audio_devices/juce_audio_devices.cpp
index ac5cf32a8..74b3624e5 100644
--- a/modules/juce_audio_devices/juce_audio_devices.cpp
+++ b/modules/juce_audio_devices/juce_audio_devices.cpp
@@ -262,6 +262,13 @@ RealtimeThreadFactory getAndroidRealtimeThreadFactory() { return nullptr; }
#endif
#elif JUCE_WASM
+#if JUCE_EMSCRIPTEN
+#include
+#include
+
+#include "native/juce_AudioWorklet_emscripten.cpp"
+#endif
+
#include "native/juce_Midi_wasm.cpp"
#endif
diff --git a/modules/juce_audio_devices/native/juce_AudioWorklet_emscripten.cpp b/modules/juce_audio_devices/native/juce_AudioWorklet_emscripten.cpp
new file mode 100644
index 000000000..f42db1f2e
--- /dev/null
+++ b/modules/juce_audio_devices/native/juce_AudioWorklet_emscripten.cpp
@@ -0,0 +1,479 @@
+/*
+ ==============================================================================
+
+ This file is part of the YUP library.
+ Copyright (c) 2024 - kunitoki@gmail.com
+
+ YUP is an open source library subject to open-source licensing.
+
+ The code included in this file is provided under the terms of the ISC license
+ http://www.isc.org/downloads/software-support-policy/isc-license. Permission
+ to use, copy, modify, and/or distribute this software for any purpose with or
+ without fee is hereby granted provided that the above copyright notice and
+ this permission notice appear in all copies.
+
+ YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
+ DISCLAIMED.
+
+ ==============================================================================
+*/
+
+namespace juce
+{
+
+namespace
+{
+
+template
+using hasAudioSampleFrameSamplesPerChannel = decltype(T::samplesPerChannel);
+
+template
+int getNumSamplesPerChannel (const T& frame)
+{
+ if constexpr (isDetected)
+ return frame.samplesPerChannel;
+ else
+ return 128;
+}
+
+} // namespace
+
+//==============================================================================
+class AudioWorkletAudioIODevice final : public AudioIODevice
+{
+public:
+ AudioWorkletAudioIODevice()
+ : AudioIODevice (AudioWorkletAudioIODevice::audioWorkletTypeName,
+ AudioWorkletAudioIODevice::audioWorkletTypeName)
+ {
+ context = emscripten_create_audio_context (0);
+ }
+
+ ~AudioWorkletAudioIODevice()
+ {
+ close();
+
+ emscripten_destroy_audio_context (context);
+ }
+
+ //==============================================================================
+ StringArray getOutputChannelNames() override
+ {
+ StringArray result;
+
+ for (int i = 1; i <= actualNumberOfOutputs; i++)
+ result.add ("Out #" + String (i));
+
+ return result;
+ }
+
+ StringArray getInputChannelNames() override
+ {
+ StringArray result;
+
+ for (int i = 1; i <= actualNumberOfInputs; i++)
+ result.add ("In #" + String (i));
+
+ return result;
+ }
+
+ Array getAvailableSampleRates() override
+ {
+ return { getDefaultSampleRate() };
+ }
+
+ double getDefaultSampleRate()
+ {
+ int outputSampleRate = EM_ASM_INT ({
+ return emscriptenGetAudioObject ($0).sampleRate;
+ }, context);
+
+ if (outputSampleRate == 0)
+ outputSampleRate = 44100;
+
+ return static_cast (outputSampleRate);
+ }
+
+ Array getAvailableBufferSizes() override
+ {
+ return { getDefaultBufferSize() };
+ }
+
+ int getDefaultBufferSize() override
+ {
+ return 128;
+ }
+
+ //==============================================================================
+ String open (const BigInteger& inputChannels,
+ const BigInteger& outputChannels,
+ double sampleRate,
+ int bufferSizeSamples) override
+ {
+ if (sampleRate != getDefaultSampleRate() || bufferSizeSamples != getDefaultBufferSize())
+ {
+ lastError = "Browser audio outputs only support 44.1 kHz sample rate and 128 samples buffer size.";
+ return lastError;
+ }
+
+ auto numIns = getNumContiguousSetBits (inputChannels);
+ auto numOuts = getNumContiguousSetBits (outputChannels);
+ actualNumberOfInputs = jmax (numIns, 1);
+ actualNumberOfOutputs = jmax (numOuts, 1);
+ actualSampleRate = sampleRate;
+ actualBufferSize = (uint32) bufferSizeSamples;
+
+ channelInBuffer.calloc (actualNumberOfInputs);
+ channelOutBuffer.calloc (actualNumberOfOutputs);
+
+ isDeviceOpen = true;
+ isRunning = false;
+ callback = nullptr;
+ underruns = 0;
+
+ emscripten_start_wasm_audio_worklet_thread_async (
+ context, audioThreadStack, sizeof (audioThreadStack), &audioThreadInitializedCallback, this);
+
+ return {};
+ }
+
+ void close() override
+ {
+ stop();
+
+ if (isDeviceOpen)
+ {
+ EM_ASM ({
+ emscriptenGetAudioObject ($0).disconnect();
+ }, audioWorkletNode);
+ audioWorkletNode = {};
+
+ isDeviceOpen = false;
+ callback = nullptr;
+ underruns = 0;
+
+ actualBufferSize = 0;
+ actualNumberOfInputs = 0;
+ actualNumberOfOutputs = 0;
+ actualSampleRate = 44100.0;
+ actualBufferSize = 128u;
+
+ channelInBuffer.free();
+ channelOutBuffer.free();
+ }
+ }
+
+ bool isOpen() override { return isDeviceOpen; }
+
+ void start (AudioIODeviceCallback* newCallback) override
+ {
+ if (! isDeviceOpen)
+ return;
+
+ if (isRunning)
+ {
+ if (newCallback != callback)
+ {
+ if (newCallback != nullptr)
+ newCallback->audioDeviceAboutToStart (this);
+
+ {
+ ScopedLock lock (callbackLock);
+ std::swap (callback, newCallback);
+ }
+
+ if (newCallback != nullptr)
+ newCallback->audioDeviceStopped();
+ }
+ }
+ else
+ {
+ callback = newCallback;
+ isRunning = emscripten_audio_context_state (context) == AUDIO_CONTEXT_STATE_RUNNING;
+
+ if (! isRunning && hasBeenActivatedAlreadyByUser)
+ {
+ emscripten_resume_audio_context_sync (context);
+ isRunning = emscripten_audio_context_state (context) == AUDIO_CONTEXT_STATE_RUNNING;
+ }
+
+ firstCallback = true;
+
+ if (callback != nullptr)
+ {
+ if (isRunning)
+ callback->audioDeviceAboutToStart (this);
+ }
+ }
+ }
+
+ void stop() override
+ {
+ AudioIODeviceCallback* oldCallback = nullptr;
+
+ if (callback != nullptr)
+ {
+ ScopedLock lock (callbackLock);
+ std::swap (callback, oldCallback);
+ }
+
+ isRunning = false;
+
+ EM_ASM ({
+ emscriptenGetAudioObject ($0).suspend();
+ }, context);
+
+ if (oldCallback != nullptr)
+ oldCallback->audioDeviceStopped();
+ }
+
+ bool isPlaying() override { return isRunning; }
+
+ String getLastError() override { return lastError; }
+
+ //==============================================================================
+ int getCurrentBufferSizeSamples() override { return (int) actualBufferSize; }
+
+ double getCurrentSampleRate() override { return actualSampleRate; }
+
+ int getCurrentBitDepth() override { return 16; }
+
+ BigInteger getActiveOutputChannels() const override
+ {
+ BigInteger b;
+ b.setRange (0, actualNumberOfOutputs, true);
+ return b;
+ }
+
+ BigInteger getActiveInputChannels() const override
+ {
+ BigInteger b;
+ b.setRange (0, actualNumberOfInputs, true);
+ return b;
+ }
+
+ int getOutputLatencyInSamples() override
+ {
+ return 0;
+ }
+
+ int getInputLatencyInSamples() override
+ {
+ return 0;
+ }
+
+ int getXRunCount() const noexcept override { return underruns; }
+
+ //==============================================================================
+ static const char* const audioWorkletTypeName;
+
+private:
+ //==============================================================================
+
+ void audioThreadInitialized()
+ {
+ WebAudioWorkletProcessorCreateOptions opts =
+ {
+ .name = audioWorkletTypeName
+ };
+
+ emscripten_create_wasm_audio_worklet_processor_async (
+ context, &opts, &audioWorkletProcessorCreatedCallback, this);
+ }
+
+ void audioWorkletProcessorCreated()
+ {
+ int outputChannelCounts[1] = { actualNumberOfOutputs };
+ EmscriptenAudioWorkletNodeCreateOptions options =
+ {
+ .numberOfInputs = actualNumberOfInputs,
+ .numberOfOutputs = 1,
+ .outputChannelCounts = outputChannelCounts
+ };
+
+ // Create node
+ audioWorkletNode = emscripten_create_wasm_audio_worklet_node (
+ context, audioWorkletTypeName, &options, renderAudioCallback, this);
+
+ // Connect it to audio context destination
+ // emscripten_audio_node_connect (audioWorkletNode, context, 0, 0);
+ EM_ASM ({
+ emscriptenGetAudioObject ($0).connect (emscriptenGetAudioObject ($1).destination);
+ }, audioWorkletNode, context);
+
+ emscripten_set_click_callback ("canvas", reinterpret_cast (this), 0, canvasClickCallback);
+ }
+
+ EM_BOOL renderAudio (int numInputs, const AudioSampleFrame* inputs,
+ int numOutputs, AudioSampleFrame* outputs,
+ int numParams, const AudioParamFrame* params)
+ {
+ const int samplesPerChannel = [&]
+ {
+ if (numOutputs > 0)
+ return getNumSamplesPerChannel (outputs[0]);
+
+ else if (numInputs > 0)
+ return getNumSamplesPerChannel (inputs[0]);
+
+ return 128;
+ }();
+
+ // check for xruns
+ calculateXruns (samplesPerChannel);
+
+ ScopedLock lock (callbackLock);
+
+ if (callback != nullptr)
+ {
+ // Setup channelInBuffers
+ for (int ch = 0; ch < actualNumberOfInputs; ++ch)
+ channelInBuffer[ch] = &(inputs[ch].data[0]);
+
+ // Setup channelOutBuffers (assume a single worklet output)
+ for (int ch = 0; ch < actualNumberOfOutputs; ++ch)
+ channelOutBuffer[ch] = &(outputs[0].data[ch * samplesPerChannel]);
+
+ callback->audioDeviceIOCallbackWithContext (channelInBuffer.getData(),
+ actualNumberOfInputs,
+ channelOutBuffer.getData(),
+ actualNumberOfOutputs,
+ samplesPerChannel,
+ {});
+
+ audioFramesElapsed += samplesPerChannel;
+ }
+
+ return EM_TRUE; // keep going !
+ }
+
+ void canvasClick()
+ {
+ if (emscripten_audio_context_state (context) != AUDIO_CONTEXT_STATE_RUNNING)
+ {
+ emscripten_resume_audio_context_sync (context);
+
+ isRunning = true;
+ hasBeenActivatedAlreadyByUser = true;
+
+ ScopedLock lock (callbackLock);
+
+ if (callback != nullptr)
+ callback->audioDeviceAboutToStart (this);
+ }
+ }
+
+ //==============================================================================
+
+ static void audioThreadInitializedCallback (EMSCRIPTEN_WEBAUDIO_T audioContext, EM_BOOL success, void* userData)
+ {
+ if (! success) return; // Check browser console in a debug build for detailed errors
+
+ static_cast (userData)->audioThreadInitialized();
+ }
+
+ static void audioWorkletProcessorCreatedCallback (EMSCRIPTEN_WEBAUDIO_T audioContext, EM_BOOL success, void* userData)
+ {
+ if (! success) return; // Check browser console in a debug build for detailed errors
+
+ static_cast (userData)->audioWorkletProcessorCreated();
+ }
+
+ static EM_BOOL renderAudioCallback (int numInputs, const AudioSampleFrame* inputs,
+ int numOutputs, AudioSampleFrame* outputs,
+ int numParams, const AudioParamFrame* params,
+ void* userData)
+ {
+ return static_cast (userData)->renderAudio (numInputs, inputs, numOutputs, outputs, numParams, params);
+ }
+
+ static EM_BOOL canvasClickCallback (int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData)
+ {
+ static_cast (userData)->canvasClick();
+
+ return EM_FALSE;
+ }
+
+ //==============================================================================
+ uint64_t expectedElapsedAudioSamples = 0;
+ uint64_t audioFramesElapsed = 0;
+ int underruns = 0;
+ bool firstCallback = false;
+
+ void calculateXruns (uint32_t numSamples)
+ {
+ if (audioFramesElapsed > expectedElapsedAudioSamples && ! firstCallback)
+ ++underruns;
+
+ firstCallback = false;
+ expectedElapsedAudioSamples = audioFramesElapsed + numSamples;
+ }
+
+ //==============================================================================
+ static int getNumContiguousSetBits (const BigInteger& value) noexcept
+ {
+ int bit = 0;
+
+ while (value[bit])
+ ++bit;
+
+ return bit;
+ }
+
+ //==============================================================================
+ EMSCRIPTEN_WEBAUDIO_T context{};
+ EMSCRIPTEN_AUDIO_WORKLET_NODE_T audioWorkletNode{};
+
+ bool isDeviceOpen = false;
+ bool isRunning = false;
+ bool hasBeenActivatedAlreadyByUser = false;
+
+ CriticalSection callbackLock;
+ AudioIODeviceCallback* callback = nullptr;
+
+ String lastError;
+ uint32 actualBufferSize = 0;
+ int actualNumberOfInputs = 0, actualNumberOfOutputs = 0;
+ double actualSampleRate = 44100.0;
+
+ HeapBlock channelInBuffer;
+ HeapBlock channelOutBuffer;
+
+ alignas(16) uint8 audioThreadStack[4096] = {};
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioWorkletAudioIODevice)
+};
+
+const char* const AudioWorkletAudioIODevice::audioWorkletTypeName = "Audio Worklet";
+
+//==============================================================================
+struct AudioWorkletAudioIODeviceType final : public AudioIODeviceType
+{
+ AudioWorkletAudioIODeviceType()
+ : AudioIODeviceType ("AudioWorklet")
+ {
+ }
+
+ StringArray getDeviceNames (bool) const override { return StringArray (AudioWorkletAudioIODevice::audioWorkletTypeName); }
+
+ void scanForDevices() override {}
+
+ int getDefaultDeviceIndex (bool) const override { return 0; }
+
+ int getIndexOfDevice (AudioIODevice* device, bool) const override { return device != nullptr ? 0 : -1; }
+
+ bool hasSeparateInputsAndOutputs() const override { return false; }
+
+ AudioIODevice* createDevice (const String& outputName, const String& inputName) override
+ {
+ if (outputName == AudioWorkletAudioIODevice::audioWorkletTypeName || inputName == AudioWorkletAudioIODevice::audioWorkletTypeName)
+ return new AudioWorkletAudioIODevice();
+
+ return nullptr;
+ }
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioWorkletAudioIODeviceType)
+};
+
+} // namespace juce
diff --git a/modules/juce_core/juce_core.cpp b/modules/juce_core/juce_core.cpp
index dbccc194b..14be4503b 100644
--- a/modules/juce_core/juce_core.cpp
+++ b/modules/juce_core/juce_core.cpp
@@ -102,6 +102,7 @@ JUCE_END_IGNORE_WARNINGS_MSVC
#if JUCE_EMSCRIPTEN
#include
+#include
#endif
#if JUCE_LINUX || JUCE_BSD
@@ -290,8 +291,10 @@ extern char** environ;
//==============================================================================
#elif JUCE_WASM
+#include "native/juce_WebAssemblyHelpers.h"
#include "native/juce_SystemStats_wasm.cpp"
#include "native/juce_Files_wasm.cpp"
+#include "native/juce_Network_wasm.cpp"
#include "native/juce_Threads_wasm.cpp"
#include "native/juce_PlatformTimer_generic.cpp"
#endif
diff --git a/modules/juce_core/juce_core.h b/modules/juce_core/juce_core.h
index 5f42d8621..8aef38fd8 100644
--- a/modules/juce_core/juce_core.h
+++ b/modules/juce_core/juce_core.h
@@ -57,7 +57,7 @@
dependencies: zlib
osxFrameworks: Cocoa Foundation IOKit Security
- iosFrameworks: Foundation
+ iosFrameworks: Foundation UIKit
linuxLibs: rt dl pthread
mingwLibs: ws2_32 uuid wininet version kernel32 user32 wsock32 advapi32 ole32 oleaut32 imm32 comdlg32 shlwapi rpcrt4 winmm
diff --git a/modules/juce_core/misc/juce_MetaProgramming.h b/modules/juce_core/misc/juce_MetaProgramming.h
index 2eaea6f72..8ab6c32a6 100644
--- a/modules/juce_core/misc/juce_MetaProgramming.h
+++ b/modules/juce_core/misc/juce_MetaProgramming.h
@@ -22,10 +22,67 @@
namespace juce
{
+//==============================================================================
template
inline constexpr bool dependentBoolValue = Value;
template
inline constexpr bool dependentFalse = dependentBoolValue;
+//==============================================================================
+struct NoneSuch
+{
+ NoneSuch() = delete;
+ ~NoneSuch() = delete;
+ NoneSuch(NoneSuch const&) = delete;
+ void operator=(NoneSuch const&) = delete;
+ NoneSuch(NoneSuch&&) = delete;
+ void operator=(NoneSuch&&) = delete;
+};
+
+namespace detail {
+template class Op, class... Args>
+struct Detector
+{
+ using ValueType = std::false_type;
+ using Type = Default;
+};
+
+template class Op, class... Args>
+struct Detector>, Op, Args...>
+{
+ using ValueType = std::true_type;
+ using Type = Op;
+};
+
+template class Op, class... Args>
+using isDetected = typename Detector::ValueType;
+
+template class Op, class... Args>
+using detectedOr = Detector;
+} // namespace detail
+
+template class Op, class... Args>
+inline constexpr bool isDetected = detail::isDetected::value;
+
+template class Op, class... Args>
+using detectedType = typename detail::Detector::Type;
+
+template class Op, class... Args>
+using detectedOr = typename detail::detectedOr::Type;
+
+namespace detail {
+template class Op, class... Args>
+using isDetectedExact = std::is_same>;
+
+template class Op, class... Args>
+using isDetectedConvertible = std::is_convertible, To>;
+} // namespace detail
+
+template class Op, class... Args>
+inline constexpr bool isDetectedExact = detail::isDetectedExact::value;
+
+template class Op, class... Args>
+inline constexpr bool isDetectedConvertible = detail::isDetectedConvertible::value;
+
} // namespace juce
diff --git a/modules/juce_core/native/juce_Files_wasm.cpp b/modules/juce_core/native/juce_Files_wasm.cpp
index 234a65fc4..69fde81a3 100644
--- a/modules/juce_core/native/juce_Files_wasm.cpp
+++ b/modules/juce_core/native/juce_Files_wasm.cpp
@@ -226,8 +226,7 @@ bool Process::openDocument (const String& fileName, const String& parameters)
document.body.appendChild (elem);
elem.click();
document.body.removeChild (elem);
- },
- cmdString.toRawUTF8());
+ }, cmdString.toRawUTF8());
return true;
}
diff --git a/modules/juce_core/native/juce_Network_wasm.cpp b/modules/juce_core/native/juce_Network_wasm.cpp
new file mode 100644
index 000000000..0c16d466f
--- /dev/null
+++ b/modules/juce_core/native/juce_Network_wasm.cpp
@@ -0,0 +1,314 @@
+/*
+ ==============================================================================
+
+ This file is part of the YUP library.
+ Copyright (c) 2024 - kunitoki@gmail.com
+
+ YUP is an open source library subject to open-source licensing.
+
+ The code included in this file is provided under the terms of the ISC license
+ http://www.isc.org/downloads/software-support-policy/isc-license. Permission
+ to use, copy, modify, and/or distribute this software for any purpose with or
+ without fee is hereby granted provided that the above copyright notice and
+ this permission notice appear in all copies.
+
+ YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
+ DISCLAIMED.
+
+ ==============================================================================
+*/
+
+namespace juce
+{
+
+void MACAddress::findAllAddresses (Array& result)
+{
+ result.clearQuick();
+}
+
+bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& /* targetEmailAddress */,
+ const String& /* emailSubject */,
+ const String& /* bodyText */,
+ const StringArray& /* filesToAttach */)
+{
+ jassertfalse; // xxx todo
+ return false;
+}
+
+//==============================================================================
+#if JUCE_EMSCRIPTEN && ! JUCE_USE_CURL
+class WebInputStream::Pimpl
+{
+public:
+ Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool addParametersToBody)
+ : owner (pimplOwner)
+ , url (urlToCopy)
+ , addParametersToRequestBody (addParametersToBody)
+ , hasBodyDataToSend (addParametersToRequestBody || url.hasBodyDataToSend())
+ , httpRequestCmd (hasBodyDataToSend ? "POST" : "GET")
+ {
+ }
+
+ ~Pimpl()
+ {
+ cancel();
+ }
+
+ //==============================================================================
+ // WebInputStream methods
+ void withExtraHeaders (const String& extraHeaders)
+ {
+ if (! headers.endsWithChar ('\n') && headers.isNotEmpty())
+ headers << "\r\n";
+
+ headers << extraHeaders;
+
+ if (! headers.endsWithChar ('\n') && headers.isNotEmpty())
+ headers << "\r\n";
+ }
+
+ void withCustomRequestCommand (const String& customRequestCommand) { httpRequestCmd = customRequestCommand; }
+
+ void withConnectionTimeout (int timeoutInMs) { timeOutMs = timeoutInMs; }
+
+ void withNumRedirectsToFollow (int maxRedirectsToFollow) { numRedirectsToFollow = maxRedirectsToFollow; }
+
+ int getStatusCode() const { return statusCode; }
+
+ StringPairArray getRequestHeaders() const { return WebInputStream::parseHttpHeaders (headers); }
+
+ StringPairArray getResponseHeaders() const
+ {
+ return responseHeaders;
+ }
+
+ bool connect (WebInputStream::Listener* listener)
+ {
+ if (hasBeenCancelled)
+ return false;
+
+ address = url.toString (! addParametersToRequestBody);
+
+ statusCode = 0;
+ finished = false;
+ position = 0;
+ totalBytesRead = 0;
+
+ // Prepare the fetch attributes
+ emscripten_fetch_attr_init (&fetchAttr);
+
+ std::strncpy (fetchAttr.requestMethod, httpRequestCmd.toRawUTF8(), sizeof (fetchAttr.requestMethod) - 1);
+ fetchAttr.requestMethod[sizeof (fetchAttr.requestMethod) - 1] = '\0';
+
+ // Handle headers
+ headerData = parseHeaders (headers);
+ fetchAttr.requestHeaders = headerData.data();
+
+ // Handle POST data
+ if (hasBodyDataToSend)
+ {
+ WebInputStream::createHeadersAndPostData (url, headers, postData, addParametersToRequestBody);
+ fetchAttr.requestData = (const char*)postData.getData();
+ fetchAttr.requestDataSize = postData.getSize();
+ }
+
+ fetchAttr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
+ fetchAttr.onsuccess = &onFetchSuccess;
+ fetchAttr.onprogress = &onFetchProgress;
+ fetchAttr.onerror = &onFetchError;
+ fetchAttr.userData = this;
+
+ // Start the fetch request
+ fetchHandle = emscripten_fetch (&fetchAttr, address.toRawUTF8());
+
+ return true;
+ }
+
+ void cancel()
+ {
+ hasBeenCancelled = true;
+ finished = true;
+
+ if (fetchHandle)
+ {
+ emscripten_fetch_close (fetchHandle);
+ fetchHandle = nullptr;
+ }
+ }
+
+ //==============================================================================
+ bool isError() const { return fetchHandle == nullptr || statusCode == 0; }
+
+ bool isExhausted() { return finished && position >= contentLength; }
+
+ int64 getPosition() { return position; }
+
+ int64 getTotalLength() { return contentLength; }
+
+ int read (void* buffer, int bytesToRead)
+ {
+ if (isError() || totalBytesRead >= contentLength)
+ return 0;
+
+ int bytesAvailable = (int) (dataBuffer.size() - position);
+ int bytesToCopy = jmin (bytesToRead, bytesAvailable);
+
+ if (bytesToCopy > 0)
+ {
+ std::memcpy (buffer, dataBuffer.data() + position, bytesToCopy);
+ position += bytesToCopy;
+ totalBytesRead += bytesToCopy;
+ return bytesToCopy;
+ }
+
+ // No data available yet, return 0
+ return 0;
+ }
+
+ bool setPosition (int64 wantedPos)
+ {
+ if (isError())
+ return false;
+
+ if (wantedPos >= 0 && wantedPos <= (int64) dataBuffer.size())
+ {
+ position = wantedPos;
+ return true;
+ }
+
+ return false;
+ }
+
+ //==============================================================================
+ int statusCode = 0;
+
+private:
+ WebInputStream& owner;
+ URL url;
+ StringArray headerLines;
+ String address, headers;
+ MemoryBlock postData;
+ int64 contentLength = -1, position = 0, totalBytesRead = 0;
+ bool finished = false;
+ const bool addParametersToRequestBody, hasBodyDataToSend;
+ int timeOutMs = 0;
+ int numRedirectsToFollow = 5;
+ String httpRequestCmd;
+ bool hasBeenCancelled = false;
+
+ emscripten_fetch_attr_t fetchAttr;
+ emscripten_fetch_t* fetchHandle = nullptr;
+ std::vector dataBuffer;
+ StringPairArray responseHeaders;
+ std::vector headerData; // to keep header strings alive
+
+ // Callback for successful fetch
+ static void onFetchSuccess (emscripten_fetch_t* fetch)
+ {
+ Pimpl* self = static_cast (fetch->userData);
+
+ self->statusCode = fetch->status;
+ self->contentLength = fetch->numBytes;
+ self->dataBuffer.assign (fetch->data, fetch->data + fetch->numBytes);
+ self->finished = true;
+
+ // Parse response headers
+ if (auto headersSize = emscripten_fetch_get_response_headers_length(fetch); headersSize > 0)
+ {
+ std::vector headersBuffer (headersSize + 1);
+ emscripten_fetch_get_response_headers (fetch, headersBuffer.data(), headersBuffer.size());
+ headersBuffer.back() = '\0';
+
+ self->parseResponseHeaders (headersBuffer.data());
+ }
+
+ // Close the fetch
+ emscripten_fetch_close (fetch);
+ self->fetchHandle = nullptr;
+ }
+
+ // Callback for fetch error
+ static void onFetchError (emscripten_fetch_t* fetch)
+ {
+ Pimpl* self = static_cast (fetch->userData);
+
+ self->statusCode = fetch->status;
+ self->finished = true;
+
+ // Close the fetch
+ emscripten_fetch_close (fetch);
+ self->fetchHandle = nullptr;
+ }
+
+ // Callback for fetch progress (optional)
+ static void onFetchProgress (emscripten_fetch_t* fetch)
+ {
+ // We can implement progress updates here if needed
+ // For now, we can leave this empty or implement listener callbacks
+ }
+
+ // Helper to parse headers into the format expected by emscripten_fetch
+ std::vector parseHeaders (const String& headersString)
+ {
+ StringArray headerLines = StringArray::fromLines (headersString.trim());
+
+ std::vector headerArray;
+ for (const auto& line : headerLines)
+ {
+ int colonPos = line.indexOfChar (':');
+ if (colonPos > 0)
+ {
+ String key = line.substring (0, colonPos).trim();
+ String value = line.substring (colonPos + 1).trim();
+
+ // Store the key and value strings
+ headerKeyValues.emplace_back (key.toRawUTF8());
+ headerKeyValues.emplace_back (value.toRawUTF8());
+
+ // Store pointers to the strings
+ headerArray.push_back (headerKeyValues[headerKeyValues.size() - 2].c_str());
+ headerArray.push_back (headerKeyValues.back().c_str());
+ }
+ }
+
+ headerArray.push_back (nullptr);
+ return headerArray;
+ }
+
+ std::vector headerKeyValues; // To keep header strings alive
+
+ // Helper to parse response headers
+ void parseResponseHeaders (const char* headers)
+ {
+ if (headers == nullptr)
+ return;
+
+ String headersString (headers);
+ StringArray lines;
+ lines.addLines (headersString);
+
+ for (const auto& line : lines)
+ {
+ int colonPos = line.indexOfChar (':');
+ if (colonPos > 0)
+ {
+ String key = line.substring (0, colonPos).trim();
+ String value = line.substring (colonPos + 1).trim();
+
+ auto previousValue = responseHeaders[key];
+ responseHeaders.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value));
+ }
+ }
+ }
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
+};
+
+std::unique_ptr URL::downloadToFile (const File& targetLocation, const DownloadTaskOptions& options)
+{
+ return URL::DownloadTask::createFallbackDownloader (*this, targetLocation, options);
+}
+#endif
+
+} // namespace juce
diff --git a/modules/juce_core/native/juce_SharedCode_posix.h b/modules/juce_core/native/juce_SharedCode_posix.h
index 9f12071f0..6afe4b605 100644
--- a/modules/juce_core/native/juce_SharedCode_posix.h
+++ b/modules/juce_core/native/juce_SharedCode_posix.h
@@ -1058,7 +1058,10 @@ static void* makeThreadHandle (PosixThreadAttribute& attr, void* userData, void*
if (status != 0)
return nullptr;
+//#if !JUCE_EMSCRIPTEN || defined(__EMSCRIPTEN_PTHREADS__)
pthread_detach (handle);
+//#endif
+
return (void*) handle;
}
diff --git a/modules/juce_core/native/juce_SystemStats_wasm.cpp b/modules/juce_core/native/juce_SystemStats_wasm.cpp
index f1e9f8e82..b1db8e8e5 100644
--- a/modules/juce_core/native/juce_SystemStats_wasm.cpp
+++ b/modules/juce_core/native/juce_SystemStats_wasm.cpp
@@ -42,17 +42,100 @@ namespace juce
void Logger::outputDebugString (const String& text)
{
- std::printf ("%.*s", text.length(), text.toRawUTF8());
+#if JUCE_EMSCRIPTEN
+ if (juce_isRunningUnderBrowser())
+ {
+ EM_ASM ({ console.log (UTF8ToString ($0)); }, text.toRawUTF8());
+ return;
+ }
+#endif
+
+ std::fprintf (stderr, "%.*s", text.length(), text.toRawUTF8());
}
//==============================================================================
-SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() { return WASM; }
+SystemStats::OperatingSystemType SystemStats::getOperatingSystemType()
+{
+#if JUCE_EMSCRIPTEN
+ return WebBrowser;
+#else
+ return WASM;
+#endif
+}
-String SystemStats::getOperatingSystemName() { return "WASM"; }
+String SystemStats::getOperatingSystemName()
+{
+#if JUCE_EMSCRIPTEN
+ char* platform = reinterpret_cast (EM_ASM_PTR ({
+ var str = (typeof navigator !== 'undefined') ? (navigator.platform || "unknown") : "unknown";
+ var lengthBytes = lengthBytesUTF8 (str) + 1;
+ var ptr = _malloc (lengthBytes);
+ stringToUTF8 (str, ptr, lengthBytes);
+ return ptr;
+ }));
+
+ String platformString (platform ? platform : "unknown");
+ free (platform);
+
+ return platformString;
+#else
+ return "WASM";
+#endif
-bool SystemStats::isOperatingSystem64Bit() { return true; }
+}
-String SystemStats::getDeviceDescription() { return "Web-browser"; }
+bool SystemStats::isOperatingSystem64Bit()
+{
+ return sizeof(void*) == 8;
+}
+
+String SystemStats::getUniqueDeviceID()
+{
+#if JUCE_EMSCRIPTEN
+ char* deviceInfo = reinterpret_cast (EM_ASM_PTR ({
+ var info = "";
+ if (typeof navigator !== 'undefined')
+ {
+ info += navigator.userAgent || "";
+ info += navigator.platform || "";
+ info += navigator.language || "";
+ }
+
+ var lengthBytes = lengthBytesUTF8 (info) + 1;
+ var ptr = _malloc (lengthBytes);
+ stringToUTF8 (info, ptr, lengthBytes);
+ return ptr;
+ }));
+
+ String infoString (deviceInfo ? deviceInfo : "");
+ free (deviceInfo);
+
+ return String (static_cast (infoString.hashCode64()));
+#else
+ return {};
+#endif
+}
+
+String SystemStats::getDeviceDescription()
+{
+#if JUCE_EMSCRIPTEN
+ char* userAgent = reinterpret_cast (EM_ASM_PTR ({
+ var str = (typeof navigator !== 'undefined') ? (navigator.userAgent || "unknown") : "unknown";
+ var lengthBytes = lengthBytesUTF8 (str) + 1;
+ var ptr = _malloc (lengthBytes);
+ stringToUTF8 (str, ptr, lengthBytes);
+ return ptr;
+ }));
+
+ String userAgentString (userAgent ? userAgent : "unknown");
+
+ free (userAgent);
+
+ return userAgentString;
+#else
+ return "WASM VM";
+#endif
+}
String SystemStats::getDeviceManufacturer() { return {}; }
@@ -62,9 +145,25 @@ String SystemStats::getCpuModel() { return {}; }
int SystemStats::getCpuSpeedInMegahertz() { return 0; }
-int SystemStats::getMemorySizeInMegabytes() { return 0; }
+int SystemStats::getMemorySizeInMegabytes()
+{
+#if JUCE_EMSCRIPTEN
+ int memoryMB = EM_ASM_INT ({
+ if ((typeof navigator !== 'undefined') && "deviceMemory" in navigator)
+ return navigator.deviceMemory * 1024;
+ return 0;
+ });
+
+ return memoryMB * 1024; // Convert GB to MB
+#else
+ return 0;
+#endif
+}
-int SystemStats::getPageSize() { return 0; }
+int SystemStats::getPageSize()
+{
+ return 65536;
+}
String SystemStats::getLogonName() { return {}; }
@@ -72,28 +171,98 @@ String SystemStats::getFullUserName() { return {}; }
String SystemStats::getComputerName() { return {}; }
-String SystemStats::getUserLanguage() { return {}; }
+String SystemStats::getUserLanguage()
+{
+#if JUCE_EMSCRIPTEN
+ char* language = reinterpret_cast (EM_ASM_PTR ({
+ var str = (typeof navigator !== 'undefined') ? (navigator.language || "") : "";
+ var lengthBytes = lengthBytesUTF8 (str) + 1;
+ var ptr = _malloc (lengthBytes);
+ stringToUTF8 (str, ptr, lengthBytes);
+ return ptr;
+ }));
+
+ String languageString (language ? language : "");
+ free (language);
+
+ return languageString;
+#else
+ return {};
+#endif
+}
-String SystemStats::getUserRegion() { return {}; }
+String SystemStats::getUserRegion()
+{
+#if JUCE_EMSCRIPTEN
+ char* locale = reinterpret_cast (EM_ASM_PTR ({
+ var str = "";
+ if (typeof Intl !== 'undefined' && Intl.DateTimeFormat)
+ {
+ var options = Intl.DateTimeFormat().resolvedOptions();
+ if (options && options.locale)
+ str = options.locale;
+ }
+
+ var lengthBytes = lengthBytesUTF8 (str) + 1;
+ var ptr = _malloc (lengthBytes);
+ stringToUTF8 (str, ptr, lengthBytes);
+ return ptr;
+ }));
+
+ String localeString (locale ? locale : "");
+ free (locale);
+
+ return localeString;
+#else
+ return {};
+#endif
+}
-String SystemStats::getDisplayLanguage() { return {}; }
+String SystemStats::getDisplayLanguage()
+{
+ return getUserLanguage();
+}
//==============================================================================
void CPUInformation::initialise() noexcept
{
+#if JUCE_EMSCRIPTEN
+ int hwConcurrency = EM_ASM_INT ({
+ if (typeof navigator !== 'undefined' && "hardwareConcurrency" in navigator)
+ return navigator.hardwareConcurrency;
+ return 1;
+ });
+
+ numLogicalCPUs = hwConcurrency > 0 ? hwConcurrency : 1;
+ numPhysicalCPUs = numLogicalCPUs; // Physical core info isn't available
+#else
numLogicalCPUs = 1;
numPhysicalCPUs = 1;
+#endif
}
//==============================================================================
uint32 juce_millisecondsSinceStartup() noexcept
{
- return static_cast (emscripten_get_now());
+#if JUCE_EMSCRIPTEN
+ if (juce_isRunningUnderBrowser())
+ return static_cast (emscripten_get_now());
+#endif
+
+ const auto elapsed = std::chrono::duration_cast
+ (std::chrono::steady_clock::now() - juce_getTimeSinceStartupFallback());
+
+ return static_cast(elapsed.count());
}
int64 Time::getHighResolutionTicks() noexcept
{
- return static_cast (emscripten_get_now() * 1000.0);
+#if JUCE_EMSCRIPTEN
+ if (juce_isRunningUnderBrowser())
+ return static_cast (emscripten_get_now() * 1000.0);
+#endif
+
+ return static_cast (juce_millisecondsSinceStartup() * 1000.0);
}
int64 Time::getHighResolutionTicksPerSecond() noexcept
@@ -103,7 +272,12 @@ int64 Time::getHighResolutionTicksPerSecond() noexcept
double Time::getMillisecondCounterHiRes() noexcept
{
- return emscripten_get_now();
+#if JUCE_EMSCRIPTEN
+ if (juce_isRunningUnderBrowser())
+ return emscripten_get_now();
+#endif
+
+ return static_cast (juce_millisecondsSinceStartup());
}
bool Time::setSystemTimeToThisTime() const
diff --git a/modules/juce_core/native/juce_WebAssemblyHelpers.h b/modules/juce_core/native/juce_WebAssemblyHelpers.h
new file mode 100644
index 000000000..8210b2946
--- /dev/null
+++ b/modules/juce_core/native/juce_WebAssemblyHelpers.h
@@ -0,0 +1,48 @@
+/*
+ ==============================================================================
+
+ This file is part of the YUP library.
+ Copyright (c) 2024 - kunitoki@gmail.com
+
+ YUP is an open source library subject to open-source licensing.
+
+ The code included in this file is provided under the terms of the ISC license
+ http://www.isc.org/downloads/software-support-policy/isc-license. Permission
+ to use, copy, modify, and/or distribute this software for any purpose with or
+ without fee is hereby granted provided that the above copyright notice and
+ this permission notice appear in all copies.
+
+ YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
+ DISCLAIMED.
+
+ ==============================================================================
+*/
+
+namespace juce {
+namespace {
+
+std::chrono::steady_clock::time_point juce_getTimeSinceStartupFallback() noexcept
+{
+ static const auto timeSinceStartup = std::chrono::steady_clock::now();
+ return timeSinceStartup;
+}
+
+bool juce_isRunningUnderBrowser()
+{
+#if JUCE_EMSCRIPTEN
+ static bool hasBrowserWindowObject = []
+ {
+ return EM_ASM_INT({
+ return typeof window !== "undefined" ? 1 : 0;
+ });
+ }();
+
+ return hasBrowserWindowObject;
+#else
+ return false;
+#endif
+}
+
+} // namespace
+} // namespace juce
\ No newline at end of file
diff --git a/modules/juce_core/system/juce_SystemStats.cpp b/modules/juce_core/system/juce_SystemStats.cpp
index 0d89b608a..50353b62c 100644
--- a/modules/juce_core/system/juce_SystemStats.cpp
+++ b/modules/juce_core/system/juce_SystemStats.cpp
@@ -243,7 +243,7 @@ String SystemStats::getStackBacktrace()
{
String result;
-#if JUCE_MINGW || JUCE_WASM
+#if JUCE_MINGW || (JUCE_WASM && !JUCE_EMSCRIPTEN)
jassertfalse; // sorry, not implemented yet!
#elif JUCE_WINDOWS
@@ -277,6 +277,12 @@ String SystemStats::getStackBacktrace()
}
}
+#elif JUCE_EMSCRIPTEN
+ std::string temporaryStack;
+ temporaryStack.resize (10 * EM_ASM_INT_V ({ return (lengthBytesUTF8 || Module.lengthBytesUTF8)(stackTrace()); }));
+ EM_ASM_ARGS({ (stringToUTF8 || Module.stringToUTF8)(stackTrace(), $0, $1); }, temporaryStack.data(), temporaryStack.size());
+ result << temporaryStack.c_str();
+
#else
void* stack[128];
@@ -332,8 +338,6 @@ String SystemStats::getStackBacktrace()
}
//==============================================================================
-#if ! JUCE_WASM
-
static SystemStats::CrashHandlerFunction globalCrashHandler = nullptr;
#if JUCE_WINDOWS
@@ -363,7 +367,7 @@ void SystemStats::setApplicationCrashHandler (CrashHandlerFunction handler)
SetUnhandledExceptionFilter (handleCrash);
#elif JUCE_WASM
- // TODO
+ // TODO
#else
const int signals[] = { SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGSYS };
@@ -377,8 +381,6 @@ void SystemStats::setApplicationCrashHandler (CrashHandlerFunction handler)
#endif
}
-#endif
-
bool SystemStats::isRunningInAppExtensionSandbox() noexcept
{
#if JUCE_MAC || JUCE_IOS
@@ -402,28 +404,4 @@ bool SystemStats::isRunningInAppExtensionSandbox() noexcept
#endif
}
-#if JUCE_UNIT_TESTS
-
-class UniqueHardwareIDTest final : public UnitTest
-{
-public:
- //==============================================================================
- UniqueHardwareIDTest()
- : UnitTest ("UniqueHardwareID", UnitTestCategories::analytics)
- {
- }
-
- void runTest() override
- {
- beginTest ("getUniqueDeviceID returns usable data.");
- {
- expect (SystemStats::getUniqueDeviceID().isNotEmpty());
- }
- }
-};
-
-static UniqueHardwareIDTest uniqueHardwareIDTest;
-
-#endif
-
} // namespace juce
diff --git a/modules/juce_core/system/juce_SystemStats.h b/modules/juce_core/system/juce_SystemStats.h
index 3022c06fd..4e924de94 100644
--- a/modules/juce_core/system/juce_SystemStats.h
+++ b/modules/juce_core/system/juce_SystemStats.h
@@ -70,6 +70,8 @@ class JUCE_API SystemStats final
iOS = 0x1000,
WASM = 0x2000,
+ WebBrowser = WASM | 0x0100,
+
MacOSX_10_7 = MacOSX | 7,
MacOSX_10_8 = MacOSX | 8,
MacOSX_10_9 = MacOSX | 9,
diff --git a/modules/juce_events/timers/juce_Timer.cpp b/modules/juce_events/timers/juce_Timer.cpp
index 8b1c88d83..431de67b2 100644
--- a/modules/juce_events/timers/juce_Timer.cpp
+++ b/modules/juce_events/timers/juce_Timer.cpp
@@ -112,7 +112,7 @@ class Timer::TimerThread final : private Thread
auto now = Time::getMillisecondCounter();
auto timeout = now + maxTimeoutMilliseconds;
-#if JUCE_WASM
+#if JUCE_EMSCRIPTEN && !defined(__EMSCRIPTEN_PTHREADS__)
auto elapsed = (int) (now >= lastCallTime ? (now - lastCallTime)
: (std::numeric_limits::max() - (lastCallTime - now)));
lastCallTime = now;
diff --git a/modules/yup_gui/native/yup_Windowing_glfw.cpp b/modules/yup_gui/native/yup_Windowing_glfw.cpp
index 9c09ca2ef..c2cd1c1c6 100644
--- a/modules/yup_gui/native/yup_Windowing_glfw.cpp
+++ b/modules/yup_gui/native/yup_Windowing_glfw.cpp
@@ -1071,7 +1071,7 @@ void GLFWComponentNative::triggerRenderingUpdate()
void GLFWComponentNative::startRendering()
{
-#if JUCE_EMSCRIPTEN && RIVE_WEBGL
+#if (JUCE_EMSCRIPTEN && RIVE_WEBGL) && !defined(__EMSCRIPTEN_PTHREADS__)
startTimerHz (desiredFrameRate);
#else
startThread (Priority::high);
@@ -1080,7 +1080,7 @@ void GLFWComponentNative::startRendering()
void GLFWComponentNative::stopRendering()
{
-#if JUCE_EMSCRIPTEN && RIVE_WEBGL
+#if (JUCE_EMSCRIPTEN && RIVE_WEBGL) && !defined(__EMSCRIPTEN_PTHREADS__)
stopTimer();
#else
signalThreadShouldExit();
diff --git a/scripts/update_unity_build_files.sh b/scripts/update_unity_build_files.sh
deleted file mode 100644
index 68750ba21..000000000
--- a/scripts/update_unity_build_files.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-find . -name "*.cpp" | awk '{print "#include \"" substr($0,3) "\"" }'
diff --git a/serve.py b/serve.py
new file mode 100644
index 000000000..d0b5cc6f6
--- /dev/null
+++ b/serve.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+
+import http.server
+import socketserver
+import argparse
+import functools
+import os
+import signal
+import sys
+
+class CORSRequestHandler (http.server.SimpleHTTPRequestHandler):
+ def end_headers (self):
+ self.send_header('Access-Control-Allow-Origin', '*')
+ self.send_header('Cross-Origin-Opener-Policy', 'same-origin')
+ self.send_header('Cross-Origin-Embedder-Policy', 'require-corp')
+ http.server.SimpleHTTPRequestHandler.end_headers(self)
+
+def signal_handler(sig, frame):
+ print('You pressed Ctrl+C!')
+ sys.exit(0)
+
+def main():
+ parser = argparse.ArgumentParser(description='Simple HTTP Server with CORS and COOP/COEP headers.')
+ parser.add_argument('-d', '--directory', default='.', help='Directory to serve.')
+ parser.add_argument('-p', '--port', type=int, default=8000, help='Port to listen on.')
+ args = parser.parse_args()
+
+ handler_class = functools.partial(CORSRequestHandler, directory=os.path.abspath(args.directory))
+
+ with socketserver.TCPServer(("", args.port), handler_class) as httpd:
+ signal.signal(signal.SIGINT, signal_handler)
+
+ print(f"Serving HTTP on port {args.port} (http://localhost:{args.port}/) ...")
+ print(f"Serving directory: {os.path.abspath(args.directory)}")
+
+ httpd.serve_forever()
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index acedbbe5a..32d7d05a0 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -28,7 +28,7 @@ endif()
include (FetchContent)
FetchContent_Declare(
googletest
- URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip
+ URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.zip
)
set (gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable (googletest)
@@ -37,6 +37,11 @@ set_target_properties (gtest_main PROPERTIES FOLDER "Tests")
set_target_properties (gmock PROPERTIES FOLDER "Tests")
set_target_properties (gmock_main PROPERTIES FOLDER "Tests")
+if ("${yup_platform}" MATCHES "^(emscripten)$")
+ target_compile_options (gmock PRIVATE -pthread)
+ target_compile_options (gtest PRIVATE -pthread)
+endif()
+
# ==== Create executable
set (target_name yup_tests)
set (target_version "1.0.0")
diff --git a/tests/juce_core/juce_ElementComparator.cpp b/tests/juce_core/juce_ElementComparator.cpp
index b954aaa15..d91d197f1 100644
--- a/tests/juce_core/juce_ElementComparator.cpp
+++ b/tests/juce_core/juce_ElementComparator.cpp
@@ -36,7 +36,7 @@ struct IntComparator
}
};
-TEST (SortFunctionConverterTest, SortFunctionConverterWorks)
+TEST (SortFunctionConverterTests, SortFunctionConverterWorks)
{
std::vector vec { 5, 2, 9, 1, 5, 6 };
diff --git a/tests/juce_core/juce_Enumerate.cpp b/tests/juce_core/juce_Enumerate.cpp
index 0b5ffd095..8d873a5ab 100644
--- a/tests/juce_core/juce_Enumerate.cpp
+++ b/tests/juce_core/juce_Enumerate.cpp
@@ -41,7 +41,7 @@
#include
-TEST (Enumerate, Works_With_Bidirectional_Iterators)
+TEST (EnumerateTests, Works_With_Bidirectional_Iterators)
{
const std::list elements { 10, 20, 30, 40, 50 };
std::vector counts;
@@ -52,7 +52,7 @@ TEST (Enumerate, Works_With_Bidirectional_Iterators)
EXPECT_EQ (counts, (std::vector { 0, 1, 2, 3, 4 }));
}
-TEST (Enumerate, Works_With_Random_Access_Iterators)
+TEST (EnumerateTests, Works_With_Random_Access_Iterators)
{
const std::vector strings { "a", "bb", "ccc", "dddd", "eeeee" };
@@ -64,7 +64,7 @@ TEST (Enumerate, Works_With_Random_Access_Iterators)
EXPECT_EQ (counts, (std::vector { 1, 3, 5, 7, 9 }));
}
-TEST (Enumerate, Works_With_Mutable_Ranges)
+TEST (EnumerateTests, Works_With_Mutable_Ranges)
{
std::vector strings { "", "", "", "", "" };
@@ -74,7 +74,7 @@ TEST (Enumerate, Works_With_Mutable_Ranges)
EXPECT_EQ (strings, (std::vector { "0", "1", "2", "3", "4" }));
}
-TEST (Enumerate, Iterator_Can_Be_Incremented_By_More_Than_One)
+TEST (EnumerateTests, Iterator_Can_Be_Incremented_By_More_Than_One)
{
std::vector ints (6);
@@ -88,7 +88,7 @@ TEST (Enumerate, Iterator_Can_Be_Incremented_By_More_Than_One)
EXPECT_EQ (counts, (std::vector { 0, 2, 4 }));
}
-TEST (Enumerate, Iterator_Can_Be_Started_At_Non_Zero_Value)
+TEST (EnumerateTests, Iterator_Can_Be_Started_At_Non_Zero_Value)
{
const std::vector ints (6);
@@ -100,14 +100,14 @@ TEST (Enumerate, Iterator_Can_Be_Started_At_Non_Zero_Value)
EXPECT_EQ (counts, (std::vector { 5, 6, 7, 8, 9, 10 }));
}
-TEST (Enumerate, Subtracting_Two_Iterators_Returns_The_Difference_Between_The_Base_Iterators)
+TEST (EnumerateTests, Subtracting_Two_Iterators_Returns_The_Difference_Between_The_Base_Iterators)
{
const std::vector ints (6);
const auto enumerated = juce::enumerate (ints);
EXPECT_EQ ((int) (enumerated.end() - enumerated.begin()), (int) ints.size());
}
-TEST (Enumerate, EnumerateIterator_Can_Be_Decremented)
+TEST (EnumerateTests, EnumerateIterator_Can_Be_Decremented)
{
const std::vector ints (5);
std::vector counts;
@@ -120,7 +120,7 @@ TEST (Enumerate, EnumerateIterator_Can_Be_Decremented)
EXPECT_EQ (counts, (std::vector { -1, -2, -3, -4, -5 }));
}
-TEST (Enumerate, EnumerateIterator_Can_Be_Compared)
+TEST (EnumerateTests, EnumerateIterator_Can_Be_Compared)
{
const std::vector ints (6);
const auto enumerated = juce::enumerate (ints);
diff --git a/tests/juce_core/juce_FlagSet.cpp b/tests/juce_core/juce_FlagSet.cpp
index 047aac163..6e305f85a 100644
--- a/tests/juce_core/juce_FlagSet.cpp
+++ b/tests/juce_core/juce_FlagSet.cpp
@@ -37,35 +37,35 @@ static inline constexpr LogOption verboseLog = LogOption::declareValue();
} // namespace
-TEST (FlagSet, Default_Constructed)
+TEST (FlagSetTests, Default_Constructed)
{
LogOption option;
EXPECT_FALSE (option.test (verboseLog));
EXPECT_FALSE (option.test (noErrorLog));
}
-TEST (FlagSet, Default_Constructed_From_Default)
+TEST (FlagSetTests, Default_Constructed_From_Default)
{
LogOption option = defaultLog;
EXPECT_FALSE (option.test (verboseLog));
EXPECT_FALSE (option.test (noErrorLog));
}
-TEST (FlagSet, Default_Constructed_From_Value)
+TEST (FlagSetTests, Default_Constructed_From_Value)
{
LogOption option = verboseLog;
EXPECT_TRUE (option.test (verboseLog));
EXPECT_FALSE (option.test (noErrorLog));
}
-TEST (FlagSet, Default_Constructed_From_Values)
+TEST (FlagSetTests, Default_Constructed_From_Values)
{
LogOption option = verboseLog | noErrorLog;
EXPECT_TRUE (option.test (verboseLog));
EXPECT_TRUE (option.test (noErrorLog));
}
-TEST (FlagSet, To_String)
+TEST (FlagSetTests, To_String)
{
EXPECT_EQ (juce::String ("00"), defaultLog.toString());
EXPECT_EQ (juce::String ("10"), verboseLog.toString());
@@ -75,7 +75,7 @@ TEST (FlagSet, To_String)
EXPECT_EQ (juce::String ("11"), option.toString());
}
-TEST (FlagSet, From_String)
+TEST (FlagSetTests, From_String)
{
EXPECT_EQ (defaultLog, LogOption::fromString ("00"));
EXPECT_EQ (verboseLog, LogOption::fromString ("10"));
diff --git a/tests/juce_core/juce_Identifier.cpp b/tests/juce_core/juce_Identifier.cpp
index 712c38966..61bbdf05f 100644
--- a/tests/juce_core/juce_Identifier.cpp
+++ b/tests/juce_core/juce_Identifier.cpp
@@ -27,21 +27,21 @@
using namespace juce;
-TEST (Identifier, DefaultConstructorCreatesNullIdentifier)
+TEST (IdentifierTests, DefaultConstructorCreatesNullIdentifier)
{
Identifier id;
EXPECT_TRUE (id.isNull());
EXPECT_FALSE (id.isValid());
}
-TEST (Identifier, ConstructFromStringLiteral)
+TEST (IdentifierTests, ConstructFromStringLiteral)
{
Identifier id ("test");
EXPECT_EQ (id.toString(), "test");
EXPECT_TRUE (id.isValid());
}
-TEST (Identifier, ConstructFromStringObject)
+TEST (IdentifierTests, ConstructFromStringObject)
{
String name = "example";
Identifier id (name);
@@ -49,28 +49,28 @@ TEST (Identifier, ConstructFromStringObject)
EXPECT_TRUE (id.isValid());
}
-TEST (Identifier, CopyConstructor)
+TEST (IdentifierTests, CopyConstructor)
{
Identifier original ("copyTest");
Identifier copy = original;
EXPECT_EQ (copy, original);
}
-TEST (Identifier, MoveConstructor)
+TEST (IdentifierTests, MoveConstructor)
{
Identifier original ("moveTest");
Identifier moved = std::move (original);
EXPECT_EQ (moved.toString(), "moveTest");
}
-TEST (Identifier, AssignmentOperator)
+TEST (IdentifierTests, AssignmentOperator)
{
Identifier id1 ("first");
Identifier id2 = id1;
EXPECT_EQ (id2, id1);
}
-TEST (Identifier, MoveAssignmentOperator)
+TEST (IdentifierTests, MoveAssignmentOperator)
{
Identifier id1 ("first");
Identifier id2 ("second");
@@ -78,7 +78,7 @@ TEST (Identifier, MoveAssignmentOperator)
EXPECT_EQ (id2.toString(), "first");
}
-TEST (Identifier, ComparisonOperators)
+TEST (IdentifierTests, ComparisonOperators)
{
Identifier id1 ("same");
Identifier id2 ("same");
@@ -90,7 +90,7 @@ TEST (Identifier, ComparisonOperators)
EXPECT_FALSE (id1 != id2);
}
-TEST (Identifier, IsValidIdentifier)
+TEST (IdentifierTests, IsValidIdentifier)
{
EXPECT_TRUE (Identifier::isValidIdentifier ("valid_name"));
EXPECT_FALSE (Identifier::isValidIdentifier ("invalid name"));
@@ -99,14 +99,14 @@ TEST (Identifier, IsValidIdentifier)
EXPECT_FALSE (Identifier::isValidIdentifier ("_1 23"));
}
-TEST (Identifier, ConversionToStringRef)
+TEST (IdentifierTests, ConversionToStringRef)
{
Identifier id ("conversion");
StringRef ref = id;
EXPECT_EQ (ref, StringRef ("conversion"));
}
-TEST (Identifier, ConversionToCharPointer)
+TEST (IdentifierTests, ConversionToCharPointer)
{
Identifier id ("pointer");
auto ptr = id.getCharPointer();
diff --git a/tests/juce_core/juce_LinkedListPointer.cpp b/tests/juce_core/juce_LinkedListPointer.cpp
index 4222bf0b6..d259a3eb6 100644
--- a/tests/juce_core/juce_LinkedListPointer.cpp
+++ b/tests/juce_core/juce_LinkedListPointer.cpp
@@ -36,13 +36,13 @@ struct TestObject
}
};
-TEST (LinkedListPointer, DefaultConstructor)
+TEST (LinkedListPointerTests, DefaultConstructor)
{
LinkedListPointer list;
EXPECT_EQ (list.get(), nullptr);
}
-TEST (LinkedListPointer, ConstructorWithHeadItem)
+TEST (LinkedListPointerTests, ConstructorWithHeadItem)
{
auto* obj = new TestObject (1);
LinkedListPointer list (obj);
@@ -50,7 +50,7 @@ TEST (LinkedListPointer, ConstructorWithHeadItem)
delete obj;
}
-TEST (LinkedListPointer, AssignmentOperator)
+TEST (LinkedListPointerTests, AssignmentOperator)
{
auto* obj = new TestObject (1);
LinkedListPointer list;
@@ -59,7 +59,7 @@ TEST (LinkedListPointer, AssignmentOperator)
delete obj;
}
-TEST (LinkedListPointer, MoveConstructor)
+TEST (LinkedListPointerTests, MoveConstructor)
{
auto* obj = new TestObject (1);
LinkedListPointer list1 (obj);
@@ -69,7 +69,7 @@ TEST (LinkedListPointer, MoveConstructor)
delete obj;
}
-TEST (LinkedListPointer, MoveAssignmentOperator)
+TEST (LinkedListPointerTests, MoveAssignmentOperator)
{
auto* obj = new TestObject (1);
LinkedListPointer list1 (obj);
@@ -81,7 +81,7 @@ TEST (LinkedListPointer, MoveAssignmentOperator)
}
/*
-TEST (LinkedListPointer, GetLast)
+TEST (LinkedListPointerTests, GetLast)
{
auto* obj1 = new TestObject(1);
auto* obj2 = new TestObject(2);
@@ -93,7 +93,7 @@ TEST (LinkedListPointer, GetLast)
}
*/
-TEST (LinkedListPointer, Size)
+TEST (LinkedListPointerTests, Size)
{
LinkedListPointer list;
EXPECT_EQ (list.size(), 0);
@@ -108,7 +108,7 @@ TEST (LinkedListPointer, Size)
delete obj2;
}
-TEST (LinkedListPointer, Contains)
+TEST (LinkedListPointerTests, Contains)
{
auto* obj1 = new TestObject (1);
auto* obj2 = new TestObject (2);
@@ -121,7 +121,7 @@ TEST (LinkedListPointer, Contains)
delete obj2;
}
-TEST (LinkedListPointer, InsertNext)
+TEST (LinkedListPointerTests, InsertNext)
{
auto* obj1 = new TestObject (1);
auto* obj2 = new TestObject (2);
@@ -134,7 +134,7 @@ TEST (LinkedListPointer, InsertNext)
delete obj2;
}
-TEST (LinkedListPointer, InsertAtIndex)
+TEST (LinkedListPointerTests, InsertAtIndex)
{
auto* obj1 = new TestObject (1);
auto* obj2 = new TestObject (2);
@@ -151,7 +151,7 @@ TEST (LinkedListPointer, InsertAtIndex)
delete obj3;
}
-TEST (LinkedListPointer, ReplaceNext)
+TEST (LinkedListPointerTests, ReplaceNext)
{
auto* obj1 = new TestObject (1);
auto* obj2 = new TestObject (2);
@@ -163,7 +163,7 @@ TEST (LinkedListPointer, ReplaceNext)
delete obj2;
}
-TEST (LinkedListPointer, RemoveNext)
+TEST (LinkedListPointerTests, RemoveNext)
{
auto* obj1 = new TestObject (1);
auto* obj2 = new TestObject (2);
@@ -176,7 +176,7 @@ TEST (LinkedListPointer, RemoveNext)
delete obj2;
}
-TEST (LinkedListPointer, Remove)
+TEST (LinkedListPointerTests, Remove)
{
auto* obj1 = new TestObject (1);
auto* obj2 = new TestObject (2);
@@ -189,7 +189,7 @@ TEST (LinkedListPointer, Remove)
delete obj2;
}
-TEST (LinkedListPointer, DeleteAll)
+TEST (LinkedListPointerTests, DeleteAll)
{
auto* obj1 = new TestObject (1);
auto* obj2 = new TestObject (2);
@@ -199,7 +199,7 @@ TEST (LinkedListPointer, DeleteAll)
EXPECT_EQ (list.get(), nullptr);
}
-TEST (LinkedListPointer, CopyToArray)
+TEST (LinkedListPointerTests, CopyToArray)
{
auto* obj1 = new TestObject (1);
auto* obj2 = new TestObject (2);
@@ -213,7 +213,7 @@ TEST (LinkedListPointer, CopyToArray)
delete obj2;
}
-TEST (LinkedListPointer, SwapWith)
+TEST (LinkedListPointerTests, SwapWith)
{
auto* obj1 = new TestObject (1);
auto* obj2 = new TestObject (2);
@@ -226,7 +226,7 @@ TEST (LinkedListPointer, SwapWith)
delete obj2;
}
-TEST (LinkedListPointer, Appender)
+TEST (LinkedListPointerTests, Appender)
{
LinkedListPointer list;
LinkedListPointer::Appender appender (list);
@@ -240,7 +240,7 @@ TEST (LinkedListPointer, Appender)
delete obj2;
}
-TEST (LinkedListPointer, FindPointerTo)
+TEST (LinkedListPointerTests, FindPointerTo)
{
auto* obj1 = new TestObject (1);
auto* obj2 = new TestObject (2);
@@ -258,7 +258,7 @@ TEST (LinkedListPointer, FindPointerTo)
}
/*
-TEST (LinkedListPointer, AddCopyOfList)
+TEST (LinkedListPointerTests, AddCopyOfList)
{
auto* obj1 = new TestObject(1);
auto* obj2 = new TestObject(2);
diff --git a/tests/juce_core/juce_ListenerList.cpp b/tests/juce_core/juce_ListenerList.cpp
index a6bd80629..a2fcb1663 100644
--- a/tests/juce_core/juce_ListenerList.cpp
+++ b/tests/juce_core/juce_ListenerList.cpp
@@ -48,7 +48,81 @@ class MockListener
MOCK_METHOD (void, myCallbackMethod, (int, bool) );
};
-TEST (ListenerList, Add_Remove_Contains)
+class ListenerListTests : public ::testing::Test
+{
+protected:
+ class TestListener
+ {
+ public:
+ explicit TestListener (std::function cb)
+ : callback (std::move (cb))
+ {
+ }
+
+ void doCallback()
+ {
+ ++numCalls;
+ callback();
+ }
+
+ int getNumCalls() const { return numCalls; }
+
+ private:
+ int numCalls = 0;
+ std::function callback;
+ };
+
+ class TestObject
+ {
+ public:
+ void addListener (std::function cb)
+ {
+ listeners.push_back (std::make_unique (std::move (cb)));
+ listenerList.add (listeners.back().get());
+ }
+
+ void removeListener (int i) { listenerList.remove (listeners[(size_t) i].get()); }
+
+ void callListeners()
+ {
+ ++callLevel;
+ listenerList.call ([] (auto& l)
+ {
+ l.doCallback();
+ });
+ --callLevel;
+ }
+
+ int getNumListeners() const { return (int) listeners.size(); }
+
+ auto& getListener (int i) { return *listeners[(size_t) i]; }
+
+ int getCallLevel() const { return callLevel; }
+
+ bool wereAllNonRemovedListenersCalled (int numCalls) const
+ {
+ return std::all_of (std::begin (listeners), std::end (listeners), [&] (auto& listener)
+ {
+ return (! listenerList.contains (listener.get())) || listener->getNumCalls() == numCalls;
+ });
+ }
+
+ private:
+ std::vector> listeners;
+ juce::ListenerList listenerList;
+ int callLevel = 0;
+ };
+
+ static std::set chooseUnique (juce::Random& random, int max, int numChosen)
+ {
+ std::set result;
+ while ((int) result.size() < numChosen)
+ result.insert (random.nextInt ({ 0, max }));
+ return result;
+ }
+};
+
+TEST_F (ListenerListTests, Add_Remove_Contains)
{
juce::ListenerList listeners;
MockListener listener1, listener2;
@@ -67,7 +141,7 @@ TEST (ListenerList, Add_Remove_Contains)
EXPECT_FALSE (listeners.contains (&listener2));
}
-TEST (ListenerList, Call)
+TEST_F (ListenerListTests, Call)
{
juce::ListenerList listeners;
MockListener listener1, listener2;
@@ -81,7 +155,7 @@ TEST (ListenerList, Call)
listeners.call (&MockListener::myCallbackMethod, 1234, true);
}
-TEST (ListenerList, Call_Excluding)
+TEST_F (ListenerListTests, Call_Excluding)
{
juce::ListenerList listeners;
MockListener listener1, listener2;
@@ -95,7 +169,7 @@ TEST (ListenerList, Call_Excluding)
listeners.callExcluding (&listener2, &MockListener::myCallbackMethod, 1234, true);
}
-TEST (ListenerList, Call_Checked)
+TEST_F (ListenerListTests, Call_Checked)
{
struct BailOutChecker
{
@@ -119,7 +193,7 @@ TEST (ListenerList, Call_Checked)
listeners.callChecked (checker, &MockListener::myCallbackMethod, 1234, true);
}
-TEST (ListenerList, Call_Checked_Excluding)
+TEST_F (ListenerListTests, Call_Checked_Excluding)
{
struct BailOutChecker
{
@@ -143,7 +217,7 @@ TEST (ListenerList, Call_Checked_Excluding)
listeners.callCheckedExcluding (&listener2, checker, &MockListener::myCallbackMethod, 1234, true);
}
-TEST (ListenerList, Add_Scoped)
+TEST_F (ListenerListTests, Add_Scoped)
{
juce::ListenerList listeners;
MockListener listener1;
@@ -156,7 +230,7 @@ TEST (ListenerList, Add_Scoped)
EXPECT_FALSE (listeners.contains (&listener1));
}
-TEST (ListenerList, Size_Is_Empty)
+TEST_F (ListenerListTests, Size_Is_Empty)
{
juce::ListenerList listeners;
MockListener listener1, listener2;
@@ -178,7 +252,7 @@ TEST (ListenerList, Size_Is_Empty)
}
/*
-TEST (ListenerList, Null_Pointer_Handling)
+TEST_F (ListenerListTests, Null_Pointer_Handling)
{
juce::ListenerList listeners;
MockListener* nullListener = nullptr;
@@ -191,7 +265,7 @@ TEST (ListenerList, Null_Pointer_Handling)
}
*/
-TEST (ListenerList, Multiple_Add_Remove)
+TEST_F (ListenerListTests, Multiple_Add_Remove)
{
juce::ListenerList listeners;
MockListener listener1;
@@ -206,7 +280,7 @@ TEST (ListenerList, Multiple_Add_Remove)
EXPECT_EQ (listeners.size(), 0);
}
-TEST (ListenerList, Call_During_Callback)
+TEST_F (ListenerListTests, Call_During_Callback)
{
juce::ListenerList listeners;
MockListener listener1, listener2;
@@ -225,7 +299,7 @@ TEST (ListenerList, Call_During_Callback)
EXPECT_EQ (listeners.size(), 2);
}
-TEST (ListenerList, Remove_During_Callback)
+TEST_F (ListenerListTests, Remove_During_Callback)
{
juce::ListenerList listeners;
MockListener listener1, listener2;
@@ -246,7 +320,7 @@ TEST (ListenerList, Remove_During_Callback)
EXPECT_FALSE (listeners.contains (&listener2));
}
-TEST (ListenerList, Clear_During_Callback)
+TEST_F (ListenerListTests, Clear_During_Callback)
{
juce::ListenerList listeners;
MockListener listener1, listener2;
@@ -264,7 +338,7 @@ TEST (ListenerList, Clear_During_Callback)
EXPECT_EQ (listeners.size(), 0);
}
-TEST (ListenerList, Nested_Call)
+TEST_F (ListenerListTests, Nested_Call)
{
juce::ListenerList listeners;
MockListener listener1, listener2;
@@ -283,80 +357,6 @@ TEST (ListenerList, Nested_Call)
listeners.call (&MockListener::myCallbackMethod, 1234, true);
}
-class ListenerListTests : public ::testing::Test
-{
-protected:
- class TestListener
- {
- public:
- explicit TestListener (std::function cb)
- : callback (std::move (cb))
- {
- }
-
- void doCallback()
- {
- ++numCalls;
- callback();
- }
-
- int getNumCalls() const { return numCalls; }
-
- private:
- int numCalls = 0;
- std::function callback;
- };
-
- class TestObject
- {
- public:
- void addListener (std::function cb)
- {
- listeners.push_back (std::make_unique (std::move (cb)));
- listenerList.add (listeners.back().get());
- }
-
- void removeListener (int i) { listenerList.remove (listeners[(size_t) i].get()); }
-
- void callListeners()
- {
- ++callLevel;
- listenerList.call ([] (auto& l)
- {
- l.doCallback();
- });
- --callLevel;
- }
-
- int getNumListeners() const { return (int) listeners.size(); }
-
- auto& getListener (int i) { return *listeners[(size_t) i]; }
-
- int getCallLevel() const { return callLevel; }
-
- bool wereAllNonRemovedListenersCalled (int numCalls) const
- {
- return std::all_of (std::begin (listeners), std::end (listeners), [&] (auto& listener)
- {
- return (! listenerList.contains (listener.get())) || listener->getNumCalls() == numCalls;
- });
- }
-
- private:
- std::vector> listeners;
- juce::ListenerList listenerList;
- int callLevel = 0;
- };
-
- static std::set chooseUnique (juce::Random& random, int max, int numChosen)
- {
- std::set result;
- while ((int) result.size() < numChosen)
- result.insert (random.nextInt ({ 0, max }));
- return result;
- }
-};
-
TEST_F (ListenerListTests, RemovingAlreadyCalledListener)
{
TestObject test;
diff --git a/tests/juce_core/juce_MetaProgramming.cpp b/tests/juce_core/juce_MetaProgramming.cpp
new file mode 100644
index 000000000..16fad86bf
--- /dev/null
+++ b/tests/juce_core/juce_MetaProgramming.cpp
@@ -0,0 +1,94 @@
+/*
+ ==============================================================================
+
+ This file is part of the YUP library.
+ Copyright (c) 2024 - kunitoki@gmail.com
+
+ YUP is an open source library subject to open-source licensing.
+
+ The code included in this file is provided under the terms of the ISC license
+ http://www.isc.org/downloads/software-support-policy/isc-license. Permission
+ to use, copy, modify, and/or distribute this software for any purpose with or
+ without fee is hereby granted provided that the above copyright notice and
+ this permission notice appear in all copies.
+
+ YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
+ DISCLAIMED.
+
+ ==============================================================================
+*/
+
+#include
+
+#include
+
+#include
+
+using namespace juce;
+
+namespace {
+
+struct HaveIt
+{
+ bool existingMethod(int, float);
+ bool existingMethod2(int, float, int);
+
+ std::string field;
+};
+
+struct DontHaveIt
+{
+ uint32_t field;
+};
+
+template
+using hasExistingMethod = decltype(&T::existingMethod);
+
+template
+using hasExistingMethod2 = decltype(&T::existingMethod2);
+
+template
+using hasField = decltype(T::field);
+
+} // namespace
+
+TEST (MetaProgrammingTests, DependentBoolValue)
+{
+ static_assert (dependentBoolValue);
+ static_assert (! dependentBoolValue);
+ static_assert (! dependentFalse<>);
+}
+
+TEST (MetaProgrammingTests, IsDetected)
+{
+ static_assert (isDetected);
+ static_assert (! isDetected);
+}
+
+TEST (MetaProgrammingTests, IsDetectedExact)
+{
+ static_assert (isDetectedExact);
+ static_assert (! isDetectedExact);
+ static_assert (! isDetectedExact);
+}
+
+TEST (MetaProgrammingTests, IsDetectedConvertible)
+{
+ static_assert (isDetectedConvertible);
+ static_assert (! isDetectedConvertible);
+ static_assert (! isDetectedConvertible);
+ static_assert (isDetectedConvertible);
+}
+
+TEST (MetaProgrammingTests, DetectedType)
+{
+ static_assert (std::is_same_v, decltype(&HaveIt::existingMethod)>);
+ static_assert (std::is_same_v, yup::NoneSuch>);
+}
+
+TEST (MetaProgrammingTests, DetectedOr)
+{
+ static_assert (std::is_same_v, decltype(&HaveIt::existingMethod)>);
+ static_assert (std::is_same_v, int>);
+}
diff --git a/tests/juce_core/juce_PlatformDefs.cpp b/tests/juce_core/juce_PlatformDefs.cpp
index f19608727..8c55ae6aa 100644
--- a/tests/juce_core/juce_PlatformDefs.cpp
+++ b/tests/juce_core/juce_PlatformDefs.cpp
@@ -27,14 +27,14 @@
using namespace juce;
-TEST (PlatformDefs, Stringify)
+TEST (PlatformDefsTests, Stringify)
{
constexpr auto x = std::string_view (JUCE_STRINGIFY (abcdf));
static_assert (x == "abcdf");
}
-TEST (PlatformDefs, IsConstantEvaluated)
+TEST (PlatformDefsTests, IsConstantEvaluated)
{
constexpr auto x = []
{
@@ -50,7 +50,7 @@ TEST (PlatformDefs, IsConstantEvaluated)
EXPECT_EQ (y, 2);
}
-TEST (PlatformDefs, ConstexprJassertfalse)
+TEST (PlatformDefsTests, ConstexprJassertfalse)
{
constexpr auto x = []
{
diff --git a/tests/juce_core/juce_StringPool.cpp b/tests/juce_core/juce_StringPool.cpp
index 0d80f8924..280892b61 100644
--- a/tests/juce_core/juce_StringPool.cpp
+++ b/tests/juce_core/juce_StringPool.cpp
@@ -28,7 +28,7 @@
using namespace juce;
-class StringPoolTest : public ::testing::Test
+class StringPoolTests : public ::testing::Test
{
protected:
StringPool pool;
@@ -40,7 +40,7 @@ class StringPoolTest : public ::testing::Test
}
};
-TEST_F (StringPoolTest, ReturnsSameInstanceForDuplicateString)
+TEST_F (StringPoolTests, ReturnsSameInstanceForDuplicateString)
{
String str = "testString";
auto pooled1 = pool.getPooledString (str);
@@ -50,7 +50,7 @@ TEST_F (StringPoolTest, ReturnsSameInstanceForDuplicateString)
EXPECT_EQ (pooled1.getCharPointer().getAddress(), pooled2.getCharPointer().getAddress());
}
-TEST_F (StringPoolTest, ReturnsSameInstanceForDifferentInputTypes)
+TEST_F (StringPoolTests, ReturnsSameInstanceForDifferentInputTypes)
{
const char* cstr = "anotherTest";
String str (cstr);
@@ -64,7 +64,7 @@ TEST_F (StringPoolTest, ReturnsSameInstanceForDifferentInputTypes)
EXPECT_EQ (pooled1.getCharPointer().getAddress(), pooled3.getCharPointer().getAddress());
}
-TEST_F (StringPoolTest, DifferentStringsDifferentInstances)
+TEST_F (StringPoolTests, DifferentStringsDifferentInstances)
{
auto pooled1 = pool.getPooledString ("stringOne");
auto pooled2 = pool.getPooledString ("stringTwo");
@@ -73,7 +73,7 @@ TEST_F (StringPoolTest, DifferentStringsDifferentInstances)
}
/*
-TEST_F (StringPoolTest, GarbageCollectFreesUnreferencedStrings)
+TEST_F (StringPoolTests, GarbageCollectFreesUnreferencedStrings)
{
std::string b{ "temp2" }, c{ "temp3" };
@@ -99,7 +99,7 @@ TEST_F (StringPoolTest, GarbageCollectFreesUnreferencedStrings)
}
*/
-TEST_F (StringPoolTest, DifferentPoolDifferentStrings)
+TEST_F (StringPoolTests, DifferentPoolDifferentStrings)
{
StringPool pool1, pool2;
@@ -109,7 +109,7 @@ TEST_F (StringPoolTest, DifferentPoolDifferentStrings)
EXPECT_NE (pooled1.getCharPointer().getAddress(), pooled2.getCharPointer().getAddress());
}
-TEST_F (StringPoolTest, GlobalPoolSingletonInstance)
+TEST_F (StringPoolTests, GlobalPoolSingletonInstance)
{
auto& globalPool1 = StringPool::getGlobalPool();
auto& globalPool2 = StringPool::getGlobalPool();
diff --git a/tests/juce_core/juce_SystemStats.cpp b/tests/juce_core/juce_SystemStats.cpp
new file mode 100644
index 000000000..0efee65f1
--- /dev/null
+++ b/tests/juce_core/juce_SystemStats.cpp
@@ -0,0 +1,232 @@
+/*
+ ==============================================================================
+
+ This file is part of the YUP library.
+ Copyright (c) 2024 - kunitoki@gmail.com
+
+ YUP is an open source library subject to open-source licensing.
+
+ The code included in this file is provided under the terms of the ISC license
+ http://www.isc.org/downloads/software-support-policy/isc-license. Permission
+ to use, copy, modify, and/or distribute this software for any purpose with or
+ without fee is hereby granted provided that the above copyright notice and
+ this permission notice appear in all copies.
+
+ YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
+ DISCLAIMED.
+
+ ==============================================================================
+*/
+
+#include
+
+#include
+
+using namespace juce;
+
+TEST (SystemStats, JUCEVersion)
+{
+ auto version = SystemStats::getJUCEVersion();
+ EXPECT_FALSE(version.isEmpty());
+}
+
+TEST (SystemStats, OperatingSystemType)
+{
+ auto systemType = SystemStats::getOperatingSystemType();
+
+#if JUCE_WINDOWS
+ EXPECT_TRUE(systemType & SystemStats::OperatingSystemType::Windows);
+#elif JUCE_MAC
+ EXPECT_TRUE(systemType & SystemStats::OperatingSystemType::MacOSX);
+#elif JUCE_LINUX
+ EXPECT_TRUE(systemType & SystemStats::OperatingSystemType::Linux);
+#elif JUCE_ANDROID
+ EXPECT_TRUE(systemType & SystemStats::OperatingSystemType::Android);
+#elif JUCE_IOS
+ EXPECT_TRUE(systemType & SystemStats::OperatingSystemType::iOS);
+#elif JUCE_EMSCRIPTEN
+ EXPECT_TRUE(systemType & SystemStats::OperatingSystemType::WebBrowser);
+#elif JUCE_WASM
+ EXPECT_TRUE(systemType & SystemStats::OperatingSystemType::WASM);
+#else
+ ignoreUnused(systemType);
+#endif
+}
+
+TEST (SystemStatsTests, GetOperatingSystemName)
+{
+ String osName = SystemStats::getOperatingSystemName();
+ EXPECT_FALSE(osName.isEmpty());
+}
+
+TEST (SystemStatsTests, IsOperatingSystem64Bit)
+{
+ bool is64Bit = SystemStats::isOperatingSystem64Bit();
+ if constexpr (sizeof(void*) == 8)
+ EXPECT_TRUE(is64Bit);
+ else
+ EXPECT_FALSE(is64Bit);
+}
+
+TEST (SystemStatsTests, GetEnvironmentVariable)
+{
+ String existingVar = SystemStats::getEnvironmentVariable("PATH", "default");
+ EXPECT_FALSE(existingVar.isEmpty());
+
+ String nonExistingVar = SystemStats::getEnvironmentVariable("NON_EXISTENT_VAR", "default");
+ EXPECT_EQ(nonExistingVar, "default");
+}
+
+TEST (SystemStatsTests, DISABLED_SetAndRemoveEnvironmentVariable)
+{
+ String varName = "JUCE_TEST_ENV_VAR";
+ String varValue = "JUCE_TEST_VALUE";
+
+ bool setResult = SystemStats::setEnvironmentVariable(varName, varValue);
+ EXPECT_TRUE(setResult);
+
+ String fetchedValue = SystemStats::getEnvironmentVariable(varName, "");
+ EXPECT_EQ(fetchedValue, varValue);
+
+ bool removeResult = SystemStats::removeEnvironmentVariable(varName);
+ EXPECT_TRUE(removeResult);
+
+ String afterRemoval = SystemStats::getEnvironmentVariable(varName, "");
+ EXPECT_EQ(afterRemoval, "");
+}
+
+TEST (SystemStatsTests, GetEnvironmentVariables)
+{
+ auto envVars = SystemStats::getEnvironmentVariables();
+ EXPECT_GT(envVars.size(), 0);
+}
+
+TEST (SystemStatsTests, DISABLED_UserAndComputerInfo)
+{
+ String logonName = SystemStats::getLogonName();
+ EXPECT_FALSE(logonName.isEmpty());
+
+ String fullUserName = SystemStats::getFullUserName();
+ EXPECT_FALSE(fullUserName.isEmpty());
+
+ String computerName = SystemStats::getComputerName();
+ EXPECT_FALSE(computerName.isEmpty());
+}
+
+TEST (SystemStatsTests, DISABLED_LocaleInfo)
+{
+ String userLanguage = SystemStats::getUserLanguage();
+ EXPECT_FALSE(userLanguage.isEmpty());
+ EXPECT_GE(userLanguage.length(), 2);
+
+ String userRegion = SystemStats::getUserRegion();
+ EXPECT_FALSE(userRegion.isEmpty());
+ EXPECT_GT(userRegion.length(), 0);
+
+ String displayLanguage = SystemStats::getDisplayLanguage();
+ EXPECT_FALSE(displayLanguage.isEmpty());
+ EXPECT_GE(displayLanguage.length(), 2);
+}
+
+TEST (SystemStatsTests, DISABLED_DeviceInfo)
+{
+ String deviceDescription = SystemStats::getDeviceDescription();
+ EXPECT_TRUE(deviceDescription.isNotEmpty());
+
+#if !JUCE_WASM
+ String deviceManufacturer = SystemStats::getDeviceManufacturer();
+ EXPECT_TRUE(deviceManufacturer.isNotEmpty());
+#endif
+}
+
+TEST (SystemStatsTests, GetUniqueDeviceID)
+{
+ String deviceID = SystemStats::getUniqueDeviceID();
+ EXPECT_TRUE(deviceID.isNotEmpty());
+
+ String deviceID2 = SystemStats::getUniqueDeviceID();
+ EXPECT_EQ(deviceID, deviceID2);
+}
+
+TEST (SystemStatsTests, GetMachineIdentifiers)
+{
+ auto identifiers = SystemStats::getMachineIdentifiers(SystemStats::MachineIdFlags::uniqueId);
+ EXPECT_FALSE(identifiers.isEmpty());
+}
+
+TEST (SystemStatsTests, CpuInfo)
+{
+ int numCpus = SystemStats::getNumCpus();
+ EXPECT_GT(numCpus, 0);
+
+ int numPhysicalCpus = SystemStats::getNumPhysicalCpus();
+ EXPECT_GT(numPhysicalCpus, 0);
+
+#if !JUCE_WASM
+ int cpuSpeed = SystemStats::getCpuSpeedInMegahertz();
+ EXPECT_GT(cpuSpeed, 0);
+
+ String cpuVendor = SystemStats::getCpuVendor();
+ EXPECT_TRUE(cpuVendor.isNotEmpty());
+
+ String cpuModel = SystemStats::getCpuModel();
+ EXPECT_TRUE(cpuModel.isNotEmpty());
+#endif
+}
+
+TEST (SystemStatsTests, CpuFeatures)
+{
+ EXPECT_NO_THROW(SystemStats::hasMMX());
+ EXPECT_NO_THROW(SystemStats::hasSSE());
+ EXPECT_NO_THROW(SystemStats::hasSSE2());
+ EXPECT_NO_THROW(SystemStats::hasSSE3());
+ EXPECT_NO_THROW(SystemStats::hasSSSE3());
+ EXPECT_NO_THROW(SystemStats::hasSSE41());
+ EXPECT_NO_THROW(SystemStats::hasSSE42());
+ EXPECT_NO_THROW(SystemStats::hasAVX());
+ EXPECT_NO_THROW(SystemStats::hasAVX2());
+ EXPECT_NO_THROW(SystemStats::hasNeon());
+}
+
+TEST (SystemStatsTests, MemoryInfo)
+{
+#if !JUCE_WASM
+ int memorySize = SystemStats::getMemorySizeInMegabytes();
+ EXPECT_GT(memorySize, 0);
+#endif
+
+ int pageSize = SystemStats::getPageSize();
+ EXPECT_GT(pageSize, 0);
+}
+
+TEST (SystemStatsTests, GetStackBacktrace)
+{
+#if !JUCE_WASM
+ String backtrace = SystemStats::getStackBacktrace();
+ EXPECT_TRUE(backtrace.isNotEmpty());
+#endif
+}
+
+TEST (SystemStatsTests, SetApplicationCrashHandler)
+{
+ static bool handlerCalled = false;
+ auto crashHandler = [](void*) { handlerCalled = true; };
+
+ SystemStats::setApplicationCrashHandler(crashHandler);
+ EXPECT_NO_THROW(SystemStats::setApplicationCrashHandler(crashHandler));
+}
+
+TEST (SystemStatsTests, IsRunningInAppExtensionSandbox)
+{
+ bool isSandboxed = SystemStats::isRunningInAppExtensionSandbox();
+ EXPECT_TRUE(isSandboxed == true || isSandboxed == false);
+}
+
+#if JUCE_MAC
+TEST (SystemStatsTests, IsAppSandboxEnabled)
+{
+ bool isSandboxEnabled = SystemStats::isAppSandboxEnabled();
+ EXPECT_TRUE(isSandboxEnabled == true || isSandboxEnabled == false);
+}
+#endif
diff --git a/tests/juce_core/juce_Time.cpp b/tests/juce_core/juce_Time.cpp
index 3de476f3a..2038491d3 100644
--- a/tests/juce_core/juce_Time.cpp
+++ b/tests/juce_core/juce_Time.cpp
@@ -38,8 +38,7 @@ TEST (TimeTests, MillisecondsConstructor)
EXPECT_EQ (time.toMilliseconds(), millis);
}
-/*
-TEST (TimeTests, DateComponentsConstructorUTC)
+TEST (TimeTests, DISABLED_DateComponentsConstructorUTC)
{
Time time(2022, 11, 1, 19, 50, 50, 111, false);
EXPECT_EQ(time.getYear(), 2022);
@@ -50,7 +49,6 @@ TEST (TimeTests, DateComponentsConstructorUTC)
EXPECT_EQ(time.getSeconds(), 50);
EXPECT_EQ(time.getMilliseconds(), 111);
}
-*/
TEST (TimeTests, DateComponentsConstructorLocalTime)
{
@@ -114,13 +112,11 @@ TEST (TimeTests, GetWeekdayName)
EXPECT_EQ (time.getWeekdayName (true), "Tue");
}
-/*
-TEST (TimeTests, GetHours)
+TEST (TimeTests, DISABLED_GetHours)
{
Time time(1625000000000);
EXPECT_EQ(time.getHours(), 22); // 10 PM UTC
}
-*/
TEST (TimeTests, IsAfternoon)
{
@@ -130,13 +126,11 @@ TEST (TimeTests, IsAfternoon)
EXPECT_FALSE (afternoon.isAfternoon());
}
-/*
-TEST (TimeTests, GetHoursInAmPmFormat)
+TEST (TimeTests, DISABLED_GetHoursInAmPmFormat)
{
Time time(1625000000000);
EXPECT_EQ(time.getHoursInAmPmFormat(), 10); // 10 AM
}
-*/
TEST (TimeTests, GetMinutes)
{
@@ -156,13 +150,11 @@ TEST (TimeTests, GetMilliseconds)
EXPECT_EQ (time.getMilliseconds(), 123);
}
-/*
-TEST (TimeTests, IsDaylightSavingTime)
+TEST (TimeTests, DISABLED_IsDaylightSavingTime)
{
Time time(1625000000000);
EXPECT_FALSE(time.isDaylightSavingTime());
}
-*/
TEST (TimeTests, GetTimeZone)
{
@@ -170,13 +162,11 @@ TEST (TimeTests, GetTimeZone)
EXPECT_FALSE (time.getTimeZone().isEmpty());
}
-/*
-TEST (TimeTests, GetUTCOffsetSeconds)
+TEST (TimeTests, DISABLED_GetUTCOffsetSeconds)
{
Time time(1625000000000);
EXPECT_NE(time.getUTCOffsetSeconds(), 0);
}
-*/
TEST (TimeTests, GetUTCOffsetString)
{
@@ -192,7 +182,7 @@ TEST (TimeTests, ToString)
}
/*
-TEST (TimeTests, Formatted)
+TEST (TimeTests, DISABLED_Formatted) // WASM undefined symbol: wcsftime
{
Time time(1625000000000);
EXPECT_EQ(time.formatted("%Y-%m-%d %H:%M:%S"), "2021-06-29 22:53:20");
@@ -248,7 +238,7 @@ TEST (TimeTests, ComparisonOperators)
TEST (TimeTests, GetMillisecondCounter)
{
uint32 millis1 = Time::getMillisecondCounter();
- EXPECT_GT (millis1, 0);
+ EXPECT_GE (millis1, 0);
Time::waitForMillisecondCounter (millis1 + 100);
uint32 millis2 = Time::getMillisecondCounter();
EXPECT_GT (millis2, millis1);
@@ -257,7 +247,7 @@ TEST (TimeTests, GetMillisecondCounter)
TEST (TimeTests, GetMillisecondCounterHiRes)
{
double hiResMillis1 = Time::getMillisecondCounterHiRes();
- EXPECT_GT (hiResMillis1, 0.0);
+ EXPECT_GE (hiResMillis1, 0.0);
Time::waitForMillisecondCounter (static_cast (hiResMillis1) + 100);
double hiResMillis2 = Time::getMillisecondCounterHiRes();
EXPECT_GT (hiResMillis2, hiResMillis1);
@@ -266,7 +256,10 @@ TEST (TimeTests, GetMillisecondCounterHiRes)
TEST (TimeTests, GetApproximateMillisecondCounter)
{
uint32 approxMillis1 = Time::getApproximateMillisecondCounter();
- EXPECT_GT (approxMillis1, 0);
+ EXPECT_GE (approxMillis1, 0);
+ Time::waitForMillisecondCounter (approxMillis1 + 100);
+ uint32 approxMillis2 = Time::getApproximateMillisecondCounter();
+ EXPECT_GT (approxMillis2, approxMillis1);
}
TEST (TimeTests, GetHighResolutionTicks)
@@ -301,9 +294,9 @@ TEST (TimeTests, GetCompilationDate)
EXPECT_GT (compilationDate.toMilliseconds(), 0);
}
-TEST (TimeTests, SetSystemTimeToThisTime)
+TEST (TimeTests, DISABLED_SetSystemTimeToThisTime)
{
Time now = Time::getCurrentTime();
// This test may fail if the system does not have sufficient privileges
- // EXPECT_TRUE(now.setSystemTimeToThisTime());
+ EXPECT_TRUE (now.setSystemTimeToThisTime());
}
diff --git a/thirdparty/rive_renderer/rive_renderer.cpp b/thirdparty/rive_renderer/rive_renderer.cpp
index 832f75785..a9cda5548 100644
--- a/thirdparty/rive_renderer/rive_renderer.cpp
+++ b/thirdparty/rive_renderer/rive_renderer.cpp
@@ -25,6 +25,9 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
#pragma clang diagnostic ignored "-Wattributes"
+#elif __GNUC__
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wattributes"
#endif
#include "source/draw.cpp"
@@ -43,4 +46,6 @@
#if __clang__
#pragma clang diagnostic pop
+#elif __GNUC__
+ #pragma GCC diagnostic pop
#endif
diff --git a/thirdparty/rive_renderer/rive_renderer_linux.cpp b/thirdparty/rive_renderer/rive_renderer_linux.cpp
index 2ae90a57b..436959a9d 100644
--- a/thirdparty/rive_renderer/rive_renderer_linux.cpp
+++ b/thirdparty/rive_renderer/rive_renderer_linux.cpp
@@ -25,6 +25,9 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
#pragma clang diagnostic ignored "-Wattributes"
+#elif __GNUC__
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wattributes"
#endif
#include "source/gl/gl_state.cpp"
@@ -40,4 +43,6 @@
#if __clang__
#pragma clang diagnostic pop
+#elif __GNUC__
+ #pragma GCC diagnostic pop
#endif