All editors implemented

This commit is contained in:
Jack Andersen 2018-08-09 20:19:23 -10:00
parent eff832bb8c
commit d24e06f101
34 changed files with 2575 additions and 414 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,26 +1268,36 @@ 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;
}
setItemEditEnabled(m_projectModel->canEdit(selected.indexes().front()));
else
setItemEditEnabled(m_projectModel->canEdit(selected.indexes().front()));
}
void MainWindow::onTextSelect()

View File

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

View File

@ -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>&amp;Audio</string>
</property>
<addaction name="actionAuto_Play"/>
<addaction name="separator"/>
<addaction name="actionSelect_Output_Device"/>
</widget>
@ -450,17 +440,6 @@
<string>New &amp;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>&amp;Auto-Play</string>
</property>
</action>
<action name="actionSelect_Output_Device">
<property name="enabled">
<bool>false</bool>

View File

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

View File

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

View File

@ -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,92 +501,98 @@ 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)
amuse::AudioGroup& group = gn.m_it->second;
auto& songGroups = group.getProj().songGroups();
auto& sfxGroups = group.getProj().sfxGroups();
auto& soundMacros = group.getPool().soundMacros();
auto& tables = group.getPool().tables();
auto& keymaps = group.getPool().keymaps();
auto& layers = group.getPool().layers();
auto& samples = group.getSdir().sampleEntries();
gn.reserve(songGroups.size() + sfxGroups.size() + 4);
for (const auto& grp : SortUnorderedMap(songGroups))
gn.makeChild<SongGroupNode>(grp.first, grp.second.get());
for (const auto& grp : SortUnorderedMap(sfxGroups))
gn.makeChild<SoundGroupNode>(grp.first, grp.second.get());
{
it->second.setIdDatabases();
GroupNode& gn = m_root->makeChild<GroupNode>(it);
amuse::AudioGroup& group = it->second;
auto& songGroups = group.getProj().songGroups();
auto& sfxGroups = group.getProj().sfxGroups();
auto& soundMacros = group.getPool().soundMacros();
auto& tables = group.getPool().tables();
auto& keymaps = group.getPool().keymaps();
auto& layers = group.getPool().layers();
auto& samples = group.getSdir().sampleEntries();
gn.reserve(songGroups.size() + sfxGroups.size() + 4);
for (const auto& grp : SortUnorderedMap(songGroups))
gn.makeChild<SongGroupNode>(grp.first, grp.second.get());
for (const auto& grp : SortUnorderedMap(sfxGroups))
gn.makeChild<SoundGroupNode>(grp.first, grp.second.get());
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Sound Macros"), QIcon(":/icons/IconSoundMacro.svg"), INode::Type::SoundMacro);
col.reserve(soundMacros.size());
for (const auto& macro : SortUnorderedMap(soundMacros))
col.makeChild<SoundMacroNode>(macro.first, macro.second.get());
}
{
auto tablesSort = SortUnorderedMap(tables);
size_t ADSRCount = 0;
size_t curveCount = 0;
for (auto& t : tablesSort)
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Sound Macros"), QIcon(":/icons/IconSoundMacro.svg"), INode::Type::SoundMacro);
col.reserve(soundMacros.size());
for (const auto& macro : SortUnorderedMap(soundMacros))
col.makeChild<SoundMacroNode>(macro.first, macro.second.get());
amuse::ITable::Type tp = (*t.second.get())->Isa();
if (tp == amuse::ITable::Type::ADSR || tp == amuse::ITable::Type::ADSRDLS)
ADSRCount += 1;
else if (tp == amuse::ITable::Type::Curve)
curveCount += 1;
}
{
auto tablesSort = SortUnorderedMap(tables);
size_t ADSRCount = 0;
size_t curveCount = 0;
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("ADSRs"), QIcon(":/icons/IconADSR.svg"), INode::Type::ADSR);
col.reserve(ADSRCount);
for (auto& t : tablesSort)
{
amuse::ITable::Type tp = (*t.second.get())->Isa();
if (tp == amuse::ITable::Type::ADSR || tp == amuse::ITable::Type::ADSRDLS)
ADSRCount += 1;
else if (tp == amuse::ITable::Type::Curve)
curveCount += 1;
col.makeChild<ADSRNode>(t.first, t.second.get());
}
}
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Curves"), QIcon(":/icons/IconCurve.svg"), INode::Type::Curve);
col.reserve(curveCount);
for (auto& t : tablesSort)
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("ADSRs"), QIcon(":/icons/IconADSR.svg"), INode::Type::ADSR);
col.reserve(ADSRCount);
for (auto& t : tablesSort)
{
amuse::ITable::Type tp = (*t.second.get())->Isa();
if (tp == amuse::ITable::Type::ADSR || tp == amuse::ITable::Type::ADSRDLS)
col.makeChild<ADSRNode>(t.first, t.second.get());
}
}
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Curves"), QIcon(":/icons/IconCurve.svg"), INode::Type::Curve);
col.reserve(curveCount);
for (auto& t : tablesSort)
{
amuse::ITable::Type tp = (*t.second.get())->Isa();
if (tp == amuse::ITable::Type::Curve)
col.makeChild<CurveNode>(t.first, t.second.get());
}
amuse::ITable::Type tp = (*t.second.get())->Isa();
if (tp == amuse::ITable::Type::Curve)
col.makeChild<CurveNode>(t.first, t.second.get());
}
}
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Keymaps"), QIcon(":/icons/IconKeymap.svg"), INode::Type::Keymap);
col.reserve(keymaps.size());
for (auto& keymap : SortUnorderedMap(keymaps))
col.makeChild<KeymapNode>(keymap.first, keymap.second.get());
}
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Layers"), QIcon(":/icons/IconLayers.svg"), INode::Type::Layer);
col.reserve(layers.size());
for (auto& keymap : SortUnorderedMap(layers))
col.makeChild<LayersNode>(keymap.first, keymap.second.get());
}
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Samples"), QIcon(":/icons/IconSample.svg"), INode::Type::Sample);
col.reserve(samples.size());
for (auto& sample : SortUnorderedMap(samples))
col.makeChild<SampleNode>(sample.first, sample.second.get());
}
}
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Keymaps"), QIcon(":/icons/IconKeymap.svg"), INode::Type::Keymap);
col.reserve(keymaps.size());
for (auto& keymap : SortUnorderedMap(keymaps))
col.makeChild<KeymapNode>(keymap.first, keymap.second.get());
}
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Layers"), QIcon(":/icons/IconLayers.svg"), INode::Type::Layer);
col.reserve(layers.size());
for (auto& keymap : SortUnorderedMap(layers))
col.makeChild<LayersNode>(keymap.first, keymap.second.get());
}
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Samples"), QIcon(":/icons/IconSample.svg"), INode::Type::Sample);
col.reserve(samples.size());
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();
}

View File

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

View File

@ -1,5 +1,6 @@
#include "SongGroupEditor.hpp"
#include "MainWindow.hpp"
#include "amuse/SongConverter.hpp"
PageObjectDelegate::PageObjectDelegate(QObject* parent)
: QStyledItemDelegate(parent) {}
@ -55,7 +56,10 @@ void PageObjectDelegate::objIndexChanged()
void MIDIFileFieldWidget::buttonPressed()
{
m_dialog.setDirectory(QFileInfo(g_MainWindow->projectModel()->dir().absoluteFilePath(m_le.text())).path());
if (m_le.text().isEmpty())
m_dialog.setDirectory(g_MainWindow->projectModel()->dir());
else
m_dialog.setDirectory(QFileInfo(g_MainWindow->projectModel()->dir().absoluteFilePath(m_le.text())).path());
m_dialog.open(this, SLOT(fileDialogOpened(const QString&)));
}
@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1153,6 +1153,7 @@ struct SoundMacro
return;
std::swap(m_cmds[a], m_cmds[b]);
}
void buildFromPrototype(const SoundMacro& other);
};
template <typename T>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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())
{

View File

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

View File

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

View File

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

View File

@ -23,6 +23,7 @@ void Voice::_destroy()
m_studio.reset();
m_backendVoice.reset();
m_curSample.reset();
m_sequencer.reset();
}
Voice::~Voice()