2024-11-09 19:19:36 +00:00
|
|
|
#include "VisualiserComponent.h"
|
2025-01-06 18:27:23 +00:00
|
|
|
|
2025-04-27 10:36:37 +00:00
|
|
|
#include "../CommonPluginEditor.h"
|
|
|
|
#include "../CommonPluginProcessor.h"
|
|
|
|
#include "../LookAndFeel.h"
|
2024-11-09 19:19:36 +00:00
|
|
|
|
2025-01-05 09:44:55 +00:00
|
|
|
VisualiserComponent::VisualiserComponent(
|
2025-04-27 10:36:37 +00:00
|
|
|
CommonAudioProcessor &processor,
|
|
|
|
CommonPluginEditor &pluginEditor,
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2025-04-27 10:36:37 +00:00
|
|
|
SharedTextureManager &sharedTextureManager,
|
2025-01-05 09:44:55 +00:00
|
|
|
#endif
|
|
|
|
juce::File ffmpegFile,
|
2025-04-27 10:36:37 +00:00
|
|
|
VisualiserSettings &settings,
|
|
|
|
RecordingSettings &recordingSettings,
|
|
|
|
VisualiserComponent *parent,
|
2025-05-08 20:26:03 +00:00
|
|
|
bool visualiserOnly) : VisualiserRenderer(settings.parameters, processor.threadManager),
|
|
|
|
settings(settings),
|
2025-05-06 20:47:39 +00:00
|
|
|
audioProcessor(processor),
|
2025-04-27 10:36:37 +00:00
|
|
|
ffmpegFile(ffmpegFile),
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2025-04-27 10:36:37 +00:00
|
|
|
sharedTextureManager(sharedTextureManager),
|
|
|
|
ffmpegEncoderManager(ffmpegFile),
|
2025-01-05 09:44:55 +00:00
|
|
|
#endif
|
2025-04-27 10:36:37 +00:00
|
|
|
recordingSettings(recordingSettings),
|
|
|
|
visualiserOnly(visualiserOnly),
|
|
|
|
parent(parent),
|
|
|
|
editor(pluginEditor) {
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2025-04-21 17:05:04 +00:00
|
|
|
addAndMakeVisible(editor.ffmpegDownloader);
|
2025-01-04 14:35:10 +00:00
|
|
|
#endif
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-01-06 18:27:23 +00:00
|
|
|
audioProcessor.haltRecording = [this] {
|
2024-11-25 22:02:32 +00:00
|
|
|
setRecording(false);
|
|
|
|
};
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2024-11-09 19:19:36 +00:00
|
|
|
addAndMakeVisible(record);
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2024-12-23 19:37:12 +00:00
|
|
|
record.setTooltip("Toggles recording of the oscilloscope's visuals and audio.");
|
2025-01-04 14:35:10 +00:00
|
|
|
#else
|
|
|
|
record.setTooltip("Toggles recording of the audio.");
|
|
|
|
#endif
|
2024-11-24 23:23:32 +00:00
|
|
|
record.setPulseAnimation(true);
|
2024-11-09 19:19:36 +00:00
|
|
|
record.onClick = [this] {
|
2024-11-25 22:02:32 +00:00
|
|
|
setRecording(record.getToggleState());
|
2024-11-09 19:19:36 +00:00
|
|
|
};
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2024-11-09 19:19:36 +00:00
|
|
|
addAndMakeVisible(stopwatch);
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2024-11-09 19:19:36 +00:00
|
|
|
setMouseCursor(juce::MouseCursor::PointingHandCursor);
|
|
|
|
setWantsKeyboardFocus(true);
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-01-07 18:28:55 +00:00
|
|
|
if (parent == nullptr) {
|
2024-11-09 19:19:36 +00:00
|
|
|
addAndMakeVisible(fullScreenButton);
|
2024-12-23 19:37:12 +00:00
|
|
|
fullScreenButton.setTooltip("Toggles fullscreen mode.");
|
2024-11-09 19:19:36 +00:00
|
|
|
}
|
2025-04-04 16:26:27 +00:00
|
|
|
if (child == nullptr && parent == nullptr) {
|
2024-11-09 19:19:36 +00:00
|
|
|
addAndMakeVisible(popOutButton);
|
2024-12-23 19:37:12 +00:00
|
|
|
popOutButton.setTooltip("Opens the oscilloscope in a new window.");
|
2024-11-09 19:19:36 +00:00
|
|
|
}
|
|
|
|
addAndMakeVisible(settingsButton);
|
2024-12-23 19:37:12 +00:00
|
|
|
settingsButton.setTooltip("Opens the visualiser settings window.");
|
2024-12-30 11:55:45 +00:00
|
|
|
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2024-12-30 11:55:45 +00:00
|
|
|
addAndMakeVisible(sharedTextureButton);
|
|
|
|
sharedTextureButton.setTooltip("Toggles sending the oscilloscope's visuals to a Syphon/Spout receiver.");
|
|
|
|
sharedTextureButton.onClick = [this] {
|
|
|
|
if (sharedTextureSender != nullptr) {
|
2025-04-27 10:36:37 +00:00
|
|
|
openGLContext.executeOnGLThread([this](juce::OpenGLContext &context) { closeSharedTexture(); },
|
|
|
|
false);
|
2024-12-30 11:55:45 +00:00
|
|
|
} else {
|
2025-04-27 10:36:37 +00:00
|
|
|
openGLContext.executeOnGLThread([this](juce::OpenGLContext &context) { initialiseSharedTexture(); },
|
|
|
|
false);
|
2024-12-30 11:55:45 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
#endif
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2024-11-09 19:19:36 +00:00
|
|
|
fullScreenButton.onClick = [this]() {
|
|
|
|
enableFullScreen();
|
|
|
|
};
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2024-11-09 19:19:36 +00:00
|
|
|
settingsButton.onClick = [this]() {
|
2025-01-06 11:17:42 +00:00
|
|
|
if (openSettings != nullptr) {
|
|
|
|
openSettings();
|
|
|
|
}
|
2024-11-09 19:19:36 +00:00
|
|
|
};
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2024-11-09 19:19:36 +00:00
|
|
|
popOutButton.onClick = [this]() {
|
|
|
|
popoutWindow();
|
|
|
|
};
|
2024-10-22 08:03:19 +00:00
|
|
|
|
2025-01-10 16:51:11 +00:00
|
|
|
if (visualiserOnly && juce::JUCEApplication::isStandaloneApp()) {
|
|
|
|
addAndMakeVisible(audioInputButton);
|
|
|
|
audioInputButton.setTooltip("Appears red when audio input is being used. Click to enable audio input and close any open audio files.");
|
|
|
|
audioInputButton.setClickingTogglesState(false);
|
|
|
|
audioInputButton.setToggleState(!audioPlayer.isInitialised(), juce::NotificationType::dontSendNotification);
|
|
|
|
audioPlayer.onParserChanged = [this] {
|
2025-04-27 10:36:37 +00:00
|
|
|
juce::MessageManager::callAsync([this] { audioInputButton.setToggleState(!audioPlayer.isInitialised(), juce::NotificationType::dontSendNotification); });
|
2025-01-10 16:51:11 +00:00
|
|
|
};
|
|
|
|
audioInputButton.onClick = [this] {
|
|
|
|
audioProcessor.stopAudioFile();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2025-04-25 18:04:15 +00:00
|
|
|
addChildComponent(audioPlayer);
|
|
|
|
audioPlayer.setVisible(visualiserOnly);
|
2025-04-27 10:36:37 +00:00
|
|
|
audioPlayer.addMouseListener(static_cast<juce::Component *>(this), true);
|
2025-01-11 21:15:12 +00:00
|
|
|
|
2025-05-06 20:47:39 +00:00
|
|
|
preRenderCallback = [this] {
|
|
|
|
if (!record.getToggleState()) {
|
|
|
|
setResolution(this->recordingSettings.getResolution());
|
|
|
|
setFrameRate(this->recordingSettings.getFrameRate());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
postRenderCallback = [this] {
|
|
|
|
#if OSCI_PREMIUM
|
|
|
|
if (sharedTextureSender != nullptr) {
|
|
|
|
sharedTextureSender->renderGL();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (record.getToggleState()) {
|
|
|
|
#if OSCI_PREMIUM
|
|
|
|
if (recordingVideo) {
|
|
|
|
// draw frame to ffmpeg
|
|
|
|
Texture renderTexture = getRenderTexture();
|
|
|
|
getFrame(framePixels);
|
|
|
|
if (ffmpegProcess.write(framePixels.data(), 4 * renderTexture.width * renderTexture.height, 3000) == 0) {
|
|
|
|
record.setToggleState(false, juce::NotificationType::dontSendNotification);
|
2024-11-13 20:12:26 +00:00
|
|
|
|
2025-05-06 20:47:39 +00:00
|
|
|
juce::MessageManager::callAsync([this] {
|
|
|
|
juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::WarningIcon,
|
|
|
|
"Recording Error",
|
|
|
|
"An error occurred while writing the video frame to the ffmpeg process. Recording has been stopped.",
|
|
|
|
"OK");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if (recordingAudio) {
|
|
|
|
audioRecorder.audioThreadCallback(audioOutputBuffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
stopwatch.addTime(juce::RelativeTime::seconds(1.0 / this->recordingSettings.getFrameRate()));
|
|
|
|
};
|
2024-10-22 08:03:19 +00:00
|
|
|
}
|
|
|
|
|
2024-11-09 19:19:36 +00:00
|
|
|
VisualiserComponent::~VisualiserComponent() {
|
2024-11-25 22:02:32 +00:00
|
|
|
setRecording(false);
|
|
|
|
if (parent == nullptr) {
|
2025-01-06 18:27:23 +00:00
|
|
|
audioProcessor.haltRecording = nullptr;
|
2024-11-25 22:02:32 +00:00
|
|
|
}
|
2024-10-22 12:52:19 +00:00
|
|
|
}
|
|
|
|
|
2025-01-10 21:08:42 +00:00
|
|
|
void VisualiserComponent::setFullScreen(bool fullScreen) {
|
|
|
|
this->fullScreen = fullScreen;
|
|
|
|
hideButtonRow = false;
|
|
|
|
setMouseCursor(juce::MouseCursor::PointingHandCursor);
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-04-26 20:40:15 +00:00
|
|
|
// Release renderingSemaphore to prevent deadlocks during layout changes
|
|
|
|
renderingSemaphore.release();
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-01-10 21:08:42 +00:00
|
|
|
resized();
|
|
|
|
}
|
|
|
|
|
2024-11-09 19:19:36 +00:00
|
|
|
void VisualiserComponent::setFullScreenCallback(std::function<void(FullScreenMode)> callback) {
|
|
|
|
fullScreenCallback = callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VisualiserComponent::enableFullScreen() {
|
|
|
|
if (fullScreenCallback) {
|
|
|
|
fullScreenCallback(FullScreenMode::TOGGLE);
|
|
|
|
}
|
|
|
|
grabKeyboardFocus();
|
|
|
|
}
|
|
|
|
|
2025-05-06 20:47:39 +00:00
|
|
|
void VisualiserComponent::mouseDoubleClick(const juce::MouseEvent &event) {
|
2025-01-10 21:08:42 +00:00
|
|
|
if (event.originalComponent == this) {
|
|
|
|
enableFullScreen();
|
|
|
|
}
|
2024-11-09 19:19:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int VisualiserComponent::prepareTask(double sampleRate, int bufferSize) {
|
2025-05-06 20:47:39 +00:00
|
|
|
int desiredBufferSize = VisualiserRenderer::prepareTask(sampleRate, bufferSize);
|
2024-12-14 14:21:26 +00:00
|
|
|
audioRecorder.setSampleRate(sampleRate);
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2024-11-17 12:18:30 +00:00
|
|
|
return desiredBufferSize;
|
2024-11-09 19:19:36 +00:00
|
|
|
}
|
|
|
|
|
2024-11-28 18:15:01 +00:00
|
|
|
void VisualiserComponent::stopTask() {
|
|
|
|
setRecording(false);
|
2025-05-06 20:47:39 +00:00
|
|
|
VisualiserRenderer::stopTask();
|
2025-01-03 16:37:36 +00:00
|
|
|
}
|
|
|
|
|
2025-04-04 16:26:27 +00:00
|
|
|
void VisualiserComponent::setPaused(bool paused, bool affectAudio) {
|
2024-11-09 19:19:36 +00:00
|
|
|
active = !paused;
|
|
|
|
setShouldBeRunning(active);
|
2025-01-07 17:51:08 +00:00
|
|
|
renderingSemaphore.release();
|
2025-04-04 16:26:27 +00:00
|
|
|
if (affectAudio) {
|
|
|
|
audioPlayer.setPaused(paused);
|
|
|
|
}
|
2024-11-09 19:19:36 +00:00
|
|
|
repaint();
|
|
|
|
}
|
|
|
|
|
2025-04-27 10:36:37 +00:00
|
|
|
void VisualiserComponent::mouseDrag(const juce::MouseEvent &event) {
|
2025-01-10 21:08:42 +00:00
|
|
|
timerId = -1;
|
|
|
|
}
|
|
|
|
|
2025-04-27 10:36:37 +00:00
|
|
|
void VisualiserComponent::mouseMove(const juce::MouseEvent &event) {
|
2025-01-10 21:08:42 +00:00
|
|
|
if (event.getScreenX() == lastMouseX && event.getScreenY() == lastMouseY) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
hideButtonRow = false;
|
|
|
|
setMouseCursor(juce::MouseCursor::PointingHandCursor);
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-04-04 16:26:27 +00:00
|
|
|
// Treat both fullScreen mode and pop-out mode (parent != nullptr) as needing auto-hide controls
|
|
|
|
if (fullScreen || parent != nullptr) {
|
2025-01-10 21:08:42 +00:00
|
|
|
if (!getScreenBounds().removeFromBottom(25).contains(event.getScreenX(), event.getScreenY()) && !event.mods.isLeftButtonDown()) {
|
|
|
|
lastMouseX = event.getScreenX();
|
|
|
|
lastMouseY = event.getScreenY();
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-01-10 21:08:42 +00:00
|
|
|
int newTimerId = juce::Random::getSystemRandom().nextInt();
|
|
|
|
timerId = newTimerId;
|
|
|
|
auto pos = event.getScreenPosition();
|
|
|
|
auto parent = this->parent;
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-01-10 21:08:42 +00:00
|
|
|
juce::WeakReference<VisualiserComponent> weakRef = this;
|
|
|
|
juce::Timer::callAfterDelay(1000, [this, weakRef, newTimerId, pos, parent]() {
|
|
|
|
if (weakRef) {
|
|
|
|
if (parent == nullptr || parent->child == this) {
|
2025-04-04 16:26:27 +00:00
|
|
|
// Check both fullscreen or pop-out mode
|
|
|
|
if (timerId == newTimerId && (fullScreen || this->parent != nullptr)) {
|
2025-01-10 21:08:42 +00:00
|
|
|
hideButtonRow = true;
|
|
|
|
setMouseCursor(juce::MouseCursor::NoCursor);
|
|
|
|
resized();
|
|
|
|
}
|
|
|
|
}
|
2025-04-27 10:36:37 +00:00
|
|
|
} });
|
2025-01-10 21:08:42 +00:00
|
|
|
}
|
|
|
|
resized();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-27 10:36:37 +00:00
|
|
|
void VisualiserComponent::mouseDown(const juce::MouseEvent &event) {
|
2025-01-10 21:08:42 +00:00
|
|
|
if (event.originalComponent == this) {
|
|
|
|
if (event.mods.isLeftButtonDown() && child == nullptr && !record.getToggleState()) {
|
|
|
|
setPaused(active);
|
|
|
|
}
|
2024-11-09 19:19:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-27 10:36:37 +00:00
|
|
|
bool VisualiserComponent::keyPressed(const juce::KeyPress &key) {
|
2024-11-09 19:19:36 +00:00
|
|
|
if (key.isKeyCode(juce::KeyPress::escapeKey)) {
|
|
|
|
if (fullScreenCallback) {
|
|
|
|
fullScreenCallback(FullScreenMode::MAIN_COMPONENT);
|
|
|
|
}
|
|
|
|
return true;
|
2025-01-12 18:21:50 +00:00
|
|
|
} else if (key.isKeyCode(juce::KeyPress::spaceKey)) {
|
|
|
|
setPaused(active);
|
|
|
|
return true;
|
2024-11-09 19:19:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-11-25 22:02:32 +00:00
|
|
|
void VisualiserComponent::setRecording(bool recording) {
|
2024-11-30 21:11:49 +00:00
|
|
|
stopwatch.stop();
|
|
|
|
stopwatch.reset();
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2025-01-04 14:35:10 +00:00
|
|
|
bool stillRecording = ffmpegProcess.isRunning() || audioRecorder.isRecording();
|
|
|
|
#else
|
|
|
|
bool stillRecording = audioRecorder.isRecording();
|
|
|
|
#endif
|
2024-11-30 21:11:49 +00:00
|
|
|
|
2025-04-26 20:40:15 +00:00
|
|
|
// Release renderingSemaphore to prevent deadlock
|
|
|
|
renderingSemaphore.release();
|
|
|
|
|
2024-11-25 22:02:32 +00:00
|
|
|
if (recording) {
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2025-01-06 18:27:23 +00:00
|
|
|
recordingVideo = recordingSettings.recordingVideo();
|
|
|
|
recordingAudio = recordingSettings.recordingAudio();
|
2024-12-15 19:23:31 +00:00
|
|
|
if (!recordingVideo && !recordingAudio) {
|
2024-11-30 21:11:49 +00:00
|
|
|
record.setToggleState(false, juce::NotificationType::dontSendNotification);
|
2024-11-29 21:28:38 +00:00
|
|
|
return;
|
|
|
|
}
|
2024-11-28 18:15:01 +00:00
|
|
|
|
2024-12-15 19:23:31 +00:00
|
|
|
if (recordingVideo) {
|
2025-04-21 17:05:04 +00:00
|
|
|
auto onDownloadSuccess = [this] {
|
|
|
|
juce::MessageManager::callAsync([this] {
|
|
|
|
record.setEnabled(true);
|
|
|
|
juce::Timer::callAfterDelay(3000, [this] {
|
|
|
|
juce::MessageManager::callAsync([this] {
|
|
|
|
editor.ffmpegDownloader.setVisible(false);
|
|
|
|
downloading = false;
|
|
|
|
resized();
|
|
|
|
});
|
2025-04-27 10:36:37 +00:00
|
|
|
}); });
|
2025-04-21 17:05:04 +00:00
|
|
|
};
|
|
|
|
auto onDownloadStart = [this] {
|
|
|
|
juce::MessageManager::callAsync([this] {
|
|
|
|
record.setEnabled(false);
|
|
|
|
downloading = true;
|
2025-04-27 10:36:37 +00:00
|
|
|
resized(); });
|
2025-04-21 17:05:04 +00:00
|
|
|
};
|
|
|
|
if (!audioProcessor.ensureFFmpegExists(onDownloadStart, onDownloadSuccess)) {
|
2024-12-15 19:23:31 +00:00
|
|
|
record.setToggleState(false, juce::NotificationType::dontSendNotification);
|
|
|
|
return;
|
|
|
|
}
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-04-04 11:10:44 +00:00
|
|
|
// Get the appropriate file extension based on codec
|
|
|
|
juce::String fileExtension = recordingSettings.getFileExtensionForCodec();
|
|
|
|
tempVideoFile = std::make_unique<juce::TemporaryFile>("." + fileExtension);
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-04-04 11:10:44 +00:00
|
|
|
VideoCodec codec = recordingSettings.getVideoCodec();
|
2025-04-27 10:36:37 +00:00
|
|
|
juce::String cmd = ffmpegEncoderManager.buildVideoEncodingCommand(
|
|
|
|
codec,
|
|
|
|
recordingSettings.getCRF(),
|
2025-05-06 20:47:39 +00:00
|
|
|
getRenderWidth(),
|
|
|
|
getRenderHeight(),
|
2025-04-27 10:36:37 +00:00
|
|
|
recordingSettings.getFrameRate(),
|
|
|
|
recordingSettings.getCompressionPreset(),
|
|
|
|
tempVideoFile->getFile());
|
2024-12-15 19:23:31 +00:00
|
|
|
|
2025-05-04 14:01:04 +00:00
|
|
|
if (!ffmpegProcess.start(cmd)) {
|
|
|
|
record.setToggleState(false, juce::NotificationType::dontSendNotification);
|
|
|
|
return;
|
|
|
|
}
|
2025-05-06 20:47:39 +00:00
|
|
|
framePixels.resize(getRenderWidth() * getRenderHeight() * 4);
|
2024-12-15 19:23:31 +00:00
|
|
|
}
|
2024-12-14 14:21:26 +00:00
|
|
|
|
2024-12-15 19:23:31 +00:00
|
|
|
if (recordingAudio) {
|
|
|
|
tempAudioFile = std::make_unique<juce::TemporaryFile>(".wav");
|
|
|
|
audioRecorder.startRecording(tempAudioFile->getFile());
|
|
|
|
}
|
2025-01-04 14:35:10 +00:00
|
|
|
#else
|
|
|
|
// audio only recording
|
|
|
|
tempAudioFile = std::make_unique<juce::TemporaryFile>(".wav");
|
|
|
|
audioRecorder.startRecording(tempAudioFile->getFile());
|
|
|
|
#endif
|
2024-12-14 14:21:26 +00:00
|
|
|
|
2024-11-25 22:02:32 +00:00
|
|
|
setPaused(false);
|
2024-11-30 21:11:49 +00:00
|
|
|
stopwatch.start();
|
2025-01-04 14:35:10 +00:00
|
|
|
} else if (stillRecording) {
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2024-12-15 19:23:31 +00:00
|
|
|
bool wasRecordingAudio = recordingAudio;
|
|
|
|
bool wasRecordingVideo = recordingVideo;
|
|
|
|
recordingAudio = false;
|
|
|
|
recordingVideo = false;
|
|
|
|
|
2025-04-04 11:10:44 +00:00
|
|
|
juce::String extension = wasRecordingVideo ? recordingSettings.getFileExtensionForCodec() : "wav";
|
2024-12-15 19:23:31 +00:00
|
|
|
if (wasRecordingAudio) {
|
|
|
|
audioRecorder.stop();
|
|
|
|
}
|
|
|
|
if (wasRecordingVideo) {
|
|
|
|
ffmpegProcess.close();
|
|
|
|
}
|
2025-01-04 14:35:10 +00:00
|
|
|
#else
|
|
|
|
audioRecorder.stop();
|
|
|
|
juce::String extension = "wav";
|
|
|
|
#endif
|
2025-04-21 13:11:11 +00:00
|
|
|
chooser = std::make_unique<juce::FileChooser>("Save recording", audioProcessor.getLastOpenedDirectory(), "*." + extension);
|
2024-12-01 19:04:43 +00:00
|
|
|
auto flags = juce::FileBrowserComponent::saveMode | juce::FileBrowserComponent::canSelectFiles | juce::FileBrowserComponent::warnAboutOverwriting;
|
|
|
|
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2025-04-27 10:36:37 +00:00
|
|
|
chooser->launchAsync(flags, [this, wasRecordingAudio, wasRecordingVideo](const juce::FileChooser &chooser) {
|
2024-12-01 19:04:43 +00:00
|
|
|
auto file = chooser.getResult();
|
|
|
|
if (file != juce::File()) {
|
2024-12-15 19:23:31 +00:00
|
|
|
if (wasRecordingAudio && wasRecordingVideo) {
|
2025-05-06 20:47:39 +00:00
|
|
|
// delete the file if it exists
|
|
|
|
if (file.existsAsFile()) {
|
|
|
|
file.deleteFile();
|
|
|
|
}
|
2025-01-06 08:37:57 +00:00
|
|
|
ffmpegProcess.start("\"" + ffmpegFile.getFullPathName() + "\" -i \"" + tempVideoFile->getFile().getFullPathName() + "\" -i \"" + tempAudioFile->getFile().getFullPathName() + "\" -c:v copy -c:a aac -b:a 384k -y \"" + file.getFullPathName() + "\"");
|
2024-12-15 19:23:31 +00:00
|
|
|
ffmpegProcess.close();
|
|
|
|
} else if (wasRecordingAudio) {
|
|
|
|
tempAudioFile->getFile().copyFileTo(file);
|
|
|
|
} else if (wasRecordingVideo) {
|
|
|
|
tempVideoFile->getFile().copyFileTo(file);
|
|
|
|
}
|
2025-04-21 13:11:11 +00:00
|
|
|
audioProcessor.setLastOpenedDirectory(file.getParentDirectory());
|
2025-04-27 10:36:37 +00:00
|
|
|
} });
|
2025-01-04 14:35:10 +00:00
|
|
|
#else
|
2025-04-27 10:36:37 +00:00
|
|
|
chooser->launchAsync(flags, [this](const juce::FileChooser &chooser) {
|
2025-01-04 14:35:10 +00:00
|
|
|
auto file = chooser.getResult();
|
|
|
|
if (file != juce::File()) {
|
|
|
|
tempAudioFile->getFile().copyFileTo(file);
|
2025-04-21 13:11:11 +00:00
|
|
|
audioProcessor.setLastOpenedDirectory(file.getParentDirectory());
|
2025-04-27 10:36:37 +00:00
|
|
|
} });
|
2025-01-04 14:35:10 +00:00
|
|
|
#endif
|
2024-11-25 22:02:32 +00:00
|
|
|
}
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2024-11-25 22:02:32 +00:00
|
|
|
setBlockOnAudioThread(recording);
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2024-11-28 18:15:01 +00:00
|
|
|
numFrames = 0;
|
2025-01-04 14:35:10 +00:00
|
|
|
#endif
|
2024-11-28 18:15:01 +00:00
|
|
|
record.setToggleState(recording, juce::NotificationType::dontSendNotification);
|
2024-11-30 21:11:49 +00:00
|
|
|
resized();
|
2024-11-09 19:19:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void VisualiserComponent::resized() {
|
|
|
|
auto area = getLocalBounds();
|
2025-04-04 16:26:27 +00:00
|
|
|
// Apply hideButtonRow logic to both fullscreen and pop-out modes
|
|
|
|
if ((fullScreen || parent != nullptr) && hideButtonRow) {
|
2025-01-10 21:08:42 +00:00
|
|
|
buttonRow = area.removeFromBottom(0);
|
|
|
|
} else {
|
|
|
|
buttonRow = area.removeFromBottom(25);
|
|
|
|
}
|
2024-12-26 23:00:52 +00:00
|
|
|
auto buttons = buttonRow;
|
2025-01-07 18:28:55 +00:00
|
|
|
if (parent == nullptr) {
|
2024-12-26 23:00:52 +00:00
|
|
|
fullScreenButton.setBounds(buttons.removeFromRight(30));
|
2024-11-09 19:19:36 +00:00
|
|
|
}
|
2025-04-04 16:26:27 +00:00
|
|
|
if (child == nullptr && parent == nullptr) {
|
2024-12-26 23:00:52 +00:00
|
|
|
popOutButton.setBounds(buttons.removeFromRight(30));
|
2024-11-09 19:19:36 +00:00
|
|
|
}
|
2025-01-06 11:17:42 +00:00
|
|
|
if (openSettings != nullptr) {
|
|
|
|
settingsButton.setVisible(true);
|
|
|
|
settingsButton.setBounds(buttons.removeFromRight(30));
|
|
|
|
} else {
|
|
|
|
settingsButton.setVisible(false);
|
|
|
|
}
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-04-04 16:26:27 +00:00
|
|
|
if (visualiserOnly && juce::JUCEApplication::isStandaloneApp() && child == nullptr) {
|
2025-01-10 16:51:11 +00:00
|
|
|
audioInputButton.setBounds(buttons.removeFromRight(30));
|
|
|
|
}
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2024-12-30 11:55:45 +00:00
|
|
|
sharedTextureButton.setBounds(buttons.removeFromRight(30));
|
|
|
|
#endif
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2024-12-26 23:00:52 +00:00
|
|
|
record.setBounds(buttons.removeFromRight(25));
|
2024-11-09 19:19:36 +00:00
|
|
|
if (record.getToggleState()) {
|
|
|
|
stopwatch.setVisible(true);
|
2024-12-26 23:00:52 +00:00
|
|
|
stopwatch.setBounds(buttons.removeFromRight(100));
|
2024-11-09 19:19:36 +00:00
|
|
|
} else {
|
|
|
|
stopwatch.setVisible(false);
|
|
|
|
}
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2025-01-06 18:27:23 +00:00
|
|
|
if (child == nullptr && downloading) {
|
2024-12-26 23:00:52 +00:00
|
|
|
auto bounds = buttons.removeFromRight(160);
|
2025-04-21 17:05:04 +00:00
|
|
|
editor.ffmpegDownloader.setBounds(bounds.withSizeKeepingCentre(bounds.getWidth() - 10, bounds.getHeight() - 10));
|
2024-11-30 21:11:49 +00:00
|
|
|
}
|
2025-01-04 14:35:10 +00:00
|
|
|
#endif
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-01-06 18:27:23 +00:00
|
|
|
buttons.removeFromRight(10); // padding
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-04-04 16:26:27 +00:00
|
|
|
if (child == nullptr) {
|
|
|
|
audioPlayer.setBounds(buttons);
|
|
|
|
}
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-05-06 20:47:39 +00:00
|
|
|
setViewportArea(area);
|
2024-11-09 19:19:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void VisualiserComponent::popoutWindow() {
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2024-12-23 19:37:12 +00:00
|
|
|
if (sharedTextureButton.getToggleState()) {
|
|
|
|
sharedTextureButton.triggerClick();
|
|
|
|
}
|
2024-12-30 11:55:45 +00:00
|
|
|
#endif
|
2024-11-25 22:02:32 +00:00
|
|
|
setRecording(false);
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-04-26 20:40:15 +00:00
|
|
|
// Release renderingSemaphore to prevent deadlock when creating a child visualizer
|
|
|
|
renderingSemaphore.release();
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2025-01-05 09:44:55 +00:00
|
|
|
auto visualiser = new VisualiserComponent(
|
2025-01-06 18:27:23 +00:00
|
|
|
audioProcessor,
|
2025-04-21 17:05:04 +00:00
|
|
|
editor,
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2025-01-05 09:44:55 +00:00
|
|
|
sharedTextureManager,
|
|
|
|
#endif
|
|
|
|
ffmpegFile,
|
|
|
|
settings,
|
2025-01-06 18:27:23 +00:00
|
|
|
recordingSettings,
|
2025-04-04 16:26:27 +00:00
|
|
|
this,
|
2025-04-27 10:36:37 +00:00
|
|
|
visualiserOnly);
|
2024-11-09 19:19:36 +00:00
|
|
|
visualiser->settings.setLookAndFeel(&getLookAndFeel());
|
|
|
|
visualiser->openSettings = openSettings;
|
|
|
|
visualiser->closeSettings = closeSettings;
|
2025-04-04 16:26:27 +00:00
|
|
|
// Pop-out visualiser is created with parent set to this component
|
2024-11-09 19:19:36 +00:00
|
|
|
child = visualiser;
|
|
|
|
childUpdated();
|
2025-04-04 16:26:27 +00:00
|
|
|
visualiser->setSize(350, 350);
|
2024-11-09 19:19:36 +00:00
|
|
|
popout = std::make_unique<VisualiserWindow>("Software Oscilloscope", this);
|
|
|
|
popout->setContentOwned(visualiser, true);
|
|
|
|
popout->setUsingNativeTitleBar(true);
|
|
|
|
popout->setResizable(true, false);
|
|
|
|
popout->setVisible(true);
|
2025-04-04 16:26:27 +00:00
|
|
|
popout->centreWithSize(350, 350);
|
|
|
|
setPaused(true, false);
|
2024-11-09 19:19:36 +00:00
|
|
|
resized();
|
|
|
|
}
|
|
|
|
|
|
|
|
void VisualiserComponent::childUpdated() {
|
|
|
|
popOutButton.setVisible(child == nullptr);
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2025-04-21 17:05:04 +00:00
|
|
|
editor.ffmpegDownloader.setVisible(child == nullptr);
|
2025-01-04 14:35:10 +00:00
|
|
|
#endif
|
2024-11-30 21:11:49 +00:00
|
|
|
record.setVisible(child == nullptr);
|
2025-04-04 16:26:27 +00:00
|
|
|
audioPlayer.setVisible(child == nullptr);
|
2024-11-25 22:02:32 +00:00
|
|
|
if (child != nullptr) {
|
2025-01-06 18:27:23 +00:00
|
|
|
audioProcessor.haltRecording = [this] {
|
2024-11-25 22:02:32 +00:00
|
|
|
setRecording(false);
|
|
|
|
child->setRecording(false);
|
|
|
|
};
|
|
|
|
} else {
|
2025-04-04 16:26:27 +00:00
|
|
|
audioPlayer.setup();
|
2025-01-06 18:27:23 +00:00
|
|
|
audioProcessor.haltRecording = [this] {
|
2024-11-25 22:02:32 +00:00
|
|
|
setRecording(false);
|
|
|
|
};
|
|
|
|
}
|
2024-11-09 19:19:36 +00:00
|
|
|
}
|
|
|
|
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2024-12-22 19:00:39 +00:00
|
|
|
void VisualiserComponent::initialiseSharedTexture() {
|
2025-05-06 20:47:39 +00:00
|
|
|
Texture renderTexture = getRenderTexture();
|
2025-01-12 20:39:20 +00:00
|
|
|
sharedTextureSender = sharedTextureManager.addSender(recordingSettings.getCustomSharedTextureServerName(), renderTexture.width, renderTexture.height);
|
2024-12-22 22:38:10 +00:00
|
|
|
sharedTextureSender->initGL();
|
|
|
|
sharedTextureSender->setSharedTextureId(renderTexture.id);
|
2025-05-06 20:47:39 +00:00
|
|
|
sharedTextureSender->setDrawFunction([this] { drawFrame(); });
|
2024-12-22 19:00:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void VisualiserComponent::closeSharedTexture() {
|
2024-12-23 19:37:12 +00:00
|
|
|
if (sharedTextureSender != nullptr) {
|
2025-01-05 09:44:55 +00:00
|
|
|
sharedTextureManager.removeSender(sharedTextureSender);
|
2024-12-23 19:37:12 +00:00
|
|
|
sharedTextureSender = nullptr;
|
2024-12-22 19:00:39 +00:00
|
|
|
}
|
|
|
|
}
|
2024-12-30 11:55:45 +00:00
|
|
|
#endif
|
2024-12-22 19:00:39 +00:00
|
|
|
|
2024-11-09 19:19:36 +00:00
|
|
|
void VisualiserComponent::openGLContextClosing() {
|
2025-04-24 10:29:13 +00:00
|
|
|
#if OSCI_PREMIUM
|
2024-12-23 19:37:12 +00:00
|
|
|
closeSharedTexture();
|
2024-12-30 11:55:45 +00:00
|
|
|
#endif
|
2024-12-22 19:00:39 +00:00
|
|
|
|
2025-05-06 20:47:39 +00:00
|
|
|
VisualiserRenderer::openGLContextClosing();
|
2024-10-22 08:03:19 +00:00
|
|
|
}
|
2024-10-23 10:23:58 +00:00
|
|
|
|
2025-04-27 10:36:37 +00:00
|
|
|
void VisualiserComponent::paint(juce::Graphics &g) {
|
2024-12-30 11:55:45 +00:00
|
|
|
g.setColour(Colours::veryDark);
|
2024-11-09 19:19:36 +00:00
|
|
|
g.fillRect(buttonRow);
|
|
|
|
if (!active) {
|
2024-11-30 21:11:49 +00:00
|
|
|
// draw a translucent overlay
|
|
|
|
g.setColour(juce::Colours::black.withAlpha(0.5f));
|
2025-05-06 20:47:39 +00:00
|
|
|
g.fillRect(getViewportArea());
|
2025-04-27 10:36:37 +00:00
|
|
|
|
2024-10-23 10:23:58 +00:00
|
|
|
g.setColour(juce::Colours::white);
|
|
|
|
g.setFont(30.0f);
|
2024-11-09 19:19:36 +00:00
|
|
|
juce::String text = child == nullptr ? "Paused" : "Open in another window";
|
2025-05-06 20:47:39 +00:00
|
|
|
g.drawText(text, getViewportArea(), juce::Justification::centred);
|
2024-10-23 10:23:58 +00:00
|
|
|
}
|
|
|
|
}
|