From f5984141fd5afa04fb0bbf9a1fcfeaa46b624567 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Fri, 27 Jul 2018 18:34:29 -1000 Subject: [PATCH] Implement amuse playback --- Editor/AudioGroupModel.cpp | 1 - Editor/AudioGroupModel.hpp | 21 -- Editor/CMakeLists.txt | 2 +- Editor/EditorWidget.hpp | 1 + Editor/KeyboardWidget.cpp | 76 +++- Editor/KeyboardWidget.hpp | 46 +++ Editor/MIDIReader.cpp | 134 +++++++ Editor/MIDIReader.hpp | 51 +++ Editor/MainWindow.cpp | 339 ++++++++++++++++-- Editor/MainWindow.hpp | 47 ++- Editor/MainWindow.ui | 238 +++++++++--- Editor/ProjectModel.cpp | 199 ++++++++-- Editor/ProjectModel.hpp | 118 +++++- Editor/SoundMacroEditor.cpp | 274 ++++++++++++-- Editor/SoundMacroEditor.hpp | 61 +++- Editor/StatusBarWidget.hpp | 29 +- Editor/resources/IconKill.svg | 110 ++++++ Editor/resources/IconSoundMacroTarget.svg | 80 +++++ .../IconSoundMacroTargetDisabled.svg | 80 +++++ Editor/resources/lang_de.ts | 130 +++++-- Editor/resources/resources.qrc | 3 + include/amuse/AudioGroup.hpp | 41 ++- include/amuse/AudioGroupPool.hpp | 23 +- include/amuse/BooBackend.hpp | 2 + include/amuse/Common.hpp | 20 ++ include/amuse/Engine.hpp | 12 + lib/AudioGroup.cpp | 2 + lib/AudioGroupPool.cpp | 11 +- lib/AudioGroupProject.cpp | 107 ++++-- lib/AudioGroupSampleDirectory.cpp | 5 + lib/Common.cpp | 58 +++ lib/Engine.cpp | 27 +- lib/SoundMacroState.cpp | 109 +++--- 33 files changed, 2140 insertions(+), 317 deletions(-) delete mode 100644 Editor/AudioGroupModel.cpp delete mode 100644 Editor/AudioGroupModel.hpp create mode 100644 Editor/MIDIReader.cpp create mode 100644 Editor/MIDIReader.hpp create mode 100644 Editor/resources/IconKill.svg create mode 100644 Editor/resources/IconSoundMacroTarget.svg create mode 100644 Editor/resources/IconSoundMacroTargetDisabled.svg diff --git a/Editor/AudioGroupModel.cpp b/Editor/AudioGroupModel.cpp deleted file mode 100644 index 725e0db..0000000 --- a/Editor/AudioGroupModel.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "AudioGroupModel.hpp" \ No newline at end of file diff --git a/Editor/AudioGroupModel.hpp b/Editor/AudioGroupModel.hpp deleted file mode 100644 index d6435aa..0000000 --- a/Editor/AudioGroupModel.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef AMUSE_AUDIO_GROUP_MODEL_HPP -#define AMUSE_AUDIO_GROUP_MODEL_HPP - -#include "amuse/AudioGroup.hpp" - -class AudioGroupModel -{ - -}; - -class SFXGroupModel : public AudioGroupModel -{ - -}; - -class SongGroupModel : public AudioGroupModel -{ - -}; - -#endif //AMUSE_AUDIO_GROUP_MODEL_HPP diff --git a/Editor/CMakeLists.txt b/Editor/CMakeLists.txt index 89bd103..599f8b4 100644 --- a/Editor/CMakeLists.txt +++ b/Editor/CMakeLists.txt @@ -47,7 +47,7 @@ add_executable(amuse-gui WIN32 MACOSX_BUNDLE SampleEditor.hpp SampleEditor.cpp SoundGroupEditor.hpp SoundGroupEditor.cpp SongGroupEditor.hpp SongGroupEditor.cpp - AudioGroupModel.hpp AudioGroupModel.cpp + MIDIReader.hpp MIDIReader.cpp resources/resources.qrc qrc_resources.cpp ${QM_FILES} qrc_translation_res.cpp ${PLAT_SRCS} diff --git a/Editor/EditorWidget.hpp b/Editor/EditorWidget.hpp index 88bc58f..49a006b 100644 --- a/Editor/EditorWidget.hpp +++ b/Editor/EditorWidget.hpp @@ -13,6 +13,7 @@ public: explicit EditorWidget(QWidget* parent = Q_NULLPTR); virtual bool valid() const { return true; } virtual void unloadData() {} + virtual ProjectModel::INode* currentNode() const { return nullptr; } }; class EditorUndoCommand : public QUndoCommand diff --git a/Editor/KeyboardWidget.cpp b/Editor/KeyboardWidget.cpp index 39f12bf..ba03cf3 100644 --- a/Editor/KeyboardWidget.cpp +++ b/Editor/KeyboardWidget.cpp @@ -135,12 +135,12 @@ std::pair KeyboardWidget::_getOctaveAndKey(QMouseEvent* event) const void KeyboardWidget::_startKey(int octave, int key) { - printf("START %d %d\n", octave, key); + emit notePressed(octave * 12 + key); } void KeyboardWidget::_stopKey() { - printf("STOP\n"); + emit noteReleased(); } void KeyboardWidget::_moveOnKey(int octave, int key) @@ -209,7 +209,75 @@ void KeyboardWidget::showEvent(QShowEvent* event) { if (QScrollArea* scroll = qobject_cast(parentWidget()->parentWidget())) { - /* Scroll to C2 */ - scroll->ensureVisible(141 * 3 + scroll->width(), 0, 0, 0); + /* Scroll to C1 */ + scroll->ensureVisible(141 * 2 + scroll->width(), 0, 0, 0); } } + +KeyboardSlider::KeyboardSlider(QWidget* parent) +: QSlider(parent) +{} + +void KeyboardSlider::enterEvent(QEvent* event) +{ + if (m_statusFocus) + m_statusFocus->enter(); +} + +void KeyboardSlider::leaveEvent(QEvent* event) +{ + if (m_statusFocus) + m_statusFocus->exit(); +} + +void KeyboardSlider::setStatusFocus(StatusBarFocus* statusFocus) +{ + m_statusFocus = statusFocus; + QString str = stringOfValue(value()); + m_statusFocus->setMessage(str); + setToolTip(str); +} + +void KeyboardSlider::sliderChange(SliderChange change) +{ + QSlider::sliderChange(change); + if (m_statusFocus && change == QAbstractSlider::SliderValueChange) + { + QString str = stringOfValue(value()); + m_statusFocus->setMessage(str); + setToolTip(str); + } +} + +VelocitySlider::VelocitySlider(QWidget* parent) +: KeyboardSlider(parent) +{} + +QString VelocitySlider::stringOfValue(int value) const +{ + return tr("Velocity: %1").arg(value); +} + +ModulationSlider::ModulationSlider(QWidget* parent) +: KeyboardSlider(parent) +{} + +QString ModulationSlider::stringOfValue(int value) const +{ + return tr("Modulation: %1").arg(value); +} + +PitchSlider::PitchSlider(QWidget* parent) +: KeyboardSlider(parent) +{} + +QString PitchSlider::stringOfValue(int value) const +{ + return tr("Pitch: %1").arg(value / 2048.0, 0, 'g', 2); +} + +void PitchSlider::mouseReleaseEvent(QMouseEvent *ev) +{ + KeyboardSlider::mouseReleaseEvent(ev); + setValue(0); +} diff --git a/Editor/KeyboardWidget.hpp b/Editor/KeyboardWidget.hpp index b2c4871..1bdccf2 100644 --- a/Editor/KeyboardWidget.hpp +++ b/Editor/KeyboardWidget.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include "StatusBarWidget.hpp" class KeyboardWidget; @@ -47,7 +49,51 @@ public: void leaveEvent(QEvent* event); void wheelEvent(QWheelEvent *event); void showEvent(QShowEvent *event); + +signals: + void notePressed(int key); + void noteReleased(); }; +class KeyboardSlider : public QSlider +{ + Q_OBJECT +protected: + StatusBarFocus* m_statusFocus = nullptr; + virtual QString stringOfValue(int value) const = 0; +public: + explicit KeyboardSlider(QWidget* parent = Q_NULLPTR); + void enterEvent(QEvent* event); + void leaveEvent(QEvent* event); + void setStatusFocus(StatusBarFocus* statusFocus); + void sliderChange(SliderChange change); +}; + +class VelocitySlider : public KeyboardSlider +{ + Q_OBJECT + QString stringOfValue(int value) const; +public: + explicit VelocitySlider(QWidget* parent = Q_NULLPTR); + +}; + +class ModulationSlider : public KeyboardSlider +{ + Q_OBJECT + QString stringOfValue(int value) const; +public: + explicit ModulationSlider(QWidget* parent = Q_NULLPTR); +}; + +class PitchSlider : public KeyboardSlider +{ +Q_OBJECT + QString stringOfValue(int value) const; +public: + explicit PitchSlider(QWidget* parent = Q_NULLPTR); + void mouseReleaseEvent(QMouseEvent *ev); + void wheelEvent(QWheelEvent* ev) { ev->ignore(); } +}; #endif //AMUSE_KEYBOARD_WIDGET_HPP diff --git a/Editor/MIDIReader.cpp b/Editor/MIDIReader.cpp new file mode 100644 index 0000000..0f79f35 --- /dev/null +++ b/Editor/MIDIReader.cpp @@ -0,0 +1,134 @@ +#include "MIDIReader.hpp" +#include "MainWindow.hpp" + +MIDIReader::MIDIReader(amuse::Engine& engine, const char* name, bool useLock) +: amuse::BooBackendMIDIReader(engine, name, useLock) {} + +void MIDIReader::noteOff(uint8_t chan, uint8_t key, uint8_t velocity) +{ + auto keySearch = m_chanVoxs.find(key); + if (keySearch == m_chanVoxs.cend()) + return; + + if (keySearch->second == m_lastVoice.lock()) + m_lastVoice.reset(); + keySearch->second->keyOff(); + m_keyoffVoxs.emplace(std::move(keySearch->second)); + m_chanVoxs.erase(keySearch); +} + +void MIDIReader::noteOn(uint8_t chan, uint8_t key, uint8_t velocity) +{ + /* If portamento is enabled for voice, pre-empt spawning new voices */ + if (std::shared_ptr lastVoice = m_lastVoice.lock()) + { + uint8_t lastNote = lastVoice->getLastNote(); + if (lastVoice->doPortamento(key)) + { + m_chanVoxs.erase(lastNote); + m_chanVoxs[key] = lastVoice; + return; + } + } + + /* Ensure keyoff sent first */ + auto keySearch = m_chanVoxs.find(key); + if (keySearch != m_chanVoxs.cend()) + { + if (keySearch->second == m_lastVoice.lock()) + m_lastVoice.reset(); + keySearch->second->keyOff(); + keySearch->second->setPedal(false); + m_keyoffVoxs.emplace(std::move(keySearch->second)); + m_chanVoxs.erase(keySearch); + } + + ProjectModel::INode* node = g_MainWindow->getEditorNode(); + if (node && node->type() == ProjectModel::INode::Type::SoundMacro) + { + ProjectModel::SoundMacroNode* cNode = static_cast(node); + amuse::AudioGroupDatabase* group = g_MainWindow->projectModel()->getGroupNode(node)->getAudioGroup(); + std::shared_ptr& vox = m_chanVoxs[key]; + vox = m_engine.macroStart(group, cNode->id(), key, velocity, g_MainWindow->m_modulation); + vox->setPedal(g_MainWindow->m_sustain); + vox->setPitchWheel(g_MainWindow->m_pitch); + } +} + +void MIDIReader::notePressure(uint8_t /*chan*/, uint8_t /*key*/, uint8_t /*pressure*/) {} + +void MIDIReader::controlChange(uint8_t chan, uint8_t control, uint8_t value) +{ + if (control == 1) + { + g_MainWindow->m_ui.modulationSlider->setValue(int(value)); + } + else if (control == 64) + { + g_MainWindow->setSustain(value >= 0x40); + } + else + { + for (auto& v : m_engine.getActiveVoices()) + v->setCtrlValue(control, value); + } +} + +void MIDIReader::programChange(uint8_t chan, uint8_t program) {} + +void MIDIReader::channelPressure(uint8_t /*chan*/, uint8_t /*pressure*/) {} + +void MIDIReader::pitchBend(uint8_t chan, int16_t pitch) +{ + g_MainWindow->m_ui.pitchSlider->setValue(int((pitch - 0x2000) / float(0x2000) * 2048.f)); +} + +void MIDIReader::allSoundOff(uint8_t chan) +{ + for (auto& v : m_engine.getActiveVoices()) + v->kill(); +} + +void MIDIReader::resetAllControllers(uint8_t /*chan*/) {} + +void MIDIReader::localControl(uint8_t /*chan*/, bool /*on*/) {} + +void MIDIReader::allNotesOff(uint8_t chan) +{ + for (auto& v : m_engine.getActiveVoices()) + v->kill(); +} + +void MIDIReader::omniMode(uint8_t /*chan*/, bool /*on*/) {} + +void MIDIReader::polyMode(uint8_t /*chan*/, bool /*on*/) {} + +void MIDIReader::sysex(const void* /*data*/, size_t /*len*/) {} + +void MIDIReader::timeCodeQuarterFrame(uint8_t /*message*/, uint8_t /*value*/) {} + +void MIDIReader::songPositionPointer(uint16_t /*pointer*/) {} + +void MIDIReader::songSelect(uint8_t /*song*/) {} + +void MIDIReader::tuneRequest() {} + +void MIDIReader::startSeq() {} + +void MIDIReader::continueSeq() {} + +void MIDIReader::stopSeq() {} + +void MIDIReader::reset() {} + +VoiceAllocator::VoiceAllocator(boo::IAudioVoiceEngine& booEngine) +: amuse::BooBackendVoiceAllocator(booEngine) {} + +std::unique_ptr +VoiceAllocator::allocateMIDIReader(amuse::Engine& engine, const char* name) +{ + std::unique_ptr ret = std::make_unique(engine, name, m_booEngine.useMIDILock()); + if (!static_cast(*ret).getMidiIn()) + return {}; + return ret; +} diff --git a/Editor/MIDIReader.hpp b/Editor/MIDIReader.hpp new file mode 100644 index 0000000..55f0fc2 --- /dev/null +++ b/Editor/MIDIReader.hpp @@ -0,0 +1,51 @@ +#ifndef AMUSE_MIDI_READER_HPP +#define AMUSE_MIDI_READER_HPP + +#include "amuse/BooBackend.hpp" +#include + +class MIDIReader : public amuse::BooBackendMIDIReader +{ + std::unordered_map> m_chanVoxs; + std::unordered_set> m_keyoffVoxs; + std::weak_ptr m_lastVoice; +public: + MIDIReader(amuse::Engine& engine, const char* name, bool useLock); + boo::IMIDIIn* getMidiIn() const { return m_midiIn.get(); } + + void noteOff(uint8_t chan, uint8_t key, uint8_t velocity); + void noteOn(uint8_t chan, uint8_t key, uint8_t velocity); + void notePressure(uint8_t chan, uint8_t key, uint8_t pressure); + void controlChange(uint8_t chan, uint8_t control, uint8_t value); + void programChange(uint8_t chan, uint8_t program); + void channelPressure(uint8_t chan, uint8_t pressure); + void pitchBend(uint8_t chan, int16_t pitch); + + void allSoundOff(uint8_t chan); + void resetAllControllers(uint8_t chan); + void localControl(uint8_t chan, bool on); + void allNotesOff(uint8_t chan); + void omniMode(uint8_t chan, bool on); + void polyMode(uint8_t chan, bool on); + + void sysex(const void* data, size_t len); + void timeCodeQuarterFrame(uint8_t message, uint8_t value); + void songPositionPointer(uint16_t pointer); + void songSelect(uint8_t song); + void tuneRequest(); + + void startSeq(); + void continueSeq(); + void stopSeq(); + + void reset(); +}; + +class VoiceAllocator : public amuse::BooBackendVoiceAllocator +{ +public: + VoiceAllocator(boo::IAudioVoiceEngine& booEngine); + std::unique_ptr allocateMIDIReader(amuse::Engine& engine, const char* name = nullptr); +}; + +#endif // AMUSE_MIDI_READER_HPP diff --git a/Editor/MainWindow.cpp b/Editor/MainWindow.cpp index 6a0964f..b2b308b 100644 --- a/Editor/MainWindow.cpp +++ b/Editor/MainWindow.cpp @@ -31,14 +31,40 @@ MainWindow::MainWindow(QWidget* parent) m_ui.projectOutline->setItemDelegate(&m_treeDelegate); connectMessenger(&m_mainMessenger, Qt::DirectConnection); + m_ui.statusbar->connectKillClicked(this, SLOT(killSounds())); + m_ui.keyboardContents->setStatusFocus(new StatusBarFocus(m_ui.statusbar)); + m_ui.velocitySlider->setStatusFocus(new StatusBarFocus(m_ui.statusbar)); + m_ui.modulationSlider->setStatusFocus(new StatusBarFocus(m_ui.statusbar)); + m_ui.pitchSlider->setStatusFocus(new StatusBarFocus(m_ui.statusbar)); + connect(m_ui.keyboardContents, SIGNAL(notePressed(int)), this, SLOT(notePressed(int))); + connect(m_ui.keyboardContents, SIGNAL(noteReleased()), this, SLOT(noteReleased())); + connect(m_ui.velocitySlider, SIGNAL(valueChanged(int)), this, SLOT(velocityChanged(int))); + connect(m_ui.modulationSlider, SIGNAL(valueChanged(int)), this, SLOT(modulationChanged(int))); + connect(m_ui.pitchSlider, SIGNAL(valueChanged(int)), this, SLOT(pitchChanged(int))); m_ui.actionNew_Project->setShortcut(QKeySequence::New); connect(m_ui.actionNew_Project, SIGNAL(triggered()), this, SLOT(newAction())); m_ui.actionOpen_Project->setShortcut(QKeySequence::Open); connect(m_ui.actionOpen_Project, SIGNAL(triggered()), this, SLOT(openAction())); + m_ui.actionSave_Project->setShortcut(QKeySequence::Save); + connect(m_ui.actionSave_Project, SIGNAL(triggered()), this, SLOT(saveAction())); + connect(m_ui.actionRevert_Project, SIGNAL(triggered()), this, SLOT(revertAction())); connect(m_ui.actionImport, SIGNAL(triggered()), this, SLOT(importAction())); connect(m_ui.actionExport_GameCube_Groups, SIGNAL(triggered()), this, SLOT(exportAction())); + + for (int i = 0; i < MaxRecentFiles; ++i) + { + m_recentFileActs[i] = new QAction(this); + m_recentFileActs[i]->setVisible(false); + m_ui.menuRecent_Projects->addAction(m_recentFileActs[i]); + connect(m_recentFileActs[i], SIGNAL(triggered()), this, SLOT(openRecentFileAction())); + } + m_ui.menuRecent_Projects->addSeparator(); + m_clearRecentFileAct = new QAction(tr("Clear Recent Projects"), this); + connect(m_clearRecentFileAct, SIGNAL(triggered()), this, SLOT(clearRecentFilesAction())); + m_ui.menuRecent_Projects->addAction(m_clearRecentFileAct); + #ifndef __APPLE__ m_ui.menuFile->addSeparator(); QAction* quitAction = m_ui.menuFile->addAction(tr("Quit")); @@ -46,6 +72,8 @@ MainWindow::MainWindow(QWidget* parent) connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); #endif + updateRecentFileActions(); + QAction* undoAction = m_undoStack->createUndoAction(this); undoAction->setShortcut(QKeySequence::Undo); m_ui.menuEdit->insertAction(m_ui.actionCut, undoAction); @@ -97,11 +125,11 @@ MainWindow::MainWindow(QWidget* parent) connect(qApp, SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(onFocusChanged(QWidget*,QWidget*))); - setFocusAudioGroup(nullptr); - m_voxEngine = boo::NewAudioVoiceEngine(); - m_voxAllocator = std::make_unique(*m_voxEngine); + m_voxAllocator = std::make_unique(*m_voxEngine); m_engine = std::make_unique(*m_voxAllocator); + + startTimer(16); } MainWindow::~MainWindow() @@ -139,13 +167,39 @@ void MainWindow::connectMessenger(UIMessenger* messenger, Qt::ConnectionType typ QMessageBox::StandardButton)), type); } +void MainWindow::updateRecentFileActions() +{ + QSettings settings; + QStringList files = settings.value("recentFileList").toStringList(); + + int numRecentFiles = std::min(files.size(), int(MaxRecentFiles)); + + for (int i = 0; i < numRecentFiles; ++i) + { + QString text = QStringLiteral("&%1 %2").arg(i + 1).arg(QDir(files[i]).dirName()); + m_recentFileActs[i]->setText(text); + m_recentFileActs[i]->setData(files[i]); + m_recentFileActs[i]->setVisible(true); + } + for (int j = numRecentFiles; j < MaxRecentFiles; ++j) + m_recentFileActs[j]->setVisible(false); + + m_ui.menuRecent_Projects->setEnabled(numRecentFiles > 0); +} + bool MainWindow::setProjectPath(const QString& path) { if (m_projectModel && m_projectModel->path() == path) return true; QDir dir(path); - if (!dir.exists()) + if (dir.path().isEmpty() || dir.path() == QStringLiteral(".") || dir.path() == QStringLiteral("..")) + { + QString msg = QString(tr("The directory at '%1' must not be empty.")).arg(path); + QMessageBox::critical(this, tr("Directory empty"), msg); + return false; + } + else if (!dir.exists()) { QString msg = QString(tr("The directory at '%1' must exist for the Amuse editor.")).arg(path); QMessageBox::critical(this, tr("Directory does not exist"), msg); @@ -165,24 +219,30 @@ bool MainWindow::setProjectPath(const QString& path) m_projectModel->deleteLater(); m_projectModel = new ProjectModel(path, this); m_ui.projectOutline->setModel(m_projectModel); + connect(m_ui.projectOutline->selectionModel(), + SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, SLOT(onOutlineSelectionChanged(const QItemSelection&, const QItemSelection&))); + m_ui.actionSave_Project->setEnabled(true); + m_ui.actionRevert_Project->setEnabled(true); m_ui.actionExport_GameCube_Groups->setEnabled(true); setWindowFilePath(path); setWindowTitle(QString("Amuse [%1]").arg(dir.dirName())); - setFocusAudioGroup(nullptr); onFocusChanged(nullptr, focusWidget()); + m_undoStack->clear(); + + QSettings settings; + QStringList files = settings.value("recentFileList").toStringList(); + files.removeAll(dir.path()); + files.prepend(dir.path()); + while (files.size() > MaxRecentFiles) + files.removeLast(); + settings.setValue("recentFileList", files); + + updateRecentFileActions(); return true; } -void MainWindow::setFocusAudioGroup(AudioGroupModel* group) -{ - m_focusAudioGroup = group; - bool active = m_focusAudioGroup != nullptr; - m_ui.actionNew_Sound_Macro->setEnabled(active); - m_ui.actionNew_Keymap->setEnabled(active); - m_ui.actionNew_Layers->setEnabled(active); -} - void MainWindow::refreshAudioIO() { QList audioActions = m_ui.menuAudio->actions(); @@ -220,6 +280,61 @@ void MainWindow::refreshMIDIIO() m_ui.menuMIDI->addAction(tr("No MIDI Devices Found"))->setEnabled(false); } +void MainWindow::timerEvent(QTimerEvent* ev) +{ + if (m_voxEngine && m_engine) + { + m_voxEngine->pumpAndMixVoices(); + m_ui.statusbar->setVoiceCount(int(m_engine->getActiveVoices().size())); + if (m_engine->getActiveVoices().empty() && m_uiDisabled) + { + m_ui.projectOutline->setEnabled(true); + m_ui.editorContents->setEnabled(true); + m_ui.menubar->setEnabled(true); + m_uiDisabled = false; + } + else if (!m_engine->getActiveVoices().empty() && !m_uiDisabled) + { + m_ui.projectOutline->setEnabled(false); + m_ui.editorContents->setEnabled(false); + m_ui.menubar->setEnabled(false); + m_uiDisabled = true; + } + } +} + +void MainWindow::setSustain(bool sustain) +{ + if (sustain && !m_sustain) + { + m_ui.statusbar->setNormalMessage(tr("SUSTAIN")); + for (auto& v : m_engine->getActiveVoices()) + v->setPedal(true); + m_sustain = true; + } + else if (!sustain && m_sustain) + { + m_ui.statusbar->setNormalMessage({}); + for (auto& v : m_engine->getActiveVoices()) + v->setPedal(false); + m_sustain = false; + } +} + +void MainWindow::keyPressEvent(QKeyEvent* ev) +{ + if (ev->key() == Qt::Key_Shift) + setSustain(true); + else if (ev->key() == Qt::Key_Escape) + killSounds(); +} + +void MainWindow::keyReleaseEvent(QKeyEvent* ev) +{ + if (ev->key() == Qt::Key_Shift) + setSustain(false); +} + void MainWindow::startBackgroundTask(const QString& windowTitle, const QString& label, std::function&& task) { @@ -335,11 +450,24 @@ void MainWindow::closeEditor() _setEditor(nullptr); } +ProjectModel::INode* MainWindow::getEditorNode() const +{ + if (m_ui.editorContents->currentWidget() != m_faceSvg) + return static_cast(m_ui.editorContents->currentWidget())->currentNode(); + return nullptr; +} + void MainWindow::pushUndoCommand(QUndoCommand* cmd) { m_undoStack->push(cmd); } +void MainWindow::aboutToDeleteNode(ProjectModel::INode* node) +{ + if (getEditorNode() == node) + closeEditor(); +} + void MainWindow::newAction() { QString path = QFileDialog::getSaveFileName(this, tr("New Project")); @@ -354,18 +482,20 @@ void MainWindow::newAction() m_projectModel->ensureModelData(); } -void MainWindow::openAction() +bool MainWindow::openProject(const QString& path) { - QString path = QFileDialog::getExistingDirectory(this, tr("Open Project")); - if (path.isEmpty()) - return; - QDir dir(path); - if (!dir.exists()) + if (dir.path().isEmpty() || dir.path() == QStringLiteral(".") || dir.path() == QStringLiteral("..")) + { + QString msg = QString(tr("The directory at '%1' must not be empty.")).arg(path); + QMessageBox::critical(this, tr("Directory empty"), msg); + return false; + } + else if (!dir.exists()) { QString msg = QString(tr("The directory at '%1' does not exist.")).arg(path); QMessageBox::critical(this, tr("Bad Directory"), msg); - return; + return false; } if (QFileInfo(dir, QStringLiteral("!project.yaml")).exists() && @@ -373,7 +503,7 @@ void MainWindow::openAction() dir.cdUp(); if (!setProjectPath(dir.path())) - return; + return false; ProjectModel* model = m_projectModel; startBackgroundTask(tr("Opening"), tr("Scanning Project"), @@ -394,6 +524,50 @@ void MainWindow::openAction() } } }); + + return true; +} + +void MainWindow::openAction() +{ + QString path = QFileDialog::getExistingDirectory(this, tr("Open Project")); + if (path.isEmpty()) + return; + openProject(path); +} + +void MainWindow::openRecentFileAction() +{ + if (QAction *action = qobject_cast(sender())) + if (!openProject(action->data().toString())) + { + QString path = action->data().toString(); + QSettings settings; + QStringList files = settings.value("recentFileList").toStringList(); + files.removeAll(path); + settings.setValue("recentFileList", files); + updateRecentFileActions(); + } +} + +void MainWindow::clearRecentFilesAction() +{ + QSettings settings; + settings.setValue("recentFileList", QStringList()); + updateRecentFileActions(); +} + +void MainWindow::saveAction() +{ + +} + +void MainWindow::revertAction() +{ + QString path = m_projectModel->path(); + delete m_projectModel; + m_projectModel = nullptr; + openProject(path); } void MainWindow::importAction() @@ -610,6 +784,83 @@ void MainWindow::setMIDIIO() //qobject_cast(sender())->data().toString().toUtf8().constData(); } +void MainWindow::notePressed(int key) +{ + if (m_engine) + { + ProjectModel::INode* node = getEditorNode(); + if (node && node->type() == ProjectModel::INode::Type::SoundMacro) + { + ProjectModel::SoundMacroNode* cNode = static_cast(node); + amuse::AudioGroupDatabase* group = m_projectModel->getGroupNode(node)->getAudioGroup(); + if (m_lastSound) + m_lastSound->keyOff(); + m_lastSound = m_engine->macroStart(group, cNode->id(), key, m_velocity, m_modulation); + m_lastSound->setPedal(m_sustain); + m_lastSound->setPitchWheel(m_pitch); + } + } +} + +void MainWindow::noteReleased() +{ + if (m_lastSound) + { + m_lastSound->keyOff(); + m_lastSound.reset(); + } +} + +void MainWindow::velocityChanged(int vel) +{ + m_velocity = vel; +} + +void MainWindow::modulationChanged(int mod) +{ + m_modulation = mod; + for (auto& v : m_engine->getActiveVoices()) + v->setCtrlValue(1, int8_t(m_modulation)); +} + +void MainWindow::pitchChanged(int pitch) +{ + m_pitch = pitch / 2048.f; + for (auto& v : m_engine->getActiveVoices()) + v->setPitchWheel(m_pitch); +} + +void MainWindow::killSounds() +{ + for (auto& v : m_engine->getActiveVoices()) + v->kill(); +} + +void MainWindow::outlineCutAction() +{ + +} + +void MainWindow::outlineCopyAction() +{ + +} + +void MainWindow::outlinePasteAction() +{ + +} + +void MainWindow::outlineDeleteAction() +{ + if (!m_projectModel) + return; + QModelIndexList indexes = m_ui.projectOutline->selectionModel()->selectedIndexes(); + if (indexes.empty()) + return; + m_projectModel->del(indexes.front()); +} + void MainWindow::onFocusChanged(QWidget* old, QWidget* now) { disconnect(m_cutConn); @@ -634,24 +885,52 @@ void MainWindow::onFocusChanged(QWidget* old, QWidget* now) if (now == m_ui.projectOutline || m_ui.projectOutline->isAncestorOf(now)) { - m_ui.actionCut->setEnabled(false); - m_ui.actionCopy->setEnabled(false); - m_ui.actionPaste->setEnabled(false); + setOutlineEditEnabled(canEditOutline()); if (m_projectModel) { - m_deleteConn = connect(m_ui.actionDelete, SIGNAL(triggered()), m_projectModel, SLOT(del())); - m_ui.actionDelete->setEnabled(m_projectModel->canDelete()); - m_canEditConn = connect(m_projectModel, SIGNAL(canDeleteChanged(bool)), - m_ui.actionDelete, SLOT(setEnabled(bool))); + m_cutConn = connect(m_ui.actionCut, SIGNAL(triggered()), this, SLOT(outlineCutAction())); + m_copyConn = connect(m_ui.actionCopy, SIGNAL(triggered()), this, SLOT(outlineCopyAction())); + m_pasteConn = connect(m_ui.actionPaste, SIGNAL(triggered()), this, SLOT(outlinePasteAction())); + m_deleteConn = connect(m_ui.actionDelete, SIGNAL(triggered()), this, SLOT(outlineDeleteAction())); } } else if (now == m_ui.editorContents || m_ui.editorContents->isAncestorOf(now)) { - + setOutlineEditEnabled(false); } } +void MainWindow::setOutlineEditEnabled(bool enabled) +{ + m_ui.actionCut->setEnabled(enabled); + m_ui.actionCopy->setEnabled(enabled); + m_ui.actionPaste->setEnabled(enabled); + m_ui.actionDelete->setEnabled(enabled); +} + +bool MainWindow::canEditOutline() +{ + if (!m_projectModel) + return false; + QModelIndexList indexes = m_ui.projectOutline->selectionModel()->selectedIndexes(); + if (indexes.empty()) + return false; + return m_projectModel->canEdit(indexes.front()); +} + +void MainWindow::onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + if (!m_projectModel) + return; + if (selected.indexes().empty()) + { + setOutlineEditEnabled(false); + return; + } + setOutlineEditEnabled(m_projectModel->canEdit(selected.indexes().front())); +} + void MainWindow::onTextSelect() { if (QLineEdit* le = qobject_cast(sender())) diff --git a/Editor/MainWindow.hpp b/Editor/MainWindow.hpp index 22cc48a..365fcc7 100644 --- a/Editor/MainWindow.hpp +++ b/Editor/MainWindow.hpp @@ -12,13 +12,15 @@ #include "boo/audiodev/IAudioVoiceEngine.hpp" #include "ProjectModel.hpp" #include "EditorWidget.hpp" +#include "MIDIReader.hpp" + +#define MaxRecentFiles 4 namespace Ui { class MainWindow; } class MainWindow; -class AudioGroupModel; class SongGroupEditor; class SoundGroupEditor; class SoundMacroEditor; @@ -67,12 +69,14 @@ public: class MainWindow : public QMainWindow { + friend class MIDIReader; Q_OBJECT Ui::MainWindow m_ui; + QAction* m_clearRecentFileAct; + QAction* m_recentFileActs[MaxRecentFiles]; TreeDelegate m_treeDelegate; UIMessenger m_mainMessenger; ProjectModel* m_projectModel = nullptr; - AudioGroupModel* m_focusAudioGroup = nullptr; QWidget* m_faceSvg; SongGroupEditor* m_songGroupEditor = nullptr; SoundGroupEditor* m_soundGroupEditor = nullptr; @@ -83,8 +87,14 @@ class MainWindow : public QMainWindow LayersEditor* m_layersEditor = nullptr; std::unique_ptr m_voxEngine; - std::unique_ptr m_voxAllocator; + std::unique_ptr m_voxAllocator; std::unique_ptr m_engine; + std::shared_ptr m_lastSound; + int m_velocity = 90; + int m_modulation = 0; + float m_pitch = 0.f; + bool m_sustain = false; + bool m_uiDisabled = false; QUndoStack* m_undoStack; @@ -100,10 +110,14 @@ class MainWindow : public QMainWindow void connectMessenger(UIMessenger* messenger, Qt::ConnectionType type); + void updateRecentFileActions(); bool setProjectPath(const QString& path); - void setFocusAudioGroup(AudioGroupModel* group); void refreshAudioIO(); void refreshMIDIIO(); + void timerEvent(QTimerEvent* ev); + void setSustain(bool sustain); + void keyPressEvent(QKeyEvent* ev); + void keyReleaseEvent(QKeyEvent* ev); void startBackgroundTask(const QString& windowTitle, const QString& label, std::function&& task); @@ -114,6 +128,8 @@ public: explicit MainWindow(QWidget* parent = Q_NULLPTR); ~MainWindow(); + bool openProject(const QString& path); + bool openEditor(ProjectModel::SongGroupNode* node); bool openEditor(ProjectModel::SoundGroupNode* node); bool openEditor(ProjectModel::SoundMacroNode* node); @@ -124,11 +140,19 @@ public: bool openEditor(ProjectModel::INode* node); void closeEditor(); + ProjectModel::INode* getEditorNode() const; void pushUndoCommand(QUndoCommand* cmd); + void aboutToDeleteNode(ProjectModel::INode* node); + + ProjectModel* projectModel() const { return m_projectModel; } public slots: void newAction(); void openAction(); + void openRecentFileAction(); + void clearRecentFilesAction(); + void saveAction(); + void revertAction(); void importAction(); void exportAction(); @@ -147,7 +171,22 @@ public slots: void setAudioIO(); void setMIDIIO(); + void notePressed(int key); + void noteReleased(); + void velocityChanged(int vel); + void modulationChanged(int mod); + void pitchChanged(int pitch); + void killSounds(); + + void outlineCutAction(); + void outlineCopyAction(); + void outlinePasteAction(); + void outlineDeleteAction(); + void onFocusChanged(QWidget* old, QWidget* now); + void setOutlineEditEnabled(bool enabled); + bool canEditOutline(); + void onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); void onTextSelect(); void onTextDelete(); diff --git a/Editor/MainWindow.ui b/Editor/MainWindow.ui index 00fa392..f7af33e 100644 --- a/Editor/MainWindow.ui +++ b/Editor/MainWindow.ui @@ -6,7 +6,7 @@ 0 0 - 1024 + 1360 768 @@ -98,22 +98,22 @@ 0 0 - 764 + 1100 610 - + - + 0 0 - 500 + 0 100 @@ -123,46 +123,157 @@ 100 - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - true - - - - - 0 - 0 - 1501 - 85 - + + + 6 - - - 0 - 0 - + + 0 - - - 1501 - 0 - + + 0 - - - 16777215 - 16777215 - + + 6 - + + 0 + + + + + + 0 + 0 + + + + + 500 + 100 + + + + + 16777215 + 100 + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + true + + + + + 0 + 0 + 1501 + 85 + + + + + 0 + 0 + + + + + 1501 + 0 + + + + + 16777215 + 16777215 + + + + + + + + + + 0 + 0 + + + + + 0 + 100 + + + + 127 + + + 90 + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + + 0 + 100 + + + + 127 + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + + 0 + 100 + + + + -2048 + + + 2048 + + + Qt::Vertical + + + + @@ -174,7 +285,7 @@ 0 0 - 1024 + 1360 27 @@ -182,8 +293,20 @@ &File + + + false + + + Recent &Projects + + + + + + @@ -417,6 +540,22 @@ New &Curve + + + false + + + &Save Project + + + + + false + + + &Revert Project + + @@ -430,6 +569,21 @@ QStatusBar
StatusBarWidget.hpp
+ + ModulationSlider + QSlider +
KeyboardWidget.hpp
+
+ + PitchSlider + QSlider +
KeyboardWidget.hpp
+
+ + VelocitySlider + QSlider +
KeyboardWidget.hpp
+
diff --git a/Editor/ProjectModel.cpp b/Editor/ProjectModel.cpp index 607e9f3..e5e4f6a 100644 --- a/Editor/ProjectModel.cpp +++ b/Editor/ProjectModel.cpp @@ -3,13 +3,98 @@ #include "ProjectModel.hpp" #include "Common.hpp" #include "athena/YAMLDocWriter.hpp" +#include "MainWindow.hpp" +#include QIcon ProjectModel::GroupNode::Icon; QIcon ProjectModel::SongGroupNode::Icon; QIcon ProjectModel::SoundGroupNode::Icon; +NullItemProxyModel::NullItemProxyModel(ProjectModel* source) +: QIdentityProxyModel(source) +{ + setSourceModel(source); +} + +QModelIndex NullItemProxyModel::mapFromSource(const QModelIndex& sourceIndex) const +{ + if (!sourceIndex.isValid()) + return QModelIndex(); + if (sourceIndex.row() == sourceModel()->rowCount(sourceIndex.parent())) + return createIndex(0, sourceIndex.column(), sourceIndex.internalPointer()); + return createIndex(sourceIndex.row() + 1, sourceIndex.column(), sourceIndex.internalPointer()); +} + +QModelIndex NullItemProxyModel::mapToSource(const QModelIndex& proxyIndex) const +{ + if (!proxyIndex.isValid()) + return QModelIndex(); + return static_cast(sourceModel())-> + proxyCreateIndex(proxyIndex.row() - 1, proxyIndex.column(), proxyIndex.internalPointer()); +} + +int NullItemProxyModel::rowCount(const QModelIndex& parent) const +{ + return QIdentityProxyModel::rowCount(parent) + 1; +} + +QModelIndex NullItemProxyModel::index(int row, int column, const QModelIndex& parent) const +{ + const QModelIndex sourceParent = mapToSource(parent); + const QModelIndex sourceIndex = sourceModel()->index(row - 1, column, sourceParent); + return mapFromSource(sourceIndex); +} + +QVariant NullItemProxyModel::data(const QModelIndex& proxyIndex, int role) const +{ + if (!proxyIndex.isValid() || proxyIndex.row() == 0) + return QVariant(); + return QIdentityProxyModel::data(proxyIndex, role); +} + +ProjectModel::INode::INode(INode* parent, int row) : m_parent(parent), m_row(row) +{ + m_nullChild = std::make_unique(this); +} + +ProjectModel::CollectionNode* ProjectModel::GroupNode::getCollectionOfType(Type tp) const +{ + for (auto it = m_children.rbegin(); it != m_children.rend(); ++it) + { + if ((*it)->type() == Type::Collection) + { + CollectionNode* col = static_cast(it->get()); + if (col->collectionType() == tp) + return col; + } + } + return nullptr; +} + +int ProjectModel::CollectionNode::indexOfId(amuse::ObjectId id) const +{ + int ret = 0; + for (auto& n : m_children) + { + if (static_cast(n.get())->id() == id) + return ret; + ++ret; + } + return -1; +} + +amuse::ObjectId ProjectModel::CollectionNode::idOfIndex(int idx) const +{ + return static_cast(m_children[idx].get())->id(); +} + +ProjectModel::BasePoolObjectNode* ProjectModel::CollectionNode::nodeOfIndex(int idx) const +{ + return static_cast(m_children[idx].get()); +} + ProjectModel::ProjectModel(const QString& path, QObject* parent) -: QAbstractItemModel(parent), m_dir(path) +: QAbstractItemModel(parent), m_dir(path), m_nullProxy(this) { m_root = std::make_shared(); @@ -43,8 +128,8 @@ bool ProjectModel::importGroupData(const QString& groupName, const amuse::AudioG m_projectDatabase.setIdDatabases(); amuse::AudioGroupDatabase& grp = m_groups.insert(std::make_pair(groupName, data)).first->second; - grp.setIdDatabases(); - amuse::AudioGroupProject::BootstrapObjectIDs(data); + //grp.setIdDatabases(); + //amuse::AudioGroupProject::BootstrapObjectIDs(data); if (!MkPath(m_dir.path(), messenger)) return false; @@ -120,15 +205,13 @@ void ProjectModel::_resetModelData() gn.makeChild(grp.first, grp.second.get()); for (const auto& grp : SortUnorderedMap(sfxGroups)) gn.makeChild(grp.first, grp.second.get()); - if (soundMacros.size()) { CollectionNode& col = - gn.makeChild(tr("Sound Macros"), QIcon(":/icons/IconSoundMacro.svg")); + gn.makeChild(tr("Sound Macros"), QIcon(":/icons/IconSoundMacro.svg"), INode::Type::SoundMacro); col.reserve(soundMacros.size()); for (const auto& macro : SortUnorderedMap(soundMacros)) col.makeChild(macro.first, macro.second.get()); } - if (tables.size()) { auto tablesSort = SortUnorderedMap(tables); size_t ADSRCount = 0; @@ -141,10 +224,9 @@ void ProjectModel::_resetModelData() else if (tp == amuse::ITable::Type::Curve) curveCount += 1; } - if (ADSRCount) { CollectionNode& col = - gn.makeChild(tr("ADSRs"), QIcon(":/icons/IconADSR.svg")); + gn.makeChild(tr("ADSRs"), QIcon(":/icons/IconADSR.svg"), INode::Type::ADSR); col.reserve(ADSRCount); for (auto& t : tablesSort) { @@ -153,10 +235,9 @@ void ProjectModel::_resetModelData() col.makeChild(t.first, t.second.get()); } } - if (curveCount) { CollectionNode& col = - gn.makeChild(tr("Curves"), QIcon(":/icons/IconCurve.svg")); + gn.makeChild(tr("Curves"), QIcon(":/icons/IconCurve.svg"), INode::Type::Curve); col.reserve(curveCount); for (auto& t : tablesSort) { @@ -166,18 +247,16 @@ void ProjectModel::_resetModelData() } } } - if (keymaps.size()) { CollectionNode& col = - gn.makeChild(tr("Keymaps"), QIcon(":/icons/IconKeymap.svg")); + gn.makeChild(tr("Keymaps"), QIcon(":/icons/IconKeymap.svg"), INode::Type::Keymap); col.reserve(keymaps.size()); for (auto& keymap : SortUnorderedMap(keymaps)) col.makeChild(keymap.first, keymap.second.get()); } - if (layers.size()) { CollectionNode& col = - gn.makeChild(tr("Layers"), QIcon(":/icons/IconLayers.svg")); + gn.makeChild(tr("Layers"), QIcon(":/icons/IconLayers.svg"), INode::Type::Layer); col.reserve(layers.size()); for (auto& keymap : SortUnorderedMap(layers)) col.makeChild(keymap.first, keymap.second.get()); @@ -195,8 +274,30 @@ void ProjectModel::ensureModelData() } } +QModelIndex ProjectModel::proxyCreateIndex(int arow, int acolumn, void *adata) const +{ + if (arow < 0) + { + INode* childItem = static_cast(adata); + return createIndex(childItem->parent()->childCount(), acolumn, adata); + } + return createIndex(arow, acolumn, adata); +} + QModelIndex ProjectModel::index(int row, int column, const QModelIndex& parent) const { + if (row < 0) + { + INode* parentItem; + if (!parent.isValid()) + parentItem = m_root.get(); + else + parentItem = static_cast(parent.internalPointer()); + + INode* childItem = parentItem->nullChild(); + return createIndex(childItem->row(), column, childItem); + } + if (!hasIndex(row, column, parent)) return QModelIndex(); @@ -213,6 +314,13 @@ QModelIndex ProjectModel::index(int row, int column, const QModelIndex& parent) return QModelIndex(); } +QModelIndex ProjectModel::index(INode* node) const +{ + if (node == m_root.get()) + return QModelIndex(); + return createIndex(node->row(), 0, node); +} + QModelIndex ProjectModel::parent(const QModelIndex& index) const { if (!index.isValid()) @@ -267,22 +375,73 @@ Qt::ItemFlags ProjectModel::flags(const QModelIndex& index) const if (!index.isValid()) return 0; - return QAbstractItemModel::flags(index); + return static_cast(index.internalPointer())->flags(); } ProjectModel::INode* ProjectModel::node(const QModelIndex& index) const { if (!index.isValid()) - return nullptr; + return m_root.get(); return static_cast(index.internalPointer()); } -bool ProjectModel::canDelete() const +ProjectModel::GroupNode* ProjectModel::getGroupNode(INode* node) const { - return false; + if (!node) + return nullptr; + if (node->type() == INode::Type::Group) + return static_cast(node); + return getGroupNode(node->parent()); } -void ProjectModel::del() +bool ProjectModel::canEdit(const QModelIndex& index) const { - + if (!index.isValid()) + return false; + return (static_cast(index.internalPointer())->flags() & Qt::ItemIsSelectable) != Qt::NoItemFlags; +} + +class DeleteNodeUndoCommand : public QUndoCommand +{ + QModelIndex m_deleteIdx; + std::shared_ptr m_node; +public: + DeleteNodeUndoCommand(const QModelIndex& index) + : QUndoCommand(QUndoStack::tr("Delete %1").arg(index.data().toString())), m_deleteIdx(index) {} + void undo() + { + g_MainWindow->projectModel()->_undoDel(m_deleteIdx, std::move(m_node)); + m_node.reset(); + } + void redo() + { + m_node = g_MainWindow->projectModel()->_redoDel(m_deleteIdx); + } +}; + +void ProjectModel::_undoDel(const QModelIndex& index, std::shared_ptr&& n) +{ + beginInsertRows(index.parent(), index.row(), index.row()); + node(index.parent())->insertChild(index.row(), std::move(n)); + endInsertRows(); +} + +std::shared_ptr ProjectModel::_redoDel(const QModelIndex& index) +{ + node(index)->depthTraverse([](INode* node) + { + g_MainWindow->aboutToDeleteNode(node); + return true; + }); + beginRemoveRows(index.parent(), index.row(), index.row()); + std::shared_ptr ret = node(index.parent())->removeChild(index.row()); + endRemoveRows(); + return ret; +} + +void ProjectModel::del(const QModelIndex& index) +{ + if (!index.isValid()) + return; + g_MainWindow->pushUndoCommand(new DeleteNodeUndoCommand(index)); } diff --git a/Editor/ProjectModel.hpp b/Editor/ProjectModel.hpp index fa125be..0eb3602 100644 --- a/Editor/ProjectModel.hpp +++ b/Editor/ProjectModel.hpp @@ -2,6 +2,7 @@ #define AMUSE_PROJECT_MODEL_HPP #include +#include #include #include #include @@ -12,6 +13,20 @@ #include "amuse/AudioGroupPool.hpp" #include "amuse/AudioGroupSampleDirectory.hpp" +class ProjectModel; + +class NullItemProxyModel : public QIdentityProxyModel +{ + Q_OBJECT +public: + explicit NullItemProxyModel(ProjectModel* source); + QModelIndex mapFromSource(const QModelIndex& sourceIndex) const; + QModelIndex mapToSource(const QModelIndex& proxyIndex) const; + int rowCount(const QModelIndex& parent) const; + QModelIndex index(int row, int column, const QModelIndex& parent) const; + QVariant data(const QModelIndex& proxyIndex, int role) const; +}; + class ProjectModel : public QAbstractItemModel { Q_OBJECT @@ -25,6 +40,7 @@ public: private: QDir m_dir; + NullItemProxyModel m_nullProxy; amuse::ProjectDatabase m_projectDatabase; std::map m_groups; @@ -35,6 +51,7 @@ public: public: enum class Type { + Null, Root, Group, // Top-level group SongGroup, @@ -46,30 +63,79 @@ public: Keymap, Layer }; - private: + protected: INode* m_parent; std::vector> m_children; + std::unique_ptr m_nullChild; int m_row; public: virtual ~INode() = default; - INode(INode* parent, int row) : m_parent(parent), m_row(row) {} + INode(INode* parent) : m_parent(parent), m_row(0) + { + /* ONLY USED BY NULL NODE! */ + } + INode(INode* parent, int row); int childCount() const { return int(m_children.size()); } - INode* child(int row) const { return m_children[row].get(); } + INode* child(int row) const + { + if (row == m_children.size()) + return nullChild(); + return m_children[row].get(); + } + INode* nullChild() const { return m_nullChild.get(); } INode* parent() const { return m_parent; } int row() const { return m_row; } + void reindexRows(int row) + { + for (auto it = m_children.begin() + row; it != m_children.end(); ++it) + (*it)->m_row = row++; + m_nullChild->m_row = row; + } + + void insertChild(int row, std::shared_ptr&& n) + { + m_children.insert(m_children.begin() + row, std::move(n)); + reindexRows(row); + } + std::shared_ptr removeChild(int row) + { + std::shared_ptr ret = std::move(m_children[row]); + m_children.erase(m_children.begin() + row); + reindexRows(row); + return ret; + } + void reserve(size_t sz) { m_children.reserve(sz); } template T& makeChild(_Args&&... args) { m_children.push_back(std::make_shared(this, m_children.size(), std::forward<_Args>(args)...)); + m_nullChild->m_row = int(m_children.size()); return static_cast(*m_children.back()); } + bool depthTraverse(const std::function& func) + { + for (auto& n : m_children) + if (!n->depthTraverse(func)) + break; + return func(this); + } + virtual Type type() const = 0; virtual QString text() const = 0; virtual QIcon icon() const = 0; + virtual Qt::ItemFlags flags() const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } + }; + struct NullNode : INode + { + NullNode(INode* parent) : INode(parent) {} + + Type type() const { return Type::Null; } + QString text() const { return {}; } + QIcon icon() const { return {}; } }; struct RootNode : INode { @@ -78,7 +144,9 @@ public: Type type() const { return Type::Root; } QString text() const { return {}; } QIcon icon() const { return {}; } + Qt::ItemFlags flags() const { return Qt::ItemIsEnabled; } }; + struct CollectionNode; struct GroupNode : INode { std::map::iterator m_it; @@ -90,6 +158,9 @@ public: QString text() const { return m_it->first; } QIcon icon() const { return Icon; } + CollectionNode* getCollectionOfType(Type tp) const; + amuse::AudioGroupDatabase* getAudioGroup() const { return &m_it->second; } + std::shared_ptr shared_from_this() { return std::static_pointer_cast(INode::shared_from_this()); } }; @@ -125,28 +196,42 @@ public: std::shared_ptr shared_from_this() { return std::static_pointer_cast(INode::shared_from_this()); } }; + struct BasePoolObjectNode; struct CollectionNode : INode { QString m_name; QIcon m_icon; - CollectionNode(INode* parent, int row, const QString& name, const QIcon& icon) - : INode(parent, row), m_name(name), m_icon(icon) {} + Type m_collectionType; + CollectionNode(INode* parent, int row, const QString& name, const QIcon& icon, Type collectionType) + : INode(parent, row), m_name(name), m_icon(icon), m_collectionType(collectionType) {} Type type() const { return Type::Collection; } QString text() const { return m_name; } QIcon icon() const { return m_icon; } + Qt::ItemFlags flags() const { return Qt::ItemIsEnabled; } + + Type collectionType() const { return m_collectionType; } + int indexOfId(amuse::ObjectId id) const; + amuse::ObjectId idOfIndex(int idx) const; + BasePoolObjectNode* nodeOfIndex(int idx) const; std::shared_ptr shared_from_this() { return std::static_pointer_cast(INode::shared_from_this()); } }; - template - struct PoolObjectNode : INode + struct BasePoolObjectNode : INode + { + amuse::ObjectId m_id; + BasePoolObjectNode(INode* parent, int row, amuse::ObjectId id) + : INode(parent, row), m_id(id) {} + amuse::ObjectId id() const { return m_id; } + }; + template + struct PoolObjectNode : BasePoolObjectNode { - ID m_id; QString m_name; std::shared_ptr m_obj; PoolObjectNode(INode* parent, int row, ID id, std::shared_ptr obj) - : INode(parent, row), m_id(id), m_name(ID::CurNameDB->resolveNameFromId(id).data()), m_obj(obj) {} + : BasePoolObjectNode(parent, row, id), m_name(ID::CurNameDB->resolveNameFromId(id).data()), m_obj(obj) {} Type type() const { return TP; } QString text() const { return m_name; } @@ -177,22 +262,23 @@ public: void ensureModelData(); + QModelIndex proxyCreateIndex(int arow, int acolumn, void *adata) const; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const; + QModelIndex index(INode* node) const; QModelIndex parent(const QModelIndex& child) const; int rowCount(const QModelIndex& parent = QModelIndex()) const; int columnCount(const QModelIndex& parent = QModelIndex()) const; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; Qt::ItemFlags flags(const QModelIndex& index) const; INode* node(const QModelIndex& index) const; + GroupNode* getGroupNode(INode* node) const; + bool canEdit(const QModelIndex& index) const; + void _undoDel(const QModelIndex& index, std::shared_ptr&& node); + std::shared_ptr _redoDel(const QModelIndex& index); + void del(const QModelIndex& index); QString path() const { return m_dir.path(); } - bool canDelete() const; - -public slots: - void del(); - -signals: - void canDeleteChanged(bool canDelete); + NullItemProxyModel* getNullProxy() { return &m_nullProxy; } }; diff --git a/Editor/SoundMacroEditor.cpp b/Editor/SoundMacroEditor.cpp index 1a3f67d..10cd337 100644 --- a/Editor/SoundMacroEditor.cpp +++ b/Editor/SoundMacroEditor.cpp @@ -10,8 +10,124 @@ #include #include -CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::CmdOp op, QWidget* parent) -: QWidget(parent), m_cmd(cmd), m_introspection(amuse::SoundMacro::GetCmdIntrospection(op)) +FieldProjectNode::FieldProjectNode(ProjectModel::CollectionNode* collection, QWidget* parent) +: FieldComboBox(parent), m_collection(collection) +{ + ProjectModel* model = g_MainWindow->projectModel(); + setModel(model->getNullProxy()); + setRootModelIndex(model->getNullProxy()->mapFromSource(model->index(collection))); +} + +TargetButton::TargetButton(QWidget* parent) +: QPushButton(parent) +{ + QIcon targetIcon(QStringLiteral(":/icons/IconSoundMacroTarget.svg")); + targetIcon.addFile(QStringLiteral(":/icons/IconSoundMacroTargetDisabled.svg"), QSize(), QIcon::Disabled); + setIcon(targetIcon); + setToolTip(tr("Set step with target click")); + setFixedSize(29, 29); +} + +SoundMacroEditor* FieldSoundMacroStep::getEditor() const +{ + return qobject_cast( + parentWidget()->parentWidget()->parentWidget()-> + parentWidget()->parentWidget()->parentWidget()->parentWidget()); +} + +SoundMacroListing* FieldSoundMacroStep::getListing() const +{ + return qobject_cast( + parentWidget()->parentWidget()->parentWidget()); +} + +void FieldSoundMacroStep::targetPressed() +{ + ProjectModel::SoundMacroNode* node = nullptr; + if (m_macroField) + { + int val = m_macroField->currentIndex(); + if (val != 0) + node = static_cast(m_macroField->collection()->nodeOfIndex(val - 1)); + } + + if (!m_macroField || node == getListing()->currentNode()) + if (SoundMacroEditor* editor = getEditor()) + editor->beginStepTarget(this); +} + +void FieldSoundMacroStep::updateMacroField() +{ + if (!m_macroField) + { + int numCmds = int(static_cast( + getListing()->currentNode())->m_obj->m_cmds.size()); + m_spinBox.setMaximum(numCmds - 1); + m_spinBox.setDisabled(false); + m_targetButton.setDisabled(false); + return; + } + + int val = m_macroField->currentIndex(); + if (val == 0) + { + m_spinBox.setValue(0); + m_spinBox.setDisabled(true); + m_targetButton.setDisabled(true); + } + else + { + ProjectModel::SoundMacroNode* node = static_cast( + m_macroField->collection()->nodeOfIndex(val - 1)); + int numCmds = int(node->m_obj->m_cmds.size()); + m_spinBox.setMaximum(numCmds - 1); + m_spinBox.setDisabled(false); + m_targetButton.setEnabled(node == getListing()->currentNode()); + } +} + +void FieldSoundMacroStep::setIndex(int index) +{ + m_targetButton.setDown(false); + m_spinBox.setValue(index); + if (SoundMacroEditor* editor = getEditor()) + editor->endStepTarget(); +} + +void FieldSoundMacroStep::cancel() +{ + m_targetButton.setDown(false); + if (SoundMacroEditor* editor = getEditor()) + editor->endStepTarget(); +} + +FieldSoundMacroStep::~FieldSoundMacroStep() +{ + if (SoundMacroEditor* editor = getEditor()) + if (editor->m_targetField == this) + editor->endStepTarget(); +} + +FieldSoundMacroStep::FieldSoundMacroStep(FieldProjectNode* macroField, QWidget* parent) +: QWidget(parent), m_macroField(macroField) +{ + QHBoxLayout* layout = new QHBoxLayout; + layout->setContentsMargins(QMargins()); + layout->setSpacing(0); + layout->addWidget(&m_spinBox); + layout->addWidget(&m_targetButton); + m_spinBox.setMinimum(0); + m_spinBox.setDisabled(true); + m_targetButton.setDisabled(true); + connect(&m_spinBox, SIGNAL(valueChanged(int)), this, SIGNAL(valueChanged(int))); + connect(&m_targetButton, SIGNAL(pressed()), this, SLOT(targetPressed())); + if (macroField) + connect(macroField, SIGNAL(currentIndexChanged(int)), this, SLOT(updateMacroField())); + setLayout(layout); +} + +CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::CmdOp op, SoundMacroListing* listing) +: QWidget(nullptr), m_cmd(cmd), m_introspection(amuse::SoundMacro::GetCmdIntrospection(op)) { QFont titleFont = m_titleLabel.font(); titleFont.setWeight(QFont::Bold); @@ -44,6 +160,7 @@ CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::Cm m_deleteButton.setFixedSize(21, 21); m_deleteButton.setIcon(QIcon(QStringLiteral(":/icons/IconSoundMacroDelete.svg"))); m_deleteButton.setFlat(true); + m_deleteButton.setToolTip(tr("Delete this SoundMacro")); connect(&m_deleteButton, SIGNAL(clicked(bool)), this, SLOT(deleteClicked())); headLayout->addWidget(&m_deleteButton); } @@ -58,6 +175,7 @@ CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::Cm { m_titleLabel.setText(tr(m_introspection->m_name.data())); m_titleLabel.setToolTip(tr(m_introspection->m_description.data())); + FieldProjectNode* nf = nullptr; for (int f = 0; f < 7; ++f) { const amuse::SoundMacro::CmdIntrospection::Field& field = m_introspection->m_fields[f]; @@ -89,6 +207,7 @@ CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::Cm sb->setProperty("fieldName", fieldName); sb->setMinimum(int(field.m_min)); sb->setMaximum(int(field.m_max)); + sb->setToolTip(QStringLiteral("[%1,%2]").arg(int(field.m_min)).arg(int(field.m_max))); switch (field.m_tp) { case amuse::SoundMacro::CmdIntrospection::Field::Type::Int8: @@ -116,6 +235,44 @@ CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::Cm layout->addWidget(sb, 1, f); break; } + case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroId: + case amuse::SoundMacro::CmdIntrospection::Field::Type::TableId: + { + ProjectModel::INode::Type collectionType; + if (field.m_tp == amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroId) + { + collectionType = ProjectModel::INode::Type::SoundMacro; + } + else if (field.m_tp == amuse::SoundMacro::CmdIntrospection::Field::Type::TableId) + { + if (!field.m_name.compare("ADSR")) + collectionType = ProjectModel::INode::Type::ADSR; + else + collectionType = ProjectModel::INode::Type::Curve; + } + auto* collection = g_MainWindow->projectModel()->getGroupNode(listing->currentNode())-> + getCollectionOfType(collectionType); + nf = new FieldProjectNode(collection); + nf->setProperty("fieldIndex", f); + nf->setProperty("fieldName", fieldName); + int index = collection->indexOfId( + amuse::AccessField>(m_cmd, field).id); + nf->setCurrentIndex(index < 0 ? 0 : index + 1); + connect(nf, SIGNAL(currentIndexChanged(int)), this, SLOT(nodeChanged(int))); + layout->addWidget(nf, 1, f); + break; + } + case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep: + { + FieldSoundMacroStep* sb = new FieldSoundMacroStep(nf); + sb->setProperty("fieldIndex", f); + sb->setProperty("fieldName", fieldName); + sb->m_spinBox.setValue(amuse::AccessField>(m_cmd, field).step); + connect(sb, SIGNAL(valueChanged(int)), this, SLOT(numChanged(int))); + layout->addWidget(sb, 1, f); + m_stepField = sb; + break; + } case amuse::SoundMacro::CmdIntrospection::Field::Type::Choice: { FieldComboBox* cb = new FieldComboBox; @@ -128,7 +285,7 @@ CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::Cm cb->addItem(tr(field.m_choices[j].data())); } cb->setCurrentIndex(int(amuse::AccessField(m_cmd, field))); - connect(cb, SIGNAL(currentIndexChanged(int)), this, SLOT(choiceChanged(int))); + connect(cb, SIGNAL(currentIndexChanged(int)), this, SLOT(numChanged(int))); layout->addWidget(cb, 1, f); break; } @@ -144,11 +301,11 @@ CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::Cm setLayout(mainLayout); } -CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, QWidget* parent) -: CommandWidget(cmd, cmd->Isa(), parent) {} +CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, SoundMacroListing* listing) +: CommandWidget(cmd, cmd->Isa(), listing) {} -CommandWidget::CommandWidget(amuse::SoundMacro::CmdOp op, QWidget* parent) -: CommandWidget(nullptr, op, parent) {} +CommandWidget::CommandWidget(amuse::SoundMacro::CmdOp op, SoundMacroListing* listing) +: CommandWidget(nullptr, op, listing) {} class ValChangedUndoCommand : public EditorUndoCommand { @@ -189,6 +346,12 @@ public: case amuse::SoundMacro::CmdIntrospection::Field::Type::UInt32: amuse::AccessField(m_cmd, m_field) = uint32_t(m_undoVal); break; + case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroId: + case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep: + case amuse::SoundMacro::CmdIntrospection::Field::Type::TableId: + case amuse::SoundMacro::CmdIntrospection::Field::Type::SampleId: + amuse::AccessField>(m_cmd, m_field).id = uint16_t(m_undoVal); + break; default: break; } @@ -227,6 +390,13 @@ public: m_undoVal = amuse::AccessField(m_cmd, m_field); amuse::AccessField(m_cmd, m_field) = uint32_t(m_redoVal); break; + case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroId: + case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep: + case amuse::SoundMacro::CmdIntrospection::Field::Type::TableId: + case amuse::SoundMacro::CmdIntrospection::Field::Type::SampleId: + m_undoVal = amuse::AccessField>(m_cmd, m_field).id; + amuse::AccessField>(m_cmd, m_field).id = uint16_t(m_redoVal); + break; default: break; } @@ -250,11 +420,10 @@ void CommandWidget::boolChanged(int state) { if (m_introspection) { - QCheckBox* cb = static_cast(sender()); const amuse::SoundMacro::CmdIntrospection::Field& field = - m_introspection->m_fields[cb->property("fieldIndex").toInt()]; - g_MainWindow->pushUndoCommand(new ValChangedUndoCommand(m_cmd, cb->property("fieldName").toString(), field, - state == Qt::Checked, getParent()->m_node)); + m_introspection->m_fields[sender()->property("fieldIndex").toInt()]; + g_MainWindow->pushUndoCommand(new ValChangedUndoCommand(m_cmd, sender()->property("fieldName").toString(), + field, state == Qt::Checked, getParent()->m_node)); } } @@ -262,23 +431,23 @@ void CommandWidget::numChanged(int value) { if (m_introspection) { - FieldSpinBox* sb = static_cast(sender()); const amuse::SoundMacro::CmdIntrospection::Field& field = - m_introspection->m_fields[sb->property("fieldIndex").toInt()]; - g_MainWindow->pushUndoCommand(new ValChangedUndoCommand(m_cmd, sb->property("fieldName").toString(), field, - value, getParent()->m_node)); + m_introspection->m_fields[sender()->property("fieldIndex").toInt()]; + g_MainWindow->pushUndoCommand(new ValChangedUndoCommand(m_cmd, sender()->property("fieldName").toString(), + field, value, getParent()->m_node)); } } -void CommandWidget::choiceChanged(int choice) +void CommandWidget::nodeChanged(int value) { if (m_introspection) { - FieldComboBox* cb = static_cast(sender()); + FieldProjectNode* fieldW = static_cast(sender()); + int v = value == 0 ? 65535 : fieldW->collection()->idOfIndex(value - 1).id; const amuse::SoundMacro::CmdIntrospection::Field& field = - m_introspection->m_fields[cb->property("fieldIndex").toInt()]; - g_MainWindow->pushUndoCommand(new ValChangedUndoCommand(m_cmd, cb->property("fieldName").toString(), field, - choice, getParent()->m_node)); + m_introspection->m_fields[fieldW->property("fieldIndex").toInt()]; + g_MainWindow->pushUndoCommand(new ValChangedUndoCommand(m_cmd, fieldW->property("fieldName").toString(), + field, v, getParent()->m_node)); } } @@ -293,6 +462,8 @@ void CommandWidget::setIndex(int index) { m_index = index; m_numberText.setText(QString::number(index)); + if (m_stepField) + m_stepField->updateMacroField(); update(); } @@ -393,10 +564,10 @@ CommandWidgetContainer::CommandWidgetContainer(CommandWidget* child, QWidget* pa { setMinimumHeight(100); setContentsMargins(QMargins()); - QBoxLayout* outerLayout = new QVBoxLayout; + QVBoxLayout* outerLayout = new QVBoxLayout; + outerLayout->setAlignment(Qt::AlignBottom); outerLayout->setContentsMargins(QMargins()); outerLayout->setSpacing(0); - outerLayout->addStretch(); outerLayout->addWidget(child); setLayout(outerLayout); } @@ -472,13 +643,13 @@ public: void undo() { m_undid = true; - std::static_pointer_cast(m_node)-> + static_cast(m_node.get())-> m_obj->swapPositions(m_a, m_b); EditorUndoCommand::undo(); } void redo() { - std::static_pointer_cast(m_node)-> + static_cast(m_node.get())-> m_obj->swapPositions(m_a, m_b); if (m_undid) EditorUndoCommand::redo(); @@ -605,7 +776,7 @@ public: : EditorUndoCommand(node, QUndoStack::tr("Insert %1").arg(text)), m_insertIdx(insertIdx) {} void undo() { - m_cmd = std::static_pointer_cast(m_node)-> + m_cmd = static_cast(m_node.get())-> m_obj->deleteCmd(m_insertIdx); EditorUndoCommand::undo(); } @@ -613,7 +784,7 @@ public: { if (!m_cmd) return; - std::static_pointer_cast(m_node)-> + static_cast(m_node.get())-> m_obj->insertCmd(m_insertIdx, std::move(m_cmd)); m_cmd.reset(); EditorUndoCommand::redo(); @@ -644,7 +815,7 @@ void SoundMacroListing::insert(amuse::SoundMacro::CmdOp op, const QString& text) g_MainWindow->pushUndoCommand(new InsertCommandUndoCommand(insertIdx, text, m_node)); m_layout->insertWidget(insertIdx, - new CommandWidgetContainer(new CommandWidget(m_node->m_obj->insertNewCmd(insertIdx, op)))); + new CommandWidgetContainer(new CommandWidget(m_node->m_obj->insertNewCmd(insertIdx, op), this))); stopAutoscroll(); reindex(); @@ -661,14 +832,14 @@ public: void undo() { m_undid = true; - std::static_pointer_cast(m_node)-> + static_cast(m_node.get())-> m_obj->insertCmd(m_deleteIdx, std::move(m_cmd)); m_cmd.reset(); EditorUndoCommand::undo(); } void redo() { - m_cmd = std::static_pointer_cast(m_node)-> + m_cmd = static_cast(m_node.get())-> m_obj->deleteCmd(m_deleteIdx); if (m_undid) EditorUndoCommand::redo(); @@ -712,9 +883,10 @@ bool SoundMacroListing::loadData(ProjectModel::SoundMacroNode* node) { if (cmd->Isa() == amuse::SoundMacro::CmdOp::End) break; - m_layout->insertWidget(i++, new CommandWidgetContainer(new CommandWidget(cmd.get()))); + m_layout->insertWidget(i++, new CommandWidgetContainer(new CommandWidget(cmd.get(), this))); } reindex(); + update(); return true; } @@ -723,12 +895,18 @@ void SoundMacroListing::unloadData() m_node.reset(); clear(); reindex(); + update(); +} + +ProjectModel::INode* SoundMacroListing::currentNode() const +{ + return m_node.get(); } SoundMacroListing::SoundMacroListing(QWidget* parent) : QWidget(parent), m_layout(new QVBoxLayout) { - m_layout->addWidget(new CommandWidgetContainer(new CommandWidget(amuse::SoundMacro::CmdOp::End))); + m_layout->addWidget(new CommandWidgetContainer(new CommandWidget(amuse::SoundMacro::CmdOp::End, this))); m_layout->addStretch(); setLayout(m_layout); reindex(); @@ -855,6 +1033,21 @@ void SoundMacroEditor::beginCatalogueDrag(CatalogueItem* item, const QPoint& eve m_draggedItem->show(); } +void SoundMacroEditor::beginStepTarget(FieldSoundMacroStep* stepField) +{ + m_targetField = stepField; + m_catalogue->setDisabled(true); + m_listing->setDisabled(true); + setFocus(); +} + +void SoundMacroEditor::endStepTarget() +{ + m_targetField = nullptr; + m_catalogue->setDisabled(false); + m_listing->setDisabled(false); +} + void SoundMacroEditor::mousePressEvent(QMouseEvent* event) { if (m_catalogue->geometry().contains(event->pos())) @@ -884,11 +1077,19 @@ void SoundMacroEditor::mousePressEvent(QMouseEvent* event) ch = ch->parentWidget(); if (child) { + if (m_targetField) + { + m_targetField->setIndex(m_listing->layout()->indexOf(child->parentWidget())); + return; + } QPoint fromParent2 = child->mapFrom(m_listing, fromParent1); beginCommandDrag(child, event->pos(), fromParent2); } } } + + if (m_targetField) + m_targetField->cancel(); } void SoundMacroEditor::mouseReleaseEvent(QMouseEvent* event) @@ -964,6 +1165,10 @@ void SoundMacroEditor::keyPressEvent(QKeyEvent* event) m_listing->cancelDrag(); m_draggedCmd = nullptr; } + else if (m_targetField) + { + m_targetField->cancel(); + } } } @@ -979,14 +1184,21 @@ void SoundMacroEditor::catalogueDoubleClicked(QTreeWidgetItem* item, int column) bool SoundMacroEditor::loadData(ProjectModel::SoundMacroNode* node) { + endStepTarget(); return m_listing->loadData(node); } void SoundMacroEditor::unloadData() { + endStepTarget(); m_listing->unloadData(); } +ProjectModel::INode* SoundMacroEditor::currentNode() const +{ + return m_listing->currentNode(); +} + SoundMacroEditor::SoundMacroEditor(QWidget* parent) : EditorWidget(parent), m_splitter(new QSplitter), m_listing(new SoundMacroListing), m_catalogue(new SoundMacroCatalogue) diff --git a/Editor/SoundMacroEditor.hpp b/Editor/SoundMacroEditor.hpp index c884b52..103454b 100644 --- a/Editor/SoundMacroEditor.hpp +++ b/Editor/SoundMacroEditor.hpp @@ -13,6 +13,7 @@ #include #include +class SoundMacroEditor; class SoundMacroListing; class CatalogueItem; @@ -20,7 +21,7 @@ class FieldSpinBox : public QSpinBox { Q_OBJECT public: - FieldSpinBox(QWidget* parent = Q_NULLPTR) + explicit FieldSpinBox(QWidget* parent = Q_NULLPTR) : QSpinBox(parent) {} /* Don't scroll */ @@ -29,15 +30,54 @@ public: class FieldComboBox : public QComboBox { -Q_OBJECT + Q_OBJECT public: - FieldComboBox(QWidget* parent = Q_NULLPTR) + explicit FieldComboBox(QWidget* parent = Q_NULLPTR) : QComboBox(parent) {} /* Don't scroll */ void wheelEvent(QWheelEvent* event) { event->ignore(); } }; +class FieldProjectNode : public FieldComboBox +{ + Q_OBJECT + ProjectModel::CollectionNode* m_collection; +public: + explicit FieldProjectNode(ProjectModel::CollectionNode* collection, QWidget* parent = Q_NULLPTR); + ProjectModel::CollectionNode* collection() const { return m_collection; } +}; + +class TargetButton : public QPushButton +{ + Q_OBJECT +public: + explicit TargetButton(QWidget* parent = Q_NULLPTR); + void mouseReleaseEvent(QMouseEvent* event) { event->ignore(); } + void mouseMoveEvent(QMouseEvent* event) { event->ignore(); } +}; + +class FieldSoundMacroStep : public QWidget +{ + friend class CommandWidget; + Q_OBJECT + FieldProjectNode* m_macroField; + FieldSpinBox m_spinBox; + TargetButton m_targetButton; + SoundMacroEditor* getEditor() const; + SoundMacroListing* getListing() const; +signals: + void valueChanged(int); +public slots: + void targetPressed(); + void updateMacroField(); +public: + explicit FieldSoundMacroStep(FieldProjectNode* macroField = Q_NULLPTR, QWidget* parent = Q_NULLPTR); + ~FieldSoundMacroStep(); + void setIndex(int index); + void cancel(); +}; + class CommandWidget : public QWidget { Q_OBJECT @@ -49,18 +89,19 @@ class CommandWidget : public QWidget int m_index = -1; amuse::SoundMacro::ICmd* m_cmd; const amuse::SoundMacro::CmdIntrospection* m_introspection; + FieldSoundMacroStep* m_stepField = nullptr; void setIndex(int index); SoundMacroListing* getParent() const; private slots: void boolChanged(int); void numChanged(int); - void choiceChanged(int); + void nodeChanged(int); void deleteClicked(); private: - CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::CmdOp op, QWidget* parent = Q_NULLPTR); + CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::CmdOp op, SoundMacroListing* listing); public: - CommandWidget(amuse::SoundMacro::ICmd* cmd, QWidget* parent = Q_NULLPTR); - CommandWidget(amuse::SoundMacro::CmdOp op, QWidget* parent = Q_NULLPTR); + CommandWidget(amuse::SoundMacro::ICmd* cmd, SoundMacroListing* listing); + CommandWidget(amuse::SoundMacro::CmdOp op, SoundMacroListing* listing); void paintEvent(QPaintEvent* event); QString getText() const { return m_titleLabel.text(); } }; @@ -113,6 +154,7 @@ public: explicit SoundMacroListing(QWidget* parent = Q_NULLPTR); bool loadData(ProjectModel::SoundMacroNode* node); void unloadData(); + ProjectModel::INode* currentNode() const; void timerEvent(QTimerEvent* event); }; @@ -144,19 +186,24 @@ class SoundMacroEditor : public EditorWidget { Q_OBJECT friend class SoundMacroCatalogue; + friend class FieldSoundMacroStep; QSplitter* m_splitter; SoundMacroListing* m_listing; SoundMacroCatalogue* m_catalogue; CommandWidget* m_draggedCmd = nullptr; CatalogueItem* m_draggedItem = nullptr; + FieldSoundMacroStep* m_targetField = nullptr; QPoint m_draggedPt; int m_dragInsertIdx = -1; void beginCommandDrag(CommandWidget* widget, const QPoint& eventPt, const QPoint& pt); void beginCatalogueDrag(CatalogueItem* item, const QPoint& eventPt, const QPoint& pt); + void beginStepTarget(FieldSoundMacroStep* stepField); + void endStepTarget(); public: explicit SoundMacroEditor(QWidget* parent = Q_NULLPTR); bool loadData(ProjectModel::SoundMacroNode* node); void unloadData(); + ProjectModel::INode* currentNode() const; void mousePressEvent(QMouseEvent* event); void mouseReleaseEvent(QMouseEvent* event); diff --git a/Editor/StatusBarWidget.hpp b/Editor/StatusBarWidget.hpp index 9c71170..c533544 100644 --- a/Editor/StatusBarWidget.hpp +++ b/Editor/StatusBarWidget.hpp @@ -3,6 +3,7 @@ #include #include +#include class StatusBarFocus; @@ -10,15 +11,35 @@ class StatusBarWidget : public QStatusBar { friend class StatusBarFocus; Q_OBJECT - QLabel* m_normalMessage; + QLabel m_normalMessage; + QPushButton m_killButton; + QLabel m_voiceCount; + int m_cachedVoiceCount = -1; StatusBarFocus* m_curFocus = nullptr; + void setKillVisible(bool vis) { m_killButton.setVisible(vis); m_voiceCount.setVisible(vis); } public: explicit StatusBarWidget(QWidget* parent = Q_NULLPTR) : QStatusBar(parent) { - m_normalMessage = new QLabel(this); - addWidget(m_normalMessage); + addWidget(&m_normalMessage); + m_killButton.setIcon(QIcon(QStringLiteral(":/icons/IconKill.svg"))); + m_killButton.setVisible(false); + m_killButton.setToolTip(tr("Immediately kill active voices")); + m_voiceCount.setVisible(false); + addPermanentWidget(&m_voiceCount); + addPermanentWidget(&m_killButton); } - void setNormalMessage(const QString& message) { m_normalMessage->setText(message); } + void setNormalMessage(const QString& message) { m_normalMessage.setText(message); } + void setVoiceCount(int voices) + { + if (voices != m_cachedVoiceCount) + { + m_voiceCount.setText(QString::number(voices)); + m_cachedVoiceCount = voices; + setKillVisible(voices != 0); + } + } + void connectKillClicked(const QObject* receiver, const char* method) + { connect(&m_killButton, SIGNAL(clicked(bool)), receiver, method); } }; class StatusBarFocus : public QObject diff --git a/Editor/resources/IconKill.svg b/Editor/resources/IconKill.svg new file mode 100644 index 0000000..d61d82e --- /dev/null +++ b/Editor/resources/IconKill.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/Editor/resources/IconSoundMacroTarget.svg b/Editor/resources/IconSoundMacroTarget.svg new file mode 100644 index 0000000..53e4e12 --- /dev/null +++ b/Editor/resources/IconSoundMacroTarget.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/Editor/resources/IconSoundMacroTargetDisabled.svg b/Editor/resources/IconSoundMacroTargetDisabled.svg new file mode 100644 index 0000000..988a8d3 --- /dev/null +++ b/Editor/resources/IconSoundMacroTargetDisabled.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/Editor/resources/lang_de.ts b/Editor/resources/lang_de.ts index 63d83a1..2d436e1 100644 --- a/Editor/resources/lang_de.ts +++ b/Editor/resources/lang_de.ts @@ -1,6 +1,14 @@ + + CommandWidget + + + Delete this SoundMacro + + + MainWindow @@ -9,12 +17,12 @@ Amuse - + &File &Datei - + P&roject Projekt @@ -52,7 +60,12 @@ & Wiederholen - + + Recent &Projects + + + + &Cut &Schnitt @@ -147,12 +160,22 @@ Neu und Kurve - + + &Save Project + + + + + &Revert Project + + + + Quit Verlassen - + The directory at '%1' must exist for the Amuse editor. Das Verzeichnis unter '% 1' muss für den Amuse-Editor vorhanden sein. @@ -177,7 +200,7 @@ Es konnte nicht in das Verzeichnis geschrieben werden - + No Audio Devices Found Keine Audiogeräte gefunden @@ -187,22 +210,44 @@ Keine MIDI-Geräte gefunden - + New Project Neues Projekt - + Open Project Offenes Projekt - + The directory at '%1' does not exist. Das Verzeichnis '% 1' existiert nicht. - + + Clear Recent Projects + + + + + + The directory at '%1' must not be empty. + + + + + + Directory empty + + + + + SUSTAIN + + + + Bad Directory Schlechtes Verzeichnis @@ -213,18 +258,18 @@ - + Scanning Project Projekt scannen - + Opening %1 Eröffnung% 1 - + Import Project Projekt importieren @@ -296,30 +341,46 @@ Importieren von% 1 + + ModulationSlider + + + Modulation: %1 + + + + + PitchSlider + + + Pitch: %1 + + + ProjectModel - + Sound Macros Sound-Makros - + ADSRs ADSRs - + Curves Kurven - + Keymaps Schlüsselkarten - + Layers Lagen @@ -340,12 +401,12 @@ QUndoStack - + Change %1 - + Reorder %1 @@ -355,7 +416,8 @@ - + + Delete %1 @@ -363,7 +425,7 @@ SoundMacroCatalogue - + Control Steuerung @@ -433,4 +495,28 @@ Befehle zum Steuern der Lautstärke der Stimme + + StatusBarWidget + + + Immediately kill active voices + + + + + TargetButton + + + Set step with target click + + + + + VelocitySlider + + + Velocity: %1 + + + diff --git a/Editor/resources/resources.qrc b/Editor/resources/resources.qrc index 2ef9622..2340610 100644 --- a/Editor/resources/resources.qrc +++ b/Editor/resources/resources.qrc @@ -20,6 +20,9 @@ IconCurve.svg IconNewCurve.svg IconSoundMacroDelete.svg + IconSoundMacroTarget.svg + IconSoundMacroTargetDisabled.svg + IconKill.svg FaceGrey.svg diff --git a/include/amuse/AudioGroup.hpp b/include/amuse/AudioGroup.hpp index 3bd24f6..4cef010 100644 --- a/include/amuse/AudioGroup.hpp +++ b/include/amuse/AudioGroup.hpp @@ -36,20 +36,23 @@ public: AudioGroupProject& getProj() { return m_proj; } AudioGroupPool& getPool() { return m_pool; } AudioGroupSampleDirectory& getSdir() { return m_sdir; } + + virtual void setIdDatabases() const {} }; -class AudioGroupDatabase : public AudioGroup +class AudioGroupDatabase final : public AudioGroup { - amuse::NameDB m_soundMacroDb; - amuse::NameDB m_sampleDb; - amuse::NameDB m_tableDb; - amuse::NameDB m_keymapDb; - amuse::NameDB m_layersDb; + NameDB m_soundMacroDb; + NameDB m_sampleDb; + NameDB m_tableDb; + NameDB m_keymapDb; + NameDB m_layersDb; public: AudioGroupDatabase() = default; explicit AudioGroupDatabase(const AudioGroupData& data) { + setIdDatabases(); assign(data); } explicit AudioGroupDatabase(SystemStringView groupPath) @@ -58,28 +61,28 @@ public: assign(groupPath); } - void setIdDatabases() + void setIdDatabases() const { - amuse::SoundMacroId::CurNameDB = &m_soundMacroDb; - amuse::SampleId::CurNameDB = &m_sampleDb; - amuse::TableId::CurNameDB = &m_tableDb; - amuse::KeymapId::CurNameDB = &m_keymapDb; - amuse::LayersId::CurNameDB = &m_layersDb; + SoundMacroId::CurNameDB = const_cast(&m_soundMacroDb); + SampleId::CurNameDB = const_cast(&m_sampleDb); + TableId::CurNameDB = const_cast(&m_tableDb); + KeymapId::CurNameDB = const_cast(&m_keymapDb); + LayersId::CurNameDB = const_cast(&m_layersDb); } }; class ProjectDatabase { - amuse::NameDB m_songDb; - amuse::NameDB m_sfxDb; - amuse::NameDB m_groupDb; + NameDB m_songDb; + NameDB m_sfxDb; + NameDB m_groupDb; public: - void setIdDatabases() + void setIdDatabases() const { - amuse::SongId::CurNameDB = &m_songDb; - amuse::SFXId::CurNameDB = &m_sfxDb; - amuse::GroupId::CurNameDB = &m_groupDb; + SongId::CurNameDB = const_cast(&m_songDb); + SFXId::CurNameDB = const_cast(&m_sfxDb); + GroupId::CurNameDB = const_cast(&m_groupDb); } }; } diff --git a/include/amuse/AudioGroupPool.hpp b/include/amuse/AudioGroupPool.hpp index 18f6928..ae0ac02 100644 --- a/include/amuse/AudioGroupPool.hpp +++ b/include/amuse/AudioGroupPool.hpp @@ -153,6 +153,7 @@ struct SoundMacro Int32, UInt32, SoundMacroId, + SoundMacroStep, TableId, SampleId, Choice @@ -200,7 +201,7 @@ struct SoundMacro static const CmdIntrospection Introspective; Value key; SoundMacroIdDNA macro; - Value macroStep; + SoundMacroStepDNA macroStep; bool Do(SoundMacroState& st, Voice& vox) const; CmdOp Isa() const { return CmdOp::SplitKey; } }; @@ -211,7 +212,7 @@ struct SoundMacro static const CmdIntrospection Introspective; Value velocity; SoundMacroIdDNA macro; - Value macroStep; + SoundMacroStepDNA macroStep; bool Do(SoundMacroState& st, Voice& vox) const; CmdOp Isa() const { return CmdOp::SplitVel; } }; @@ -237,7 +238,7 @@ struct SoundMacro Value keyOff; Value random; Value sampleEnd; - Value macroStep; + SoundMacroStepDNA macroStep; Value times; bool Do(SoundMacroState& st, Voice& vox) const; CmdOp Isa() const { return CmdOp::Loop; } @@ -249,7 +250,7 @@ struct SoundMacro static const CmdIntrospection Introspective; Seek<1, athena::SeekOrigin::Current> dummy; SoundMacroIdDNA macro; - Value macroStep; + SoundMacroStepDNA macroStep; bool Do(SoundMacroState& st, Voice& vox) const; CmdOp Isa() const { return CmdOp::Goto; } }; @@ -274,7 +275,7 @@ struct SoundMacro static const CmdIntrospection Introspective; Value addNote; SoundMacroIdDNA macro; - Value macroStep; + SoundMacroStepDNA macroStep; Value priority; Value maxVoices; bool Do(SoundMacroState& st, Voice& vox) const; @@ -297,7 +298,7 @@ struct SoundMacro static const CmdIntrospection Introspective; Value modValue; SoundMacroIdDNA macro; - Value macroStep; + SoundMacroStepDNA macroStep; bool Do(SoundMacroState& st, Voice& vox) const; CmdOp Isa() const { return CmdOp::SplitMod; } }; @@ -398,7 +399,7 @@ struct SoundMacro static const CmdIntrospection Introspective; Value rnd; SoundMacroIdDNA macro; - Value macroStep; + SoundMacroStepDNA macroStep; bool Do(SoundMacroState& st, Voice& vox) const; CmdOp Isa() const { return CmdOp::SplitRnd; } }; @@ -623,7 +624,7 @@ struct SoundMacro static const CmdIntrospection Introspective; Seek<1, athena::SeekOrigin::Current> seek; SoundMacroIdDNA macro; - Value macroStep; + SoundMacroStepDNA macroStep; bool Do(SoundMacroState& st, Voice& vox) const; CmdOp Isa() const { return CmdOp::GoSub; } }; @@ -640,7 +641,7 @@ struct SoundMacro }; Value event; SoundMacroIdDNA macro; - Value macroStep; + SoundMacroStepDNA macroStep; bool Do(SoundMacroState& st, Voice& vox) const; CmdOp Isa() const { return CmdOp::TrapEvent; } }; @@ -1098,7 +1099,7 @@ struct SoundMacro Value varCtrlB; Value b; Value notEq; - Value macroStep; + SoundMacroStepDNA macroStep; bool Do(SoundMacroState& st, Voice& vox) const; CmdOp Isa() const { return CmdOp::IfEqual; } }; @@ -1112,7 +1113,7 @@ struct SoundMacro Value varCtrlB; Value b; Value notLt; - Value macroStep; + SoundMacroStepDNA macroStep; bool Do(SoundMacroState& st, Voice& vox) const; CmdOp Isa() const { return CmdOp::IfLess; } }; diff --git a/include/amuse/BooBackend.hpp b/include/amuse/BooBackend.hpp index d9fd706..5ee5a3f 100644 --- a/include/amuse/BooBackend.hpp +++ b/include/amuse/BooBackend.hpp @@ -71,6 +71,7 @@ public: class BooBackendMIDIReader : public IMIDIReader, public boo::IMIDIReader { friend class BooBackendVoiceAllocator; +protected: Engine& m_engine; std::unique_ptr m_midiIn; boo::MIDIDecoder m_decoder; @@ -119,6 +120,7 @@ public: class BooBackendVoiceAllocator : public IBackendVoiceAllocator, public boo::IAudioVoiceEngineCallback { friend class BooBackendMIDIReader; +protected: boo::IAudioVoiceEngine& m_booEngine; Engine* m_cbInterface = nullptr; diff --git a/include/amuse/Common.hpp b/include/amuse/Common.hpp index ab2bfdf..bbbb524 100644 --- a/include/amuse/Common.hpp +++ b/include/amuse/Common.hpp @@ -104,6 +104,26 @@ PageObjectIdDNA : BigDNA operator ObjectId() const { return id; } }; +struct SoundMacroStep +{ + uint16_t step = 0; + operator uint16_t() const { return step; } + SoundMacroStep() = default; + SoundMacroStep(uint16_t idIn) : step(idIn) {} + SoundMacroStep& operator=(uint16_t idIn) { step = idIn; return *this; } +}; + +template +struct AT_SPECIALIZE_PARMS(athena::Endian::Big, athena::Endian::Little) +SoundMacroStepDNA : BigDNA +{ + AT_DECL_EXPLICIT_DNA_YAML + SoundMacroStep step; + SoundMacroStepDNA() = default; + SoundMacroStepDNA(SoundMacroStep idIn) : step(idIn) {} + operator SoundMacroStep() const { return step; } +}; + struct LittleUInt24 : LittleDNA { AT_DECL_EXPLICIT_DNA_YAML diff --git a/include/amuse/Engine.hpp b/include/amuse/Engine.hpp index ab01099..6e9fca4 100644 --- a/include/amuse/Engine.hpp +++ b/include/amuse/Engine.hpp @@ -96,6 +96,15 @@ public: return fxStart(sfxId, vol, pan, m_defaultStudio); } + /** Start SoundMacro node playing directly (for editor use) */ + std::shared_ptr macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key, + uint8_t vel, uint8_t mod, std::weak_ptr smx); + std::shared_ptr macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key, + uint8_t vel, uint8_t mod) + { + return macroStart(group, id, key, vel, mod, m_defaultStudio); + } + /** Start soundFX playing from loaded audio groups, attach to positional emitter */ std::shared_ptr addEmitter(const float* pos, const float* dir, float maxDist, float falloff, int sfxId, float minVol, float maxVol, bool doppler, @@ -136,6 +145,9 @@ public: /** Obtain next random number from engine's PRNG */ uint32_t nextRandom() { return m_random(); } + /** Obtain list of active voices */ + std::list>& getActiveVoices() { return m_activeVoices; } + /** Obtain list of active sequencers */ std::list>& getActiveSequencers() { return m_activeSequencers; } diff --git a/lib/AudioGroup.cpp b/lib/AudioGroup.cpp index 8ece0d5..9614ecc 100644 --- a/lib/AudioGroup.cpp +++ b/lib/AudioGroup.cpp @@ -14,6 +14,7 @@ void AudioGroup::assign(const AudioGroupData& data) void AudioGroup::assign(SystemStringView groupPath) { /* Reverse order when loading intermediates */ + m_groupPath = groupPath; m_sdir = AudioGroupSampleDirectory::CreateAudioGroupSampleDirectory(groupPath); m_pool = AudioGroupPool::CreateAudioGroupPool(groupPath); m_proj = AudioGroupProject::CreateAudioGroupProject(groupPath); @@ -32,6 +33,7 @@ const unsigned char* AudioGroup::getSampleData(SampleId sfxId, const AudioGroupS { if (sample->m_looseData) { + setIdDatabases(); #if _WIN32 SystemString basePath = m_groupPath + _S('/') + athena::utility::utf8ToWide(SampleId::CurNameDB->resolveNameFromId(sfxId)); diff --git a/lib/AudioGroupPool.cpp b/lib/AudioGroupPool.cpp index b5959ec..ea89e2a 100644 --- a/lib/AudioGroupPool.cpp +++ b/lib/AudioGroupPool.cpp @@ -61,6 +61,7 @@ struct MakeDefaultCmdOp AccessField(ret.get(), field) = uint32_t(field.m_default); break; case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroId: + case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep: case amuse::SoundMacro::CmdIntrospection::Field::Type::TableId: case amuse::SoundMacro::CmdIntrospection::Field::Type::SampleId: AccessField>(ret.get(), field).id = uint16_t(field.m_default); @@ -153,7 +154,8 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r) objHead.read(r); KeymapDNA kmData; kmData.read(r); - *ret.m_keymaps[objHead.objectId.id] = kmData; + auto& km = ret.m_keymaps[objHead.objectId.id]; + km = std::make_shared(kmData); r.seek(startPos + objHead.size, athena::Begin); } } @@ -166,15 +168,16 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r) ObjectHeader objHead; atInt64 startPos = r.position(); objHead.read(r); - std::vector& lm = *ret.m_layers[objHead.objectId.id]; + auto& lm = ret.m_layers[objHead.objectId.id]; + lm = std::make_shared>(); uint32_t count; athena::io::Read::Do({}, count, r); - lm.reserve(count); + lm->reserve(count); for (uint32_t i = 0; i < count; ++i) { LayerMappingDNA lmData; lmData.read(r); - lm.push_back(lmData); + lm->push_back(lmData); } r.seek(startPos + objHead.size, athena::Begin); } diff --git a/lib/AudioGroupProject.cpp b/lib/AudioGroupProject.cpp index 6624c38..04bfa45 100644 --- a/lib/AudioGroupProject.cpp +++ b/lib/AudioGroupProject.cpp @@ -21,6 +21,29 @@ static bool AtEnd16(athena::io::IStreamReader& r) return v == 0xffff; } +template +static void ReadRangedObjectIds(NameDB* db, athena::io::IStreamReader& r, NameDB::Type tp) +{ + uint16_t id; + athena::io::Read::Do({}, id, r); + if ((id & 0x8000) == 0x8000) + { + uint16_t endId; + athena::io::Read::Do({}, endId, r); + for (uint16_t i = uint16_t(id & 0x7fff); i <= uint16_t(endId & 0x7fff); ++i) + { + ObjectId useId = i; + if (tp == NameDB::Type::Layer) + useId.id |= 0x8000; + db->registerPair(NameDB::generateName(useId, tp), useId); + } + } + else + { + db->registerPair(NameDB::generateName(id, tp), id); + } +} + AudioGroupProject::AudioGroupProject(athena::io::IStreamReader& r, GCNDataTag) { while (!AtEnd32(r)) @@ -28,6 +51,33 @@ AudioGroupProject::AudioGroupProject(athena::io::IStreamReader& r, GCNDataTag) GroupHeader header; header.read(r); + GroupId::CurNameDB->registerPair(NameDB::generateName(header.groupId, NameDB::Type::Group), header.groupId); + + /* Sound Macros */ + r.seek(header.soundMacroIdsOff, athena::Begin); + while (!AtEnd16(r)) + ReadRangedObjectIds(SoundMacroId::CurNameDB, r, NameDB::Type::SoundMacro); + + /* Samples */ + r.seek(header.samplIdsOff, athena::Begin); + while (!AtEnd16(r)) + ReadRangedObjectIds(SampleId::CurNameDB, r, NameDB::Type::Sample); + + /* Tables */ + r.seek(header.tableIdsOff, athena::Begin); + while (!AtEnd16(r)) + ReadRangedObjectIds(TableId::CurNameDB, r, NameDB::Type::Table); + + /* Keymaps */ + r.seek(header.keymapIdsOff, athena::Begin); + while (!AtEnd16(r)) + ReadRangedObjectIds(KeymapId::CurNameDB, r, NameDB::Type::Keymap); + + /* Layers */ + r.seek(header.layerIdsOff, athena::Begin); + while (!AtEnd16(r)) + ReadRangedObjectIds(LayersId::CurNameDB, r, NameDB::Type::Layer); + if (header.type == GroupType::Song) { auto& idx = m_songGroups[header.groupId]; @@ -60,6 +110,7 @@ AudioGroupProject::AudioGroupProject(athena::io::IStreamReader& r, GCNDataTag) std::array& setup = idx->m_midiSetups[songId]; for (int i = 0; i < 16 ; ++i) setup[i].read(r); + SongId::CurNameDB->registerPair(NameDB::generateName(songId, NameDB::Type::Song), songId); } } else if (header.type == GroupType::SFX) @@ -77,6 +128,8 @@ AudioGroupProject::AudioGroupProject(athena::io::IStreamReader& r, GCNDataTag) SFXGroupIndex::SFXEntryDNA entry; entry.read(r); idx->m_sfxEntries[entry.sfxId.id] = entry; + SFXId::CurNameDB->registerPair( + NameDB::generateName(entry.sfxId.id, NameDB::Type::SFX), entry.sfxId.id); } } @@ -96,6 +149,33 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade GroupHeader header; header.read(r); + GroupId::CurNameDB->registerPair(NameDB::generateName(header.groupId, NameDB::Type::Group), header.groupId); + + /* Sound Macros */ + r.seek(subDataOff + header.soundMacroIdsOff, athena::Begin); + while (!AtEnd16(r)) + ReadRangedObjectIds(SoundMacroId::CurNameDB, r, NameDB::Type::SoundMacro); + + /* Samples */ + r.seek(subDataOff + header.samplIdsOff, athena::Begin); + while (!AtEnd16(r)) + ReadRangedObjectIds(SampleId::CurNameDB, r, NameDB::Type::Sample); + + /* Tables */ + r.seek(subDataOff + header.tableIdsOff, athena::Begin); + while (!AtEnd16(r)) + ReadRangedObjectIds(TableId::CurNameDB, r, NameDB::Type::Table); + + /* Keymaps */ + r.seek(subDataOff + header.keymapIdsOff, athena::Begin); + while (!AtEnd16(r)) + ReadRangedObjectIds(KeymapId::CurNameDB, r, NameDB::Type::Keymap); + + /* Layers */ + r.seek(subDataOff + header.layerIdsOff, athena::Begin); + while (!AtEnd16(r)) + ReadRangedObjectIds(LayersId::CurNameDB, r, NameDB::Type::Layer); + if (header.type == GroupType::Song) { auto& idx = ret.m_songGroups[header.groupId]; @@ -131,6 +211,7 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade std::array& setup = idx->m_midiSetups[songId]; for (int i = 0; i < 16 ; ++i) setup[i].read(r); + SongId::CurNameDB->registerPair(NameDB::generateName(songId, NameDB::Type::Song), songId); } } else @@ -167,6 +248,7 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade ent.read(r); setup[i] = ent; } + SongId::CurNameDB->registerPair(NameDB::generateName(songId, NameDB::Type::Song), songId); } } } @@ -187,6 +269,8 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade entry.read(r); r.seek(2, athena::Current); idx->m_sfxEntries[entry.sfxId.id] = entry; + SFXId::CurNameDB->registerPair( + NameDB::generateName(entry.sfxId.id, NameDB::Type::SFX), entry.sfxId.id); } } @@ -323,29 +407,6 @@ AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView gr return ret; } -template -static void ReadRangedObjectIds(NameDB* db, athena::io::IStreamReader& r, NameDB::Type tp) -{ - uint16_t id; - athena::io::Read::Do({}, id, r); - if ((id & 0x8000) == 0x8000) - { - uint16_t endId; - athena::io::Read::Do({}, endId, r); - for (uint16_t i = uint16_t(id & 0x7fff); i <= uint16_t(endId & 0x7fff); ++i) - { - ObjectId useId = i; - if (tp == NameDB::Type::Layer) - useId.id |= 0x8000; - db->registerPair(NameDB::generateName(useId, tp), useId); - } - } - else - { - db->registerPair(NameDB::generateName(id, tp), id); - } -} - void AudioGroupProject::BootstrapObjectIDs(athena::io::IStreamReader& r, GCNDataTag) { while (!AtEnd32(r)) diff --git a/lib/AudioGroupSampleDirectory.cpp b/lib/AudioGroupSampleDirectory.cpp index fe2bd06..fbcae2b 100644 --- a/lib/AudioGroupSampleDirectory.cpp +++ b/lib/AudioGroupSampleDirectory.cpp @@ -45,6 +45,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(athena::io::IStreamReader& EntryDNA ent; ent.read(r); m_entries[ent.m_sfxId] = ent; + SampleId::CurNameDB->registerPair(NameDB::generateName(ent.m_sfxId, NameDB::Type::Sample), ent.m_sfxId); } for (auto& p : m_entries) @@ -68,6 +69,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(athena::io::IStreamReader& MusyX1AbsSdirEntry ent; ent.read(r); m_entries[ent.m_sfxId] = ent; + SampleId::CurNameDB->registerPair(NameDB::generateName(ent.m_sfxId, NameDB::Type::Sample), ent.m_sfxId); } } else @@ -77,6 +79,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(athena::io::IStreamReader& MusyX1SdirEntry ent; ent.read(r); m_entries[ent.m_sfxId] = ent; + SampleId::CurNameDB->registerPair(NameDB::generateName(ent.m_sfxId, NameDB::Type::Sample), ent.m_sfxId); } } @@ -98,6 +101,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(athena::io::IStreamReader& Entry& store = m_entries[ent.m_sfxId]; store = ent; store.m_numSamples |= atUint32(SampleFormat::PCM_PC) << 24; + SampleId::CurNameDB->registerPair(NameDB::generateName(ent.m_sfxId, NameDB::Type::Sample), ent.m_sfxId); } } else @@ -109,6 +113,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(athena::io::IStreamReader& Entry& store = m_entries[ent.m_sfxId]; store = ent; store.m_numSamples |= atUint32(SampleFormat::PCM_PC) << 24; + SampleId::CurNameDB->registerPair(NameDB::generateName(ent.m_sfxId, NameDB::Type::Sample), ent.m_sfxId); } } } diff --git a/lib/Common.cpp b/lib/Common.cpp index 993a143..26c3f22 100644 --- a/lib/Common.cpp +++ b/lib/Common.cpp @@ -194,6 +194,64 @@ const char* PageObjectIdDNA::DNAType() template struct PageObjectIdDNA; template struct PageObjectIdDNA; +template<> template<> +void SoundMacroStepDNA::Enumerate(athena::io::IStreamReader& reader) +{ + step = reader.readUint16Little(); +} +template<> template<> +void SoundMacroStepDNA::Enumerate(athena::io::IStreamWriter& writer) +{ + writer.writeUint16Little(step); +} +template<> template<> +void SoundMacroStepDNA::Enumerate(size_t& sz) +{ + sz += 2; +} +template<> template<> +void SoundMacroStepDNA::Enumerate(athena::io::YAMLDocReader& reader) +{ + step = reader.readUint16(nullptr); +} +template<> template<> +void SoundMacroStepDNA::Enumerate(athena::io::YAMLDocWriter& writer) +{ + writer.writeUint16(nullptr, step); +} +template<> template<> +void SoundMacroStepDNA::Enumerate(athena::io::IStreamReader& reader) +{ + step = reader.readUint16Big(); +} +template<> template<> +void SoundMacroStepDNA::Enumerate(athena::io::IStreamWriter& writer) +{ + writer.writeUint16Big(step); +} +template<> template<> +void SoundMacroStepDNA::Enumerate(size_t& sz) +{ + sz += 2; +} +template<> template<> +void SoundMacroStepDNA::Enumerate(athena::io::YAMLDocReader& reader) +{ + step = reader.readUint16(nullptr); +} +template<> template<> +void SoundMacroStepDNA::Enumerate(athena::io::YAMLDocWriter& writer) +{ + writer.writeUint16(nullptr, step); +} +template +const char* SoundMacroStepDNA::DNAType() +{ + return "amuse::SoundMacroStepDNA"; +} +template struct SoundMacroStepDNA; +template struct SoundMacroStepDNA; + ObjectId NameDB::generateId(Type tp) const { uint16_t maxMatch = uint16_t(tp == Type::Layer ? 0x8000 : 0); diff --git a/lib/Engine.cpp b/lib/Engine.cpp index 00b10f7..86d36aa 100644 --- a/lib/Engine.cpp +++ b/lib/Engine.cpp @@ -280,12 +280,12 @@ std::shared_ptr Engine::fxStart(int sfxId, float vol, float pan, std::wea { auto search = m_sfxLookup.find(sfxId); if (search == m_sfxLookup.end()) - return nullptr; + return {}; AudioGroup* grp = std::get<0>(search->second); const SFXGroupIndex::SFXEntry* entry = std::get<2>(search->second); if (!grp) - return nullptr; + return {}; std::list>::iterator ret = _allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, false, smx); @@ -301,6 +301,25 @@ std::shared_ptr Engine::fxStart(int sfxId, float vol, float pan, std::wea return *ret; } +/** Start SoundMacro node playing directly (for editor use) */ +std::shared_ptr Engine::macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key, uint8_t vel, + uint8_t mod, std::weak_ptr smx) +{ + if (!group) + return {}; + + std::list>::iterator ret = + _allocateVoice(*group, {}, NativeSampleRate, true, false, smx); + + if (!(*ret)->loadMacroObject(id, 0, 1000.f, key, vel, mod)) + { + _destroyVoice(ret); + return {}; + } + + return *ret; +} + /** Start soundFX playing from loaded audio groups, attach to positional emitter */ std::shared_ptr Engine::addEmitter(const float* pos, const float* dir, float maxDist, float falloff, int sfxId, float minVol, float maxVol, bool doppler, @@ -308,12 +327,12 @@ std::shared_ptr Engine::addEmitter(const float* pos, const float* dir, { auto search = m_sfxLookup.find(sfxId); if (search == m_sfxLookup.end()) - return nullptr; + return {}; AudioGroup* grp = std::get<0>(search->second); const SFXGroupIndex::SFXEntry* entry = std::get<2>(search->second); if (!grp) - return nullptr; + return {}; std::list>::iterator vox = _allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, true, smx); diff --git a/lib/SoundMacroState.cpp b/lib/SoundMacroState.cpp index c4857e8..abdf7a4 100644 --- a/lib/SoundMacroState.cpp +++ b/lib/SoundMacroState.cpp @@ -169,6 +169,9 @@ template <> constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType>() { return SoundMacro::CmdIntrospection::Field::Type::SoundMacroId; } template <> +constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType>() +{ return SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep; } +template <> constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType>() { return SoundMacro::CmdIntrospection::Field::Type::TableId; } template <> @@ -214,12 +217,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSplitKey::Introspective = }, { FIELD_HEAD(SoundMacro::CmdSplitKey, macro), - "SoundMacro"sv, - 0, 65535, 0 + "Macro"sv, + 0, 65535, 65535 }, { FIELD_HEAD(SoundMacro::CmdSplitKey, macroStep), - "SoundMacro Step"sv, + "Macro Step"sv, 0, 65535, 0 } } @@ -230,9 +233,9 @@ bool SoundMacro::CmdSplitKey::Do(SoundMacroState& st, Voice& vox) const { /* Do Branch */ if (macro.id == std::get<0>(st.m_pc.back())) - st._setPC(macroStep); + st._setPC(macroStep.step); else - vox.loadMacroObject(macro.id, macroStep, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod); + vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod); } return false; @@ -251,12 +254,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSplitVel::Introspective = }, { FIELD_HEAD(SoundMacro::CmdSplitVel, macro), - "SoundMacro"sv, - 0, 65535, 0 + "Macro"sv, + 0, 65535, 65535 }, { FIELD_HEAD(SoundMacro::CmdSplitVel, macroStep), - "SoundMacro Step"sv, + "Macro Step"sv, 0, 65535, 0 } } @@ -267,9 +270,9 @@ bool SoundMacro::CmdSplitVel::Do(SoundMacroState& st, Voice& vox) const { /* Do Branch */ if (macro.id == std::get<0>(st.m_pc.back())) - st._setPC(macroStep); + st._setPC(macroStep.step); else - vox.loadMacroObject(macro.id, macroStep, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod); + vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod); } return false; @@ -370,7 +373,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdLoop::Introspective = }, { FIELD_HEAD(SoundMacro::CmdLoop, macroStep), - "SoundMacro Step"sv, + "Macro Step"sv, 0, 65535, 0 }, { @@ -400,7 +403,7 @@ bool SoundMacro::CmdLoop::Do(SoundMacroState& st, Voice& vox) const { /* Loop back to step */ --st.m_loopCountdown; - st._setPC(macroStep); + st._setPC(macroStep.step); } else /* Break out of loop */ st.m_loopCountdown = -1; @@ -416,12 +419,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdGoto::Introspective = { { FIELD_HEAD(SoundMacro::CmdGoto, macro), - "SoundMacro"sv, - 0, 65535, 0 + "Macro"sv, + 0, 65535, 65535 }, { FIELD_HEAD(SoundMacro::CmdGoto, macroStep), - "SoundMacro Step"sv, + "Macro Step"sv, 0, 65535, 0 } } @@ -430,9 +433,9 @@ bool SoundMacro::CmdGoto::Do(SoundMacroState& st, Voice& vox) const { /* Do Branch */ if (macro.id == std::get<0>(st.m_pc.back())) - st._setPC(macroStep); + st._setPC(macroStep.step); else - vox.loadMacroObject(macro.id, macroStep, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod); + vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod); return false; } @@ -516,12 +519,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdPlayMacro::Introspective = }, { FIELD_HEAD(SoundMacro::CmdPlayMacro, macro), - "SoundMacro"sv, - 0, 65535, 0 + "Macro"sv, + 0, 65535, 65535 }, { FIELD_HEAD(SoundMacro::CmdPlayMacro, macroStep), - "SoundMacro Step"sv, + "Macro Step"sv, 0, 65535, 0 }, { @@ -538,7 +541,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdPlayMacro::Introspective = }; bool SoundMacro::CmdPlayMacro::Do(SoundMacroState& st, Voice& vox) const { - std::shared_ptr sibVox = vox.startChildMacro(addNote, macro.id, macroStep); + std::shared_ptr sibVox = vox.startChildMacro(addNote, macro.id, macroStep.step); if (sibVox) st.m_lastPlayMacroVid = sibVox->vid(); @@ -597,12 +600,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSplitMod::Introspective = }, { FIELD_HEAD(SoundMacro::CmdSplitMod, macro), - "SoundMacro"sv, - 0, 65535, 0 + "Macro"sv, + 0, 65535, 65535 }, { FIELD_HEAD(SoundMacro::CmdSplitMod, macroStep), - "SoundMacro Step"sv, + "Macro Step"sv, 0, 65535, 0 } } @@ -613,9 +616,9 @@ bool SoundMacro::CmdSplitMod::Do(SoundMacroState& st, Voice& vox) const { /* Do Branch */ if (macro.id == std::get<0>(st.m_pc.back())) - st._setPC(macroStep); + st._setPC(macroStep.step); else - vox.loadMacroObject(macro.id, macroStep, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod); + vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod); } return false; @@ -663,7 +666,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSetAdsr::Introspective = { FIELD_HEAD(SoundMacro::CmdSetAdsr, table), "ADSR"sv, - 0, 16383, 0 + 0, 65535, 65535 }, { FIELD_HEAD(SoundMacro::CmdSetAdsr, dlsMode), @@ -698,7 +701,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdScaleVolume::Introspective = { FIELD_HEAD(SoundMacro::CmdScaleVolume, table), "Curve"sv, - 0, 16383, 0 + 0, 65535, 65535 }, { FIELD_HEAD(SoundMacro::CmdScaleVolume, originalVol), @@ -777,7 +780,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdEnvelope::Introspective = { FIELD_HEAD(SoundMacro::CmdEnvelope, table), "Curve"sv, - 0, 16383, 0 + 0, 65535, 65535 }, { FIELD_HEAD(SoundMacro::CmdEnvelope, msSwitch), @@ -900,12 +903,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSplitRnd::Introspective = }, { FIELD_HEAD(SoundMacro::CmdSplitRnd, macro), - "SoundMacro"sv, - 0, 65535, 0 + "Macro"sv, + 0, 65535, 65535 }, { FIELD_HEAD(SoundMacro::CmdSplitRnd, macroStep), - "SoundMacro Step"sv, + "Macro Step"sv, 0, 65535, 0 }, } @@ -916,9 +919,9 @@ bool SoundMacro::CmdSplitRnd::Do(SoundMacroState& st, Voice& vox) const { /* Do branch */ if (macro.id == std::get<0>(st.m_pc.back())) - st._setPC(macroStep); + st._setPC(macroStep.step); else - vox.loadMacroObject(macro.id, macroStep, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod); + vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod); } return false; @@ -944,7 +947,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdFadeIn::Introspective = { FIELD_HEAD(SoundMacro::CmdFadeIn, table), "Curve"sv, - 0, 16383, 0 + 0, 65535, 65535 }, { FIELD_HEAD(SoundMacro::CmdFadeIn, msSwitch), @@ -1474,7 +1477,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSetPitchAdsr::Introspective = { FIELD_HEAD(SoundMacro::CmdSetPitchAdsr, table), "ADSR"sv, - 0, 16383, 0, + 0, 65535, 65535, }, { FIELD_HEAD(SoundMacro::CmdSetPitchAdsr, keys), @@ -1592,12 +1595,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdGoSub::Introspective = { { FIELD_HEAD(SoundMacro::CmdSplitRnd, macro), - "SoundMacro"sv, - 0, 65535, 0 + "Macro"sv, + 0, 65535, 65535 }, { FIELD_HEAD(SoundMacro::CmdSplitRnd, macroStep), - "SoundMacro Step"sv, + "Macro Step"sv, 0, 65535, 0 }, } @@ -1606,9 +1609,9 @@ bool SoundMacro::CmdGoSub::Do(SoundMacroState& st, Voice& vox) const { if (macro.id == std::get<0>(st.m_pc.back())) st.m_pc.emplace_back(std::get<0>(st.m_pc.back()), std::get<1>(st.m_pc.back()), - std::get<1>(st.m_pc.back())->assertPC(macroStep)); + std::get<1>(st.m_pc.back())->assertPC(macroStep.step)); else - vox.loadMacroObject(macro.id, macroStep, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod, true); + vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod, true); vox._setObjectId(std::get<0>(st.m_pc.back())); @@ -1633,12 +1636,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdTrapEvent::Introspective = }, { FIELD_HEAD(SoundMacro::CmdTrapEvent, macro), - "SoundMacro"sv, - 0, 65535, 0 + "Macro"sv, + 0, 65535, 65535 }, { FIELD_HEAD(SoundMacro::CmdTrapEvent, macroStep), - "SoundMacro Step"sv, + "Macro Step"sv, 0, 65535, 0 }, } @@ -1649,15 +1652,15 @@ bool SoundMacro::CmdTrapEvent::Do(SoundMacroState& st, Voice& vox) const { case EventType::KeyOff: vox.m_keyoffTrap.macroId = macro.id; - vox.m_keyoffTrap.macroStep = macroStep; + vox.m_keyoffTrap.macroStep = macroStep.step; break; case EventType::SampleEnd: vox.m_sampleEndTrap.macroId = macro.id; - vox.m_sampleEndTrap.macroStep = macroStep; + vox.m_sampleEndTrap.macroStep = macroStep.step; break; case EventType::MessageRecv: vox.m_messageTrap.macroId = macro.id; - vox.m_messageTrap.macroStep = macroStep; + vox.m_messageTrap.macroStep = macroStep.step; break; default: break; @@ -1722,8 +1725,8 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSendMessage::Introspective = }, { FIELD_HEAD(SoundMacro::CmdSendMessage, macro), - "SoundMacro"sv, - 1, 16383, 1 + "Macro"sv, + 1, 65535, 65535 }, { FIELD_HEAD(SoundMacro::CmdSendMessage, voiceVar), @@ -3118,7 +3121,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdIfEqual::Introspective = }, { FIELD_HEAD(SoundMacro::CmdIfEqual, macroStep), - "SoundMacro Step"sv, + "Macro Step"sv, 0, 65535, 0 }, } @@ -3138,7 +3141,7 @@ bool SoundMacro::CmdIfEqual::Do(SoundMacroState& st, Voice& vox) const useB = st.m_variables[b & 0x1f]; if ((useA == useB) ^ notEq) - st._setPC(macroStep); + st._setPC(macroStep.step); return false; } @@ -3176,7 +3179,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdIfLess::Introspective = }, { FIELD_HEAD(SoundMacro::CmdIfLess, macroStep), - "SoundMacro Step"sv, + "Macro Step"sv, 0, 65535, 0 }, } @@ -3196,7 +3199,7 @@ bool SoundMacro::CmdIfLess::Do(SoundMacroState& st, Voice& vox) const useB = st.m_variables[b & 0x1f]; if ((useA < useB) ^ notLt) - st._setPC(macroStep); + st._setPC(macroStep.step); return false; }