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
|
||||
SampleEditor.hpp
|
||||
SoundGroupEditor.hpp
|
||||
SongGroupEditor.hpp)
|
||||
SongGroupEditor.hpp
|
||||
NewSoundMacroDialog.hpp)
|
||||
|
||||
add_executable(amuse-gui WIN32 MACOSX_BUNDLE
|
||||
Common.hpp Common.cpp
|
||||
|
@ -64,6 +65,7 @@ add_executable(amuse-gui WIN32 MACOSX_BUNDLE
|
|||
SampleEditor.hpp SampleEditor.cpp
|
||||
SoundGroupEditor.hpp SoundGroupEditor.cpp
|
||||
SongGroupEditor.hpp SongGroupEditor.cpp
|
||||
NewSoundMacroDialog.hpp NewSoundMacroDialog.cpp
|
||||
MIDIReader.hpp MIDIReader.cpp
|
||||
resources/resources.qrc qrc_resources.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 */
|
||||
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
|
||||
|
|
|
@ -59,3 +59,21 @@ void FieldPageObjectNode::setGroup(ProjectModel::GroupNode* group)
|
|||
setModel(model->getPageObjectProxy());
|
||||
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 <QWheelEvent>
|
||||
#include <QItemEditorFactory>
|
||||
#include <QToolButton>
|
||||
#include <QAction>
|
||||
#include "ProjectModel.hpp"
|
||||
|
||||
class EditorWidget : public QWidget
|
||||
|
@ -18,8 +20,9 @@ public:
|
|||
virtual bool valid() const { return true; }
|
||||
virtual void unloadData() {}
|
||||
virtual ProjectModel::INode* currentNode() const { return nullptr; }
|
||||
public slots:
|
||||
virtual void setEditorEnabled(bool en) { setEnabled(en); }
|
||||
virtual bool isItemEditEnabled() const { return false; }
|
||||
public slots:
|
||||
virtual void itemCutAction() {}
|
||||
virtual void itemCopyAction() {}
|
||||
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
|
||||
|
|
|
@ -390,8 +390,7 @@ ProjectModel::INode* LayersEditor::currentNode() const
|
|||
void LayersEditor::resizeEvent(QResizeEvent* ev)
|
||||
{
|
||||
m_tableView.setGeometry(QRect({}, ev->size()));
|
||||
m_addButton.move(0, ev->size().height() - 32);
|
||||
m_removeButton.move(32, ev->size().height() - 32);
|
||||
m_addRemoveButtons.move(0, ev->size().height() - 32);
|
||||
}
|
||||
|
||||
void LayersEditor::doAdd()
|
||||
|
@ -403,9 +402,9 @@ void LayersEditor::doAdd()
|
|||
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();
|
||||
}
|
||||
|
||||
|
@ -435,23 +434,14 @@ void LayersEditor::itemDeleteAction()
|
|||
}
|
||||
|
||||
LayersEditor::LayersEditor(QWidget* parent)
|
||||
: EditorWidget(parent), m_model(this), m_tableView(this),
|
||||
m_addAction(tr("Add Row")), m_addButton(this), m_removeAction(tr("Remove Row")), m_removeButton(this)
|
||||
: EditorWidget(parent), m_model(this), m_tableView(this), m_addRemoveButtons(this)
|
||||
{
|
||||
m_tableView.setModel(&m_model);
|
||||
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_addAction.setToolTip(tr("Add new layer mapping"));
|
||||
m_addButton.setDefaultAction(&m_addAction);
|
||||
m_addButton.setFixedSize(32, 32);
|
||||
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);
|
||||
m_addRemoveButtons.addAction()->setToolTip(tr("Add new layer mapping"));
|
||||
connect(m_addRemoveButtons.addAction(), SIGNAL(triggered(bool)), this, SLOT(doAdd()));
|
||||
m_addRemoveButtons.removeAction()->setToolTip(tr("Remove selected layer mappings"));
|
||||
connect(m_addRemoveButtons.removeAction(), SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));
|
||||
}
|
||||
|
|
|
@ -66,21 +66,18 @@ class LayersEditor : public EditorWidget
|
|||
Q_OBJECT
|
||||
LayersModel m_model;
|
||||
LayersTableView m_tableView;
|
||||
QAction m_addAction;
|
||||
QToolButton m_addButton;
|
||||
QAction m_removeAction;
|
||||
QToolButton m_removeButton;
|
||||
AddRemoveButtons m_addRemoveButtons;
|
||||
public:
|
||||
explicit LayersEditor(QWidget* parent = Q_NULLPTR);
|
||||
bool loadData(ProjectModel::LayersNode* node);
|
||||
void unloadData();
|
||||
ProjectModel::INode* currentNode() const;
|
||||
void resizeEvent(QResizeEvent* ev);
|
||||
bool isItemEditEnabled() const;
|
||||
public slots:
|
||||
void doAdd();
|
||||
void doSelectionChanged(const QItemSelection& selected);
|
||||
void doSelectionChanged();
|
||||
|
||||
bool isItemEditEnabled() const;
|
||||
void itemCutAction();
|
||||
void itemCopyAction();
|
||||
void itemPasteAction();
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "KeymapEditor.hpp"
|
||||
#include "LayersEditor.hpp"
|
||||
#include "SampleEditor.hpp"
|
||||
#include "NewSoundMacroDialog.hpp"
|
||||
|
||||
MainWindow::MainWindow(QWidget* parent)
|
||||
: QMainWindow(parent),
|
||||
|
@ -258,6 +259,7 @@ bool MainWindow::setProjectPath(const QString& path)
|
|||
m_ui.actionRevert_Project->setEnabled(true);
|
||||
m_ui.actionReload_Sample_Data->setEnabled(true);
|
||||
m_ui.actionExport_GameCube_Groups->setEnabled(true);
|
||||
m_ui.actionNew_Subproject->setEnabled(true);
|
||||
setWindowFilePath(path);
|
||||
updateWindowTitle();
|
||||
updateFocus();
|
||||
|
@ -322,14 +324,16 @@ void MainWindow::timerEvent(QTimerEvent* ev)
|
|||
if (m_engine->getActiveVoices().empty() && m_uiDisabled)
|
||||
{
|
||||
m_ui.projectOutline->setEnabled(true);
|
||||
m_ui.editorContents->setEnabled(true);
|
||||
if (EditorWidget* w = getEditorWidget())
|
||||
w->setEditorEnabled(true);
|
||||
m_ui.menubar->setEnabled(true);
|
||||
m_uiDisabled = false;
|
||||
}
|
||||
else if (!m_engine->getActiveVoices().empty() && !m_uiDisabled)
|
||||
{
|
||||
m_ui.projectOutline->setEnabled(false);
|
||||
m_ui.editorContents->setEnabled(false);
|
||||
if (EditorWidget* w = getEditorWidget())
|
||||
w->setEditorEnabled(false);
|
||||
m_ui.menubar->setEnabled(false);
|
||||
m_uiDisabled = true;
|
||||
}
|
||||
|
@ -342,6 +346,21 @@ void MainWindow::timerEvent(QTimerEvent* ev)
|
|||
else
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
m_undoStack->push(cmd);
|
||||
|
@ -858,44 +898,217 @@ bool TreeDelegate::editorEvent(QEvent* event,
|
|||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
|
@ -1055,25 +1268,35 @@ void MainWindow::setItemEditEnabled(bool 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()
|
||||
{
|
||||
if (!m_projectModel)
|
||||
return false;
|
||||
QModelIndexList indexes = m_ui.projectOutline->selectionModel()->selectedIndexes();
|
||||
if (indexes.empty())
|
||||
QModelIndex curIndex = m_ui.projectOutline->selectionModel()->currentIndex();
|
||||
if (!curIndex.isValid())
|
||||
return false;
|
||||
return m_projectModel->canEdit(indexes.front());
|
||||
return m_projectModel->canEdit(curIndex);
|
||||
}
|
||||
|
||||
void MainWindow::onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
|
||||
{
|
||||
if (!m_projectModel)
|
||||
return;
|
||||
setItemNewEnabled(m_ui.projectOutline->selectionModel()->currentIndex().isValid());
|
||||
if (selected.indexes().empty())
|
||||
{
|
||||
setItemEditEnabled(false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
setItemEditEnabled(m_projectModel->canEdit(selected.indexes().front()));
|
||||
}
|
||||
|
||||
|
|
|
@ -146,10 +146,19 @@ public:
|
|||
ProjectModel::INode* getEditorNode() const;
|
||||
EditorWidget* getEditorWidget() const;
|
||||
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 updateFocus();
|
||||
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; }
|
||||
|
||||
public slots:
|
||||
|
@ -193,6 +202,7 @@ public slots:
|
|||
void onFocusChanged(QWidget* old, QWidget* now);
|
||||
void outlineItemActivated(const QModelIndex& index);
|
||||
void setItemEditEnabled(bool enabled);
|
||||
void setItemNewEnabled(bool enabled);
|
||||
bool canEditOutline();
|
||||
void onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
|
||||
void onTextSelect();
|
||||
|
|
|
@ -77,16 +77,7 @@
|
|||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<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="QStackedWidget" name="editorContents"/>
|
||||
<widget class="QWidget" name="keyboard" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
|
@ -232,7 +223,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1501</width>
|
||||
<height>80</height>
|
||||
<height>85</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -269,7 +260,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1360</width>
|
||||
<height>34</height>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
|
@ -313,7 +304,6 @@
|
|||
<property name="title">
|
||||
<string>&Audio</string>
|
||||
</property>
|
||||
<addaction name="actionAuto_Play"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSelect_Output_Device"/>
|
||||
</widget>
|
||||
|
@ -450,17 +440,6 @@
|
|||
<string>New &Layers</string>
|
||||
</property>
|
||||
</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">
|
||||
<property name="enabled">
|
||||
<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");
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
m_projectDatabase = amuse::ProjectDatabase();
|
||||
m_groups.clear();
|
||||
m_sorted.clear();
|
||||
m_midiFiles.clear();
|
||||
|
||||
m_needsReset = true;
|
||||
|
@ -473,17 +501,9 @@ bool ProjectModel::saveToFile(UIMessenger& messenger)
|
|||
return true;
|
||||
}
|
||||
|
||||
void ProjectModel::_resetModelData()
|
||||
void ProjectModel::_buildGroupNode(GroupNode& gn)
|
||||
{
|
||||
beginResetModel();
|
||||
m_projectDatabase.setIdDatabases();
|
||||
m_root = amuse::MakeObj<RootNode>();
|
||||
m_root->reserve(m_groups.size());
|
||||
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it)
|
||||
{
|
||||
it->second.setIdDatabases();
|
||||
GroupNode& gn = m_root->makeChild<GroupNode>(it);
|
||||
amuse::AudioGroup& group = it->second;
|
||||
amuse::AudioGroup& group = gn.m_it->second;
|
||||
auto& songGroups = group.getProj().songGroups();
|
||||
auto& sfxGroups = group.getProj().sfxGroups();
|
||||
auto& soundMacros = group.getPool().soundMacros();
|
||||
|
@ -559,6 +579,20 @@ void ProjectModel::_resetModelData()
|
|||
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();
|
||||
}
|
||||
|
@ -704,32 +738,41 @@ class DeleteNodeUndoCommand : public QUndoCommand
|
|||
{
|
||||
QModelIndex m_deleteIdx;
|
||||
amuse::ObjToken<ProjectModel::INode> m_node;
|
||||
ProjectModel::NameUndoRegistry m_nameReg;
|
||||
public:
|
||||
DeleteNodeUndoCommand(const QModelIndex& index)
|
||||
: QUndoCommand(ProjectModel::tr("Delete %1").arg(index.data().toString())), m_deleteIdx(index) {}
|
||||
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_nameReg.clear();
|
||||
}
|
||||
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());
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
node->unregisterNames(nameReg);
|
||||
return true;
|
||||
});
|
||||
beginRemoveRows(index.parent(), index.row(), index.row());
|
||||
|
@ -745,6 +788,186 @@ void ProjectModel::del(const QModelIndex& 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* ret = nullptr;
|
||||
|
@ -797,3 +1020,10 @@ void ProjectModel::setMIDIPathOfSong(amuse::SongId id, const QString& 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 <map>
|
||||
#include "Common.hpp"
|
||||
#include "NewSoundMacroDialog.hpp"
|
||||
#include "amuse/AudioGroup.hpp"
|
||||
#include "amuse/AudioGroupData.hpp"
|
||||
#include "amuse/AudioGroupProject.hpp"
|
||||
|
@ -52,13 +53,71 @@ public:
|
|||
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:
|
||||
QDir m_dir;
|
||||
NullItemProxyModel m_nullProxy;
|
||||
PageObjectProxyModel m_pageObjectProxy;
|
||||
|
||||
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;
|
||||
|
||||
public:
|
||||
|
@ -133,6 +192,13 @@ public:
|
|||
m_nullChild->m_row = int(m_children.size());
|
||||
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)
|
||||
{
|
||||
|
@ -154,6 +220,9 @@ public:
|
|||
virtual QString text() const = 0;
|
||||
virtual QIcon icon() const = 0;
|
||||
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
|
||||
{
|
||||
|
@ -176,8 +245,8 @@ public:
|
|||
struct BasePoolObjectNode;
|
||||
struct GroupNode : INode
|
||||
{
|
||||
std::map<QString, amuse::AudioGroupDatabase>::iterator m_it;
|
||||
GroupNode(INode* parent, int row, std::map<QString, amuse::AudioGroupDatabase>::iterator it)
|
||||
std::unordered_map<QString, amuse::AudioGroupDatabase>::iterator m_it;
|
||||
GroupNode(INode* parent, int row, std::unordered_map<QString, amuse::AudioGroupDatabase>::iterator it)
|
||||
: INode(parent, row), m_it(it) {}
|
||||
|
||||
static QIcon Icon;
|
||||
|
@ -201,6 +270,19 @@ public:
|
|||
Type type() const { return Type::SongGroup; }
|
||||
QString text() const { return m_name; }
|
||||
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
|
||||
{
|
||||
|
@ -214,6 +296,19 @@ public:
|
|||
Type type() const { return Type::SoundGroup; }
|
||||
QString text() const { return m_name; }
|
||||
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
|
||||
{
|
||||
|
@ -252,6 +347,15 @@ public:
|
|||
: BasePoolObjectNode(parent, row, id, ID::CurNameDB->resolveNameFromId(id).data()), m_obj(obj) {}
|
||||
|
||||
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 ADSRNode = PoolObjectNode<amuse::TableId, std::unique_ptr<amuse::ITable>, INode::Type::ADSR>;
|
||||
|
@ -263,6 +367,7 @@ public:
|
|||
amuse::ObjToken<RootNode> m_root;
|
||||
|
||||
bool m_needsReset = false;
|
||||
void _buildGroupNode(GroupNode& gn);
|
||||
void _resetModelData();
|
||||
|
||||
public:
|
||||
|
@ -289,11 +394,21 @@ public:
|
|||
INode* node(const QModelIndex& index) const;
|
||||
GroupNode* getGroupNode(INode* node) const;
|
||||
bool canEdit(const QModelIndex& index) const;
|
||||
void _undoDel(const QModelIndex& index, amuse::ObjToken<ProjectModel::INode> node);
|
||||
amuse::ObjToken<ProjectModel::INode> _redoDel(const QModelIndex& index);
|
||||
void _undoDel(const QModelIndex& index, amuse::ObjToken<ProjectModel::INode> node, const NameUndoRegistry& nameReg);
|
||||
amuse::ObjToken<ProjectModel::INode> _redoDel(const QModelIndex& index, NameUndoRegistry& nameReg);
|
||||
void del(const QModelIndex& index);
|
||||
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; }
|
||||
QString path() const { return m_dir.path(); }
|
||||
NullItemProxyModel* getNullProxy() { return &m_nullProxy; }
|
||||
|
@ -303,6 +418,8 @@ public:
|
|||
GroupNode* getGroupOfSong(amuse::SongId id) const;
|
||||
QString getMIDIPathOfSong(amuse::SongId id) const;
|
||||
void setMIDIPathOfSong(amuse::SongId id, const QString& path);
|
||||
|
||||
void setIdDatabases(INode* context) const;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "SongGroupEditor.hpp"
|
||||
#include "MainWindow.hpp"
|
||||
#include "amuse/SongConverter.hpp"
|
||||
|
||||
PageObjectDelegate::PageObjectDelegate(QObject* parent)
|
||||
: QStyledItemDelegate(parent) {}
|
||||
|
@ -55,6 +56,9 @@ void PageObjectDelegate::objIndexChanged()
|
|||
|
||||
void MIDIFileFieldWidget::buttonPressed()
|
||||
{
|
||||
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&)));
|
||||
}
|
||||
|
@ -721,15 +725,15 @@ PageTableView::PageTableView(QWidget* parent)
|
|||
void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* table)
|
||||
{
|
||||
{
|
||||
m_listView.setModel(list);
|
||||
auto hheader = m_listView.horizontalHeader();
|
||||
m_listView->setModel(list);
|
||||
auto hheader = m_listView->horizontalHeader();
|
||||
hheader->setMinimumSectionSize(200);
|
||||
hheader->resizeSection(0, 200);
|
||||
hheader->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||
}
|
||||
{
|
||||
m_tableView.setModel(table);
|
||||
auto hheader = m_tableView.horizontalHeader();
|
||||
m_tableView->setModel(table);
|
||||
auto hheader = m_tableView->horizontalHeader();
|
||||
hheader->setSectionResizeMode(QHeaderView::Stretch);
|
||||
}
|
||||
}
|
||||
|
@ -737,8 +741,8 @@ void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* tabl
|
|||
void SetupTableView::deleteSelection()
|
||||
{
|
||||
QModelIndexList list;
|
||||
while (!(list = m_listView.selectionModel()->selectedRows()).isEmpty())
|
||||
m_listView.model()->removeRow(list.back().row());
|
||||
while (!(list = m_listView->selectionModel()->selectedRows()).isEmpty())
|
||||
m_listView->model()->removeRow(list.back().row());
|
||||
}
|
||||
|
||||
void SetupTableView::showEvent(QShowEvent* event)
|
||||
|
@ -747,30 +751,30 @@ void SetupTableView::showEvent(QShowEvent* event)
|
|||
}
|
||||
|
||||
SetupTableView::SetupTableView(QWidget* parent)
|
||||
: QSplitter(parent), m_listView(this), m_tableView(this)
|
||||
: QSplitter(parent), m_listView(new QTableView), m_tableView(new QTableView)
|
||||
{
|
||||
setChildrenCollapsible(false);
|
||||
setStretchFactor(0, 1);
|
||||
setStretchFactor(1, 0);
|
||||
|
||||
addWidget(&m_listView);
|
||||
addWidget(&m_tableView);
|
||||
addWidget(m_listView);
|
||||
addWidget(m_tableView);
|
||||
|
||||
m_listView.setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_listView.setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
m_listView.setGridStyle(Qt::NoPen);
|
||||
m_listView.setItemDelegateForColumn(1, &m_midiDelegate);
|
||||
m_listView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
m_listView->setGridStyle(Qt::NoPen);
|
||||
m_listView->setItemDelegateForColumn(1, &m_midiDelegate);
|
||||
|
||||
m_tableView.setSelectionMode(QAbstractItemView::NoSelection);
|
||||
m_tableView.setGridStyle(Qt::NoPen);
|
||||
m_tableView->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
m_tableView->setGridStyle(Qt::NoPen);
|
||||
|
||||
m_127Delegate.setItemEditorFactory(&m_127Factory);
|
||||
|
||||
m_tableView.setItemDelegateForColumn(0, &m_127Delegate);
|
||||
m_tableView.setItemDelegateForColumn(1, &m_127Delegate);
|
||||
m_tableView.setItemDelegateForColumn(2, &m_127Delegate);
|
||||
m_tableView.setItemDelegateForColumn(3, &m_127Delegate);
|
||||
m_tableView.setItemDelegateForColumn(4, &m_127Delegate);
|
||||
m_tableView->setItemDelegateForColumn(0, &m_127Delegate);
|
||||
m_tableView->setItemDelegateForColumn(1, &m_127Delegate);
|
||||
m_tableView->setItemDelegateForColumn(2, &m_127Delegate);
|
||||
m_tableView->setItemDelegateForColumn(3, &m_127Delegate);
|
||||
m_tableView->setItemDelegateForColumn(4, &m_127Delegate);
|
||||
}
|
||||
|
||||
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)
|
||||
: QTabBar(parent), m_style(style())
|
||||
: QTabBar(parent), m_style(new ColoredTabBarStyle(style()))
|
||||
{
|
||||
setDrawBase(false);
|
||||
setStyle(&m_style);
|
||||
setStyle(m_style);
|
||||
}
|
||||
|
||||
ColoredTabWidget::ColoredTabWidget(QWidget* parent)
|
||||
|
@ -809,6 +813,58 @@ ColoredTabWidget::ColoredTabWidget(QWidget* parent)
|
|||
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)
|
||||
{
|
||||
m_normPages.loadData(node);
|
||||
|
@ -834,8 +890,7 @@ ProjectModel::INode* SongGroupEditor::currentNode() const
|
|||
void SongGroupEditor::resizeEvent(QResizeEvent* ev)
|
||||
{
|
||||
m_tabs.setGeometry(QRect({}, ev->size()));
|
||||
m_addButton.move(0, ev->size().height() - 32);
|
||||
m_removeButton.move(32, ev->size().height() - 32);
|
||||
m_addRemoveButtons.move(0, ev->size().height() - 32);
|
||||
}
|
||||
|
||||
void SongGroupEditor::doAdd()
|
||||
|
@ -848,34 +903,37 @@ void SongGroupEditor::doAdd()
|
|||
else
|
||||
table->model()->insertRow(idx.row());
|
||||
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()))
|
||||
{
|
||||
QModelIndex idx = table->m_listView.selectionModel()->currentIndex();
|
||||
QModelIndex idx = table->m_listView->selectionModel()->currentIndex();
|
||||
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
|
||||
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();
|
||||
}
|
||||
|
||||
void SongGroupEditor::doSetupSelectionChanged(const QItemSelection& selected)
|
||||
void SongGroupEditor::doSetupSelectionChanged()
|
||||
{
|
||||
doSelectionChanged(selected);
|
||||
if (selected.indexes().isEmpty() || m_setupList.m_sorted.empty())
|
||||
doSelectionChanged();
|
||||
if (m_setupTable->m_listView->selectionModel()->selectedRows().isEmpty() || m_setupList.m_sorted.empty())
|
||||
{
|
||||
m_setup.unloadData();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -884,13 +942,13 @@ void SongGroupEditor::currentTabChanged(int idx)
|
|||
{
|
||||
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
|
||||
{
|
||||
m_addAction.setDisabled(table->model()->rowCount() >= 128);
|
||||
doSelectionChanged(table->selectionModel()->selection());
|
||||
m_addRemoveButtons.addAction()->setDisabled(table->model()->rowCount() >= 128);
|
||||
doSelectionChanged();
|
||||
}
|
||||
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
|
||||
{
|
||||
m_addAction.setDisabled(false);
|
||||
doSelectionChanged(table->m_listView.selectionModel()->selection());
|
||||
m_addRemoveButtons.addAction()->setDisabled(false);
|
||||
doSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -912,12 +970,74 @@ void SongGroupEditor::modelAboutToBeReset()
|
|||
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
|
||||
{
|
||||
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
|
||||
return table->hasFocus() && !table->selectionModel()->selectedRows().isEmpty();
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -946,42 +1066,44 @@ void SongGroupEditor::itemDeleteAction()
|
|||
|
||||
SongGroupEditor::SongGroupEditor(QWidget* parent)
|
||||
: 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_addAction(tr("Add Row")), m_addButton(this), m_removeAction(tr("Remove Row")), m_removeButton(this)
|
||||
m_normTable(new PageTableView), m_drumTable(new PageTableView),
|
||||
m_setupTable(new SetupTableView), m_tabs(this), m_addRemoveButtons(this)
|
||||
{
|
||||
m_tabs.addTab(&m_normTable, tr("Normal Pages"));
|
||||
m_tabs.addTab(&m_drumTable, tr("Drum Pages"));
|
||||
m_tabs.addTab(&m_setupTable, tr("MIDI Setups"));
|
||||
m_tabs.addTab(m_normTable, tr("Normal Pages"));
|
||||
m_tabs.addTab(m_drumTable, tr("Drum Pages"));
|
||||
m_tabs.addTab(m_setupTable, tr("MIDI Setups"));
|
||||
|
||||
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)),
|
||||
this, SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
|
||||
connect(&m_setupList, SIGNAL(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_drumTable.setModel(&m_drumPages);
|
||||
m_setupTable.setModel(&m_setupList, &m_setup);
|
||||
connect(m_normTable.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
|
||||
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_addRemoveButtons.addAction()->setToolTip(tr("Add new page entry"));
|
||||
connect(m_addRemoveButtons.addAction(), SIGNAL(triggered(bool)), this, SLOT(doAdd()));
|
||||
m_addRemoveButtons.removeAction()->setToolTip(tr("Remove selected page entries"));
|
||||
connect(m_addRemoveButtons.removeAction(), SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));
|
||||
|
||||
m_tabs.setCurrentIndex(0);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <QPushButton>
|
||||
#include <QFileDialog>
|
||||
#include <QProxyStyle>
|
||||
#include "amuse/Sequencer.hpp"
|
||||
|
||||
class PageObjectDelegate : public QStyledItemDelegate
|
||||
{
|
||||
|
@ -176,8 +177,8 @@ class SetupTableView : public QSplitter
|
|||
{
|
||||
Q_OBJECT
|
||||
friend class SongGroupEditor;
|
||||
QTableView m_listView;
|
||||
QTableView m_tableView;
|
||||
QTableView* m_listView;
|
||||
QTableView* m_tableView;
|
||||
MIDIFileDelegate m_midiDelegate;
|
||||
RangedValueFactory<0, 127> m_127Factory;
|
||||
QStyledItemDelegate m_127Delegate;
|
||||
|
@ -199,7 +200,7 @@ public:
|
|||
class ColoredTabBar : public QTabBar
|
||||
{
|
||||
Q_OBJECT
|
||||
ColoredTabBarStyle m_style;
|
||||
ColoredTabBarStyle* m_style;
|
||||
public:
|
||||
explicit ColoredTabBar(QWidget* parent = Q_NULLPTR);
|
||||
};
|
||||
|
@ -212,6 +213,29 @@ public:
|
|||
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
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -219,34 +243,33 @@ class SongGroupEditor : public EditorWidget
|
|||
PageModel m_drumPages;
|
||||
SetupListModel m_setupList;
|
||||
SetupModel m_setup;
|
||||
PageTableView m_normTable;
|
||||
PageTableView m_drumTable;
|
||||
SetupTableView m_setupTable;
|
||||
PageTableView* m_normTable;
|
||||
PageTableView* m_drumTable;
|
||||
SetupTableView* m_setupTable;
|
||||
ColoredTabWidget m_tabs;
|
||||
QAction m_addAction;
|
||||
QToolButton m_addButton;
|
||||
QAction m_removeAction;
|
||||
QToolButton m_removeButton;
|
||||
AddRemoveButtons m_addRemoveButtons;
|
||||
public:
|
||||
explicit SongGroupEditor(QWidget* parent = Q_NULLPTR);
|
||||
bool loadData(ProjectModel::SongGroupNode* node);
|
||||
void unloadData();
|
||||
ProjectModel::INode* currentNode() const;
|
||||
void setEditorEnabled(bool en) {}
|
||||
void resizeEvent(QResizeEvent* ev);
|
||||
QTableView* getSetupListView() const { return m_setupTable->m_listView; }
|
||||
bool isItemEditEnabled() const;
|
||||
public slots:
|
||||
void doAdd();
|
||||
void doSelectionChanged(const QItemSelection& selected);
|
||||
void doSetupSelectionChanged(const QItemSelection& selected);
|
||||
void doSelectionChanged();
|
||||
void doSetupSelectionChanged();
|
||||
void currentTabChanged(int idx);
|
||||
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
|
||||
void modelAboutToBeReset();
|
||||
void setupDataChanged();
|
||||
|
||||
bool isItemEditEnabled() const;
|
||||
void itemCutAction();
|
||||
void itemCopyAction();
|
||||
void itemPasteAction();
|
||||
void itemDeleteAction();
|
||||
};
|
||||
|
||||
|
||||
#endif //AMUSE_SONG_GROUP_EDITOR_HPP
|
||||
|
|
|
@ -1,12 +1,494 @@
|
|||
#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;
|
||||
}
|
||||
|
||||
SoundGroupEditor::SoundGroupEditor(QWidget* parent)
|
||||
: EditorWidget(parent)
|
||||
QVariant SFXModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
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
|
||||
|
||||
#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
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
SFXModel m_sfxs;
|
||||
SFXTableView* m_sfxTable;
|
||||
AddRemoveButtons m_addRemoveButtons;
|
||||
public:
|
||||
explicit SoundGroupEditor(QWidget* parent = Q_NULLPTR);
|
||||
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
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
viewBox="0 0 4.2333332 4.2333335"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91+devel+osxmenu r12922"
|
||||
sodipodi:docname="NewSoundMacro.svg">
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||
sodipodi:docname="IconNewSoundMacro.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
|
@ -26,16 +26,16 @@
|
|||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="30.33"
|
||||
inkscape:cx="8.5002398"
|
||||
inkscape:cy="8.6348884"
|
||||
inkscape:cx="2.9941402"
|
||||
inkscape:cy="4.6784097"
|
||||
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="23"
|
||||
inkscape:window-x="606"
|
||||
inkscape:window-y="435"
|
||||
inkscape:window-maximized="0"
|
||||
showborder="false"
|
||||
objecttolerance="4"
|
||||
|
@ -45,7 +45,7 @@
|
|||
type="xygrid"
|
||||
id="grid4173"
|
||||
empspacing="1"
|
||||
visible="false" />
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
|
@ -66,14 +66,14 @@
|
|||
transform="translate(0,-292.76665)">
|
||||
<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"
|
||||
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"
|
||||
id="rect5899"
|
||||
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-3"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sccssss" />
|
||||
<path
|
||||
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"
|
||||
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" />
|
||||
</g>
|
||||
</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"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91+devel+osxmenu r12922"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||
sodipodi:docname="IconSoundMacro.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
|
@ -25,9 +25,9 @@
|
|||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="30.33"
|
||||
inkscape:cx="8.5002398"
|
||||
inkscape:cy="8.6348884"
|
||||
inkscape:zoom="49.27"
|
||||
inkscape:cx="6.2572034"
|
||||
inkscape:cy="5.0825963"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
|
@ -35,7 +35,7 @@
|
|||
inkscape:window-width="1260"
|
||||
inkscape:window-height="787"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-y="40"
|
||||
inkscape:window-maximized="0"
|
||||
showborder="false"
|
||||
objecttolerance="4"
|
||||
|
@ -45,7 +45,7 @@
|
|||
type="xygrid"
|
||||
id="grid4173"
|
||||
empspacing="1"
|
||||
visible="false" />
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
|
@ -66,7 +66,7 @@
|
|||
transform="translate(0,-292.76665)">
|
||||
<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"
|
||||
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"
|
||||
inkscape:connector-curvature="0"
|
||||
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>IconAdd.svg</file>
|
||||
<file>IconRemove.svg</file>
|
||||
<file>IconStop.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/bg">
|
||||
<file>FaceGrey.svg</file>
|
||||
|
|
|
@ -1153,6 +1153,7 @@ struct SoundMacro
|
|||
return;
|
||||
std::swap(m_cmds[a], m_cmds[b]);
|
||||
}
|
||||
void buildFromPrototype(const SoundMacro& other);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
|
|
@ -135,7 +135,7 @@ struct SFXGroupIndex : AudioGroupIndex
|
|||
{
|
||||
AT_DECL_DNA
|
||||
SFXIdDNA<DNAEn> sfxId;
|
||||
SoundMacroIdDNA<DNAEn> macro;
|
||||
PageObjectIdDNA<DNAEn> objId;
|
||||
Value<atUint8> priority;
|
||||
Value<atUint8> maxVoices;
|
||||
Value<atUint8> defVel;
|
||||
|
@ -146,18 +146,18 @@ struct SFXGroupIndex : AudioGroupIndex
|
|||
struct SFXEntry : BigDNA
|
||||
{
|
||||
AT_DECL_DNA_YAML
|
||||
SoundMacroIdDNA<athena::Big> macro;
|
||||
Value<atUint8> priority;
|
||||
Value<atUint8> maxVoices;
|
||||
Value<atUint8> defVel;
|
||||
Value<atUint8> panning;
|
||||
Value<atUint8> defKey;
|
||||
PageObjectIdDNA<athena::Big> objId;
|
||||
Value<atUint8> priority = 0;
|
||||
Value<atUint8> maxVoices = 255;
|
||||
Value<atUint8> defVel = 127;
|
||||
Value<atUint8> panning = 64;
|
||||
Value<atUint8> defKey = 60;
|
||||
|
||||
SFXEntry() = default;
|
||||
|
||||
template <athena::Endian DNAE>
|
||||
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) {}
|
||||
|
||||
template <athena::Endian DNAEn>
|
||||
|
@ -165,7 +165,7 @@ struct SFXGroupIndex : AudioGroupIndex
|
|||
{
|
||||
SFXEntryDNA<DNAEn> ret;
|
||||
ret.sfxId.id = id;
|
||||
ret.macro = macro;
|
||||
ret.objId = objId;
|
||||
ret.priority = priority;
|
||||
ret.maxVoices = maxVoices;
|
||||
ret.defVel = defVel;
|
||||
|
|
|
@ -617,6 +617,7 @@ struct NameDB
|
|||
|
||||
ObjectId generateId(Type tp) const;
|
||||
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 resolveNameFromId(ObjectId id) const;
|
||||
ObjectId resolveIdFromName(std::string_view str) const;
|
||||
|
|
|
@ -48,20 +48,20 @@ class Engine
|
|||
std::list<ObjToken<Sequencer>> m_activeSequencers;
|
||||
bool m_defaultStudioReady = false;
|
||||
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;
|
||||
int m_nextVid = 0;
|
||||
float m_masterVolume = 1.f;
|
||||
AudioChannelSet m_channelSet = AudioChannelSet::Unknown;
|
||||
|
||||
AudioGroup* _addAudioGroup(const AudioGroupData& data, std::unique_ptr<AudioGroup>&& grp);
|
||||
std::pair<AudioGroup*, const SongGroupIndex*> _findSongGroup(int groupId) const;
|
||||
std::pair<AudioGroup*, const SFXGroupIndex*> _findSFXGroup(int groupId) const;
|
||||
std::pair<AudioGroup*, const SongGroupIndex*> _findSongGroup(GroupId 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);
|
||||
std::list<ObjToken<Sequencer>>::iterator _allocateSequencer(const AudioGroup& group, int groupId,
|
||||
int setupId, ObjToken<Studio> studio);
|
||||
std::list<ObjToken<Sequencer>>::iterator _allocateSequencer(const AudioGroup& group, GroupId groupId,
|
||||
SongId setupId, ObjToken<Studio> studio);
|
||||
ObjToken<Studio> _allocateStudio(bool mainOut);
|
||||
std::list<ObjToken<Voice>>::iterator _destroyVoice(std::list<ObjToken<Voice>>::iterator it);
|
||||
std::list<ObjToken<Sequencer>>::iterator
|
||||
|
@ -88,12 +88,19 @@ public:
|
|||
ObjToken<Studio> addStudio(bool mainOut);
|
||||
|
||||
/** Start soundFX playing from loaded audio groups */
|
||||
ObjToken<Voice> fxStart(int 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, ObjToken<Studio> smx);
|
||||
ObjToken<Voice> fxStart(SFXId sfxId, float vol, float pan)
|
||||
{
|
||||
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) */
|
||||
ObjToken<Voice> macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key,
|
||||
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 */
|
||||
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,
|
||||
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);
|
||||
}
|
||||
|
@ -138,12 +145,19 @@ public:
|
|||
void removeListener(Listener* listener);
|
||||
|
||||
/** Start song playing from loaded audio groups */
|
||||
ObjToken<Sequencer> seqPlay(int groupId, int 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, ObjToken<Studio> smx);
|
||||
ObjToken<Sequencer> seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData)
|
||||
{
|
||||
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 */
|
||||
void setVolume(float vol);
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ class Engine;
|
|||
class AudioGroup;
|
||||
|
||||
/** Common 'engine child' class */
|
||||
class Entity
|
||||
class Entity : public IObj
|
||||
{
|
||||
/* Only the Engine will manage Entity lifetimes,
|
||||
* 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 */
|
||||
bool m_emitter; /**< Voice is part of an Emitter */
|
||||
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 */
|
||||
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
|
||||
{
|
||||
template <class Tp, class R>
|
||||
|
@ -209,7 +218,7 @@ AudioGroupPool AudioGroupPool::CreateAudioGroupPool(SystemStringView groupPath)
|
|||
AudioGroupPool ret;
|
||||
SystemString poolPath(groupPath);
|
||||
poolPath += _S("/!pool.yaml");
|
||||
athena::io::FileReader fi(poolPath);
|
||||
athena::io::FileReader fi(poolPath, 32 * 1024, false);
|
||||
|
||||
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::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
|
||||
{
|
||||
auto search = m_soundMacros.find(id);
|
||||
|
@ -447,6 +463,11 @@ static SoundMacro::CmdOp _ReadCmdOp(SoundMacro::CmdOp& op)
|
|||
return op;
|
||||
}
|
||||
|
||||
static SoundMacro::CmdOp _ReadCmdOp(const SoundMacro::ICmd& op)
|
||||
{
|
||||
return op.Isa();
|
||||
}
|
||||
|
||||
template <class Op, class O, class... _Args>
|
||||
O SoundMacro::CmdDo(_Args&&... args)
|
||||
{
|
||||
|
|
|
@ -318,7 +318,7 @@ AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView gr
|
|||
AudioGroupProject ret;
|
||||
SystemString projPath(groupPath);
|
||||
projPath += _S("/!project.yaml");
|
||||
athena::io::FileReader fi(projPath);
|
||||
athena::io::FileReader fi(projPath, 32 * 1024, false);
|
||||
|
||||
if (!fi.hasError())
|
||||
{
|
||||
|
|
|
@ -316,6 +316,11 @@ std::string NameDB::generateName(ObjectId id, Type tp)
|
|||
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)
|
||||
{
|
||||
m_stringToId[std::string(str)] = id;
|
||||
|
|
|
@ -35,7 +35,7 @@ Engine::Engine(IBackendVoiceAllocator& backend, AmplitudeMode ampMode)
|
|||
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)
|
||||
{
|
||||
|
@ -46,7 +46,7 @@ std::pair<AudioGroup*, const SongGroupIndex*> Engine::_findSongGroup(int groupId
|
|||
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)
|
||||
{
|
||||
|
@ -57,7 +57,7 @@ std::pair<AudioGroup*, const SFXGroupIndex*> Engine::_findSFXGroup(int groupId)
|
|||
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,
|
||||
ObjToken<Studio> studio)
|
||||
{
|
||||
|
@ -70,8 +70,8 @@ std::list<ObjToken<Voice>>::iterator Engine::_allocateVoice(const AudioGroup& gr
|
|||
return it;
|
||||
}
|
||||
|
||||
std::list<ObjToken<Sequencer>>::iterator Engine::_allocateSequencer(const AudioGroup& group, int groupId,
|
||||
int setupId, ObjToken<Studio> studio)
|
||||
std::list<ObjToken<Sequencer>>::iterator Engine::_allocateSequencer(const AudioGroup& group, GroupId groupId,
|
||||
SongId setupId, ObjToken<Studio> studio)
|
||||
{
|
||||
const SongGroupIndex* songGroup = group.getProj().getSongGroupIndex(groupId);
|
||||
if (songGroup)
|
||||
|
@ -265,7 +265,7 @@ void Engine::removeAudioGroup(const AudioGroupData& data)
|
|||
ObjToken<Studio> Engine::addStudio(bool mainOut) { return _allocateStudio(mainOut); }
|
||||
|
||||
/** 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);
|
||||
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 =
|
||||
_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);
|
||||
return {};
|
||||
}
|
||||
|
||||
(*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;
|
||||
}
|
||||
|
||||
/** 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) */
|
||||
ObjToken<Voice> Engine::macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key, uint8_t vel,
|
||||
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 */
|
||||
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);
|
||||
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 =
|
||||
_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);
|
||||
return {};
|
||||
|
@ -409,7 +441,7 @@ void Engine::removeListener(Listener* listener)
|
|||
}
|
||||
|
||||
/** 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);
|
||||
if (songGrp.second)
|
||||
|
@ -435,6 +467,33 @@ ObjToken<Sequencer> Engine::seqPlay(int groupId, int songId, const unsigned char
|
|||
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 */
|
||||
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);
|
||||
if (*ret)
|
||||
{
|
||||
(*ret)->m_sequencer = m_parent;
|
||||
m_chanVoxs[note] = *ret;
|
||||
(*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();
|
||||
const SFXGroupIndex::SFXEntry* sfxEntry = m_parent->m_sfxMappings[lookupIdx];
|
||||
oid = sfxEntry->macro;
|
||||
oid = sfxEntry->objId;
|
||||
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
|
||||
return {};
|
||||
|
|
|
@ -23,6 +23,7 @@ void Voice::_destroy()
|
|||
m_studio.reset();
|
||||
m_backendVoice.reset();
|
||||
m_curSample.reset();
|
||||
m_sequencer.reset();
|
||||
}
|
||||
|
||||
Voice::~Voice()
|
||||
|
|
Loading…
Reference in New Issue