325 lines
11 KiB
C++
325 lines
11 KiB
C++
/* Copyright 2013-2019 Matt Tytel
|
|
*
|
|
* vital is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* vital is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with vital. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "interface_test.h"
|
|
#include "filter_section.h"
|
|
#include "default_look_and_feel.h"
|
|
#include "full_interface.h"
|
|
#include "preset_selector.h"
|
|
#include "sound_engine.h"
|
|
#include "synth_base.h"
|
|
#include "synth_gui_interface.h"
|
|
#include "synth_slider.h"
|
|
#include "skin.h"
|
|
|
|
namespace {
|
|
template<class T>
|
|
std::vector<T*> getAllComponentsOfType(Component* component) {
|
|
std::vector<T*> results;
|
|
Array<Component*> children = component->getChildren();
|
|
for (Component* child : children) {
|
|
T* result = dynamic_cast<T*>(child);
|
|
if (result)
|
|
results.push_back(result);
|
|
else {
|
|
std::vector<T*> sub_results = getAllComponentsOfType<T>(child);
|
|
results.insert(results.end(), sub_results.begin(), sub_results.end());
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
class TestFullInterface : public FullInterface {
|
|
public:
|
|
TestFullInterface() { }
|
|
|
|
void resized() override {
|
|
SynthSection::resized();
|
|
redoBackground();
|
|
}
|
|
|
|
void paintBackground(Graphics& g) override {
|
|
paintChildrenBackgrounds(g);
|
|
}
|
|
};
|
|
|
|
class TestTopComponent : public Component, public Timer {
|
|
public:
|
|
static constexpr float kMsBetweenUpdates = 10.0f;
|
|
static constexpr float kSliderRatioChangesPerUpdate = 0.2f;
|
|
static constexpr int kButtonTries = 32;
|
|
|
|
TestTopComponent(FullInterface* full_interface) : Component("Test Top Component"), test_section_(nullptr) {
|
|
if (full_interface == nullptr)
|
|
full_interface_ = std::make_unique<TestFullInterface>();
|
|
else {
|
|
full_interface->reset();
|
|
full_interface_.reset(full_interface);
|
|
}
|
|
addAndMakeVisible(full_interface_.get());
|
|
|
|
startTimer(kMsBetweenUpdates);
|
|
}
|
|
|
|
TestTopComponent() : TestTopComponent(nullptr) { }
|
|
|
|
~TestTopComponent() {
|
|
stopTimer();
|
|
}
|
|
|
|
FullInterface* full_interface() {
|
|
return full_interface_.get();
|
|
}
|
|
|
|
void addTestSection(SynthSection* section) {
|
|
test_section_ = section;
|
|
|
|
if (full_interface_.get() != section)
|
|
full_interface_->addSubSection(section);
|
|
|
|
full_interface_->redoBackground();
|
|
startTimer(kMsBetweenUpdates);
|
|
}
|
|
|
|
void resized() override {
|
|
Component::resized();
|
|
full_interface_->setBounds(getLocalBounds());
|
|
startTimer(kMsBetweenUpdates);
|
|
}
|
|
|
|
void doSliderChanges() {
|
|
auto sliders = test_section_->getAllSliders();
|
|
|
|
if (sliders.size()) {
|
|
int num_changes = std::ceil(kSliderRatioChangesPerUpdate * sliders.size());
|
|
for (int i = 0; i < num_changes; ++i) {
|
|
int index = rand() % sliders.size();
|
|
auto iter = sliders.begin();
|
|
std::advance(iter, index);
|
|
SynthSlider* slider = iter->second;
|
|
if (!slider->isShowing())
|
|
continue;
|
|
|
|
float min = slider->getMinimum();
|
|
float max = slider->getMaximum();
|
|
|
|
int decision = rand() % 6;
|
|
if (decision == 0)
|
|
slider->setValue(min, NotificationType::sendNotification);
|
|
else if (decision == 1)
|
|
slider->setValue(max, NotificationType::sendNotification);
|
|
else
|
|
slider->setValue((rand() * (max - min)) / RAND_MAX + min, NotificationType::sendNotification);
|
|
}
|
|
}
|
|
}
|
|
|
|
void doButtonChanges() {
|
|
auto buttons = getAllComponentsOfType<ToggleButton>(test_section_);
|
|
|
|
int num_changes = static_cast<int>(buttons.size());
|
|
for (int i = 0; i < num_changes; ++i) {
|
|
Button* button = buttons[i];
|
|
if (rand() % kButtonTries == 0 && button->isShowing())
|
|
button->setToggleState(!button->getToggleState(), NotificationType::sendNotification);
|
|
}
|
|
}
|
|
|
|
void doPresetChanges() {
|
|
auto preset_selectors = getAllComponentsOfType<PresetSelector>(test_section_);
|
|
int num_changes = static_cast<int>(preset_selectors.size());
|
|
for (int i = 0; i < num_changes; ++i) {
|
|
PresetSelector* preset_selector = preset_selectors[i];
|
|
if (rand() % kButtonTries == 0 && preset_selector->isShowing())
|
|
preset_selector->clickNext();
|
|
}
|
|
}
|
|
|
|
void timerCallback() override {
|
|
if (test_section_ == nullptr)
|
|
return;
|
|
|
|
doSliderChanges();
|
|
doButtonChanges();
|
|
doPresetChanges();
|
|
|
|
PopupMenu::dismissAllActiveMenus();
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<FullInterface> full_interface_;
|
|
SynthSection* test_section_;
|
|
};
|
|
|
|
class TestAudioComponentBase : public AudioAppComponent {
|
|
public:
|
|
TestAudioComponentBase(TestSynthBase* synth_base, FullInterface* full_interface = nullptr) :
|
|
synth_base_(synth_base), top_component_(full_interface) {
|
|
addAndMakeVisible(&top_component_);
|
|
|
|
setAudioChannels(0, vital::kNumChannels);
|
|
|
|
AudioDeviceManager::AudioDeviceSetup setup;
|
|
deviceManager.getAudioDeviceSetup(setup);
|
|
setup.sampleRate = vital::kDefaultSampleRate;
|
|
deviceManager.initialise(0, vital::kNumChannels, nullptr, true, "", &setup);
|
|
|
|
if (deviceManager.getCurrentAudioDevice() == nullptr) {
|
|
const OwnedArray<AudioIODeviceType>& device_types = deviceManager.getAvailableDeviceTypes();
|
|
|
|
for (AudioIODeviceType* device_type : device_types) {
|
|
deviceManager.setCurrentAudioDeviceType(device_type->getTypeName(), true);
|
|
if (deviceManager.getCurrentAudioDevice())
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
~TestAudioComponentBase() {
|
|
shutdownAudio();
|
|
}
|
|
|
|
void setSizes() {
|
|
top_component_.setSize(vital::kDefaultWindowWidth, vital::kDefaultWindowHeight);
|
|
}
|
|
|
|
TestTopComponent* top_component() { return &top_component_; }
|
|
|
|
void prepareToPlay(int buffer_size, double sample_rate) override {
|
|
synth_base_->getEngine()->setSampleRate(sample_rate);
|
|
synth_base_->getEngine()->updateAllModulationSwitches();
|
|
}
|
|
|
|
void getNextAudioBlock(const AudioSourceChannelInfo& buffer) override {
|
|
int num_samples = buffer.buffer->getNumSamples();
|
|
int synth_samples = std::min(num_samples, vital::kMaxBufferSize);
|
|
|
|
MidiBuffer midi_messages;
|
|
|
|
for (int b = 0; b < num_samples; b += synth_samples) {
|
|
int current_samples = std::min<int>(synth_samples, num_samples - b);
|
|
|
|
synth_base_->process(buffer.buffer, vital::kNumChannels, current_samples, b);
|
|
}
|
|
}
|
|
|
|
void releaseResources() override {
|
|
}
|
|
|
|
private:
|
|
TestSynthBase* synth_base_;
|
|
TestTopComponent top_component_;
|
|
};
|
|
|
|
class TestWindow : public DocumentWindow, public SynthGuiInterface, public Timer {
|
|
public:
|
|
static constexpr int kTestMs = 8000;
|
|
|
|
TestWindow(TestSynthBase* synth_base, FullInterface* full_interface = nullptr) :
|
|
DocumentWindow("Interface Test", Colours::lightgrey, DocumentWindow::allButtons, true),
|
|
SynthGuiInterface(synth_base, false) {
|
|
synth_base->setGuiInterface(this);
|
|
top_audio_component_ = std::make_unique<TestAudioComponentBase>(synth_base, full_interface);
|
|
setUsingNativeTitleBar(true);
|
|
setResizable(true, true);
|
|
top_audio_component_->setSize(vital::kDefaultWindowWidth, vital::kDefaultWindowHeight);
|
|
setContentOwned(top_audio_component_.get(), true);
|
|
top_audio_component_->setSizes();
|
|
setLookAndFeel(DefaultLookAndFeel::instance());
|
|
startTimer(kTestMs);
|
|
}
|
|
|
|
TestTopComponent* top_component() { return top_audio_component_->top_component(); }
|
|
|
|
void closeButtonPressed() override {
|
|
JUCEApplication::getInstance()->systemRequestedQuit();
|
|
}
|
|
|
|
void timerCallback() override {
|
|
closeButtonPressed();
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<TestAudioComponentBase> top_audio_component_;
|
|
};
|
|
|
|
class TestApp : public JUCEApplication {
|
|
public:
|
|
TestApp(TestSynthBase* synth_base, FullInterface* full_interface = nullptr) {
|
|
main_window_ = std::make_unique<TestWindow>(synth_base, full_interface);
|
|
main_window_->centreWithSize(vital::kDefaultWindowWidth, vital::kDefaultWindowHeight);
|
|
main_window_->setVisible(true);
|
|
}
|
|
|
|
const String getApplicationName() override { return ProjectInfo::projectName; }
|
|
const String getApplicationVersion() override { return ProjectInfo::versionString; }
|
|
bool moreThanOneInstanceAllowed() override { return true; }
|
|
|
|
void initialise(const String& command_line) override {
|
|
}
|
|
|
|
void systemRequestedQuit() override {
|
|
MessageManager::getInstance()->stopDispatchLoop();
|
|
main_window_ = nullptr;
|
|
}
|
|
|
|
void shutdown() override {
|
|
main_window_ = nullptr;
|
|
}
|
|
|
|
TestWindow* window() { return main_window_.get(); }
|
|
|
|
protected:
|
|
std::unique_ptr<TestWindow> main_window_;
|
|
};
|
|
}
|
|
|
|
namespace {
|
|
JUCEApplicationBase* createNullApplication() { return nullptr; }
|
|
}
|
|
|
|
void InterfaceTest::runStressRandomTest(SynthSection* component, FullInterface* full_interface) {
|
|
beginTest("Stress Random Controls");
|
|
MessageManager::getInstance();
|
|
|
|
ScopedJuceInitialiser_GUI library_initializer;
|
|
juce::JUCEApplicationBase::createInstance = &createNullApplication;
|
|
|
|
if (synth_base_ == nullptr)
|
|
createSynthEngine();
|
|
|
|
vital::SoundEngine* engine = getSynthEngine();
|
|
engine->noteOn(30, 0, 0, 0);
|
|
engine->noteOn(37, 0, 0, 0);
|
|
engine->noteOn(42, 0, 0, 0);
|
|
|
|
TestApp test_app(synth_base_.get(), full_interface);
|
|
test_app.window()->top_component()->addTestSection(component);
|
|
component->setSize(vital::kDefaultWindowWidth, vital::kDefaultWindowHeight);
|
|
test_app.window()->resized();
|
|
vital::control_map controls = engine->getControls();
|
|
test_app.window()->top_component()->full_interface()->setAllValues(controls);
|
|
test_app.window()->top_component()->full_interface()->reset();
|
|
|
|
JUCE_TRY {
|
|
MessageManager::getInstance()->runDispatchLoop();
|
|
}
|
|
JUCE_CATCH_EXCEPTION
|
|
|
|
engine->noteOff(30, 0, 0, 0);
|
|
engine->noteOff(37, 0, 0, 0);
|
|
engine->noteOff(42, 0, 0, 0);
|
|
}
|