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 LayersEditor.hpp
SampleEditor.hpp SampleEditor.hpp
SoundGroupEditor.hpp SoundGroupEditor.hpp
SongGroupEditor.hpp) SongGroupEditor.hpp
NewSoundMacroDialog.hpp)
add_executable(amuse-gui WIN32 MACOSX_BUNDLE add_executable(amuse-gui WIN32 MACOSX_BUNDLE
Common.hpp Common.cpp Common.hpp Common.cpp
@ -64,6 +65,7 @@ add_executable(amuse-gui WIN32 MACOSX_BUNDLE
SampleEditor.hpp SampleEditor.cpp SampleEditor.hpp SampleEditor.cpp
SoundGroupEditor.hpp SoundGroupEditor.cpp SoundGroupEditor.hpp SoundGroupEditor.cpp
SongGroupEditor.hpp SongGroupEditor.cpp SongGroupEditor.hpp SongGroupEditor.cpp
NewSoundMacroDialog.hpp NewSoundMacroDialog.cpp
MIDIReader.hpp MIDIReader.cpp MIDIReader.hpp MIDIReader.cpp
resources/resources.qrc qrc_resources.cpp resources/resources.qrc qrc_resources.cpp
${QM_FILES} qrc_translation_res.cpp ${QM_FILES} qrc_translation_res.cpp

View File

@ -47,4 +47,15 @@ static QLatin1String StringViewToQString(std::string_view sv)
/* Used for generating transform matrices to map SVG coordinate space */ /* Used for generating transform matrices to map SVG coordinate space */
QTransform RectToRect(const QRectF& from, const QRectF& to); QTransform RectToRect(const QRectF& from, const QRectF& to);
namespace std
{
template<> struct hash<QString>
{
std::size_t operator()(const QString& s) const noexcept
{
return qHash(s);
}
};
}
#endif //AMUSE_COMMON_HPP #endif //AMUSE_COMMON_HPP

View File

@ -59,3 +59,21 @@ void FieldPageObjectNode::setGroup(ProjectModel::GroupNode* group)
setModel(model->getPageObjectProxy()); setModel(model->getPageObjectProxy());
setRootModelIndex(model->getPageObjectProxy()->mapFromSource(model->index(group))); setRootModelIndex(model->getPageObjectProxy()->mapFromSource(model->index(group)));
} }
AddRemoveButtons::AddRemoveButtons(QWidget* parent)
: QWidget(parent), m_addAction(tr("Add Row")), m_addButton(this),
m_removeAction(tr("Remove Row")), m_removeButton(this)
{
setFixedSize(64, 32);
m_addAction.setIcon(QIcon(QStringLiteral(":/icons/IconAdd.svg")));
m_addButton.setDefaultAction(&m_addAction);
m_addButton.setFixedSize(32, 32);
m_addButton.move(0, 0);
m_removeAction.setIcon(QIcon(QStringLiteral(":/icons/IconRemove.svg")));
m_removeButton.setDefaultAction(&m_removeAction);
m_removeButton.setFixedSize(32, 32);
m_removeButton.move(32, 0);
m_removeAction.setEnabled(false);
}

View File

@ -8,6 +8,8 @@
#include <QComboBox> #include <QComboBox>
#include <QWheelEvent> #include <QWheelEvent>
#include <QItemEditorFactory> #include <QItemEditorFactory>
#include <QToolButton>
#include <QAction>
#include "ProjectModel.hpp" #include "ProjectModel.hpp"
class EditorWidget : public QWidget class EditorWidget : public QWidget
@ -18,8 +20,9 @@ public:
virtual bool valid() const { return true; } virtual bool valid() const { return true; }
virtual void unloadData() {} virtual void unloadData() {}
virtual ProjectModel::INode* currentNode() const { return nullptr; } virtual ProjectModel::INode* currentNode() const { return nullptr; }
public slots: virtual void setEditorEnabled(bool en) { setEnabled(en); }
virtual bool isItemEditEnabled() const { return false; } virtual bool isItemEditEnabled() const { return false; }
public slots:
virtual void itemCutAction() {} virtual void itemCutAction() {}
virtual void itemCopyAction() {} virtual void itemCopyAction() {}
virtual void itemPasteAction() {} virtual void itemPasteAction() {}
@ -127,4 +130,17 @@ public:
} }
}; };
class AddRemoveButtons : public QWidget
{
Q_OBJECT
QAction m_addAction;
QToolButton m_addButton;
QAction m_removeAction;
QToolButton m_removeButton;
public:
explicit AddRemoveButtons(QWidget* parent = Q_NULLPTR);
QAction* addAction() { return &m_addAction; }
QAction* removeAction() { return &m_removeAction; }
};
#endif //AMUSE_EDITOR_WIDGET_HPP #endif //AMUSE_EDITOR_WIDGET_HPP

View File

@ -390,8 +390,7 @@ ProjectModel::INode* LayersEditor::currentNode() const
void LayersEditor::resizeEvent(QResizeEvent* ev) void LayersEditor::resizeEvent(QResizeEvent* ev)
{ {
m_tableView.setGeometry(QRect({}, ev->size())); m_tableView.setGeometry(QRect({}, ev->size()));
m_addButton.move(0, ev->size().height() - 32); m_addRemoveButtons.move(0, ev->size().height() - 32);
m_removeButton.move(32, ev->size().height() - 32);
} }
void LayersEditor::doAdd() void LayersEditor::doAdd()
@ -403,9 +402,9 @@ void LayersEditor::doAdd()
m_model.insertRow(idx.row()); m_model.insertRow(idx.row());
} }
void LayersEditor::doSelectionChanged(const QItemSelection& selected) void LayersEditor::doSelectionChanged()
{ {
m_removeAction.setDisabled(selected.isEmpty()); m_addRemoveButtons.removeAction()->setDisabled(m_tableView.selectionModel()->selectedRows().isEmpty());
g_MainWindow->updateFocus(); g_MainWindow->updateFocus();
} }
@ -435,23 +434,14 @@ void LayersEditor::itemDeleteAction()
} }
LayersEditor::LayersEditor(QWidget* parent) LayersEditor::LayersEditor(QWidget* parent)
: EditorWidget(parent), m_model(this), m_tableView(this), : EditorWidget(parent), m_model(this), m_tableView(this), m_addRemoveButtons(this)
m_addAction(tr("Add Row")), m_addButton(this), m_removeAction(tr("Remove Row")), m_removeButton(this)
{ {
m_tableView.setModel(&m_model); m_tableView.setModel(&m_model);
connect(m_tableView.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), connect(m_tableView.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
this, SLOT(doSelectionChanged(const QItemSelection&))); this, SLOT(doSelectionChanged()));
m_addAction.setIcon(QIcon(QStringLiteral(":/icons/IconAdd.svg"))); m_addRemoveButtons.addAction()->setToolTip(tr("Add new layer mapping"));
m_addAction.setToolTip(tr("Add new layer mapping")); connect(m_addRemoveButtons.addAction(), SIGNAL(triggered(bool)), this, SLOT(doAdd()));
m_addButton.setDefaultAction(&m_addAction); m_addRemoveButtons.removeAction()->setToolTip(tr("Remove selected layer mappings"));
m_addButton.setFixedSize(32, 32); connect(m_addRemoveButtons.removeAction(), SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));
connect(&m_addAction, SIGNAL(triggered(bool)), this, SLOT(doAdd()));
m_removeAction.setIcon(QIcon(QStringLiteral(":/icons/IconRemove.svg")));
m_removeAction.setToolTip(tr("Remove selected layer mappings"));
m_removeButton.setDefaultAction(&m_removeAction);
m_removeButton.setFixedSize(32, 32);
connect(&m_removeAction, SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));
m_removeAction.setEnabled(false);
} }

View File

@ -66,21 +66,18 @@ class LayersEditor : public EditorWidget
Q_OBJECT Q_OBJECT
LayersModel m_model; LayersModel m_model;
LayersTableView m_tableView; LayersTableView m_tableView;
QAction m_addAction; AddRemoveButtons m_addRemoveButtons;
QToolButton m_addButton;
QAction m_removeAction;
QToolButton m_removeButton;
public: public:
explicit LayersEditor(QWidget* parent = Q_NULLPTR); explicit LayersEditor(QWidget* parent = Q_NULLPTR);
bool loadData(ProjectModel::LayersNode* node); bool loadData(ProjectModel::LayersNode* node);
void unloadData(); void unloadData();
ProjectModel::INode* currentNode() const; ProjectModel::INode* currentNode() const;
void resizeEvent(QResizeEvent* ev); void resizeEvent(QResizeEvent* ev);
bool isItemEditEnabled() const;
public slots: public slots:
void doAdd(); void doAdd();
void doSelectionChanged(const QItemSelection& selected); void doSelectionChanged();
bool isItemEditEnabled() const;
void itemCutAction(); void itemCutAction();
void itemCopyAction(); void itemCopyAction();
void itemPasteAction(); void itemPasteAction();

View File

@ -17,6 +17,7 @@
#include "KeymapEditor.hpp" #include "KeymapEditor.hpp"
#include "LayersEditor.hpp" #include "LayersEditor.hpp"
#include "SampleEditor.hpp" #include "SampleEditor.hpp"
#include "NewSoundMacroDialog.hpp"
MainWindow::MainWindow(QWidget* parent) MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent), : QMainWindow(parent),
@ -258,6 +259,7 @@ bool MainWindow::setProjectPath(const QString& path)
m_ui.actionRevert_Project->setEnabled(true); m_ui.actionRevert_Project->setEnabled(true);
m_ui.actionReload_Sample_Data->setEnabled(true); m_ui.actionReload_Sample_Data->setEnabled(true);
m_ui.actionExport_GameCube_Groups->setEnabled(true); m_ui.actionExport_GameCube_Groups->setEnabled(true);
m_ui.actionNew_Subproject->setEnabled(true);
setWindowFilePath(path); setWindowFilePath(path);
updateWindowTitle(); updateWindowTitle();
updateFocus(); updateFocus();
@ -322,14 +324,16 @@ void MainWindow::timerEvent(QTimerEvent* ev)
if (m_engine->getActiveVoices().empty() && m_uiDisabled) if (m_engine->getActiveVoices().empty() && m_uiDisabled)
{ {
m_ui.projectOutline->setEnabled(true); m_ui.projectOutline->setEnabled(true);
m_ui.editorContents->setEnabled(true); if (EditorWidget* w = getEditorWidget())
w->setEditorEnabled(true);
m_ui.menubar->setEnabled(true); m_ui.menubar->setEnabled(true);
m_uiDisabled = false; m_uiDisabled = false;
} }
else if (!m_engine->getActiveVoices().empty() && !m_uiDisabled) else if (!m_engine->getActiveVoices().empty() && !m_uiDisabled)
{ {
m_ui.projectOutline->setEnabled(false); m_ui.projectOutline->setEnabled(false);
m_ui.editorContents->setEnabled(false); if (EditorWidget* w = getEditorWidget())
w->setEditorEnabled(false);
m_ui.menubar->setEnabled(false); m_ui.menubar->setEnabled(false);
m_uiDisabled = true; m_uiDisabled = true;
} }
@ -342,6 +346,21 @@ void MainWindow::timerEvent(QTimerEvent* ev)
else else
sampleEditor->setSamplePos(-1); sampleEditor->setSamplePos(-1);
} }
QTableView* songTable = m_songGroupEditor->getSetupListView();
for (int i = 0; i < songTable->model()->rowCount(); ++i)
if (MIDIPlayerWidget* player = qobject_cast<MIDIPlayerWidget*>(
songTable->indexWidget(songTable->model()->index(i, 1))))
for (auto& p : m_engine->getActiveSequencers())
if (p.get() == player->sequencer() && p->state() != amuse::SequencerState::Playing)
player->stopped();
QTableView* sfxTable = m_soundGroupEditor->getSFXListView();
for (int i = 0; i < sfxTable->model()->rowCount(); ++i)
if (SFXPlayerWidget* player = qobject_cast<SFXPlayerWidget*>(
sfxTable->indexWidget(sfxTable->model()->index(i, 1))))
if (player->voice() && player->voice()->state() != amuse::VoiceState::Playing)
player->stopped();
} }
} }
@ -547,6 +566,27 @@ amuse::ObjToken<amuse::Voice> MainWindow::startEditorVoice(uint8_t key, uint8_t
return vox; return vox;
} }
amuse::ObjToken<amuse::Voice> MainWindow::startSFX(amuse::GroupId groupId, amuse::SFXId sfxId)
{
if (ProjectModel::INode* node = getEditorNode())
{
amuse::AudioGroupDatabase* group = projectModel()->getGroupNode(node)->getAudioGroup();
return m_engine->fxStart(group, groupId, sfxId, 1.f, 0.f);
}
return {};
}
amuse::ObjToken<amuse::Sequencer> MainWindow::startSong(amuse::GroupId groupId, amuse::SongId songId,
const unsigned char* arrData)
{
if (ProjectModel::INode* node = getEditorNode())
{
amuse::AudioGroupDatabase* group = projectModel()->getGroupNode(node)->getAudioGroup();
return m_engine->seqPlay(group, groupId, songId, arrData);
}
return {};
}
void MainWindow::pushUndoCommand(QUndoCommand* cmd) void MainWindow::pushUndoCommand(QUndoCommand* cmd)
{ {
m_undoStack->push(cmd); m_undoStack->push(cmd);
@ -858,44 +898,217 @@ bool TreeDelegate::editorEvent(QEvent* event,
return false; return false;
} }
QString MainWindow::getGroupName(ProjectModel::GroupNode* group) const
{
if (group)
return group->text();
return {};
}
ProjectModel::GroupNode* MainWindow::getSelectedGroupNode() const
{
if (!m_projectModel)
return nullptr;
if (!m_ui.projectOutline->selectionModel()->currentIndex().isValid())
return nullptr;
return m_projectModel->getGroupNode(m_projectModel->node(m_ui.projectOutline->selectionModel()->currentIndex()));
}
QString MainWindow::getSelectedGroupName() const
{
return getGroupName(getSelectedGroupNode());
}
void MainWindow::recursiveExpandOutline(const QModelIndex& index) const
{
if (index.isValid())
{
recursiveExpandAndSelectOutline(index.parent());
m_ui.projectOutline->expand(index);
}
}
void MainWindow::recursiveExpandAndSelectOutline(const QModelIndex& index) const
{
recursiveExpandOutline(index);
if (index.isValid())
{
m_ui.projectOutline->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
m_ui.projectOutline->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Current);
}
}
void MainWindow::newSubprojectAction() void MainWindow::newSubprojectAction()
{ {
QString newName;
bool ok = true;
while (ok && newName.isEmpty())
newName = QInputDialog::getText(this, tr("New Subproject"),
tr("What should this subproject be named?"), QLineEdit::Normal, QString(), &ok);
if (!ok)
return;
ProjectModel::GroupNode* node = m_projectModel->newSubproject(newName, m_mainMessenger);
if (node)
recursiveExpandAndSelectOutline(m_projectModel->index(node));
} }
void MainWindow::newSFXGroupAction() void MainWindow::newSFXGroupAction()
{ {
ProjectModel::GroupNode* group = getSelectedGroupNode();
m_projectModel->setIdDatabases(group);
QString groupName = getGroupName(group);
QString newName;
bool ok = true;
while (ok && newName.isEmpty())
newName = QInputDialog::getText(this, tr("New SFX Group"),
tr("What should the new SFX group in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(
amuse::GroupId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Group)), &ok);
if (!ok)
return;
ProjectModel::SoundGroupNode* node = m_projectModel->newSoundGroup(group, newName, m_mainMessenger);
if (node)
{
recursiveExpandAndSelectOutline(m_projectModel->index(node));
openEditor(node);
}
} }
void MainWindow::newSongGroupAction() void MainWindow::newSongGroupAction()
{ {
ProjectModel::GroupNode* group = getSelectedGroupNode();
m_projectModel->setIdDatabases(group);
QString groupName = getGroupName(group);
QString newName;
bool ok = true;
while (ok && newName.isEmpty())
newName = QInputDialog::getText(this, tr("New Song Group"),
tr("What should the new Song group in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(
amuse::GroupId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Group)), &ok);
if (!ok)
return;
ProjectModel::SongGroupNode* node = m_projectModel->newSongGroup(group, newName, m_mainMessenger);
if (node)
{
recursiveExpandAndSelectOutline(m_projectModel->index(node));
openEditor(node);
}
} }
void MainWindow::newSoundMacroAction() void MainWindow::newSoundMacroAction()
{ {
ProjectModel::GroupNode* group = getSelectedGroupNode();
m_projectModel->setIdDatabases(group);
QString groupName = getGroupName(group);
QString newName;
const SoundMacroTemplateEntry* templ = nullptr;
int result = QDialog::Accepted;
while (result == QDialog::Accepted && newName.isEmpty())
{
NewSoundMacroDialog dialog(groupName, this);
result = dialog.exec();
newName = dialog.getName();
templ = dialog.getSelectedTemplate();
}
if (result == QDialog::Rejected)
return;
ProjectModel::SoundMacroNode* node = m_projectModel->newSoundMacro(group, newName, m_mainMessenger, templ);
if (node)
{
recursiveExpandAndSelectOutline(m_projectModel->index(node));
openEditor(node);
}
} }
void MainWindow::newADSRAction() void MainWindow::newADSRAction()
{ {
ProjectModel::GroupNode* group = getSelectedGroupNode();
m_projectModel->setIdDatabases(group);
QString groupName = getGroupName(group);
QString newName;
bool ok = true;
while (ok && newName.isEmpty())
newName = QInputDialog::getText(this, tr("New ADSR"),
tr("What should the new ADSR in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(
amuse::TableId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Table)), &ok);
if (!ok)
return;
ProjectModel::ADSRNode* node = m_projectModel->newADSR(group, newName, m_mainMessenger);
if (node)
{
recursiveExpandAndSelectOutline(m_projectModel->index(node));
openEditor(node);
}
} }
void MainWindow::newCurveAction() void MainWindow::newCurveAction()
{ {
ProjectModel::GroupNode* group = getSelectedGroupNode();
m_projectModel->setIdDatabases(group);
QString groupName = getGroupName(group);
QString newName;
bool ok = true;
while (ok && newName.isEmpty())
newName = QInputDialog::getText(this, tr("New Curve"),
tr("What should the new Curve in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(
amuse::TableId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Table)), &ok);
if (!ok)
return;
ProjectModel::CurveNode* node = m_projectModel->newCurve(group, newName, m_mainMessenger);
if (node)
{
recursiveExpandAndSelectOutline(m_projectModel->index(node));
openEditor(node);
}
} }
void MainWindow::newKeymapAction() void MainWindow::newKeymapAction()
{ {
ProjectModel::GroupNode* group = getSelectedGroupNode();
m_projectModel->setIdDatabases(group);
QString groupName = getGroupName(group);
QString newName;
bool ok = true;
while (ok && newName.isEmpty())
newName = QInputDialog::getText(this, tr("New Keymap"),
tr("What should the new Keymap in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(
amuse::KeymapId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Keymap)), &ok);
if (!ok)
return;
ProjectModel::KeymapNode* node = m_projectModel->newKeymap(group, newName, m_mainMessenger);
if (node)
{
recursiveExpandAndSelectOutline(m_projectModel->index(node));
openEditor(node);
}
} }
void MainWindow::newLayersAction() void MainWindow::newLayersAction()
{ {
ProjectModel::GroupNode* group = getSelectedGroupNode();
m_projectModel->setIdDatabases(group);
QString groupName = getGroupName(group);
QString newName;
bool ok = true;
while (ok && newName.isEmpty())
newName = QInputDialog::getText(this, tr("New Layers"),
tr("What should the new Layers in %1 be named?").arg(groupName), QLineEdit::Normal, QString::fromStdString(
amuse::LayersId::CurNameDB->generateDefaultName(amuse::NameDB::Type::Layer)), &ok);
if (!ok)
return;
ProjectModel::LayersNode* node = m_projectModel->newLayers(group, newName, m_mainMessenger);
if (node)
{
recursiveExpandAndSelectOutline(m_projectModel->index(node));
openEditor(node);
}
} }
void MainWindow::aboutToShowAudioIOMenu() void MainWindow::aboutToShowAudioIOMenu()
@ -1055,26 +1268,36 @@ void MainWindow::setItemEditEnabled(bool enabled)
m_ui.actionDelete->setEnabled(enabled); m_ui.actionDelete->setEnabled(enabled);
} }
void MainWindow::setItemNewEnabled(bool enabled)
{
m_ui.actionNew_SFX_Group->setEnabled(enabled);
m_ui.actionNew_Song_Group->setEnabled(enabled);
m_ui.actionNew_Sound_Macro->setEnabled(enabled);
m_ui.actionNew_ADSR->setEnabled(enabled);
m_ui.actionNew_Curve->setEnabled(enabled);
m_ui.actionNew_Keymap->setEnabled(enabled);
m_ui.actionNew_Layers->setEnabled(enabled);
}
bool MainWindow::canEditOutline() bool MainWindow::canEditOutline()
{ {
if (!m_projectModel) if (!m_projectModel)
return false; return false;
QModelIndexList indexes = m_ui.projectOutline->selectionModel()->selectedIndexes(); QModelIndex curIndex = m_ui.projectOutline->selectionModel()->currentIndex();
if (indexes.empty()) if (!curIndex.isValid())
return false; return false;
return m_projectModel->canEdit(indexes.front()); return m_projectModel->canEdit(curIndex);
} }
void MainWindow::onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) void MainWindow::onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
{ {
if (!m_projectModel) if (!m_projectModel)
return; return;
setItemNewEnabled(m_ui.projectOutline->selectionModel()->currentIndex().isValid());
if (selected.indexes().empty()) if (selected.indexes().empty())
{
setItemEditEnabled(false); setItemEditEnabled(false);
return; else
} setItemEditEnabled(m_projectModel->canEdit(selected.indexes().front()));
setItemEditEnabled(m_projectModel->canEdit(selected.indexes().front()));
} }
void MainWindow::onTextSelect() void MainWindow::onTextSelect()

View File

@ -146,10 +146,19 @@ public:
ProjectModel::INode* getEditorNode() const; ProjectModel::INode* getEditorNode() const;
EditorWidget* getEditorWidget() const; EditorWidget* getEditorWidget() const;
amuse::ObjToken<amuse::Voice> startEditorVoice(uint8_t key, uint8_t vel); amuse::ObjToken<amuse::Voice> startEditorVoice(uint8_t key, uint8_t vel);
amuse::ObjToken<amuse::Voice> startSFX(amuse::GroupId groupId, amuse::SFXId sfxId);
amuse::ObjToken<amuse::Sequencer> startSong(amuse::GroupId groupId, amuse::SongId songId,
const unsigned char* arrData);
void pushUndoCommand(QUndoCommand* cmd); void pushUndoCommand(QUndoCommand* cmd);
void updateFocus(); void updateFocus();
void aboutToDeleteNode(ProjectModel::INode* node); void aboutToDeleteNode(ProjectModel::INode* node);
QString getGroupName(ProjectModel::GroupNode* group) const;
ProjectModel::GroupNode* getSelectedGroupNode() const;
QString getSelectedGroupName() const;
void recursiveExpandOutline(const QModelIndex& index) const;
void recursiveExpandAndSelectOutline(const QModelIndex& index) const;
ProjectModel* projectModel() const { return m_projectModel; } ProjectModel* projectModel() const { return m_projectModel; }
public slots: public slots:
@ -193,6 +202,7 @@ public slots:
void onFocusChanged(QWidget* old, QWidget* now); void onFocusChanged(QWidget* old, QWidget* now);
void outlineItemActivated(const QModelIndex& index); void outlineItemActivated(const QModelIndex& index);
void setItemEditEnabled(bool enabled); void setItemEditEnabled(bool enabled);
void setItemNewEnabled(bool enabled);
bool canEditOutline(); bool canEditOutline();
void onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); void onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
void onTextSelect(); void onTextSelect();

View File

@ -77,16 +77,7 @@
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
<widget class="QStackedWidget" name="editorContents"> <widget class="QStackedWidget" name="editorContents"/>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1103</width>
<height>606</height>
</rect>
</property>
</widget>
<widget class="QWidget" name="keyboard" native="true"> <widget class="QWidget" name="keyboard" native="true">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
@ -232,7 +223,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1501</width> <width>1501</width>
<height>80</height> <height>85</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -269,7 +260,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1360</width> <width>1360</width>
<height>34</height> <height>27</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuFile"> <widget class="QMenu" name="menuFile">
@ -313,7 +304,6 @@
<property name="title"> <property name="title">
<string>&amp;Audio</string> <string>&amp;Audio</string>
</property> </property>
<addaction name="actionAuto_Play"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionSelect_Output_Device"/> <addaction name="actionSelect_Output_Device"/>
</widget> </widget>
@ -450,17 +440,6 @@
<string>New &amp;Layers</string> <string>New &amp;Layers</string>
</property> </property>
</action> </action>
<action name="actionAuto_Play">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Auto-Play</string>
</property>
</action>
<action name="actionSelect_Output_Device"> <action name="actionSelect_Output_Device">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>

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"); SoundGroupNode::Icon = QIcon(":/icons/IconSoundGroup.svg");
} }
void ProjectModel::_buildSortedList()
{
m_sorted.clear();
m_sorted.reserve(m_groups.size());
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it)
m_sorted.emplace_back(it);
std::sort(m_sorted.begin(), m_sorted.end());
}
QModelIndex ProjectModel::_indexOfGroup(const QString& groupName) const
{
auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), groupName);
if (search == m_sorted.cend() || search->m_it->first != groupName)
return QModelIndex();
else
{
int idx = search - m_sorted.begin();
return createIndex(idx, 0, m_root->child(idx));
}
}
int ProjectModel::_hypotheticalIndexOfGroup(const QString& groupName) const
{
auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), groupName);
return search - m_sorted.begin();
}
bool ProjectModel::clearProjectData() bool ProjectModel::clearProjectData()
{ {
m_projectDatabase = amuse::ProjectDatabase(); m_projectDatabase = amuse::ProjectDatabase();
m_groups.clear(); m_groups.clear();
m_sorted.clear();
m_midiFiles.clear(); m_midiFiles.clear();
m_needsReset = true; m_needsReset = true;
@ -473,92 +501,98 @@ bool ProjectModel::saveToFile(UIMessenger& messenger)
return true; return true;
} }
void ProjectModel::_resetModelData() void ProjectModel::_buildGroupNode(GroupNode& gn)
{ {
beginResetModel(); amuse::AudioGroup& group = gn.m_it->second;
m_projectDatabase.setIdDatabases(); auto& songGroups = group.getProj().songGroups();
m_root = amuse::MakeObj<RootNode>(); auto& sfxGroups = group.getProj().sfxGroups();
m_root->reserve(m_groups.size()); auto& soundMacros = group.getPool().soundMacros();
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it) auto& tables = group.getPool().tables();
auto& keymaps = group.getPool().keymaps();
auto& layers = group.getPool().layers();
auto& samples = group.getSdir().sampleEntries();
gn.reserve(songGroups.size() + sfxGroups.size() + 4);
for (const auto& grp : SortUnorderedMap(songGroups))
gn.makeChild<SongGroupNode>(grp.first, grp.second.get());
for (const auto& grp : SortUnorderedMap(sfxGroups))
gn.makeChild<SoundGroupNode>(grp.first, grp.second.get());
{ {
it->second.setIdDatabases(); CollectionNode& col =
GroupNode& gn = m_root->makeChild<GroupNode>(it); gn.makeChild<CollectionNode>(tr("Sound Macros"), QIcon(":/icons/IconSoundMacro.svg"), INode::Type::SoundMacro);
amuse::AudioGroup& group = it->second; col.reserve(soundMacros.size());
auto& songGroups = group.getProj().songGroups(); for (const auto& macro : SortUnorderedMap(soundMacros))
auto& sfxGroups = group.getProj().sfxGroups(); col.makeChild<SoundMacroNode>(macro.first, macro.second.get());
auto& soundMacros = group.getPool().soundMacros(); }
auto& tables = group.getPool().tables(); {
auto& keymaps = group.getPool().keymaps(); auto tablesSort = SortUnorderedMap(tables);
auto& layers = group.getPool().layers(); size_t ADSRCount = 0;
auto& samples = group.getSdir().sampleEntries(); size_t curveCount = 0;
gn.reserve(songGroups.size() + sfxGroups.size() + 4); for (auto& t : tablesSort)
for (const auto& grp : SortUnorderedMap(songGroups))
gn.makeChild<SongGroupNode>(grp.first, grp.second.get());
for (const auto& grp : SortUnorderedMap(sfxGroups))
gn.makeChild<SoundGroupNode>(grp.first, grp.second.get());
{ {
CollectionNode& col = amuse::ITable::Type tp = (*t.second.get())->Isa();
gn.makeChild<CollectionNode>(tr("Sound Macros"), QIcon(":/icons/IconSoundMacro.svg"), INode::Type::SoundMacro); if (tp == amuse::ITable::Type::ADSR || tp == amuse::ITable::Type::ADSRDLS)
col.reserve(soundMacros.size()); ADSRCount += 1;
for (const auto& macro : SortUnorderedMap(soundMacros)) else if (tp == amuse::ITable::Type::Curve)
col.makeChild<SoundMacroNode>(macro.first, macro.second.get()); curveCount += 1;
} }
{ {
auto tablesSort = SortUnorderedMap(tables); CollectionNode& col =
size_t ADSRCount = 0; gn.makeChild<CollectionNode>(tr("ADSRs"), QIcon(":/icons/IconADSR.svg"), INode::Type::ADSR);
size_t curveCount = 0; col.reserve(ADSRCount);
for (auto& t : tablesSort) for (auto& t : tablesSort)
{ {
amuse::ITable::Type tp = (*t.second.get())->Isa(); amuse::ITable::Type tp = (*t.second.get())->Isa();
if (tp == amuse::ITable::Type::ADSR || tp == amuse::ITable::Type::ADSRDLS) if (tp == amuse::ITable::Type::ADSR || tp == amuse::ITable::Type::ADSRDLS)
ADSRCount += 1; col.makeChild<ADSRNode>(t.first, t.second.get());
else if (tp == amuse::ITable::Type::Curve)
curveCount += 1;
} }
}
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Curves"), QIcon(":/icons/IconCurve.svg"), INode::Type::Curve);
col.reserve(curveCount);
for (auto& t : tablesSort)
{ {
CollectionNode& col = amuse::ITable::Type tp = (*t.second.get())->Isa();
gn.makeChild<CollectionNode>(tr("ADSRs"), QIcon(":/icons/IconADSR.svg"), INode::Type::ADSR); if (tp == amuse::ITable::Type::Curve)
col.reserve(ADSRCount); col.makeChild<CurveNode>(t.first, t.second.get());
for (auto& t : tablesSort)
{
amuse::ITable::Type tp = (*t.second.get())->Isa();
if (tp == amuse::ITable::Type::ADSR || tp == amuse::ITable::Type::ADSRDLS)
col.makeChild<ADSRNode>(t.first, t.second.get());
}
}
{
CollectionNode& col =
gn.makeChild<CollectionNode>(tr("Curves"), QIcon(":/icons/IconCurve.svg"), INode::Type::Curve);
col.reserve(curveCount);
for (auto& t : tablesSort)
{
amuse::ITable::Type tp = (*t.second.get())->Isa();
if (tp == amuse::ITable::Type::Curve)
col.makeChild<CurveNode>(t.first, t.second.get());
}
} }
} }
{ }
CollectionNode& col = {
gn.makeChild<CollectionNode>(tr("Keymaps"), QIcon(":/icons/IconKeymap.svg"), INode::Type::Keymap); CollectionNode& col =
col.reserve(keymaps.size()); gn.makeChild<CollectionNode>(tr("Keymaps"), QIcon(":/icons/IconKeymap.svg"), INode::Type::Keymap);
for (auto& keymap : SortUnorderedMap(keymaps)) col.reserve(keymaps.size());
col.makeChild<KeymapNode>(keymap.first, keymap.second.get()); for (auto& keymap : SortUnorderedMap(keymaps))
} col.makeChild<KeymapNode>(keymap.first, keymap.second.get());
{ }
CollectionNode& col = {
gn.makeChild<CollectionNode>(tr("Layers"), QIcon(":/icons/IconLayers.svg"), INode::Type::Layer); CollectionNode& col =
col.reserve(layers.size()); gn.makeChild<CollectionNode>(tr("Layers"), QIcon(":/icons/IconLayers.svg"), INode::Type::Layer);
for (auto& keymap : SortUnorderedMap(layers)) col.reserve(layers.size());
col.makeChild<LayersNode>(keymap.first, keymap.second.get()); for (auto& keymap : SortUnorderedMap(layers))
} col.makeChild<LayersNode>(keymap.first, keymap.second.get());
{ }
CollectionNode& col = {
gn.makeChild<CollectionNode>(tr("Samples"), QIcon(":/icons/IconSample.svg"), INode::Type::Sample); CollectionNode& col =
col.reserve(samples.size()); gn.makeChild<CollectionNode>(tr("Samples"), QIcon(":/icons/IconSample.svg"), INode::Type::Sample);
for (auto& sample : SortUnorderedMap(samples)) col.reserve(samples.size());
col.makeChild<SampleNode>(sample.first, sample.second.get()); for (auto& sample : SortUnorderedMap(samples))
} col.makeChild<SampleNode>(sample.first, sample.second.get());
}
}
void ProjectModel::_resetModelData()
{
beginResetModel();
_buildSortedList();
m_projectDatabase.setIdDatabases();
m_root = amuse::MakeObj<RootNode>();
m_root->reserve(m_sorted.size());
for (auto it = m_sorted.begin() ; it != m_sorted.end() ; ++it)
{
it->m_it->second.setIdDatabases();
GroupNode& gn = m_root->makeChild<GroupNode>(it->m_it);
_buildGroupNode(gn);
} }
endResetModel(); endResetModel();
} }
@ -704,32 +738,41 @@ class DeleteNodeUndoCommand : public QUndoCommand
{ {
QModelIndex m_deleteIdx; QModelIndex m_deleteIdx;
amuse::ObjToken<ProjectModel::INode> m_node; amuse::ObjToken<ProjectModel::INode> m_node;
ProjectModel::NameUndoRegistry m_nameReg;
public: public:
DeleteNodeUndoCommand(const QModelIndex& index) DeleteNodeUndoCommand(const QModelIndex& index)
: QUndoCommand(ProjectModel::tr("Delete %1").arg(index.data().toString())), m_deleteIdx(index) {} : QUndoCommand(ProjectModel::tr("Delete %1").arg(index.data().toString())), m_deleteIdx(index) {}
void undo() void undo()
{ {
g_MainWindow->projectModel()->_undoDel(m_deleteIdx, std::move(m_node)); g_MainWindow->projectModel()->_undoDel(m_deleteIdx, std::move(m_node), m_nameReg);
m_node.reset(); m_node.reset();
m_nameReg.clear();
} }
void redo() void redo()
{ {
m_node = g_MainWindow->projectModel()->_redoDel(m_deleteIdx); m_node = g_MainWindow->projectModel()->_redoDel(m_deleteIdx, m_nameReg);
} }
}; };
void ProjectModel::_undoDel(const QModelIndex& index, amuse::ObjToken<ProjectModel::INode> n) void ProjectModel::_undoDel(const QModelIndex& index, amuse::ObjToken<ProjectModel::INode> n, const NameUndoRegistry& nameReg)
{ {
beginInsertRows(index.parent(), index.row(), index.row()); beginInsertRows(index.parent(), index.row(), index.row());
node(index.parent())->insertChild(index.row(), std::move(n)); node(index.parent())->insertChild(index.row(), n);
setIdDatabases(n.get());
n->depthTraverse([&nameReg](INode* node)
{
node->registerNames(nameReg);
return true;
});
endInsertRows(); endInsertRows();
} }
amuse::ObjToken<ProjectModel::INode> ProjectModel::_redoDel(const QModelIndex& index) amuse::ObjToken<ProjectModel::INode> ProjectModel::_redoDel(const QModelIndex& index, NameUndoRegistry& nameReg)
{ {
node(index)->depthTraverse([](INode* node) node(index)->depthTraverse([&nameReg](INode* node)
{ {
g_MainWindow->aboutToDeleteNode(node); g_MainWindow->aboutToDeleteNode(node);
node->unregisterNames(nameReg);
return true; return true;
}); });
beginRemoveRows(index.parent(), index.row(), index.row()); beginRemoveRows(index.parent(), index.row(), index.row());
@ -745,6 +788,186 @@ void ProjectModel::del(const QModelIndex& index)
g_MainWindow->pushUndoCommand(new DeleteNodeUndoCommand(index)); g_MainWindow->pushUndoCommand(new DeleteNodeUndoCommand(index));
} }
ProjectModel::GroupNode* ProjectModel::newSubproject(const QString& name, UIMessenger& messenger)
{
if (m_groups.find(name) != m_groups.cend())
{
messenger.critical(tr("Subproject Conflict"), tr("The subproject %1 is already defined").arg(name));
return nullptr;
}
QDir dir(QFileInfo(m_dir, name).filePath());
if (!MkPath(dir.path(), messenger))
return nullptr;
QFile(QFileInfo(dir, QStringLiteral("!project.yaml")).filePath()).open(QFile::WriteOnly);
QFile(QFileInfo(dir, QStringLiteral("!pool.yaml")).filePath()).open(QFile::WriteOnly);
int idx = _hypotheticalIndexOfGroup(name);
beginInsertRows(QModelIndex(), idx, idx);
auto it = m_groups.emplace(std::make_pair(name, amuse::AudioGroupDatabase(QStringToSysString(dir.path())))).first;
_buildSortedList();
m_projectDatabase.setIdDatabases();
it->second.setIdDatabases();
GroupNode& gn = m_root->makeChildAtIdx<GroupNode>(idx, it);
_buildGroupNode(gn);
endInsertRows();
return &gn;
}
ProjectModel::SoundGroupNode* ProjectModel::newSoundGroup(GroupNode* group, const QString& name, UIMessenger& messenger)
{
setIdDatabases(group);
auto nameKey = name.toUtf8();
if (amuse::GroupId::CurNameDB->m_stringToId.find(nameKey.data()) != amuse::GroupId::CurNameDB->m_stringToId.cend())
{
messenger.critical(tr("Sound Group Conflict"), tr("The group %1 is already defined").arg(name));
return nullptr;
}
beginInsertRows(index(group), 0, 0);
amuse::GroupId newId = amuse::GroupId::CurNameDB->generateId(amuse::NameDB::Type::Group);
amuse::GroupId::CurNameDB->registerPair(nameKey.data(), newId);
auto node = amuse::MakeObj<amuse::SFXGroupIndex>();
group->getAudioGroup()->getProj().sfxGroups()[newId] = node;
SoundGroupNode& ret = group->makeChildAtIdx<SoundGroupNode>(0, newId, node);
endInsertRows();
return &ret;
}
ProjectModel::SongGroupNode* ProjectModel::newSongGroup(GroupNode* group, const QString& name, UIMessenger& messenger)
{
setIdDatabases(group);
auto nameKey = name.toUtf8();
if (amuse::GroupId::CurNameDB->m_stringToId.find(nameKey.data()) != amuse::GroupId::CurNameDB->m_stringToId.cend())
{
messenger.critical(tr("Song Group Conflict"), tr("The group %1 is already defined").arg(name));
return nullptr;
}
beginInsertRows(index(group), 0, 0);
amuse::GroupId newId = amuse::GroupId::CurNameDB->generateId(amuse::NameDB::Type::Group);
amuse::GroupId::CurNameDB->registerPair(nameKey.data(), newId);
auto node = amuse::MakeObj<amuse::SongGroupIndex>();
group->getAudioGroup()->getProj().songGroups()[newId] = node;
SongGroupNode& ret = group->makeChildAtIdx<SongGroupNode>(0, newId, node);
endInsertRows();
return &ret;
}
ProjectModel::SoundMacroNode* ProjectModel::newSoundMacro(GroupNode* group, const QString& name, UIMessenger& messenger,
const SoundMacroTemplateEntry* templ)
{
setIdDatabases(group);
auto nameKey = name.toUtf8();
if (amuse::SoundMacroId::CurNameDB->m_stringToId.find(nameKey.data()) != amuse::SoundMacroId::CurNameDB->m_stringToId.cend())
{
messenger.critical(tr("Sound Macro Conflict"), tr("The macro %1 is already defined").arg(name));
return nullptr;
}
ProjectModel::CollectionNode* coll = group->getCollectionOfType(INode::Type::SoundMacro);
QModelIndex parentIdx = index(coll);
int insertIdx = rowCount(parentIdx);
beginInsertRows(parentIdx, insertIdx, insertIdx);
amuse::SoundMacroId newId = amuse::SoundMacroId::CurNameDB->generateId(amuse::NameDB::Type::SoundMacro);
amuse::SoundMacroId::CurNameDB->registerPair(nameKey.data(), newId);
auto node = amuse::MakeObj<amuse::SoundMacro>();
if (templ)
{
athena::io::MemoryReader r(templ->m_data, templ->m_length);
node->readCmds<athena::utility::NotSystemEndian>(r, templ->m_length);
}
group->getAudioGroup()->getPool().soundMacros()[newId] = node;
SoundMacroNode& ret = coll->makeChildAtIdx<SoundMacroNode>(insertIdx, newId, node);
endInsertRows();
return &ret;
}
ProjectModel::ADSRNode* ProjectModel::newADSR(GroupNode* group, const QString& name, UIMessenger& messenger)
{
setIdDatabases(group);
auto nameKey = name.toUtf8();
if (amuse::TableId::CurNameDB->m_stringToId.find(nameKey.data()) != amuse::TableId::CurNameDB->m_stringToId.cend())
{
messenger.critical(tr("ADSR Conflict"), tr("The ADSR %1 is already defined").arg(name));
return nullptr;
}
ProjectModel::CollectionNode* coll = group->getCollectionOfType(INode::Type::ADSR);
QModelIndex parentIdx = index(coll);
int insertIdx = rowCount(parentIdx);
beginInsertRows(parentIdx, insertIdx, insertIdx);
amuse::TableId newId = amuse::TableId::CurNameDB->generateId(amuse::NameDB::Type::Table);
amuse::TableId::CurNameDB->registerPair(nameKey.data(), newId);
auto node = amuse::MakeObj<std::unique_ptr<amuse::ITable>>();
*node = std::make_unique<amuse::ADSR>();
group->getAudioGroup()->getPool().tables()[newId] = node;
ADSRNode& ret = coll->makeChildAtIdx<ADSRNode>(insertIdx, newId, node);
endInsertRows();
return &ret;
}
ProjectModel::CurveNode* ProjectModel::newCurve(GroupNode* group, const QString& name, UIMessenger& messenger)
{
setIdDatabases(group);
auto nameKey = name.toUtf8();
if (amuse::TableId::CurNameDB->m_stringToId.find(nameKey.data()) != amuse::TableId::CurNameDB->m_stringToId.cend())
{
messenger.critical(tr("Curve Conflict"), tr("The Curve %1 is already defined").arg(name));
return nullptr;
}
ProjectModel::CollectionNode* coll = group->getCollectionOfType(INode::Type::Curve);
QModelIndex parentIdx = index(coll);
int insertIdx = rowCount(parentIdx);
beginInsertRows(parentIdx, insertIdx, insertIdx);
amuse::TableId newId = amuse::TableId::CurNameDB->generateId(amuse::NameDB::Type::Table);
amuse::TableId::CurNameDB->registerPair(nameKey.data(), newId);
auto node = amuse::MakeObj<std::unique_ptr<amuse::ITable>>();
*node = std::make_unique<amuse::Curve>();
group->getAudioGroup()->getPool().tables()[newId] = node;
CurveNode& ret = coll->makeChildAtIdx<CurveNode>(insertIdx, newId, node);
endInsertRows();
return &ret;
}
ProjectModel::KeymapNode* ProjectModel::newKeymap(GroupNode* group, const QString& name, UIMessenger& messenger)
{
setIdDatabases(group);
auto nameKey = name.toUtf8();
if (amuse::KeymapId::CurNameDB->m_stringToId.find(nameKey.data()) != amuse::KeymapId::CurNameDB->m_stringToId.cend())
{
messenger.critical(tr("Keymap Conflict"), tr("The Keymap %1 is already defined").arg(name));
return nullptr;
}
ProjectModel::CollectionNode* coll = group->getCollectionOfType(INode::Type::Keymap);
QModelIndex parentIdx = index(coll);
int insertIdx = rowCount(parentIdx);
beginInsertRows(parentIdx, insertIdx, insertIdx);
amuse::KeymapId newId = amuse::KeymapId::CurNameDB->generateId(amuse::NameDB::Type::Keymap);
amuse::KeymapId::CurNameDB->registerPair(nameKey.data(), newId);
auto node = amuse::MakeObj<std::array<amuse::Keymap, 128>>();
group->getAudioGroup()->getPool().keymaps()[newId] = node;
KeymapNode& ret = coll->makeChildAtIdx<KeymapNode>(insertIdx, newId, node);
endInsertRows();
return &ret;
}
ProjectModel::LayersNode* ProjectModel::newLayers(GroupNode* group, const QString& name, UIMessenger& messenger)
{
setIdDatabases(group);
auto nameKey = name.toUtf8();
if (amuse::LayersId::CurNameDB->m_stringToId.find(nameKey.data()) != amuse::LayersId::CurNameDB->m_stringToId.cend())
{
messenger.critical(tr("Layers Conflict"), tr("Layers %1 is already defined").arg(name));
return nullptr;
}
ProjectModel::CollectionNode* coll = group->getCollectionOfType(INode::Type::Layer);
QModelIndex parentIdx = index(coll);
int insertIdx = rowCount(parentIdx);
beginInsertRows(parentIdx, insertIdx, insertIdx);
amuse::LayersId newId = amuse::LayersId::CurNameDB->generateId(amuse::NameDB::Type::Layer);
amuse::LayersId::CurNameDB->registerPair(nameKey.data(), newId);
auto node = amuse::MakeObj<std::vector<amuse::LayerMapping>>();
group->getAudioGroup()->getPool().layers()[newId] = node;
LayersNode& ret = coll->makeChildAtIdx<LayersNode>(insertIdx, newId, node);
endInsertRows();
return &ret;
}
ProjectModel::GroupNode* ProjectModel::getGroupOfSfx(amuse::SFXId id) const ProjectModel::GroupNode* ProjectModel::getGroupOfSfx(amuse::SFXId id) const
{ {
ProjectModel::GroupNode* ret = nullptr; ProjectModel::GroupNode* ret = nullptr;
@ -797,3 +1020,10 @@ void ProjectModel::setMIDIPathOfSong(amuse::SongId id, const QString& path)
{ {
m_midiFiles[id] = path; m_midiFiles[id] = path;
} }
void ProjectModel::setIdDatabases(INode* context) const
{
m_projectDatabase.setIdDatabases();
if (ProjectModel::GroupNode* group = getGroupNode(context))
group->getAudioGroup()->setIdDatabases();
}

View File

@ -7,6 +7,7 @@
#include <QIcon> #include <QIcon>
#include <map> #include <map>
#include "Common.hpp" #include "Common.hpp"
#include "NewSoundMacroDialog.hpp"
#include "amuse/AudioGroup.hpp" #include "amuse/AudioGroup.hpp"
#include "amuse/AudioGroupData.hpp" #include "amuse/AudioGroupData.hpp"
#include "amuse/AudioGroupProject.hpp" #include "amuse/AudioGroupProject.hpp"
@ -52,13 +53,71 @@ public:
Both Both
}; };
struct NameUndoRegistry
{
std::unordered_map<amuse::SongId, std::string> m_songIDs;
std::unordered_map<amuse::SFXId, std::string> m_sfxIDs;
void registerSongName(amuse::SongId id) const
{
auto search = m_songIDs.find(id);
if (search != m_songIDs.cend())
amuse::SongId::CurNameDB->registerPair(search->second, id);
}
void unregisterSongName(amuse::SongId id)
{
auto search = amuse::SongId::CurNameDB->m_idToString.find(id);
if (search != amuse::SongId::CurNameDB->m_idToString.cend())
m_songIDs[id] = search->second;
amuse::SongId::CurNameDB->remove(id);
}
void registerSFXName(amuse::SongId id) const
{
auto search = m_sfxIDs.find(id);
if (search != m_sfxIDs.cend())
amuse::SFXId::CurNameDB->registerPair(search->second, id);
}
void unregisterSFXName(amuse::SongId id)
{
auto search = amuse::SFXId::CurNameDB->m_idToString.find(id);
if (search != amuse::SFXId::CurNameDB->m_idToString.cend())
m_sfxIDs[id] = search->second;
amuse::SFXId::CurNameDB->remove(id);
}
void clear()
{
m_songIDs.clear();
m_sfxIDs.clear();
}
};
private: private:
QDir m_dir; QDir m_dir;
NullItemProxyModel m_nullProxy; NullItemProxyModel m_nullProxy;
PageObjectProxyModel m_pageObjectProxy; PageObjectProxyModel m_pageObjectProxy;
amuse::ProjectDatabase m_projectDatabase; amuse::ProjectDatabase m_projectDatabase;
std::map<QString, amuse::AudioGroupDatabase> m_groups;
std::unordered_map<QString, amuse::AudioGroupDatabase> m_groups;
struct Iterator
{
using ItTp = std::unordered_map<QString, amuse::AudioGroupDatabase>::iterator;
ItTp m_it;
Iterator(ItTp it) : m_it(it) {}
ItTp::pointer operator->() { return m_it.operator->(); }
bool operator<(const Iterator& other) const
{
return m_it->first < other.m_it->first;
}
bool operator<(const QString& name) const
{
return m_it->first < name;
}
};
std::vector<Iterator> m_sorted;
void _buildSortedList();
QModelIndex _indexOfGroup(const QString& groupName) const;
int _hypotheticalIndexOfGroup(const QString& groupName) const;
std::unordered_map<amuse::SongId, QString> m_midiFiles; std::unordered_map<amuse::SongId, QString> m_midiFiles;
public: public:
@ -133,6 +192,13 @@ public:
m_nullChild->m_row = int(m_children.size()); m_nullChild->m_row = int(m_children.size());
return static_cast<T&>(*m_children.back()); return static_cast<T&>(*m_children.back());
} }
template<class T, class... _Args>
T& makeChildAtIdx(int idx, _Args&&... args)
{
auto tok = amuse::MakeObj<T>(this, idx, std::forward<_Args>(args)...);
insertChild(idx, tok.get());
return static_cast<T&>(*tok);
}
bool depthTraverse(const std::function<bool(INode* node)>& func) bool depthTraverse(const std::function<bool(INode* node)>& func)
{ {
@ -154,6 +220,9 @@ public:
virtual QString text() const = 0; virtual QString text() const = 0;
virtual QIcon icon() const = 0; virtual QIcon icon() const = 0;
virtual Qt::ItemFlags flags() const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } virtual Qt::ItemFlags flags() const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; }
virtual void registerNames(const NameUndoRegistry& registry) const {}
virtual void unregisterNames(NameUndoRegistry& registry) const {}
}; };
struct NullNode : INode struct NullNode : INode
{ {
@ -176,8 +245,8 @@ public:
struct BasePoolObjectNode; struct BasePoolObjectNode;
struct GroupNode : INode struct GroupNode : INode
{ {
std::map<QString, amuse::AudioGroupDatabase>::iterator m_it; std::unordered_map<QString, amuse::AudioGroupDatabase>::iterator m_it;
GroupNode(INode* parent, int row, std::map<QString, amuse::AudioGroupDatabase>::iterator it) GroupNode(INode* parent, int row, std::unordered_map<QString, amuse::AudioGroupDatabase>::iterator it)
: INode(parent, row), m_it(it) {} : INode(parent, row), m_it(it) {}
static QIcon Icon; static QIcon Icon;
@ -201,6 +270,19 @@ public:
Type type() const { return Type::SongGroup; } Type type() const { return Type::SongGroup; }
QString text() const { return m_name; } QString text() const { return m_name; }
QIcon icon() const { return Icon; } QIcon icon() const { return Icon; }
void registerNames(const NameUndoRegistry& registry) const
{
amuse::GroupId::CurNameDB->registerPair(text().toUtf8().data(), m_id);
for (auto& p : m_index->m_midiSetups)
registry.registerSongName(p.first);
}
void unregisterNames(NameUndoRegistry& registry) const
{
amuse::GroupId::CurNameDB->remove(m_id);
for (auto& p : m_index->m_midiSetups)
registry.unregisterSongName(p.first);
}
}; };
struct SoundGroupNode : INode struct SoundGroupNode : INode
{ {
@ -214,6 +296,19 @@ public:
Type type() const { return Type::SoundGroup; } Type type() const { return Type::SoundGroup; }
QString text() const { return m_name; } QString text() const { return m_name; }
QIcon icon() const { return Icon; } QIcon icon() const { return Icon; }
void registerNames(const NameUndoRegistry& registry) const
{
amuse::GroupId::CurNameDB->registerPair(text().toUtf8().data(), m_id);
for (auto& p : m_index->m_sfxEntries)
registry.registerSFXName(p.first);
}
void unregisterNames(NameUndoRegistry& registry) const
{
amuse::GroupId::CurNameDB->remove(m_id);
for (auto& p : m_index->m_sfxEntries)
registry.unregisterSFXName(p.first);
}
}; };
struct CollectionNode : INode struct CollectionNode : INode
{ {
@ -252,6 +347,15 @@ public:
: BasePoolObjectNode(parent, row, id, ID::CurNameDB->resolveNameFromId(id).data()), m_obj(obj) {} : BasePoolObjectNode(parent, row, id, ID::CurNameDB->resolveNameFromId(id).data()), m_obj(obj) {}
Type type() const { return TP; } Type type() const { return TP; }
void registerNames(const NameUndoRegistry& registry) const
{
ID::CurNameDB->registerPair(text().toUtf8().data(), m_id);
}
void unregisterNames(NameUndoRegistry& registry) const
{
ID::CurNameDB->remove(m_id);
}
}; };
using SoundMacroNode = PoolObjectNode<amuse::SoundMacroId, amuse::SoundMacro, INode::Type::SoundMacro>; using SoundMacroNode = PoolObjectNode<amuse::SoundMacroId, amuse::SoundMacro, INode::Type::SoundMacro>;
using ADSRNode = PoolObjectNode<amuse::TableId, std::unique_ptr<amuse::ITable>, INode::Type::ADSR>; using ADSRNode = PoolObjectNode<amuse::TableId, std::unique_ptr<amuse::ITable>, INode::Type::ADSR>;
@ -263,6 +367,7 @@ public:
amuse::ObjToken<RootNode> m_root; amuse::ObjToken<RootNode> m_root;
bool m_needsReset = false; bool m_needsReset = false;
void _buildGroupNode(GroupNode& gn);
void _resetModelData(); void _resetModelData();
public: public:
@ -289,11 +394,21 @@ public:
INode* node(const QModelIndex& index) const; INode* node(const QModelIndex& index) const;
GroupNode* getGroupNode(INode* node) const; GroupNode* getGroupNode(INode* node) const;
bool canEdit(const QModelIndex& index) const; bool canEdit(const QModelIndex& index) const;
void _undoDel(const QModelIndex& index, amuse::ObjToken<ProjectModel::INode> node); void _undoDel(const QModelIndex& index, amuse::ObjToken<ProjectModel::INode> node, const NameUndoRegistry& nameReg);
amuse::ObjToken<ProjectModel::INode> _redoDel(const QModelIndex& index); amuse::ObjToken<ProjectModel::INode> _redoDel(const QModelIndex& index, NameUndoRegistry& nameReg);
void del(const QModelIndex& index); void del(const QModelIndex& index);
RootNode* rootNode() const { return m_root.get(); } RootNode* rootNode() const { return m_root.get(); }
GroupNode* newSubproject(const QString& name, UIMessenger& messenger);
SoundGroupNode* newSoundGroup(GroupNode* group, const QString& name, UIMessenger& messenger);
SongGroupNode* newSongGroup(GroupNode* group, const QString& name, UIMessenger& messenger);
SoundMacroNode* newSoundMacro(GroupNode* group, const QString& name, UIMessenger& messenger,
const SoundMacroTemplateEntry* templ = nullptr);
ADSRNode* newADSR(GroupNode* group, const QString& name, UIMessenger& messenger);
CurveNode* newCurve(GroupNode* group, const QString& name, UIMessenger& messenger);
KeymapNode* newKeymap(GroupNode* group, const QString& name, UIMessenger& messenger);
LayersNode* newLayers(GroupNode* group, const QString& name, UIMessenger& messenger);
const QDir& dir() const { return m_dir; } const QDir& dir() const { return m_dir; }
QString path() const { return m_dir.path(); } QString path() const { return m_dir.path(); }
NullItemProxyModel* getNullProxy() { return &m_nullProxy; } NullItemProxyModel* getNullProxy() { return &m_nullProxy; }
@ -303,6 +418,8 @@ public:
GroupNode* getGroupOfSong(amuse::SongId id) const; GroupNode* getGroupOfSong(amuse::SongId id) const;
QString getMIDIPathOfSong(amuse::SongId id) const; QString getMIDIPathOfSong(amuse::SongId id) const;
void setMIDIPathOfSong(amuse::SongId id, const QString& path); void setMIDIPathOfSong(amuse::SongId id, const QString& path);
void setIdDatabases(INode* context) const;
}; };

View File

@ -1,5 +1,6 @@
#include "SongGroupEditor.hpp" #include "SongGroupEditor.hpp"
#include "MainWindow.hpp" #include "MainWindow.hpp"
#include "amuse/SongConverter.hpp"
PageObjectDelegate::PageObjectDelegate(QObject* parent) PageObjectDelegate::PageObjectDelegate(QObject* parent)
: QStyledItemDelegate(parent) {} : QStyledItemDelegate(parent) {}
@ -55,7 +56,10 @@ void PageObjectDelegate::objIndexChanged()
void MIDIFileFieldWidget::buttonPressed() void MIDIFileFieldWidget::buttonPressed()
{ {
m_dialog.setDirectory(QFileInfo(g_MainWindow->projectModel()->dir().absoluteFilePath(m_le.text())).path()); if (m_le.text().isEmpty())
m_dialog.setDirectory(g_MainWindow->projectModel()->dir());
else
m_dialog.setDirectory(QFileInfo(g_MainWindow->projectModel()->dir().absoluteFilePath(m_le.text())).path());
m_dialog.open(this, SLOT(fileDialogOpened(const QString&))); m_dialog.open(this, SLOT(fileDialogOpened(const QString&)));
} }
@ -721,15 +725,15 @@ PageTableView::PageTableView(QWidget* parent)
void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* table) void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* table)
{ {
{ {
m_listView.setModel(list); m_listView->setModel(list);
auto hheader = m_listView.horizontalHeader(); auto hheader = m_listView->horizontalHeader();
hheader->setMinimumSectionSize(200); hheader->setMinimumSectionSize(200);
hheader->resizeSection(0, 200); hheader->resizeSection(0, 200);
hheader->setSectionResizeMode(1, QHeaderView::Stretch); hheader->setSectionResizeMode(1, QHeaderView::Stretch);
} }
{ {
m_tableView.setModel(table); m_tableView->setModel(table);
auto hheader = m_tableView.horizontalHeader(); auto hheader = m_tableView->horizontalHeader();
hheader->setSectionResizeMode(QHeaderView::Stretch); hheader->setSectionResizeMode(QHeaderView::Stretch);
} }
} }
@ -737,8 +741,8 @@ void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* tabl
void SetupTableView::deleteSelection() void SetupTableView::deleteSelection()
{ {
QModelIndexList list; QModelIndexList list;
while (!(list = m_listView.selectionModel()->selectedRows()).isEmpty()) while (!(list = m_listView->selectionModel()->selectedRows()).isEmpty())
m_listView.model()->removeRow(list.back().row()); m_listView->model()->removeRow(list.back().row());
} }
void SetupTableView::showEvent(QShowEvent* event) void SetupTableView::showEvent(QShowEvent* event)
@ -747,30 +751,30 @@ void SetupTableView::showEvent(QShowEvent* event)
} }
SetupTableView::SetupTableView(QWidget* parent) SetupTableView::SetupTableView(QWidget* parent)
: QSplitter(parent), m_listView(this), m_tableView(this) : QSplitter(parent), m_listView(new QTableView), m_tableView(new QTableView)
{ {
setChildrenCollapsible(false); setChildrenCollapsible(false);
setStretchFactor(0, 1); setStretchFactor(0, 1);
setStretchFactor(1, 0); setStretchFactor(1, 0);
addWidget(&m_listView); addWidget(m_listView);
addWidget(&m_tableView); addWidget(m_tableView);
m_listView.setSelectionBehavior(QAbstractItemView::SelectRows); m_listView->setSelectionBehavior(QAbstractItemView::SelectRows);
m_listView.setSelectionMode(QAbstractItemView::ExtendedSelection); m_listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_listView.setGridStyle(Qt::NoPen); m_listView->setGridStyle(Qt::NoPen);
m_listView.setItemDelegateForColumn(1, &m_midiDelegate); m_listView->setItemDelegateForColumn(1, &m_midiDelegate);
m_tableView.setSelectionMode(QAbstractItemView::NoSelection); m_tableView->setSelectionMode(QAbstractItemView::NoSelection);
m_tableView.setGridStyle(Qt::NoPen); m_tableView->setGridStyle(Qt::NoPen);
m_127Delegate.setItemEditorFactory(&m_127Factory); m_127Delegate.setItemEditorFactory(&m_127Factory);
m_tableView.setItemDelegateForColumn(0, &m_127Delegate); m_tableView->setItemDelegateForColumn(0, &m_127Delegate);
m_tableView.setItemDelegateForColumn(1, &m_127Delegate); m_tableView->setItemDelegateForColumn(1, &m_127Delegate);
m_tableView.setItemDelegateForColumn(2, &m_127Delegate); m_tableView->setItemDelegateForColumn(2, &m_127Delegate);
m_tableView.setItemDelegateForColumn(3, &m_127Delegate); m_tableView->setItemDelegateForColumn(3, &m_127Delegate);
m_tableView.setItemDelegateForColumn(4, &m_127Delegate); m_tableView->setItemDelegateForColumn(4, &m_127Delegate);
} }
void ColoredTabBarStyle::drawControl(QStyle::ControlElement element, const QStyleOption *option, void ColoredTabBarStyle::drawControl(QStyle::ControlElement element, const QStyleOption *option,
@ -797,10 +801,10 @@ void ColoredTabBarStyle::drawControl(QStyle::ControlElement element, const QStyl
} }
ColoredTabBar::ColoredTabBar(QWidget* parent) ColoredTabBar::ColoredTabBar(QWidget* parent)
: QTabBar(parent), m_style(style()) : QTabBar(parent), m_style(new ColoredTabBarStyle(style()))
{ {
setDrawBase(false); setDrawBase(false);
setStyle(&m_style); setStyle(m_style);
} }
ColoredTabWidget::ColoredTabWidget(QWidget* parent) ColoredTabWidget::ColoredTabWidget(QWidget* parent)
@ -809,6 +813,58 @@ ColoredTabWidget::ColoredTabWidget(QWidget* parent)
setTabBar(&m_tabBar); setTabBar(&m_tabBar);
} }
void MIDIPlayerWidget::clicked()
{
if (!m_seq)
{
m_seq = g_MainWindow->startSong(m_groupId, m_songId, m_arrData.data());
if (m_seq)
{
m_playAction.setText(tr("Stop"));
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconStop.svg")));
}
}
else
{
stopped();
}
}
void MIDIPlayerWidget::stopped()
{
m_seq->stopSong();
m_seq.reset();
m_playAction.setText(tr("Play"));
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconSoundMacro.svg")));
}
void MIDIPlayerWidget::resizeEvent(QResizeEvent* event)
{
m_button.setGeometry(event->size().width() - event->size().height(), 0, event->size().height(), event->size().height());
}
void MIDIPlayerWidget::mouseDoubleClickEvent(QMouseEvent* event)
{
qobject_cast<QTableView*>(parentWidget()->parentWidget())->setIndexWidget(m_index, nullptr);
event->ignore();
}
MIDIPlayerWidget::~MIDIPlayerWidget()
{
if (m_seq)
m_seq->stopSong();
}
MIDIPlayerWidget::MIDIPlayerWidget(QModelIndex index, amuse::GroupId gid, amuse::SongId id,
std::vector<uint8_t>&& arrData, QWidget* parent)
: QWidget(parent), m_button(this), m_playAction(tr("Play")), m_index(index),
m_groupId(gid), m_songId(id), m_arrData(std::move(arrData))
{
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconSoundMacro.svg")));
m_button.setDefaultAction(&m_playAction);
connect(&m_playAction, SIGNAL(triggered()), this, SLOT(clicked()));
}
bool SongGroupEditor::loadData(ProjectModel::SongGroupNode* node) bool SongGroupEditor::loadData(ProjectModel::SongGroupNode* node)
{ {
m_normPages.loadData(node); m_normPages.loadData(node);
@ -834,8 +890,7 @@ ProjectModel::INode* SongGroupEditor::currentNode() const
void SongGroupEditor::resizeEvent(QResizeEvent* ev) void SongGroupEditor::resizeEvent(QResizeEvent* ev)
{ {
m_tabs.setGeometry(QRect({}, ev->size())); m_tabs.setGeometry(QRect({}, ev->size()));
m_addButton.move(0, ev->size().height() - 32); m_addRemoveButtons.move(0, ev->size().height() - 32);
m_removeButton.move(32, ev->size().height() - 32);
} }
void SongGroupEditor::doAdd() void SongGroupEditor::doAdd()
@ -848,34 +903,37 @@ void SongGroupEditor::doAdd()
else else
table->model()->insertRow(idx.row()); table->model()->insertRow(idx.row());
if (PageTableView* ctable = qobject_cast<PageTableView*>(table)) if (PageTableView* ctable = qobject_cast<PageTableView*>(table))
m_addAction.setDisabled(ctable->model()->rowCount() >= 128); m_addRemoveButtons.addAction()->setDisabled(ctable->model()->rowCount() >= 128);
} }
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget())) else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
{ {
QModelIndex idx = table->m_listView.selectionModel()->currentIndex(); QModelIndex idx = table->m_listView->selectionModel()->currentIndex();
if (!idx.isValid()) if (!idx.isValid())
table->m_listView.model()->insertRow(table->m_listView.model()->rowCount() - 1); table->m_listView->model()->insertRow(table->m_listView->model()->rowCount() - 1);
else else
table->m_listView.model()->insertRow(idx.row()); table->m_listView->model()->insertRow(idx.row());
} }
} }
void SongGroupEditor::doSelectionChanged(const QItemSelection& selected) void SongGroupEditor::doSelectionChanged()
{ {
m_removeAction.setDisabled(selected.isEmpty()); if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
m_addRemoveButtons.removeAction()->setDisabled(table->selectionModel()->selectedRows().isEmpty());
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
m_addRemoveButtons.removeAction()->setDisabled(table->m_listView->selectionModel()->selectedRows().isEmpty());
g_MainWindow->updateFocus(); g_MainWindow->updateFocus();
} }
void SongGroupEditor::doSetupSelectionChanged(const QItemSelection& selected) void SongGroupEditor::doSetupSelectionChanged()
{ {
doSelectionChanged(selected); doSelectionChanged();
if (selected.indexes().isEmpty() || m_setupList.m_sorted.empty()) if (m_setupTable->m_listView->selectionModel()->selectedRows().isEmpty() || m_setupList.m_sorted.empty())
{ {
m_setup.unloadData(); m_setup.unloadData();
} }
else else
{ {
auto entry = m_setupList.m_sorted[selected.indexes().last().row()]; auto entry = m_setupList.m_sorted[m_setupTable->m_listView->selectionModel()->selectedRows().last().row()];
m_setup.loadData(&*entry.m_it); m_setup.loadData(&*entry.m_it);
} }
} }
@ -884,13 +942,13 @@ void SongGroupEditor::currentTabChanged(int idx)
{ {
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget())) if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
{ {
m_addAction.setDisabled(table->model()->rowCount() >= 128); m_addRemoveButtons.addAction()->setDisabled(table->model()->rowCount() >= 128);
doSelectionChanged(table->selectionModel()->selection()); doSelectionChanged();
} }
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget())) else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
{ {
m_addAction.setDisabled(false); m_addRemoveButtons.addAction()->setDisabled(false);
doSelectionChanged(table->m_listView.selectionModel()->selection()); doSelectionChanged();
} }
} }
@ -912,12 +970,74 @@ void SongGroupEditor::modelAboutToBeReset()
m_setup.unloadData(); m_setup.unloadData();
} }
static std::vector<uint8_t> LoadSongFile(QString path)
{
QFileInfo fi(path);
if (!fi.isFile())
return {};
std::vector<uint8_t> data;
{
QFile f(path);
if (!f.open(QFile::ReadOnly))
return {};
auto d = f.readAll();
data.resize(d.size());
memcpy(&data[0], d.data(), d.size());
}
if (!memcmp(data.data(), "MThd", 4))
{
data = amuse::SongConverter::MIDIToSong(data, 1, true);
}
else
{
bool isBig;
if (amuse::SongState::DetectVersion(data.data(), isBig) < 0)
return {};
}
return data;
}
void SongGroupEditor::setupDataChanged()
{
int idx = 0;
for (const auto& p : m_setupList.m_sorted)
{
QString path = g_MainWindow->projectModel()->getMIDIPathOfSong(p.m_it->first);
QModelIndex index = m_setupList.index(idx, 1);
if (path.isEmpty())
{
m_setupTable->m_listView->setIndexWidget(index, nullptr);
}
else
{
MIDIPlayerWidget* w = qobject_cast<MIDIPlayerWidget*>(m_setupTable->m_listView->indexWidget(index));
if (!w || w->songId() != p.m_it->first)
{
std::vector<uint8_t> arrData = LoadSongFile(g_MainWindow->projectModel()->dir().absoluteFilePath(path));
if (!arrData.empty())
{
MIDIPlayerWidget* newW = new MIDIPlayerWidget(index, m_setupList.m_node->m_id, p.m_it->first, std::move(arrData));
m_setupTable->m_listView->setIndexWidget(index, newW);
}
else
{
m_setupTable->m_listView->setIndexWidget(index, nullptr);
}
}
}
++idx;
}
}
bool SongGroupEditor::isItemEditEnabled() const bool SongGroupEditor::isItemEditEnabled() const
{ {
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget())) if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
return table->hasFocus() && !table->selectionModel()->selectedRows().isEmpty(); return table->hasFocus() && !table->selectionModel()->selectedRows().isEmpty();
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget())) else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
return table->m_listView.hasFocus() && !table->m_listView.selectionModel()->selectedRows().isEmpty(); return table->m_listView->hasFocus() && !table->m_listView->selectionModel()->selectedRows().isEmpty();
return false; return false;
} }
@ -946,42 +1066,44 @@ void SongGroupEditor::itemDeleteAction()
SongGroupEditor::SongGroupEditor(QWidget* parent) SongGroupEditor::SongGroupEditor(QWidget* parent)
: EditorWidget(parent), m_normPages(false, this), m_drumPages(true, this), m_setup(this), : EditorWidget(parent), m_normPages(false, this), m_drumPages(true, this), m_setup(this),
m_normTable(this), m_drumTable(this), m_setupTable(this), m_tabs(this), m_normTable(new PageTableView), m_drumTable(new PageTableView),
m_addAction(tr("Add Row")), m_addButton(this), m_removeAction(tr("Remove Row")), m_removeButton(this) m_setupTable(new SetupTableView), m_tabs(this), m_addRemoveButtons(this)
{ {
m_tabs.addTab(&m_normTable, tr("Normal Pages")); m_tabs.addTab(m_normTable, tr("Normal Pages"));
m_tabs.addTab(&m_drumTable, tr("Drum Pages")); m_tabs.addTab(m_drumTable, tr("Drum Pages"));
m_tabs.addTab(&m_setupTable, tr("MIDI Setups")); m_tabs.addTab(m_setupTable, tr("MIDI Setups"));
connect(&m_tabs, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged(int))); connect(&m_tabs, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged(int)));
m_normTable->setModel(&m_normPages);
m_drumTable->setModel(&m_drumPages);
m_setupTable->setModel(&m_setupList, &m_setup);
connect(m_normTable->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
this, SLOT(doSelectionChanged()));
connect(m_drumTable->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
this, SLOT(doSelectionChanged()));
connect(m_setupTable->m_listView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
this, SLOT(doSetupSelectionChanged()));
connect(&m_setupList, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), connect(&m_setupList, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
this, SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int))); this, SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
connect(&m_setupList, SIGNAL(modelAboutToBeReset()), connect(&m_setupList, SIGNAL(modelAboutToBeReset()),
this, SLOT(modelAboutToBeReset())); this, SLOT(modelAboutToBeReset()));
connect(&m_setupList, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
this, SLOT(setupDataChanged()));
connect(&m_setupList, SIGNAL(rowsRemoved(const QModelIndex&, int, int)),
this, SLOT(setupDataChanged()));
connect(&m_setupList, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
this, SLOT(setupDataChanged()));
connect(&m_setupList, SIGNAL(layoutChanged(const QList<QPersistentModelIndex>&, QAbstractItemModel::LayoutChangeHint)),
this, SLOT(setupDataChanged()));
connect(&m_setupList, SIGNAL(modelReset()),
this, SLOT(setupDataChanged()));
m_normTable.setModel(&m_normPages); m_addRemoveButtons.addAction()->setToolTip(tr("Add new page entry"));
m_drumTable.setModel(&m_drumPages); connect(m_addRemoveButtons.addAction(), SIGNAL(triggered(bool)), this, SLOT(doAdd()));
m_setupTable.setModel(&m_setupList, &m_setup); m_addRemoveButtons.removeAction()->setToolTip(tr("Remove selected page entries"));
connect(m_normTable.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), connect(m_addRemoveButtons.removeAction(), SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));
this, SLOT(doSelectionChanged(const QItemSelection&)));
connect(m_drumTable.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
this, SLOT(doSelectionChanged(const QItemSelection&)));
connect(m_setupTable.m_listView.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
this, SLOT(doSetupSelectionChanged(const QItemSelection&)));
m_addAction.setIcon(QIcon(QStringLiteral(":/icons/IconAdd.svg")));
m_addButton.setDefaultAction(&m_addAction);
m_addAction.setToolTip(tr("Add new page entry"));
m_addButton.setFixedSize(32, 32);
connect(&m_addAction, SIGNAL(triggered(bool)), this, SLOT(doAdd()));
m_removeAction.setIcon(QIcon(QStringLiteral(":/icons/IconRemove.svg")));
m_removeButton.setDefaultAction(&m_removeAction);
m_removeAction.setToolTip(tr("Remove selected page entries"));
m_removeButton.setFixedSize(32, 32);
connect(&m_removeAction, SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));
m_removeAction.setEnabled(false);
m_tabs.setCurrentIndex(0); m_tabs.setCurrentIndex(0);
} }

View File

@ -14,6 +14,7 @@
#include <QPushButton> #include <QPushButton>
#include <QFileDialog> #include <QFileDialog>
#include <QProxyStyle> #include <QProxyStyle>
#include "amuse/Sequencer.hpp"
class PageObjectDelegate : public QStyledItemDelegate class PageObjectDelegate : public QStyledItemDelegate
{ {
@ -176,8 +177,8 @@ class SetupTableView : public QSplitter
{ {
Q_OBJECT Q_OBJECT
friend class SongGroupEditor; friend class SongGroupEditor;
QTableView m_listView; QTableView* m_listView;
QTableView m_tableView; QTableView* m_tableView;
MIDIFileDelegate m_midiDelegate; MIDIFileDelegate m_midiDelegate;
RangedValueFactory<0, 127> m_127Factory; RangedValueFactory<0, 127> m_127Factory;
QStyledItemDelegate m_127Delegate; QStyledItemDelegate m_127Delegate;
@ -199,7 +200,7 @@ public:
class ColoredTabBar : public QTabBar class ColoredTabBar : public QTabBar
{ {
Q_OBJECT Q_OBJECT
ColoredTabBarStyle m_style; ColoredTabBarStyle* m_style;
public: public:
explicit ColoredTabBar(QWidget* parent = Q_NULLPTR); explicit ColoredTabBar(QWidget* parent = Q_NULLPTR);
}; };
@ -212,6 +213,29 @@ public:
explicit ColoredTabWidget(QWidget* parent = Q_NULLPTR); explicit ColoredTabWidget(QWidget* parent = Q_NULLPTR);
}; };
class MIDIPlayerWidget : public QWidget
{
Q_OBJECT
QAction m_playAction;
QToolButton m_button;
QModelIndex m_index;
amuse::GroupId m_groupId;
amuse::SongId m_songId;
std::vector<uint8_t> m_arrData;
amuse::ObjToken<amuse::Sequencer> m_seq;
public:
explicit MIDIPlayerWidget(QModelIndex index, amuse::GroupId gid, amuse::SongId id,
std::vector<uint8_t>&& arrData, QWidget* parent = Q_NULLPTR);
~MIDIPlayerWidget();
amuse::SongId songId() const { return m_songId; }
amuse::Sequencer* sequencer() const { return m_seq.get(); }
void stopped();
void resizeEvent(QResizeEvent* event);
void mouseDoubleClickEvent(QMouseEvent* event);
public slots:
void clicked();
};
class SongGroupEditor : public EditorWidget class SongGroupEditor : public EditorWidget
{ {
Q_OBJECT Q_OBJECT
@ -219,34 +243,33 @@ class SongGroupEditor : public EditorWidget
PageModel m_drumPages; PageModel m_drumPages;
SetupListModel m_setupList; SetupListModel m_setupList;
SetupModel m_setup; SetupModel m_setup;
PageTableView m_normTable; PageTableView* m_normTable;
PageTableView m_drumTable; PageTableView* m_drumTable;
SetupTableView m_setupTable; SetupTableView* m_setupTable;
ColoredTabWidget m_tabs; ColoredTabWidget m_tabs;
QAction m_addAction; AddRemoveButtons m_addRemoveButtons;
QToolButton m_addButton;
QAction m_removeAction;
QToolButton m_removeButton;
public: public:
explicit SongGroupEditor(QWidget* parent = Q_NULLPTR); explicit SongGroupEditor(QWidget* parent = Q_NULLPTR);
bool loadData(ProjectModel::SongGroupNode* node); bool loadData(ProjectModel::SongGroupNode* node);
void unloadData(); void unloadData();
ProjectModel::INode* currentNode() const; ProjectModel::INode* currentNode() const;
void setEditorEnabled(bool en) {}
void resizeEvent(QResizeEvent* ev); void resizeEvent(QResizeEvent* ev);
QTableView* getSetupListView() const { return m_setupTable->m_listView; }
bool isItemEditEnabled() const;
public slots: public slots:
void doAdd(); void doAdd();
void doSelectionChanged(const QItemSelection& selected); void doSelectionChanged();
void doSetupSelectionChanged(const QItemSelection& selected); void doSetupSelectionChanged();
void currentTabChanged(int idx); void currentTabChanged(int idx);
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
void modelAboutToBeReset(); void modelAboutToBeReset();
void setupDataChanged();
bool isItemEditEnabled() const;
void itemCutAction(); void itemCutAction();
void itemCopyAction(); void itemCopyAction();
void itemPasteAction(); void itemPasteAction();
void itemDeleteAction(); void itemDeleteAction();
}; };
#endif //AMUSE_SONG_GROUP_EDITOR_HPP #endif //AMUSE_SONG_GROUP_EDITOR_HPP

View File

@ -1,12 +1,494 @@
#include "SoundGroupEditor.hpp" #include "SoundGroupEditor.hpp"
#include "MainWindow.hpp"
bool SoundGroupEditor::loadData(ProjectModel::SoundGroupNode* node) SFXObjectDelegate::SFXObjectDelegate(QObject* parent)
: QStyledItemDelegate(parent) {}
QWidget* SFXObjectDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{ {
const SFXModel* model = static_cast<const SFXModel*>(index.model());
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get());
EditorFieldPageObjectNode* cb = new EditorFieldPageObjectNode(group, parent);
connect(cb, SIGNAL(currentIndexChanged(int)), this, SLOT(objIndexChanged()));
return cb;
}
void SFXObjectDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
const SFXModel* model = static_cast<const SFXModel*>(index.model());
auto entry = model->m_sorted[index.row()];
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get());
ProjectModel::BasePoolObjectNode* node = group->pageObjectNodeOfId(entry->second.objId.id);
int idx = 0;
if (node)
idx = g_MainWindow->projectModel()->getPageObjectProxy()->mapFromSource(g_MainWindow->projectModel()->index(node)).row();
static_cast<EditorFieldPageObjectNode*>(editor)->setCurrentIndex(idx);
if (static_cast<EditorFieldPageObjectNode*>(editor)->shouldPopupOpen())
static_cast<EditorFieldPageObjectNode*>(editor)->showPopup();
}
void SFXObjectDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const
{
const SFXModel* model = static_cast<const SFXModel*>(m);
auto entry = model->m_sorted[index.row()];
int idx = static_cast<EditorFieldPageObjectNode*>(editor)->currentIndex();
if (idx == 0)
{
entry->second.objId.id = amuse::ObjectId();
}
else
{
ProjectModel::BasePoolObjectNode* node = static_cast<ProjectModel::BasePoolObjectNode*>(
g_MainWindow->projectModel()->node(
g_MainWindow->projectModel()->getPageObjectProxy()->mapToSource(
g_MainWindow->projectModel()->getPageObjectProxy()->index(idx, 0,
static_cast<EditorFieldPageObjectNode*>(editor)->rootModelIndex()))));
entry->second.objId.id = node->id();
}
emit m->dataChanged(index, index);
}
void SFXObjectDelegate::objIndexChanged()
{
emit commitData(static_cast<QWidget*>(sender()));
}
std::unordered_map<amuse::SFXId, amuse::SFXGroupIndex::SFXEntry>& SFXModel::_getMap() const
{
return m_node->m_index->m_sfxEntries;
}
void SFXModel::_buildSortedList()
{
m_sorted.clear();
if (!m_node)
return;
auto& map = _getMap();
m_sorted.reserve(map.size());
for (auto it = map.begin() ; it != map.end() ; ++it)
m_sorted.emplace_back(it);
std::sort(m_sorted.begin(), m_sorted.end());
}
QModelIndex SFXModel::_indexOfSFX(amuse::SFXId sfx) const
{
auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), sfx);
if (search == m_sorted.cend() || search->m_it->first != sfx)
return QModelIndex();
else
return createIndex(search - m_sorted.begin(), 0);
}
int SFXModel::_hypotheticalIndexOfSFX(const std::string& sfxName) const
{
auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), sfxName);
return search - m_sorted.begin();
}
void SFXModel::loadData(ProjectModel::SoundGroupNode* node)
{
beginResetModel();
m_node = node;
_buildSortedList();
endResetModel();
}
void SFXModel::unloadData()
{
beginResetModel();
m_node.reset();
m_sorted.clear();
endResetModel();
}
int SFXModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
if (!m_node)
return 0;
return int(m_sorted.size()) + 1;
}
int SFXModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return 7;
}
QVariant SFXModel::data(const QModelIndex& index, int role) const
{
if (!m_node)
return QVariant();
if (index.row() == m_sorted.size())
return QVariant();
auto entry = m_sorted[index.row()];
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
switch (index.column())
{
case 0:
{
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
return amuse::SFXId::CurNameDB->resolveNameFromId(entry->first.id).data();
}
case 1:
{
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(m_node.get());
if (ProjectModel::BasePoolObjectNode* node = group->pageObjectNodeOfId(entry->second.objId.id))
return node->text();
return QVariant();
}
case 2:
return entry->second.priority;
case 3:
return entry->second.maxVoices;
case 4:
return entry->second.defVel;
case 5:
return entry->second.panning;
case 6:
return entry->second.defKey;
default:
break;
}
}
return QVariant();
}
bool SFXModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!m_node || role != Qt::EditRole)
return false;
auto& map = _getMap();
auto entry = m_sorted[index.row()];
switch (index.column())
{
case 0:
{
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
auto utf8key = value.toString().toUtf8();
std::unordered_map<std::string, amuse::ObjectId>::iterator idIt;
if ((idIt = amuse::SFXId::CurNameDB->m_stringToId.find(utf8key.data())) != amuse::SFXId::CurNameDB->m_stringToId.cend())
{
if (idIt->second == entry->first)
return false;
QMessageBox::critical(g_MainWindow, tr("SFX Conflict"),
tr("SFX %1 is already defined in project").arg(value.toString()));
return false;
}
emit layoutAboutToBeChanged();
amuse::SFXId::CurNameDB->rename(entry.m_it->first, utf8key.data());
_buildSortedList();
QModelIndex newIndex = _indexOfSFX(entry.m_it->first);
changePersistentIndex(index, newIndex);
emit layoutChanged();
emit dataChanged(newIndex, newIndex, {Qt::DisplayRole, Qt::EditRole});
return true;
}
case 2:
entry->second.priority = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 3:
entry->second.maxVoices = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 4:
entry->second.defVel = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 5:
entry->second.panning = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 6:
entry->second.defKey = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
default:
break;
}
return false; return false;
} }
SoundGroupEditor::SoundGroupEditor(QWidget* parent) QVariant SFXModel::headerData(int section, Qt::Orientation orientation, int role) const
: EditorWidget(parent) {
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
switch (section)
{
case 0:
return tr("SFX");
case 1:
return tr("Object");
case 2:
return tr("Priority");
case 3:
return tr("Max Voices");
case 4:
return tr("Velocity");
case 5:
return tr("Panning");
case 6:
return tr("Key");
default:
break;
}
}
return QVariant();
}
Qt::ItemFlags SFXModel::flags(const QModelIndex& index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
if (index.row() == m_sorted.size())
return Qt::NoItemFlags;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
}
bool SFXModel::insertRows(int row, int count, const QModelIndex& parent)
{
if (!m_node)
return false;
auto& map = _getMap();
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
for (int i = 0; i < count; ++i)
{
amuse::ObjectId sfxId = amuse::SFXId::CurNameDB->generateId(amuse::NameDB::Type::SFX);
std::string sfxName = amuse::SFXId::CurNameDB->generateName(sfxId, amuse::NameDB::Type::SFX);
int insertIdx = _hypotheticalIndexOfSFX(sfxName);
beginInsertRows(parent, insertIdx, insertIdx);
amuse::SFXId::CurNameDB->registerPair(sfxName, sfxId);
map.emplace(std::make_pair(sfxId, amuse::SFXGroupIndex::SFXEntry{}));
_buildSortedList();
endInsertRows();
++row;
}
return true;
}
bool SFXModel::removeRows(int row, int count, const QModelIndex& parent)
{
if (!m_node)
return false;
auto& map = _getMap();
beginRemoveRows(parent, row, row + count - 1);
std::vector<amuse::SFXId> removeSFXs;
removeSFXs.reserve(count);
for (int i = 0; i < count; ++i)
removeSFXs.push_back(m_sorted[row+i].m_it->first);
for (amuse::SFXId sfx : removeSFXs)
map.erase(sfx);
_buildSortedList();
endRemoveRows();
return true;
}
SFXModel::SFXModel(QObject* parent)
: QAbstractTableModel(parent)
{}
void SFXTableView::deleteSelection()
{
QModelIndexList list;
while (!(list = selectionModel()->selectedRows()).isEmpty())
model()->removeRow(list.back().row());
}
void SFXTableView::setModel(QAbstractItemModel* model)
{
QTableView::setModel(model);
horizontalHeader()->setMinimumSectionSize(75);
horizontalHeader()->resizeSection(0, 200);
horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed);
horizontalHeader()->resizeSection(2, 75);
horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
horizontalHeader()->resizeSection(3, 100);
horizontalHeader()->setSectionResizeMode(4, QHeaderView::Fixed);
horizontalHeader()->resizeSection(4, 75);
horizontalHeader()->setSectionResizeMode(5, QHeaderView::Fixed);
horizontalHeader()->resizeSection(5, 75);
horizontalHeader()->setSectionResizeMode(6, QHeaderView::Fixed);
horizontalHeader()->resizeSection(6, 75);
}
SFXTableView::SFXTableView(QWidget* parent)
: QTableView(parent)
{
setSelectionBehavior(QAbstractItemView::SelectRows);
setSelectionMode(QAbstractItemView::ExtendedSelection);
setGridStyle(Qt::NoPen);
m_127Delegate.setItemEditorFactory(&m_127Factory);
m_255Delegate.setItemEditorFactory(&m_255Factory);
setItemDelegateForColumn(1, &m_sfxDelegate);
setItemDelegateForColumn(2, &m_255Delegate);
setItemDelegateForColumn(3, &m_255Delegate);
setItemDelegateForColumn(4, &m_127Delegate);
setItemDelegateForColumn(5, &m_127Delegate);
setItemDelegateForColumn(6, &m_127Delegate);
}
void SFXPlayerWidget::clicked()
{
if (!m_vox)
{
m_vox = g_MainWindow->startSFX(m_groupId, m_sfxId);
if (m_vox)
{
m_playAction.setText(tr("Stop"));
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconStop.svg")));
}
}
else
{
stopped();
}
}
void SFXPlayerWidget::stopped()
{
m_vox->keyOff();
m_vox.reset();
m_playAction.setText(tr("Play"));
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconSoundMacro.svg")));
}
void SFXPlayerWidget::resizeEvent(QResizeEvent* event)
{
m_button.setGeometry(event->size().width() - event->size().height(), 0, event->size().height(), event->size().height());
}
void SFXPlayerWidget::mouseDoubleClickEvent(QMouseEvent* event)
{
qobject_cast<QTableView*>(parentWidget()->parentWidget())->setIndexWidget(m_index, nullptr);
event->ignore();
}
SFXPlayerWidget::~SFXPlayerWidget()
{
if (m_vox)
m_vox->keyOff();
}
SFXPlayerWidget::SFXPlayerWidget(QModelIndex index, amuse::GroupId gid, amuse::SFXId id, QWidget* parent)
: QWidget(parent), m_button(this), m_playAction(tr("Play")), m_index(index),
m_groupId(gid), m_sfxId(id)
{
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconSoundMacro.svg")));
m_button.setDefaultAction(&m_playAction);
connect(&m_playAction, SIGNAL(triggered()), this, SLOT(clicked()));
}
bool SoundGroupEditor::loadData(ProjectModel::SoundGroupNode* node)
{
m_sfxs.loadData(node);
return true;
}
void SoundGroupEditor::unloadData()
{
m_sfxs.unloadData();
}
ProjectModel::INode* SoundGroupEditor::currentNode() const
{
return m_sfxs.m_node.get();
}
void SoundGroupEditor::resizeEvent(QResizeEvent* ev)
{
m_sfxTable->setGeometry(QRect({}, ev->size()));
m_addRemoveButtons.move(0, ev->size().height() - 32);
}
bool SoundGroupEditor::isItemEditEnabled() const
{
return m_sfxTable->hasFocus() && !m_sfxTable->selectionModel()->selectedRows().isEmpty();
}
void SoundGroupEditor::doAdd()
{
QModelIndex idx = m_sfxTable->selectionModel()->currentIndex();
if (!idx.isValid())
m_sfxTable->model()->insertRow(m_sfxTable->model()->rowCount() - 1);
else
m_sfxTable->model()->insertRow(idx.row());
}
void SoundGroupEditor::doSelectionChanged()
{
m_addRemoveButtons.removeAction()->setDisabled(m_sfxTable->selectionModel()->selectedRows().isEmpty());
g_MainWindow->updateFocus();
}
void SoundGroupEditor::sfxDataChanged()
{
int idx = 0;
for (const auto& p : m_sfxs.m_sorted)
{
QModelIndex index = m_sfxs.index(idx, 1);
SFXPlayerWidget* w = qobject_cast<SFXPlayerWidget*>(m_sfxTable->indexWidget(index));
if (!w || w->sfxId() != p.m_it->first)
{
SFXPlayerWidget* newW = new SFXPlayerWidget(index, m_sfxs.m_node->m_id, p.m_it->first);
m_sfxTable->setIndexWidget(index, newW);
}
++idx;
}
}
void SoundGroupEditor::itemCutAction()
{ {
} }
void SoundGroupEditor::itemCopyAction()
{
}
void SoundGroupEditor::itemPasteAction()
{
}
void SoundGroupEditor::itemDeleteAction()
{
m_sfxTable->deleteSelection();
}
SoundGroupEditor::SoundGroupEditor(QWidget* parent)
: EditorWidget(parent), m_sfxs(this), m_sfxTable(new SFXTableView(this)), m_addRemoveButtons(this)
{
m_sfxTable->setModel(&m_sfxs);
connect(m_sfxTable->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
this, SLOT(doSelectionChanged()));
connect(&m_sfxs, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
this, SLOT(sfxDataChanged()));
connect(&m_sfxs, SIGNAL(rowsRemoved(const QModelIndex&, int, int)),
this, SLOT(sfxDataChanged()));
connect(&m_sfxs, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
this, SLOT(sfxDataChanged()));
connect(&m_sfxs, SIGNAL(layoutChanged(const QList<QPersistentModelIndex>&, QAbstractItemModel::LayoutChangeHint)),
this, SLOT(sfxDataChanged()));
connect(&m_sfxs, SIGNAL(modelReset()),
this, SLOT(sfxDataChanged()));
m_addRemoveButtons.addAction()->setToolTip(tr("Add new SFX entry"));
connect(m_addRemoveButtons.addAction(), SIGNAL(triggered(bool)), this, SLOT(doAdd()));
m_addRemoveButtons.removeAction()->setToolTip(tr("Remove selected SFX entries"));
connect(m_addRemoveButtons.removeAction(), SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));
}

View File

@ -2,14 +2,129 @@
#define AMUSE_SOUND_GROUP_EDITOR_HPP #define AMUSE_SOUND_GROUP_EDITOR_HPP
#include "EditorWidget.hpp" #include "EditorWidget.hpp"
#include <QStyledItemDelegate>
#include <QTableView>
#include "amuse/Voice.hpp"
class SFXObjectDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit SFXObjectDelegate(QObject* parent = Q_NULLPTR);
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
void setEditorData(QWidget* editor, const QModelIndex& index) const;
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;
private slots:
void objIndexChanged();
};
class SFXModel : public QAbstractTableModel
{
Q_OBJECT
friend class SoundGroupEditor;
friend class SFXObjectDelegate;
amuse::ObjToken<ProjectModel::SoundGroupNode> m_node;
struct Iterator
{
using ItTp = std::unordered_map<amuse::SFXId, amuse::SFXGroupIndex::SFXEntry>::iterator;
ItTp m_it;
Iterator(ItTp it) : m_it(it) {}
ItTp::pointer operator->() { return m_it.operator->(); }
bool operator<(const Iterator& other) const
{
return amuse::SFXId::CurNameDB->resolveNameFromId(m_it->first) <
amuse::SFXId::CurNameDB->resolveNameFromId(other.m_it->first);
}
bool operator<(amuse::SongId other) const
{
return amuse::SFXId::CurNameDB->resolveNameFromId(m_it->first) <
amuse::SFXId::CurNameDB->resolveNameFromId(other);
}
bool operator<(const std::string& name) const
{
return amuse::SFXId::CurNameDB->resolveNameFromId(m_it->first) < name;
}
};
std::vector<Iterator> m_sorted;
std::unordered_map<amuse::SFXId, amuse::SFXGroupIndex::SFXEntry>& _getMap() const;
void _buildSortedList();
QModelIndex _indexOfSFX(amuse::SFXId sfx) const;
int _hypotheticalIndexOfSFX(const std::string& sfxName) const;
public:
explicit SFXModel(QObject* parent = Q_NULLPTR);
void loadData(ProjectModel::SoundGroupNode* node);
void unloadData();
int rowCount(const QModelIndex& parent = QModelIndex()) const;
int columnCount(const QModelIndex& parent = QModelIndex()) const;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole);
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex& index) const;
bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex());
};
class SFXTableView : public QTableView
{
Q_OBJECT
SFXObjectDelegate m_sfxDelegate;
RangedValueFactory<0, 127> m_127Factory;
RangedValueFactory<0, 255> m_255Factory;
QStyledItemDelegate m_127Delegate, m_255Delegate;
public:
explicit SFXTableView(QWidget* parent = Q_NULLPTR);
void setModel(QAbstractItemModel* model);
void deleteSelection();
};
class SFXPlayerWidget : public QWidget
{
Q_OBJECT
QAction m_playAction;
QToolButton m_button;
QModelIndex m_index;
amuse::GroupId m_groupId;
amuse::SFXId m_sfxId;
amuse::ObjToken<amuse::Voice> m_vox;
public:
explicit SFXPlayerWidget(QModelIndex index, amuse::GroupId gid, amuse::SFXId id,
QWidget* parent = Q_NULLPTR);
~SFXPlayerWidget();
amuse::SongId sfxId() const { return m_sfxId; }
amuse::Voice* voice() const { return m_vox.get(); }
void stopped();
void resizeEvent(QResizeEvent* event);
void mouseDoubleClickEvent(QMouseEvent* event);
public slots:
void clicked();
};
class SoundGroupEditor : public EditorWidget class SoundGroupEditor : public EditorWidget
{ {
Q_OBJECT Q_OBJECT
SFXModel m_sfxs;
SFXTableView* m_sfxTable;
AddRemoveButtons m_addRemoveButtons;
public: public:
explicit SoundGroupEditor(QWidget* parent = Q_NULLPTR); explicit SoundGroupEditor(QWidget* parent = Q_NULLPTR);
bool loadData(ProjectModel::SoundGroupNode* node); bool loadData(ProjectModel::SoundGroupNode* node);
void unloadData();
ProjectModel::INode* currentNode() const;
void setEditorEnabled(bool en) {}
void resizeEvent(QResizeEvent* ev);
QTableView* getSFXListView() const { return m_sfxTable; }
bool isItemEditEnabled() const;
public slots:
void doAdd();
void doSelectionChanged();
void sfxDataChanged();
void itemCutAction();
void itemCopyAction();
void itemPasteAction();
void itemDeleteAction();
}; };
#endif //AMUSE_SOUND_GROUP_EDITOR_HPP #endif //AMUSE_SOUND_GROUP_EDITOR_HPP

View File

@ -14,8 +14,8 @@
viewBox="0 0 4.2333332 4.2333335" viewBox="0 0 4.2333332 4.2333335"
id="svg2" id="svg2"
version="1.1" version="1.1"
inkscape:version="0.91+devel+osxmenu r12922" inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="NewSoundMacro.svg"> sodipodi:docname="IconNewSoundMacro.svg">
<defs <defs
id="defs4" /> id="defs4" />
<sodipodi:namedview <sodipodi:namedview
@ -26,16 +26,16 @@
inkscape:pageopacity="0" inkscape:pageopacity="0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="30.33" inkscape:zoom="30.33"
inkscape:cx="8.5002398" inkscape:cx="2.9941402"
inkscape:cy="8.6348884" inkscape:cy="4.6784097"
inkscape:document-units="px" inkscape:document-units="px"
inkscape:current-layer="layer1" inkscape:current-layer="layer1"
showgrid="true" showgrid="true"
units="px" units="px"
inkscape:window-width="1260" inkscape:window-width="1260"
inkscape:window-height="787" inkscape:window-height="787"
inkscape:window-x="0" inkscape:window-x="606"
inkscape:window-y="23" inkscape:window-y="435"
inkscape:window-maximized="0" inkscape:window-maximized="0"
showborder="false" showborder="false"
objecttolerance="4" objecttolerance="4"
@ -45,7 +45,7 @@
type="xygrid" type="xygrid"
id="grid4173" id="grid4173"
empspacing="1" empspacing="1"
visible="false" /> visible="true" />
</sodipodi:namedview> </sodipodi:namedview>
<metadata <metadata
id="metadata7"> id="metadata7">
@ -66,14 +66,14 @@
transform="translate(0,-292.76665)"> transform="translate(0,-292.76665)">
<path <path
style="opacity:1;fill:#62ff04;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.30031049;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="opacity:1;fill:#62ff04;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.30031049;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 0.96793039,292.83382 2.60268711,1.68348 c 0.392506,0.24382 0.3952842,0.49652 0,0.73535 l -2.60268711,1.69163 C 0.68893458,297.12561 0.3672936,296.82502 0.3672936,296.49227 l 0,-3.29415 c 0,-0.33276 0.32123894,-0.54502 0.60063679,-0.3643 z" d="m 0.96793039,292.83382 2.60268711,1.68348 c 0.392506,0.24382 0.3952841,0.49652 0,0.73535 l -2.60268711,1.69163 c -0.2789936,0.18133 -0.6006367,-0.0548 -0.6006367,-0.39294 v -3.35322 c 0,-0.33276 0.3212394,-0.54502 0.6006367,-0.3643 z"
id="rect5899" id="rect5899-3"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
sodipodi:nodetypes="sccssss" /> sodipodi:nodetypes="sccssss" />
<path <path
id="path5323" id="path5323"
style="fill:#ff0000;fill-rule:evenodd;stroke:#df0000;stroke-width:0.26458332;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="fill:#ff0000;fill-rule:evenodd;stroke:#df0000;stroke-width:0.26458332;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 2.8386428,295.6133 0.9362114,0.93622 m -0.9362104,-10e-6 0.9362094,-0.9362 m -1.1301062,0.4681 1.324003,0 m -0.6620015,0.662 0,-1.324" d="m 2.8386428,295.6133 0.9362114,0.93622 m -0.9362104,-10e-6 0.9362094,-0.9362 m -1.1301062,0.4681 H 3.96875 m -0.6620015,0.662 v -1.324"
inkscape:connector-curvature="0" /> inkscape:connector-curvature="0" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -14,7 +14,7 @@
viewBox="0 0 4.2333332 4.2333335" viewBox="0 0 4.2333332 4.2333335"
id="svg2" id="svg2"
version="1.1" version="1.1"
inkscape:version="0.91+devel+osxmenu r12922" inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="IconSoundMacro.svg"> sodipodi:docname="IconSoundMacro.svg">
<defs <defs
id="defs4" /> id="defs4" />
@ -25,9 +25,9 @@
borderopacity="1.0" borderopacity="1.0"
inkscape:pageopacity="0" inkscape:pageopacity="0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="30.33" inkscape:zoom="49.27"
inkscape:cx="8.5002398" inkscape:cx="6.2572034"
inkscape:cy="8.6348884" inkscape:cy="5.0825963"
inkscape:document-units="px" inkscape:document-units="px"
inkscape:current-layer="layer1" inkscape:current-layer="layer1"
showgrid="true" showgrid="true"
@ -35,7 +35,7 @@
inkscape:window-width="1260" inkscape:window-width="1260"
inkscape:window-height="787" inkscape:window-height="787"
inkscape:window-x="0" inkscape:window-x="0"
inkscape:window-y="23" inkscape:window-y="40"
inkscape:window-maximized="0" inkscape:window-maximized="0"
showborder="false" showborder="false"
objecttolerance="4" objecttolerance="4"
@ -45,7 +45,7 @@
type="xygrid" type="xygrid"
id="grid4173" id="grid4173"
empspacing="1" empspacing="1"
visible="false" /> visible="true" />
</sodipodi:namedview> </sodipodi:namedview>
<metadata <metadata
id="metadata7"> id="metadata7">
@ -66,7 +66,7 @@
transform="translate(0,-292.76665)"> transform="translate(0,-292.76665)">
<path <path
style="opacity:1;fill:#62ff04;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.30031049;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="opacity:1;fill:#62ff04;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.30031049;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 0.96793039,292.83382 2.60268711,1.68348 c 0.392506,0.24382 0.3952842,0.49652 0,0.73535 l -2.60268711,1.69163 C 0.68893458,297.12561 0.3672936,296.82502 0.3672936,296.49227 l 0,-3.29415 c 0,-0.33276 0.32123894,-0.54502 0.60063679,-0.3643 z" d="m 0.96793039,292.83382 2.60268711,1.68348 c 0.392506,0.24382 0.3952842,0.49652 0,0.73535 l -2.60268711,1.69163 c -0.27899363,0.18133 -0.60063679,-0.0548 -0.60063679,-0.39294 l 0,-3.35322 c 0,-0.33276 0.32123948,-0.54502 0.60063679,-0.3643 z"
id="rect5899" id="rect5899"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
sodipodi:nodetypes="sccssss" /> sodipodi:nodetypes="sccssss" />

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

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>IconPaintbrush.svg</file>
<file>IconAdd.svg</file> <file>IconAdd.svg</file>
<file>IconRemove.svg</file> <file>IconRemove.svg</file>
<file>IconStop.svg</file>
</qresource> </qresource>
<qresource prefix="/bg"> <qresource prefix="/bg">
<file>FaceGrey.svg</file> <file>FaceGrey.svg</file>

View File

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

View File

@ -135,7 +135,7 @@ struct SFXGroupIndex : AudioGroupIndex
{ {
AT_DECL_DNA AT_DECL_DNA
SFXIdDNA<DNAEn> sfxId; SFXIdDNA<DNAEn> sfxId;
SoundMacroIdDNA<DNAEn> macro; PageObjectIdDNA<DNAEn> objId;
Value<atUint8> priority; Value<atUint8> priority;
Value<atUint8> maxVoices; Value<atUint8> maxVoices;
Value<atUint8> defVel; Value<atUint8> defVel;
@ -146,18 +146,18 @@ struct SFXGroupIndex : AudioGroupIndex
struct SFXEntry : BigDNA struct SFXEntry : BigDNA
{ {
AT_DECL_DNA_YAML AT_DECL_DNA_YAML
SoundMacroIdDNA<athena::Big> macro; PageObjectIdDNA<athena::Big> objId;
Value<atUint8> priority; Value<atUint8> priority = 0;
Value<atUint8> maxVoices; Value<atUint8> maxVoices = 255;
Value<atUint8> defVel; Value<atUint8> defVel = 127;
Value<atUint8> panning; Value<atUint8> panning = 64;
Value<atUint8> defKey; Value<atUint8> defKey = 60;
SFXEntry() = default; SFXEntry() = default;
template <athena::Endian DNAE> template <athena::Endian DNAE>
SFXEntry(const SFXEntryDNA<DNAE>& in) SFXEntry(const SFXEntryDNA<DNAE>& in)
: macro(in.macro.id), priority(in.priority), maxVoices(in.maxVoices), : objId(in.objId.id), priority(in.priority), maxVoices(in.maxVoices),
defVel(in.defVel), panning(in.panning), defKey(in.defKey) {} defVel(in.defVel), panning(in.panning), defKey(in.defKey) {}
template <athena::Endian DNAEn> template <athena::Endian DNAEn>
@ -165,7 +165,7 @@ struct SFXGroupIndex : AudioGroupIndex
{ {
SFXEntryDNA<DNAEn> ret; SFXEntryDNA<DNAEn> ret;
ret.sfxId.id = id; ret.sfxId.id = id;
ret.macro = macro; ret.objId = objId;
ret.priority = priority; ret.priority = priority;
ret.maxVoices = maxVoices; ret.maxVoices = maxVoices;
ret.defVel = defVel; ret.defVel = defVel;

View File

@ -617,6 +617,7 @@ struct NameDB
ObjectId generateId(Type tp) const; ObjectId generateId(Type tp) const;
static std::string generateName(ObjectId id, Type tp); static std::string generateName(ObjectId id, Type tp);
std::string generateDefaultName(Type tp) const;
std::string_view registerPair(std::string_view str, ObjectId id); std::string_view registerPair(std::string_view str, ObjectId id);
std::string_view resolveNameFromId(ObjectId id) const; std::string_view resolveNameFromId(ObjectId id) const;
ObjectId resolveIdFromName(std::string_view str) const; ObjectId resolveIdFromName(std::string_view str) const;

View File

@ -48,20 +48,20 @@ class Engine
std::list<ObjToken<Sequencer>> m_activeSequencers; std::list<ObjToken<Sequencer>> m_activeSequencers;
bool m_defaultStudioReady = false; bool m_defaultStudioReady = false;
ObjToken<Studio> m_defaultStudio; ObjToken<Studio> m_defaultStudio;
std::unordered_map<SFXId, std::tuple<AudioGroup*, int, const SFXGroupIndex::SFXEntry*>> m_sfxLookup; std::unordered_map<SFXId, std::tuple<AudioGroup*, GroupId, const SFXGroupIndex::SFXEntry*>> m_sfxLookup;
std::linear_congruential_engine<uint32_t, 0x41c64e6d, 0x3039, UINT32_MAX> m_random; std::linear_congruential_engine<uint32_t, 0x41c64e6d, 0x3039, UINT32_MAX> m_random;
int m_nextVid = 0; int m_nextVid = 0;
float m_masterVolume = 1.f; float m_masterVolume = 1.f;
AudioChannelSet m_channelSet = AudioChannelSet::Unknown; AudioChannelSet m_channelSet = AudioChannelSet::Unknown;
AudioGroup* _addAudioGroup(const AudioGroupData& data, std::unique_ptr<AudioGroup>&& grp); AudioGroup* _addAudioGroup(const AudioGroupData& data, std::unique_ptr<AudioGroup>&& grp);
std::pair<AudioGroup*, const SongGroupIndex*> _findSongGroup(int groupId) const; std::pair<AudioGroup*, const SongGroupIndex*> _findSongGroup(GroupId groupId) const;
std::pair<AudioGroup*, const SFXGroupIndex*> _findSFXGroup(int groupId) const; std::pair<AudioGroup*, const SFXGroupIndex*> _findSFXGroup(GroupId groupId) const;
std::list<ObjToken<Voice>>::iterator _allocateVoice(const AudioGroup& group, int groupId, double sampleRate, std::list<ObjToken<Voice>>::iterator _allocateVoice(const AudioGroup& group, GroupId groupId, double sampleRate,
bool dynamicPitch, bool emitter, ObjToken<Studio> studio); bool dynamicPitch, bool emitter, ObjToken<Studio> studio);
std::list<ObjToken<Sequencer>>::iterator _allocateSequencer(const AudioGroup& group, int groupId, std::list<ObjToken<Sequencer>>::iterator _allocateSequencer(const AudioGroup& group, GroupId groupId,
int setupId, ObjToken<Studio> studio); SongId setupId, ObjToken<Studio> studio);
ObjToken<Studio> _allocateStudio(bool mainOut); ObjToken<Studio> _allocateStudio(bool mainOut);
std::list<ObjToken<Voice>>::iterator _destroyVoice(std::list<ObjToken<Voice>>::iterator it); std::list<ObjToken<Voice>>::iterator _destroyVoice(std::list<ObjToken<Voice>>::iterator it);
std::list<ObjToken<Sequencer>>::iterator std::list<ObjToken<Sequencer>>::iterator
@ -88,12 +88,19 @@ public:
ObjToken<Studio> addStudio(bool mainOut); ObjToken<Studio> addStudio(bool mainOut);
/** Start soundFX playing from loaded audio groups */ /** Start soundFX playing from loaded audio groups */
ObjToken<Voice> fxStart(int sfxId, float vol, float pan, ObjToken<Studio> smx); ObjToken<Voice> fxStart(SFXId sfxId, float vol, float pan, ObjToken<Studio> smx);
ObjToken<Voice> fxStart(int sfxId, float vol, float pan) ObjToken<Voice> fxStart(SFXId sfxId, float vol, float pan)
{ {
return fxStart(sfxId, vol, pan, m_defaultStudio); return fxStart(sfxId, vol, pan, m_defaultStudio);
} }
/** Start soundFX playing from explicit group data (for editor use) */
ObjToken<Voice> fxStart(const AudioGroup* group, GroupId groupId, SFXId sfxId, float vol, float pan, ObjToken<Studio> smx);
ObjToken<Voice> fxStart(const AudioGroup* group, GroupId groupId, SFXId sfxId, float vol, float pan)
{
return fxStart(group, groupId, sfxId, vol, pan, m_defaultStudio);
}
/** Start SoundMacro node playing directly (for editor use) */ /** Start SoundMacro node playing directly (for editor use) */
ObjToken<Voice> macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key, ObjToken<Voice> macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key,
uint8_t vel, uint8_t mod, ObjToken<Studio> smx); uint8_t vel, uint8_t mod, ObjToken<Studio> smx);
@ -123,9 +130,9 @@ public:
/** Start soundFX playing from loaded audio groups, attach to positional emitter */ /** Start soundFX playing from loaded audio groups, attach to positional emitter */
ObjToken<Emitter> addEmitter(const float* pos, const float* dir, float maxDist, float falloff, ObjToken<Emitter> addEmitter(const float* pos, const float* dir, float maxDist, float falloff,
int sfxId, float minVol, float maxVol, bool doppler, ObjToken<Studio> smx); SFXId sfxId, float minVol, float maxVol, bool doppler, ObjToken<Studio> smx);
ObjToken<Emitter> addEmitter(const float* pos, const float* dir, float maxDist, float falloff, ObjToken<Emitter> addEmitter(const float* pos, const float* dir, float maxDist, float falloff,
int sfxId, float minVol, float maxVol, bool doppler) SFXId sfxId, float minVol, float maxVol, bool doppler)
{ {
return addEmitter(pos, dir, maxDist, falloff, sfxId, minVol, maxVol, doppler, m_defaultStudio); return addEmitter(pos, dir, maxDist, falloff, sfxId, minVol, maxVol, doppler, m_defaultStudio);
} }
@ -138,12 +145,19 @@ public:
void removeListener(Listener* listener); void removeListener(Listener* listener);
/** Start song playing from loaded audio groups */ /** Start song playing from loaded audio groups */
ObjToken<Sequencer> seqPlay(int groupId, int songId, const unsigned char* arrData, ObjToken<Studio> smx); ObjToken<Sequencer> seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData, ObjToken<Studio> smx);
ObjToken<Sequencer> seqPlay(int groupId, int songId, const unsigned char* arrData) ObjToken<Sequencer> seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData)
{ {
return seqPlay(groupId, songId, arrData, m_defaultStudio); return seqPlay(groupId, songId, arrData, m_defaultStudio);
} }
/** Start song playing from explicit group data (for editor use) */
ObjToken<Sequencer> seqPlay(const AudioGroup* group, GroupId groupId, SongId songId, const unsigned char* arrData, ObjToken<Studio> smx);
ObjToken<Sequencer> seqPlay(const AudioGroup* group, GroupId groupId, SongId songId, const unsigned char* arrData)
{
return seqPlay(group, groupId, songId, arrData, m_defaultStudio);
}
/** Set total volume of engine */ /** Set total volume of engine */
void setVolume(float vol); void setVolume(float vol);

View File

@ -12,7 +12,7 @@ class Engine;
class AudioGroup; class AudioGroup;
/** Common 'engine child' class */ /** Common 'engine child' class */
class Entity class Entity : public IObj
{ {
/* Only the Engine will manage Entity lifetimes, /* Only the Engine will manage Entity lifetimes,
* but shared_ptrs are issued to the client so it can safely track state */ * but shared_ptrs are issued to the client so it can safely track state */

View File

@ -48,6 +48,7 @@ class Voice : public Entity
int m_vid; /**< VoiceID of this voice instance */ int m_vid; /**< VoiceID of this voice instance */
bool m_emitter; /**< Voice is part of an Emitter */ bool m_emitter; /**< Voice is part of an Emitter */
ObjToken<Studio> m_studio; /**< Studio this voice outputs to */ ObjToken<Studio> m_studio; /**< Studio this voice outputs to */
IObjToken<Sequencer> m_sequencer; /**< Strong reference to parent sequencer to retain ctrl vals */
std::unique_ptr<IBackendVoice> m_backendVoice; /**< Handle to client-implemented backend voice */ std::unique_ptr<IBackendVoice> m_backendVoice; /**< Handle to client-implemented backend voice */
SoundMacroState m_state; /**< State container for SoundMacro playback */ SoundMacroState m_state; /**< State container for SoundMacro playback */

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 struct MakeDefaultCmdOp
{ {
template <class Tp, class R> template <class Tp, class R>
@ -209,7 +218,7 @@ AudioGroupPool AudioGroupPool::CreateAudioGroupPool(SystemStringView groupPath)
AudioGroupPool ret; AudioGroupPool ret;
SystemString poolPath(groupPath); SystemString poolPath(groupPath);
poolPath += _S("/!pool.yaml"); poolPath += _S("/!pool.yaml");
athena::io::FileReader fi(poolPath); athena::io::FileReader fi(poolPath, 32 * 1024, false);
if (!fi.hasError()) if (!fi.hasError())
{ {
@ -384,6 +393,13 @@ void SoundMacro::readCmds(athena::io::IStreamReader& r, uint32_t size)
template void SoundMacro::readCmds<athena::Big>(athena::io::IStreamReader& r, uint32_t size); template void SoundMacro::readCmds<athena::Big>(athena::io::IStreamReader& r, uint32_t size);
template void SoundMacro::readCmds<athena::Little>(athena::io::IStreamReader& r, uint32_t size); template void SoundMacro::readCmds<athena::Little>(athena::io::IStreamReader& r, uint32_t size);
void SoundMacro::buildFromPrototype(const SoundMacro& other)
{
m_cmds.reserve(other.m_cmds.size());
for (auto& cmd : other.m_cmds)
m_cmds.push_back(CmdDo<MakeCopyCmdOp, std::unique_ptr<SoundMacro::ICmd>>(*cmd));
}
const SoundMacro* AudioGroupPool::soundMacro(ObjectId id) const const SoundMacro* AudioGroupPool::soundMacro(ObjectId id) const
{ {
auto search = m_soundMacros.find(id); auto search = m_soundMacros.find(id);
@ -447,6 +463,11 @@ static SoundMacro::CmdOp _ReadCmdOp(SoundMacro::CmdOp& op)
return op; return op;
} }
static SoundMacro::CmdOp _ReadCmdOp(const SoundMacro::ICmd& op)
{
return op.Isa();
}
template <class Op, class O, class... _Args> template <class Op, class O, class... _Args>
O SoundMacro::CmdDo(_Args&&... args) O SoundMacro::CmdDo(_Args&&... args)
{ {

View File

@ -318,7 +318,7 @@ AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView gr
AudioGroupProject ret; AudioGroupProject ret;
SystemString projPath(groupPath); SystemString projPath(groupPath);
projPath += _S("/!project.yaml"); projPath += _S("/!project.yaml");
athena::io::FileReader fi(projPath); athena::io::FileReader fi(projPath, 32 * 1024, false);
if (!fi.hasError()) if (!fi.hasError())
{ {

View File

@ -316,6 +316,11 @@ std::string NameDB::generateName(ObjectId id, Type tp)
return name; return name;
} }
std::string NameDB::generateDefaultName(Type tp) const
{
return generateName(generateId(tp), tp);
}
std::string_view NameDB::registerPair(std::string_view str, ObjectId id) std::string_view NameDB::registerPair(std::string_view str, ObjectId id)
{ {
m_stringToId[std::string(str)] = id; m_stringToId[std::string(str)] = id;

View File

@ -35,7 +35,7 @@ Engine::Engine(IBackendVoiceAllocator& backend, AmplitudeMode ampMode)
m_midiReader = backend.allocateMIDIReader(*this); m_midiReader = backend.allocateMIDIReader(*this);
} }
std::pair<AudioGroup*, const SongGroupIndex*> Engine::_findSongGroup(int groupId) const std::pair<AudioGroup*, const SongGroupIndex*> Engine::_findSongGroup(GroupId groupId) const
{ {
for (const auto& pair : m_audioGroups) for (const auto& pair : m_audioGroups)
{ {
@ -46,7 +46,7 @@ std::pair<AudioGroup*, const SongGroupIndex*> Engine::_findSongGroup(int groupId
return {}; return {};
} }
std::pair<AudioGroup*, const SFXGroupIndex*> Engine::_findSFXGroup(int groupId) const std::pair<AudioGroup*, const SFXGroupIndex*> Engine::_findSFXGroup(GroupId groupId) const
{ {
for (const auto& pair : m_audioGroups) for (const auto& pair : m_audioGroups)
{ {
@ -57,7 +57,7 @@ std::pair<AudioGroup*, const SFXGroupIndex*> Engine::_findSFXGroup(int groupId)
return {}; return {};
} }
std::list<ObjToken<Voice>>::iterator Engine::_allocateVoice(const AudioGroup& group, int groupId, std::list<ObjToken<Voice>>::iterator Engine::_allocateVoice(const AudioGroup& group, GroupId groupId,
double sampleRate, bool dynamicPitch, bool emitter, double sampleRate, bool dynamicPitch, bool emitter,
ObjToken<Studio> studio) ObjToken<Studio> studio)
{ {
@ -70,8 +70,8 @@ std::list<ObjToken<Voice>>::iterator Engine::_allocateVoice(const AudioGroup& gr
return it; return it;
} }
std::list<ObjToken<Sequencer>>::iterator Engine::_allocateSequencer(const AudioGroup& group, int groupId, std::list<ObjToken<Sequencer>>::iterator Engine::_allocateSequencer(const AudioGroup& group, GroupId groupId,
int setupId, ObjToken<Studio> studio) SongId setupId, ObjToken<Studio> studio)
{ {
const SongGroupIndex* songGroup = group.getProj().getSongGroupIndex(groupId); const SongGroupIndex* songGroup = group.getProj().getSongGroupIndex(groupId);
if (songGroup) if (songGroup)
@ -265,7 +265,7 @@ void Engine::removeAudioGroup(const AudioGroupData& data)
ObjToken<Studio> Engine::addStudio(bool mainOut) { return _allocateStudio(mainOut); } ObjToken<Studio> Engine::addStudio(bool mainOut) { return _allocateStudio(mainOut); }
/** Start soundFX playing from loaded audio groups */ /** Start soundFX playing from loaded audio groups */
ObjToken<Voice> Engine::fxStart(int sfxId, float vol, float pan, ObjToken<Studio> smx) ObjToken<Voice> Engine::fxStart(SFXId sfxId, float vol, float pan, ObjToken<Studio> smx)
{ {
auto search = m_sfxLookup.find(sfxId); auto search = m_sfxLookup.find(sfxId);
if (search == m_sfxLookup.end()) if (search == m_sfxLookup.end())
@ -279,17 +279,49 @@ ObjToken<Voice> Engine::fxStart(int sfxId, float vol, float pan, ObjToken<Studio
std::list<ObjToken<Voice>>::iterator ret = std::list<ObjToken<Voice>>::iterator ret =
_allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, false, smx); _allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, false, smx);
if (!(*ret)->loadMacroObject(entry->macro.id, 0, 1000.f, entry->defKey, entry->defVel, 0)) if (!(*ret)->loadPageObject(entry->objId, 1000.f, entry->defKey, entry->defVel, 0))
{ {
_destroyVoice(ret); _destroyVoice(ret);
return {}; return {};
} }
(*ret)->setVolume(vol); (*ret)->setVolume(vol);
(*ret)->setPan(pan); float evalPan = pan != 0.f ? pan : ((entry->panning - 64.f) / 63.f);
evalPan = clamp(-1.f, evalPan, 1.f);
(*ret)->setPan(evalPan);
return *ret; return *ret;
} }
/** Start soundFX playing from explicit group data (for editor use) */
ObjToken<Voice> Engine::fxStart(const AudioGroup* group, GroupId groupId, SFXId sfxId, float vol, float pan, ObjToken<Studio> smx)
{
const SFXGroupIndex* sfxIdx = group->getProj().getSFXGroupIndex(groupId);
if (sfxIdx)
{
auto search = sfxIdx->m_sfxEntries.find(sfxId);
if (search != sfxIdx->m_sfxEntries.cend())
{
std::list<ObjToken<Voice>>::iterator ret =
_allocateVoice(*group, groupId, NativeSampleRate, true, false, smx);
auto& entry = search->second;
if (!(*ret)->loadPageObject(entry.objId, 1000.f, entry.defKey, entry.defVel, 0))
{
_destroyVoice(ret);
return {};
}
(*ret)->setVolume(vol);
float evalPan = pan != 0.f ? pan : ((entry.panning - 64.f) / 63.f);
evalPan = clamp(-1.f, evalPan, 1.f);
(*ret)->setPan(evalPan);
return *ret;
}
}
return {};
}
/** Start SoundMacro node playing directly (for editor use) */ /** Start SoundMacro node playing directly (for editor use) */
ObjToken<Voice> Engine::macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key, uint8_t vel, ObjToken<Voice> Engine::macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key, uint8_t vel,
uint8_t mod, ObjToken<Studio> smx) uint8_t mod, ObjToken<Studio> smx)
@ -353,7 +385,7 @@ ObjToken<Voice> Engine::pageObjectStart(const AudioGroup* group, ObjectId id, ui
/** Start soundFX playing from loaded audio groups, attach to positional emitter */ /** Start soundFX playing from loaded audio groups, attach to positional emitter */
ObjToken<Emitter> Engine::addEmitter(const float* pos, const float* dir, float maxDist, float falloff, ObjToken<Emitter> Engine::addEmitter(const float* pos, const float* dir, float maxDist, float falloff,
int sfxId, float minVol, float maxVol, bool doppler, ObjToken<Studio> smx) SFXId sfxId, float minVol, float maxVol, bool doppler, ObjToken<Studio> smx)
{ {
auto search = m_sfxLookup.find(sfxId); auto search = m_sfxLookup.find(sfxId);
if (search == m_sfxLookup.end()) if (search == m_sfxLookup.end())
@ -367,7 +399,7 @@ ObjToken<Emitter> Engine::addEmitter(const float* pos, const float* dir, float m
std::list<ObjToken<Voice>>::iterator vox = std::list<ObjToken<Voice>>::iterator vox =
_allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, true, smx); _allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, true, smx);
if (!(*vox)->loadMacroObject(entry->macro, 0, 1000.f, entry->defKey, entry->defVel, 0)) if (!(*vox)->loadPageObject(entry->objId, 1000.f, entry->defKey, entry->defVel, 0))
{ {
_destroyVoice(vox); _destroyVoice(vox);
return {}; return {};
@ -409,7 +441,7 @@ void Engine::removeListener(Listener* listener)
} }
/** Start song playing from loaded audio groups */ /** Start song playing from loaded audio groups */
ObjToken<Sequencer> Engine::seqPlay(int groupId, int songId, const unsigned char* arrData, ObjToken<Studio> smx) ObjToken<Sequencer> Engine::seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData, ObjToken<Studio> smx)
{ {
std::pair<AudioGroup*, const SongGroupIndex*> songGrp = _findSongGroup(groupId); std::pair<AudioGroup*, const SongGroupIndex*> songGrp = _findSongGroup(groupId);
if (songGrp.second) if (songGrp.second)
@ -435,6 +467,33 @@ ObjToken<Sequencer> Engine::seqPlay(int groupId, int songId, const unsigned char
return {}; return {};
} }
ObjToken<Sequencer> Engine::seqPlay(const AudioGroup* group, GroupId groupId, SongId songId,
const unsigned char* arrData, ObjToken<Studio> smx)
{
const SongGroupIndex* sgIdx = group->getProj().getSongGroupIndex(groupId);
if (sgIdx)
{
std::list<ObjToken<Sequencer>>::iterator ret = _allocateSequencer(*group, groupId, songId, smx);
if (!*ret)
return {};
if (arrData)
(*ret)->playSong(arrData);
return *ret;
}
const SFXGroupIndex* sfxIdx = group->getProj().getSFXGroupIndex(groupId);
if (sfxIdx)
{
std::list<ObjToken<Sequencer>>::iterator ret = _allocateSequencer(*group, groupId, songId, smx);
if (!*ret)
return {};
return *ret;
}
return {};
}
/** Set total volume of engine */ /** Set total volume of engine */
void Engine::setVolume(float vol) void Engine::setVolume(float vol)
{ {

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); m_parent->m_audioGroup, m_parent->m_groupId, NativeSampleRate, true, false, m_parent->m_studio);
if (*ret) if (*ret)
{ {
(*ret)->m_sequencer = m_parent;
m_chanVoxs[note] = *ret; m_chanVoxs[note] = *ret;
(*ret)->installCtrlValues(m_ctrlVals); (*ret)->installCtrlValues(m_ctrlVals);
@ -260,9 +261,9 @@ ObjToken<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
{ {
size_t lookupIdx = note % m_parent->m_sfxMappings.size(); size_t lookupIdx = note % m_parent->m_sfxMappings.size();
const SFXGroupIndex::SFXEntry* sfxEntry = m_parent->m_sfxMappings[lookupIdx]; const SFXGroupIndex::SFXEntry* sfxEntry = m_parent->m_sfxMappings[lookupIdx];
oid = sfxEntry->macro; oid = sfxEntry->objId;
note = sfxEntry->defKey; note = sfxEntry->defKey;
res = (*ret)->loadMacroObject(oid, 0, m_parent->m_ticksPerSec, note, velocity, m_ctrlVals[1]); res = (*ret)->loadPageObject(oid, m_parent->m_ticksPerSec, note, velocity, m_ctrlVals[1]);
} }
else else
return {}; return {};

View File

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