Implement amuse playback

This commit is contained in:
Jack Andersen 2018-07-27 18:34:29 -10:00
parent cb24322fc1
commit f5984141fd
33 changed files with 2140 additions and 317 deletions

View File

@ -1 +0,0 @@
#include "AudioGroupModel.hpp"

View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -135,12 +135,12 @@ std::pair<int, int> 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<QScrollArea*>(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);
}

View File

@ -3,6 +3,8 @@
#include <QWidget>
#include <QSvgWidget>
#include <QSlider>
#include <QWheelEvent>
#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

134
Editor/MIDIReader.cpp Normal file
View File

@ -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<amuse::Voice> 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<ProjectModel::SoundMacroNode*>(node);
amuse::AudioGroupDatabase* group = g_MainWindow->projectModel()->getGroupNode(node)->getAudioGroup();
std::shared_ptr<amuse::Voice>& 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<amuse::IMIDIReader>
VoiceAllocator::allocateMIDIReader(amuse::Engine& engine, const char* name)
{
std::unique_ptr<amuse::IMIDIReader> ret = std::make_unique<MIDIReader>(engine, name, m_booEngine.useMIDILock());
if (!static_cast<MIDIReader&>(*ret).getMidiIn())
return {};
return ret;
}

51
Editor/MIDIReader.hpp Normal file
View File

@ -0,0 +1,51 @@
#ifndef AMUSE_MIDI_READER_HPP
#define AMUSE_MIDI_READER_HPP
#include "amuse/BooBackend.hpp"
#include <unordered_set>
class MIDIReader : public amuse::BooBackendMIDIReader
{
std::unordered_map<uint8_t, std::shared_ptr<amuse::Voice>> m_chanVoxs;
std::unordered_set<std::shared_ptr<amuse::Voice>> m_keyoffVoxs;
std::weak_ptr<amuse::Voice> 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<amuse::IMIDIReader> allocateMIDIReader(amuse::Engine& engine, const char* name = nullptr);
};
#endif // AMUSE_MIDI_READER_HPP

View File

@ -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<amuse::BooBackendVoiceAllocator>(*m_voxEngine);
m_voxAllocator = std::make_unique<VoiceAllocator>(*m_voxEngine);
m_engine = std::make_unique<amuse::Engine>(*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<QAction*> 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<void(BackgroundTask&)>&& 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<EditorWidget*>(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<QAction *>(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<QAction*>(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<ProjectModel::SoundMacroNode*>(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<QLineEdit*>(sender()))

View File

@ -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<boo::IAudioVoiceEngine> m_voxEngine;
std::unique_ptr<amuse::BooBackendVoiceAllocator> m_voxAllocator;
std::unique_ptr<VoiceAllocator> m_voxAllocator;
std::unique_ptr<amuse::Engine> m_engine;
std::shared_ptr<amuse::Voice> 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<void(BackgroundTask&)>&& 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();

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>1024</width>
<width>1360</width>
<height>768</height>
</rect>
</property>
@ -98,22 +98,22 @@
<rect>
<x>0</x>
<y>0</y>
<width>764</width>
<width>1100</width>
<height>610</height>
</rect>
</property>
</widget>
</widget>
<widget class="QScrollArea" name="keyboardScrollArea">
<widget class="QWidget" name="keyboard" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<width>0</width>
<height>100</height>
</size>
</property>
@ -123,46 +123,157 @@
<height>100</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="KeyboardWidget" name="keyboardContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1501</width>
<height>85</height>
</rect>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<property name="leftMargin">
<number>0</number>
</property>
<property name="minimumSize">
<size>
<width>1501</width>
<height>0</height>
</size>
<property name="topMargin">
<number>0</number>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
<property name="rightMargin">
<number>6</number>
</property>
</widget>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="keyboardScrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>100</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="KeyboardWidget" name="keyboardContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1501</width>
<height>85</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>1501</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
</widget>
</widget>
</item>
<item>
<widget class="VelocitySlider" name="velocitySlider">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="maximum">
<number>127</number>
</property>
<property name="value">
<number>90</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="ModulationSlider" name="modulationSlider">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="maximum">
<number>127</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="PitchSlider" name="pitchSlider">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="minimum">
<number>-2048</number>
</property>
<property name="maximum">
<number>2048</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
@ -174,7 +285,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>1024</width>
<width>1360</width>
<height>27</height>
</rect>
</property>
@ -182,8 +293,20 @@
<property name="title">
<string>&amp;File</string>
</property>
<widget class="QMenu" name="menuRecent_Projects">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Recent &amp;Projects</string>
</property>
</widget>
<addaction name="actionNew_Project"/>
<addaction name="actionOpen_Project"/>
<addaction name="menuRecent_Projects"/>
<addaction name="actionSave_Project"/>
<addaction name="actionRevert_Project"/>
<addaction name="separator"/>
<addaction name="actionImport"/>
<addaction name="actionExport_GameCube_Groups"/>
</widget>
@ -417,6 +540,22 @@
<string>New &amp;Curve</string>
</property>
</action>
<action name="actionSave_Project">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Save Project</string>
</property>
</action>
<action name="actionRevert_Project">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Revert Project</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
@ -430,6 +569,21 @@
<extends>QStatusBar</extends>
<header>StatusBarWidget.hpp</header>
</customwidget>
<customwidget>
<class>ModulationSlider</class>
<extends>QSlider</extends>
<header>KeyboardWidget.hpp</header>
</customwidget>
<customwidget>
<class>PitchSlider</class>
<extends>QSlider</extends>
<header>KeyboardWidget.hpp</header>
</customwidget>
<customwidget>
<class>VelocitySlider</class>
<extends>QSlider</extends>
<header>KeyboardWidget.hpp</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View File

@ -3,13 +3,98 @@
#include "ProjectModel.hpp"
#include "Common.hpp"
#include "athena/YAMLDocWriter.hpp"
#include "MainWindow.hpp"
#include <QUndoCommand>
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<ProjectModel*>(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<NullNode>(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<CollectionNode*>(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<BasePoolObjectNode*>(n.get())->id() == id)
return ret;
++ret;
}
return -1;
}
amuse::ObjectId ProjectModel::CollectionNode::idOfIndex(int idx) const
{
return static_cast<BasePoolObjectNode*>(m_children[idx].get())->id();
}
ProjectModel::BasePoolObjectNode* ProjectModel::CollectionNode::nodeOfIndex(int idx) const
{
return static_cast<BasePoolObjectNode*>(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<RootNode>();
@ -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<SongGroupNode>(grp.first, grp.second.get());
for (const auto& grp : SortUnorderedMap(sfxGroups))
gn.makeChild<SoundGroupNode>(grp.first, grp.second.get());
if (soundMacros.size())
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Sound Macros"), QIcon(":/icons/IconSoundMacro.svg"));
gn.makeChild<CollectionNode>(tr("Sound Macros"), QIcon(":/icons/IconSoundMacro.svg"), INode::Type::SoundMacro);
col.reserve(soundMacros.size());
for (const auto& macro : SortUnorderedMap(soundMacros))
col.makeChild<SoundMacroNode>(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<CollectionNode>(tr("ADSRs"), QIcon(":/icons/IconADSR.svg"));
gn.makeChild<CollectionNode>(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<ADSRNode>(t.first, t.second.get());
}
}
if (curveCount)
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Curves"), QIcon(":/icons/IconCurve.svg"));
gn.makeChild<CollectionNode>(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<CollectionNode>(tr("Keymaps"), QIcon(":/icons/IconKeymap.svg"));
gn.makeChild<CollectionNode>(tr("Keymaps"), QIcon(":/icons/IconKeymap.svg"), INode::Type::Keymap);
col.reserve(keymaps.size());
for (auto& keymap : SortUnorderedMap(keymaps))
col.makeChild<KeymapNode>(keymap.first, keymap.second.get());
}
if (layers.size())
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Layers"), QIcon(":/icons/IconLayers.svg"));
gn.makeChild<CollectionNode>(tr("Layers"), QIcon(":/icons/IconLayers.svg"), INode::Type::Layer);
col.reserve(layers.size());
for (auto& keymap : SortUnorderedMap(layers))
col.makeChild<LayersNode>(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<INode*>(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<INode*>(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<INode*>(index.internalPointer())->flags();
}
ProjectModel::INode* ProjectModel::node(const QModelIndex& index) const
{
if (!index.isValid())
return nullptr;
return m_root.get();
return static_cast<INode*>(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<GroupNode*>(node);
return getGroupNode(node->parent());
}
void ProjectModel::del()
bool ProjectModel::canEdit(const QModelIndex& index) const
{
if (!index.isValid())
return false;
return (static_cast<INode*>(index.internalPointer())->flags() & Qt::ItemIsSelectable) != Qt::NoItemFlags;
}
class DeleteNodeUndoCommand : public QUndoCommand
{
QModelIndex m_deleteIdx;
std::shared_ptr<ProjectModel::INode> 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<ProjectModel::INode>&& n)
{
beginInsertRows(index.parent(), index.row(), index.row());
node(index.parent())->insertChild(index.row(), std::move(n));
endInsertRows();
}
std::shared_ptr<ProjectModel::INode> 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<ProjectModel::INode> 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));
}

View File

@ -2,6 +2,7 @@
#define AMUSE_PROJECT_MODEL_HPP
#include <QAbstractItemModel>
#include <QIdentityProxyModel>
#include <QDir>
#include <QIcon>
#include <map>
@ -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<QString, amuse::AudioGroupDatabase> 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<std::shared_ptr<INode>> m_children;
std::unique_ptr<INode> 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<INode>&& n)
{
m_children.insert(m_children.begin() + row, std::move(n));
reindexRows(row);
}
std::shared_ptr<INode> removeChild(int row)
{
std::shared_ptr<INode> 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<class T, class... _Args>
T& makeChild(_Args&&... args)
{
m_children.push_back(std::make_shared<T>(this, m_children.size(), std::forward<_Args>(args)...));
m_nullChild->m_row = int(m_children.size());
return static_cast<T&>(*m_children.back());
}
bool depthTraverse(const std::function<bool(INode* node)>& 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<QString, amuse::AudioGroupDatabase>::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<GroupNode> shared_from_this()
{ return std::static_pointer_cast<GroupNode>(INode::shared_from_this()); }
};
@ -125,28 +196,42 @@ public:
std::shared_ptr<SoundGroupNode> shared_from_this()
{ return std::static_pointer_cast<SoundGroupNode>(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<CollectionNode> shared_from_this()
{ return std::static_pointer_cast<CollectionNode>(INode::shared_from_this()); }
};
template <class ID, class T, INode::Type TP>
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 <class ID, class T, INode::Type TP>
struct PoolObjectNode : BasePoolObjectNode
{
ID m_id;
QString m_name;
std::shared_ptr<T> m_obj;
PoolObjectNode(INode* parent, int row, ID id, std::shared_ptr<T> 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<ProjectModel::INode>&& node);
std::shared_ptr<ProjectModel::INode> _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; }
};

View File

@ -10,8 +10,124 @@
#include <QApplication>
#include <QCheckBox>
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<SoundMacroEditor*>(
parentWidget()->parentWidget()->parentWidget()->
parentWidget()->parentWidget()->parentWidget()->parentWidget());
}
SoundMacroListing* FieldSoundMacroStep::getListing() const
{
return qobject_cast<SoundMacroListing*>(
parentWidget()->parentWidget()->parentWidget());
}
void FieldSoundMacroStep::targetPressed()
{
ProjectModel::SoundMacroNode* node = nullptr;
if (m_macroField)
{
int val = m_macroField->currentIndex();
if (val != 0)
node = static_cast<ProjectModel::SoundMacroNode*>(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<ProjectModel::SoundMacroNode*>(
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<ProjectModel::SoundMacroNode*>(
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<amuse::SoundMacroIdDNA<athena::Little>>(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<amuse::SoundMacroStepDNA<athena::Little>>(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<int8_t>(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<uint32_t>(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<amuse::SoundMacroIdDNA<athena::Little>>(m_cmd, m_field).id = uint16_t(m_undoVal);
break;
default:
break;
}
@ -227,6 +390,13 @@ public:
m_undoVal = amuse::AccessField<uint32_t>(m_cmd, m_field);
amuse::AccessField<uint32_t>(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<amuse::SoundMacroIdDNA<athena::Little>>(m_cmd, m_field).id;
amuse::AccessField<amuse::SoundMacroIdDNA<athena::Little>>(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<QCheckBox*>(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<FieldSpinBox*>(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<FieldComboBox*>(sender());
FieldProjectNode* fieldW = static_cast<FieldProjectNode*>(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<ProjectModel::SoundMacroNode>(m_node)->
static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
m_obj->swapPositions(m_a, m_b);
EditorUndoCommand::undo();
}
void redo()
{
std::static_pointer_cast<ProjectModel::SoundMacroNode>(m_node)->
static_cast<ProjectModel::SoundMacroNode*>(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<ProjectModel::SoundMacroNode>(m_node)->
m_cmd = static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
m_obj->deleteCmd(m_insertIdx);
EditorUndoCommand::undo();
}
@ -613,7 +784,7 @@ public:
{
if (!m_cmd)
return;
std::static_pointer_cast<ProjectModel::SoundMacroNode>(m_node)->
static_cast<ProjectModel::SoundMacroNode*>(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<ProjectModel::SoundMacroNode>(m_node)->
static_cast<ProjectModel::SoundMacroNode*>(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<ProjectModel::SoundMacroNode>(m_node)->
m_cmd = static_cast<ProjectModel::SoundMacroNode*>(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)

View File

@ -13,6 +13,7 @@
#include <QTreeWidget>
#include <QPushButton>
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);

View File

@ -3,6 +3,7 @@
#include <QStatusBar>
#include <QLabel>
#include <QPushButton>
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

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="20"
height="20"
viewBox="0 0 5.2916665 5.2916668"
version="1.1"
id="svg8"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="IconKill.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#353535"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="39.226909"
inkscape:cx="9.3355483"
inkscape:cy="10.583863"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1452"
inkscape:window-height="1061"
inkscape:window-x="651"
inkscape:window-y="170"
inkscape:window-maximized="0"
gridtolerance="10"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid817"
empspacing="0"
spacingx="0.52916666"
spacingy="0.52916666"
visible="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-291.70832)">
<path
style="fill:#fffff8;fill-opacity:1;stroke:#000000;stroke-width:0.30217573px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 1.4046674,296.70833 v -1.17708 c 0,0 -1.24116588,-0.58856 -1.24116588,-1.1771 0,-0.58853 0,-2.35417 2.48233178,-2.35417 2.4823318,0 2.4823318,1.76564 2.4823318,2.35417 0,0.58854 -1.2411659,1.1771 -1.2411659,1.1771 v 1.17708 z"
id="path842"
inkscape:connector-curvature="0" />
<path
style="fill:#000006;fill-opacity:1;stroke:none;stroke-width:0.52916664;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.49803922"
id="path844"
sodipodi:type="arc"
sodipodi:cx="1.8520833"
sodipodi:cy="293.56039"
sodipodi:rx="0.47706053"
sodipodi:ry="0.43746531"
sodipodi:start="0"
sodipodi:end="5.866374"
d="m 2.3291439,293.56039 a 0.47706053,0.43746531 0 0 1 -0.4274394,0.4351 0.47706053,0.43746531 0 0 1 -0.5163591,-0.34458 0.47706053,0.43746531 0 0 1 0.3200219,-0.50678 0.47706053,0.43746531 0 0 1 0.5829328,0.23916 l -0.4362168,0.1771 z" />
<path
d="m 3.9161899,293.56039 a 0.47706053,0.43746531 0 0 1 -0.4274394,0.4351 0.47706053,0.43746531 0 0 1 -0.5163591,-0.34458 0.47706053,0.43746531 0 0 1 0.3200219,-0.50678 0.47706053,0.43746531 0 0 1 0.5829328,0.23916 l -0.4362167,0.1771 z"
sodipodi:end="5.866374"
sodipodi:start="0"
sodipodi:ry="0.43746531"
sodipodi:rx="0.47706053"
sodipodi:cy="293.56039"
sodipodi:cx="3.4391294"
sodipodi:type="arc"
id="path846"
style="fill:#000006;fill-opacity:1;stroke:none;stroke-width:0.52916664;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.49803922" />
<path
style="fill:none;stroke:#000000;stroke-width:0.29107958px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 2.6458333,295.41249 v 1.28091"
id="path848"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.29184496px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 2.0289823,295.41249 v 1.28766"
id="path850"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.29107958px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 3.2694292,295.41249 v 1.28091"
id="path852"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="20"
height="20"
viewBox="0 0 5.2916665 5.2916668"
version="1.1"
id="svg8"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="IconSoundMacroTarget.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#353535"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="14.029515"
inkscape:cx="4.9858624"
inkscape:cy="10.149308"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="3840"
inkscape:window-height="2079"
inkscape:window-x="0"
inkscape:window-y="40"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid817"
empspacing="0"
spacingx="0.52916666"
spacingy="0.52916666"
visible="true" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-291.70832)">
<ellipse
style="fill:none;fill-opacity:1;stroke:#a4a4a4;stroke-width:0.52916664;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path842"
cx="2.6458333"
cy="294.35416"
rx="2.1166666"
ry="2.1166699" />
<ellipse
style="fill:#a4a4a4;fill-opacity:1;stroke:none;stroke-width:0.52916664;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path844"
cy="294.35416"
cx="2.6458333"
rx="1.0583333"
ry="1.0583365" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="20"
height="20"
viewBox="0 0 5.2916665 5.2916668"
version="1.1"
id="svg8"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="IconSoundMacroTargetDisabled.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#353535"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="14.029515"
inkscape:cx="4.9858624"
inkscape:cy="10.149308"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="3840"
inkscape:window-height="2079"
inkscape:window-x="0"
inkscape:window-y="40"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid817"
empspacing="0"
spacingx="0.52916666"
spacingy="0.52916666"
visible="true" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-291.70832)">
<ellipse
style="fill:none;fill-opacity:1;stroke:#a4a4a4;stroke-width:0.52916664;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.49803922"
id="path842"
cx="2.6458333"
cy="294.35416"
rx="2.1166666"
ry="2.1166699" />
<ellipse
style="fill:#a4a4a4;fill-opacity:0.49803922;stroke:none;stroke-width:0.52916664;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path844"
cy="294.35416"
cx="2.6458333"
rx="1.0583333"
ry="1.0583365" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,6 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de_DE">
<context>
<name>CommandWidget</name>
<message>
<location filename="../SoundMacroEditor.cpp" line="+163"/>
<source>Delete this SoundMacro</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
@ -9,12 +17,12 @@
<translation>Amuse</translation>
</message>
<message>
<location line="+169"/>
<location line="+280"/>
<source>&amp;File</source>
<translation>&amp;Datei</translation>
</message>
<message>
<location line="+9"/>
<location line="+21"/>
<source>P&amp;roject</source>
<translation>Projekt</translation>
</message>
@ -52,7 +60,12 @@
<translation type="vanished">&amp; Wiederholen</translation>
</message>
<message>
<location line="+8"/>
<location line="-65"/>
<source>Recent &amp;Projects</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="+73"/>
<source>&amp;Cut</source>
<translation>&amp;Schnitt</translation>
</message>
@ -147,12 +160,22 @@
<translation>Neu und Kurve</translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="+44"/>
<location line="+8"/>
<source>&amp;Save Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="+8"/>
<source>&amp;Revert Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="+70"/>
<source>Quit</source>
<translation>Verlassen</translation>
</message>
<message>
<location line="+106"/>
<location line="+134"/>
<source>The directory at &apos;%1&apos; must exist for the Amuse editor.</source>
<translation>Das Verzeichnis unter &apos;% 1&apos; muss für den Amuse-Editor vorhanden sein.</translation>
</message>
@ -177,7 +200,7 @@
<translation>Es konnte nicht in das Verzeichnis geschrieben werden</translation>
</message>
<message>
<location line="+38"/>
<location line="+44"/>
<source>No Audio Devices Found</source>
<translation>Keine Audiogeräte gefunden</translation>
</message>
@ -187,22 +210,44 @@
<translation>Keine MIDI-Geräte gefunden</translation>
</message>
<message>
<location line="+125"/>
<location line="+193"/>
<source>New Project</source>
<translation>Neues Projekt</translation>
</message>
<message>
<location line="+14"/>
<location line="+60"/>
<source>Open Project</source>
<translation>Offenes Projekt</translation>
</message>
<message>
<location line="+7"/>
<location line="-37"/>
<source>The directory at &apos;%1&apos; does not exist.</source>
<translation>Das Verzeichnis &apos;% 1&apos; existiert nicht.</translation>
</message>
<message>
<location line="+1"/>
<location line="-432"/>
<source>Clear Recent Projects</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="+134"/>
<location line="+292"/>
<source>The directory at &apos;%1&apos; must not be empty.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="-291"/>
<location line="+292"/>
<source>Directory empty</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="-181"/>
<source>SUSTAIN</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="+187"/>
<source>Bad Directory</source>
<translation>Schlechtes Verzeichnis</translation>
</message>
@ -213,18 +258,18 @@
</message>
<message>
<location line="+0"/>
<location line="+83"/>
<location line="+127"/>
<location line="+45"/>
<source>Scanning Project</source>
<translation>Projekt scannen</translation>
</message>
<message>
<location line="-116"/>
<location line="-160"/>
<source>Opening %1</source>
<translation>Eröffnung% 1</translation>
</message>
<message>
<location line="+10"/>
<location line="+54"/>
<source>Import Project</source>
<translation>Projekt importieren</translation>
</message>
@ -296,30 +341,46 @@
<translation>Importieren von% 1</translation>
</message>
</context>
<context>
<name>ModulationSlider</name>
<message>
<location filename="../KeyboardWidget.cpp" line="+267"/>
<source>Modulation: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>PitchSlider</name>
<message>
<location line="+9"/>
<source>Pitch: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ProjectModel</name>
<message>
<location filename="../ProjectModel.cpp" line="+126"/>
<location filename="../ProjectModel.cpp" line="+210"/>
<source>Sound Macros</source>
<translation>Sound-Makros</translation>
</message>
<message>
<location line="+21"/>
<location line="+19"/>
<source>ADSRs</source>
<translation>ADSRs</translation>
</message>
<message>
<location line="+12"/>
<location line="+11"/>
<source>Curves</source>
<translation>Kurven</translation>
</message>
<message>
<location line="+13"/>
<location line="+12"/>
<source>Keymaps</source>
<translation>Schlüsselkarten</translation>
</message>
<message>
<location line="+8"/>
<location line="+7"/>
<source>Layers</source>
<translation>Lagen</translation>
</message>
@ -340,12 +401,12 @@
<context>
<name>QUndoStack</name>
<message>
<location filename="../SoundMacroEditor.cpp" line="+163"/>
<location filename="../SoundMacroEditor.cpp" line="+157"/>
<source>Change %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="+308"/>
<location line="+322"/>
<source>Reorder %1</source>
<translation type="unfinished"></translation>
</message>
@ -355,7 +416,8 @@
<translation type="unfinished"></translation>
</message>
<message>
<location line="+55"/>
<location filename="../ProjectModel.cpp" line="+151"/>
<location filename="../SoundMacroEditor.cpp" line="+55"/>
<source>Delete %1</source>
<translation type="unfinished"></translation>
</message>
@ -363,7 +425,7 @@
<context>
<name>SoundMacroCatalogue</name>
<message>
<location line="+111"/>
<location filename="../SoundMacroEditor.cpp" line="+118"/>
<source>Control</source>
<translation>Steuerung</translation>
</message>
@ -433,4 +495,28 @@
<translation>Befehle zum Steuern der Lautstärke der Stimme</translation>
</message>
</context>
<context>
<name>StatusBarWidget</name>
<message>
<location filename="../StatusBarWidget.hpp" line="+26"/>
<source>Immediately kill active voices</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>TargetButton</name>
<message>
<location filename="../SoundMacroEditor.cpp" line="-939"/>
<source>Set step with target click</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>VelocitySlider</name>
<message>
<location filename="../KeyboardWidget.cpp" line="-18"/>
<source>Velocity: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -20,6 +20,9 @@
<file>IconCurve.svg</file>
<file>IconNewCurve.svg</file>
<file>IconSoundMacroDelete.svg</file>
<file>IconSoundMacroTarget.svg</file>
<file>IconSoundMacroTargetDisabled.svg</file>
<file>IconKill.svg</file>
</qresource>
<qresource prefix="/bg">
<file>FaceGrey.svg</file>

View File

@ -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<NameDB*>(&m_soundMacroDb);
SampleId::CurNameDB = const_cast<NameDB*>(&m_sampleDb);
TableId::CurNameDB = const_cast<NameDB*>(&m_tableDb);
KeymapId::CurNameDB = const_cast<NameDB*>(&m_keymapDb);
LayersId::CurNameDB = const_cast<NameDB*>(&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<NameDB*>(&m_songDb);
SFXId::CurNameDB = const_cast<NameDB*>(&m_sfxDb);
GroupId::CurNameDB = const_cast<NameDB*>(&m_groupDb);
}
};
}

View File

@ -153,6 +153,7 @@ struct SoundMacro
Int32,
UInt32,
SoundMacroId,
SoundMacroStep,
TableId,
SampleId,
Choice
@ -200,7 +201,7 @@ struct SoundMacro
static const CmdIntrospection Introspective;
Value<atInt8> key;
SoundMacroIdDNA<athena::Little> macro;
Value<atUint16> macroStep;
SoundMacroStepDNA<athena::Little> 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<atInt8> velocity;
SoundMacroIdDNA<athena::Little> macro;
Value<atUint16> macroStep;
SoundMacroStepDNA<athena::Little> macroStep;
bool Do(SoundMacroState& st, Voice& vox) const;
CmdOp Isa() const { return CmdOp::SplitVel; }
};
@ -237,7 +238,7 @@ struct SoundMacro
Value<bool> keyOff;
Value<bool> random;
Value<bool> sampleEnd;
Value<atUint16> macroStep;
SoundMacroStepDNA<athena::Little> macroStep;
Value<atUint16> 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<athena::Little> macro;
Value<atUint16> macroStep;
SoundMacroStepDNA<athena::Little> 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<atInt8> addNote;
SoundMacroIdDNA<athena::Little> macro;
Value<atUint16> macroStep;
SoundMacroStepDNA<athena::Little> macroStep;
Value<atUint8> priority;
Value<atUint8> maxVoices;
bool Do(SoundMacroState& st, Voice& vox) const;
@ -297,7 +298,7 @@ struct SoundMacro
static const CmdIntrospection Introspective;
Value<atInt8> modValue;
SoundMacroIdDNA<athena::Little> macro;
Value<atUint16> macroStep;
SoundMacroStepDNA<athena::Little> 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<atUint8> rnd;
SoundMacroIdDNA<athena::Little> macro;
Value<atUint16> macroStep;
SoundMacroStepDNA<athena::Little> 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<athena::Little> macro;
Value<atUint16> macroStep;
SoundMacroStepDNA<athena::Little> macroStep;
bool Do(SoundMacroState& st, Voice& vox) const;
CmdOp Isa() const { return CmdOp::GoSub; }
};
@ -640,7 +641,7 @@ struct SoundMacro
};
Value<EventType> event;
SoundMacroIdDNA<athena::Little> macro;
Value<atUint16> macroStep;
SoundMacroStepDNA<athena::Little> macroStep;
bool Do(SoundMacroState& st, Voice& vox) const;
CmdOp Isa() const { return CmdOp::TrapEvent; }
};
@ -1098,7 +1099,7 @@ struct SoundMacro
Value<bool> varCtrlB;
Value<atInt8> b;
Value<bool> notEq;
Value<atUint16> macroStep;
SoundMacroStepDNA<athena::Little> macroStep;
bool Do(SoundMacroState& st, Voice& vox) const;
CmdOp Isa() const { return CmdOp::IfEqual; }
};
@ -1112,7 +1113,7 @@ struct SoundMacro
Value<bool> varCtrlB;
Value<atInt8> b;
Value<bool> notLt;
Value<atUint16> macroStep;
SoundMacroStepDNA<athena::Little> macroStep;
bool Do(SoundMacroState& st, Voice& vox) const;
CmdOp Isa() const { return CmdOp::IfLess; }
};

View File

@ -71,6 +71,7 @@ public:
class BooBackendMIDIReader : public IMIDIReader, public boo::IMIDIReader
{
friend class BooBackendVoiceAllocator;
protected:
Engine& m_engine;
std::unique_ptr<boo::IMIDIIn> 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;

View File

@ -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 <athena::Endian DNAEn>
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

View File

@ -96,6 +96,15 @@ public:
return fxStart(sfxId, vol, pan, m_defaultStudio);
}
/** Start SoundMacro node playing directly (for editor use) */
std::shared_ptr<Voice> macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key,
uint8_t vel, uint8_t mod, std::weak_ptr<Studio> smx);
std::shared_ptr<Voice> 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<Emitter> 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<std::shared_ptr<Voice>>& getActiveVoices() { return m_activeVoices; }
/** Obtain list of active sequencers */
std::list<std::shared_ptr<Sequencer>>& getActiveSequencers() { return m_activeSequencers; }

View File

@ -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));

View File

@ -61,6 +61,7 @@ struct MakeDefaultCmdOp
AccessField<uint32_t>(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<SoundMacroIdDNA<athena::Little>>(ret.get(), field).id = uint16_t(field.m_default);
@ -153,7 +154,8 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
objHead.read(r);
KeymapDNA<DNAE> kmData;
kmData.read(r);
*ret.m_keymaps[objHead.objectId.id] = kmData;
auto& km = ret.m_keymaps[objHead.objectId.id];
km = std::make_shared<Keymap>(kmData);
r.seek(startPos + objHead.size, athena::Begin);
}
}
@ -166,15 +168,16 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
ObjectHeader<DNAE> objHead;
atInt64 startPos = r.position();
objHead.read(r);
std::vector<LayerMapping>& lm = *ret.m_layers[objHead.objectId.id];
auto& lm = ret.m_layers[objHead.objectId.id];
lm = std::make_shared<std::vector<LayerMapping>>();
uint32_t count;
athena::io::Read<athena::io::PropType::None>::Do<decltype(count), DNAE>({}, count, r);
lm.reserve(count);
lm->reserve(count);
for (uint32_t i = 0; i < count; ++i)
{
LayerMappingDNA<DNAE> lmData;
lmData.read(r);
lm.push_back(lmData);
lm->push_back(lmData);
}
r.seek(startPos + objHead.size, athena::Begin);
}

View File

@ -21,6 +21,29 @@ static bool AtEnd16(athena::io::IStreamReader& r)
return v == 0xffff;
}
template <athena::Endian DNAE>
static void ReadRangedObjectIds(NameDB* db, athena::io::IStreamReader& r, NameDB::Type tp)
{
uint16_t id;
athena::io::Read<athena::io::PropType::None>::Do<decltype(id), DNAE>({}, id, r);
if ((id & 0x8000) == 0x8000)
{
uint16_t endId;
athena::io::Read<athena::io::PropType::None>::Do<decltype(endId), DNAE>({}, 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<athena::Big> 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<athena::Big>(SoundMacroId::CurNameDB, r, NameDB::Type::SoundMacro);
/* Samples */
r.seek(header.samplIdsOff, athena::Begin);
while (!AtEnd16(r))
ReadRangedObjectIds<athena::Big>(SampleId::CurNameDB, r, NameDB::Type::Sample);
/* Tables */
r.seek(header.tableIdsOff, athena::Begin);
while (!AtEnd16(r))
ReadRangedObjectIds<athena::Big>(TableId::CurNameDB, r, NameDB::Type::Table);
/* Keymaps */
r.seek(header.keymapIdsOff, athena::Begin);
while (!AtEnd16(r))
ReadRangedObjectIds<athena::Big>(KeymapId::CurNameDB, r, NameDB::Type::Keymap);
/* Layers */
r.seek(header.layerIdsOff, athena::Begin);
while (!AtEnd16(r))
ReadRangedObjectIds<athena::Big>(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<SongGroupIndex::MIDISetup, 16>& 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<athena::Big> 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<DNAE> 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<DNAE>(SoundMacroId::CurNameDB, r, NameDB::Type::SoundMacro);
/* Samples */
r.seek(subDataOff + header.samplIdsOff, athena::Begin);
while (!AtEnd16(r))
ReadRangedObjectIds<DNAE>(SampleId::CurNameDB, r, NameDB::Type::Sample);
/* Tables */
r.seek(subDataOff + header.tableIdsOff, athena::Begin);
while (!AtEnd16(r))
ReadRangedObjectIds<DNAE>(TableId::CurNameDB, r, NameDB::Type::Table);
/* Keymaps */
r.seek(subDataOff + header.keymapIdsOff, athena::Begin);
while (!AtEnd16(r))
ReadRangedObjectIds<DNAE>(KeymapId::CurNameDB, r, NameDB::Type::Keymap);
/* Layers */
r.seek(subDataOff + header.layerIdsOff, athena::Begin);
while (!AtEnd16(r))
ReadRangedObjectIds<DNAE>(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<SongGroupIndex::MIDISetup, 16>& 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 <athena::Endian DNAE>
static void ReadRangedObjectIds(NameDB* db, athena::io::IStreamReader& r, NameDB::Type tp)
{
uint16_t id;
athena::io::Read<athena::io::PropType::None>::Do<decltype(id), DNAE>({}, id, r);
if ((id & 0x8000) == 0x8000)
{
uint16_t endId;
athena::io::Read<athena::io::PropType::None>::Do<decltype(endId), DNAE>({}, 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))

View File

@ -45,6 +45,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(athena::io::IStreamReader&
EntryDNA<athena::Big> 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<athena::Big> 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<athena::Big> 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);
}
}
}

View File

@ -194,6 +194,64 @@ const char* PageObjectIdDNA<DNAE>::DNAType()
template struct PageObjectIdDNA<athena::Big>;
template struct PageObjectIdDNA<athena::Little>;
template<> template<>
void SoundMacroStepDNA<athena::Little>::Enumerate<BigDNA::Read>(athena::io::IStreamReader& reader)
{
step = reader.readUint16Little();
}
template<> template<>
void SoundMacroStepDNA<athena::Little>::Enumerate<BigDNA::Write>(athena::io::IStreamWriter& writer)
{
writer.writeUint16Little(step);
}
template<> template<>
void SoundMacroStepDNA<athena::Little>::Enumerate<BigDNA::BinarySize>(size_t& sz)
{
sz += 2;
}
template<> template<>
void SoundMacroStepDNA<athena::Little>::Enumerate<BigDNA::ReadYaml>(athena::io::YAMLDocReader& reader)
{
step = reader.readUint16(nullptr);
}
template<> template<>
void SoundMacroStepDNA<athena::Little>::Enumerate<BigDNA::WriteYaml>(athena::io::YAMLDocWriter& writer)
{
writer.writeUint16(nullptr, step);
}
template<> template<>
void SoundMacroStepDNA<athena::Big>::Enumerate<BigDNA::Read>(athena::io::IStreamReader& reader)
{
step = reader.readUint16Big();
}
template<> template<>
void SoundMacroStepDNA<athena::Big>::Enumerate<BigDNA::Write>(athena::io::IStreamWriter& writer)
{
writer.writeUint16Big(step);
}
template<> template<>
void SoundMacroStepDNA<athena::Big>::Enumerate<BigDNA::BinarySize>(size_t& sz)
{
sz += 2;
}
template<> template<>
void SoundMacroStepDNA<athena::Big>::Enumerate<BigDNA::ReadYaml>(athena::io::YAMLDocReader& reader)
{
step = reader.readUint16(nullptr);
}
template<> template<>
void SoundMacroStepDNA<athena::Big>::Enumerate<BigDNA::WriteYaml>(athena::io::YAMLDocWriter& writer)
{
writer.writeUint16(nullptr, step);
}
template <athena::Endian DNAE>
const char* SoundMacroStepDNA<DNAE>::DNAType()
{
return "amuse::SoundMacroStepDNA";
}
template struct SoundMacroStepDNA<athena::Big>;
template struct SoundMacroStepDNA<athena::Little>;
ObjectId NameDB::generateId(Type tp) const
{
uint16_t maxMatch = uint16_t(tp == Type::Layer ? 0x8000 : 0);

View File

@ -280,12 +280,12 @@ std::shared_ptr<Voice> 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<std::shared_ptr<Voice>>::iterator ret =
_allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, false, smx);
@ -301,6 +301,25 @@ std::shared_ptr<Voice> Engine::fxStart(int sfxId, float vol, float pan, std::wea
return *ret;
}
/** Start SoundMacro node playing directly (for editor use) */
std::shared_ptr<Voice> Engine::macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key, uint8_t vel,
uint8_t mod, std::weak_ptr<Studio> smx)
{
if (!group)
return {};
std::list<std::shared_ptr<Voice>>::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<Emitter> 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<Emitter> 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<std::shared_ptr<Voice>>::iterator vox =
_allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, true, smx);

View File

@ -169,6 +169,9 @@ template <>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<SoundMacroIdDNA<athena::Little>>()
{ return SoundMacro::CmdIntrospection::Field::Type::SoundMacroId; }
template <>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<SoundMacroStepDNA<athena::Little>>()
{ return SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep; }
template <>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<TableIdDNA<athena::Little>>()
{ 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<Voice> sibVox = vox.startChildMacro(addNote, macro.id, macroStep);
std::shared_ptr<Voice> 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;
}