mirror of https://github.com/AxioDL/amuse.git
All editors implemented
This commit is contained in:
parent
eff832bb8c
commit
d24e06f101
|
@ -46,7 +46,8 @@ QT5_WRAP_CPP(AMUSE_MOC
|
||||||
LayersEditor.hpp
|
LayersEditor.hpp
|
||||||
SampleEditor.hpp
|
SampleEditor.hpp
|
||||||
SoundGroupEditor.hpp
|
SoundGroupEditor.hpp
|
||||||
SongGroupEditor.hpp)
|
SongGroupEditor.hpp
|
||||||
|
NewSoundMacroDialog.hpp)
|
||||||
|
|
||||||
add_executable(amuse-gui WIN32 MACOSX_BUNDLE
|
add_executable(amuse-gui WIN32 MACOSX_BUNDLE
|
||||||
Common.hpp Common.cpp
|
Common.hpp Common.cpp
|
||||||
|
@ -64,6 +65,7 @@ add_executable(amuse-gui WIN32 MACOSX_BUNDLE
|
||||||
SampleEditor.hpp SampleEditor.cpp
|
SampleEditor.hpp SampleEditor.cpp
|
||||||
SoundGroupEditor.hpp SoundGroupEditor.cpp
|
SoundGroupEditor.hpp SoundGroupEditor.cpp
|
||||||
SongGroupEditor.hpp SongGroupEditor.cpp
|
SongGroupEditor.hpp SongGroupEditor.cpp
|
||||||
|
NewSoundMacroDialog.hpp NewSoundMacroDialog.cpp
|
||||||
MIDIReader.hpp MIDIReader.cpp
|
MIDIReader.hpp MIDIReader.cpp
|
||||||
resources/resources.qrc qrc_resources.cpp
|
resources/resources.qrc qrc_resources.cpp
|
||||||
${QM_FILES} qrc_translation_res.cpp
|
${QM_FILES} qrc_translation_res.cpp
|
||||||
|
|
|
@ -47,4 +47,15 @@ static QLatin1String StringViewToQString(std::string_view sv)
|
||||||
/* Used for generating transform matrices to map SVG coordinate space */
|
/* Used for generating transform matrices to map SVG coordinate space */
|
||||||
QTransform RectToRect(const QRectF& from, const QRectF& to);
|
QTransform RectToRect(const QRectF& from, const QRectF& to);
|
||||||
|
|
||||||
|
namespace std
|
||||||
|
{
|
||||||
|
template<> struct hash<QString>
|
||||||
|
{
|
||||||
|
std::size_t operator()(const QString& s) const noexcept
|
||||||
|
{
|
||||||
|
return qHash(s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#endif //AMUSE_COMMON_HPP
|
#endif //AMUSE_COMMON_HPP
|
||||||
|
|
|
@ -59,3 +59,21 @@ void FieldPageObjectNode::setGroup(ProjectModel::GroupNode* group)
|
||||||
setModel(model->getPageObjectProxy());
|
setModel(model->getPageObjectProxy());
|
||||||
setRootModelIndex(model->getPageObjectProxy()->mapFromSource(model->index(group)));
|
setRootModelIndex(model->getPageObjectProxy()->mapFromSource(model->index(group)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AddRemoveButtons::AddRemoveButtons(QWidget* parent)
|
||||||
|
: QWidget(parent), m_addAction(tr("Add Row")), m_addButton(this),
|
||||||
|
m_removeAction(tr("Remove Row")), m_removeButton(this)
|
||||||
|
{
|
||||||
|
setFixedSize(64, 32);
|
||||||
|
|
||||||
|
m_addAction.setIcon(QIcon(QStringLiteral(":/icons/IconAdd.svg")));
|
||||||
|
m_addButton.setDefaultAction(&m_addAction);
|
||||||
|
m_addButton.setFixedSize(32, 32);
|
||||||
|
m_addButton.move(0, 0);
|
||||||
|
|
||||||
|
m_removeAction.setIcon(QIcon(QStringLiteral(":/icons/IconRemove.svg")));
|
||||||
|
m_removeButton.setDefaultAction(&m_removeAction);
|
||||||
|
m_removeButton.setFixedSize(32, 32);
|
||||||
|
m_removeButton.move(32, 0);
|
||||||
|
m_removeAction.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QWheelEvent>
|
#include <QWheelEvent>
|
||||||
#include <QItemEditorFactory>
|
#include <QItemEditorFactory>
|
||||||
|
#include <QToolButton>
|
||||||
|
#include <QAction>
|
||||||
#include "ProjectModel.hpp"
|
#include "ProjectModel.hpp"
|
||||||
|
|
||||||
class EditorWidget : public QWidget
|
class EditorWidget : public QWidget
|
||||||
|
@ -18,8 +20,9 @@ public:
|
||||||
virtual bool valid() const { return true; }
|
virtual bool valid() const { return true; }
|
||||||
virtual void unloadData() {}
|
virtual void unloadData() {}
|
||||||
virtual ProjectModel::INode* currentNode() const { return nullptr; }
|
virtual ProjectModel::INode* currentNode() const { return nullptr; }
|
||||||
public slots:
|
virtual void setEditorEnabled(bool en) { setEnabled(en); }
|
||||||
virtual bool isItemEditEnabled() const { return false; }
|
virtual bool isItemEditEnabled() const { return false; }
|
||||||
|
public slots:
|
||||||
virtual void itemCutAction() {}
|
virtual void itemCutAction() {}
|
||||||
virtual void itemCopyAction() {}
|
virtual void itemCopyAction() {}
|
||||||
virtual void itemPasteAction() {}
|
virtual void itemPasteAction() {}
|
||||||
|
@ -127,4 +130,17 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AddRemoveButtons : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QAction m_addAction;
|
||||||
|
QToolButton m_addButton;
|
||||||
|
QAction m_removeAction;
|
||||||
|
QToolButton m_removeButton;
|
||||||
|
public:
|
||||||
|
explicit AddRemoveButtons(QWidget* parent = Q_NULLPTR);
|
||||||
|
QAction* addAction() { return &m_addAction; }
|
||||||
|
QAction* removeAction() { return &m_removeAction; }
|
||||||
|
};
|
||||||
|
|
||||||
#endif //AMUSE_EDITOR_WIDGET_HPP
|
#endif //AMUSE_EDITOR_WIDGET_HPP
|
||||||
|
|
|
@ -390,8 +390,7 @@ ProjectModel::INode* LayersEditor::currentNode() const
|
||||||
void LayersEditor::resizeEvent(QResizeEvent* ev)
|
void LayersEditor::resizeEvent(QResizeEvent* ev)
|
||||||
{
|
{
|
||||||
m_tableView.setGeometry(QRect({}, ev->size()));
|
m_tableView.setGeometry(QRect({}, ev->size()));
|
||||||
m_addButton.move(0, ev->size().height() - 32);
|
m_addRemoveButtons.move(0, ev->size().height() - 32);
|
||||||
m_removeButton.move(32, ev->size().height() - 32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LayersEditor::doAdd()
|
void LayersEditor::doAdd()
|
||||||
|
@ -403,9 +402,9 @@ void LayersEditor::doAdd()
|
||||||
m_model.insertRow(idx.row());
|
m_model.insertRow(idx.row());
|
||||||
}
|
}
|
||||||
|
|
||||||
void LayersEditor::doSelectionChanged(const QItemSelection& selected)
|
void LayersEditor::doSelectionChanged()
|
||||||
{
|
{
|
||||||
m_removeAction.setDisabled(selected.isEmpty());
|
m_addRemoveButtons.removeAction()->setDisabled(m_tableView.selectionModel()->selectedRows().isEmpty());
|
||||||
g_MainWindow->updateFocus();
|
g_MainWindow->updateFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,23 +434,14 @@ void LayersEditor::itemDeleteAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
LayersEditor::LayersEditor(QWidget* parent)
|
LayersEditor::LayersEditor(QWidget* parent)
|
||||||
: EditorWidget(parent), m_model(this), m_tableView(this),
|
: EditorWidget(parent), m_model(this), m_tableView(this), m_addRemoveButtons(this)
|
||||||
m_addAction(tr("Add Row")), m_addButton(this), m_removeAction(tr("Remove Row")), m_removeButton(this)
|
|
||||||
{
|
{
|
||||||
m_tableView.setModel(&m_model);
|
m_tableView.setModel(&m_model);
|
||||||
connect(m_tableView.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
|
connect(m_tableView.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
|
||||||
this, SLOT(doSelectionChanged(const QItemSelection&)));
|
this, SLOT(doSelectionChanged()));
|
||||||
|
|
||||||
m_addAction.setIcon(QIcon(QStringLiteral(":/icons/IconAdd.svg")));
|
m_addRemoveButtons.addAction()->setToolTip(tr("Add new layer mapping"));
|
||||||
m_addAction.setToolTip(tr("Add new layer mapping"));
|
connect(m_addRemoveButtons.addAction(), SIGNAL(triggered(bool)), this, SLOT(doAdd()));
|
||||||
m_addButton.setDefaultAction(&m_addAction);
|
m_addRemoveButtons.removeAction()->setToolTip(tr("Remove selected layer mappings"));
|
||||||
m_addButton.setFixedSize(32, 32);
|
connect(m_addRemoveButtons.removeAction(), SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));
|
||||||
connect(&m_addAction, SIGNAL(triggered(bool)), this, SLOT(doAdd()));
|
|
||||||
|
|
||||||
m_removeAction.setIcon(QIcon(QStringLiteral(":/icons/IconRemove.svg")));
|
|
||||||
m_removeAction.setToolTip(tr("Remove selected layer mappings"));
|
|
||||||
m_removeButton.setDefaultAction(&m_removeAction);
|
|
||||||
m_removeButton.setFixedSize(32, 32);
|
|
||||||
connect(&m_removeAction, SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));
|
|
||||||
m_removeAction.setEnabled(false);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,21 +66,18 @@ class LayersEditor : public EditorWidget
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
LayersModel m_model;
|
LayersModel m_model;
|
||||||
LayersTableView m_tableView;
|
LayersTableView m_tableView;
|
||||||
QAction m_addAction;
|
AddRemoveButtons m_addRemoveButtons;
|
||||||
QToolButton m_addButton;
|
|
||||||
QAction m_removeAction;
|
|
||||||
QToolButton m_removeButton;
|
|
||||||
public:
|
public:
|
||||||
explicit LayersEditor(QWidget* parent = Q_NULLPTR);
|
explicit LayersEditor(QWidget* parent = Q_NULLPTR);
|
||||||
bool loadData(ProjectModel::LayersNode* node);
|
bool loadData(ProjectModel::LayersNode* node);
|
||||||
void unloadData();
|
void unloadData();
|
||||||
ProjectModel::INode* currentNode() const;
|
ProjectModel::INode* currentNode() const;
|
||||||
void resizeEvent(QResizeEvent* ev);
|
void resizeEvent(QResizeEvent* ev);
|
||||||
|
bool isItemEditEnabled() const;
|
||||||
public slots:
|
public slots:
|
||||||
void doAdd();
|
void doAdd();
|
||||||
void doSelectionChanged(const QItemSelection& selected);
|
void doSelectionChanged();
|
||||||
|
|
||||||
bool isItemEditEnabled() const;
|
|
||||||
void itemCutAction();
|
void itemCutAction();
|
||||||
void itemCopyAction();
|
void itemCopyAction();
|
||||||
void itemPasteAction();
|
void itemPasteAction();
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "KeymapEditor.hpp"
|
#include "KeymapEditor.hpp"
|
||||||
#include "LayersEditor.hpp"
|
#include "LayersEditor.hpp"
|
||||||
#include "SampleEditor.hpp"
|
#include "SampleEditor.hpp"
|
||||||
|
#include "NewSoundMacroDialog.hpp"
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget* parent)
|
MainWindow::MainWindow(QWidget* parent)
|
||||||
: QMainWindow(parent),
|
: QMainWindow(parent),
|
||||||
|
@ -258,6 +259,7 @@ bool MainWindow::setProjectPath(const QString& path)
|
||||||
m_ui.actionRevert_Project->setEnabled(true);
|
m_ui.actionRevert_Project->setEnabled(true);
|
||||||
m_ui.actionReload_Sample_Data->setEnabled(true);
|
m_ui.actionReload_Sample_Data->setEnabled(true);
|
||||||
m_ui.actionExport_GameCube_Groups->setEnabled(true);
|
m_ui.actionExport_GameCube_Groups->setEnabled(true);
|
||||||
|
m_ui.actionNew_Subproject->setEnabled(true);
|
||||||
setWindowFilePath(path);
|
setWindowFilePath(path);
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
updateFocus();
|
updateFocus();
|
||||||
|
@ -322,14 +324,16 @@ void MainWindow::timerEvent(QTimerEvent* ev)
|
||||||
if (m_engine->getActiveVoices().empty() && m_uiDisabled)
|
if (m_engine->getActiveVoices().empty() && m_uiDisabled)
|
||||||
{
|
{
|
||||||
m_ui.projectOutline->setEnabled(true);
|
m_ui.projectOutline->setEnabled(true);
|
||||||
m_ui.editorContents->setEnabled(true);
|
if (EditorWidget* w = getEditorWidget())
|
||||||
|
w->setEditorEnabled(true);
|
||||||
m_ui.menubar->setEnabled(true);
|
m_ui.menubar->setEnabled(true);
|
||||||
m_uiDisabled = false;
|
m_uiDisabled = false;
|
||||||
}
|
}
|
||||||
else if (!m_engine->getActiveVoices().empty() && !m_uiDisabled)
|
else if (!m_engine->getActiveVoices().empty() && !m_uiDisabled)
|
||||||
{
|
{
|
||||||
m_ui.projectOutline->setEnabled(false);
|
m_ui.projectOutline->setEnabled(false);
|
||||||
m_ui.editorContents->setEnabled(false);
|
if (EditorWidget* w = getEditorWidget())
|
||||||
|
w->setEditorEnabled(false);
|
||||||
m_ui.menubar->setEnabled(false);
|
m_ui.menubar->setEnabled(false);
|
||||||
m_uiDisabled = true;
|
m_uiDisabled = true;
|
||||||
}
|
}
|
||||||
|
@ -342,6 +346,21 @@ void MainWindow::timerEvent(QTimerEvent* ev)
|
||||||
else
|
else
|
||||||
sampleEditor->setSamplePos(-1);
|
sampleEditor->setSamplePos(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QTableView* songTable = m_songGroupEditor->getSetupListView();
|
||||||
|
for (int i = 0; i < songTable->model()->rowCount(); ++i)
|
||||||
|
if (MIDIPlayerWidget* player = qobject_cast<MIDIPlayerWidget*>(
|
||||||
|
songTable->indexWidget(songTable->model()->index(i, 1))))
|
||||||
|
for (auto& p : m_engine->getActiveSequencers())
|
||||||
|
if (p.get() == player->sequencer() && p->state() != amuse::SequencerState::Playing)
|
||||||
|
player->stopped();
|
||||||
|
|
||||||
|
QTableView* sfxTable = m_soundGroupEditor->getSFXListView();
|
||||||
|
for (int i = 0; i < sfxTable->model()->rowCount(); ++i)
|
||||||
|
if (SFXPlayerWidget* player = qobject_cast<SFXPlayerWidget*>(
|
||||||
|
sfxTable->indexWidget(sfxTable->model()->index(i, 1))))
|
||||||
|
if (player->voice() && player->voice()->state() != amuse::VoiceState::Playing)
|
||||||
|
player->stopped();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -547,6 +566,27 @@ amuse::ObjToken<amuse::Voice> MainWindow::startEditorVoice(uint8_t key, uint8_t
|
||||||
return vox;
|
return vox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
amuse::ObjToken<amuse::Voice> MainWindow::startSFX(amuse::GroupId groupId, amuse::SFXId sfxId)
|
||||||
|
{
|
||||||
|
if (ProjectModel::INode* node = getEditorNode())
|
||||||
|
{
|
||||||
|
amuse::AudioGroupDatabase* group = projectModel()->getGroupNode(node)->getAudioGroup();
|
||||||
|
return m_engine->fxStart(group, groupId, sfxId, 1.f, 0.f);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
amuse::ObjToken<amuse::Sequencer> MainWindow::startSong(amuse::GroupId groupId, amuse::SongId songId,
|
||||||
|
const unsigned char* arrData)
|
||||||
|
{
|
||||||
|
if (ProjectModel::INode* node = getEditorNode())
|
||||||
|
{
|
||||||
|
amuse::AudioGroupDatabase* group = projectModel()->getGroupNode(node)->getAudioGroup();
|
||||||
|
return m_engine->seqPlay(group, groupId, songId, arrData);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::pushUndoCommand(QUndoCommand* cmd)
|
void MainWindow::pushUndoCommand(QUndoCommand* cmd)
|
||||||
{
|
{
|
||||||
m_undoStack->push(cmd);
|
m_undoStack->push(cmd);
|
||||||
|
@ -858,44 +898,217 @@ bool TreeDelegate::editorEvent(QEvent* event,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString MainWindow::getGroupName(ProjectModel::GroupNode* group) const
|
||||||
|
{
|
||||||
|
if (group)
|
||||||
|
return group->text();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectModel::GroupNode* MainWindow::getSelectedGroupNode() const
|
||||||
|
{
|
||||||
|
if (!m_projectModel)
|
||||||
|
return nullptr;
|
||||||
|
if (!m_ui.projectOutline->selectionModel()->currentIndex().isValid())
|
||||||
|
return nullptr;
|
||||||
|
return m_projectModel->getGroupNode(m_projectModel->node(m_ui.projectOutline->selectionModel()->currentIndex()));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MainWindow::getSelectedGroupName() const
|
||||||
|
{
|
||||||
|
return getGroupName(getSelectedGroupNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::recursiveExpandOutline(const QModelIndex& index) const
|
||||||
|
{
|
||||||
|
if (index.isValid())
|
||||||
|
{
|
||||||
|
recursiveExpandAndSelectOutline(index.parent());
|
||||||
|
m_ui.projectOutline->expand(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::recursiveExpandAndSelectOutline(const QModelIndex& index) const
|
||||||
|
{
|
||||||
|
recursiveExpandOutline(index);
|
||||||
|
if (index.isValid())
|
||||||
|
{
|
||||||
|
m_ui.projectOutline->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
|
||||||
|
m_ui.projectOutline->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::newSubprojectAction()
|
void MainWindow::newSubprojectAction()
|
||||||
{
|
{
|
||||||
|
QString newName;
|
||||||
|
bool ok = true;
|
||||||
|
while (ok && newName.isEmpty())
|
||||||
|
newName = QInputDialog::getText(this, tr("New Subproject"),
|
||||||
|
tr("What should this subproject be named?"), QLineEdit::Normal, QString(), &ok);
|
||||||
|
if (!ok)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProjectModel::GroupNode* node = m_projectModel->newSubproject(newName, m_mainMessenger);
|
||||||
|
if (node)
|
||||||
|
recursiveExpandAndSelectOutline(m_projectModel->index(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::newSFXGroupAction()
|
void MainWindow::newSFXGroupAction()
|
||||||
{
|
{
|
||||||
|
ProjectModel::GroupNode* group = getSelectedGroupNode();
|
||||||
|
m_projectModel->setIdDatabases(group);
|
||||||
|
QString groupName = getGroupName(group);
|
||||||
|
QString newName;
|
||||||
|
bool ok = true;
|
||||||
|
while (ok && newName.isEmpty())
|
||||||
|
newName = QInputDialog::getText(this, tr("New SFX Group"),
|
||||||
|
tr("What should the new SFX group in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(
|
||||||
|
amuse::GroupId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Group)), &ok);
|
||||||
|
if (!ok)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProjectModel::SoundGroupNode* node = m_projectModel->newSoundGroup(group, newName, m_mainMessenger);
|
||||||
|
if (node)
|
||||||
|
{
|
||||||
|
recursiveExpandAndSelectOutline(m_projectModel->index(node));
|
||||||
|
openEditor(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::newSongGroupAction()
|
void MainWindow::newSongGroupAction()
|
||||||
{
|
{
|
||||||
|
ProjectModel::GroupNode* group = getSelectedGroupNode();
|
||||||
|
m_projectModel->setIdDatabases(group);
|
||||||
|
QString groupName = getGroupName(group);
|
||||||
|
QString newName;
|
||||||
|
bool ok = true;
|
||||||
|
while (ok && newName.isEmpty())
|
||||||
|
newName = QInputDialog::getText(this, tr("New Song Group"),
|
||||||
|
tr("What should the new Song group in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(
|
||||||
|
amuse::GroupId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Group)), &ok);
|
||||||
|
if (!ok)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProjectModel::SongGroupNode* node = m_projectModel->newSongGroup(group, newName, m_mainMessenger);
|
||||||
|
if (node)
|
||||||
|
{
|
||||||
|
recursiveExpandAndSelectOutline(m_projectModel->index(node));
|
||||||
|
openEditor(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::newSoundMacroAction()
|
void MainWindow::newSoundMacroAction()
|
||||||
{
|
{
|
||||||
|
ProjectModel::GroupNode* group = getSelectedGroupNode();
|
||||||
|
m_projectModel->setIdDatabases(group);
|
||||||
|
QString groupName = getGroupName(group);
|
||||||
|
QString newName;
|
||||||
|
const SoundMacroTemplateEntry* templ = nullptr;
|
||||||
|
int result = QDialog::Accepted;
|
||||||
|
while (result == QDialog::Accepted && newName.isEmpty())
|
||||||
|
{
|
||||||
|
NewSoundMacroDialog dialog(groupName, this);
|
||||||
|
result = dialog.exec();
|
||||||
|
newName = dialog.getName();
|
||||||
|
templ = dialog.getSelectedTemplate();
|
||||||
|
}
|
||||||
|
if (result == QDialog::Rejected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProjectModel::SoundMacroNode* node = m_projectModel->newSoundMacro(group, newName, m_mainMessenger, templ);
|
||||||
|
if (node)
|
||||||
|
{
|
||||||
|
recursiveExpandAndSelectOutline(m_projectModel->index(node));
|
||||||
|
openEditor(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::newADSRAction()
|
void MainWindow::newADSRAction()
|
||||||
{
|
{
|
||||||
|
ProjectModel::GroupNode* group = getSelectedGroupNode();
|
||||||
|
m_projectModel->setIdDatabases(group);
|
||||||
|
QString groupName = getGroupName(group);
|
||||||
|
QString newName;
|
||||||
|
bool ok = true;
|
||||||
|
while (ok && newName.isEmpty())
|
||||||
|
newName = QInputDialog::getText(this, tr("New ADSR"),
|
||||||
|
tr("What should the new ADSR in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(
|
||||||
|
amuse::TableId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Table)), &ok);
|
||||||
|
if (!ok)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProjectModel::ADSRNode* node = m_projectModel->newADSR(group, newName, m_mainMessenger);
|
||||||
|
if (node)
|
||||||
|
{
|
||||||
|
recursiveExpandAndSelectOutline(m_projectModel->index(node));
|
||||||
|
openEditor(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::newCurveAction()
|
void MainWindow::newCurveAction()
|
||||||
{
|
{
|
||||||
|
ProjectModel::GroupNode* group = getSelectedGroupNode();
|
||||||
|
m_projectModel->setIdDatabases(group);
|
||||||
|
QString groupName = getGroupName(group);
|
||||||
|
QString newName;
|
||||||
|
bool ok = true;
|
||||||
|
while (ok && newName.isEmpty())
|
||||||
|
newName = QInputDialog::getText(this, tr("New Curve"),
|
||||||
|
tr("What should the new Curve in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(
|
||||||
|
amuse::TableId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Table)), &ok);
|
||||||
|
if (!ok)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProjectModel::CurveNode* node = m_projectModel->newCurve(group, newName, m_mainMessenger);
|
||||||
|
if (node)
|
||||||
|
{
|
||||||
|
recursiveExpandAndSelectOutline(m_projectModel->index(node));
|
||||||
|
openEditor(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::newKeymapAction()
|
void MainWindow::newKeymapAction()
|
||||||
{
|
{
|
||||||
|
ProjectModel::GroupNode* group = getSelectedGroupNode();
|
||||||
|
m_projectModel->setIdDatabases(group);
|
||||||
|
QString groupName = getGroupName(group);
|
||||||
|
QString newName;
|
||||||
|
bool ok = true;
|
||||||
|
while (ok && newName.isEmpty())
|
||||||
|
newName = QInputDialog::getText(this, tr("New Keymap"),
|
||||||
|
tr("What should the new Keymap in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(
|
||||||
|
amuse::KeymapId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Keymap)), &ok);
|
||||||
|
if (!ok)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProjectModel::KeymapNode* node = m_projectModel->newKeymap(group, newName, m_mainMessenger);
|
||||||
|
if (node)
|
||||||
|
{
|
||||||
|
recursiveExpandAndSelectOutline(m_projectModel->index(node));
|
||||||
|
openEditor(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::newLayersAction()
|
void MainWindow::newLayersAction()
|
||||||
{
|
{
|
||||||
|
ProjectModel::GroupNode* group = getSelectedGroupNode();
|
||||||
|
m_projectModel->setIdDatabases(group);
|
||||||
|
QString groupName = getGroupName(group);
|
||||||
|
QString newName;
|
||||||
|
bool ok = true;
|
||||||
|
while (ok && newName.isEmpty())
|
||||||
|
newName = QInputDialog::getText(this, tr("New Layers"),
|
||||||
|
tr("What should the new Layers in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(
|
||||||
|
amuse::LayersId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Layer)), &ok);
|
||||||
|
if (!ok)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProjectModel::LayersNode* node = m_projectModel->newLayers(group, newName, m_mainMessenger);
|
||||||
|
if (node)
|
||||||
|
{
|
||||||
|
recursiveExpandAndSelectOutline(m_projectModel->index(node));
|
||||||
|
openEditor(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::aboutToShowAudioIOMenu()
|
void MainWindow::aboutToShowAudioIOMenu()
|
||||||
|
@ -1055,26 +1268,36 @@ void MainWindow::setItemEditEnabled(bool enabled)
|
||||||
m_ui.actionDelete->setEnabled(enabled);
|
m_ui.actionDelete->setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::setItemNewEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
m_ui.actionNew_SFX_Group->setEnabled(enabled);
|
||||||
|
m_ui.actionNew_Song_Group->setEnabled(enabled);
|
||||||
|
m_ui.actionNew_Sound_Macro->setEnabled(enabled);
|
||||||
|
m_ui.actionNew_ADSR->setEnabled(enabled);
|
||||||
|
m_ui.actionNew_Curve->setEnabled(enabled);
|
||||||
|
m_ui.actionNew_Keymap->setEnabled(enabled);
|
||||||
|
m_ui.actionNew_Layers->setEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
bool MainWindow::canEditOutline()
|
bool MainWindow::canEditOutline()
|
||||||
{
|
{
|
||||||
if (!m_projectModel)
|
if (!m_projectModel)
|
||||||
return false;
|
return false;
|
||||||
QModelIndexList indexes = m_ui.projectOutline->selectionModel()->selectedIndexes();
|
QModelIndex curIndex = m_ui.projectOutline->selectionModel()->currentIndex();
|
||||||
if (indexes.empty())
|
if (!curIndex.isValid())
|
||||||
return false;
|
return false;
|
||||||
return m_projectModel->canEdit(indexes.front());
|
return m_projectModel->canEdit(curIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
|
void MainWindow::onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
|
||||||
{
|
{
|
||||||
if (!m_projectModel)
|
if (!m_projectModel)
|
||||||
return;
|
return;
|
||||||
|
setItemNewEnabled(m_ui.projectOutline->selectionModel()->currentIndex().isValid());
|
||||||
if (selected.indexes().empty())
|
if (selected.indexes().empty())
|
||||||
{
|
|
||||||
setItemEditEnabled(false);
|
setItemEditEnabled(false);
|
||||||
return;
|
else
|
||||||
}
|
setItemEditEnabled(m_projectModel->canEdit(selected.indexes().front()));
|
||||||
setItemEditEnabled(m_projectModel->canEdit(selected.indexes().front()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onTextSelect()
|
void MainWindow::onTextSelect()
|
||||||
|
|
|
@ -146,10 +146,19 @@ public:
|
||||||
ProjectModel::INode* getEditorNode() const;
|
ProjectModel::INode* getEditorNode() const;
|
||||||
EditorWidget* getEditorWidget() const;
|
EditorWidget* getEditorWidget() const;
|
||||||
amuse::ObjToken<amuse::Voice> startEditorVoice(uint8_t key, uint8_t vel);
|
amuse::ObjToken<amuse::Voice> startEditorVoice(uint8_t key, uint8_t vel);
|
||||||
|
amuse::ObjToken<amuse::Voice> startSFX(amuse::GroupId groupId, amuse::SFXId sfxId);
|
||||||
|
amuse::ObjToken<amuse::Sequencer> startSong(amuse::GroupId groupId, amuse::SongId songId,
|
||||||
|
const unsigned char* arrData);
|
||||||
void pushUndoCommand(QUndoCommand* cmd);
|
void pushUndoCommand(QUndoCommand* cmd);
|
||||||
void updateFocus();
|
void updateFocus();
|
||||||
void aboutToDeleteNode(ProjectModel::INode* node);
|
void aboutToDeleteNode(ProjectModel::INode* node);
|
||||||
|
|
||||||
|
QString getGroupName(ProjectModel::GroupNode* group) const;
|
||||||
|
ProjectModel::GroupNode* getSelectedGroupNode() const;
|
||||||
|
QString getSelectedGroupName() const;
|
||||||
|
void recursiveExpandOutline(const QModelIndex& index) const;
|
||||||
|
void recursiveExpandAndSelectOutline(const QModelIndex& index) const;
|
||||||
|
|
||||||
ProjectModel* projectModel() const { return m_projectModel; }
|
ProjectModel* projectModel() const { return m_projectModel; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -193,6 +202,7 @@ public slots:
|
||||||
void onFocusChanged(QWidget* old, QWidget* now);
|
void onFocusChanged(QWidget* old, QWidget* now);
|
||||||
void outlineItemActivated(const QModelIndex& index);
|
void outlineItemActivated(const QModelIndex& index);
|
||||||
void setItemEditEnabled(bool enabled);
|
void setItemEditEnabled(bool enabled);
|
||||||
|
void setItemNewEnabled(bool enabled);
|
||||||
bool canEditOutline();
|
bool canEditOutline();
|
||||||
void onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
|
void onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
|
||||||
void onTextSelect();
|
void onTextSelect();
|
||||||
|
|
|
@ -77,16 +77,7 @@
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QStackedWidget" name="editorContents">
|
<widget class="QStackedWidget" name="editorContents"/>
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>1103</width>
|
|
||||||
<height>606</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QWidget" name="keyboard" native="true">
|
<widget class="QWidget" name="keyboard" native="true">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
@ -232,7 +223,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1501</width>
|
<width>1501</width>
|
||||||
<height>80</height>
|
<height>85</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
|
@ -269,7 +260,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1360</width>
|
<width>1360</width>
|
||||||
<height>34</height>
|
<height>27</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuFile">
|
<widget class="QMenu" name="menuFile">
|
||||||
|
@ -313,7 +304,6 @@
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>&Audio</string>
|
<string>&Audio</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionAuto_Play"/>
|
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionSelect_Output_Device"/>
|
<addaction name="actionSelect_Output_Device"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -450,17 +440,6 @@
|
||||||
<string>New &Layers</string>
|
<string>New &Layers</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionAuto_Play">
|
|
||||||
<property name="checkable">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="checked">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>&Auto-Play</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionSelect_Output_Device">
|
<action name="actionSelect_Output_Device">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
|
|
|
@ -0,0 +1,287 @@
|
||||||
|
#include "NewSoundMacroDialog.hpp"
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include "amuse/Common.hpp"
|
||||||
|
|
||||||
|
static const uint32_t BasicMacro[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x07010001, 0x0000FFFF,
|
||||||
|
0x11000000, 0x00000000,
|
||||||
|
0x31000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t Looped[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x07010001, 0x0000FFFF,
|
||||||
|
0x3000D08A, 0x00000000,
|
||||||
|
0x38000000, 0xE8030000,
|
||||||
|
0x0F000000, 0x0001E803,
|
||||||
|
0x07000001, 0x0000E803,
|
||||||
|
0x11000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t LoopRelease[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x1C000F01, 0x0001FA00,
|
||||||
|
0x07010000, 0x0000FFFF,
|
||||||
|
0x3000D08A, 0x00000000,
|
||||||
|
0x0F500000, 0x00013200,
|
||||||
|
0x07000000, 0x00003200,
|
||||||
|
0x38000000, 0xE8030000,
|
||||||
|
0x0F000000, 0x0001E803,
|
||||||
|
0x07000000, 0x0000E803,
|
||||||
|
0x11000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t LoopSoftRelease[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x1C000F01, 0x0001C800,
|
||||||
|
0x07010000, 0x0000FFFF,
|
||||||
|
0x3000D08A, 0x00000000,
|
||||||
|
0x0F600000, 0x00016400,
|
||||||
|
0x07000000, 0x00006400,
|
||||||
|
0x38000000, 0xE8030000,
|
||||||
|
0x0F000000, 0x0001E803,
|
||||||
|
0x07000000, 0x0000E803,
|
||||||
|
0x11000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t LoopSoftReleaseNoClick[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x147F0000, 0x00010100,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x1C000F01, 0x0001C800,
|
||||||
|
0x07010000, 0x0000FFFF,
|
||||||
|
0x3000D08A, 0x00000000,
|
||||||
|
0x0F600000, 0x00016400,
|
||||||
|
0x07000000, 0x00006400,
|
||||||
|
0x38000000, 0xE8030000,
|
||||||
|
0x0F000000, 0x0001E803,
|
||||||
|
0x07000000, 0x0000E803,
|
||||||
|
0x11000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t LoopADSR[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x0C000000, 0x00000000,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x1C000F01, 0x0001C800,
|
||||||
|
0x07010001, 0x0000FFFF,
|
||||||
|
0x3000D08A, 0x00000000,
|
||||||
|
0x12000000, 0x00000000,
|
||||||
|
0x07000001, 0x0000E803,
|
||||||
|
0x38000000, 0xE8030000,
|
||||||
|
0x07000001, 0x0000E803,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t LoopADSRSoftRelease[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x0C000000, 0x00000000,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x1C000F01, 0x0001C800,
|
||||||
|
0x07010001, 0x0000FFFF,
|
||||||
|
0x3000D08A, 0x00000000,
|
||||||
|
0x0F600000, 0x00017800,
|
||||||
|
0x07000001, 0x00007800,
|
||||||
|
0x12000000, 0x00000000,
|
||||||
|
0x38000000, 0xE8030000,
|
||||||
|
0x07000001, 0x0000E803,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t LoopHold[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x1C000F01, 0x0001C800,
|
||||||
|
0x07010001, 0x0000FFFF,
|
||||||
|
0x3000D08A, 0x00000000,
|
||||||
|
0x0F600000, 0x00016400,
|
||||||
|
0x07000001, 0x00006400,
|
||||||
|
0x38000000, 0xE8030000,
|
||||||
|
0x0F000000, 0x0001E803,
|
||||||
|
0x07000001, 0x0000E803,
|
||||||
|
0x11000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t OneShot[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x07000001, 0x0000FFFF,
|
||||||
|
0x31000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t OneShotFixedNote[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x193C0000, 0x00010000,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x07000001, 0x0000FFFF,
|
||||||
|
0x31000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t OneShotNoClick[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x147F0000, 0x00010100,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x07000001, 0x0000FFFF,
|
||||||
|
0x31000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t OneShotFixedNoClick[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x147F0000, 0x00010100,
|
||||||
|
0x193C0000, 0x00010000,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x07000001, 0x0000FFFF,
|
||||||
|
0x31000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t Bubbles[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x0D600000, 0x00010000,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x1D08E803, 0x00010000,
|
||||||
|
0x1E0544FD, 0x00010000,
|
||||||
|
0x0F000000, 0x0001F401,
|
||||||
|
0x07000000, 0x0000F401,
|
||||||
|
0x11000000, 0x00000000,
|
||||||
|
0x31000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t DownTrigger[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x07000000, 0x00001200,
|
||||||
|
0x11000000, 0x00000000,
|
||||||
|
0x07000000, 0x00000100,
|
||||||
|
0x18FE0000, 0x00010000,
|
||||||
|
0x05000000, 0x01000C00,
|
||||||
|
0x0F000000, 0x00016400,
|
||||||
|
0x07000001, 0x00006400,
|
||||||
|
0x31000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t LongFadeInAndStop[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x147F0000, 0x0001E803,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x07000001, 0x0000E803,
|
||||||
|
0x11000000, 0x00000000,
|
||||||
|
0x31000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t FadeInAndStop[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x147F0000, 0x0001C800,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x07000001, 0x0000C800,
|
||||||
|
0x11000000, 0x00000000,
|
||||||
|
0x31000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t RandomTrigger[] =
|
||||||
|
{
|
||||||
|
0x38000000, 0xC07A1000,
|
||||||
|
0x0F000000, 0x0001F401,
|
||||||
|
0x195A0000, 0x00010000,
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x1750005A, 0x01000000,
|
||||||
|
0x07000001, 0x00002300,
|
||||||
|
0x05000000, 0x03001400,
|
||||||
|
0x11000000, 0x00000000,
|
||||||
|
0x31000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t SimplePlaySample[] =
|
||||||
|
{
|
||||||
|
0x10000000, 0x00000000,
|
||||||
|
0x00000000, 0x00000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const SoundMacroTemplateEntry Entries[] =
|
||||||
|
{
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "Basic Macro"), sizeof(BasicMacro), BasicMacro},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "Looped"), sizeof(Looped), Looped},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "Loop Release"), sizeof(LoopRelease), LoopRelease},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "Loop Soft Release"), sizeof(LoopSoftRelease), LoopSoftRelease},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "Loop Soft Release No Click"), sizeof(LoopSoftReleaseNoClick), LoopSoftReleaseNoClick},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "Loop ADSR"), sizeof(LoopADSR), LoopADSR},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "Loop ADSR Soft Release"), sizeof(LoopADSRSoftRelease), LoopADSRSoftRelease},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "Loop Hold"), sizeof(LoopHold), LoopHold},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "One-Shot"), sizeof(OneShot), OneShot},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "One-Shot Fixed Note"), sizeof(OneShotFixedNote), OneShotFixedNote},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "One-Shot No Click"), sizeof(OneShotNoClick), OneShotNoClick},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "One-Shot Fixed No Click"), sizeof(OneShotFixedNoClick), OneShotFixedNoClick},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "Bubbles"), sizeof(Bubbles), Bubbles},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "Down Trigger"), sizeof(DownTrigger), DownTrigger},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "Long Fade in and Stop"), sizeof(LongFadeInAndStop), LongFadeInAndStop},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "Fade in and Stop"), sizeof(FadeInAndStop), FadeInAndStop},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "Random Trigger"), sizeof(RandomTrigger), RandomTrigger},
|
||||||
|
{QT_TRANSLATE_NOOP("NewSoundMacroDialog", "Simple Play Sample"), sizeof(SimplePlaySample), SimplePlaySample}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SoundMacroTemplateEntry* NewSoundMacroDialog::getSelectedTemplate() const
|
||||||
|
{
|
||||||
|
return &Entries[m_combo.currentIndex()];
|
||||||
|
}
|
||||||
|
|
||||||
|
NewSoundMacroDialog::NewSoundMacroDialog(const QString& groupName, QWidget* parent)
|
||||||
|
: QDialog(parent),
|
||||||
|
m_le(QString::fromStdString(amuse::SoundMacroId::CurNameDB->generateDefaultName(amuse::NameDB::Type::SoundMacro))),
|
||||||
|
m_buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal)
|
||||||
|
{
|
||||||
|
setWindowTitle(tr("New Sound Macro"));
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
for (const auto& ent : Entries)
|
||||||
|
m_combo.addItem(tr(ent.m_name), idx++);
|
||||||
|
m_combo.setCurrentIndex(0);
|
||||||
|
|
||||||
|
QObject::connect(&m_buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
|
||||||
|
QObject::connect(&m_buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
|
||||||
|
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout;
|
||||||
|
|
||||||
|
layout->addWidget(new QLabel(tr("What should the new macro in %1 be named?").arg(groupName)));
|
||||||
|
layout->addWidget(&m_le);
|
||||||
|
layout->addWidget(new QLabel(tr("Sound Macro Template")));
|
||||||
|
layout->addWidget(&m_combo);
|
||||||
|
layout->addWidget(&m_buttonBox);
|
||||||
|
|
||||||
|
setLayout(layout);
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
#ifndef AMUSE_NEW_SOUND_MACRO_DIALOG_HPP
|
||||||
|
#define AMUSE_NEW_SOUND_MACRO_DIALOG_HPP
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
|
||||||
|
struct SoundMacroTemplateEntry
|
||||||
|
{
|
||||||
|
const char* m_name;
|
||||||
|
size_t m_length;
|
||||||
|
const uint32_t* m_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NewSoundMacroDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QLineEdit m_le;
|
||||||
|
QComboBox m_combo;
|
||||||
|
QDialogButtonBox m_buttonBox;
|
||||||
|
public:
|
||||||
|
explicit NewSoundMacroDialog(const QString& groupName, QWidget* parent = Q_NULLPTR);
|
||||||
|
QString getName() const { return m_le.text(); }
|
||||||
|
const SoundMacroTemplateEntry* getSelectedTemplate() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // AMUSE_NEW_SOUND_MACRO_DIALOG_HPP
|
|
@ -336,10 +336,38 @@ ProjectModel::ProjectModel(const QString& path, QObject* parent)
|
||||||
SoundGroupNode::Icon = QIcon(":/icons/IconSoundGroup.svg");
|
SoundGroupNode::Icon = QIcon(":/icons/IconSoundGroup.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProjectModel::_buildSortedList()
|
||||||
|
{
|
||||||
|
m_sorted.clear();
|
||||||
|
m_sorted.reserve(m_groups.size());
|
||||||
|
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it)
|
||||||
|
m_sorted.emplace_back(it);
|
||||||
|
std::sort(m_sorted.begin(), m_sorted.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex ProjectModel::_indexOfGroup(const QString& groupName) const
|
||||||
|
{
|
||||||
|
auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), groupName);
|
||||||
|
if (search == m_sorted.cend() || search->m_it->first != groupName)
|
||||||
|
return QModelIndex();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int idx = search - m_sorted.begin();
|
||||||
|
return createIndex(idx, 0, m_root->child(idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int ProjectModel::_hypotheticalIndexOfGroup(const QString& groupName) const
|
||||||
|
{
|
||||||
|
auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), groupName);
|
||||||
|
return search - m_sorted.begin();
|
||||||
|
}
|
||||||
|
|
||||||
bool ProjectModel::clearProjectData()
|
bool ProjectModel::clearProjectData()
|
||||||
{
|
{
|
||||||
m_projectDatabase = amuse::ProjectDatabase();
|
m_projectDatabase = amuse::ProjectDatabase();
|
||||||
m_groups.clear();
|
m_groups.clear();
|
||||||
|
m_sorted.clear();
|
||||||
m_midiFiles.clear();
|
m_midiFiles.clear();
|
||||||
|
|
||||||
m_needsReset = true;
|
m_needsReset = true;
|
||||||
|
@ -473,92 +501,98 @@ bool ProjectModel::saveToFile(UIMessenger& messenger)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectModel::_resetModelData()
|
void ProjectModel::_buildGroupNode(GroupNode& gn)
|
||||||
{
|
{
|
||||||
beginResetModel();
|
amuse::AudioGroup& group = gn.m_it->second;
|
||||||
m_projectDatabase.setIdDatabases();
|
auto& songGroups = group.getProj().songGroups();
|
||||||
m_root = amuse::MakeObj<RootNode>();
|
auto& sfxGroups = group.getProj().sfxGroups();
|
||||||
m_root->reserve(m_groups.size());
|
auto& soundMacros = group.getPool().soundMacros();
|
||||||
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it)
|
auto& tables = group.getPool().tables();
|
||||||
|
auto& keymaps = group.getPool().keymaps();
|
||||||
|
auto& layers = group.getPool().layers();
|
||||||
|
auto& samples = group.getSdir().sampleEntries();
|
||||||
|
gn.reserve(songGroups.size() + sfxGroups.size() + 4);
|
||||||
|
for (const auto& grp : SortUnorderedMap(songGroups))
|
||||||
|
gn.makeChild<SongGroupNode>(grp.first, grp.second.get());
|
||||||
|
for (const auto& grp : SortUnorderedMap(sfxGroups))
|
||||||
|
gn.makeChild<SoundGroupNode>(grp.first, grp.second.get());
|
||||||
{
|
{
|
||||||
it->second.setIdDatabases();
|
CollectionNode& col =
|
||||||
GroupNode& gn = m_root->makeChild<GroupNode>(it);
|
gn.makeChild<CollectionNode>(tr("Sound Macros"), QIcon(":/icons/IconSoundMacro.svg"), INode::Type::SoundMacro);
|
||||||
amuse::AudioGroup& group = it->second;
|
col.reserve(soundMacros.size());
|
||||||
auto& songGroups = group.getProj().songGroups();
|
for (const auto& macro : SortUnorderedMap(soundMacros))
|
||||||
auto& sfxGroups = group.getProj().sfxGroups();
|
col.makeChild<SoundMacroNode>(macro.first, macro.second.get());
|
||||||
auto& soundMacros = group.getPool().soundMacros();
|
}
|
||||||
auto& tables = group.getPool().tables();
|
{
|
||||||
auto& keymaps = group.getPool().keymaps();
|
auto tablesSort = SortUnorderedMap(tables);
|
||||||
auto& layers = group.getPool().layers();
|
size_t ADSRCount = 0;
|
||||||
auto& samples = group.getSdir().sampleEntries();
|
size_t curveCount = 0;
|
||||||
gn.reserve(songGroups.size() + sfxGroups.size() + 4);
|
for (auto& t : tablesSort)
|
||||||
for (const auto& grp : SortUnorderedMap(songGroups))
|
|
||||||
gn.makeChild<SongGroupNode>(grp.first, grp.second.get());
|
|
||||||
for (const auto& grp : SortUnorderedMap(sfxGroups))
|
|
||||||
gn.makeChild<SoundGroupNode>(grp.first, grp.second.get());
|
|
||||||
{
|
{
|
||||||
CollectionNode& col =
|
amuse::ITable::Type tp = (*t.second.get())->Isa();
|
||||||
gn.makeChild<CollectionNode>(tr("Sound Macros"), QIcon(":/icons/IconSoundMacro.svg"), INode::Type::SoundMacro);
|
if (tp == amuse::ITable::Type::ADSR || tp == amuse::ITable::Type::ADSRDLS)
|
||||||
col.reserve(soundMacros.size());
|
ADSRCount += 1;
|
||||||
for (const auto& macro : SortUnorderedMap(soundMacros))
|
else if (tp == amuse::ITable::Type::Curve)
|
||||||
col.makeChild<SoundMacroNode>(macro.first, macro.second.get());
|
curveCount += 1;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto tablesSort = SortUnorderedMap(tables);
|
CollectionNode& col =
|
||||||
size_t ADSRCount = 0;
|
gn.makeChild<CollectionNode>(tr("ADSRs"), QIcon(":/icons/IconADSR.svg"), INode::Type::ADSR);
|
||||||
size_t curveCount = 0;
|
col.reserve(ADSRCount);
|
||||||
for (auto& t : tablesSort)
|
for (auto& t : tablesSort)
|
||||||
{
|
{
|
||||||
amuse::ITable::Type tp = (*t.second.get())->Isa();
|
amuse::ITable::Type tp = (*t.second.get())->Isa();
|
||||||
if (tp == amuse::ITable::Type::ADSR || tp == amuse::ITable::Type::ADSRDLS)
|
if (tp == amuse::ITable::Type::ADSR || tp == amuse::ITable::Type::ADSRDLS)
|
||||||
ADSRCount += 1;
|
col.makeChild<ADSRNode>(t.first, t.second.get());
|
||||||
else if (tp == amuse::ITable::Type::Curve)
|
|
||||||
curveCount += 1;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CollectionNode& col =
|
||||||
|
gn.makeChild<CollectionNode>(tr("Curves"), QIcon(":/icons/IconCurve.svg"), INode::Type::Curve);
|
||||||
|
col.reserve(curveCount);
|
||||||
|
for (auto& t : tablesSort)
|
||||||
{
|
{
|
||||||
CollectionNode& col =
|
amuse::ITable::Type tp = (*t.second.get())->Isa();
|
||||||
gn.makeChild<CollectionNode>(tr("ADSRs"), QIcon(":/icons/IconADSR.svg"), INode::Type::ADSR);
|
if (tp == amuse::ITable::Type::Curve)
|
||||||
col.reserve(ADSRCount);
|
col.makeChild<CurveNode>(t.first, t.second.get());
|
||||||
for (auto& t : tablesSort)
|
|
||||||
{
|
|
||||||
amuse::ITable::Type tp = (*t.second.get())->Isa();
|
|
||||||
if (tp == amuse::ITable::Type::ADSR || tp == amuse::ITable::Type::ADSRDLS)
|
|
||||||
col.makeChild<ADSRNode>(t.first, t.second.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
CollectionNode& col =
|
|
||||||
gn.makeChild<CollectionNode>(tr("Curves"), QIcon(":/icons/IconCurve.svg"), INode::Type::Curve);
|
|
||||||
col.reserve(curveCount);
|
|
||||||
for (auto& t : tablesSort)
|
|
||||||
{
|
|
||||||
amuse::ITable::Type tp = (*t.second.get())->Isa();
|
|
||||||
if (tp == amuse::ITable::Type::Curve)
|
|
||||||
col.makeChild<CurveNode>(t.first, t.second.get());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
}
|
||||||
CollectionNode& col =
|
{
|
||||||
gn.makeChild<CollectionNode>(tr("Keymaps"), QIcon(":/icons/IconKeymap.svg"), INode::Type::Keymap);
|
CollectionNode& col =
|
||||||
col.reserve(keymaps.size());
|
gn.makeChild<CollectionNode>(tr("Keymaps"), QIcon(":/icons/IconKeymap.svg"), INode::Type::Keymap);
|
||||||
for (auto& keymap : SortUnorderedMap(keymaps))
|
col.reserve(keymaps.size());
|
||||||
col.makeChild<KeymapNode>(keymap.first, keymap.second.get());
|
for (auto& keymap : SortUnorderedMap(keymaps))
|
||||||
}
|
col.makeChild<KeymapNode>(keymap.first, keymap.second.get());
|
||||||
{
|
}
|
||||||
CollectionNode& col =
|
{
|
||||||
gn.makeChild<CollectionNode>(tr("Layers"), QIcon(":/icons/IconLayers.svg"), INode::Type::Layer);
|
CollectionNode& col =
|
||||||
col.reserve(layers.size());
|
gn.makeChild<CollectionNode>(tr("Layers"), QIcon(":/icons/IconLayers.svg"), INode::Type::Layer);
|
||||||
for (auto& keymap : SortUnorderedMap(layers))
|
col.reserve(layers.size());
|
||||||
col.makeChild<LayersNode>(keymap.first, keymap.second.get());
|
for (auto& keymap : SortUnorderedMap(layers))
|
||||||
}
|
col.makeChild<LayersNode>(keymap.first, keymap.second.get());
|
||||||
{
|
}
|
||||||
CollectionNode& col =
|
{
|
||||||
gn.makeChild<CollectionNode>(tr("Samples"), QIcon(":/icons/IconSample.svg"), INode::Type::Sample);
|
CollectionNode& col =
|
||||||
col.reserve(samples.size());
|
gn.makeChild<CollectionNode>(tr("Samples"), QIcon(":/icons/IconSample.svg"), INode::Type::Sample);
|
||||||
for (auto& sample : SortUnorderedMap(samples))
|
col.reserve(samples.size());
|
||||||
col.makeChild<SampleNode>(sample.first, sample.second.get());
|
for (auto& sample : SortUnorderedMap(samples))
|
||||||
}
|
col.makeChild<SampleNode>(sample.first, sample.second.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectModel::_resetModelData()
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
_buildSortedList();
|
||||||
|
m_projectDatabase.setIdDatabases();
|
||||||
|
m_root = amuse::MakeObj<RootNode>();
|
||||||
|
m_root->reserve(m_sorted.size());
|
||||||
|
for (auto it = m_sorted.begin() ; it != m_sorted.end() ; ++it)
|
||||||
|
{
|
||||||
|
it->m_it->second.setIdDatabases();
|
||||||
|
GroupNode& gn = m_root->makeChild<GroupNode>(it->m_it);
|
||||||
|
_buildGroupNode(gn);
|
||||||
}
|
}
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
@ -704,32 +738,41 @@ class DeleteNodeUndoCommand : public QUndoCommand
|
||||||
{
|
{
|
||||||
QModelIndex m_deleteIdx;
|
QModelIndex m_deleteIdx;
|
||||||
amuse::ObjToken<ProjectModel::INode> m_node;
|
amuse::ObjToken<ProjectModel::INode> m_node;
|
||||||
|
ProjectModel::NameUndoRegistry m_nameReg;
|
||||||
public:
|
public:
|
||||||
DeleteNodeUndoCommand(const QModelIndex& index)
|
DeleteNodeUndoCommand(const QModelIndex& index)
|
||||||
: QUndoCommand(ProjectModel::tr("Delete %1").arg(index.data().toString())), m_deleteIdx(index) {}
|
: QUndoCommand(ProjectModel::tr("Delete %1").arg(index.data().toString())), m_deleteIdx(index) {}
|
||||||
void undo()
|
void undo()
|
||||||
{
|
{
|
||||||
g_MainWindow->projectModel()->_undoDel(m_deleteIdx, std::move(m_node));
|
g_MainWindow->projectModel()->_undoDel(m_deleteIdx, std::move(m_node), m_nameReg);
|
||||||
m_node.reset();
|
m_node.reset();
|
||||||
|
m_nameReg.clear();
|
||||||
}
|
}
|
||||||
void redo()
|
void redo()
|
||||||
{
|
{
|
||||||
m_node = g_MainWindow->projectModel()->_redoDel(m_deleteIdx);
|
m_node = g_MainWindow->projectModel()->_redoDel(m_deleteIdx, m_nameReg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void ProjectModel::_undoDel(const QModelIndex& index, amuse::ObjToken<ProjectModel::INode> n)
|
void ProjectModel::_undoDel(const QModelIndex& index, amuse::ObjToken<ProjectModel::INode> n, const NameUndoRegistry& nameReg)
|
||||||
{
|
{
|
||||||
beginInsertRows(index.parent(), index.row(), index.row());
|
beginInsertRows(index.parent(), index.row(), index.row());
|
||||||
node(index.parent())->insertChild(index.row(), std::move(n));
|
node(index.parent())->insertChild(index.row(), n);
|
||||||
|
setIdDatabases(n.get());
|
||||||
|
n->depthTraverse([&nameReg](INode* node)
|
||||||
|
{
|
||||||
|
node->registerNames(nameReg);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
amuse::ObjToken<ProjectModel::INode> ProjectModel::_redoDel(const QModelIndex& index)
|
amuse::ObjToken<ProjectModel::INode> ProjectModel::_redoDel(const QModelIndex& index, NameUndoRegistry& nameReg)
|
||||||
{
|
{
|
||||||
node(index)->depthTraverse([](INode* node)
|
node(index)->depthTraverse([&nameReg](INode* node)
|
||||||
{
|
{
|
||||||
g_MainWindow->aboutToDeleteNode(node);
|
g_MainWindow->aboutToDeleteNode(node);
|
||||||
|
node->unregisterNames(nameReg);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
beginRemoveRows(index.parent(), index.row(), index.row());
|
beginRemoveRows(index.parent(), index.row(), index.row());
|
||||||
|
@ -745,6 +788,186 @@ void ProjectModel::del(const QModelIndex& index)
|
||||||
g_MainWindow->pushUndoCommand(new DeleteNodeUndoCommand(index));
|
g_MainWindow->pushUndoCommand(new DeleteNodeUndoCommand(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProjectModel::GroupNode* ProjectModel::newSubproject(const QString& name, UIMessenger& messenger)
|
||||||
|
{
|
||||||
|
if (m_groups.find(name) != m_groups.cend())
|
||||||
|
{
|
||||||
|
messenger.critical(tr("Subproject Conflict"), tr("The subproject %1 is already defined").arg(name));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
QDir dir(QFileInfo(m_dir, name).filePath());
|
||||||
|
if (!MkPath(dir.path(), messenger))
|
||||||
|
return nullptr;
|
||||||
|
QFile(QFileInfo(dir, QStringLiteral("!project.yaml")).filePath()).open(QFile::WriteOnly);
|
||||||
|
QFile(QFileInfo(dir, QStringLiteral("!pool.yaml")).filePath()).open(QFile::WriteOnly);
|
||||||
|
int idx = _hypotheticalIndexOfGroup(name);
|
||||||
|
beginInsertRows(QModelIndex(), idx, idx);
|
||||||
|
auto it = m_groups.emplace(std::make_pair(name, amuse::AudioGroupDatabase(QStringToSysString(dir.path())))).first;
|
||||||
|
_buildSortedList();
|
||||||
|
m_projectDatabase.setIdDatabases();
|
||||||
|
it->second.setIdDatabases();
|
||||||
|
GroupNode& gn = m_root->makeChildAtIdx<GroupNode>(idx, it);
|
||||||
|
_buildGroupNode(gn);
|
||||||
|
endInsertRows();
|
||||||
|
return &gn;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectModel::SoundGroupNode* ProjectModel::newSoundGroup(GroupNode* group, const QString& name, UIMessenger& messenger)
|
||||||
|
{
|
||||||
|
setIdDatabases(group);
|
||||||
|
auto nameKey = name.toUtf8();
|
||||||
|
if (amuse::GroupId::CurNameDB->m_stringToId.find(nameKey.data()) != amuse::GroupId::CurNameDB->m_stringToId.cend())
|
||||||
|
{
|
||||||
|
messenger.critical(tr("Sound Group Conflict"), tr("The group %1 is already defined").arg(name));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
beginInsertRows(index(group), 0, 0);
|
||||||
|
amuse::GroupId newId = amuse::GroupId::CurNameDB->generateId(amuse::NameDB::Type::Group);
|
||||||
|
amuse::GroupId::CurNameDB->registerPair(nameKey.data(), newId);
|
||||||
|
auto node = amuse::MakeObj<amuse::SFXGroupIndex>();
|
||||||
|
group->getAudioGroup()->getProj().sfxGroups()[newId] = node;
|
||||||
|
SoundGroupNode& ret = group->makeChildAtIdx<SoundGroupNode>(0, newId, node);
|
||||||
|
endInsertRows();
|
||||||
|
return &ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectModel::SongGroupNode* ProjectModel::newSongGroup(GroupNode* group, const QString& name, UIMessenger& messenger)
|
||||||
|
{
|
||||||
|
setIdDatabases(group);
|
||||||
|
auto nameKey = name.toUtf8();
|
||||||
|
if (amuse::GroupId::CurNameDB->m_stringToId.find(nameKey.data()) != amuse::GroupId::CurNameDB->m_stringToId.cend())
|
||||||
|
{
|
||||||
|
messenger.critical(tr("Song Group Conflict"), tr("The group %1 is already defined").arg(name));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
beginInsertRows(index(group), 0, 0);
|
||||||
|
amuse::GroupId newId = amuse::GroupId::CurNameDB->generateId(amuse::NameDB::Type::Group);
|
||||||
|
amuse::GroupId::CurNameDB->registerPair(nameKey.data(), newId);
|
||||||
|
auto node = amuse::MakeObj<amuse::SongGroupIndex>();
|
||||||
|
group->getAudioGroup()->getProj().songGroups()[newId] = node;
|
||||||
|
SongGroupNode& ret = group->makeChildAtIdx<SongGroupNode>(0, newId, node);
|
||||||
|
endInsertRows();
|
||||||
|
return &ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectModel::SoundMacroNode* ProjectModel::newSoundMacro(GroupNode* group, const QString& name, UIMessenger& messenger,
|
||||||
|
const SoundMacroTemplateEntry* templ)
|
||||||
|
{
|
||||||
|
setIdDatabases(group);
|
||||||
|
auto nameKey = name.toUtf8();
|
||||||
|
if (amuse::SoundMacroId::CurNameDB->m_stringToId.find(nameKey.data()) != amuse::SoundMacroId::CurNameDB->m_stringToId.cend())
|
||||||
|
{
|
||||||
|
messenger.critical(tr("Sound Macro Conflict"), tr("The macro %1 is already defined").arg(name));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
ProjectModel::CollectionNode* coll = group->getCollectionOfType(INode::Type::SoundMacro);
|
||||||
|
QModelIndex parentIdx = index(coll);
|
||||||
|
int insertIdx = rowCount(parentIdx);
|
||||||
|
beginInsertRows(parentIdx, insertIdx, insertIdx);
|
||||||
|
amuse::SoundMacroId newId = amuse::SoundMacroId::CurNameDB->generateId(amuse::NameDB::Type::SoundMacro);
|
||||||
|
amuse::SoundMacroId::CurNameDB->registerPair(nameKey.data(), newId);
|
||||||
|
auto node = amuse::MakeObj<amuse::SoundMacro>();
|
||||||
|
if (templ)
|
||||||
|
{
|
||||||
|
athena::io::MemoryReader r(templ->m_data, templ->m_length);
|
||||||
|
node->readCmds<athena::utility::NotSystemEndian>(r, templ->m_length);
|
||||||
|
}
|
||||||
|
group->getAudioGroup()->getPool().soundMacros()[newId] = node;
|
||||||
|
SoundMacroNode& ret = coll->makeChildAtIdx<SoundMacroNode>(insertIdx, newId, node);
|
||||||
|
endInsertRows();
|
||||||
|
return &ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectModel::ADSRNode* ProjectModel::newADSR(GroupNode* group, const QString& name, UIMessenger& messenger)
|
||||||
|
{
|
||||||
|
setIdDatabases(group);
|
||||||
|
auto nameKey = name.toUtf8();
|
||||||
|
if (amuse::TableId::CurNameDB->m_stringToId.find(nameKey.data()) != amuse::TableId::CurNameDB->m_stringToId.cend())
|
||||||
|
{
|
||||||
|
messenger.critical(tr("ADSR Conflict"), tr("The ADSR %1 is already defined").arg(name));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
ProjectModel::CollectionNode* coll = group->getCollectionOfType(INode::Type::ADSR);
|
||||||
|
QModelIndex parentIdx = index(coll);
|
||||||
|
int insertIdx = rowCount(parentIdx);
|
||||||
|
beginInsertRows(parentIdx, insertIdx, insertIdx);
|
||||||
|
amuse::TableId newId = amuse::TableId::CurNameDB->generateId(amuse::NameDB::Type::Table);
|
||||||
|
amuse::TableId::CurNameDB->registerPair(nameKey.data(), newId);
|
||||||
|
auto node = amuse::MakeObj<std::unique_ptr<amuse::ITable>>();
|
||||||
|
*node = std::make_unique<amuse::ADSR>();
|
||||||
|
group->getAudioGroup()->getPool().tables()[newId] = node;
|
||||||
|
ADSRNode& ret = coll->makeChildAtIdx<ADSRNode>(insertIdx, newId, node);
|
||||||
|
endInsertRows();
|
||||||
|
return &ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectModel::CurveNode* ProjectModel::newCurve(GroupNode* group, const QString& name, UIMessenger& messenger)
|
||||||
|
{
|
||||||
|
setIdDatabases(group);
|
||||||
|
auto nameKey = name.toUtf8();
|
||||||
|
if (amuse::TableId::CurNameDB->m_stringToId.find(nameKey.data()) != amuse::TableId::CurNameDB->m_stringToId.cend())
|
||||||
|
{
|
||||||
|
messenger.critical(tr("Curve Conflict"), tr("The Curve %1 is already defined").arg(name));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
ProjectModel::CollectionNode* coll = group->getCollectionOfType(INode::Type::Curve);
|
||||||
|
QModelIndex parentIdx = index(coll);
|
||||||
|
int insertIdx = rowCount(parentIdx);
|
||||||
|
beginInsertRows(parentIdx, insertIdx, insertIdx);
|
||||||
|
amuse::TableId newId = amuse::TableId::CurNameDB->generateId(amuse::NameDB::Type::Table);
|
||||||
|
amuse::TableId::CurNameDB->registerPair(nameKey.data(), newId);
|
||||||
|
auto node = amuse::MakeObj<std::unique_ptr<amuse::ITable>>();
|
||||||
|
*node = std::make_unique<amuse::Curve>();
|
||||||
|
group->getAudioGroup()->getPool().tables()[newId] = node;
|
||||||
|
CurveNode& ret = coll->makeChildAtIdx<CurveNode>(insertIdx, newId, node);
|
||||||
|
endInsertRows();
|
||||||
|
return &ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectModel::KeymapNode* ProjectModel::newKeymap(GroupNode* group, const QString& name, UIMessenger& messenger)
|
||||||
|
{
|
||||||
|
setIdDatabases(group);
|
||||||
|
auto nameKey = name.toUtf8();
|
||||||
|
if (amuse::KeymapId::CurNameDB->m_stringToId.find(nameKey.data()) != amuse::KeymapId::CurNameDB->m_stringToId.cend())
|
||||||
|
{
|
||||||
|
messenger.critical(tr("Keymap Conflict"), tr("The Keymap %1 is already defined").arg(name));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
ProjectModel::CollectionNode* coll = group->getCollectionOfType(INode::Type::Keymap);
|
||||||
|
QModelIndex parentIdx = index(coll);
|
||||||
|
int insertIdx = rowCount(parentIdx);
|
||||||
|
beginInsertRows(parentIdx, insertIdx, insertIdx);
|
||||||
|
amuse::KeymapId newId = amuse::KeymapId::CurNameDB->generateId(amuse::NameDB::Type::Keymap);
|
||||||
|
amuse::KeymapId::CurNameDB->registerPair(nameKey.data(), newId);
|
||||||
|
auto node = amuse::MakeObj<std::array<amuse::Keymap, 128>>();
|
||||||
|
group->getAudioGroup()->getPool().keymaps()[newId] = node;
|
||||||
|
KeymapNode& ret = coll->makeChildAtIdx<KeymapNode>(insertIdx, newId, node);
|
||||||
|
endInsertRows();
|
||||||
|
return &ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectModel::LayersNode* ProjectModel::newLayers(GroupNode* group, const QString& name, UIMessenger& messenger)
|
||||||
|
{
|
||||||
|
setIdDatabases(group);
|
||||||
|
auto nameKey = name.toUtf8();
|
||||||
|
if (amuse::LayersId::CurNameDB->m_stringToId.find(nameKey.data()) != amuse::LayersId::CurNameDB->m_stringToId.cend())
|
||||||
|
{
|
||||||
|
messenger.critical(tr("Layers Conflict"), tr("Layers %1 is already defined").arg(name));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
ProjectModel::CollectionNode* coll = group->getCollectionOfType(INode::Type::Layer);
|
||||||
|
QModelIndex parentIdx = index(coll);
|
||||||
|
int insertIdx = rowCount(parentIdx);
|
||||||
|
beginInsertRows(parentIdx, insertIdx, insertIdx);
|
||||||
|
amuse::LayersId newId = amuse::LayersId::CurNameDB->generateId(amuse::NameDB::Type::Layer);
|
||||||
|
amuse::LayersId::CurNameDB->registerPair(nameKey.data(), newId);
|
||||||
|
auto node = amuse::MakeObj<std::vector<amuse::LayerMapping>>();
|
||||||
|
group->getAudioGroup()->getPool().layers()[newId] = node;
|
||||||
|
LayersNode& ret = coll->makeChildAtIdx<LayersNode>(insertIdx, newId, node);
|
||||||
|
endInsertRows();
|
||||||
|
return &ret;
|
||||||
|
}
|
||||||
|
|
||||||
ProjectModel::GroupNode* ProjectModel::getGroupOfSfx(amuse::SFXId id) const
|
ProjectModel::GroupNode* ProjectModel::getGroupOfSfx(amuse::SFXId id) const
|
||||||
{
|
{
|
||||||
ProjectModel::GroupNode* ret = nullptr;
|
ProjectModel::GroupNode* ret = nullptr;
|
||||||
|
@ -797,3 +1020,10 @@ void ProjectModel::setMIDIPathOfSong(amuse::SongId id, const QString& path)
|
||||||
{
|
{
|
||||||
m_midiFiles[id] = path;
|
m_midiFiles[id] = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProjectModel::setIdDatabases(INode* context) const
|
||||||
|
{
|
||||||
|
m_projectDatabase.setIdDatabases();
|
||||||
|
if (ProjectModel::GroupNode* group = getGroupNode(context))
|
||||||
|
group->getAudioGroup()->setIdDatabases();
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include "Common.hpp"
|
#include "Common.hpp"
|
||||||
|
#include "NewSoundMacroDialog.hpp"
|
||||||
#include "amuse/AudioGroup.hpp"
|
#include "amuse/AudioGroup.hpp"
|
||||||
#include "amuse/AudioGroupData.hpp"
|
#include "amuse/AudioGroupData.hpp"
|
||||||
#include "amuse/AudioGroupProject.hpp"
|
#include "amuse/AudioGroupProject.hpp"
|
||||||
|
@ -52,13 +53,71 @@ public:
|
||||||
Both
|
Both
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct NameUndoRegistry
|
||||||
|
{
|
||||||
|
std::unordered_map<amuse::SongId, std::string> m_songIDs;
|
||||||
|
std::unordered_map<amuse::SFXId, std::string> m_sfxIDs;
|
||||||
|
void registerSongName(amuse::SongId id) const
|
||||||
|
{
|
||||||
|
auto search = m_songIDs.find(id);
|
||||||
|
if (search != m_songIDs.cend())
|
||||||
|
amuse::SongId::CurNameDB->registerPair(search->second, id);
|
||||||
|
}
|
||||||
|
void unregisterSongName(amuse::SongId id)
|
||||||
|
{
|
||||||
|
auto search = amuse::SongId::CurNameDB->m_idToString.find(id);
|
||||||
|
if (search != amuse::SongId::CurNameDB->m_idToString.cend())
|
||||||
|
m_songIDs[id] = search->second;
|
||||||
|
amuse::SongId::CurNameDB->remove(id);
|
||||||
|
}
|
||||||
|
void registerSFXName(amuse::SongId id) const
|
||||||
|
{
|
||||||
|
auto search = m_sfxIDs.find(id);
|
||||||
|
if (search != m_sfxIDs.cend())
|
||||||
|
amuse::SFXId::CurNameDB->registerPair(search->second, id);
|
||||||
|
}
|
||||||
|
void unregisterSFXName(amuse::SongId id)
|
||||||
|
{
|
||||||
|
auto search = amuse::SFXId::CurNameDB->m_idToString.find(id);
|
||||||
|
if (search != amuse::SFXId::CurNameDB->m_idToString.cend())
|
||||||
|
m_sfxIDs[id] = search->second;
|
||||||
|
amuse::SFXId::CurNameDB->remove(id);
|
||||||
|
}
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
m_songIDs.clear();
|
||||||
|
m_sfxIDs.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QDir m_dir;
|
QDir m_dir;
|
||||||
NullItemProxyModel m_nullProxy;
|
NullItemProxyModel m_nullProxy;
|
||||||
PageObjectProxyModel m_pageObjectProxy;
|
PageObjectProxyModel m_pageObjectProxy;
|
||||||
|
|
||||||
amuse::ProjectDatabase m_projectDatabase;
|
amuse::ProjectDatabase m_projectDatabase;
|
||||||
std::map<QString, amuse::AudioGroupDatabase> m_groups;
|
|
||||||
|
std::unordered_map<QString, amuse::AudioGroupDatabase> m_groups;
|
||||||
|
struct Iterator
|
||||||
|
{
|
||||||
|
using ItTp = std::unordered_map<QString, amuse::AudioGroupDatabase>::iterator;
|
||||||
|
ItTp m_it;
|
||||||
|
Iterator(ItTp it) : m_it(it) {}
|
||||||
|
ItTp::pointer operator->() { return m_it.operator->(); }
|
||||||
|
bool operator<(const Iterator& other) const
|
||||||
|
{
|
||||||
|
return m_it->first < other.m_it->first;
|
||||||
|
}
|
||||||
|
bool operator<(const QString& name) const
|
||||||
|
{
|
||||||
|
return m_it->first < name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
std::vector<Iterator> m_sorted;
|
||||||
|
void _buildSortedList();
|
||||||
|
QModelIndex _indexOfGroup(const QString& groupName) const;
|
||||||
|
int _hypotheticalIndexOfGroup(const QString& groupName) const;
|
||||||
|
|
||||||
std::unordered_map<amuse::SongId, QString> m_midiFiles;
|
std::unordered_map<amuse::SongId, QString> m_midiFiles;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -133,6 +192,13 @@ public:
|
||||||
m_nullChild->m_row = int(m_children.size());
|
m_nullChild->m_row = int(m_children.size());
|
||||||
return static_cast<T&>(*m_children.back());
|
return static_cast<T&>(*m_children.back());
|
||||||
}
|
}
|
||||||
|
template<class T, class... _Args>
|
||||||
|
T& makeChildAtIdx(int idx, _Args&&... args)
|
||||||
|
{
|
||||||
|
auto tok = amuse::MakeObj<T>(this, idx, std::forward<_Args>(args)...);
|
||||||
|
insertChild(idx, tok.get());
|
||||||
|
return static_cast<T&>(*tok);
|
||||||
|
}
|
||||||
|
|
||||||
bool depthTraverse(const std::function<bool(INode* node)>& func)
|
bool depthTraverse(const std::function<bool(INode* node)>& func)
|
||||||
{
|
{
|
||||||
|
@ -154,6 +220,9 @@ public:
|
||||||
virtual QString text() const = 0;
|
virtual QString text() const = 0;
|
||||||
virtual QIcon icon() const = 0;
|
virtual QIcon icon() const = 0;
|
||||||
virtual Qt::ItemFlags flags() const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; }
|
virtual Qt::ItemFlags flags() const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; }
|
||||||
|
|
||||||
|
virtual void registerNames(const NameUndoRegistry& registry) const {}
|
||||||
|
virtual void unregisterNames(NameUndoRegistry& registry) const {}
|
||||||
};
|
};
|
||||||
struct NullNode : INode
|
struct NullNode : INode
|
||||||
{
|
{
|
||||||
|
@ -176,8 +245,8 @@ public:
|
||||||
struct BasePoolObjectNode;
|
struct BasePoolObjectNode;
|
||||||
struct GroupNode : INode
|
struct GroupNode : INode
|
||||||
{
|
{
|
||||||
std::map<QString, amuse::AudioGroupDatabase>::iterator m_it;
|
std::unordered_map<QString, amuse::AudioGroupDatabase>::iterator m_it;
|
||||||
GroupNode(INode* parent, int row, std::map<QString, amuse::AudioGroupDatabase>::iterator it)
|
GroupNode(INode* parent, int row, std::unordered_map<QString, amuse::AudioGroupDatabase>::iterator it)
|
||||||
: INode(parent, row), m_it(it) {}
|
: INode(parent, row), m_it(it) {}
|
||||||
|
|
||||||
static QIcon Icon;
|
static QIcon Icon;
|
||||||
|
@ -201,6 +270,19 @@ public:
|
||||||
Type type() const { return Type::SongGroup; }
|
Type type() const { return Type::SongGroup; }
|
||||||
QString text() const { return m_name; }
|
QString text() const { return m_name; }
|
||||||
QIcon icon() const { return Icon; }
|
QIcon icon() const { return Icon; }
|
||||||
|
|
||||||
|
void registerNames(const NameUndoRegistry& registry) const
|
||||||
|
{
|
||||||
|
amuse::GroupId::CurNameDB->registerPair(text().toUtf8().data(), m_id);
|
||||||
|
for (auto& p : m_index->m_midiSetups)
|
||||||
|
registry.registerSongName(p.first);
|
||||||
|
}
|
||||||
|
void unregisterNames(NameUndoRegistry& registry) const
|
||||||
|
{
|
||||||
|
amuse::GroupId::CurNameDB->remove(m_id);
|
||||||
|
for (auto& p : m_index->m_midiSetups)
|
||||||
|
registry.unregisterSongName(p.first);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
struct SoundGroupNode : INode
|
struct SoundGroupNode : INode
|
||||||
{
|
{
|
||||||
|
@ -214,6 +296,19 @@ public:
|
||||||
Type type() const { return Type::SoundGroup; }
|
Type type() const { return Type::SoundGroup; }
|
||||||
QString text() const { return m_name; }
|
QString text() const { return m_name; }
|
||||||
QIcon icon() const { return Icon; }
|
QIcon icon() const { return Icon; }
|
||||||
|
|
||||||
|
void registerNames(const NameUndoRegistry& registry) const
|
||||||
|
{
|
||||||
|
amuse::GroupId::CurNameDB->registerPair(text().toUtf8().data(), m_id);
|
||||||
|
for (auto& p : m_index->m_sfxEntries)
|
||||||
|
registry.registerSFXName(p.first);
|
||||||
|
}
|
||||||
|
void unregisterNames(NameUndoRegistry& registry) const
|
||||||
|
{
|
||||||
|
amuse::GroupId::CurNameDB->remove(m_id);
|
||||||
|
for (auto& p : m_index->m_sfxEntries)
|
||||||
|
registry.unregisterSFXName(p.first);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
struct CollectionNode : INode
|
struct CollectionNode : INode
|
||||||
{
|
{
|
||||||
|
@ -252,6 +347,15 @@ public:
|
||||||
: BasePoolObjectNode(parent, row, id, ID::CurNameDB->resolveNameFromId(id).data()), m_obj(obj) {}
|
: BasePoolObjectNode(parent, row, id, ID::CurNameDB->resolveNameFromId(id).data()), m_obj(obj) {}
|
||||||
|
|
||||||
Type type() const { return TP; }
|
Type type() const { return TP; }
|
||||||
|
|
||||||
|
void registerNames(const NameUndoRegistry& registry) const
|
||||||
|
{
|
||||||
|
ID::CurNameDB->registerPair(text().toUtf8().data(), m_id);
|
||||||
|
}
|
||||||
|
void unregisterNames(NameUndoRegistry& registry) const
|
||||||
|
{
|
||||||
|
ID::CurNameDB->remove(m_id);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
using SoundMacroNode = PoolObjectNode<amuse::SoundMacroId, amuse::SoundMacro, INode::Type::SoundMacro>;
|
using SoundMacroNode = PoolObjectNode<amuse::SoundMacroId, amuse::SoundMacro, INode::Type::SoundMacro>;
|
||||||
using ADSRNode = PoolObjectNode<amuse::TableId, std::unique_ptr<amuse::ITable>, INode::Type::ADSR>;
|
using ADSRNode = PoolObjectNode<amuse::TableId, std::unique_ptr<amuse::ITable>, INode::Type::ADSR>;
|
||||||
|
@ -263,6 +367,7 @@ public:
|
||||||
amuse::ObjToken<RootNode> m_root;
|
amuse::ObjToken<RootNode> m_root;
|
||||||
|
|
||||||
bool m_needsReset = false;
|
bool m_needsReset = false;
|
||||||
|
void _buildGroupNode(GroupNode& gn);
|
||||||
void _resetModelData();
|
void _resetModelData();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -289,11 +394,21 @@ public:
|
||||||
INode* node(const QModelIndex& index) const;
|
INode* node(const QModelIndex& index) const;
|
||||||
GroupNode* getGroupNode(INode* node) const;
|
GroupNode* getGroupNode(INode* node) const;
|
||||||
bool canEdit(const QModelIndex& index) const;
|
bool canEdit(const QModelIndex& index) const;
|
||||||
void _undoDel(const QModelIndex& index, amuse::ObjToken<ProjectModel::INode> node);
|
void _undoDel(const QModelIndex& index, amuse::ObjToken<ProjectModel::INode> node, const NameUndoRegistry& nameReg);
|
||||||
amuse::ObjToken<ProjectModel::INode> _redoDel(const QModelIndex& index);
|
amuse::ObjToken<ProjectModel::INode> _redoDel(const QModelIndex& index, NameUndoRegistry& nameReg);
|
||||||
void del(const QModelIndex& index);
|
void del(const QModelIndex& index);
|
||||||
RootNode* rootNode() const { return m_root.get(); }
|
RootNode* rootNode() const { return m_root.get(); }
|
||||||
|
|
||||||
|
GroupNode* newSubproject(const QString& name, UIMessenger& messenger);
|
||||||
|
SoundGroupNode* newSoundGroup(GroupNode* group, const QString& name, UIMessenger& messenger);
|
||||||
|
SongGroupNode* newSongGroup(GroupNode* group, const QString& name, UIMessenger& messenger);
|
||||||
|
SoundMacroNode* newSoundMacro(GroupNode* group, const QString& name, UIMessenger& messenger,
|
||||||
|
const SoundMacroTemplateEntry* templ = nullptr);
|
||||||
|
ADSRNode* newADSR(GroupNode* group, const QString& name, UIMessenger& messenger);
|
||||||
|
CurveNode* newCurve(GroupNode* group, const QString& name, UIMessenger& messenger);
|
||||||
|
KeymapNode* newKeymap(GroupNode* group, const QString& name, UIMessenger& messenger);
|
||||||
|
LayersNode* newLayers(GroupNode* group, const QString& name, UIMessenger& messenger);
|
||||||
|
|
||||||
const QDir& dir() const { return m_dir; }
|
const QDir& dir() const { return m_dir; }
|
||||||
QString path() const { return m_dir.path(); }
|
QString path() const { return m_dir.path(); }
|
||||||
NullItemProxyModel* getNullProxy() { return &m_nullProxy; }
|
NullItemProxyModel* getNullProxy() { return &m_nullProxy; }
|
||||||
|
@ -303,6 +418,8 @@ public:
|
||||||
GroupNode* getGroupOfSong(amuse::SongId id) const;
|
GroupNode* getGroupOfSong(amuse::SongId id) const;
|
||||||
QString getMIDIPathOfSong(amuse::SongId id) const;
|
QString getMIDIPathOfSong(amuse::SongId id) const;
|
||||||
void setMIDIPathOfSong(amuse::SongId id, const QString& path);
|
void setMIDIPathOfSong(amuse::SongId id, const QString& path);
|
||||||
|
|
||||||
|
void setIdDatabases(INode* context) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "SongGroupEditor.hpp"
|
#include "SongGroupEditor.hpp"
|
||||||
#include "MainWindow.hpp"
|
#include "MainWindow.hpp"
|
||||||
|
#include "amuse/SongConverter.hpp"
|
||||||
|
|
||||||
PageObjectDelegate::PageObjectDelegate(QObject* parent)
|
PageObjectDelegate::PageObjectDelegate(QObject* parent)
|
||||||
: QStyledItemDelegate(parent) {}
|
: QStyledItemDelegate(parent) {}
|
||||||
|
@ -55,7 +56,10 @@ void PageObjectDelegate::objIndexChanged()
|
||||||
|
|
||||||
void MIDIFileFieldWidget::buttonPressed()
|
void MIDIFileFieldWidget::buttonPressed()
|
||||||
{
|
{
|
||||||
m_dialog.setDirectory(QFileInfo(g_MainWindow->projectModel()->dir().absoluteFilePath(m_le.text())).path());
|
if (m_le.text().isEmpty())
|
||||||
|
m_dialog.setDirectory(g_MainWindow->projectModel()->dir());
|
||||||
|
else
|
||||||
|
m_dialog.setDirectory(QFileInfo(g_MainWindow->projectModel()->dir().absoluteFilePath(m_le.text())).path());
|
||||||
m_dialog.open(this, SLOT(fileDialogOpened(const QString&)));
|
m_dialog.open(this, SLOT(fileDialogOpened(const QString&)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -721,15 +725,15 @@ PageTableView::PageTableView(QWidget* parent)
|
||||||
void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* table)
|
void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* table)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
m_listView.setModel(list);
|
m_listView->setModel(list);
|
||||||
auto hheader = m_listView.horizontalHeader();
|
auto hheader = m_listView->horizontalHeader();
|
||||||
hheader->setMinimumSectionSize(200);
|
hheader->setMinimumSectionSize(200);
|
||||||
hheader->resizeSection(0, 200);
|
hheader->resizeSection(0, 200);
|
||||||
hheader->setSectionResizeMode(1, QHeaderView::Stretch);
|
hheader->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
m_tableView.setModel(table);
|
m_tableView->setModel(table);
|
||||||
auto hheader = m_tableView.horizontalHeader();
|
auto hheader = m_tableView->horizontalHeader();
|
||||||
hheader->setSectionResizeMode(QHeaderView::Stretch);
|
hheader->setSectionResizeMode(QHeaderView::Stretch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -737,8 +741,8 @@ void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* tabl
|
||||||
void SetupTableView::deleteSelection()
|
void SetupTableView::deleteSelection()
|
||||||
{
|
{
|
||||||
QModelIndexList list;
|
QModelIndexList list;
|
||||||
while (!(list = m_listView.selectionModel()->selectedRows()).isEmpty())
|
while (!(list = m_listView->selectionModel()->selectedRows()).isEmpty())
|
||||||
m_listView.model()->removeRow(list.back().row());
|
m_listView->model()->removeRow(list.back().row());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetupTableView::showEvent(QShowEvent* event)
|
void SetupTableView::showEvent(QShowEvent* event)
|
||||||
|
@ -747,30 +751,30 @@ void SetupTableView::showEvent(QShowEvent* event)
|
||||||
}
|
}
|
||||||
|
|
||||||
SetupTableView::SetupTableView(QWidget* parent)
|
SetupTableView::SetupTableView(QWidget* parent)
|
||||||
: QSplitter(parent), m_listView(this), m_tableView(this)
|
: QSplitter(parent), m_listView(new QTableView), m_tableView(new QTableView)
|
||||||
{
|
{
|
||||||
setChildrenCollapsible(false);
|
setChildrenCollapsible(false);
|
||||||
setStretchFactor(0, 1);
|
setStretchFactor(0, 1);
|
||||||
setStretchFactor(1, 0);
|
setStretchFactor(1, 0);
|
||||||
|
|
||||||
addWidget(&m_listView);
|
addWidget(m_listView);
|
||||||
addWidget(&m_tableView);
|
addWidget(m_tableView);
|
||||||
|
|
||||||
m_listView.setSelectionBehavior(QAbstractItemView::SelectRows);
|
m_listView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
m_listView.setSelectionMode(QAbstractItemView::ExtendedSelection);
|
m_listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
m_listView.setGridStyle(Qt::NoPen);
|
m_listView->setGridStyle(Qt::NoPen);
|
||||||
m_listView.setItemDelegateForColumn(1, &m_midiDelegate);
|
m_listView->setItemDelegateForColumn(1, &m_midiDelegate);
|
||||||
|
|
||||||
m_tableView.setSelectionMode(QAbstractItemView::NoSelection);
|
m_tableView->setSelectionMode(QAbstractItemView::NoSelection);
|
||||||
m_tableView.setGridStyle(Qt::NoPen);
|
m_tableView->setGridStyle(Qt::NoPen);
|
||||||
|
|
||||||
m_127Delegate.setItemEditorFactory(&m_127Factory);
|
m_127Delegate.setItemEditorFactory(&m_127Factory);
|
||||||
|
|
||||||
m_tableView.setItemDelegateForColumn(0, &m_127Delegate);
|
m_tableView->setItemDelegateForColumn(0, &m_127Delegate);
|
||||||
m_tableView.setItemDelegateForColumn(1, &m_127Delegate);
|
m_tableView->setItemDelegateForColumn(1, &m_127Delegate);
|
||||||
m_tableView.setItemDelegateForColumn(2, &m_127Delegate);
|
m_tableView->setItemDelegateForColumn(2, &m_127Delegate);
|
||||||
m_tableView.setItemDelegateForColumn(3, &m_127Delegate);
|
m_tableView->setItemDelegateForColumn(3, &m_127Delegate);
|
||||||
m_tableView.setItemDelegateForColumn(4, &m_127Delegate);
|
m_tableView->setItemDelegateForColumn(4, &m_127Delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColoredTabBarStyle::drawControl(QStyle::ControlElement element, const QStyleOption *option,
|
void ColoredTabBarStyle::drawControl(QStyle::ControlElement element, const QStyleOption *option,
|
||||||
|
@ -797,10 +801,10 @@ void ColoredTabBarStyle::drawControl(QStyle::ControlElement element, const QStyl
|
||||||
}
|
}
|
||||||
|
|
||||||
ColoredTabBar::ColoredTabBar(QWidget* parent)
|
ColoredTabBar::ColoredTabBar(QWidget* parent)
|
||||||
: QTabBar(parent), m_style(style())
|
: QTabBar(parent), m_style(new ColoredTabBarStyle(style()))
|
||||||
{
|
{
|
||||||
setDrawBase(false);
|
setDrawBase(false);
|
||||||
setStyle(&m_style);
|
setStyle(m_style);
|
||||||
}
|
}
|
||||||
|
|
||||||
ColoredTabWidget::ColoredTabWidget(QWidget* parent)
|
ColoredTabWidget::ColoredTabWidget(QWidget* parent)
|
||||||
|
@ -809,6 +813,58 @@ ColoredTabWidget::ColoredTabWidget(QWidget* parent)
|
||||||
setTabBar(&m_tabBar);
|
setTabBar(&m_tabBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MIDIPlayerWidget::clicked()
|
||||||
|
{
|
||||||
|
if (!m_seq)
|
||||||
|
{
|
||||||
|
m_seq = g_MainWindow->startSong(m_groupId, m_songId, m_arrData.data());
|
||||||
|
if (m_seq)
|
||||||
|
{
|
||||||
|
m_playAction.setText(tr("Stop"));
|
||||||
|
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconStop.svg")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stopped();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MIDIPlayerWidget::stopped()
|
||||||
|
{
|
||||||
|
m_seq->stopSong();
|
||||||
|
m_seq.reset();
|
||||||
|
m_playAction.setText(tr("Play"));
|
||||||
|
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconSoundMacro.svg")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MIDIPlayerWidget::resizeEvent(QResizeEvent* event)
|
||||||
|
{
|
||||||
|
m_button.setGeometry(event->size().width() - event->size().height(), 0, event->size().height(), event->size().height());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MIDIPlayerWidget::mouseDoubleClickEvent(QMouseEvent* event)
|
||||||
|
{
|
||||||
|
qobject_cast<QTableView*>(parentWidget()->parentWidget())->setIndexWidget(m_index, nullptr);
|
||||||
|
event->ignore();
|
||||||
|
}
|
||||||
|
|
||||||
|
MIDIPlayerWidget::~MIDIPlayerWidget()
|
||||||
|
{
|
||||||
|
if (m_seq)
|
||||||
|
m_seq->stopSong();
|
||||||
|
}
|
||||||
|
|
||||||
|
MIDIPlayerWidget::MIDIPlayerWidget(QModelIndex index, amuse::GroupId gid, amuse::SongId id,
|
||||||
|
std::vector<uint8_t>&& arrData, QWidget* parent)
|
||||||
|
: QWidget(parent), m_button(this), m_playAction(tr("Play")), m_index(index),
|
||||||
|
m_groupId(gid), m_songId(id), m_arrData(std::move(arrData))
|
||||||
|
{
|
||||||
|
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconSoundMacro.svg")));
|
||||||
|
m_button.setDefaultAction(&m_playAction);
|
||||||
|
connect(&m_playAction, SIGNAL(triggered()), this, SLOT(clicked()));
|
||||||
|
}
|
||||||
|
|
||||||
bool SongGroupEditor::loadData(ProjectModel::SongGroupNode* node)
|
bool SongGroupEditor::loadData(ProjectModel::SongGroupNode* node)
|
||||||
{
|
{
|
||||||
m_normPages.loadData(node);
|
m_normPages.loadData(node);
|
||||||
|
@ -834,8 +890,7 @@ ProjectModel::INode* SongGroupEditor::currentNode() const
|
||||||
void SongGroupEditor::resizeEvent(QResizeEvent* ev)
|
void SongGroupEditor::resizeEvent(QResizeEvent* ev)
|
||||||
{
|
{
|
||||||
m_tabs.setGeometry(QRect({}, ev->size()));
|
m_tabs.setGeometry(QRect({}, ev->size()));
|
||||||
m_addButton.move(0, ev->size().height() - 32);
|
m_addRemoveButtons.move(0, ev->size().height() - 32);
|
||||||
m_removeButton.move(32, ev->size().height() - 32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SongGroupEditor::doAdd()
|
void SongGroupEditor::doAdd()
|
||||||
|
@ -848,34 +903,37 @@ void SongGroupEditor::doAdd()
|
||||||
else
|
else
|
||||||
table->model()->insertRow(idx.row());
|
table->model()->insertRow(idx.row());
|
||||||
if (PageTableView* ctable = qobject_cast<PageTableView*>(table))
|
if (PageTableView* ctable = qobject_cast<PageTableView*>(table))
|
||||||
m_addAction.setDisabled(ctable->model()->rowCount() >= 128);
|
m_addRemoveButtons.addAction()->setDisabled(ctable->model()->rowCount() >= 128);
|
||||||
}
|
}
|
||||||
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
|
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
|
||||||
{
|
{
|
||||||
QModelIndex idx = table->m_listView.selectionModel()->currentIndex();
|
QModelIndex idx = table->m_listView->selectionModel()->currentIndex();
|
||||||
if (!idx.isValid())
|
if (!idx.isValid())
|
||||||
table->m_listView.model()->insertRow(table->m_listView.model()->rowCount() - 1);
|
table->m_listView->model()->insertRow(table->m_listView->model()->rowCount() - 1);
|
||||||
else
|
else
|
||||||
table->m_listView.model()->insertRow(idx.row());
|
table->m_listView->model()->insertRow(idx.row());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SongGroupEditor::doSelectionChanged(const QItemSelection& selected)
|
void SongGroupEditor::doSelectionChanged()
|
||||||
{
|
{
|
||||||
m_removeAction.setDisabled(selected.isEmpty());
|
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
|
||||||
|
m_addRemoveButtons.removeAction()->setDisabled(table->selectionModel()->selectedRows().isEmpty());
|
||||||
|
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
|
||||||
|
m_addRemoveButtons.removeAction()->setDisabled(table->m_listView->selectionModel()->selectedRows().isEmpty());
|
||||||
g_MainWindow->updateFocus();
|
g_MainWindow->updateFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SongGroupEditor::doSetupSelectionChanged(const QItemSelection& selected)
|
void SongGroupEditor::doSetupSelectionChanged()
|
||||||
{
|
{
|
||||||
doSelectionChanged(selected);
|
doSelectionChanged();
|
||||||
if (selected.indexes().isEmpty() || m_setupList.m_sorted.empty())
|
if (m_setupTable->m_listView->selectionModel()->selectedRows().isEmpty() || m_setupList.m_sorted.empty())
|
||||||
{
|
{
|
||||||
m_setup.unloadData();
|
m_setup.unloadData();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto entry = m_setupList.m_sorted[selected.indexes().last().row()];
|
auto entry = m_setupList.m_sorted[m_setupTable->m_listView->selectionModel()->selectedRows().last().row()];
|
||||||
m_setup.loadData(&*entry.m_it);
|
m_setup.loadData(&*entry.m_it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -884,13 +942,13 @@ void SongGroupEditor::currentTabChanged(int idx)
|
||||||
{
|
{
|
||||||
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
|
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
|
||||||
{
|
{
|
||||||
m_addAction.setDisabled(table->model()->rowCount() >= 128);
|
m_addRemoveButtons.addAction()->setDisabled(table->model()->rowCount() >= 128);
|
||||||
doSelectionChanged(table->selectionModel()->selection());
|
doSelectionChanged();
|
||||||
}
|
}
|
||||||
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
|
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
|
||||||
{
|
{
|
||||||
m_addAction.setDisabled(false);
|
m_addRemoveButtons.addAction()->setDisabled(false);
|
||||||
doSelectionChanged(table->m_listView.selectionModel()->selection());
|
doSelectionChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -912,12 +970,74 @@ void SongGroupEditor::modelAboutToBeReset()
|
||||||
m_setup.unloadData();
|
m_setup.unloadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<uint8_t> LoadSongFile(QString path)
|
||||||
|
{
|
||||||
|
QFileInfo fi(path);
|
||||||
|
if (!fi.isFile())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
{
|
||||||
|
QFile f(path);
|
||||||
|
if (!f.open(QFile::ReadOnly))
|
||||||
|
return {};
|
||||||
|
auto d = f.readAll();
|
||||||
|
data.resize(d.size());
|
||||||
|
memcpy(&data[0], d.data(), d.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!memcmp(data.data(), "MThd", 4))
|
||||||
|
{
|
||||||
|
data = amuse::SongConverter::MIDIToSong(data, 1, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool isBig;
|
||||||
|
if (amuse::SongState::DetectVersion(data.data(), isBig) < 0)
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SongGroupEditor::setupDataChanged()
|
||||||
|
{
|
||||||
|
int idx = 0;
|
||||||
|
for (const auto& p : m_setupList.m_sorted)
|
||||||
|
{
|
||||||
|
QString path = g_MainWindow->projectModel()->getMIDIPathOfSong(p.m_it->first);
|
||||||
|
QModelIndex index = m_setupList.index(idx, 1);
|
||||||
|
if (path.isEmpty())
|
||||||
|
{
|
||||||
|
m_setupTable->m_listView->setIndexWidget(index, nullptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MIDIPlayerWidget* w = qobject_cast<MIDIPlayerWidget*>(m_setupTable->m_listView->indexWidget(index));
|
||||||
|
if (!w || w->songId() != p.m_it->first)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> arrData = LoadSongFile(g_MainWindow->projectModel()->dir().absoluteFilePath(path));
|
||||||
|
if (!arrData.empty())
|
||||||
|
{
|
||||||
|
MIDIPlayerWidget* newW = new MIDIPlayerWidget(index, m_setupList.m_node->m_id, p.m_it->first, std::move(arrData));
|
||||||
|
m_setupTable->m_listView->setIndexWidget(index, newW);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_setupTable->m_listView->setIndexWidget(index, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool SongGroupEditor::isItemEditEnabled() const
|
bool SongGroupEditor::isItemEditEnabled() const
|
||||||
{
|
{
|
||||||
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
|
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
|
||||||
return table->hasFocus() && !table->selectionModel()->selectedRows().isEmpty();
|
return table->hasFocus() && !table->selectionModel()->selectedRows().isEmpty();
|
||||||
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
|
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
|
||||||
return table->m_listView.hasFocus() && !table->m_listView.selectionModel()->selectedRows().isEmpty();
|
return table->m_listView->hasFocus() && !table->m_listView->selectionModel()->selectedRows().isEmpty();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -946,42 +1066,44 @@ void SongGroupEditor::itemDeleteAction()
|
||||||
|
|
||||||
SongGroupEditor::SongGroupEditor(QWidget* parent)
|
SongGroupEditor::SongGroupEditor(QWidget* parent)
|
||||||
: EditorWidget(parent), m_normPages(false, this), m_drumPages(true, this), m_setup(this),
|
: EditorWidget(parent), m_normPages(false, this), m_drumPages(true, this), m_setup(this),
|
||||||
m_normTable(this), m_drumTable(this), m_setupTable(this), m_tabs(this),
|
m_normTable(new PageTableView), m_drumTable(new PageTableView),
|
||||||
m_addAction(tr("Add Row")), m_addButton(this), m_removeAction(tr("Remove Row")), m_removeButton(this)
|
m_setupTable(new SetupTableView), m_tabs(this), m_addRemoveButtons(this)
|
||||||
{
|
{
|
||||||
m_tabs.addTab(&m_normTable, tr("Normal Pages"));
|
m_tabs.addTab(m_normTable, tr("Normal Pages"));
|
||||||
m_tabs.addTab(&m_drumTable, tr("Drum Pages"));
|
m_tabs.addTab(m_drumTable, tr("Drum Pages"));
|
||||||
m_tabs.addTab(&m_setupTable, tr("MIDI Setups"));
|
m_tabs.addTab(m_setupTable, tr("MIDI Setups"));
|
||||||
|
|
||||||
connect(&m_tabs, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged(int)));
|
connect(&m_tabs, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged(int)));
|
||||||
|
|
||||||
|
m_normTable->setModel(&m_normPages);
|
||||||
|
m_drumTable->setModel(&m_drumPages);
|
||||||
|
m_setupTable->setModel(&m_setupList, &m_setup);
|
||||||
|
connect(m_normTable->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
|
||||||
|
this, SLOT(doSelectionChanged()));
|
||||||
|
connect(m_drumTable->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
|
||||||
|
this, SLOT(doSelectionChanged()));
|
||||||
|
connect(m_setupTable->m_listView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
|
||||||
|
this, SLOT(doSetupSelectionChanged()));
|
||||||
|
|
||||||
connect(&m_setupList, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
|
connect(&m_setupList, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
|
||||||
this, SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
|
this, SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
|
||||||
connect(&m_setupList, SIGNAL(modelAboutToBeReset()),
|
connect(&m_setupList, SIGNAL(modelAboutToBeReset()),
|
||||||
this, SLOT(modelAboutToBeReset()));
|
this, SLOT(modelAboutToBeReset()));
|
||||||
|
connect(&m_setupList, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
|
||||||
|
this, SLOT(setupDataChanged()));
|
||||||
|
connect(&m_setupList, SIGNAL(rowsRemoved(const QModelIndex&, int, int)),
|
||||||
|
this, SLOT(setupDataChanged()));
|
||||||
|
connect(&m_setupList, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
|
||||||
|
this, SLOT(setupDataChanged()));
|
||||||
|
connect(&m_setupList, SIGNAL(layoutChanged(const QList<QPersistentModelIndex>&, QAbstractItemModel::LayoutChangeHint)),
|
||||||
|
this, SLOT(setupDataChanged()));
|
||||||
|
connect(&m_setupList, SIGNAL(modelReset()),
|
||||||
|
this, SLOT(setupDataChanged()));
|
||||||
|
|
||||||
m_normTable.setModel(&m_normPages);
|
m_addRemoveButtons.addAction()->setToolTip(tr("Add new page entry"));
|
||||||
m_drumTable.setModel(&m_drumPages);
|
connect(m_addRemoveButtons.addAction(), SIGNAL(triggered(bool)), this, SLOT(doAdd()));
|
||||||
m_setupTable.setModel(&m_setupList, &m_setup);
|
m_addRemoveButtons.removeAction()->setToolTip(tr("Remove selected page entries"));
|
||||||
connect(m_normTable.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
|
connect(m_addRemoveButtons.removeAction(), SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));
|
||||||
this, SLOT(doSelectionChanged(const QItemSelection&)));
|
|
||||||
connect(m_drumTable.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
|
|
||||||
this, SLOT(doSelectionChanged(const QItemSelection&)));
|
|
||||||
connect(m_setupTable.m_listView.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
|
|
||||||
this, SLOT(doSetupSelectionChanged(const QItemSelection&)));
|
|
||||||
|
|
||||||
m_addAction.setIcon(QIcon(QStringLiteral(":/icons/IconAdd.svg")));
|
|
||||||
m_addButton.setDefaultAction(&m_addAction);
|
|
||||||
m_addAction.setToolTip(tr("Add new page entry"));
|
|
||||||
m_addButton.setFixedSize(32, 32);
|
|
||||||
connect(&m_addAction, SIGNAL(triggered(bool)), this, SLOT(doAdd()));
|
|
||||||
|
|
||||||
m_removeAction.setIcon(QIcon(QStringLiteral(":/icons/IconRemove.svg")));
|
|
||||||
m_removeButton.setDefaultAction(&m_removeAction);
|
|
||||||
m_removeAction.setToolTip(tr("Remove selected page entries"));
|
|
||||||
m_removeButton.setFixedSize(32, 32);
|
|
||||||
connect(&m_removeAction, SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));
|
|
||||||
m_removeAction.setEnabled(false);
|
|
||||||
|
|
||||||
m_tabs.setCurrentIndex(0);
|
m_tabs.setCurrentIndex(0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QProxyStyle>
|
#include <QProxyStyle>
|
||||||
|
#include "amuse/Sequencer.hpp"
|
||||||
|
|
||||||
class PageObjectDelegate : public QStyledItemDelegate
|
class PageObjectDelegate : public QStyledItemDelegate
|
||||||
{
|
{
|
||||||
|
@ -176,8 +177,8 @@ class SetupTableView : public QSplitter
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
friend class SongGroupEditor;
|
friend class SongGroupEditor;
|
||||||
QTableView m_listView;
|
QTableView* m_listView;
|
||||||
QTableView m_tableView;
|
QTableView* m_tableView;
|
||||||
MIDIFileDelegate m_midiDelegate;
|
MIDIFileDelegate m_midiDelegate;
|
||||||
RangedValueFactory<0, 127> m_127Factory;
|
RangedValueFactory<0, 127> m_127Factory;
|
||||||
QStyledItemDelegate m_127Delegate;
|
QStyledItemDelegate m_127Delegate;
|
||||||
|
@ -199,7 +200,7 @@ public:
|
||||||
class ColoredTabBar : public QTabBar
|
class ColoredTabBar : public QTabBar
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
ColoredTabBarStyle m_style;
|
ColoredTabBarStyle* m_style;
|
||||||
public:
|
public:
|
||||||
explicit ColoredTabBar(QWidget* parent = Q_NULLPTR);
|
explicit ColoredTabBar(QWidget* parent = Q_NULLPTR);
|
||||||
};
|
};
|
||||||
|
@ -212,6 +213,29 @@ public:
|
||||||
explicit ColoredTabWidget(QWidget* parent = Q_NULLPTR);
|
explicit ColoredTabWidget(QWidget* parent = Q_NULLPTR);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MIDIPlayerWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QAction m_playAction;
|
||||||
|
QToolButton m_button;
|
||||||
|
QModelIndex m_index;
|
||||||
|
amuse::GroupId m_groupId;
|
||||||
|
amuse::SongId m_songId;
|
||||||
|
std::vector<uint8_t> m_arrData;
|
||||||
|
amuse::ObjToken<amuse::Sequencer> m_seq;
|
||||||
|
public:
|
||||||
|
explicit MIDIPlayerWidget(QModelIndex index, amuse::GroupId gid, amuse::SongId id,
|
||||||
|
std::vector<uint8_t>&& arrData, QWidget* parent = Q_NULLPTR);
|
||||||
|
~MIDIPlayerWidget();
|
||||||
|
amuse::SongId songId() const { return m_songId; }
|
||||||
|
amuse::Sequencer* sequencer() const { return m_seq.get(); }
|
||||||
|
void stopped();
|
||||||
|
void resizeEvent(QResizeEvent* event);
|
||||||
|
void mouseDoubleClickEvent(QMouseEvent* event);
|
||||||
|
public slots:
|
||||||
|
void clicked();
|
||||||
|
};
|
||||||
|
|
||||||
class SongGroupEditor : public EditorWidget
|
class SongGroupEditor : public EditorWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -219,34 +243,33 @@ class SongGroupEditor : public EditorWidget
|
||||||
PageModel m_drumPages;
|
PageModel m_drumPages;
|
||||||
SetupListModel m_setupList;
|
SetupListModel m_setupList;
|
||||||
SetupModel m_setup;
|
SetupModel m_setup;
|
||||||
PageTableView m_normTable;
|
PageTableView* m_normTable;
|
||||||
PageTableView m_drumTable;
|
PageTableView* m_drumTable;
|
||||||
SetupTableView m_setupTable;
|
SetupTableView* m_setupTable;
|
||||||
ColoredTabWidget m_tabs;
|
ColoredTabWidget m_tabs;
|
||||||
QAction m_addAction;
|
AddRemoveButtons m_addRemoveButtons;
|
||||||
QToolButton m_addButton;
|
|
||||||
QAction m_removeAction;
|
|
||||||
QToolButton m_removeButton;
|
|
||||||
public:
|
public:
|
||||||
explicit SongGroupEditor(QWidget* parent = Q_NULLPTR);
|
explicit SongGroupEditor(QWidget* parent = Q_NULLPTR);
|
||||||
bool loadData(ProjectModel::SongGroupNode* node);
|
bool loadData(ProjectModel::SongGroupNode* node);
|
||||||
void unloadData();
|
void unloadData();
|
||||||
ProjectModel::INode* currentNode() const;
|
ProjectModel::INode* currentNode() const;
|
||||||
|
void setEditorEnabled(bool en) {}
|
||||||
void resizeEvent(QResizeEvent* ev);
|
void resizeEvent(QResizeEvent* ev);
|
||||||
|
QTableView* getSetupListView() const { return m_setupTable->m_listView; }
|
||||||
|
bool isItemEditEnabled() const;
|
||||||
public slots:
|
public slots:
|
||||||
void doAdd();
|
void doAdd();
|
||||||
void doSelectionChanged(const QItemSelection& selected);
|
void doSelectionChanged();
|
||||||
void doSetupSelectionChanged(const QItemSelection& selected);
|
void doSetupSelectionChanged();
|
||||||
void currentTabChanged(int idx);
|
void currentTabChanged(int idx);
|
||||||
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
|
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
|
||||||
void modelAboutToBeReset();
|
void modelAboutToBeReset();
|
||||||
|
void setupDataChanged();
|
||||||
|
|
||||||
bool isItemEditEnabled() const;
|
|
||||||
void itemCutAction();
|
void itemCutAction();
|
||||||
void itemCopyAction();
|
void itemCopyAction();
|
||||||
void itemPasteAction();
|
void itemPasteAction();
|
||||||
void itemDeleteAction();
|
void itemDeleteAction();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif //AMUSE_SONG_GROUP_EDITOR_HPP
|
#endif //AMUSE_SONG_GROUP_EDITOR_HPP
|
||||||
|
|
|
@ -1,12 +1,494 @@
|
||||||
#include "SoundGroupEditor.hpp"
|
#include "SoundGroupEditor.hpp"
|
||||||
|
#include "MainWindow.hpp"
|
||||||
|
|
||||||
bool SoundGroupEditor::loadData(ProjectModel::SoundGroupNode* node)
|
SFXObjectDelegate::SFXObjectDelegate(QObject* parent)
|
||||||
|
: QStyledItemDelegate(parent) {}
|
||||||
|
|
||||||
|
QWidget* SFXObjectDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
|
||||||
{
|
{
|
||||||
|
const SFXModel* model = static_cast<const SFXModel*>(index.model());
|
||||||
|
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get());
|
||||||
|
EditorFieldPageObjectNode* cb = new EditorFieldPageObjectNode(group, parent);
|
||||||
|
connect(cb, SIGNAL(currentIndexChanged(int)), this, SLOT(objIndexChanged()));
|
||||||
|
return cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SFXObjectDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
|
||||||
|
{
|
||||||
|
const SFXModel* model = static_cast<const SFXModel*>(index.model());
|
||||||
|
auto entry = model->m_sorted[index.row()];
|
||||||
|
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get());
|
||||||
|
ProjectModel::BasePoolObjectNode* node = group->pageObjectNodeOfId(entry->second.objId.id);
|
||||||
|
int idx = 0;
|
||||||
|
if (node)
|
||||||
|
idx = g_MainWindow->projectModel()->getPageObjectProxy()->mapFromSource(g_MainWindow->projectModel()->index(node)).row();
|
||||||
|
static_cast<EditorFieldPageObjectNode*>(editor)->setCurrentIndex(idx);
|
||||||
|
if (static_cast<EditorFieldPageObjectNode*>(editor)->shouldPopupOpen())
|
||||||
|
static_cast<EditorFieldPageObjectNode*>(editor)->showPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SFXObjectDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const
|
||||||
|
{
|
||||||
|
const SFXModel* model = static_cast<const SFXModel*>(m);
|
||||||
|
auto entry = model->m_sorted[index.row()];
|
||||||
|
int idx = static_cast<EditorFieldPageObjectNode*>(editor)->currentIndex();
|
||||||
|
if (idx == 0)
|
||||||
|
{
|
||||||
|
entry->second.objId.id = amuse::ObjectId();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ProjectModel::BasePoolObjectNode* node = static_cast<ProjectModel::BasePoolObjectNode*>(
|
||||||
|
g_MainWindow->projectModel()->node(
|
||||||
|
g_MainWindow->projectModel()->getPageObjectProxy()->mapToSource(
|
||||||
|
g_MainWindow->projectModel()->getPageObjectProxy()->index(idx, 0,
|
||||||
|
static_cast<EditorFieldPageObjectNode*>(editor)->rootModelIndex()))));
|
||||||
|
entry->second.objId.id = node->id();
|
||||||
|
}
|
||||||
|
emit m->dataChanged(index, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SFXObjectDelegate::objIndexChanged()
|
||||||
|
{
|
||||||
|
emit commitData(static_cast<QWidget*>(sender()));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<amuse::SFXId, amuse::SFXGroupIndex::SFXEntry>& SFXModel::_getMap() const
|
||||||
|
{
|
||||||
|
return m_node->m_index->m_sfxEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SFXModel::_buildSortedList()
|
||||||
|
{
|
||||||
|
m_sorted.clear();
|
||||||
|
if (!m_node)
|
||||||
|
return;
|
||||||
|
auto& map = _getMap();
|
||||||
|
m_sorted.reserve(map.size());
|
||||||
|
for (auto it = map.begin() ; it != map.end() ; ++it)
|
||||||
|
m_sorted.emplace_back(it);
|
||||||
|
std::sort(m_sorted.begin(), m_sorted.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex SFXModel::_indexOfSFX(amuse::SFXId sfx) const
|
||||||
|
{
|
||||||
|
auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), sfx);
|
||||||
|
if (search == m_sorted.cend() || search->m_it->first != sfx)
|
||||||
|
return QModelIndex();
|
||||||
|
else
|
||||||
|
return createIndex(search - m_sorted.begin(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int SFXModel::_hypotheticalIndexOfSFX(const std::string& sfxName) const
|
||||||
|
{
|
||||||
|
auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), sfxName);
|
||||||
|
return search - m_sorted.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SFXModel::loadData(ProjectModel::SoundGroupNode* node)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
m_node = node;
|
||||||
|
_buildSortedList();
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SFXModel::unloadData()
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
m_node.reset();
|
||||||
|
m_sorted.clear();
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
int SFXModel::rowCount(const QModelIndex& parent) const
|
||||||
|
{
|
||||||
|
if (parent.isValid())
|
||||||
|
return 0;
|
||||||
|
if (!m_node)
|
||||||
|
return 0;
|
||||||
|
return int(m_sorted.size()) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SFXModel::columnCount(const QModelIndex& parent) const
|
||||||
|
{
|
||||||
|
if (parent.isValid())
|
||||||
|
return 0;
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SFXModel::data(const QModelIndex& index, int role) const
|
||||||
|
{
|
||||||
|
if (!m_node)
|
||||||
|
return QVariant();
|
||||||
|
if (index.row() == m_sorted.size())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
auto entry = m_sorted[index.row()];
|
||||||
|
|
||||||
|
if (role == Qt::DisplayRole || role == Qt::EditRole)
|
||||||
|
{
|
||||||
|
switch (index.column())
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
{
|
||||||
|
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
|
||||||
|
return amuse::SFXId::CurNameDB->resolveNameFromId(entry->first.id).data();
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(m_node.get());
|
||||||
|
if (ProjectModel::BasePoolObjectNode* node = group->pageObjectNodeOfId(entry->second.objId.id))
|
||||||
|
return node->text();
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
return entry->second.priority;
|
||||||
|
case 3:
|
||||||
|
return entry->second.maxVoices;
|
||||||
|
case 4:
|
||||||
|
return entry->second.defVel;
|
||||||
|
case 5:
|
||||||
|
return entry->second.panning;
|
||||||
|
case 6:
|
||||||
|
return entry->second.defKey;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SFXModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||||
|
{
|
||||||
|
if (!m_node || role != Qt::EditRole)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto& map = _getMap();
|
||||||
|
auto entry = m_sorted[index.row()];
|
||||||
|
|
||||||
|
switch (index.column())
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
{
|
||||||
|
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
|
||||||
|
auto utf8key = value.toString().toUtf8();
|
||||||
|
std::unordered_map<std::string, amuse::ObjectId>::iterator idIt;
|
||||||
|
if ((idIt = amuse::SFXId::CurNameDB->m_stringToId.find(utf8key.data())) != amuse::SFXId::CurNameDB->m_stringToId.cend())
|
||||||
|
{
|
||||||
|
if (idIt->second == entry->first)
|
||||||
|
return false;
|
||||||
|
QMessageBox::critical(g_MainWindow, tr("SFX Conflict"),
|
||||||
|
tr("SFX %1 is already defined in project").arg(value.toString()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
emit layoutAboutToBeChanged();
|
||||||
|
amuse::SFXId::CurNameDB->rename(entry.m_it->first, utf8key.data());
|
||||||
|
_buildSortedList();
|
||||||
|
QModelIndex newIndex = _indexOfSFX(entry.m_it->first);
|
||||||
|
changePersistentIndex(index, newIndex);
|
||||||
|
emit layoutChanged();
|
||||||
|
emit dataChanged(newIndex, newIndex, {Qt::DisplayRole, Qt::EditRole});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
entry->second.priority = value.toInt();
|
||||||
|
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
|
||||||
|
return true;
|
||||||
|
case 3:
|
||||||
|
entry->second.maxVoices = value.toInt();
|
||||||
|
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
|
||||||
|
return true;
|
||||||
|
case 4:
|
||||||
|
entry->second.defVel = value.toInt();
|
||||||
|
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
|
||||||
|
return true;
|
||||||
|
case 5:
|
||||||
|
entry->second.panning = value.toInt();
|
||||||
|
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
|
||||||
|
return true;
|
||||||
|
case 6:
|
||||||
|
entry->second.defKey = value.toInt();
|
||||||
|
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SoundGroupEditor::SoundGroupEditor(QWidget* parent)
|
QVariant SFXModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
: EditorWidget(parent)
|
{
|
||||||
|
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
|
||||||
|
{
|
||||||
|
switch (section)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return tr("SFX");
|
||||||
|
case 1:
|
||||||
|
return tr("Object");
|
||||||
|
case 2:
|
||||||
|
return tr("Priority");
|
||||||
|
case 3:
|
||||||
|
return tr("Max Voices");
|
||||||
|
case 4:
|
||||||
|
return tr("Velocity");
|
||||||
|
case 5:
|
||||||
|
return tr("Panning");
|
||||||
|
case 6:
|
||||||
|
return tr("Key");
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::ItemFlags SFXModel::flags(const QModelIndex& index) const
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return Qt::NoItemFlags;
|
||||||
|
if (index.row() == m_sorted.size())
|
||||||
|
return Qt::NoItemFlags;
|
||||||
|
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SFXModel::insertRows(int row, int count, const QModelIndex& parent)
|
||||||
|
{
|
||||||
|
if (!m_node)
|
||||||
|
return false;
|
||||||
|
auto& map = _getMap();
|
||||||
|
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
amuse::ObjectId sfxId = amuse::SFXId::CurNameDB->generateId(amuse::NameDB::Type::SFX);
|
||||||
|
std::string sfxName = amuse::SFXId::CurNameDB->generateName(sfxId, amuse::NameDB::Type::SFX);
|
||||||
|
int insertIdx = _hypotheticalIndexOfSFX(sfxName);
|
||||||
|
beginInsertRows(parent, insertIdx, insertIdx);
|
||||||
|
amuse::SFXId::CurNameDB->registerPair(sfxName, sfxId);
|
||||||
|
map.emplace(std::make_pair(sfxId, amuse::SFXGroupIndex::SFXEntry{}));
|
||||||
|
_buildSortedList();
|
||||||
|
endInsertRows();
|
||||||
|
++row;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SFXModel::removeRows(int row, int count, const QModelIndex& parent)
|
||||||
|
{
|
||||||
|
if (!m_node)
|
||||||
|
return false;
|
||||||
|
auto& map = _getMap();
|
||||||
|
beginRemoveRows(parent, row, row + count - 1);
|
||||||
|
std::vector<amuse::SFXId> removeSFXs;
|
||||||
|
removeSFXs.reserve(count);
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
removeSFXs.push_back(m_sorted[row+i].m_it->first);
|
||||||
|
for (amuse::SFXId sfx : removeSFXs)
|
||||||
|
map.erase(sfx);
|
||||||
|
_buildSortedList();
|
||||||
|
endRemoveRows();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SFXModel::SFXModel(QObject* parent)
|
||||||
|
: QAbstractTableModel(parent)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void SFXTableView::deleteSelection()
|
||||||
|
{
|
||||||
|
QModelIndexList list;
|
||||||
|
while (!(list = selectionModel()->selectedRows()).isEmpty())
|
||||||
|
model()->removeRow(list.back().row());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SFXTableView::setModel(QAbstractItemModel* model)
|
||||||
|
{
|
||||||
|
QTableView::setModel(model);
|
||||||
|
horizontalHeader()->setMinimumSectionSize(75);
|
||||||
|
horizontalHeader()->resizeSection(0, 200);
|
||||||
|
horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||||
|
horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed);
|
||||||
|
horizontalHeader()->resizeSection(2, 75);
|
||||||
|
horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
|
||||||
|
horizontalHeader()->resizeSection(3, 100);
|
||||||
|
horizontalHeader()->setSectionResizeMode(4, QHeaderView::Fixed);
|
||||||
|
horizontalHeader()->resizeSection(4, 75);
|
||||||
|
horizontalHeader()->setSectionResizeMode(5, QHeaderView::Fixed);
|
||||||
|
horizontalHeader()->resizeSection(5, 75);
|
||||||
|
horizontalHeader()->setSectionResizeMode(6, QHeaderView::Fixed);
|
||||||
|
horizontalHeader()->resizeSection(6, 75);
|
||||||
|
}
|
||||||
|
|
||||||
|
SFXTableView::SFXTableView(QWidget* parent)
|
||||||
|
: QTableView(parent)
|
||||||
|
{
|
||||||
|
setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
|
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
setGridStyle(Qt::NoPen);
|
||||||
|
|
||||||
|
m_127Delegate.setItemEditorFactory(&m_127Factory);
|
||||||
|
m_255Delegate.setItemEditorFactory(&m_255Factory);
|
||||||
|
|
||||||
|
setItemDelegateForColumn(1, &m_sfxDelegate);
|
||||||
|
setItemDelegateForColumn(2, &m_255Delegate);
|
||||||
|
setItemDelegateForColumn(3, &m_255Delegate);
|
||||||
|
setItemDelegateForColumn(4, &m_127Delegate);
|
||||||
|
setItemDelegateForColumn(5, &m_127Delegate);
|
||||||
|
setItemDelegateForColumn(6, &m_127Delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SFXPlayerWidget::clicked()
|
||||||
|
{
|
||||||
|
if (!m_vox)
|
||||||
|
{
|
||||||
|
m_vox = g_MainWindow->startSFX(m_groupId, m_sfxId);
|
||||||
|
if (m_vox)
|
||||||
|
{
|
||||||
|
m_playAction.setText(tr("Stop"));
|
||||||
|
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconStop.svg")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stopped();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SFXPlayerWidget::stopped()
|
||||||
|
{
|
||||||
|
m_vox->keyOff();
|
||||||
|
m_vox.reset();
|
||||||
|
m_playAction.setText(tr("Play"));
|
||||||
|
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconSoundMacro.svg")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SFXPlayerWidget::resizeEvent(QResizeEvent* event)
|
||||||
|
{
|
||||||
|
m_button.setGeometry(event->size().width() - event->size().height(), 0, event->size().height(), event->size().height());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SFXPlayerWidget::mouseDoubleClickEvent(QMouseEvent* event)
|
||||||
|
{
|
||||||
|
qobject_cast<QTableView*>(parentWidget()->parentWidget())->setIndexWidget(m_index, nullptr);
|
||||||
|
event->ignore();
|
||||||
|
}
|
||||||
|
|
||||||
|
SFXPlayerWidget::~SFXPlayerWidget()
|
||||||
|
{
|
||||||
|
if (m_vox)
|
||||||
|
m_vox->keyOff();
|
||||||
|
}
|
||||||
|
|
||||||
|
SFXPlayerWidget::SFXPlayerWidget(QModelIndex index, amuse::GroupId gid, amuse::SFXId id, QWidget* parent)
|
||||||
|
: QWidget(parent), m_button(this), m_playAction(tr("Play")), m_index(index),
|
||||||
|
m_groupId(gid), m_sfxId(id)
|
||||||
|
{
|
||||||
|
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconSoundMacro.svg")));
|
||||||
|
m_button.setDefaultAction(&m_playAction);
|
||||||
|
connect(&m_playAction, SIGNAL(triggered()), this, SLOT(clicked()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SoundGroupEditor::loadData(ProjectModel::SoundGroupNode* node)
|
||||||
|
{
|
||||||
|
m_sfxs.loadData(node);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundGroupEditor::unloadData()
|
||||||
|
{
|
||||||
|
m_sfxs.unloadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectModel::INode* SoundGroupEditor::currentNode() const
|
||||||
|
{
|
||||||
|
return m_sfxs.m_node.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundGroupEditor::resizeEvent(QResizeEvent* ev)
|
||||||
|
{
|
||||||
|
m_sfxTable->setGeometry(QRect({}, ev->size()));
|
||||||
|
m_addRemoveButtons.move(0, ev->size().height() - 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SoundGroupEditor::isItemEditEnabled() const
|
||||||
|
{
|
||||||
|
return m_sfxTable->hasFocus() && !m_sfxTable->selectionModel()->selectedRows().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundGroupEditor::doAdd()
|
||||||
|
{
|
||||||
|
QModelIndex idx = m_sfxTable->selectionModel()->currentIndex();
|
||||||
|
if (!idx.isValid())
|
||||||
|
m_sfxTable->model()->insertRow(m_sfxTable->model()->rowCount() - 1);
|
||||||
|
else
|
||||||
|
m_sfxTable->model()->insertRow(idx.row());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundGroupEditor::doSelectionChanged()
|
||||||
|
{
|
||||||
|
m_addRemoveButtons.removeAction()->setDisabled(m_sfxTable->selectionModel()->selectedRows().isEmpty());
|
||||||
|
g_MainWindow->updateFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundGroupEditor::sfxDataChanged()
|
||||||
|
{
|
||||||
|
int idx = 0;
|
||||||
|
for (const auto& p : m_sfxs.m_sorted)
|
||||||
|
{
|
||||||
|
QModelIndex index = m_sfxs.index(idx, 1);
|
||||||
|
SFXPlayerWidget* w = qobject_cast<SFXPlayerWidget*>(m_sfxTable->indexWidget(index));
|
||||||
|
if (!w || w->sfxId() != p.m_it->first)
|
||||||
|
{
|
||||||
|
SFXPlayerWidget* newW = new SFXPlayerWidget(index, m_sfxs.m_node->m_id, p.m_it->first);
|
||||||
|
m_sfxTable->setIndexWidget(index, newW);
|
||||||
|
}
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundGroupEditor::itemCutAction()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SoundGroupEditor::itemCopyAction()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundGroupEditor::itemPasteAction()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundGroupEditor::itemDeleteAction()
|
||||||
|
{
|
||||||
|
m_sfxTable->deleteSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
SoundGroupEditor::SoundGroupEditor(QWidget* parent)
|
||||||
|
: EditorWidget(parent), m_sfxs(this), m_sfxTable(new SFXTableView(this)), m_addRemoveButtons(this)
|
||||||
|
{
|
||||||
|
m_sfxTable->setModel(&m_sfxs);
|
||||||
|
connect(m_sfxTable->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
|
||||||
|
this, SLOT(doSelectionChanged()));
|
||||||
|
|
||||||
|
connect(&m_sfxs, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
|
||||||
|
this, SLOT(sfxDataChanged()));
|
||||||
|
connect(&m_sfxs, SIGNAL(rowsRemoved(const QModelIndex&, int, int)),
|
||||||
|
this, SLOT(sfxDataChanged()));
|
||||||
|
connect(&m_sfxs, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
|
||||||
|
this, SLOT(sfxDataChanged()));
|
||||||
|
connect(&m_sfxs, SIGNAL(layoutChanged(const QList<QPersistentModelIndex>&, QAbstractItemModel::LayoutChangeHint)),
|
||||||
|
this, SLOT(sfxDataChanged()));
|
||||||
|
connect(&m_sfxs, SIGNAL(modelReset()),
|
||||||
|
this, SLOT(sfxDataChanged()));
|
||||||
|
|
||||||
|
m_addRemoveButtons.addAction()->setToolTip(tr("Add new SFX entry"));
|
||||||
|
connect(m_addRemoveButtons.addAction(), SIGNAL(triggered(bool)), this, SLOT(doAdd()));
|
||||||
|
m_addRemoveButtons.removeAction()->setToolTip(tr("Remove selected SFX entries"));
|
||||||
|
connect(m_addRemoveButtons.removeAction(), SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));
|
||||||
|
}
|
||||||
|
|
|
@ -2,14 +2,129 @@
|
||||||
#define AMUSE_SOUND_GROUP_EDITOR_HPP
|
#define AMUSE_SOUND_GROUP_EDITOR_HPP
|
||||||
|
|
||||||
#include "EditorWidget.hpp"
|
#include "EditorWidget.hpp"
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
#include <QTableView>
|
||||||
|
#include "amuse/Voice.hpp"
|
||||||
|
|
||||||
|
class SFXObjectDelegate : public QStyledItemDelegate
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit SFXObjectDelegate(QObject* parent = Q_NULLPTR);
|
||||||
|
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
|
||||||
|
void setEditorData(QWidget* editor, const QModelIndex& index) const;
|
||||||
|
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;
|
||||||
|
private slots:
|
||||||
|
void objIndexChanged();
|
||||||
|
};
|
||||||
|
|
||||||
|
class SFXModel : public QAbstractTableModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
friend class SoundGroupEditor;
|
||||||
|
friend class SFXObjectDelegate;
|
||||||
|
amuse::ObjToken<ProjectModel::SoundGroupNode> m_node;
|
||||||
|
struct Iterator
|
||||||
|
{
|
||||||
|
using ItTp = std::unordered_map<amuse::SFXId, amuse::SFXGroupIndex::SFXEntry>::iterator;
|
||||||
|
ItTp m_it;
|
||||||
|
Iterator(ItTp it) : m_it(it) {}
|
||||||
|
ItTp::pointer operator->() { return m_it.operator->(); }
|
||||||
|
bool operator<(const Iterator& other) const
|
||||||
|
{
|
||||||
|
return amuse::SFXId::CurNameDB->resolveNameFromId(m_it->first) <
|
||||||
|
amuse::SFXId::CurNameDB->resolveNameFromId(other.m_it->first);
|
||||||
|
}
|
||||||
|
bool operator<(amuse::SongId other) const
|
||||||
|
{
|
||||||
|
return amuse::SFXId::CurNameDB->resolveNameFromId(m_it->first) <
|
||||||
|
amuse::SFXId::CurNameDB->resolveNameFromId(other);
|
||||||
|
}
|
||||||
|
bool operator<(const std::string& name) const
|
||||||
|
{
|
||||||
|
return amuse::SFXId::CurNameDB->resolveNameFromId(m_it->first) < name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
std::vector<Iterator> m_sorted;
|
||||||
|
std::unordered_map<amuse::SFXId, amuse::SFXGroupIndex::SFXEntry>& _getMap() const;
|
||||||
|
void _buildSortedList();
|
||||||
|
QModelIndex _indexOfSFX(amuse::SFXId sfx) const;
|
||||||
|
int _hypotheticalIndexOfSFX(const std::string& sfxName) const;
|
||||||
|
public:
|
||||||
|
explicit SFXModel(QObject* parent = Q_NULLPTR);
|
||||||
|
void loadData(ProjectModel::SoundGroupNode* node);
|
||||||
|
void unloadData();
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex& parent = QModelIndex()) const;
|
||||||
|
int columnCount(const QModelIndex& parent = QModelIndex()) const;
|
||||||
|
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
|
||||||
|
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole);
|
||||||
|
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
|
||||||
|
Qt::ItemFlags flags(const QModelIndex& index) const;
|
||||||
|
|
||||||
|
bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex());
|
||||||
|
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex());
|
||||||
|
};
|
||||||
|
|
||||||
|
class SFXTableView : public QTableView
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
SFXObjectDelegate m_sfxDelegate;
|
||||||
|
RangedValueFactory<0, 127> m_127Factory;
|
||||||
|
RangedValueFactory<0, 255> m_255Factory;
|
||||||
|
QStyledItemDelegate m_127Delegate, m_255Delegate;
|
||||||
|
public:
|
||||||
|
explicit SFXTableView(QWidget* parent = Q_NULLPTR);
|
||||||
|
void setModel(QAbstractItemModel* model);
|
||||||
|
void deleteSelection();
|
||||||
|
};
|
||||||
|
|
||||||
|
class SFXPlayerWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QAction m_playAction;
|
||||||
|
QToolButton m_button;
|
||||||
|
QModelIndex m_index;
|
||||||
|
amuse::GroupId m_groupId;
|
||||||
|
amuse::SFXId m_sfxId;
|
||||||
|
amuse::ObjToken<amuse::Voice> m_vox;
|
||||||
|
public:
|
||||||
|
explicit SFXPlayerWidget(QModelIndex index, amuse::GroupId gid, amuse::SFXId id,
|
||||||
|
QWidget* parent = Q_NULLPTR);
|
||||||
|
~SFXPlayerWidget();
|
||||||
|
amuse::SongId sfxId() const { return m_sfxId; }
|
||||||
|
amuse::Voice* voice() const { return m_vox.get(); }
|
||||||
|
void stopped();
|
||||||
|
void resizeEvent(QResizeEvent* event);
|
||||||
|
void mouseDoubleClickEvent(QMouseEvent* event);
|
||||||
|
public slots:
|
||||||
|
void clicked();
|
||||||
|
};
|
||||||
|
|
||||||
class SoundGroupEditor : public EditorWidget
|
class SoundGroupEditor : public EditorWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
SFXModel m_sfxs;
|
||||||
|
SFXTableView* m_sfxTable;
|
||||||
|
AddRemoveButtons m_addRemoveButtons;
|
||||||
public:
|
public:
|
||||||
explicit SoundGroupEditor(QWidget* parent = Q_NULLPTR);
|
explicit SoundGroupEditor(QWidget* parent = Q_NULLPTR);
|
||||||
bool loadData(ProjectModel::SoundGroupNode* node);
|
bool loadData(ProjectModel::SoundGroupNode* node);
|
||||||
|
void unloadData();
|
||||||
|
ProjectModel::INode* currentNode() const;
|
||||||
|
void setEditorEnabled(bool en) {}
|
||||||
|
void resizeEvent(QResizeEvent* ev);
|
||||||
|
QTableView* getSFXListView() const { return m_sfxTable; }
|
||||||
|
bool isItemEditEnabled() const;
|
||||||
|
public slots:
|
||||||
|
void doAdd();
|
||||||
|
void doSelectionChanged();
|
||||||
|
void sfxDataChanged();
|
||||||
|
|
||||||
|
void itemCutAction();
|
||||||
|
void itemCopyAction();
|
||||||
|
void itemPasteAction();
|
||||||
|
void itemDeleteAction();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif //AMUSE_SOUND_GROUP_EDITOR_HPP
|
#endif //AMUSE_SOUND_GROUP_EDITOR_HPP
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
viewBox="0 0 4.2333332 4.2333335"
|
viewBox="0 0 4.2333332 4.2333335"
|
||||||
id="svg2"
|
id="svg2"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
inkscape:version="0.91+devel+osxmenu r12922"
|
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||||
sodipodi:docname="NewSoundMacro.svg">
|
sodipodi:docname="IconNewSoundMacro.svg">
|
||||||
<defs
|
<defs
|
||||||
id="defs4" />
|
id="defs4" />
|
||||||
<sodipodi:namedview
|
<sodipodi:namedview
|
||||||
|
@ -26,16 +26,16 @@
|
||||||
inkscape:pageopacity="0"
|
inkscape:pageopacity="0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="30.33"
|
inkscape:zoom="30.33"
|
||||||
inkscape:cx="8.5002398"
|
inkscape:cx="2.9941402"
|
||||||
inkscape:cy="8.6348884"
|
inkscape:cy="4.6784097"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:current-layer="layer1"
|
inkscape:current-layer="layer1"
|
||||||
showgrid="true"
|
showgrid="true"
|
||||||
units="px"
|
units="px"
|
||||||
inkscape:window-width="1260"
|
inkscape:window-width="1260"
|
||||||
inkscape:window-height="787"
|
inkscape:window-height="787"
|
||||||
inkscape:window-x="0"
|
inkscape:window-x="606"
|
||||||
inkscape:window-y="23"
|
inkscape:window-y="435"
|
||||||
inkscape:window-maximized="0"
|
inkscape:window-maximized="0"
|
||||||
showborder="false"
|
showborder="false"
|
||||||
objecttolerance="4"
|
objecttolerance="4"
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
type="xygrid"
|
type="xygrid"
|
||||||
id="grid4173"
|
id="grid4173"
|
||||||
empspacing="1"
|
empspacing="1"
|
||||||
visible="false" />
|
visible="true" />
|
||||||
</sodipodi:namedview>
|
</sodipodi:namedview>
|
||||||
<metadata
|
<metadata
|
||||||
id="metadata7">
|
id="metadata7">
|
||||||
|
@ -66,14 +66,14 @@
|
||||||
transform="translate(0,-292.76665)">
|
transform="translate(0,-292.76665)">
|
||||||
<path
|
<path
|
||||||
style="opacity:1;fill:#62ff04;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.30031049;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
style="opacity:1;fill:#62ff04;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.30031049;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="m 0.96793039,292.83382 2.60268711,1.68348 c 0.392506,0.24382 0.3952842,0.49652 0,0.73535 l -2.60268711,1.69163 C 0.68893458,297.12561 0.3672936,296.82502 0.3672936,296.49227 l 0,-3.29415 c 0,-0.33276 0.32123894,-0.54502 0.60063679,-0.3643 z"
|
d="m 0.96793039,292.83382 2.60268711,1.68348 c 0.392506,0.24382 0.3952841,0.49652 0,0.73535 l -2.60268711,1.69163 c -0.2789936,0.18133 -0.6006367,-0.0548 -0.6006367,-0.39294 v -3.35322 c 0,-0.33276 0.3212394,-0.54502 0.6006367,-0.3643 z"
|
||||||
id="rect5899"
|
id="rect5899-3"
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
sodipodi:nodetypes="sccssss" />
|
sodipodi:nodetypes="sccssss" />
|
||||||
<path
|
<path
|
||||||
id="path5323"
|
id="path5323"
|
||||||
style="fill:#ff0000;fill-rule:evenodd;stroke:#df0000;stroke-width:0.26458332;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
style="fill:#ff0000;fill-rule:evenodd;stroke:#df0000;stroke-width:0.26458332;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="m 2.8386428,295.6133 0.9362114,0.93622 m -0.9362104,-10e-6 0.9362094,-0.9362 m -1.1301062,0.4681 1.324003,0 m -0.6620015,0.662 0,-1.324"
|
d="m 2.8386428,295.6133 0.9362114,0.93622 m -0.9362104,-10e-6 0.9362094,-0.9362 m -1.1301062,0.4681 H 3.96875 m -0.6620015,0.662 v -1.324"
|
||||||
inkscape:connector-curvature="0" />
|
inkscape:connector-curvature="0" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
@ -14,7 +14,7 @@
|
||||||
viewBox="0 0 4.2333332 4.2333335"
|
viewBox="0 0 4.2333332 4.2333335"
|
||||||
id="svg2"
|
id="svg2"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
inkscape:version="0.91+devel+osxmenu r12922"
|
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||||
sodipodi:docname="IconSoundMacro.svg">
|
sodipodi:docname="IconSoundMacro.svg">
|
||||||
<defs
|
<defs
|
||||||
id="defs4" />
|
id="defs4" />
|
||||||
|
@ -25,9 +25,9 @@
|
||||||
borderopacity="1.0"
|
borderopacity="1.0"
|
||||||
inkscape:pageopacity="0"
|
inkscape:pageopacity="0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="30.33"
|
inkscape:zoom="49.27"
|
||||||
inkscape:cx="8.5002398"
|
inkscape:cx="6.2572034"
|
||||||
inkscape:cy="8.6348884"
|
inkscape:cy="5.0825963"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:current-layer="layer1"
|
inkscape:current-layer="layer1"
|
||||||
showgrid="true"
|
showgrid="true"
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
inkscape:window-width="1260"
|
inkscape:window-width="1260"
|
||||||
inkscape:window-height="787"
|
inkscape:window-height="787"
|
||||||
inkscape:window-x="0"
|
inkscape:window-x="0"
|
||||||
inkscape:window-y="23"
|
inkscape:window-y="40"
|
||||||
inkscape:window-maximized="0"
|
inkscape:window-maximized="0"
|
||||||
showborder="false"
|
showborder="false"
|
||||||
objecttolerance="4"
|
objecttolerance="4"
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
type="xygrid"
|
type="xygrid"
|
||||||
id="grid4173"
|
id="grid4173"
|
||||||
empspacing="1"
|
empspacing="1"
|
||||||
visible="false" />
|
visible="true" />
|
||||||
</sodipodi:namedview>
|
</sodipodi:namedview>
|
||||||
<metadata
|
<metadata
|
||||||
id="metadata7">
|
id="metadata7">
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
transform="translate(0,-292.76665)">
|
transform="translate(0,-292.76665)">
|
||||||
<path
|
<path
|
||||||
style="opacity:1;fill:#62ff04;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.30031049;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
style="opacity:1;fill:#62ff04;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.30031049;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="m 0.96793039,292.83382 2.60268711,1.68348 c 0.392506,0.24382 0.3952842,0.49652 0,0.73535 l -2.60268711,1.69163 C 0.68893458,297.12561 0.3672936,296.82502 0.3672936,296.49227 l 0,-3.29415 c 0,-0.33276 0.32123894,-0.54502 0.60063679,-0.3643 z"
|
d="m 0.96793039,292.83382 2.60268711,1.68348 c 0.392506,0.24382 0.3952842,0.49652 0,0.73535 l -2.60268711,1.69163 c -0.27899363,0.18133 -0.60063679,-0.0548 -0.60063679,-0.39294 l 0,-3.35322 c 0,-0.33276 0.32123948,-0.54502 0.60063679,-0.3643 z"
|
||||||
id="rect5899"
|
id="rect5899"
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
sodipodi:nodetypes="sccssss" />
|
sodipodi:nodetypes="sccssss" />
|
||||||
|
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1,75 @@
|
||||||
|
<?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="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 4.2333332 4.2333335"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||||
|
sodipodi:docname="IconStop.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#393939"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="30.328889"
|
||||||
|
inkscape:cx="6.3383887"
|
||||||
|
inkscape:cy="7.7203454"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="true"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="1260"
|
||||||
|
inkscape:window-height="787"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="40"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
showborder="true"
|
||||||
|
objecttolerance="4"
|
||||||
|
gridtolerance="4"
|
||||||
|
guidetolerance="5">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4173"
|
||||||
|
empspacing="1"
|
||||||
|
visible="true" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<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,-292.76665)">
|
||||||
|
<rect
|
||||||
|
style="fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect842"
|
||||||
|
width="3.7041667"
|
||||||
|
height="3.7041805"
|
||||||
|
x="0.26458335"
|
||||||
|
y="293.03122" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
File diff suppressed because it is too large
Load Diff
|
@ -28,6 +28,7 @@
|
||||||
<file>IconPaintbrush.svg</file>
|
<file>IconPaintbrush.svg</file>
|
||||||
<file>IconAdd.svg</file>
|
<file>IconAdd.svg</file>
|
||||||
<file>IconRemove.svg</file>
|
<file>IconRemove.svg</file>
|
||||||
|
<file>IconStop.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/bg">
|
<qresource prefix="/bg">
|
||||||
<file>FaceGrey.svg</file>
|
<file>FaceGrey.svg</file>
|
||||||
|
|
|
@ -1153,6 +1153,7 @@ struct SoundMacro
|
||||||
return;
|
return;
|
||||||
std::swap(m_cmds[a], m_cmds[b]);
|
std::swap(m_cmds[a], m_cmds[b]);
|
||||||
}
|
}
|
||||||
|
void buildFromPrototype(const SoundMacro& other);
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|
|
@ -135,7 +135,7 @@ struct SFXGroupIndex : AudioGroupIndex
|
||||||
{
|
{
|
||||||
AT_DECL_DNA
|
AT_DECL_DNA
|
||||||
SFXIdDNA<DNAEn> sfxId;
|
SFXIdDNA<DNAEn> sfxId;
|
||||||
SoundMacroIdDNA<DNAEn> macro;
|
PageObjectIdDNA<DNAEn> objId;
|
||||||
Value<atUint8> priority;
|
Value<atUint8> priority;
|
||||||
Value<atUint8> maxVoices;
|
Value<atUint8> maxVoices;
|
||||||
Value<atUint8> defVel;
|
Value<atUint8> defVel;
|
||||||
|
@ -146,18 +146,18 @@ struct SFXGroupIndex : AudioGroupIndex
|
||||||
struct SFXEntry : BigDNA
|
struct SFXEntry : BigDNA
|
||||||
{
|
{
|
||||||
AT_DECL_DNA_YAML
|
AT_DECL_DNA_YAML
|
||||||
SoundMacroIdDNA<athena::Big> macro;
|
PageObjectIdDNA<athena::Big> objId;
|
||||||
Value<atUint8> priority;
|
Value<atUint8> priority = 0;
|
||||||
Value<atUint8> maxVoices;
|
Value<atUint8> maxVoices = 255;
|
||||||
Value<atUint8> defVel;
|
Value<atUint8> defVel = 127;
|
||||||
Value<atUint8> panning;
|
Value<atUint8> panning = 64;
|
||||||
Value<atUint8> defKey;
|
Value<atUint8> defKey = 60;
|
||||||
|
|
||||||
SFXEntry() = default;
|
SFXEntry() = default;
|
||||||
|
|
||||||
template <athena::Endian DNAE>
|
template <athena::Endian DNAE>
|
||||||
SFXEntry(const SFXEntryDNA<DNAE>& in)
|
SFXEntry(const SFXEntryDNA<DNAE>& in)
|
||||||
: macro(in.macro.id), priority(in.priority), maxVoices(in.maxVoices),
|
: objId(in.objId.id), priority(in.priority), maxVoices(in.maxVoices),
|
||||||
defVel(in.defVel), panning(in.panning), defKey(in.defKey) {}
|
defVel(in.defVel), panning(in.panning), defKey(in.defKey) {}
|
||||||
|
|
||||||
template <athena::Endian DNAEn>
|
template <athena::Endian DNAEn>
|
||||||
|
@ -165,7 +165,7 @@ struct SFXGroupIndex : AudioGroupIndex
|
||||||
{
|
{
|
||||||
SFXEntryDNA<DNAEn> ret;
|
SFXEntryDNA<DNAEn> ret;
|
||||||
ret.sfxId.id = id;
|
ret.sfxId.id = id;
|
||||||
ret.macro = macro;
|
ret.objId = objId;
|
||||||
ret.priority = priority;
|
ret.priority = priority;
|
||||||
ret.maxVoices = maxVoices;
|
ret.maxVoices = maxVoices;
|
||||||
ret.defVel = defVel;
|
ret.defVel = defVel;
|
||||||
|
|
|
@ -617,6 +617,7 @@ struct NameDB
|
||||||
|
|
||||||
ObjectId generateId(Type tp) const;
|
ObjectId generateId(Type tp) const;
|
||||||
static std::string generateName(ObjectId id, Type tp);
|
static std::string generateName(ObjectId id, Type tp);
|
||||||
|
std::string generateDefaultName(Type tp) const;
|
||||||
std::string_view registerPair(std::string_view str, ObjectId id);
|
std::string_view registerPair(std::string_view str, ObjectId id);
|
||||||
std::string_view resolveNameFromId(ObjectId id) const;
|
std::string_view resolveNameFromId(ObjectId id) const;
|
||||||
ObjectId resolveIdFromName(std::string_view str) const;
|
ObjectId resolveIdFromName(std::string_view str) const;
|
||||||
|
|
|
@ -48,20 +48,20 @@ class Engine
|
||||||
std::list<ObjToken<Sequencer>> m_activeSequencers;
|
std::list<ObjToken<Sequencer>> m_activeSequencers;
|
||||||
bool m_defaultStudioReady = false;
|
bool m_defaultStudioReady = false;
|
||||||
ObjToken<Studio> m_defaultStudio;
|
ObjToken<Studio> m_defaultStudio;
|
||||||
std::unordered_map<SFXId, std::tuple<AudioGroup*, int, const SFXGroupIndex::SFXEntry*>> m_sfxLookup;
|
std::unordered_map<SFXId, std::tuple<AudioGroup*, GroupId, const SFXGroupIndex::SFXEntry*>> m_sfxLookup;
|
||||||
std::linear_congruential_engine<uint32_t, 0x41c64e6d, 0x3039, UINT32_MAX> m_random;
|
std::linear_congruential_engine<uint32_t, 0x41c64e6d, 0x3039, UINT32_MAX> m_random;
|
||||||
int m_nextVid = 0;
|
int m_nextVid = 0;
|
||||||
float m_masterVolume = 1.f;
|
float m_masterVolume = 1.f;
|
||||||
AudioChannelSet m_channelSet = AudioChannelSet::Unknown;
|
AudioChannelSet m_channelSet = AudioChannelSet::Unknown;
|
||||||
|
|
||||||
AudioGroup* _addAudioGroup(const AudioGroupData& data, std::unique_ptr<AudioGroup>&& grp);
|
AudioGroup* _addAudioGroup(const AudioGroupData& data, std::unique_ptr<AudioGroup>&& grp);
|
||||||
std::pair<AudioGroup*, const SongGroupIndex*> _findSongGroup(int groupId) const;
|
std::pair<AudioGroup*, const SongGroupIndex*> _findSongGroup(GroupId groupId) const;
|
||||||
std::pair<AudioGroup*, const SFXGroupIndex*> _findSFXGroup(int groupId) const;
|
std::pair<AudioGroup*, const SFXGroupIndex*> _findSFXGroup(GroupId groupId) const;
|
||||||
|
|
||||||
std::list<ObjToken<Voice>>::iterator _allocateVoice(const AudioGroup& group, int groupId, double sampleRate,
|
std::list<ObjToken<Voice>>::iterator _allocateVoice(const AudioGroup& group, GroupId groupId, double sampleRate,
|
||||||
bool dynamicPitch, bool emitter, ObjToken<Studio> studio);
|
bool dynamicPitch, bool emitter, ObjToken<Studio> studio);
|
||||||
std::list<ObjToken<Sequencer>>::iterator _allocateSequencer(const AudioGroup& group, int groupId,
|
std::list<ObjToken<Sequencer>>::iterator _allocateSequencer(const AudioGroup& group, GroupId groupId,
|
||||||
int setupId, ObjToken<Studio> studio);
|
SongId setupId, ObjToken<Studio> studio);
|
||||||
ObjToken<Studio> _allocateStudio(bool mainOut);
|
ObjToken<Studio> _allocateStudio(bool mainOut);
|
||||||
std::list<ObjToken<Voice>>::iterator _destroyVoice(std::list<ObjToken<Voice>>::iterator it);
|
std::list<ObjToken<Voice>>::iterator _destroyVoice(std::list<ObjToken<Voice>>::iterator it);
|
||||||
std::list<ObjToken<Sequencer>>::iterator
|
std::list<ObjToken<Sequencer>>::iterator
|
||||||
|
@ -88,12 +88,19 @@ public:
|
||||||
ObjToken<Studio> addStudio(bool mainOut);
|
ObjToken<Studio> addStudio(bool mainOut);
|
||||||
|
|
||||||
/** Start soundFX playing from loaded audio groups */
|
/** Start soundFX playing from loaded audio groups */
|
||||||
ObjToken<Voice> fxStart(int sfxId, float vol, float pan, ObjToken<Studio> smx);
|
ObjToken<Voice> fxStart(SFXId sfxId, float vol, float pan, ObjToken<Studio> smx);
|
||||||
ObjToken<Voice> fxStart(int sfxId, float vol, float pan)
|
ObjToken<Voice> fxStart(SFXId sfxId, float vol, float pan)
|
||||||
{
|
{
|
||||||
return fxStart(sfxId, vol, pan, m_defaultStudio);
|
return fxStart(sfxId, vol, pan, m_defaultStudio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Start soundFX playing from explicit group data (for editor use) */
|
||||||
|
ObjToken<Voice> fxStart(const AudioGroup* group, GroupId groupId, SFXId sfxId, float vol, float pan, ObjToken<Studio> smx);
|
||||||
|
ObjToken<Voice> fxStart(const AudioGroup* group, GroupId groupId, SFXId sfxId, float vol, float pan)
|
||||||
|
{
|
||||||
|
return fxStart(group, groupId, sfxId, vol, pan, m_defaultStudio);
|
||||||
|
}
|
||||||
|
|
||||||
/** Start SoundMacro node playing directly (for editor use) */
|
/** Start SoundMacro node playing directly (for editor use) */
|
||||||
ObjToken<Voice> macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key,
|
ObjToken<Voice> macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key,
|
||||||
uint8_t vel, uint8_t mod, ObjToken<Studio> smx);
|
uint8_t vel, uint8_t mod, ObjToken<Studio> smx);
|
||||||
|
@ -123,9 +130,9 @@ public:
|
||||||
|
|
||||||
/** Start soundFX playing from loaded audio groups, attach to positional emitter */
|
/** Start soundFX playing from loaded audio groups, attach to positional emitter */
|
||||||
ObjToken<Emitter> addEmitter(const float* pos, const float* dir, float maxDist, float falloff,
|
ObjToken<Emitter> addEmitter(const float* pos, const float* dir, float maxDist, float falloff,
|
||||||
int sfxId, float minVol, float maxVol, bool doppler, ObjToken<Studio> smx);
|
SFXId sfxId, float minVol, float maxVol, bool doppler, ObjToken<Studio> smx);
|
||||||
ObjToken<Emitter> addEmitter(const float* pos, const float* dir, float maxDist, float falloff,
|
ObjToken<Emitter> addEmitter(const float* pos, const float* dir, float maxDist, float falloff,
|
||||||
int sfxId, float minVol, float maxVol, bool doppler)
|
SFXId sfxId, float minVol, float maxVol, bool doppler)
|
||||||
{
|
{
|
||||||
return addEmitter(pos, dir, maxDist, falloff, sfxId, minVol, maxVol, doppler, m_defaultStudio);
|
return addEmitter(pos, dir, maxDist, falloff, sfxId, minVol, maxVol, doppler, m_defaultStudio);
|
||||||
}
|
}
|
||||||
|
@ -138,12 +145,19 @@ public:
|
||||||
void removeListener(Listener* listener);
|
void removeListener(Listener* listener);
|
||||||
|
|
||||||
/** Start song playing from loaded audio groups */
|
/** Start song playing from loaded audio groups */
|
||||||
ObjToken<Sequencer> seqPlay(int groupId, int songId, const unsigned char* arrData, ObjToken<Studio> smx);
|
ObjToken<Sequencer> seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData, ObjToken<Studio> smx);
|
||||||
ObjToken<Sequencer> seqPlay(int groupId, int songId, const unsigned char* arrData)
|
ObjToken<Sequencer> seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData)
|
||||||
{
|
{
|
||||||
return seqPlay(groupId, songId, arrData, m_defaultStudio);
|
return seqPlay(groupId, songId, arrData, m_defaultStudio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Start song playing from explicit group data (for editor use) */
|
||||||
|
ObjToken<Sequencer> seqPlay(const AudioGroup* group, GroupId groupId, SongId songId, const unsigned char* arrData, ObjToken<Studio> smx);
|
||||||
|
ObjToken<Sequencer> seqPlay(const AudioGroup* group, GroupId groupId, SongId songId, const unsigned char* arrData)
|
||||||
|
{
|
||||||
|
return seqPlay(group, groupId, songId, arrData, m_defaultStudio);
|
||||||
|
}
|
||||||
|
|
||||||
/** Set total volume of engine */
|
/** Set total volume of engine */
|
||||||
void setVolume(float vol);
|
void setVolume(float vol);
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Engine;
|
||||||
class AudioGroup;
|
class AudioGroup;
|
||||||
|
|
||||||
/** Common 'engine child' class */
|
/** Common 'engine child' class */
|
||||||
class Entity
|
class Entity : public IObj
|
||||||
{
|
{
|
||||||
/* Only the Engine will manage Entity lifetimes,
|
/* Only the Engine will manage Entity lifetimes,
|
||||||
* but shared_ptrs are issued to the client so it can safely track state */
|
* but shared_ptrs are issued to the client so it can safely track state */
|
||||||
|
|
|
@ -48,6 +48,7 @@ class Voice : public Entity
|
||||||
int m_vid; /**< VoiceID of this voice instance */
|
int m_vid; /**< VoiceID of this voice instance */
|
||||||
bool m_emitter; /**< Voice is part of an Emitter */
|
bool m_emitter; /**< Voice is part of an Emitter */
|
||||||
ObjToken<Studio> m_studio; /**< Studio this voice outputs to */
|
ObjToken<Studio> m_studio; /**< Studio this voice outputs to */
|
||||||
|
IObjToken<Sequencer> m_sequencer; /**< Strong reference to parent sequencer to retain ctrl vals */
|
||||||
|
|
||||||
std::unique_ptr<IBackendVoice> m_backendVoice; /**< Handle to client-implemented backend voice */
|
std::unique_ptr<IBackendVoice> m_backendVoice; /**< Handle to client-implemented backend voice */
|
||||||
SoundMacroState m_state; /**< State container for SoundMacro playback */
|
SoundMacroState m_state; /**< State container for SoundMacro playback */
|
||||||
|
|
|
@ -23,6 +23,15 @@ struct MakeCmdOp
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MakeCopyCmdOp
|
||||||
|
{
|
||||||
|
template <class Tp, class R>
|
||||||
|
static std::unique_ptr<SoundMacro::ICmd> Do(R& r)
|
||||||
|
{
|
||||||
|
return std::make_unique<Tp>(static_cast<const Tp&>(r));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct MakeDefaultCmdOp
|
struct MakeDefaultCmdOp
|
||||||
{
|
{
|
||||||
template <class Tp, class R>
|
template <class Tp, class R>
|
||||||
|
@ -209,7 +218,7 @@ AudioGroupPool AudioGroupPool::CreateAudioGroupPool(SystemStringView groupPath)
|
||||||
AudioGroupPool ret;
|
AudioGroupPool ret;
|
||||||
SystemString poolPath(groupPath);
|
SystemString poolPath(groupPath);
|
||||||
poolPath += _S("/!pool.yaml");
|
poolPath += _S("/!pool.yaml");
|
||||||
athena::io::FileReader fi(poolPath);
|
athena::io::FileReader fi(poolPath, 32 * 1024, false);
|
||||||
|
|
||||||
if (!fi.hasError())
|
if (!fi.hasError())
|
||||||
{
|
{
|
||||||
|
@ -384,6 +393,13 @@ void SoundMacro::readCmds(athena::io::IStreamReader& r, uint32_t size)
|
||||||
template void SoundMacro::readCmds<athena::Big>(athena::io::IStreamReader& r, uint32_t size);
|
template void SoundMacro::readCmds<athena::Big>(athena::io::IStreamReader& r, uint32_t size);
|
||||||
template void SoundMacro::readCmds<athena::Little>(athena::io::IStreamReader& r, uint32_t size);
|
template void SoundMacro::readCmds<athena::Little>(athena::io::IStreamReader& r, uint32_t size);
|
||||||
|
|
||||||
|
void SoundMacro::buildFromPrototype(const SoundMacro& other)
|
||||||
|
{
|
||||||
|
m_cmds.reserve(other.m_cmds.size());
|
||||||
|
for (auto& cmd : other.m_cmds)
|
||||||
|
m_cmds.push_back(CmdDo<MakeCopyCmdOp, std::unique_ptr<SoundMacro::ICmd>>(*cmd));
|
||||||
|
}
|
||||||
|
|
||||||
const SoundMacro* AudioGroupPool::soundMacro(ObjectId id) const
|
const SoundMacro* AudioGroupPool::soundMacro(ObjectId id) const
|
||||||
{
|
{
|
||||||
auto search = m_soundMacros.find(id);
|
auto search = m_soundMacros.find(id);
|
||||||
|
@ -447,6 +463,11 @@ static SoundMacro::CmdOp _ReadCmdOp(SoundMacro::CmdOp& op)
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SoundMacro::CmdOp _ReadCmdOp(const SoundMacro::ICmd& op)
|
||||||
|
{
|
||||||
|
return op.Isa();
|
||||||
|
}
|
||||||
|
|
||||||
template <class Op, class O, class... _Args>
|
template <class Op, class O, class... _Args>
|
||||||
O SoundMacro::CmdDo(_Args&&... args)
|
O SoundMacro::CmdDo(_Args&&... args)
|
||||||
{
|
{
|
||||||
|
|
|
@ -318,7 +318,7 @@ AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView gr
|
||||||
AudioGroupProject ret;
|
AudioGroupProject ret;
|
||||||
SystemString projPath(groupPath);
|
SystemString projPath(groupPath);
|
||||||
projPath += _S("/!project.yaml");
|
projPath += _S("/!project.yaml");
|
||||||
athena::io::FileReader fi(projPath);
|
athena::io::FileReader fi(projPath, 32 * 1024, false);
|
||||||
|
|
||||||
if (!fi.hasError())
|
if (!fi.hasError())
|
||||||
{
|
{
|
||||||
|
|
|
@ -316,6 +316,11 @@ std::string NameDB::generateName(ObjectId id, Type tp)
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string NameDB::generateDefaultName(Type tp) const
|
||||||
|
{
|
||||||
|
return generateName(generateId(tp), tp);
|
||||||
|
}
|
||||||
|
|
||||||
std::string_view NameDB::registerPair(std::string_view str, ObjectId id)
|
std::string_view NameDB::registerPair(std::string_view str, ObjectId id)
|
||||||
{
|
{
|
||||||
m_stringToId[std::string(str)] = id;
|
m_stringToId[std::string(str)] = id;
|
||||||
|
|
|
@ -35,7 +35,7 @@ Engine::Engine(IBackendVoiceAllocator& backend, AmplitudeMode ampMode)
|
||||||
m_midiReader = backend.allocateMIDIReader(*this);
|
m_midiReader = backend.allocateMIDIReader(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<AudioGroup*, const SongGroupIndex*> Engine::_findSongGroup(int groupId) const
|
std::pair<AudioGroup*, const SongGroupIndex*> Engine::_findSongGroup(GroupId groupId) const
|
||||||
{
|
{
|
||||||
for (const auto& pair : m_audioGroups)
|
for (const auto& pair : m_audioGroups)
|
||||||
{
|
{
|
||||||
|
@ -46,7 +46,7 @@ std::pair<AudioGroup*, const SongGroupIndex*> Engine::_findSongGroup(int groupId
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<AudioGroup*, const SFXGroupIndex*> Engine::_findSFXGroup(int groupId) const
|
std::pair<AudioGroup*, const SFXGroupIndex*> Engine::_findSFXGroup(GroupId groupId) const
|
||||||
{
|
{
|
||||||
for (const auto& pair : m_audioGroups)
|
for (const auto& pair : m_audioGroups)
|
||||||
{
|
{
|
||||||
|
@ -57,7 +57,7 @@ std::pair<AudioGroup*, const SFXGroupIndex*> Engine::_findSFXGroup(int groupId)
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::list<ObjToken<Voice>>::iterator Engine::_allocateVoice(const AudioGroup& group, int groupId,
|
std::list<ObjToken<Voice>>::iterator Engine::_allocateVoice(const AudioGroup& group, GroupId groupId,
|
||||||
double sampleRate, bool dynamicPitch, bool emitter,
|
double sampleRate, bool dynamicPitch, bool emitter,
|
||||||
ObjToken<Studio> studio)
|
ObjToken<Studio> studio)
|
||||||
{
|
{
|
||||||
|
@ -70,8 +70,8 @@ std::list<ObjToken<Voice>>::iterator Engine::_allocateVoice(const AudioGroup& gr
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::list<ObjToken<Sequencer>>::iterator Engine::_allocateSequencer(const AudioGroup& group, int groupId,
|
std::list<ObjToken<Sequencer>>::iterator Engine::_allocateSequencer(const AudioGroup& group, GroupId groupId,
|
||||||
int setupId, ObjToken<Studio> studio)
|
SongId setupId, ObjToken<Studio> studio)
|
||||||
{
|
{
|
||||||
const SongGroupIndex* songGroup = group.getProj().getSongGroupIndex(groupId);
|
const SongGroupIndex* songGroup = group.getProj().getSongGroupIndex(groupId);
|
||||||
if (songGroup)
|
if (songGroup)
|
||||||
|
@ -265,7 +265,7 @@ void Engine::removeAudioGroup(const AudioGroupData& data)
|
||||||
ObjToken<Studio> Engine::addStudio(bool mainOut) { return _allocateStudio(mainOut); }
|
ObjToken<Studio> Engine::addStudio(bool mainOut) { return _allocateStudio(mainOut); }
|
||||||
|
|
||||||
/** Start soundFX playing from loaded audio groups */
|
/** Start soundFX playing from loaded audio groups */
|
||||||
ObjToken<Voice> Engine::fxStart(int sfxId, float vol, float pan, ObjToken<Studio> smx)
|
ObjToken<Voice> Engine::fxStart(SFXId sfxId, float vol, float pan, ObjToken<Studio> smx)
|
||||||
{
|
{
|
||||||
auto search = m_sfxLookup.find(sfxId);
|
auto search = m_sfxLookup.find(sfxId);
|
||||||
if (search == m_sfxLookup.end())
|
if (search == m_sfxLookup.end())
|
||||||
|
@ -279,17 +279,49 @@ ObjToken<Voice> Engine::fxStart(int sfxId, float vol, float pan, ObjToken<Studio
|
||||||
std::list<ObjToken<Voice>>::iterator ret =
|
std::list<ObjToken<Voice>>::iterator ret =
|
||||||
_allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, false, smx);
|
_allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, false, smx);
|
||||||
|
|
||||||
if (!(*ret)->loadMacroObject(entry->macro.id, 0, 1000.f, entry->defKey, entry->defVel, 0))
|
if (!(*ret)->loadPageObject(entry->objId, 1000.f, entry->defKey, entry->defVel, 0))
|
||||||
{
|
{
|
||||||
_destroyVoice(ret);
|
_destroyVoice(ret);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
(*ret)->setVolume(vol);
|
(*ret)->setVolume(vol);
|
||||||
(*ret)->setPan(pan);
|
float evalPan = pan != 0.f ? pan : ((entry->panning - 64.f) / 63.f);
|
||||||
|
evalPan = clamp(-1.f, evalPan, 1.f);
|
||||||
|
(*ret)->setPan(evalPan);
|
||||||
return *ret;
|
return *ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Start soundFX playing from explicit group data (for editor use) */
|
||||||
|
ObjToken<Voice> Engine::fxStart(const AudioGroup* group, GroupId groupId, SFXId sfxId, float vol, float pan, ObjToken<Studio> smx)
|
||||||
|
{
|
||||||
|
const SFXGroupIndex* sfxIdx = group->getProj().getSFXGroupIndex(groupId);
|
||||||
|
if (sfxIdx)
|
||||||
|
{
|
||||||
|
auto search = sfxIdx->m_sfxEntries.find(sfxId);
|
||||||
|
if (search != sfxIdx->m_sfxEntries.cend())
|
||||||
|
{
|
||||||
|
std::list<ObjToken<Voice>>::iterator ret =
|
||||||
|
_allocateVoice(*group, groupId, NativeSampleRate, true, false, smx);
|
||||||
|
|
||||||
|
auto& entry = search->second;
|
||||||
|
if (!(*ret)->loadPageObject(entry.objId, 1000.f, entry.defKey, entry.defVel, 0))
|
||||||
|
{
|
||||||
|
_destroyVoice(ret);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
(*ret)->setVolume(vol);
|
||||||
|
float evalPan = pan != 0.f ? pan : ((entry.panning - 64.f) / 63.f);
|
||||||
|
evalPan = clamp(-1.f, evalPan, 1.f);
|
||||||
|
(*ret)->setPan(evalPan);
|
||||||
|
return *ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
/** Start SoundMacro node playing directly (for editor use) */
|
/** Start SoundMacro node playing directly (for editor use) */
|
||||||
ObjToken<Voice> Engine::macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key, uint8_t vel,
|
ObjToken<Voice> Engine::macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key, uint8_t vel,
|
||||||
uint8_t mod, ObjToken<Studio> smx)
|
uint8_t mod, ObjToken<Studio> smx)
|
||||||
|
@ -353,7 +385,7 @@ ObjToken<Voice> Engine::pageObjectStart(const AudioGroup* group, ObjectId id, ui
|
||||||
|
|
||||||
/** Start soundFX playing from loaded audio groups, attach to positional emitter */
|
/** Start soundFX playing from loaded audio groups, attach to positional emitter */
|
||||||
ObjToken<Emitter> Engine::addEmitter(const float* pos, const float* dir, float maxDist, float falloff,
|
ObjToken<Emitter> Engine::addEmitter(const float* pos, const float* dir, float maxDist, float falloff,
|
||||||
int sfxId, float minVol, float maxVol, bool doppler, ObjToken<Studio> smx)
|
SFXId sfxId, float minVol, float maxVol, bool doppler, ObjToken<Studio> smx)
|
||||||
{
|
{
|
||||||
auto search = m_sfxLookup.find(sfxId);
|
auto search = m_sfxLookup.find(sfxId);
|
||||||
if (search == m_sfxLookup.end())
|
if (search == m_sfxLookup.end())
|
||||||
|
@ -367,7 +399,7 @@ ObjToken<Emitter> Engine::addEmitter(const float* pos, const float* dir, float m
|
||||||
std::list<ObjToken<Voice>>::iterator vox =
|
std::list<ObjToken<Voice>>::iterator vox =
|
||||||
_allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, true, smx);
|
_allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, true, smx);
|
||||||
|
|
||||||
if (!(*vox)->loadMacroObject(entry->macro, 0, 1000.f, entry->defKey, entry->defVel, 0))
|
if (!(*vox)->loadPageObject(entry->objId, 1000.f, entry->defKey, entry->defVel, 0))
|
||||||
{
|
{
|
||||||
_destroyVoice(vox);
|
_destroyVoice(vox);
|
||||||
return {};
|
return {};
|
||||||
|
@ -409,7 +441,7 @@ void Engine::removeListener(Listener* listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Start song playing from loaded audio groups */
|
/** Start song playing from loaded audio groups */
|
||||||
ObjToken<Sequencer> Engine::seqPlay(int groupId, int songId, const unsigned char* arrData, ObjToken<Studio> smx)
|
ObjToken<Sequencer> Engine::seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData, ObjToken<Studio> smx)
|
||||||
{
|
{
|
||||||
std::pair<AudioGroup*, const SongGroupIndex*> songGrp = _findSongGroup(groupId);
|
std::pair<AudioGroup*, const SongGroupIndex*> songGrp = _findSongGroup(groupId);
|
||||||
if (songGrp.second)
|
if (songGrp.second)
|
||||||
|
@ -435,6 +467,33 @@ ObjToken<Sequencer> Engine::seqPlay(int groupId, int songId, const unsigned char
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ObjToken<Sequencer> Engine::seqPlay(const AudioGroup* group, GroupId groupId, SongId songId,
|
||||||
|
const unsigned char* arrData, ObjToken<Studio> smx)
|
||||||
|
{
|
||||||
|
const SongGroupIndex* sgIdx = group->getProj().getSongGroupIndex(groupId);
|
||||||
|
if (sgIdx)
|
||||||
|
{
|
||||||
|
std::list<ObjToken<Sequencer>>::iterator ret = _allocateSequencer(*group, groupId, songId, smx);
|
||||||
|
if (!*ret)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (arrData)
|
||||||
|
(*ret)->playSong(arrData);
|
||||||
|
return *ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SFXGroupIndex* sfxIdx = group->getProj().getSFXGroupIndex(groupId);
|
||||||
|
if (sfxIdx)
|
||||||
|
{
|
||||||
|
std::list<ObjToken<Sequencer>>::iterator ret = _allocateSequencer(*group, groupId, songId, smx);
|
||||||
|
if (!*ret)
|
||||||
|
return {};
|
||||||
|
return *ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
/** Set total volume of engine */
|
/** Set total volume of engine */
|
||||||
void Engine::setVolume(float vol)
|
void Engine::setVolume(float vol)
|
||||||
{
|
{
|
||||||
|
|
|
@ -246,6 +246,7 @@ ObjToken<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
|
||||||
m_parent->m_audioGroup, m_parent->m_groupId, NativeSampleRate, true, false, m_parent->m_studio);
|
m_parent->m_audioGroup, m_parent->m_groupId, NativeSampleRate, true, false, m_parent->m_studio);
|
||||||
if (*ret)
|
if (*ret)
|
||||||
{
|
{
|
||||||
|
(*ret)->m_sequencer = m_parent;
|
||||||
m_chanVoxs[note] = *ret;
|
m_chanVoxs[note] = *ret;
|
||||||
(*ret)->installCtrlValues(m_ctrlVals);
|
(*ret)->installCtrlValues(m_ctrlVals);
|
||||||
|
|
||||||
|
@ -260,9 +261,9 @@ ObjToken<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
|
||||||
{
|
{
|
||||||
size_t lookupIdx = note % m_parent->m_sfxMappings.size();
|
size_t lookupIdx = note % m_parent->m_sfxMappings.size();
|
||||||
const SFXGroupIndex::SFXEntry* sfxEntry = m_parent->m_sfxMappings[lookupIdx];
|
const SFXGroupIndex::SFXEntry* sfxEntry = m_parent->m_sfxMappings[lookupIdx];
|
||||||
oid = sfxEntry->macro;
|
oid = sfxEntry->objId;
|
||||||
note = sfxEntry->defKey;
|
note = sfxEntry->defKey;
|
||||||
res = (*ret)->loadMacroObject(oid, 0, m_parent->m_ticksPerSec, note, velocity, m_ctrlVals[1]);
|
res = (*ret)->loadPageObject(oid, m_parent->m_ticksPerSec, note, velocity, m_ctrlVals[1]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -23,6 +23,7 @@ void Voice::_destroy()
|
||||||
m_studio.reset();
|
m_studio.reset();
|
||||||
m_backendVoice.reset();
|
m_backendVoice.reset();
|
||||||
m_curSample.reset();
|
m_curSample.reset();
|
||||||
|
m_sequencer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
Voice::~Voice()
|
Voice::~Voice()
|
||||||
|
|
Loading…
Reference in New Issue