kopia lustrzana https://github.com/jameshball/osci-render
Add support for recording oscilloscope visualiser
rodzic
925f2a7b80
commit
f30ac1823e
|
@ -2,7 +2,7 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<style>
|
||||
<style>
|
||||
body {
|
||||
font-family: Sans-Serif;
|
||||
font-size: 14px;
|
||||
|
@ -73,6 +73,10 @@
|
|||
filter: brightness(50%);
|
||||
}
|
||||
|
||||
#download {
|
||||
background: url(download.svg) no-repeat;
|
||||
}
|
||||
|
||||
#fullscreen {
|
||||
background: url(fullscreen.svg) no-repeat;
|
||||
}
|
||||
|
@ -84,18 +88,19 @@
|
|||
#settings {
|
||||
background: url(cog.svg) no-repeat;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body bgcolor="black" text="white" autocomplete="off" style="margin: 0px;">
|
||||
|
||||
<div id="buttonRow">
|
||||
<div id="buttonRow">
|
||||
<button onClick="toggleRecording()" id="download"/>
|
||||
<button id="fullscreen"/>
|
||||
<button id="popout"/>
|
||||
<button id="settings"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script>
|
||||
var controls=
|
||||
{
|
||||
swapXY : false,
|
||||
|
@ -132,10 +137,38 @@
|
|||
let openInAnotherWindow = false;
|
||||
let externalSampleRate = 96000;
|
||||
let externalBufferSize = 1920;
|
||||
let recording = false;
|
||||
let mediaRecorder = undefined;
|
||||
let downloadCallback = undefined;
|
||||
|
||||
</script>
|
||||
const toggleRecording = () => {
|
||||
recording = !recording;
|
||||
if (recording) {
|
||||
const canvas = document.getElementById("crtCanvas");
|
||||
const data = [];
|
||||
const stream = canvas.captureStream(60);
|
||||
mediaRecorder = new MediaRecorder(stream);
|
||||
mediaRecorder.ondataavailable = (e) => data.push(e.data);
|
||||
mediaRecorder.onstop = (e) => {
|
||||
const div = document.getElementById("buttonRow");
|
||||
var a = document.createElement("a");
|
||||
const video = new Blob(data, { type: "video/webm;codecs=h264" });
|
||||
var reader = new FileReader();
|
||||
reader.readAsDataURL(video);
|
||||
reader.onloadend = function() {
|
||||
var dataUrl = reader.result;
|
||||
var base64 = dataUrl.split(',')[1];
|
||||
downloadCallback(base64);
|
||||
}
|
||||
};
|
||||
mediaRecorder.start();
|
||||
} else {
|
||||
mediaRecorder.stop();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
<script type="module">
|
||||
import * as Juce from "./index.js";
|
||||
|
||||
const fullscreen = document.getElementById('fullscreen');
|
||||
|
@ -201,37 +234,45 @@
|
|||
}
|
||||
});
|
||||
|
||||
window.__JUCE__.backend.addEventListener("toggleRecording", hasChild => {
|
||||
toggleRecording();
|
||||
});
|
||||
|
||||
document.addEventListener("dblclick", function() {
|
||||
toggleFullscreen();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="mainScreen">
|
||||
downloadCallback = (base64) => {
|
||||
Juce.getNativeFunction("downloadVideo")(base64);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div id="mainScreen">
|
||||
<div id="overlay">Paused</div>
|
||||
<canvas id="crtCanvas" width="800" height="800"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script id="vertex" type="x-shader">
|
||||
<script id="vertex" type="x-shader">
|
||||
attribute vec2 vertexPosition;
|
||||
void main()
|
||||
{
|
||||
gl_Position = vec4(vertexPosition, 0.0, 1.0);
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script id="fragment" type="x-shader">
|
||||
<script id="fragment" type="x-shader">
|
||||
precision highp float;
|
||||
uniform vec4 colour;
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = colour;
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<!-- The Gaussian line-drawing code, the next two shaders, is adapted
|
||||
<!-- The Gaussian line-drawing code, the next two shaders, is adapted
|
||||
from woscope by e1ml : https://github.com/m1el/woscope -->
|
||||
|
||||
<script id="gaussianVertex" type="x-shader">
|
||||
<script id="gaussianVertex" type="x-shader">
|
||||
#define EPS 1E-6
|
||||
uniform float uInvert;
|
||||
uniform float uSize;
|
||||
|
@ -298,9 +339,9 @@
|
|||
//seed = mod(sin(seed*seed), 7.0);
|
||||
//if (mod(seed/2.0, 1.0)<0.5) gl_Position = vec4(10.0);
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script id="gaussianFragment" type="x-shader">
|
||||
<script id="gaussianFragment" type="x-shader">
|
||||
#define EPS 1E-6
|
||||
#define TAU 6.283185307179586
|
||||
#define TAUR 2.5066282746310002
|
||||
|
@ -351,9 +392,9 @@
|
|||
gl_FragColor = 2.0 * texture2D(uScreen, vTexCoord) * brightness;
|
||||
gl_FragColor.a = 1.0;
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script id="texturedVertex" type="x-shader">
|
||||
<script id="texturedVertex" type="x-shader">
|
||||
precision highp float;
|
||||
attribute vec2 aPos;
|
||||
varying vec2 vTexCoord;
|
||||
|
@ -362,9 +403,9 @@
|
|||
gl_Position = vec4(aPos, 0.0, 1.0);
|
||||
vTexCoord = (0.5*aPos+0.5);
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script id="texturedVertexWithResize" type="x-shader">
|
||||
<script id="texturedVertexWithResize" type="x-shader">
|
||||
precision highp float;
|
||||
attribute vec2 aPos;
|
||||
varying vec2 vTexCoord;
|
||||
|
@ -374,9 +415,9 @@
|
|||
gl_Position = vec4(aPos, 0.0, 1.0);
|
||||
vTexCoord = (0.5*aPos+0.5)*uResizeForCanvas;
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script id="texturedFragment" type="x-shader">
|
||||
<script id="texturedFragment" type="x-shader">
|
||||
precision highp float;
|
||||
uniform sampler2D uTexture0;
|
||||
varying vec2 vTexCoord;
|
||||
|
@ -385,9 +426,9 @@
|
|||
gl_FragColor = texture2D(uTexture0, vTexCoord);
|
||||
gl_FragColor.a= 1.0;
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script id="blurFragment" type="x-shader">
|
||||
<script id="blurFragment" type="x-shader">
|
||||
precision highp float;
|
||||
uniform sampler2D uTexture0;
|
||||
uniform vec2 uOffset;
|
||||
|
@ -414,9 +455,9 @@
|
|||
sum += texture2D(uTexture0, vTexCoord + uOffset*8.0) * 0.000078;
|
||||
gl_FragColor = sum;
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script id="outputVertex" type="x-shader">
|
||||
<script id="outputVertex" type="x-shader">
|
||||
precision highp float;
|
||||
attribute vec2 aPos;
|
||||
varying vec2 vTexCoord;
|
||||
|
@ -428,9 +469,9 @@
|
|||
vTexCoord = (0.5*aPos+0.5);
|
||||
vTexCoordCanvas = vTexCoord*uResizeForCanvas;
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script id="outputFragment" type="x-shader">
|
||||
<script id="outputFragment" type="x-shader">
|
||||
precision highp float;
|
||||
uniform sampler2D uTexture0; //line
|
||||
uniform sampler2D uTexture1; //tight glow
|
||||
|
@ -448,8 +489,13 @@
|
|||
return vec3(mix(color, gray, factor));
|
||||
}
|
||||
|
||||
void main (void)
|
||||
{
|
||||
/* Gradient noise from Jorge Jimenez's presentation: */
|
||||
/* http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare */
|
||||
float gradientNoise(in vec2 uv) {
|
||||
return fract(52.9829189 * fract(dot(uv, vec2(0.06711056, 0.00583715))));
|
||||
}
|
||||
|
||||
void main (void) {
|
||||
vec4 line = texture2D(uTexture0, vTexCoordCanvas);
|
||||
// r components have grid; g components do not.
|
||||
vec4 screen = texture2D(uTexture3, vTexCoord);
|
||||
|
@ -461,8 +507,9 @@
|
|||
float tlight2 = tlight*tlight*tlight;
|
||||
gl_FragColor.rgb = mix(uColour, vec3(1.0), 0.3+tlight2*tlight2*0.5)*tlight;
|
||||
gl_FragColor.rgb = desaturate(gl_FragColor.rgb, 1.0 - uSaturation);
|
||||
gl_FragColor.rgb += (1.0 / 255.0) * gradientNoise(gl_FragCoord.xy) - (0.5 / 255.0);
|
||||
gl_FragColor.a = 1.0;
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script src="oscilloscope.js" type="module"></script>
|
||||
<script src="oscilloscope.js" type="module"></script>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0,12a12,12 0 1,0 24,0a12,12 0 1,0 -24,0Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 120 B |
|
@ -39,6 +39,12 @@ SosciPluginEditor::SosciPluginEditor(SosciAudioProcessor& p)
|
|||
openVisualiserSettings();
|
||||
};
|
||||
|
||||
addAndMakeVisible(record);
|
||||
record.setPulseAnimation(true);
|
||||
record.onClick = [this] {
|
||||
visualiser.toggleRecording();
|
||||
};
|
||||
|
||||
addAndMakeVisible(visualiser);
|
||||
|
||||
visualiser.openSettings = [this] {
|
||||
|
@ -49,6 +55,10 @@ SosciPluginEditor::SosciPluginEditor(SosciAudioProcessor& p)
|
|||
visualiserSettingsWindow.setVisible(false);
|
||||
};
|
||||
|
||||
visualiser.recordingHalted = [this] {
|
||||
record.setToggleState(false, juce::NotificationType::dontSendNotification);
|
||||
};
|
||||
|
||||
visualiserSettingsWindow.setResizable(false, false);
|
||||
#if JUCE_WINDOWS
|
||||
// if not standalone, use native title bar for compatibility with DAWs
|
||||
|
@ -80,6 +90,7 @@ void SosciPluginEditor::resized() {
|
|||
|
||||
auto topBar = area.removeFromTop(25);
|
||||
settings.setBounds(topBar.removeFromRight(25));
|
||||
record.setBounds(topBar.removeFromRight(25));
|
||||
menuBar.setBounds(topBar);
|
||||
|
||||
visualiser.setBounds(area);
|
||||
|
|
|
@ -39,6 +39,7 @@ public:
|
|||
|
||||
juce::TooltipWindow tooltipWindow{nullptr, 0};
|
||||
|
||||
SvgButton record{"Record", BinaryData::record_2_svg, juce::Colours::red, juce::Colours::red.withAlpha(0.01f)};
|
||||
SvgButton settings{"Settings", BinaryData::cog_svg, juce::Colours::white, juce::Colours::white};
|
||||
|
||||
bool usingNativeMenuBar = false;
|
||||
|
|
|
@ -25,6 +25,8 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame
|
|||
changeSvgColour(doc.get(), colourOn.withBrightness(0.3f));
|
||||
disabledImageOn = juce::Drawable::createFromSVG(*doc);
|
||||
|
||||
path = normalImage->getOutlineAsPath();
|
||||
|
||||
getLookAndFeel().setColour(juce::DrawableButton::backgroundOnColourId, juce::Colours::transparentWhite);
|
||||
|
||||
if (colour != colourOn) {
|
||||
|
@ -37,6 +39,8 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame
|
|||
setToggleState(toggle->getBoolValue(), juce::NotificationType::dontSendNotification);
|
||||
setTooltip(toggle->getDescription());
|
||||
}
|
||||
|
||||
updater.addAnimator(pulse);
|
||||
}
|
||||
|
||||
SvgButton(juce::String name, juce::String svg, juce::Colour colour) : SvgButton(name, svg, colour, colour) {}
|
||||
|
@ -70,6 +74,30 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame
|
|||
setMouseCursor(juce::MouseCursor::NormalCursor);
|
||||
}
|
||||
|
||||
void setPulseAnimation(bool pulseUsed) {
|
||||
this->pulseUsed = pulseUsed;
|
||||
}
|
||||
|
||||
void paintOverChildren(juce::Graphics& g) override {
|
||||
if (pulseUsed && getToggleState()) {
|
||||
g.setColour(juce::Colours::black.withAlpha(colourFade / 1.5f));
|
||||
g.fillPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
void buttonStateChanged() override {
|
||||
juce::DrawableButton::buttonStateChanged();
|
||||
if (pulseUsed && getToggleState() != prevToggleState) {
|
||||
if (getToggleState()) {
|
||||
pulse.start();
|
||||
} else {
|
||||
pulse.complete();
|
||||
colourFade = 1.0;
|
||||
}
|
||||
prevToggleState = getToggleState();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<juce::Drawable> normalImage;
|
||||
std::unique_ptr<juce::Drawable> overImage;
|
||||
|
@ -83,6 +111,21 @@ private:
|
|||
|
||||
BooleanParameter* toggle;
|
||||
|
||||
juce::VBlankAnimatorUpdater updater{this};
|
||||
float colourFade = 0.0;
|
||||
bool pulseUsed = false;
|
||||
bool prevToggleState = false;
|
||||
juce::Path path;
|
||||
juce::Animator pulse = juce::ValueAnimatorBuilder {}
|
||||
.withEasing([] (float t) { return std::sin(3.14159 * t) / 2 + 0.5; })
|
||||
.withDurationMs(500)
|
||||
.runningInfinitely()
|
||||
.withValueChangedCallback([this] (auto value) {
|
||||
colourFade = value;
|
||||
repaint();
|
||||
})
|
||||
.build();
|
||||
|
||||
void changeSvgColour(juce::XmlElement* xml, juce::Colour colour) {
|
||||
forEachXmlChildElement(*xml, xmlnode) {
|
||||
xmlnode->setAttribute("fill", '#' + colour.toDisplayString(false));
|
||||
|
|
|
@ -270,6 +270,9 @@ void VisualiserComponent::paintXY(juce::Graphics& g, juce::Rectangle<float> area
|
|||
}
|
||||
|
||||
void VisualiserComponent::initialiseBrowser() {
|
||||
if (recordingHalted != nullptr) {
|
||||
recordingHalted();
|
||||
}
|
||||
oldBrowser = std::move(browser);
|
||||
if (oldBrowser != nullptr) {
|
||||
removeChildComponent(oldBrowser.get());
|
||||
|
@ -323,6 +326,21 @@ void VisualiserComponent::initialiseBrowser() {
|
|||
.withNativeFunction("isVisualiserOnly", [this](auto& var, auto complete) {
|
||||
complete(visualiserOnly);
|
||||
})
|
||||
.withNativeFunction("downloadVideo", [this](const juce::Array<juce::var>& args, auto complete) {
|
||||
juce::String base64 = args[0].toString();
|
||||
chooser = std::make_unique<juce::FileChooser>("Save video", juce::File::getSpecialLocation(juce::File::SpecialLocationType::userDesktopDirectory).getChildFile("osci-render.webm"), "*.webm");
|
||||
chooser->launchAsync(juce::FileBrowserComponent::saveMode,
|
||||
[base64](const juce::FileChooser& chooser) {
|
||||
juce::File result = chooser.getResult();
|
||||
if (result.getFullPathName().isNotEmpty()) {
|
||||
juce::FileOutputStream stream(result);
|
||||
stream.setPosition(0);
|
||||
stream.truncate();
|
||||
juce::Base64::convertFromBase64(stream, base64);
|
||||
stream.flush();
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
addAndMakeVisible(*browser);
|
||||
|
@ -366,6 +384,13 @@ void VisualiserComponent::handleAsyncUpdate() {
|
|||
}
|
||||
}
|
||||
|
||||
void VisualiserComponent::toggleRecording() {
|
||||
if (oldVisualiser) {
|
||||
return;
|
||||
}
|
||||
browser->emitEventIfBrowserIsVisible("toggleRecording", juce::var());
|
||||
}
|
||||
|
||||
void VisualiserComponent::resized() {
|
||||
if (!oldVisualiser) {
|
||||
browser->setBounds(getLocalBounds());
|
||||
|
@ -390,10 +415,14 @@ void VisualiserComponent::childChanged() {
|
|||
}
|
||||
|
||||
void VisualiserComponent::popoutWindow() {
|
||||
if (recordingHalted != nullptr) {
|
||||
recordingHalted();
|
||||
}
|
||||
auto visualiser = new VisualiserComponent(sampleRateManager, consumerManager, settings, this, oldVisualiser);
|
||||
visualiser->settings.setLookAndFeel(&getLookAndFeel());
|
||||
visualiser->openSettings = openSettings;
|
||||
visualiser->closeSettings = closeSettings;
|
||||
visualiser->recordingHalted = recordingHalted;
|
||||
child = visualiser;
|
||||
childChanged();
|
||||
popOutButton.setVisible(false);
|
||||
|
|
|
@ -42,6 +42,7 @@ public:
|
|||
void setFullScreen(bool fullScreen);
|
||||
void setVisualiserType(bool oldVisualiser);
|
||||
void handleAsyncUpdate() override;
|
||||
void toggleRecording();
|
||||
|
||||
VisualiserComponent* parent = nullptr;
|
||||
VisualiserComponent* child = nullptr;
|
||||
|
@ -49,6 +50,8 @@ public:
|
|||
|
||||
std::atomic<bool> active = true;
|
||||
|
||||
std::function<void()> recordingHalted;
|
||||
|
||||
private:
|
||||
// 60fps
|
||||
const double BUFFER_LENGTH_SECS = 1/60.0;
|
||||
|
@ -120,6 +123,8 @@ private:
|
|||
// keeping this around for memory management reasons
|
||||
std::unique_ptr<juce::WebBrowserComponent> oldBrowser = nullptr;
|
||||
|
||||
std::unique_ptr<juce::FileChooser> chooser;
|
||||
|
||||
void initialiseBrowser();
|
||||
void resetBuffer();
|
||||
void popoutWindow();
|
||||
|
|
|
@ -658,6 +658,7 @@
|
|||
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_animation" path="../JUCE/modules"/>
|
||||
</MODULEPATHS>
|
||||
</LINUX_MAKE>
|
||||
<VS2022 targetFolder="Builds/VisualStudio2022" smallIcon="pSc1mq" bigIcon="pSc1mq">
|
||||
|
@ -681,6 +682,7 @@
|
|||
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_animation" path="../JUCE/modules"/>
|
||||
</MODULEPATHS>
|
||||
</VS2022>
|
||||
<XCODE_MAC targetFolder="Builds/MacOSX" extraLinkerFlags="-Wl,-weak_reference_mismatches,weak"
|
||||
|
@ -705,10 +707,12 @@
|
|||
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_animation" path="../JUCE/modules"/>
|
||||
</MODULEPATHS>
|
||||
</XCODE_MAC>
|
||||
</EXPORTFORMATS>
|
||||
<MODULES>
|
||||
<MODULE id="juce_animation" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
<FILE id="PFc2q2" name="random.svg" compile="0" resource="1" file="Resources/svg/random.svg"/>
|
||||
<FILE id="CE6di2" name="range.svg" compile="0" resource="1" file="Resources/svg/range.svg"/>
|
||||
<FILE id="n79IAy" name="record.svg" compile="0" resource="1" file="Resources/svg/record.svg"/>
|
||||
<FILE id="TWt5MY" name="record_2.svg" compile="0" resource="1" file="Resources/svg/record_2.svg"/>
|
||||
<FILE id="OaqZb1" name="right_arrow.svg" compile="0" resource="1" file="Resources/svg/right_arrow.svg"/>
|
||||
<FILE id="rXjNlx" name="threshold.svg" compile="0" resource="1" file="Resources/svg/threshold.svg"/>
|
||||
<FILE id="rFYmV8" name="timer.svg" compile="0" resource="1" file="Resources/svg/timer.svg"/>
|
||||
|
@ -139,6 +140,7 @@
|
|||
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_animation" path="../../../JUCE/modules"/>
|
||||
</MODULEPATHS>
|
||||
</LINUX_MAKE>
|
||||
<VS2022 targetFolder="Builds/sosci/VisualStudio2022" smallIcon="pSc1mq"
|
||||
|
@ -163,6 +165,7 @@
|
|||
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_animation" path="../../../JUCE/modules"/>
|
||||
</MODULEPATHS>
|
||||
</VS2022>
|
||||
<XCODE_MAC targetFolder="Builds/sosci/MacOSX" extraLinkerFlags="-Wl,-weak_reference_mismatches,weak"
|
||||
|
@ -187,10 +190,12 @@
|
|||
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_animation" path="../../../JUCE/modules"/>
|
||||
</MODULEPATHS>
|
||||
</XCODE_MAC>
|
||||
</EXPORTFORMATS>
|
||||
<MODULES>
|
||||
<MODULE id="juce_animation" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
|
|
Ładowanie…
Reference in New Issue