Clipboard support and various bug fixes

This commit is contained in:
Jack Andersen 2018-08-24 22:34:04 -10:00
parent cefa0ac18c
commit 27cdee0c14
38 changed files with 3022 additions and 854 deletions

View File

@ -91,8 +91,22 @@ void FieldDoubleSlider::doValueChanged(int value)
}
FieldProjectNode::FieldProjectNode(ProjectModel::CollectionNode* collection, QWidget* parent)
: FieldComboBox(parent)
: QWidget(parent), m_comboBox(this), m_button(this)
{
m_button.setDisabled(true);
m_button.setFixedSize(30, 30);
m_comboBox.setFixedHeight(30);
connect(&m_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(_currentIndexChanged(int)));
connect(&m_button, SIGNAL(clicked(bool)), this, SLOT(openCurrent()));
QIcon icon(QStringLiteral(":/icons/IconForward.svg"));
icon.addFile(QStringLiteral(":/icons/IconForwardDisabled.svg"), {}, QIcon::Disabled);
m_button.setIcon(icon);
QHBoxLayout* layout = new QHBoxLayout;
layout->setContentsMargins({});
layout->setSpacing(0);
layout->addWidget(&m_comboBox);
layout->addWidget(&m_button);
setLayout(layout);
setCollection(collection);
}
@ -102,18 +116,65 @@ void FieldProjectNode::setCollection(ProjectModel::CollectionNode* collection)
if (!collection)
{
setModel(new QStandardItemModel(0, 1, this));
m_comboBox.setModel(new QStandardItemModel(0, 1, this));
m_button.setDisabled(true);
return;
}
ProjectModel* model = g_MainWindow->projectModel();
setModel(model->getNullProxy());
setRootModelIndex(model->getNullProxy()->mapFromSource(model->index(collection)));
m_comboBox.setModel(model->getNullProxy());
m_comboBox.setRootModelIndex(model->getNullProxy()->mapFromSource(model->index(collection)));
}
void FieldProjectNode::_currentIndexChanged(int index)
{
m_button.setEnabled(index != 0);
emit currentIndexChanged(index);
}
ProjectModel::BasePoolObjectNode* FieldProjectNode::currentNode() const
{
int index = m_comboBox.currentIndex();
if (index == 0)
return nullptr;
else
return m_collection->nodeOfIndex(index - 1);
}
bool FieldProjectNode::event(QEvent* ev)
{
if (ev->type() == QEvent::User)
{
showPopup();
return true;
}
return QWidget::event(ev);
}
void FieldProjectNode::openCurrent()
{
if (ProjectModel::BasePoolObjectNode* node = currentNode())
if (!g_MainWindow->isUiDisabled())
g_MainWindow->openEditor(node);
}
FieldPageObjectNode::FieldPageObjectNode(ProjectModel::GroupNode* group, QWidget* parent)
: FieldComboBox(parent)
: QWidget(parent), m_comboBox(this), m_button(this)
{
m_button.setDisabled(true);
m_button.setFixedSize(30, 30);
m_comboBox.setFixedHeight(30);
connect(&m_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(_currentIndexChanged(int)));
connect(&m_button, SIGNAL(clicked(bool)), this, SLOT(openCurrent()));
QIcon icon(QStringLiteral(":/icons/IconForward.svg"));
icon.addFile(QStringLiteral(":/icons/IconForwardDisabled.svg"), {}, QIcon::Disabled);
m_button.setIcon(icon);
QHBoxLayout* layout = new QHBoxLayout;
layout->setContentsMargins({});
layout->setSpacing(0);
layout->addWidget(&m_comboBox);
layout->addWidget(&m_button);
setLayout(layout);
setGroup(group);
}
@ -123,13 +184,53 @@ void FieldPageObjectNode::setGroup(ProjectModel::GroupNode* group)
if (!group)
{
setModel(new QStandardItemModel(0, 1, this));
m_comboBox.setModel(new QStandardItemModel(0, 1, this));
m_button.setDisabled(true);
return;
}
ProjectModel* model = g_MainWindow->projectModel();
setModel(model->getPageObjectProxy());
setRootModelIndex(model->getPageObjectProxy()->mapFromSource(model->index(group)));
m_comboBox.setModel(model->getPageObjectProxy());
m_comboBox.setRootModelIndex(model->getPageObjectProxy()->mapFromSource(model->index(group)));
}
void FieldPageObjectNode::_currentIndexChanged(int index)
{
m_button.setEnabled(index != 0);
emit currentIndexChanged(index);
}
ProjectModel::BasePoolObjectNode* FieldPageObjectNode::currentNode() const
{
int index = m_comboBox.currentIndex();
if (index == 0)
{
return nullptr;
}
else
{
ProjectModel* model = g_MainWindow->projectModel();
return static_cast<ProjectModel::BasePoolObjectNode*>(
model->node(model->getPageObjectProxy()->mapToSource(
model->getPageObjectProxy()->index(index, 0, m_comboBox.rootModelIndex()))));
}
}
bool FieldPageObjectNode::event(QEvent* ev)
{
if (ev->type() == QEvent::User)
{
showPopup();
return true;
}
return QWidget::event(ev);
}
void FieldPageObjectNode::openCurrent()
{
if (ProjectModel::BasePoolObjectNode* node = currentNode())
if (!g_MainWindow->isUiDisabled())
g_MainWindow->openEditor(node);
}
AddRemoveButtons::AddRemoveButtons(QWidget* parent)

View File

@ -23,7 +23,7 @@ public:
virtual void unloadData() {}
virtual ProjectModel::INode* currentNode() const { return nullptr; }
virtual void setEditorEnabled(bool en) { setEnabled(en); }
virtual bool isItemEditEnabled() const { return false; }
virtual AmuseItemEditFlags itemEditFlags() const { return AmuseItemNone; }
public slots:
virtual void itemCutAction() {}
virtual void itemCopyAction() {}
@ -125,24 +125,51 @@ public:
void wheelEvent(QWheelEvent* event) { event->ignore(); }
};
class FieldProjectNode : public FieldComboBox
class FieldProjectNode : public QWidget
{
Q_OBJECT
ProjectModel::CollectionNode* m_collection;
FieldComboBox m_comboBox;
QPushButton m_button;
public:
explicit FieldProjectNode(ProjectModel::CollectionNode* collection = Q_NULLPTR, QWidget* parent = Q_NULLPTR);
void setCollection(ProjectModel::CollectionNode* collection);
ProjectModel::CollectionNode* collection() const { return m_collection; }
int currentIndex() const { return m_comboBox.currentIndex(); }
void setCurrentIndex(int index) { m_comboBox.setCurrentIndex(index); }
void showPopup() { m_comboBox.showPopup(); }
ProjectModel::BasePoolObjectNode* currentNode() const;
bool event(QEvent* ev);
private slots:
void _currentIndexChanged(int);
public slots:
void openCurrent();
signals:
void currentIndexChanged(int);
};
class FieldPageObjectNode : public FieldComboBox
class FieldPageObjectNode : public QWidget
{
Q_OBJECT
ProjectModel::GroupNode* m_group;
FieldComboBox m_comboBox;
QPushButton m_button;
public:
explicit FieldPageObjectNode(ProjectModel::GroupNode* group = Q_NULLPTR, QWidget* parent = Q_NULLPTR);
void setGroup(ProjectModel::GroupNode* group);
ProjectModel::GroupNode* group() const { return m_group; }
int currentIndex() const { return m_comboBox.currentIndex(); }
void setCurrentIndex(int index) { m_comboBox.setCurrentIndex(index); }
QModelIndex rootModelIndex() const { return m_comboBox.rootModelIndex(); }
void showPopup() { m_comboBox.showPopup(); }
ProjectModel::BasePoolObjectNode* currentNode() const;
bool event(QEvent* ev);
private slots:
void _currentIndexChanged(int);
public slots:
void openCurrent();
signals:
void currentIndexChanged(int);
};
template <class T>

View File

@ -118,7 +118,7 @@ void SoundMacroDelegate::setEditorData(QWidget* editor, const QModelIndex& index
ProjectModel::CollectionNode* smColl = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro);
static_cast<EditorFieldProjectNode*>(editor)->setCurrentIndex(smColl->indexOfId(layer.macro.id) + 1);
if (static_cast<EditorFieldProjectNode*>(editor)->shouldPopupOpen())
static_cast<EditorFieldProjectNode*>(editor)->showPopup();
QApplication::postEvent(editor, new QEvent(QEvent::User));
}
void SoundMacroDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const
@ -636,24 +636,9 @@ void LayersEditor::doSelectionChanged()
g_MainWindow->updateFocus();
}
bool LayersEditor::isItemEditEnabled() const
AmuseItemEditFlags LayersEditor::itemEditFlags() const
{
return !m_tableView.selectionModel()->selectedRows().isEmpty();
}
void LayersEditor::itemCutAction()
{
}
void LayersEditor::itemCopyAction()
{
}
void LayersEditor::itemPasteAction()
{
return m_tableView.selectionModel()->selectedRows().isEmpty() ? AmuseItemNone : AmuseItemDelete;
}
void LayersEditor::itemDeleteAction()

View File

@ -78,14 +78,10 @@ public:
void unloadData();
ProjectModel::INode* currentNode() const;
void resizeEvent(QResizeEvent* ev);
bool isItemEditEnabled() const;
AmuseItemEditFlags itemEditFlags() const;
public slots:
void doAdd();
void doSelectionChanged();
void itemCutAction();
void itemCopyAction();
void itemPasteAction();
void itemDeleteAction();
};

View File

@ -6,6 +6,9 @@ MIDIReader::MIDIReader(amuse::Engine& engine, bool useLock)
void MIDIReader::noteOff(uint8_t chan, uint8_t key, uint8_t velocity)
{
if (g_MainWindow->m_interactiveSeq)
g_MainWindow->m_interactiveSeq->keyOff(chan, key, velocity);
auto keySearch = m_chanVoxs.find(key);
if (keySearch == m_chanVoxs.cend())
return;
@ -19,6 +22,9 @@ void MIDIReader::noteOff(uint8_t chan, uint8_t key, uint8_t velocity)
void MIDIReader::noteOn(uint8_t chan, uint8_t key, uint8_t velocity)
{
if (g_MainWindow->m_interactiveSeq)
g_MainWindow->m_interactiveSeq->keyOn(chan, key, velocity);
if (m_lastVoice && m_lastVoice->isDestroyed())
m_lastVoice.reset();
@ -48,13 +54,19 @@ void MIDIReader::noteOn(uint8_t chan, uint8_t key, uint8_t velocity)
amuse::ObjToken<amuse::Voice> newVox = g_MainWindow->startEditorVoice(key, velocity);
if (newVox)
{
m_chanVoxs[key] = newVox;
m_lastVoice = newVox;
}
}
void MIDIReader::notePressure(uint8_t /*chan*/, uint8_t /*key*/, uint8_t /*pressure*/) {}
void MIDIReader::controlChange(uint8_t chan, uint8_t control, uint8_t value)
{
if (g_MainWindow->m_interactiveSeq)
g_MainWindow->m_interactiveSeq->setCtrlValue(chan, control, value);
if (control == 1)
{
g_MainWindow->m_ui.modulationSlider->setValue(int(value));
@ -71,17 +83,28 @@ void MIDIReader::controlChange(uint8_t chan, uint8_t control, uint8_t value)
g_MainWindow->m_ctrlVals[control] = value;
}
void MIDIReader::programChange(uint8_t chan, uint8_t program) {}
void MIDIReader::programChange(uint8_t chan, uint8_t program)
{
if (g_MainWindow->m_interactiveSeq)
g_MainWindow->m_interactiveSeq->setChanProgram(chan, program);
}
void MIDIReader::channelPressure(uint8_t /*chan*/, uint8_t /*pressure*/) {}
void MIDIReader::pitchBend(uint8_t chan, int16_t pitch)
{
g_MainWindow->m_ui.pitchSlider->setValue(int((pitch - 0x2000) / float(0x2000) * 2048.f));
float pWheel = (pitch - 0x2000) / float(0x2000);
if (g_MainWindow->m_interactiveSeq)
g_MainWindow->m_interactiveSeq->setPitchWheel(chan, pWheel);
else
g_MainWindow->m_ui.pitchSlider->setValue(int(pWheel * 2048.f));
}
void MIDIReader::allSoundOff(uint8_t chan)
{
if (g_MainWindow->m_interactiveSeq)
g_MainWindow->m_interactiveSeq->kill();
for (auto& v : m_engine.getActiveVoices())
v->kill();
}
@ -92,6 +115,9 @@ void MIDIReader::localControl(uint8_t /*chan*/, bool /*on*/) {}
void MIDIReader::allNotesOff(uint8_t chan)
{
if (g_MainWindow->m_interactiveSeq)
g_MainWindow->m_interactiveSeq->kill();
for (auto& v : m_engine.getActiveVoices())
v->kill();
}

View File

@ -5,6 +5,7 @@
#include <QInputDialog>
#include <QProgressDialog>
#include <QMouseEvent>
#include <QClipboard>
#include <QtSvg/QtSvg>
#include "amuse/ContainerRegistry.hpp"
#include "Common.hpp"
@ -21,6 +22,7 @@
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent),
m_navIt(m_navList.begin()),
m_treeDelegate(*this, this),
m_mainMessenger(this),
m_filterProjectModel(this),
@ -41,6 +43,17 @@ MainWindow::MainWindow(QWidget* parent)
m_ui.projectOutline->setItemDelegate(&m_treeDelegate);
connect(m_ui.projectOutline, SIGNAL(activated(const QModelIndex&)),
this, SLOT(outlineItemActivated(const QModelIndex&)));
m_goBack = new QAction(m_ui.backButton->icon(), tr("Go Back"), this);
m_goBack->setEnabled(false);
connect(m_goBack, SIGNAL(triggered()), this, SLOT(goBack()));
m_goBack->setShortcut(QKeySequence::Back);
m_ui.backButton->setDefaultAction(m_goBack);
m_goForward = new QAction(m_ui.forwardButton->icon(), tr("Go Forward"), this);
m_goForward->setEnabled(false);
connect(m_goForward, SIGNAL(triggered()), this, SLOT(goForward()));
m_goForward->setShortcut(QKeySequence::Forward);
m_ui.forwardButton->setDefaultAction(m_goForward);
connectMessenger(&m_mainMessenger, Qt::DirectConnection);
m_ui.statusbar->connectKillClicked(this, SLOT(killSounds()));
@ -71,6 +84,10 @@ MainWindow::MainWindow(QWidget* parent)
connect(m_ui.actionImport_Groups, SIGNAL(triggered()), this, SLOT(importAction()));
connect(m_ui.actionImport_Songs, SIGNAL(triggered()), this, SLOT(importSongsAction()));
connect(m_ui.actionExport_GameCube_Groups, SIGNAL(triggered()), this, SLOT(exportAction()));
connect(m_ui.actionImport_C_Headers, SIGNAL(triggered()), this, SLOT(importHeadersAction()));
connect(m_ui.actionExport_C_Headers, SIGNAL(triggered()), this, SLOT(exportHeadersAction()));
m_ui.actionQuit->setShortcut(QKeySequence::Quit);
connect(m_ui.actionQuit, SIGNAL(triggered()), qApp, SLOT(quit()));
for (int i = 0; i < MaxRecentFiles; ++i)
{
@ -84,21 +101,16 @@ MainWindow::MainWindow(QWidget* parent)
connect(m_clearRecentFileAct, SIGNAL(triggered()), this, SLOT(clearRecentFilesAction()));
m_ui.menuRecent_Projects->addAction(m_clearRecentFileAct);
#ifndef __APPLE__
m_ui.menuFile->addSeparator();
QAction* quitAction = m_ui.menuFile->addAction(tr("Quit"));
quitAction->setShortcut(QKeySequence::Quit);
connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
#endif
updateRecentFileActions();
connect(m_undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(cleanChanged(bool)));
QAction* undoAction = m_undoStack->createUndoAction(this);
undoAction->setShortcut(QKeySequence::Undo);
undoAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
m_ui.menuEdit->insertAction(m_ui.actionCut, undoAction);
QAction* redoAction = m_undoStack->createRedoAction(this);
redoAction->setShortcut(QKeySequence::Redo);
redoAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-redo")));
m_ui.menuEdit->insertAction(m_ui.actionCut, redoAction);
m_ui.menuEdit->insertSeparator(m_ui.actionCut);
m_ui.actionCut->setShortcut(QKeySequence::Cut);
@ -153,6 +165,7 @@ MainWindow::MainWindow(QWidget* parent)
connect(m_ui.menuMIDI, SIGNAL(aboutToShow()), this, SLOT(aboutToShowMIDIIOMenu()));
connect(qApp, SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(onFocusChanged(QWidget*,QWidget*)));
connect(QGuiApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(onClipboardChanged()));
m_voxEngine = boo::NewAudioVoiceEngine();
m_voxAllocator = std::make_unique<VoiceAllocator>(*m_voxEngine);
@ -233,6 +246,7 @@ void MainWindow::updateRecentFileActions()
QString text = QStringLiteral("&%1 %2").arg(i + 1).arg(QDir(files[i]).dirName());
m_recentFileActs[i]->setText(text);
m_recentFileActs[i]->setData(files[i]);
m_recentFileActs[i]->setToolTip(files[i]);
m_recentFileActs[i]->setVisible(true);
}
for (int j = numRecentFiles; j < MaxRecentFiles; ++j)
@ -282,11 +296,17 @@ bool MainWindow::setProjectPath(const QString& path)
m_ui.actionReload_Sample_Data->setEnabled(true);
m_ui.actionImport_Songs->setEnabled(true);
m_ui.actionExport_GameCube_Groups->setEnabled(true);
m_ui.actionImport_C_Headers->setEnabled(true);
m_ui.actionExport_C_Headers->setEnabled(true);
m_ui.actionNew_Subproject->setEnabled(true);
setWindowFilePath(path);
updateWindowTitle();
updateFocus();
m_undoStack->clear();
m_navList.clear();
m_navIt = m_navList.begin();
updateNavigationButtons();
QSettings settings;
QStringList files = settings.value("recentFileList").toStringList();
@ -399,9 +419,8 @@ void MainWindow::timerEvent(QTimerEvent* ev)
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();
if (player->sequencer() && player->sequencer()->state() != amuse::SequencerState::Playing)
player->stopped();
QTableView* sfxTable = m_soundGroupEditor->getSFXListView();
for (int i = 0; i < sfxTable->model()->rowCount(); ++i)
@ -481,83 +500,119 @@ void MainWindow::startBackgroundTask(int id, const QString& windowTitle, const Q
QMetaObject::invokeMethod(m_backgroundTask, "run", Qt::QueuedConnection);
}
bool MainWindow::_setEditor(EditorWidget* editor)
bool MainWindow::_setEditor(EditorWidget* editor, bool appendNav)
{
m_interactiveSeq.reset();
if (editor != m_ui.editorContents->currentWidget() &&
m_ui.editorContents->currentWidget() != m_faceSvg)
getEditorWidget()->unloadData();
if (appendNav && m_navIt != m_navList.end())
m_navList.erase(m_navIt + 1, m_navList.end());
if (!editor || !editor->valid())
{
m_ui.editorContents->setCurrentWidget(m_faceSvg);
updateWindowTitle();
if (appendNav)
{
m_navIt = m_navList.end();
updateNavigationButtons();
}
return false;
}
m_ui.editorContents->setCurrentWidget(editor);
m_ui.editorContents->update();
updateWindowTitle();
ProjectModel::INode* currentNode = editor->currentNode();
if (appendNav)
{
if (m_navIt == m_navList.end() || *m_navIt != currentNode)
m_navIt = m_navList.insert(m_navList.end(), currentNode);
updateNavigationButtons();
}
recursiveExpandAndSelectOutline(m_projectModel->index(currentNode));
return true;
}
bool MainWindow::openEditor(ProjectModel::SongGroupNode* node)
bool MainWindow::openEditor(ProjectModel::SongGroupNode* node, bool appendNav)
{
return _setEditor(m_songGroupEditor->loadData(node) ? m_songGroupEditor : nullptr);
bool ret = _setEditor(m_songGroupEditor->loadData(node) ? m_songGroupEditor : nullptr, appendNav);
if (ProjectModel::INode* n = getEditorNode())
{
if (n->type() == ProjectModel::INode::Type::SongGroup)
{
ProjectModel::SongGroupNode* cn = static_cast<ProjectModel::SongGroupNode*>(n);
ProjectModel::GroupNode* gn = m_projectModel->getGroupNode(n);
m_interactiveSeq = m_engine->seqPlay(gn->getAudioGroup(), cn->m_id, {}, nullptr);
}
}
return ret;
}
bool MainWindow::openEditor(ProjectModel::SoundGroupNode* node)
bool MainWindow::openEditor(ProjectModel::SoundGroupNode* node, bool appendNav)
{
return _setEditor(m_soundGroupEditor->loadData(node) ? m_soundGroupEditor : nullptr);
bool ret = _setEditor(m_soundGroupEditor->loadData(node) ? m_soundGroupEditor : nullptr, appendNav);
if (ProjectModel::INode* n = getEditorNode())
{
if (n->type() == ProjectModel::INode::Type::SoundGroup)
{
ProjectModel::SoundGroupNode* cn = static_cast<ProjectModel::SoundGroupNode*>(n);
ProjectModel::GroupNode* gn = m_projectModel->getGroupNode(n);
m_interactiveSeq = m_engine->seqPlay(gn->getAudioGroup(), cn->m_id, {}, nullptr);
}
}
return ret;
}
bool MainWindow::openEditor(ProjectModel::SoundMacroNode* node)
bool MainWindow::openEditor(ProjectModel::SoundMacroNode* node, bool appendNav)
{
return _setEditor(m_soundMacroEditor->loadData(node) ? m_soundMacroEditor : nullptr);
return _setEditor(m_soundMacroEditor->loadData(node) ? m_soundMacroEditor : nullptr, appendNav);
}
bool MainWindow::openEditor(ProjectModel::ADSRNode* node)
bool MainWindow::openEditor(ProjectModel::ADSRNode* node, bool appendNav)
{
return _setEditor(m_adsrEditor->loadData(node) ? m_adsrEditor : nullptr);
return _setEditor(m_adsrEditor->loadData(node) ? m_adsrEditor : nullptr, appendNav);
}
bool MainWindow::openEditor(ProjectModel::CurveNode* node)
bool MainWindow::openEditor(ProjectModel::CurveNode* node, bool appendNav)
{
return _setEditor(m_curveEditor->loadData(node) ? m_curveEditor : nullptr);
return _setEditor(m_curveEditor->loadData(node) ? m_curveEditor : nullptr, appendNav);
}
bool MainWindow::openEditor(ProjectModel::KeymapNode* node)
bool MainWindow::openEditor(ProjectModel::KeymapNode* node, bool appendNav)
{
return _setEditor(m_keymapEditor->loadData(node) ? m_keymapEditor : nullptr);
return _setEditor(m_keymapEditor->loadData(node) ? m_keymapEditor : nullptr, appendNav);
}
bool MainWindow::openEditor(ProjectModel::LayersNode* node)
bool MainWindow::openEditor(ProjectModel::LayersNode* node, bool appendNav)
{
return _setEditor(m_layersEditor->loadData(node) ? m_layersEditor : nullptr);
return _setEditor(m_layersEditor->loadData(node) ? m_layersEditor : nullptr, appendNav);
}
bool MainWindow::openEditor(ProjectModel::SampleNode* node)
bool MainWindow::openEditor(ProjectModel::SampleNode* node, bool appendNav)
{
return _setEditor(m_sampleEditor->loadData(node) ? m_sampleEditor : nullptr);
return _setEditor(m_sampleEditor->loadData(node) ? m_sampleEditor : nullptr, appendNav);
}
bool MainWindow::openEditor(ProjectModel::INode* node)
bool MainWindow::openEditor(ProjectModel::INode* node, bool appendNav)
{
switch (node->type())
{
case ProjectModel::INode::Type::SongGroup:
return openEditor(static_cast<ProjectModel::SongGroupNode*>(node));
return openEditor(static_cast<ProjectModel::SongGroupNode*>(node), appendNav);
case ProjectModel::INode::Type::SoundGroup:
return openEditor(static_cast<ProjectModel::SoundGroupNode*>(node));
return openEditor(static_cast<ProjectModel::SoundGroupNode*>(node), appendNav);
case ProjectModel::INode::Type::SoundMacro:
return openEditor(static_cast<ProjectModel::SoundMacroNode*>(node));
return openEditor(static_cast<ProjectModel::SoundMacroNode*>(node), appendNav);
case ProjectModel::INode::Type::ADSR:
return openEditor(static_cast<ProjectModel::ADSRNode*>(node));
return openEditor(static_cast<ProjectModel::ADSRNode*>(node), appendNav);
case ProjectModel::INode::Type::Curve:
return openEditor(static_cast<ProjectModel::CurveNode*>(node));
return openEditor(static_cast<ProjectModel::CurveNode*>(node), appendNav);
case ProjectModel::INode::Type::Keymap:
return openEditor(static_cast<ProjectModel::KeymapNode*>(node));
return openEditor(static_cast<ProjectModel::KeymapNode*>(node), appendNav);
case ProjectModel::INode::Type::Layer:
return openEditor(static_cast<ProjectModel::LayersNode*>(node));
return openEditor(static_cast<ProjectModel::LayersNode*>(node), appendNav);
case ProjectModel::INode::Type::Sample:
return openEditor(static_cast<ProjectModel::SampleNode*>(node));
return openEditor(static_cast<ProjectModel::SampleNode*>(node), appendNav);
default:
return false;
}
@ -646,7 +701,7 @@ amuse::ObjToken<amuse::Sequencer> MainWindow::startSong(amuse::GroupId groupId,
return {};
}
void MainWindow::pushUndoCommand(QUndoCommand* cmd)
void MainWindow::pushUndoCommand(EditorUndoCommand* cmd)
{
m_undoStack->push(cmd);
}
@ -658,6 +713,21 @@ void MainWindow::updateFocus()
void MainWindow::aboutToDeleteNode(ProjectModel::INode* node)
{
if (*m_navIt == node && m_navIt != m_navList.end())
{
m_navList.erase(m_navIt, m_navList.end());
m_navIt = m_navList.end();
}
for (auto it = m_navList.begin(); it != m_navList.end();)
{
if (*it == node)
{
it = m_navList.erase(it);
continue;
}
++it;
}
updateNavigationButtons();
if (getEditorNode() == node)
closeEditor();
}
@ -813,6 +883,9 @@ void MainWindow::revertAction()
QString path = m_projectModel->path();
closeEditor();
m_undoStack->clear();
m_navList.clear();
m_navIt = m_navList.begin();
updateNavigationButtons();
delete m_projectModel;
m_projectModel = nullptr;
openProject(path);
@ -936,6 +1009,7 @@ void MainWindow::importAction()
return;
}
}
model->openSongsData();
});
@ -978,6 +1052,7 @@ void MainWindow::importAction()
return;
task.setValue(++curVal);
}
model->openSongsData();
});
}
@ -1024,28 +1099,178 @@ void MainWindow::exportAction()
});
}
void MainWindow::importHeadersAction()
{
if (!m_projectModel)
return;
int confirm = m_mainMessenger.warning(tr("Import C Headers"),
tr("<p>Importing names from C headers depends on up-to-date, "
"consistent names relative to the sound group data.</p>"
"<p>Headers are imported on a per-subproject basis from "
"a single directory. Headers must be named with the form "
"<code>&lt;subproject&gt;.h</code>.</p>"
"<p>Group, Song and SFX definitions are matched according to the following forms:"
"<pre>#define GRP&lt;name&gt; &lt;id&gt;\n#define SNG&lt;name&gt; &lt;id&gt;\n"
"#define SFX&lt;name> &lt;id&gt;</pre></p>"
"<p><strong>This operation cannot be undone! Is is recommended to "
"make a backup of the project directory before proceeding.</strong></p>"
"<p>Continue?</p>"), QMessageBox::Yes | QMessageBox::No);
if (confirm == QMessageBox::No)
return;
QString path = QFileDialog::getExistingDirectory(this, tr("Import C Headers"), m_projectModel->dir().path());
if (path.isEmpty())
return;
closeEditor();
for (QString group : m_projectModel->getGroupList())
m_projectModel->importHeader(path, group, m_mainMessenger);
m_projectModel->updateNodeNames();
/* Samples may have been renamed.. stay consistent! */
saveAction();
}
void MainWindow::exportHeadersAction()
{
if (!m_projectModel)
return;
QString path = QFileDialog::getExistingDirectory(this, tr("Export C Headers"), m_projectModel->dir().path());
if (path.isEmpty())
return;
bool yesToAll = false;
for (QString group : m_projectModel->getGroupList())
if (!m_projectModel->exportHeader(path, group, yesToAll, m_mainMessenger))
return;
}
bool TreeDelegate::editorEvent(QEvent* event,
QAbstractItemModel* _model,
QAbstractItemModel* __model,
const QStyleOptionViewItem& option,
const QModelIndex& index)
{
ProjectModel* model = static_cast<ProjectModel*>(_model);
ProjectModel::INode* node = model->node(index);
if (!node)
QSortFilterProxyModel* _model = static_cast<QSortFilterProxyModel*>(__model);
ProjectModel* model = static_cast<ProjectModel*>(_model->sourceModel());
ProjectModel::INode* node = model->node(_model->mapToSource(index));
if (!node || !(node->flags() & Qt::ItemIsEditable))
return false;
AmuseItemEditFlags flags = node->editFlags();
#if 0
if ((event->type() == QEvent::MouseButtonDblClick &&
static_cast<QMouseEvent*>(event)->button() == Qt::LeftButton))
if (event->type() == QEvent::MouseButtonPress)
{
// Open in editor
return m_window.openEditor(node);
QMouseEvent* ev = static_cast<QMouseEvent*>(event);
if (ev->button() == Qt::RightButton)
{
m_window.m_ui.projectOutline->setCurrentIndex(index);
QMenu* menu = new QMenu(g_MainWindow->m_ui.projectOutline);
QAction* cutAction = new QAction(tr("Cut"), menu);
cutAction->setData(index);
cutAction->setEnabled(flags & AmuseItemCut);
cutAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-cut")));
cutAction->setShortcut(QKeySequence::Cut);
connect(cutAction, SIGNAL(triggered()), this, SLOT(doCut()));
menu->addAction(cutAction);
QAction* copyAction = new QAction(tr("Copy"), menu);
copyAction->setData(index);
copyAction->setEnabled(flags & AmuseItemCopy);
copyAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
copyAction->setShortcut(QKeySequence::Copy);
connect(copyAction, SIGNAL(triggered()), this, SLOT(doCopy()));
menu->addAction(copyAction);
QAction* pasteAction = new QAction(tr("Paste"), menu);
pasteAction->setData(index);
pasteAction->setEnabled(g_MainWindow->m_clipboardAmuseData && flags & AmuseItemPaste);
pasteAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste")));
pasteAction->setShortcut(QKeySequence::Paste);
connect(pasteAction, SIGNAL(triggered()), this, SLOT(doPaste()));
menu->addAction(pasteAction);
QAction* dupeAction = new QAction(tr("Duplicate"), menu);
dupeAction->setData(index);
connect(dupeAction, SIGNAL(triggered()), this, SLOT(doDuplicate()));
menu->addAction(dupeAction);
QAction* deleteAction = new QAction(tr("Delete"), menu);
deleteAction->setData(index);
deleteAction->setEnabled(flags & AmuseItemDelete);
deleteAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
deleteAction->setShortcut(QKeySequence::Delete);
connect(deleteAction, SIGNAL(triggered()), this, SLOT(doDelete()));
menu->addAction(deleteAction);
QAction* renameAction = new QAction(tr("Rename"), menu);
renameAction->setData(index);
renameAction->setShortcut(tr("F2"));
connect(renameAction, SIGNAL(triggered()), this, SLOT(doRename()));
menu->addAction(renameAction);
menu->popup(ev->globalPos());
return true;
}
}
#endif
return false;
}
void TreeDelegate::doCut()
{
QAction* act = qobject_cast<QAction*>(sender());
if (!m_window.m_projectModel)
return;
m_window.m_projectModel->cut(m_window.m_filterProjectModel.mapToSource(act->data().toModelIndex()));
}
void TreeDelegate::doCopy()
{
QAction* act = qobject_cast<QAction*>(sender());
if (!m_window.m_projectModel)
return;
m_window.m_projectModel->copy(m_window.m_filterProjectModel.mapToSource(act->data().toModelIndex()));
}
void TreeDelegate::doPaste()
{
QAction* act = qobject_cast<QAction*>(sender());
if (!m_window.m_projectModel)
return;
m_window.m_projectModel->paste(m_window.m_filterProjectModel.mapToSource(act->data().toModelIndex()));
}
void TreeDelegate::doDuplicate()
{
QAction* act = qobject_cast<QAction*>(sender());
if (!m_window.m_projectModel)
return;
QModelIndex newIdx =
m_window.m_projectModel->duplicate(m_window.m_filterProjectModel.mapToSource(act->data().toModelIndex()));
if (newIdx.isValid())
{
newIdx = m_window.m_filterProjectModel.mapFromSource(newIdx);
m_window.m_ui.projectOutline->edit(newIdx);
}
}
void TreeDelegate::doDelete()
{
QAction* act = qobject_cast<QAction*>(sender());
if (!m_window.m_projectModel)
return;
m_window.m_projectModel->del(m_window.m_filterProjectModel.mapToSource(act->data().toModelIndex()));
}
void TreeDelegate::doRename()
{
QAction* act = qobject_cast<QAction*>(sender());
m_window.m_ui.projectOutline->edit(act->data().toModelIndex());
}
QString MainWindow::getGroupName(ProjectModel::GroupNode* group) const
{
if (group)
@ -1057,10 +1282,10 @@ ProjectModel::GroupNode* MainWindow::getSelectedGroupNode() const
{
if (!m_projectModel)
return nullptr;
if (!m_ui.projectOutline->selectionModel()->currentIndex().isValid())
if (!m_ui.projectOutline->currentIndex().isValid())
return nullptr;
return m_projectModel->getGroupNode(m_projectModel->node(
m_filterProjectModel.mapToSource(m_ui.projectOutline->selectionModel()->currentIndex())));
m_filterProjectModel.mapToSource(m_ui.projectOutline->currentIndex())));
}
QString MainWindow::getSelectedGroupName() const
@ -1082,10 +1307,7 @@ void MainWindow::recursiveExpandAndSelectOutline(const QModelIndex& index) const
QModelIndex filterIndex = m_filterProjectModel.mapFromSource(index);
_recursiveExpandOutline(filterIndex);
if (filterIndex.isValid())
{
m_ui.projectOutline->selectionModel()->setCurrentIndex(filterIndex, QItemSelectionModel::ClearAndSelect);
m_ui.projectOutline->selectionModel()->setCurrentIndex(filterIndex, QItemSelectionModel::Current);
}
m_ui.projectOutline->setCurrentIndex(filterIndex);
}
void MainWindow::newSubprojectAction()
@ -1238,6 +1460,30 @@ void MainWindow::newLayersAction()
openEditor(node);
}
void MainWindow::updateNavigationButtons()
{
m_goForward->setDisabled(m_navIt == m_navList.end() || m_navIt + 1 == m_navList.end());
m_goBack->setDisabled(m_navIt == m_navList.begin());
}
void MainWindow::goForward()
{
if (m_navIt == m_navList.end() || m_navIt + 1 == m_navList.end())
return;
++m_navIt;
openEditor(*m_navIt, false);
updateNavigationButtons();
}
void MainWindow::goBack()
{
if (m_navIt == m_navList.begin())
return;
--m_navIt;
openEditor(*m_navIt, false);
updateNavigationButtons();
}
void MainWindow::aboutToShowAudioIOMenu()
{
refreshAudioIO();
@ -1413,27 +1659,30 @@ void MainWindow::auxBChanged(int vol)
void MainWindow::outlineCutAction()
{
if (!m_projectModel)
return;
m_projectModel->cut(m_filterProjectModel.mapToSource(m_ui.projectOutline->currentIndex()));
}
void MainWindow::outlineCopyAction()
{
if (!m_projectModel)
return;
m_projectModel->copy(m_filterProjectModel.mapToSource(m_ui.projectOutline->currentIndex()));
}
void MainWindow::outlinePasteAction()
{
if (!m_projectModel)
return;
m_projectModel->paste(m_filterProjectModel.mapToSource(m_ui.projectOutline->currentIndex()));
}
void MainWindow::outlineDeleteAction()
{
if (!m_projectModel)
return;
QModelIndexList indexes = m_ui.projectOutline->selectionModel()->selectedIndexes();
if (indexes.empty())
return;
m_projectModel->del(m_filterProjectModel.mapToSource(indexes.front()));
m_projectModel->del(m_filterProjectModel.mapToSource(m_ui.projectOutline->currentIndex()));
}
void MainWindow::onFocusChanged(QWidget* old, QWidget* now)
@ -1460,7 +1709,10 @@ void MainWindow::onFocusChanged(QWidget* old, QWidget* now)
if (now == m_ui.projectOutline || m_ui.projectOutline->isAncestorOf(now))
{
setItemEditEnabled(canEditOutline());
AmuseItemEditFlags editFlags = outlineEditFlags();
if (!m_clipboardAmuseData)
editFlags = AmuseItemEditFlags(editFlags & ~AmuseItemPaste);
setItemEditFlags(editFlags);
if (m_projectModel)
{
m_cutConn = connect(m_ui.actionCut, SIGNAL(triggered()), this, SLOT(outlineCutAction()));
@ -1471,20 +1723,40 @@ void MainWindow::onFocusChanged(QWidget* old, QWidget* now)
}
else if (now == m_ui.editorContents || m_ui.editorContents->isAncestorOf(now))
{
setItemEditEnabled(false);
if (EditorWidget* editor = getEditorWidget())
{
if (editor->isItemEditEnabled())
setItemEditFlags(editor->itemEditFlags());
m_cutConn = connect(m_ui.actionCut, SIGNAL(triggered()), editor, SLOT(itemCutAction()));
m_copyConn = connect(m_ui.actionCopy, SIGNAL(triggered()), editor, SLOT(itemCopyAction()));
m_pasteConn = connect(m_ui.actionPaste, SIGNAL(triggered()), editor, SLOT(itemPasteAction()));
m_deleteConn = connect(m_ui.actionDelete, SIGNAL(triggered()), editor, SLOT(itemDeleteAction()));
}
else
{
setItemEditFlags(AmuseItemNone);
}
}
}
void MainWindow::onClipboardChanged()
{
if (QClipboard* cb = qobject_cast<QClipboard*>(sender()))
{
if (const QMimeData* md = cb->mimeData())
{
for (const QString& str : md->formats())
{
setItemEditEnabled(true);
m_cutConn = connect(m_ui.actionCut, SIGNAL(triggered()), editor, SLOT(itemCutAction()));
m_copyConn = connect(m_ui.actionCopy, SIGNAL(triggered()), editor, SLOT(itemCopyAction()));
m_pasteConn = connect(m_ui.actionPaste, SIGNAL(triggered()), editor, SLOT(itemPasteAction()));
m_deleteConn = connect(m_ui.actionDelete, SIGNAL(triggered()), editor, SLOT(itemDeleteAction()));
if (str.startsWith(QStringLiteral("application/x-amuse-")))
{
m_clipboardAmuseData = true;
updateFocus();
return;
}
}
}
}
m_clipboardAmuseData = false;
updateFocus();
}
void MainWindow::outlineItemActivated(const QModelIndex& index)
@ -1495,12 +1767,12 @@ void MainWindow::outlineItemActivated(const QModelIndex& index)
openEditor(node);
}
void MainWindow::setItemEditEnabled(bool enabled)
void MainWindow::setItemEditFlags(AmuseItemEditFlags flags)
{
m_ui.actionCut->setEnabled(enabled);
m_ui.actionCopy->setEnabled(enabled);
m_ui.actionPaste->setEnabled(enabled);
m_ui.actionDelete->setEnabled(enabled);
m_ui.actionCut->setEnabled(flags & AmuseItemCut);
m_ui.actionCopy->setEnabled(flags & AmuseItemCopy);
m_ui.actionPaste->setEnabled(flags & AmuseItemPaste);
m_ui.actionDelete->setEnabled(flags & AmuseItemDelete);
}
void MainWindow::setItemNewEnabled(bool enabled)
@ -1514,25 +1786,25 @@ void MainWindow::setItemNewEnabled(bool enabled)
m_ui.actionNew_Layers->setEnabled(enabled);
}
bool MainWindow::canEditOutline()
AmuseItemEditFlags MainWindow::outlineEditFlags()
{
if (!m_projectModel)
return false;
QModelIndex curIndex = m_ui.projectOutline->selectionModel()->currentIndex();
return AmuseItemNone;
QModelIndex curIndex = m_ui.projectOutline->currentIndex();
if (!curIndex.isValid())
return false;
return m_projectModel->canEdit(m_filterProjectModel.mapToSource(curIndex));
return AmuseItemNone;
return m_projectModel->editFlags(m_filterProjectModel.mapToSource(curIndex));
}
void MainWindow::onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
{
if (!m_projectModel)
return;
setItemNewEnabled(m_ui.projectOutline->selectionModel()->currentIndex().isValid());
setItemNewEnabled(m_ui.projectOutline->currentIndex().isValid());
if (selected.indexes().empty())
setItemEditEnabled(false);
setItemEditFlags(AmuseItemNone);
else
setItemEditEnabled(m_projectModel->canEdit(m_filterProjectModel.mapToSource(selected.indexes().front())));
setItemEditFlags(m_projectModel->editFlags(m_filterProjectModel.mapToSource(selected.indexes().front())));
}
void MainWindow::onTextSelect()

View File

@ -7,6 +7,7 @@
#include <QThread>
#include <QStyledItemDelegate>
#include <QSortFilterProxyModel>
#include <QLinkedList>
#include "ui_MainWindow.h"
#include "amuse/Engine.hpp"
#include "amuse/BooBackend.hpp"
@ -77,13 +78,27 @@ public:
QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index);
public slots:
void doCut();
void doCopy();
void doPaste();
void doDuplicate();
void doDelete();
void doRename();
};
class MainWindow : public QMainWindow
{
friend class MIDIReader;
friend class ProjectModel;
friend class GroupNodeUndoCommand;
friend class TreeDelegate;
Q_OBJECT
Ui::MainWindow m_ui;
QAction* m_goBack;
QAction* m_goForward;
QLinkedList<ProjectModel::INode*> m_navList;
QLinkedList<ProjectModel::INode*>::iterator m_navIt;
QAction* m_clearRecentFileAct;
QAction* m_recentFileActs[MaxRecentFiles];
TreeDelegate m_treeDelegate;
@ -105,12 +120,14 @@ class MainWindow : public QMainWindow
std::unique_ptr<VoiceAllocator> m_voxAllocator;
std::unique_ptr<amuse::Engine> m_engine;
amuse::ObjToken<amuse::Voice> m_lastSound;
amuse::ObjToken<amuse::Sequencer> m_interactiveSeq;
int m_velocity = 90;
float m_pitch = 0.f;
int8_t m_ctrlVals[128] = {};
float m_auxAVol = 0.f;
float m_auxBVol = 0.f;
bool m_uiDisabled = false;
bool m_clipboardAmuseData = false;
QUndoStack* m_undoStack;
@ -128,6 +145,7 @@ class MainWindow : public QMainWindow
void updateWindowTitle();
void updateRecentFileActions();
void updateNavigationButtons();
bool setProjectPath(const QString& path);
void refreshAudioIO();
void refreshMIDIIO();
@ -139,7 +157,7 @@ class MainWindow : public QMainWindow
void startBackgroundTask(int id, const QString& windowTitle, const QString& label,
std::function<void(BackgroundTask&)>&& task);
bool _setEditor(EditorWidget* widget);
bool _setEditor(EditorWidget* widget, bool appendNav = true);
public:
explicit MainWindow(QWidget* parent = Q_NULLPTR);
@ -147,15 +165,15 @@ public:
bool openProject(const QString& path);
bool openEditor(ProjectModel::SongGroupNode* node);
bool openEditor(ProjectModel::SoundGroupNode* node);
bool openEditor(ProjectModel::SoundMacroNode* node);
bool openEditor(ProjectModel::ADSRNode* node);
bool openEditor(ProjectModel::CurveNode* node);
bool openEditor(ProjectModel::KeymapNode* node);
bool openEditor(ProjectModel::LayersNode* node);
bool openEditor(ProjectModel::SampleNode* node);
bool openEditor(ProjectModel::INode* node);
bool openEditor(ProjectModel::SongGroupNode* node, bool appendNav = true);
bool openEditor(ProjectModel::SoundGroupNode* node, bool appendNav = true);
bool openEditor(ProjectModel::SoundMacroNode* node, bool appendNav = true);
bool openEditor(ProjectModel::ADSRNode* node, bool appendNav = true);
bool openEditor(ProjectModel::CurveNode* node, bool appendNav = true);
bool openEditor(ProjectModel::KeymapNode* node, bool appendNav = true);
bool openEditor(ProjectModel::LayersNode* node, bool appendNav = true);
bool openEditor(ProjectModel::SampleNode* node, bool appendNav = true);
bool openEditor(ProjectModel::INode* node, bool appendNav = true);
void closeEditor();
ProjectModel::INode* getEditorNode() const;
@ -164,7 +182,7 @@ public:
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(EditorUndoCommand* cmd);
void updateFocus();
void aboutToDeleteNode(ProjectModel::INode* node);
void closeEvent(QCloseEvent* ev);
@ -179,6 +197,11 @@ public:
ProjectModel* projectModel() const { return m_projectModel; }
UIMessenger& uiMessenger() { return m_mainMessenger; }
void setItemEditFlags(AmuseItemEditFlags flags);
void setItemNewEnabled(bool enabled);
AmuseItemEditFlags outlineEditFlags();
bool isUiDisabled() const { return m_uiDisabled; }
public slots:
void newAction();
void openAction();
@ -190,6 +213,8 @@ public slots:
void importAction();
void importSongsAction();
void exportAction();
void importHeadersAction();
void exportHeadersAction();
void newSubprojectAction();
void newSFXGroupAction();
@ -200,6 +225,9 @@ public slots:
void newKeymapAction();
void newLayersAction();
void goForward();
void goBack();
void aboutToShowAudioIOMenu();
void aboutToShowMIDIIOMenu();
@ -226,10 +254,8 @@ public slots:
void outlineDeleteAction();
void onFocusChanged(QWidget* old, QWidget* now);
void onClipboardChanged();
void outlineItemActivated(const QModelIndex& index);
void setItemEditEnabled(bool enabled);
void setItemNewEnabled(bool enabled);
bool canEditOutline();
void onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
void onTextSelect();
void onTextDelete();

View File

@ -53,26 +53,59 @@
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="projectOutlineFilter">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<layout class="QHBoxLayout" name="outlineLayout2">
<property name="spacing">
<number>0</number>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="placeholderText">
<string>Filter</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
<item>
<widget class="QLineEdit" name="projectOutlineFilter">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="placeholderText">
<string>Filter</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="backButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/IconBack.svg</normaloff>
<disabledoff>:/icons/IconBackDisabled.svg</disabledoff>
<disabledon>:/icons/IconBackDisabled.svg</disabledon>:/icons/IconBack.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="forwardButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/IconForward.svg</normaloff>
<disabledoff>:/icons/IconForwardDisabled.svg</disabledoff>
<disabledon>:/icons/IconForwardDisabled.svg</disabledon>:/icons/IconForward.svg</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeView" name="projectOutline">
@ -91,6 +124,9 @@
<property name="focusPolicy">
<enum>Qt::WheelFocus</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
</property>
<property name="animated">
<bool>true</bool>
</property>
@ -257,7 +293,7 @@
<x>0</x>
<y>0</y>
<width>1501</width>
<height>83</height>
<height>85</height>
</rect>
</property>
<property name="sizePolicy">
@ -294,7 +330,7 @@
<x>0</x>
<y>0</y>
<width>1360</width>
<height>25</height>
<height>27</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@ -308,6 +344,9 @@
<property name="title">
<string>Recent &amp;Projects</string>
</property>
<property name="icon">
<iconset theme="document-open-recent"/>
</property>
</widget>
<addaction name="actionNew_Project"/>
<addaction name="actionOpen_Project"/>
@ -319,6 +358,10 @@
<addaction name="actionImport_Groups"/>
<addaction name="actionImport_Songs"/>
<addaction name="actionExport_GameCube_Groups"/>
<addaction name="actionImport_C_Headers"/>
<addaction name="actionExport_C_Headers"/>
<addaction name="separator"/>
<addaction name="actionQuit"/>
</widget>
<widget class="QMenu" name="menuProject">
<property name="title">
@ -360,7 +403,7 @@
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
<string>&amp;Help</string>
</property>
<addaction name="actionAbout_Amuse"/>
<addaction name="actionAbout_Qt"/>
@ -374,11 +417,17 @@
</widget>
<widget class="StatusBarWidget" name="statusbar"/>
<action name="actionNew_Project">
<property name="icon">
<iconset theme="document-new"/>
</property>
<property name="text">
<string>&amp;New Project</string>
</property>
</action>
<action name="actionOpen_Project">
<property name="icon">
<iconset theme="document-open"/>
</property>
<property name="text">
<string>&amp;Open Project</string>
</property>
@ -387,6 +436,9 @@
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="edit-cut"/>
</property>
<property name="text">
<string>&amp;Cut</string>
</property>
@ -395,6 +447,9 @@
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="edit-copy"/>
</property>
<property name="text">
<string>C&amp;opy</string>
</property>
@ -403,6 +458,9 @@
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="edit-paste"/>
</property>
<property name="text">
<string>&amp;Paste</string>
</property>
@ -411,6 +469,9 @@
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="edit-delete"/>
</property>
<property name="text">
<string>&amp;Delete</string>
</property>
@ -550,6 +611,9 @@
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="document-save"/>
</property>
<property name="text">
<string>&amp;Save Project</string>
</property>
@ -558,6 +622,9 @@
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="document-revert"/>
</property>
<property name="text">
<string>&amp;Revert Project</string>
</property>
@ -579,21 +646,54 @@
</property>
</action>
<action name="actionAbout_Amuse">
<property name="icon">
<iconset theme="help-about"/>
</property>
<property name="text">
<string>About Amuse</string>
<string>&amp;About Amuse</string>
</property>
<property name="menuRole">
<enum>QAction::AboutRole</enum>
</property>
</action>
<action name="actionAbout_Qt">
<property name="icon">
<iconset theme="help-about"/>
</property>
<property name="text">
<string>About Qt</string>
<string>About &amp;Qt</string>
</property>
<property name="menuRole">
<enum>QAction::AboutQtRole</enum>
</property>
</action>
<action name="actionExport_C_Headers">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Export &amp;C Headers</string>
</property>
</action>
<action name="actionQuit">
<property name="icon">
<iconset theme="application-exit"/>
</property>
<property name="text">
<string>&amp;Quit</string>
</property>
<property name="menuRole">
<enum>QAction::QuitRole</enum>
</property>
</action>
<action name="actionImport_C_Headers">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Import C &amp;Headers</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -3,10 +3,14 @@
#include "ProjectModel.hpp"
#include "Common.hpp"
#include "athena/YAMLDocWriter.hpp"
#include "athena/VectorWriter.hpp"
#include "MainWindow.hpp"
#include "EditorWidget.hpp"
#include "amuse/SongConverter.hpp"
#include "amuse/ContainerRegistry.hpp"
#include <QUndoCommand>
#include <QDate>
#include <QMimeData>
#include <QClipboard>
QIcon ProjectModel::GroupNode::Icon;
QIcon ProjectModel::SongGroupNode::Icon;
@ -305,6 +309,16 @@ ProjectModel::INode::INode(const QString& name)
m_nullChild = nullNode.get();
}
void ProjectModel::INode::_sortChildren()
{
std::sort(m_children.begin(), m_children.end(),
[](const auto& a, const auto& b)
{
return a->name() < b->name();
});
reindexRows(0);
}
int ProjectModel::INode::hypotheticalIndex(const QString& name) const
{
auto search = std::lower_bound(m_children.cbegin(), m_children.cend(), name,
@ -326,6 +340,17 @@ int ProjectModel::GroupNode::hypotheticalIndex(const QString& name) const
return int(search - m_children.cbegin());
}
void ProjectModel::GroupNode::_sortChildren()
{
/* Sort prior to pool object collections */
std::sort(m_children.begin(), m_children.end() - 6,
[](const auto& a, const auto& b)
{
return a->name() < b->name();
});
reindexRows(0);
}
ProjectModel::CollectionNode* ProjectModel::GroupNode::getCollectionOfType(Type tp) const
{
for (auto it = m_children.rbegin(); it != m_children.rend(); ++it)
@ -624,6 +649,104 @@ bool ProjectModel::exportGroup(const QString& path, const QString& groupName, UI
return true;
}
bool ProjectModel::importHeader(const QString& path, const QString& groupName, UIMessenger& messenger) const
{
m_projectDatabase.setIdDatabases();
auto search = m_groups.find(groupName);
if (search == m_groups.cend())
{
messenger.critical(tr("Import Error"), tr("Unable to find group %1").arg(groupName));
return false;
}
QFileInfo fi(QDir(path), QStringLiteral("%1.h").arg(groupName));
if (!fi.exists())
return true;
QFile fo(fi.filePath());
if (!fo.open(QFile::ReadOnly))
{
messenger.critical(tr("Export Header Error"), tr("Unable to open %1 for reading").arg(fi.filePath()));
return false;
}
auto data = fo.readAll();
search->second->importCHeader(std::string_view(data.data(), data.size()));
return true;
}
bool ProjectModel::exportHeader(const QString& path, const QString& groupName,
bool& yesToAll, UIMessenger& messenger) const
{
m_projectDatabase.setIdDatabases();
auto search = m_groups.find(groupName);
if (search == m_groups.cend())
{
messenger.critical(tr("Export Error"), tr("Unable to find group %1").arg(groupName));
return false;
}
QFileInfo fi(QDir(path), QStringLiteral("%1.h").arg(groupName));
if (!yesToAll && fi.exists())
{
auto result = messenger.question(tr("File Exists"),
tr("%1 already exists. Overwrite?").arg(fi.filePath()),
QMessageBox::Yes | QMessageBox::No | QMessageBox::YesToAll | QMessageBox::NoToAll);
if (result == QMessageBox::No)
return true;
else if (result == QMessageBox::NoToAll)
return false;
else if (result == QMessageBox::YesToAll)
yesToAll = true;
}
std::string header = search->second->exportCHeader(m_dir.dirName().toUtf8().data(), groupName.toUtf8().data());
QFile fo(fi.filePath());
if (fo.open(QFile::WriteOnly))
fo.write(header.data(), header.size());
return true;
}
void ProjectModel::updateNodeNames()
{
beginResetModel();
m_root->oneLevelTraverse([this](INode* n)
{
GroupNode* gn = static_cast<GroupNode*>(n);
setIdDatabases(gn);
gn->oneLevelTraverse([](INode* n)
{
if (n->type() == INode::Type::SongGroup)
{
SongGroupNode* sgn = static_cast<SongGroupNode*>(n);
sgn->m_name = amuse::GroupId::CurNameDB->resolveNameFromId(sgn->m_id).data();
}
else if (n->type() == INode::Type::SoundGroup)
{
SoundGroupNode* sgn = static_cast<SoundGroupNode*>(n);
sgn->m_name = amuse::GroupId::CurNameDB->resolveNameFromId(sgn->m_id).data();
}
else if (n->type() == INode::Type::Collection)
{
CollectionNode* cn = static_cast<CollectionNode*>(n);
cn->oneLevelTraverse([](INode* n)
{
BasePoolObjectNode* on = static_cast<BasePoolObjectNode*>(n);
on->m_name = on->getNameDb()->resolveNameFromId(on->m_id).data();
return true;
});
cn->_sortChildren();
}
return true;
});
gn->_sortChildren();
return true;
});
m_root->_sortChildren();
endResetModel();
}
void ProjectModel::_buildGroupNodeCollections(GroupNode& gn)
{
gn.reserve(6);
@ -635,9 +758,8 @@ void ProjectModel::_buildGroupNodeCollections(GroupNode& gn)
gn._appendChild<CollectionNode>(tr("Samples"), QIcon(":/icons/IconSample.svg"), INode::Type::Sample);
}
void ProjectModel::_buildGroupNode(GroupNode& gn)
void ProjectModel::_buildGroupNode(GroupNode& gn, amuse::AudioGroup& group)
{
amuse::AudioGroup& group = *gn.m_it->second;
auto& songGroups = group.getProj().songGroups();
auto& sfxGroups = group.getProj().sfxGroups();
auto& soundMacros = group.getPool().soundMacros();
@ -720,7 +842,7 @@ void ProjectModel::_resetModelData()
{
it->second->setIdDatabases();
GroupNode& gn = m_root->makeChild<GroupNode>(it);
_buildGroupNode(gn);
_buildGroupNode(gn, *gn.m_it->second);
}
endResetModel();
}
@ -835,6 +957,7 @@ QVariant ProjectModel::data(const QModelIndex& index, int role) const
switch (role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return item->text();
case Qt::DecorationRole:
return item->icon();
@ -843,6 +966,87 @@ QVariant ProjectModel::data(const QModelIndex& index, int role) const
}
}
class RenameNodeUndoCommand : public EditorUndoCommand
{
QString m_redoVal, m_undoVal;
public:
RenameNodeUndoCommand(const QString& text, ProjectModel::INode* node, const QString& redoVal)
: EditorUndoCommand(node, text.arg(node->name())), m_redoVal(redoVal) {}
void undo()
{
g_MainWindow->projectModel()->_renameNode(m_node.get(), m_undoVal);
}
void redo()
{
m_undoVal = m_node->name();
g_MainWindow->projectModel()->_renameNode(m_node.get(), m_redoVal);
}
};
void ProjectModel::_renameNode(INode* node, const QString& name)
{
INode* parent = node->parent();
if (!parent)
return;
if (parent->findChild(name))
return;
QString oldName = node->m_name;
int oldIdx = parent->hypotheticalIndex(oldName);
int newIdx = parent->hypotheticalIndex(name);
bool moving = beginMoveRows(index(parent), oldIdx, oldIdx, index(parent), newIdx);
node->m_name = name;
parent->_sortChildren();
switch (node->type())
{
case INode::Type::Group:
m_dir.rename(oldName, name);
break;
case INode::Type::Sample:
{
ProjectModel::GroupNode* group = getGroupNode(node);
auto utf8Name = name.toUtf8();
group->getAudioGroup()->renameSample(static_cast<SampleNode*>(node)->id(),
std::string_view(utf8Name.data(), utf8Name.length()));
g_MainWindow->saveAction();
break;
}
default:
break;
}
QModelIndex idx = index(node);
emit dataChanged(idx, idx, {Qt::DisplayRole, Qt::EditRole});
if (g_MainWindow->getEditorNode() == node)
g_MainWindow->updateWindowTitle();
if (moving)
endMoveRows();
}
bool ProjectModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!index.isValid() || role != Qt::EditRole)
return false;
assert(index.model() == this && "Not ProjectModel");
INode* item = static_cast<INode*>(index.internalPointer());
INode* parent = item->parent();
if (!parent)
return false;
if (item->name() == value.toString())
return false;
if (parent->findChild(value.toString()))
{
g_MainWindow->uiMessenger().critical(tr("Naming Conflict"),
tr("%1 already exists in this context").arg(value.toString()));
return false;
}
g_MainWindow->pushUndoCommand(new RenameNodeUndoCommand(tr("Rename %1"), item, value.toString()));
return true;
}
Qt::ItemFlags ProjectModel::flags(const QModelIndex& index) const
{
if (!index.isValid())
@ -869,12 +1073,12 @@ ProjectModel::GroupNode* ProjectModel::getGroupNode(INode* node) const
return getGroupNode(node->parent());
}
bool ProjectModel::canEdit(const QModelIndex& index) const
AmuseItemEditFlags ProjectModel::editFlags(const QModelIndex& index) const
{
if (!index.isValid())
return false;
return AmuseItemNone;
assert(index.model() == this && "Not ProjectModel");
return (static_cast<INode*>(index.internalPointer())->flags() & Qt::ItemIsSelectable) != Qt::NoItemFlags;
return static_cast<INode*>(index.internalPointer())->editFlags();
}
void ProjectModel::_postAddNode(INode* n, const NameUndoRegistry& registry)
@ -897,31 +1101,34 @@ void ProjectModel::_preDelNode(INode* n, NameUndoRegistry& registry)
});
}
class GroupNodeUndoCommand : public QUndoCommand
class GroupNodeUndoCommand : public EditorUndoCommand
{
protected:
std::unique_ptr<amuse::AudioGroupDatabase> m_data;
amuse::ObjToken<ProjectModel::GroupNode> m_node;
ProjectModel::NameUndoRegistry m_nameReg;
void add()
{
g_MainWindow->projectModel()->_addNode(m_node.get(), std::move(m_data), m_nameReg);
g_MainWindow->projectModel()->_addNode(static_cast<ProjectModel::GroupNode*>(m_node.get()), std::move(m_data), m_nameReg);
g_MainWindow->recursiveExpandAndSelectOutline(g_MainWindow->projectModel()->index(m_node.get()));
}
void del()
{
m_data = g_MainWindow->projectModel()->_delNode(m_node.get(), m_nameReg);
m_data = g_MainWindow->projectModel()->_delNode(static_cast<ProjectModel::GroupNode*>(m_node.get()), m_nameReg);
setObsolete(true);
g_MainWindow->m_undoStack->clear();
}
public:
explicit GroupNodeUndoCommand(const QString& text, std::unique_ptr<amuse::AudioGroupDatabase>&& data, ProjectModel::GroupNode* node)
: QUndoCommand(text.arg(node->text())), m_data(std::move(data)), m_node(node) {}
explicit GroupNodeUndoCommand(const QString& text, std::unique_ptr<amuse::AudioGroupDatabase>&& data,
ProjectModel::GroupNode* node)
: EditorUndoCommand(node, text.arg(node->text())), m_data(std::move(data)) {}
};
class GroupNodeAddUndoCommand : public GroupNodeUndoCommand
{
using base = GroupNodeUndoCommand;
public:
explicit GroupNodeAddUndoCommand(const QString& text, std::unique_ptr<amuse::AudioGroupDatabase>&& data, ProjectModel::GroupNode* node)
explicit GroupNodeAddUndoCommand(const QString& text, std::unique_ptr<amuse::AudioGroupDatabase>&& data,
ProjectModel::GroupNode* node)
: GroupNodeUndoCommand(text, std::move(data), node) {}
void undo() { base::del(); }
void redo() { base::add(); }
@ -956,6 +1163,7 @@ std::unique_ptr<amuse::AudioGroupDatabase> ProjectModel::_delNode(GroupNode* nod
std::unique_ptr<amuse::AudioGroupDatabase> ret = std::move(node->m_it->second);
m_groups.erase(node->m_it);
node->m_it = {};
QDir(QFileInfo(m_dir, node->name()).filePath()).removeRecursively();
m_root->removeChild(node);
endRemoveRows();
return ret;
@ -978,24 +1186,23 @@ ProjectModel::GroupNode* ProjectModel::newSubproject(const QString& name)
}
template <class NT>
class NodeUndoCommand : public QUndoCommand
class NodeUndoCommand : public EditorUndoCommand
{
protected:
amuse::ObjToken<ProjectModel::GroupNode> m_parent;
amuse::ObjToken<NT> m_node;
ProjectModel::NameUndoRegistry m_nameReg;
void add()
{
g_MainWindow->projectModel()->_addNode(m_node.get(), m_parent.get(), m_nameReg);
g_MainWindow->projectModel()->_addNode(static_cast<NT*>(m_node.get()), m_parent.get(), m_nameReg);
g_MainWindow->recursiveExpandAndSelectOutline(g_MainWindow->projectModel()->index(m_node.get()));
}
void del()
{
g_MainWindow->projectModel()->_delNode(m_node.get(), m_parent.get(), m_nameReg);
g_MainWindow->projectModel()->_delNode(static_cast<NT*>(m_node.get()), m_parent.get(), m_nameReg);
}
public:
explicit NodeUndoCommand(const QString& text, NT* node, ProjectModel::GroupNode* parent)
: QUndoCommand(text.arg(node->text())), m_parent(parent), m_node(node){}
: EditorUndoCommand(node, text.arg(node->text())), m_parent(parent) {}
};
template <class NT>
@ -1109,6 +1316,8 @@ static constexpr ProjectModel::INode::Type GetINodeType(ProjectModel::KeymapNode
{ return ProjectModel::INode::Type::Keymap; }
static constexpr ProjectModel::INode::Type GetINodeType(ProjectModel::LayersNode*)
{ return ProjectModel::INode::Type::Layer; }
static constexpr ProjectModel::INode::Type GetINodeType(ProjectModel::SampleNode*)
{ return ProjectModel::INode::Type::Sample; }
static constexpr amuse::NameDB::Type GetNameDBType(ProjectModel::SoundMacroNode*)
{ return amuse::NameDB::Type::SoundMacro; }
@ -1120,12 +1329,23 @@ static constexpr amuse::NameDB::Type GetNameDBType(ProjectModel::KeymapNode*)
{ return amuse::NameDB::Type::Keymap; }
static constexpr amuse::NameDB::Type GetNameDBType(ProjectModel::LayersNode*)
{ return amuse::NameDB::Type::Layer; }
static constexpr amuse::NameDB::Type GetNameDBType(ProjectModel::SampleNode*)
{ return amuse::NameDB::Type::Sample; }
static amuse::NameDB* GetNameDB(ProjectModel::SoundMacroNode*) { return amuse::SoundMacroId::CurNameDB; }
static amuse::NameDB* GetNameDB(ProjectModel::ADSRNode*) { return amuse::TableId::CurNameDB; }
static amuse::NameDB* GetNameDB(ProjectModel::CurveNode*) { return amuse::TableId::CurNameDB; }
static amuse::NameDB* GetNameDB(ProjectModel::KeymapNode*) { return amuse::KeymapId::CurNameDB; }
static amuse::NameDB* GetNameDB(ProjectModel::LayersNode*) { return amuse::LayersId::CurNameDB; }
template <class NT>
inline amuse::NameDB* GetNameDB() { return nullptr; }
template <>
inline amuse::NameDB* GetNameDB<ProjectModel::SoundMacroNode>() { return amuse::SoundMacroId::CurNameDB; }
template <>
inline amuse::NameDB* GetNameDB<ProjectModel::ADSRNode>() { return amuse::TableId::CurNameDB; }
template <>
inline amuse::NameDB* GetNameDB<ProjectModel::CurveNode>() { return amuse::TableId::CurNameDB; }
template <>
inline amuse::NameDB* GetNameDB<ProjectModel::KeymapNode>() { return amuse::KeymapId::CurNameDB; }
template <>
inline amuse::NameDB* GetNameDB<ProjectModel::LayersNode>() { return amuse::LayersId::CurNameDB; }
template <>
inline amuse::NameDB* GetNameDB<ProjectModel::SampleNode>() { return amuse::SampleId::CurNameDB; }
template <class NT, class T>
void ProjectModel::_addPoolNode(NT* node, GroupNode* parent, const NameUndoRegistry& registry, T& container)
@ -1134,8 +1354,8 @@ void ProjectModel::_addPoolNode(NT* node, GroupNode* parent, const NameUndoRegis
CollectionNode* coll = parent->getCollectionOfType(GetINodeType(node));
int insertIdx = coll->hypotheticalIndex(node->name());
beginInsertRows(index(coll), insertIdx, insertIdx);
auto newId = GetNameDB(node)->generateId(GetNameDBType(node));
GetNameDB(node)->registerPair(node->name().toUtf8().data(), newId);
auto newId = GetNameDB<NT>()->generateId(GetNameDBType(node));
GetNameDB<NT>()->registerPair(node->name().toUtf8().data(), newId);
container[newId] = node->m_obj;
node->m_id = newId;
coll->insertChild(node);
@ -1147,11 +1367,11 @@ template <class NT, class T>
void ProjectModel::_delPoolNode(NT* node, GroupNode* parent, NameUndoRegistry& registry, T& container)
{
int idx = node->row();
setIdDatabases(parent);
CollectionNode* coll = parent->getCollectionOfType(GetINodeType(node));
beginRemoveRows(index(coll), idx, idx);
_preDelNode(node, registry);
setIdDatabases(parent);
GetNameDB(node)->remove(node->m_id);
GetNameDB<NT>()->remove(node->m_id);
container.erase(node->m_id);
node->m_id = {};
coll->removeChild(node);
@ -1296,18 +1516,505 @@ ProjectModel::LayersNode* ProjectModel::newLayers(GroupNode* group, const QStrin
return node.get();
}
QString ProjectModel::MakeDedupedSubprojectName(const QString& origName)
{
QString dupeName = origName + tr("-copy");
QString useName = dupeName;
int dupeIdx = 1;
while (QFileInfo(m_dir.filePath(useName)).exists())
useName = dupeName + QString::number(dupeIdx++);
return useName;
}
QString ProjectModel::MakeDedupedName(const QString& origName, amuse::NameDB* db)
{
QString dupeName = origName + tr("-copy");
QString useName = dupeName;
int dupeIdx = 1;
while (db->m_stringToId.find(useName.toUtf8().data()) != db->m_stringToId.cend())
useName = dupeName + QString::number(dupeIdx++);
return useName;
}
static amuse::ObjToken<std::unique_ptr<amuse::ITable>> DuplicateTable(amuse::ITable* table)
{
switch (table->Isa())
{
case amuse::ITable::Type::ADSR:
return amuse::MakeObj<std::unique_ptr<amuse::ITable>>
(std::make_unique<amuse::ADSR>(static_cast<amuse::ADSR&>(*table)));
case amuse::ITable::Type::ADSRDLS:
return amuse::MakeObj<std::unique_ptr<amuse::ITable>>
(std::make_unique<amuse::ADSRDLS>(static_cast<amuse::ADSRDLS&>(*table)));
case amuse::ITable::Type::Curve:
return amuse::MakeObj<std::unique_ptr<amuse::ITable>>
(std::make_unique<amuse::Curve>(static_cast<amuse::Curve&>(*table)));
default:
return {};
}
}
QStringList ProjectModel::mimeTypes() const
{
return {QStringLiteral("application/x-amuse-subprojectpath"),
QStringLiteral("application/x-amuse-songgroup"),
QStringLiteral("application/x-amuse-soundgroup"),
QStringLiteral("application/x-amuse-soundmacro"),
QStringLiteral("application/x-amuse-adsr"),
QStringLiteral("application/x-amuse-curve"),
QStringLiteral("application/x-amuse-keymap"),
QStringLiteral("application/x-amuse-layers"),
QStringLiteral("application/x-amuse-samplepath")};
}
static void WriteMimeYAML(athena::io::YAMLDocWriter& w, ProjectModel::SongGroupNode* n) { n->m_index->toYAML(w); }
static void WriteMimeYAML(athena::io::YAMLDocWriter& w, ProjectModel::SoundGroupNode* n) { n->m_index->toYAML(w); }
static void WriteMimeYAML(athena::io::YAMLDocWriter& w, ProjectModel::SoundMacroNode* n)
{
if (auto __r2 = w.enterSubVector("cmds"))
n->m_obj->toYAML(w);
}
static void WriteMimeYAML(athena::io::YAMLDocWriter& w, ProjectModel::ADSRNode* n) { n->m_obj->get()->write(w); }
static void WriteMimeYAML(athena::io::YAMLDocWriter& w, ProjectModel::CurveNode* n) { n->m_obj->get()->write(w); }
static void WriteMimeYAML(athena::io::YAMLDocWriter& w, ProjectModel::KeymapNode* n)
{
if (auto __v = w.enterSubVector("entries"))
{
for (const auto& km : *n->m_obj)
{
if (auto __r2 = w.enterSubRecord(nullptr))
{
w.setStyle(athena::io::YAMLNodeStyle::Flow);
km.write(w);
}
}
}
}
static void WriteMimeYAML(athena::io::YAMLDocWriter& w, ProjectModel::LayersNode* n)
{
if (auto __v = w.enterSubVector("entries"))
{
for (const auto& lm : *n->m_obj)
{
if (auto __r2 = w.enterSubRecord(nullptr))
{
w.setStyle(athena::io::YAMLNodeStyle::Flow);
lm.write(w);
}
}
}
}
template <class NT>
EditorUndoCommand* ProjectModel::readMimeYAML(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn) {}
template <>
EditorUndoCommand* ProjectModel::readMimeYAML<ProjectModel::SongGroupNode>
(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn)
{
auto dataNode = amuse::MakeObj<amuse::SongGroupIndex>();
dataNode->fromYAML(r);
auto node = amuse::MakeObj<SongGroupNode>(name, dataNode);
return new NodeAddUndoCommand(ProjectModel::tr("Add Song Group %1"), node.get(), gn);
}
template <>
EditorUndoCommand* ProjectModel::readMimeYAML<ProjectModel::SoundGroupNode>
(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn)
{
auto dataNode = amuse::MakeObj<amuse::SFXGroupIndex>();
dataNode->fromYAML(r);
auto node = amuse::MakeObj<SoundGroupNode>(name, dataNode);
return new NodeAddUndoCommand(ProjectModel::tr("Add Sound Group %1"), node.get(), gn);
}
template <>
EditorUndoCommand* ProjectModel::readMimeYAML<ProjectModel::SoundMacroNode>
(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn)
{
auto dataNode = amuse::MakeObj<amuse::SoundMacro>();
size_t cmdCount;
if (auto __v = r.enterSubVector("cmds", cmdCount))
dataNode->fromYAML(r, cmdCount);
auto node = amuse::MakeObj<SoundMacroNode>(name, dataNode);
return new NodeAddUndoCommand(ProjectModel::tr("Add SoundMacro %1"), node.get(), gn);
}
template <>
EditorUndoCommand* ProjectModel::readMimeYAML<ProjectModel::ADSRNode>
(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn)
{
amuse::ObjToken<std::unique_ptr<amuse::ITable>> dataNode;
if (auto __vta = r.enterSubRecord("velToAttack"))
{
__vta.leave();
dataNode = amuse::MakeObj<std::unique_ptr<amuse::ITable>>(std::make_unique<amuse::ADSRDLS>());
static_cast<amuse::ADSRDLS&>(**dataNode).read(r);
}
else
{
dataNode = amuse::MakeObj<std::unique_ptr<amuse::ITable>>(std::make_unique<amuse::ADSR>());
static_cast<amuse::ADSR&>(**dataNode).read(r);
}
auto node = amuse::MakeObj<ADSRNode>(name, dataNode);
return new NodeAddUndoCommand(ProjectModel::tr("Add ADSR %1"), node.get(), gn);
}
template <>
EditorUndoCommand* ProjectModel::readMimeYAML<ProjectModel::CurveNode>
(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn)
{
auto dataNode = amuse::MakeObj<std::unique_ptr<amuse::ITable>>(std::make_unique<amuse::Curve>());
static_cast<amuse::Curve&>(**dataNode).read(r);
auto node = amuse::MakeObj<CurveNode>(name, dataNode);
return new NodeAddUndoCommand(ProjectModel::tr("Add Curve %1"), node.get(), gn);
}
template <>
EditorUndoCommand* ProjectModel::readMimeYAML<ProjectModel::KeymapNode>
(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn)
{
auto dataNode = amuse::MakeObj<std::array<amuse::Keymap, 128>>();
size_t entryCount;
if (auto __v = r.enterSubVector("entries", entryCount))
{
for (size_t i = 0; i < entryCount; ++i)
{
if (auto __r2 = r.enterSubRecord(nullptr))
{
(*dataNode)[i].read(r);
}
}
}
auto node = amuse::MakeObj<KeymapNode>(name, dataNode);
return new NodeAddUndoCommand(ProjectModel::tr("Add Keymap %1"), node.get(), gn);
}
template <>
EditorUndoCommand* ProjectModel::readMimeYAML<ProjectModel::LayersNode>
(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn)
{
auto dataNode = amuse::MakeObj<std::vector<amuse::LayerMapping>>();
size_t entryCount;
if (auto __v = r.enterSubVector("entries", entryCount))
{
dataNode->resize(entryCount);
for (size_t i = 0; i < entryCount; ++i)
{
if (auto __r2 = r.enterSubRecord(nullptr))
{
(*dataNode)[i].read(r);
}
}
}
auto node = amuse::MakeObj<LayersNode>(name, dataNode);
return new NodeAddUndoCommand(ProjectModel::tr("Add Layers %1"), node.get(), gn);
}
template <class NT>
void ProjectModel::loadMimeData(const QMimeData* data, const QString& mimeType, GroupNode* gn)
{
auto d = data->data(mimeType);
athena::io::MemoryReader mr(d.data(), atUint64(d.length()));
athena::io::YAMLDocReader r;
if (r.parse(&mr))
{
QString newName = MakeDedupedName(QString::fromStdString(r.readString("name")), GetNameDB<NT>());
g_MainWindow->pushUndoCommand(readMimeYAML<NT>(r, newName, gn));
}
}
template <class NT>
QMimeData* MakeMimeData(NT* n, const QString& mimeType)
{
QMimeData* data = new QMimeData;
athena::io::VectorWriter vw;
athena::io::YAMLDocWriter w(nullptr);
w.writeString("name", QStringToSysString(n->name()));
WriteMimeYAML(w, n);
w.finish(&vw);
data->setData(mimeType, QByteArray((char*)vw.data().data(), int(vw.data().size())));
return data;
}
QMimeData* ProjectModel::mimeData(const QModelIndexList& indexes) const
{
QModelIndex index = indexes.first();
if (!index.isValid())
return nullptr;
assert(index.model() == this && "Not ProjectModel");
INode* n = node(index);
switch (n->type())
{
case INode::Type::Group:
{
QDir dir(QFileInfo(m_dir, n->name()).filePath());
QMimeData* data = new QMimeData;
data->setData(QStringLiteral("application/x-amuse-subprojectpath"), dir.path().toUtf8());
return data;
}
case INode::Type::SoundGroup:
return MakeMimeData(static_cast<SoundGroupNode*>(n), QStringLiteral("application/x-amuse-soundgroup"));
case INode::Type::SongGroup:
return MakeMimeData(static_cast<SongGroupNode*>(n), QStringLiteral("application/x-amuse-songgroup"));
case INode::Type::SoundMacro:
return MakeMimeData(static_cast<SoundMacroNode*>(n), QStringLiteral("application/x-amuse-soundmacro"));
case INode::Type::ADSR:
return MakeMimeData(static_cast<ADSRNode*>(n), QStringLiteral("application/x-amuse-adsr"));
case INode::Type::Curve:
return MakeMimeData(static_cast<CurveNode*>(n), QStringLiteral("application/x-amuse-curve"));
case INode::Type::Keymap:
return MakeMimeData(static_cast<KeymapNode*>(n), QStringLiteral("application/x-amuse-keymap"));
case INode::Type::Layer:
return MakeMimeData(static_cast<LayersNode*>(n), QStringLiteral("application/x-amuse-layers"));
case INode::Type::Sample:
{
GroupNode* gn = getGroupNode(n);
QString path = SysStringToQString(gn->getAudioGroup()->getSampleBasePath(static_cast<SampleNode*>(n)->id()));
QMimeData* data = new QMimeData;
data->setData(QStringLiteral("application/x-amuse-samplepath"), path.toUtf8());
return data;
}
default:
return nullptr;
}
}
bool ProjectModel::dropMimeData(const QMimeData* data, Qt::DropAction action,
int row, int column, const QModelIndex& parent)
{
if (data->hasFormat(QStringLiteral("application/x-amuse-subprojectpath")))
{
auto path = data->data(QStringLiteral("application/x-amuse-subprojectpath"));
QDir oldDir(path);
QString newName = MakeDedupedSubprojectName(oldDir.dirName());
m_dir.mkdir(newName);
QDir newDir(QFileInfo(m_dir, newName).filePath());
for (auto ent : oldDir.entryList({"*.wav", "*.dsp", "*.vadpcm", "!pool.yaml", "!project.yaml"}, QDir::Files))
QFile::copy(QFileInfo(oldDir, ent).filePath(), QFileInfo(newDir, ent).filePath());
auto dataNode = std::make_unique<amuse::AudioGroupDatabase>(QStringToSysString(newDir.path()));
auto node = amuse::MakeObj<GroupNode>(newName);
_buildGroupNode(*node, *dataNode);
g_MainWindow->pushUndoCommand(
new GroupNodeAddUndoCommand(tr("Add Subproject %1"), std::move(dataNode), node.get()));
return true;
}
GroupNode* gn;
if (parent.isValid())
gn = getGroupNode(node(parent));
else
gn = static_cast<GroupNode*>(m_root->child(row));
setIdDatabases(gn);
if (data->hasFormat(QStringLiteral("application/x-amuse-soundgroup")))
loadMimeData<SoundGroupNode>(data, QStringLiteral("application/x-amuse-soundgroup"), gn);
else if (data->hasFormat(QStringLiteral("application/x-amuse-songgroup")))
loadMimeData<SongGroupNode>(data, QStringLiteral("application/x-amuse-songgroup"), gn);
else if (data->hasFormat(QStringLiteral("application/x-amuse-soundmacro")))
loadMimeData<SoundMacroNode>(data, QStringLiteral("application/x-amuse-soundmacro"), gn);
else if (data->hasFormat(QStringLiteral("application/x-amuse-adsr")))
loadMimeData<ADSRNode>(data, QStringLiteral("application/x-amuse-adsr"), gn);
else if (data->hasFormat(QStringLiteral("application/x-amuse-curve")))
loadMimeData<CurveNode>(data, QStringLiteral("application/x-amuse-curve"), gn);
else if (data->hasFormat(QStringLiteral("application/x-amuse-keymap")))
loadMimeData<KeymapNode>(data, QStringLiteral("application/x-amuse-keymap"), gn);
else if (data->hasFormat(QStringLiteral("application/x-amuse-layers")))
loadMimeData<LayersNode>(data, QStringLiteral("application/x-amuse-layers"), gn);
else if (data->hasFormat(QStringLiteral("application/x-amuse-samplepath")))
{
auto path = data->data(QStringLiteral("application/x-amuse-samplepath"));
QString newName = MakeDedupedName(QFileInfo(path).completeBaseName(), amuse::SampleId::CurNameDB);
QString newBasePath = QFileInfo(QFileInfo(m_dir, gn->name()).filePath(), newName).filePath();
amuse::SystemString newBasePathStr = QStringToSysString(newBasePath);
gn->getAudioGroup()->copySampleInto(QStringToSysString(QString::fromUtf8(path)), newBasePathStr);
auto dataNode = amuse::MakeObj<amuse::SampleEntry>();
dataNode->loadLooseData(newBasePathStr);
auto node = amuse::MakeObj<SampleNode>(newName, dataNode);
NameUndoRegistry dummy;
_addPoolNode(node.get(), gn, dummy, gn->getAudioGroup()->getSdir().sampleEntries());
}
else
return false;
return true;
}
void ProjectModel::cut(const QModelIndex& index)
{
if (!index.isValid())
return;
assert(index.model() == this && "Not ProjectModel");
QMimeData* data = mimeData({index});
if (data)
{
QGuiApplication::clipboard()->setMimeData(data);
INode* n = node(index);
EditorUndoCommand* cmd = nullptr;
switch (n->type())
{
case INode::Type::SongGroup:
cmd = new NodeDelUndoCommand(tr("Cut SongGroup %1"), static_cast<SongGroupNode*>(n));
break;
case INode::Type::SoundGroup:
cmd = new NodeDelUndoCommand(tr("Cut SFXGroup %1"), static_cast<SoundGroupNode*>(n));
break;
case INode::Type::SoundMacro:
cmd = new NodeDelUndoCommand(tr("Cut SoundMacro %1"), static_cast<SoundMacroNode*>(n));
break;
case INode::Type::ADSR:
cmd = new NodeDelUndoCommand(tr("Cut ADSR %1"), static_cast<ADSRNode*>(n));
break;
case INode::Type::Curve:
cmd = new NodeDelUndoCommand(tr("Cut Curve %1"), static_cast<CurveNode*>(n));
break;
case INode::Type::Keymap:
cmd = new NodeDelUndoCommand(tr("Cut Keymap %1"), static_cast<KeymapNode*>(n));
break;
case INode::Type::Layer:
cmd = new NodeDelUndoCommand(tr("Cut Layers %1"), static_cast<LayersNode*>(n));
break;
default:
break;
}
if (cmd)
g_MainWindow->pushUndoCommand(cmd);
}
}
void ProjectModel::copy(const QModelIndex& index)
{
if (!index.isValid())
return;
assert(index.model() == this && "Not ProjectModel");
QMimeData* data = mimeData({index});
if (data)
QGuiApplication::clipboard()->setMimeData(data);
}
void ProjectModel::paste(const QModelIndex& index)
{
if (!index.isValid())
return;
assert(index.model() == this && "Not ProjectModel");
dropMimeData(QGuiApplication::clipboard()->mimeData(), Qt::DropAction::CopyAction,
index.row(), index.column(), index.parent());
}
QModelIndex ProjectModel::duplicate(const QModelIndex& index)
{
QModelIndex ret;
if (!index.isValid())
return ret;
assert(index.model() == this && "Not ProjectModel");
INode* n = node(index);
setIdDatabases(n);
EditorUndoCommand* cmd = nullptr;
amuse::NameDB* nameDb = n->getNameDb();
QString newName;
if (nameDb)
newName = MakeDedupedName(n->name(), nameDb);
GroupNode* gn = getGroupNode(n);
switch (n->type())
{
case INode::Type::Group:
{
GroupNode* cn = static_cast<GroupNode*>(n);
newName = MakeDedupedSubprojectName(n->name());
m_dir.mkdir(newName);
QDir oldDir(QFileInfo(m_dir, n->name()).filePath());
QDir newDir(QFileInfo(m_dir, newName).filePath());
for (auto ent : oldDir.entryList({"*.wav", "*.dsp", "*.vadpcm", "!pool.yaml"}, QDir::Files))
QFile::copy(QFileInfo(oldDir, ent).filePath(), QFileInfo(newDir, ent).filePath());
auto data = std::make_unique<amuse::AudioGroupDatabase>(*cn->getAudioGroup(), QStringToSysString(newDir.path()));
auto node = amuse::MakeObj<GroupNode>(newName);
_buildGroupNode(*node, *data);
cmd = new GroupNodeAddUndoCommand(tr("Add Subproject %1"), std::move(data), node.get());
ret = ProjectModel::index(node.get());
break;
}
case INode::Type::SoundGroup:
{
SoundGroupNode* cn = static_cast<SoundGroupNode*>(n);
auto node = amuse::MakeObj<SoundGroupNode>(newName, amuse::MakeObj<amuse::SFXGroupIndex>(*cn->m_index));
cmd = new NodeAddUndoCommand(tr("Add Sound Group %1"), node.get(), gn);
ret = ProjectModel::index(node.get());
break;
}
case INode::Type::SongGroup:
{
SongGroupNode* cn = static_cast<SongGroupNode*>(n);
auto node = amuse::MakeObj<SongGroupNode>(newName, amuse::MakeObj<amuse::SongGroupIndex>(*cn->m_index));
cmd = new NodeAddUndoCommand(tr("Add Song Group %1"), node.get(), gn);
ret = ProjectModel::index(node.get());
break;
}
case INode::Type::SoundMacro:
{
SoundMacroNode* cn = static_cast<SoundMacroNode*>(n);
auto dataNode = amuse::MakeObj<amuse::SoundMacro>();
dataNode->buildFromPrototype(*cn->m_obj);
auto node = amuse::MakeObj<SoundMacroNode>(newName, dataNode);
cmd = new NodeAddUndoCommand(tr("Add Sound Macro %1"), node.get(), gn);
ret = ProjectModel::index(node.get());
break;
}
case INode::Type::ADSR:
{
ADSRNode* cn = static_cast<ADSRNode*>(n);
auto node = amuse::MakeObj<ADSRNode>(newName, DuplicateTable(cn->m_obj->get()));
cmd = new NodeAddUndoCommand(tr("Add ADSR %1"), node.get(), gn);
ret = ProjectModel::index(node.get());
break;
}
case INode::Type::Curve:
{
CurveNode* cn = static_cast<CurveNode*>(n);
auto node = amuse::MakeObj<CurveNode>(newName, DuplicateTable(cn->m_obj->get()));
cmd = new NodeAddUndoCommand(tr("Add Curve %1"), node.get(), gn);
ret = ProjectModel::index(node.get());
break;
}
case INode::Type::Keymap:
{
KeymapNode* cn = static_cast<KeymapNode*>(n);
auto node = amuse::MakeObj<KeymapNode>(newName, amuse::MakeObj<std::array<amuse::Keymap, 128>>(*cn->m_obj));
cmd = new NodeAddUndoCommand(tr("Add Keymap %1"), node.get(), gn);
ret = ProjectModel::index(node.get());
break;
}
case INode::Type::Layer:
{
LayersNode* cn = static_cast<LayersNode*>(n);
auto node = amuse::MakeObj<LayersNode>(newName, amuse::MakeObj<std::vector<amuse::LayerMapping>>(*cn->m_obj));
cmd = new NodeAddUndoCommand(tr("Add Layers %1"), node.get(), gn);
ret = ProjectModel::index(node.get());
break;
}
default:
break;
}
if (cmd)
g_MainWindow->pushUndoCommand(cmd);
return ret;
}
void ProjectModel::del(const QModelIndex& index)
{
if (!index.isValid())
return;
assert(index.model() == this && "Not ProjectModel");
INode* n = node(index);
QUndoCommand* cmd = nullptr;
EditorUndoCommand* cmd = nullptr;
switch (n->type())
{
case INode::Type::Group:
cmd = new GroupNodeDelUndoCommand(tr("Delete Subproject %1"), static_cast<GroupNode*>(n));
{
int result = g_MainWindow->uiMessenger().warning(tr("Delete Subproject"),
tr("<p>The subproject %1 will be permanently deleted from the project. "
"Sample files will be permanently removed from the file system.</p>"
"<p><strong>This action cannot be undone!</strong></p><p>Continue?</p>").arg(n->name()),
QMessageBox::Yes, QMessageBox::No);
if (result == QMessageBox::No)
return;
NameUndoRegistry nameReg;
g_MainWindow->projectModel()->_delNode(static_cast<GroupNode*>(n), nameReg);
g_MainWindow->m_undoStack->clear();
break;
}
case INode::Type::SongGroup:
cmd = new NodeDelUndoCommand(tr("Delete SongGroup %1"), static_cast<SongGroupNode*>(n));
break;
@ -1329,6 +2036,21 @@ void ProjectModel::del(const QModelIndex& index)
case INode::Type::Layer:
cmd = new NodeDelUndoCommand(tr("Delete Layers %1"), static_cast<LayersNode*>(n));
break;
case INode::Type::Sample:
{
int result = g_MainWindow->uiMessenger().warning(tr("Delete Sample"),
tr("<p>The sample %1 will be permanently deleted from the file system. "
"<p><strong>This action cannot be undone!</strong></p><p>Continue?</p>").arg(n->name()),
QMessageBox::Yes, QMessageBox::No);
if (result == QMessageBox::No)
return;
NameUndoRegistry nameReg;
GroupNode* gn = getGroupNode(n);
gn->getAudioGroup()->deleteSample(static_cast<SampleNode*>(n)->id());
_delPoolNode(static_cast<SampleNode*>(n), gn, nameReg, gn->getAudioGroup()->getSdir().sampleEntries());
g_MainWindow->m_undoStack->clear();
break;
}
default:
break;
}

View File

@ -15,6 +15,18 @@
#include "amuse/AudioGroupSampleDirectory.hpp"
class ProjectModel;
class EditorUndoCommand;
enum AmuseItemEditFlags
{
AmuseItemNone = 0,
AmuseItemCut = 1,
AmuseItemCopy = 2,
AmuseItemPaste = 4,
AmuseItemDelete = 8,
AmuseItemNoCut = (AmuseItemCopy | AmuseItemPaste | AmuseItemDelete),
AmuseItemAll = (AmuseItemCut | AmuseItemCopy | AmuseItemPaste | AmuseItemDelete)
};
class NullItemProxyModel : public QIdentityProxyModel
{
@ -86,6 +98,8 @@ private:
public:
class INode : public amuse::IObj
{
friend class ProjectModel;
virtual void _sortChildren();
public:
enum class Type
{
@ -144,13 +158,14 @@ public:
}
amuse::ObjToken<INode> removeChild(INode* n)
{
int row = n->row();
assert(n == m_children.at(row).get() && "Removing non-child from node");
amuse::ObjToken<INode> ret = n;
int row = ret->row();
assert(ret.get() == m_children.at(row).get() && "Removing non-child from node");
m_children.erase(m_children.begin() + row);
reindexRows(row);
n->m_parent = nullptr;
n->m_row = -1;
return n;
ret->m_parent = nullptr;
ret->m_row = -1;
return ret;
}
void reserve(size_t sz) { m_children.reserve(sz); }
@ -172,6 +187,17 @@ public:
return static_cast<T&>(*tok);
}
INode* findChild(const QString& name) const
{
int idx = hypotheticalIndex(name);
if (idx >= m_children.size())
return nullptr;
INode* ret = m_children[idx].get();
if (ret->name() == name)
return ret;
return nullptr;
}
bool depthTraverse(const std::function<bool(INode* node)>& func)
{
for (auto& n : m_children)
@ -191,15 +217,18 @@ public:
const QString& name() const { return m_name; }
virtual int hypotheticalIndex(const QString& name) const;
virtual amuse::NameDB* getNameDb() const { return nullptr; }
virtual Type type() const = 0;
virtual QString text() const = 0;
virtual QIcon icon() const = 0;
virtual Qt::ItemFlags flags() const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; }
virtual Qt::ItemFlags flags() const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; }
virtual AmuseItemEditFlags editFlags() const { return AmuseItemNone; }
virtual void registerNames(const NameUndoRegistry& registry) const {}
virtual void unregisterNames(NameUndoRegistry& registry) const {}
};
struct NullNode : INode
struct NullNode final : INode
{
NullNode(INode* parent) : INode(parent) {}
@ -207,7 +236,7 @@ public:
QString text() const { return {}; }
QIcon icon() const { return {}; }
};
struct RootNode : INode
struct RootNode final : INode
{
RootNode() : INode(QStringLiteral("<root>")) {}
@ -218,7 +247,7 @@ public:
};
struct CollectionNode;
struct BasePoolObjectNode;
struct GroupNode : INode
struct GroupNode final : INode
{
std::unordered_map<QString, std::unique_ptr<amuse::AudioGroupDatabase>>::iterator m_it;
GroupNode(const QString& name) : INode(name) {}
@ -226,17 +255,19 @@ public:
: INode(it->first), m_it(it) {}
int hypotheticalIndex(const QString& name) const;
void _sortChildren();
static QIcon Icon;
Type type() const { return Type::Group; }
QString text() const { return m_name; }
QIcon icon() const { return Icon; }
AmuseItemEditFlags editFlags() const { return AmuseItemNoCut; }
CollectionNode* getCollectionOfType(Type tp) const;
amuse::AudioGroupDatabase* getAudioGroup() const { return m_it->second.get(); }
BasePoolObjectNode* pageObjectNodeOfId(amuse::ObjectId id) const;
};
struct SongGroupNode : INode
struct SongGroupNode final : INode
{
amuse::GroupId m_id;
amuse::ObjToken<amuse::SongGroupIndex> m_index;
@ -249,6 +280,9 @@ public:
Type type() const { return Type::SongGroup; }
QString text() const { return m_name; }
QIcon icon() const { return Icon; }
AmuseItemEditFlags editFlags() const { return AmuseItemAll; }
amuse::NameDB* getNameDb() const { return amuse::GroupId::CurNameDB; }
void registerNames(const NameUndoRegistry& registry) const
{
@ -263,7 +297,7 @@ public:
registry.unregisterSongName(p.first);
}
};
struct SoundGroupNode : INode
struct SoundGroupNode final : INode
{
amuse::GroupId m_id;
amuse::ObjToken<amuse::SFXGroupIndex> m_index;
@ -276,6 +310,9 @@ public:
Type type() const { return Type::SoundGroup; }
QString text() const { return m_name; }
QIcon icon() const { return Icon; }
AmuseItemEditFlags editFlags() const { return AmuseItemAll; }
amuse::NameDB* getNameDb() const { return amuse::GroupId::CurNameDB; }
void registerNames(const NameUndoRegistry& registry) const
{
@ -290,7 +327,7 @@ public:
registry.unregisterSFXName(p.first);
}
};
struct CollectionNode : INode
struct CollectionNode final : INode
{
QIcon m_icon;
Type m_collectionType;
@ -319,7 +356,7 @@ public:
QIcon icon() const { return {}; }
};
template <class ID, class T, INode::Type TP>
struct PoolObjectNode : BasePoolObjectNode
struct PoolObjectNode final : BasePoolObjectNode
{
amuse::ObjToken<T> m_obj;
PoolObjectNode(const QString& name, amuse::ObjToken<T> obj) : BasePoolObjectNode(name), m_obj(obj) {}
@ -327,6 +364,7 @@ public:
: BasePoolObjectNode(id, ID::CurNameDB->resolveNameFromId(id).data()), m_obj(obj) {}
Type type() const { return TP; }
AmuseItemEditFlags editFlags() const { return TP == INode::Type::Sample ? AmuseItemNoCut : AmuseItemAll; }
void registerNames(const NameUndoRegistry& registry) const
{
@ -336,6 +374,10 @@ public:
{
ID::CurNameDB->remove(m_id);
}
amuse::NameDB* getNameDb() const
{
return ID::CurNameDB;
}
};
using SoundMacroNode = PoolObjectNode<amuse::SoundMacroId, amuse::SoundMacro, INode::Type::SoundMacro>;
using ADSRNode = PoolObjectNode<amuse::TableId, std::unique_ptr<amuse::ITable>, INode::Type::ADSR>;
@ -348,9 +390,11 @@ public:
bool m_needsReset = false;
void _buildGroupNodeCollections(GroupNode& gn);
void _buildGroupNode(GroupNode& gn);
void _buildGroupNode(GroupNode& gn, amuse::AudioGroup& group);
void _resetModelData();
void _resetSongRefCount();
QString MakeDedupedSubprojectName(const QString& origName);
static QString MakeDedupedName(const QString& origName, amuse::NameDB* db);
public:
explicit ProjectModel(const QString& path, QObject* parent = Q_NULLPTR);
@ -366,7 +410,10 @@ public:
bool saveToFile(UIMessenger& messenger);
QStringList getGroupList() const;
bool exportGroup(const QString& path, const QString& groupName, UIMessenger& messenger) const;
bool importHeader(const QString& path, const QString& groupName, UIMessenger& messenger) const;
bool exportHeader(const QString& path, const QString& groupName, bool& yesToAll, UIMessenger& messenger) const;
void updateNodeNames();
bool ensureModelData();
QModelIndex proxyCreateIndex(int arow, int acolumn, void *adata) const;
@ -376,10 +423,11 @@ public:
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);
Qt::ItemFlags flags(const QModelIndex& index) const;
INode* node(const QModelIndex& index) const;
GroupNode* getGroupNode(INode* node) const;
bool canEdit(const QModelIndex& index) const;
AmuseItemEditFlags editFlags(const QModelIndex& index) const;
RootNode* rootNode() const { return m_root.get(); }
void _postAddNode(INode* n, const NameUndoRegistry& registry);
@ -417,6 +465,21 @@ public:
void _addNode(LayersNode* node, GroupNode* parent, const NameUndoRegistry& registry);
void _delNode(LayersNode* node, GroupNode* parent, NameUndoRegistry& registry);
LayersNode* newLayers(GroupNode* group, const QString& name);
void _renameNode(INode* node, const QString& name);
template <class NT>
EditorUndoCommand* readMimeYAML(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn);
template <class NT>
void loadMimeData(const QMimeData* data, const QString& mimeType, GroupNode* gn);
QStringList mimeTypes() const;
QMimeData* mimeData(const QModelIndexList& indexes) const;
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
void cut(const QModelIndex& index);
void copy(const QModelIndex& index);
void paste(const QModelIndex& index);
QModelIndex duplicate(const QModelIndex& index);
void del(const QModelIndex& index);
const QDir& dir() const { return m_dir; }

View File

@ -251,24 +251,17 @@ void PageObjectDelegate::setEditorData(QWidget* editor, const QModelIndex& index
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();
QApplication::postEvent(editor, new QEvent(QEvent::User));
}
void PageObjectDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const
{
const PageModel* model = static_cast<const PageModel*>(m);
auto entry = model->m_sorted[index.row()];
int idx = static_cast<EditorFieldPageObjectNode*>(editor)->currentIndex();
ProjectModel::BasePoolObjectNode* node = static_cast<EditorFieldPageObjectNode*>(editor)->currentNode();
amuse::ObjectId id;
if (idx != 0)
{
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()))));
if (node)
id = node->id();
}
if (id == entry->second.objId.id)
{
emit m->dataChanged(index, index);
@ -432,7 +425,7 @@ QVariant PageModel::data(const QModelIndex& index, int role) const
switch (index.column())
{
case 0:
return entry->first;
return entry->first + 1;
case 1:
{
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(m_node.get());
@ -464,9 +457,10 @@ bool PageModel::setData(const QModelIndex& index, const QVariant& value, int rol
{
case 0:
{
if (value.toInt() == entry->first)
int prog = value.toInt() - 1;
if (prog == entry->first)
return false;
if (map.find(value.toInt()) != map.cend())
if (map.find(prog) != map.cend())
{
QMessageBox::critical(g_MainWindow, tr("Program Conflict"),
tr("Program %1 is already defined in table").arg(value.toInt()));
@ -935,7 +929,7 @@ QVariant SetupModel::data(const QModelIndex& index, int role) const
switch (index.column())
{
case 0:
return entry.programNo;
return entry.programNo + 1;
case 1:
return entry.volume;
case 2:
@ -959,26 +953,28 @@ bool SetupModel::setData(const QModelIndex& index, const QVariant& value, int ro
auto& entry = m_data->second[index.row()];
int val = value.toInt();
switch (index.column())
{
case 0:
if (entry.programNo == value.toInt())
val -= 1;
if (entry.programNo == val)
return false;
break;
case 1:
if (entry.volume == value.toInt())
if (entry.volume == val)
return false;
break;
case 2:
if (entry.panning == value.toInt())
if (entry.panning == val)
return false;
break;
case 3:
if (entry.reverb == value.toInt())
if (entry.reverb == val)
return false;
break;
case 4:
if (entry.chorus == value.toInt())
if (entry.chorus == val)
return false;
break;
default:
@ -988,7 +984,7 @@ bool SetupModel::setData(const QModelIndex& index, const QVariant& value, int ro
g_MainWindow->pushUndoCommand(new SetupDataChangeUndoCommand(
static_cast<SongGroupEditor*>(parent())->m_setupList.m_node.get(),
tr("Change %1").arg(headerData(index.column(), Qt::Horizontal).toString()), m_data->first,
index.row(), index.column(), value.toInt()));
index.row(), index.column(), val));
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
@ -1076,9 +1072,10 @@ PageTableView::PageTableView(QWidget* parent)
setGridStyle(Qt::NoPen);
m_127Delegate.setItemEditorFactory(&m_127Factory);
m_128Delegate.setItemEditorFactory(&m_128Factory);
m_255Delegate.setItemEditorFactory(&m_255Factory);
setItemDelegateForColumn(0, &m_127Delegate);
setItemDelegateForColumn(0, &m_128Delegate);
setItemDelegateForColumn(1, &m_poDelegate);
setItemDelegateForColumn(2, &m_255Delegate);
setItemDelegateForColumn(3, &m_255Delegate);
@ -1095,8 +1092,8 @@ void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* tabl
}
{
m_tableView->setModel(table);
auto hheader = m_tableView->horizontalHeader();
hheader->setSectionResizeMode(QHeaderView::Stretch);
m_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
m_tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
}
}
@ -1142,8 +1139,9 @@ SetupTableView::SetupTableView(QWidget* parent)
m_tableView->setGridStyle(Qt::NoPen);
m_127Delegate.setItemEditorFactory(&m_127Factory);
m_128Delegate.setItemEditorFactory(&m_128Factory);
m_tableView->setItemDelegateForColumn(0, &m_127Delegate);
m_tableView->setItemDelegateForColumn(0, &m_128Delegate);
m_tableView->setItemDelegateForColumn(1, &m_127Delegate);
m_tableView->setItemDelegateForColumn(2, &m_127Delegate);
m_tableView->setItemDelegateForColumn(3, &m_127Delegate);
@ -1434,28 +1432,13 @@ void SongGroupEditor::setupDataChanged()
}
}
bool SongGroupEditor::isItemEditEnabled() const
AmuseItemEditFlags SongGroupEditor::itemEditFlags() const
{
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
return table->hasFocus() && !table->selectionModel()->selectedRows().isEmpty();
return (table->hasFocus() && !table->selectionModel()->selectedRows().isEmpty()) ? AmuseItemDelete : AmuseItemNone;
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
return table->m_listView->hasFocus() && !table->m_listView->selectionModel()->selectedRows().isEmpty();
return false;
}
void SongGroupEditor::itemCutAction()
{
}
void SongGroupEditor::itemCopyAction()
{
}
void SongGroupEditor::itemPasteAction()
{
return (table->m_listView->hasFocus() && !table->m_listView->selectionModel()->selectedRows().isEmpty()) ? AmuseItemDelete : AmuseItemNone;
return AmuseItemNone;
}
void SongGroupEditor::itemDeleteAction()

View File

@ -170,8 +170,9 @@ class PageTableView : public QTableView
Q_OBJECT
PageObjectDelegate m_poDelegate;
RangedValueFactory<0, 127> m_127Factory;
RangedValueFactory<1, 128> m_128Factory;
RangedValueFactory<0, 255> m_255Factory;
QStyledItemDelegate m_127Delegate, m_255Delegate;
QStyledItemDelegate m_127Delegate, m_128Delegate, m_255Delegate;
public:
explicit PageTableView(QWidget* parent = Q_NULLPTR);
void setModel(QAbstractItemModel* model);
@ -187,7 +188,8 @@ class SetupTableView : public QSplitter
QTableView* m_tableView;
MIDIFileDelegate m_midiDelegate;
RangedValueFactory<0, 127> m_127Factory;
QStyledItemDelegate m_127Delegate;
RangedValueFactory<1, 128> m_128Factory;
QStyledItemDelegate m_127Delegate, m_128Delegate;
public:
explicit SetupTableView(QWidget* parent = Q_NULLPTR);
void setModel(QAbstractItemModel* list, QAbstractItemModel* table);
@ -262,7 +264,7 @@ public:
void setEditorEnabled(bool en) {}
void resizeEvent(QResizeEvent* ev);
QTableView* getSetupListView() const { return m_setupTable->m_listView; }
bool isItemEditEnabled() const;
AmuseItemEditFlags itemEditFlags() const;
public slots:
void doAdd();
void doSelectionChanged();
@ -271,10 +273,6 @@ public slots:
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
void modelAboutToBeReset();
void setupDataChanged();
void itemCutAction();
void itemCopyAction();
void itemPasteAction();
void itemDeleteAction();
};

View File

@ -142,24 +142,17 @@ void SFXObjectDelegate::setEditorData(QWidget* editor, const QModelIndex& index)
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();
QApplication::postEvent(editor, new QEvent(QEvent::User));
}
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();
ProjectModel::BasePoolObjectNode* node = static_cast<EditorFieldPageObjectNode*>(editor)->currentNode();
amuse::ObjectId id;
if (idx != 0)
{
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()))));
if (node)
id = node->id();
}
if (id == entry->second.objId.id)
{
emit m->dataChanged(index, index);
@ -614,9 +607,9 @@ void SoundGroupEditor::resizeEvent(QResizeEvent* ev)
m_addRemoveButtons.move(0, ev->size().height() - 32);
}
bool SoundGroupEditor::isItemEditEnabled() const
AmuseItemEditFlags SoundGroupEditor::itemEditFlags() const
{
return m_sfxTable->hasFocus() && !m_sfxTable->selectionModel()->selectedRows().isEmpty();
return (m_sfxTable->hasFocus() && !m_sfxTable->selectionModel()->selectedRows().isEmpty()) ? AmuseItemDelete : AmuseItemNone;
}
void SoundGroupEditor::doAdd()
@ -652,21 +645,6 @@ void SoundGroupEditor::sfxDataChanged()
}
}
void SoundGroupEditor::itemCutAction()
{
}
void SoundGroupEditor::itemCopyAction()
{
}
void SoundGroupEditor::itemPasteAction()
{
}
void SoundGroupEditor::itemDeleteAction()
{
m_sfxTable->deleteSelection();

View File

@ -8,7 +8,7 @@
class SFXObjectDelegate : public QStyledItemDelegate
{
Q_OBJECT
Q_OBJECT
public:
explicit SFXObjectDelegate(QObject* parent = Q_NULLPTR);
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
@ -20,7 +20,7 @@ private slots:
class SFXModel : public QAbstractTableModel
{
Q_OBJECT
Q_OBJECT
friend class SoundGroupEditor;
friend class SFXObjectDelegate;
friend class SFXTableView;
@ -69,7 +69,7 @@ public:
class SFXTableView : public QTableView
{
Q_OBJECT
Q_OBJECT
SFXObjectDelegate m_sfxDelegate;
RangedValueFactory<0, 127> m_127Factory;
RangedValueFactory<0, 255> m_255Factory;
@ -104,7 +104,7 @@ public slots:
class SoundGroupEditor : public EditorWidget
{
Q_OBJECT
Q_OBJECT
SFXModel m_sfxs;
SFXTableView* m_sfxTable;
AddRemoveButtons m_addRemoveButtons;
@ -116,15 +116,11 @@ public:
void setEditorEnabled(bool en) {}
void resizeEvent(QResizeEvent* ev);
QTableView* getSFXListView() const { return m_sfxTable; }
bool isItemEditEnabled() const;
AmuseItemEditFlags itemEditFlags() const;
public slots:
void doAdd();
void doSelectionChanged();
void sfxDataChanged();
void itemCutAction();
void itemCopyAction();
void itemPasteAction();
void itemDeleteAction();
};

View File

@ -37,11 +37,7 @@ void FieldSoundMacroStep::targetPressed()
{
ProjectModel::SoundMacroNode* node = nullptr;
if (m_macroField)
{
int val = m_macroField->currentIndex();
if (val != 0)
node = static_cast<ProjectModel::SoundMacroNode*>(m_macroField->collection()->nodeOfIndex(val - 1));
}
node = static_cast<ProjectModel::SoundMacroNode*>(m_macroField->currentNode());
if (!m_macroField || node == getListing()->currentNode())
if (SoundMacroEditor* editor = getEditor())
@ -60,8 +56,8 @@ void FieldSoundMacroStep::updateMacroField()
return;
}
int val = m_macroField->currentIndex();
if (val == 0)
ProjectModel::SoundMacroNode* node = static_cast<ProjectModel::SoundMacroNode*>(m_macroField->currentNode());
if (node == nullptr)
{
m_spinBox.setValue(0);
m_spinBox.setDisabled(true);
@ -69,8 +65,6 @@ void FieldSoundMacroStep::updateMacroField()
}
else
{
ProjectModel::SoundMacroNode* node = static_cast<ProjectModel::SoundMacroNode*>(
m_macroField->collection()->nodeOfIndex(val - 1));
int numCmds = int(node->m_obj->m_cmds.size());
m_spinBox.setMaximum(numCmds - 1);
m_spinBox.setDisabled(false);

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="20"
height="20"
viewBox="0 0 5.2916665 5.2916669"
id="svg2"
version="1.1"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="IconBack.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#393939"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="39.383707"
inkscape:cx="6.3383887"
inkscape:cy="7.7203454"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="3840"
inkscape:window-height="2079"
inkscape:window-x="0"
inkscape:window-y="40"
inkscape:window-maximized="1"
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,-291.70832)">
<path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0,294.35415 2.6458333,-2.64583 v 1.5875 h 2.6458334 v 1.85208 H 2.6458333 l 0,1.85209 z"
id="path841"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="20"
height="20"
viewBox="0 0 5.2916665 5.2916669"
id="svg2"
version="1.1"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="IconBackDisabled.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#393939"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="39.383707"
inkscape:cx="1.7552752"
inkscape:cy="7.7203454"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="3840"
inkscape:window-height="2079"
inkscape:window-x="0"
inkscape:window-y="40"
inkscape:window-maximized="1"
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,-291.70832)">
<path
style="fill:#ffffff;fill-opacity:0.39215687;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0,294.35415 2.6458333,-2.64583 v 1.5875 h 2.6458334 v 1.85208 H 2.6458333 v 1.85209 z"
id="path841"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="20"
height="20"
viewBox="0 0 5.2916665 5.2916669"
id="svg2"
version="1.1"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="IconForward.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#393939"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="39.383707"
inkscape:cx="6.3383887"
inkscape:cy="7.7203454"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="3840"
inkscape:window-height="2079"
inkscape:window-x="0"
inkscape:window-y="40"
inkscape:window-maximized="1"
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,-291.70832)">
<path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 5.2916667,294.35415 -2.6458333,-2.64583 v 1.5875 H 0 v 1.85208 h 2.6458334 v 1.85209 z"
id="path841"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="20"
height="20"
viewBox="0 0 5.2916665 5.2916669"
id="svg2"
version="1.1"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="IconForwardDisabled.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#393939"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="39.383707"
inkscape:cx="6.3383887"
inkscape:cy="7.7203454"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="3840"
inkscape:window-height="2079"
inkscape:window-x="0"
inkscape:window-y="40"
inkscape:window-maximized="1"
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,-291.70832)">
<path
style="fill:#ffffff;fill-opacity:0.39170507;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 5.2916667,294.35415 -2.6458333,-2.64583 v 1.5875 H 0 v 1.85208 h 2.6458334 v 1.85209 z"
id="path841"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

File diff suppressed because it is too large Load Diff

View File

@ -36,6 +36,10 @@
<file>IconFX.svg</file>
<file>IconB.svg</file>
<file>IconA.svg</file>
<file>IconBack.svg</file>
<file>IconForward.svg</file>
<file>IconBackDisabled.svg</file>
<file>IconForwardDisabled.svg</file>
</qresource>
<qresource prefix="/bg">
<file>FaceGrey.svg</file>

View File

@ -4,15 +4,18 @@
#include "AudioGroupPool.hpp"
#include "AudioGroupProject.hpp"
#include "AudioGroupSampleDirectory.hpp"
#include <unordered_set>
namespace amuse
{
class AudioGroupData;
class ProjectDatabase;
/** Runtime audio group index container */
class AudioGroup
{
friend class AudioGroupSampleDirectory;
protected:
AudioGroupProject m_proj;
AudioGroupPool m_pool;
AudioGroupSampleDirectory m_sdir;
@ -20,16 +23,17 @@ class AudioGroup
SystemString m_groupPath; /* Typically only set by editor */
bool m_valid;
SystemString getSampleBasePath(SampleId sfxId) const;
public:
SystemString getSampleBasePath(SampleId sfxId) const;
operator bool() const { return m_valid; }
AudioGroup() = default;
explicit AudioGroup(const AudioGroupData& data) { assign(data); }
explicit AudioGroup(SystemStringView groupPath) { assign(groupPath); }
explicit AudioGroup(const AudioGroup& data, SystemStringView groupPath) { assign(data, groupPath); }
void assign(const AudioGroupData& data);
void assign(SystemStringView groupPath);
void assign(const AudioGroup& data, SystemStringView groupPath);
void setGroupPath(SystemStringView groupPath) { m_groupPath = groupPath; }
const SampleEntry* getSample(SampleId sfxId) const;
@ -58,6 +62,9 @@ class AudioGroupDatabase final : public AudioGroup
NameDB m_keymapDb;
NameDB m_layersDb;
void _recursiveRenameMacro(SoundMacroId id, std::string_view str, int& macroIdx,
std::unordered_set<SoundMacroId>& renamedIds);
public:
AudioGroupDatabase() = default;
explicit AudioGroupDatabase(const AudioGroupData& data)
@ -70,6 +77,11 @@ public:
setIdDatabases();
assign(groupPath);
}
explicit AudioGroupDatabase(const AudioGroupDatabase& data, SystemStringView groupPath)
{
setIdDatabases();
assign(data, groupPath);
}
void setIdDatabases() const
{
@ -79,6 +91,13 @@ public:
KeymapId::CurNameDB = const_cast<NameDB*>(&m_keymapDb);
LayersId::CurNameDB = const_cast<NameDB*>(&m_layersDb);
}
void renameSample(SampleId id, std::string_view str);
void deleteSample(SampleId id);
void copySampleInto(const SystemString& basePath, const SystemString& newBasePath);
void importCHeader(std::string_view header);
std::string exportCHeader(std::string_view projectName, std::string_view groupName) const;
};
class ProjectDatabase

View File

@ -1157,6 +1157,9 @@ struct SoundMacro
std::swap(m_cmds[a], m_cmds[b]);
}
void buildFromPrototype(const SoundMacro& other);
void toYAML(athena::io::YAMLDocWriter& w) const;
void fromYAML(athena::io::YAMLDocReader& r, size_t cmdCount);
};
template <typename T>

View File

@ -125,6 +125,9 @@ struct SongGroupIndex : AudioGroupIndex
reverb(setup.reverb), chorus(setup.chorus) {}
};
std::unordered_map<SongId, std::array<MIDISetup, 16>> m_midiSetups;
void toYAML(athena::io::YAMLDocWriter& w) const;
void fromYAML(athena::io::YAMLDocReader& r);
};
/** Root index of SFXGroup */
@ -177,6 +180,12 @@ struct SFXGroupIndex : AudioGroupIndex
}
};
std::unordered_map<SFXId, SFXEntry> m_sfxEntries;
SFXGroupIndex() = default;
SFXGroupIndex(const SFXGroupIndex& other);
void toYAML(athena::io::YAMLDocWriter& w) const;
void fromYAML(athena::io::YAMLDocReader& r);
};
/** Collection of SongGroup and SFXGroup indexes */
@ -196,6 +205,7 @@ public:
AudioGroupProject() = default;
static AudioGroupProject CreateAudioGroupProject(const AudioGroupData& data);
static AudioGroupProject CreateAudioGroupProject(SystemStringView groupPath);
static AudioGroupProject CreateAudioGroupProject(const AudioGroupProject& oldProj);
static void BootstrapObjectIDs(const AudioGroupData& data);
const SongGroupIndex* getSongGroupIndex(int groupId) const;

View File

@ -349,6 +349,7 @@ public:
static AudioGroupSampleDirectory CreateAudioGroupSampleDirectory(SystemStringView groupPath);
const std::unordered_map<SampleId, ObjToken<Entry>>& sampleEntries() const { return m_entries; }
std::unordered_map<SampleId, ObjToken<Entry>>& sampleEntries() { return m_entries; }
void extractWAV(SampleId id, amuse::SystemStringView destDir, const unsigned char* samp) const;
void extractAllWAV(amuse::SystemStringView destDir, const unsigned char* samp) const;

View File

@ -16,6 +16,7 @@
#include <strings.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#else
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
@ -176,6 +177,10 @@ public:
ObjTokenBase& operator=(ObjTokenBase&& other)
{ if (m_obj) m_obj->decrement(); m_obj = other.m_obj; other.m_obj = nullptr; return *this; }
~ObjTokenBase() { if (m_obj) m_obj->decrement(); }
bool operator==(const ObjTokenBase& other) const { return m_obj == other.m_obj; }
bool operator!=(const ObjTokenBase& other) const { return m_obj != other.m_obj; }
bool operator<(const ObjTokenBase& other) const { return m_obj < other.m_obj; }
bool operator>(const ObjTokenBase& other) const { return m_obj > other.m_obj; }
operator bool() const { return m_obj != nullptr; }
void reset() { if (m_obj) m_obj->decrement(); m_obj = nullptr; }
};
@ -276,6 +281,16 @@ static inline int Mkdir(const char* path, mode_t mode) { return mkdir(path, mode
static inline int Stat(const char* path, Sstat* statout) { return stat(path, statout); }
#endif
static inline int Rename(const SystemChar* oldpath, const SystemChar* newpath)
{
#if _WIN32
//return _wrename(oldpath, newpath);
return MoveFileExW(oldpath, newpath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) == 0;
#else
return rename(oldpath, newpath);
#endif
}
#if _WIN32
static inline int CompareCaseInsensitive(const char* a, const char* b) { return _stricmp(a, b); }
#endif
@ -415,6 +430,8 @@ static inline void Unlink(const SystemChar* file)
#endif
}
bool Copy(const SystemChar* from, const SystemChar* to);
#undef bswap16
#undef bswap32
#undef bswap64
@ -618,7 +635,7 @@ DECL_ID_HASH(GroupId)
template<class T>
struct hash<amuse::ObjToken<T>>
{
size_t operator()(const amuse::ObjToken<T>& val) const noexcept { return reinterpret_cast<size_t>(val.get()); }
size_t operator()(const amuse::ObjToken<T>& val) const noexcept { return hash<T*>()(val.get()); }
};
}

View File

@ -28,10 +28,10 @@ protected:
}
Engine& m_engine;
const AudioGroup& m_audioGroup;
int m_groupId;
GroupId m_groupId;
ObjectId m_objectId; /* if applicable */
public:
Entity(Engine& engine, const AudioGroup& group, int groupId, ObjectId oid = ObjectId())
Entity(Engine& engine, const AudioGroup& group, GroupId groupId, ObjectId oid = ObjectId())
: m_engine(engine), m_audioGroup(group), m_groupId(groupId), m_objectId(oid)
{
}
@ -43,7 +43,7 @@ public:
Engine& getEngine() { return m_engine; }
const AudioGroup& getAudioGroup() const { return m_audioGroup; }
int getGroupId() const { return m_groupId; }
GroupId getGroupId() const { return m_groupId; }
ObjectId getObjectId() const { return m_objectId; }
bool isDestroyed() const { return m_destroyed; }
};

View File

@ -92,9 +92,9 @@ class Sequencer : public Entity
public:
~Sequencer();
Sequencer(Engine& engine, const AudioGroup& group, int groupId, const SongGroupIndex* songGroup, int setupId,
Sequencer(Engine& engine, const AudioGroup& group, GroupId groupId, const SongGroupIndex* songGroup, SongId setupId,
ObjToken<Studio> studio);
Sequencer(Engine& engine, const AudioGroup& group, int groupId, const SFXGroupIndex* sfxGroup,
Sequencer(Engine& engine, const AudioGroup& group, GroupId groupId, const SFXGroupIndex* sfxGroup,
ObjToken<Studio> studio);
/** Advance current song data (if any) */

View File

@ -84,7 +84,7 @@ class SongState
int32_t m_modVal = 0; /**< Accumulated value of mod */
uint32_t m_nextModTick = 0; /**< Upcoming position of mod wheel change */
int32_t m_nextModDelta = 0; /**< Upcoming delta value of mod */
std::array<int, 128> m_remNoteLengths; /**< Remaining ticks per note */
std::array<int, 128> m_remNoteLengths = {}; /**< Remaining ticks per note */
int32_t m_eventWaitCountdown = 0; /**< Current wait in ticks */
int32_t m_lastN64EventTick =

View File

@ -1,5 +1,9 @@
#include "amuse/AudioGroup.hpp"
#include "amuse/AudioGroupData.hpp"
#include <regex>
#include <athena/FileReader.hpp>
using namespace std::literals;
namespace amuse
{
@ -20,6 +24,15 @@ void AudioGroup::assign(SystemStringView groupPath)
m_proj = AudioGroupProject::CreateAudioGroupProject(groupPath);
m_samp = nullptr;
}
void AudioGroup::assign(const AudioGroup& data, SystemStringView groupPath)
{
/* Reverse order when loading intermediates */
m_groupPath = groupPath;
m_sdir = AudioGroupSampleDirectory::CreateAudioGroupSampleDirectory(groupPath);
m_pool = AudioGroupPool::CreateAudioGroupPool(groupPath);
m_proj = AudioGroupProject::CreateAudioGroupProject(data.getProj());
m_samp = nullptr;
}
const SampleEntry* AudioGroup::getSample(SampleId sfxId) const
{
@ -93,4 +106,249 @@ void AudioGroup::makeCompressedVersion(SampleId sfxId, const SampleEntry* sample
m_sdir._extractCompressed(sfxId, *sample->m_data, m_groupPath, sample->m_data->m_looseData.get(), true);
}
}
void AudioGroupDatabase::renameSample(SampleId id, std::string_view str)
{
SystemString oldBasePath = getSampleBasePath(id);
SampleId::CurNameDB->rename(id, str);
SystemString newBasePath = getSampleBasePath(id);
Rename((oldBasePath + _S(".wav")).c_str(), (newBasePath + _S(".wav")).c_str());
Rename((oldBasePath + _S(".dsp")).c_str(), (newBasePath + _S(".dsp")).c_str());
Rename((oldBasePath + _S(".vadpcm")).c_str(), (newBasePath + _S(".vadpcm")).c_str());
}
void AudioGroupDatabase::deleteSample(SampleId id)
{
SystemString basePath = getSampleBasePath(id);
Unlink((basePath + _S(".wav")).c_str());
Unlink((basePath + _S(".dsp")).c_str());
Unlink((basePath + _S(".vadpcm")).c_str());
}
void AudioGroupDatabase::copySampleInto(const SystemString& basePath, const SystemString& newBasePath)
{
Copy((basePath + _S(".wav")).c_str(), (newBasePath + _S(".wav")).c_str());
Copy((basePath + _S(".dsp")).c_str(), (newBasePath + _S(".dsp")).c_str());
Copy((basePath + _S(".vadpcm")).c_str(), (newBasePath + _S(".vadpcm")).c_str());
}
void AudioGroupDatabase::_recursiveRenameMacro(SoundMacroId id, std::string_view str, int& macroIdx,
std::unordered_set<SoundMacroId>& renamedIds)
{
if (renamedIds.find(id) != renamedIds.cend())
return;
if (const SoundMacro* macro = getPool().soundMacro(id))
{
if (!strncmp(SoundMacroId::CurNameDB->resolveNameFromId(id).data(), "macro", 5))
{
std::string macroName("macro"sv);
if (macroIdx)
{
char num[16];
snprintf(num, 16, "%d", macroIdx);
macroName += num;
}
macroName += '_';
macroName += str;
++macroIdx;
SoundMacroId::CurNameDB->rename(id, macroName);
renamedIds.insert(id);
int sampleIdx = 0;
for (const auto& cmd : macro->m_cmds)
{
switch (cmd->Isa())
{
case SoundMacro::CmdOp::StartSample:
{
SoundMacro::CmdStartSample* ss = static_cast<SoundMacro::CmdStartSample*>(cmd.get());
if (!strncmp(SampleId::CurNameDB->resolveNameFromId(ss->sample.id).data(), "sample", 6))
{
std::string sampleName("sample"sv);
if (sampleIdx)
{
char num[16];
snprintf(num, 16, "%d", sampleIdx);
sampleName += num;
}
sampleName += '_';
sampleName += macroName;
++sampleIdx;
renameSample(ss->sample.id, sampleName);
}
}
case SoundMacro::CmdOp::SplitKey:
_recursiveRenameMacro(static_cast<SoundMacro::CmdSplitKey*>(cmd.get())->macro, str, macroIdx, renamedIds);
break;
case SoundMacro::CmdOp::SplitVel:
_recursiveRenameMacro(static_cast<SoundMacro::CmdSplitVel*>(cmd.get())->macro, str, macroIdx, renamedIds);
break;
case SoundMacro::CmdOp::Goto:
_recursiveRenameMacro(static_cast<SoundMacro::CmdGoto*>(cmd.get())->macro, str, macroIdx, renamedIds);
break;
case SoundMacro::CmdOp::PlayMacro:
_recursiveRenameMacro(static_cast<SoundMacro::CmdPlayMacro*>(cmd.get())->macro, str, macroIdx, renamedIds);
break;
case SoundMacro::CmdOp::SplitMod:
_recursiveRenameMacro(static_cast<SoundMacro::CmdSplitMod*>(cmd.get())->macro, str, macroIdx, renamedIds);
break;
case SoundMacro::CmdOp::SplitRnd:
_recursiveRenameMacro(static_cast<SoundMacro::CmdSplitRnd*>(cmd.get())->macro, str, macroIdx, renamedIds);
break;
case SoundMacro::CmdOp::GoSub:
_recursiveRenameMacro(static_cast<SoundMacro::CmdGoSub*>(cmd.get())->macro, str, macroIdx, renamedIds);
break;
case SoundMacro::CmdOp::TrapEvent:
_recursiveRenameMacro(static_cast<SoundMacro::CmdTrapEvent*>(cmd.get())->macro, str, macroIdx, renamedIds);
break;
case SoundMacro::CmdOp::SendMessage:
_recursiveRenameMacro(static_cast<SoundMacro::CmdSendMessage*>(cmd.get())->macro, str, macroIdx, renamedIds);
break;
default:
break;
}
}
}
}
}
static const std::regex DefineGRPEntry(R"(#define\s+GRP(\S+)\s+(\S+))",
std::regex::ECMAScript|std::regex::optimize);
static const std::regex DefineSNGEntry(R"(#define\s+SNG(\S+)\s+(\S+))",
std::regex::ECMAScript|std::regex::optimize);
static const std::regex DefineSFXEntry(R"(#define\s+SFX(\S+)\s+(\S+))",
std::regex::ECMAScript|std::regex::optimize);
void AudioGroupDatabase::importCHeader(std::string_view header)
{
setIdDatabases();
std::match_results<std::string_view::const_iterator> dirMatch;
auto begin = header.cbegin();
auto end = header.cend();
while (std::regex_search(begin, end, dirMatch, DefineGRPEntry))
{
std::string key = dirMatch[1].str();
std::string value = dirMatch[2].str();
char* endPtr;
amuse::ObjectId id;
id.id = strtoul(value.c_str(), &endPtr, 0);
if (endPtr == value.c_str())
continue;
GroupId::CurNameDB->rename(id, key);
begin = dirMatch.suffix().first;
}
begin = header.cbegin();
end = header.cend();
while (std::regex_search(begin, end, dirMatch, DefineSNGEntry))
{
std::string key = dirMatch[1].str();
std::string value = dirMatch[2].str();
char* endPtr;
amuse::ObjectId id;
id.id = strtoul(value.c_str(), &endPtr, 0);
if (endPtr == value.c_str())
continue;
SongId::CurNameDB->rename(id, key);
begin = dirMatch.suffix().first;
}
begin = header.cbegin();
end = header.cend();
std::unordered_set<SoundMacroId> renamedMacroIDs;
while (std::regex_search(begin, end, dirMatch, DefineSFXEntry))
{
std::string key = dirMatch[1].str();
std::string value = dirMatch[2].str();
char* endPtr;
amuse::ObjectId id;
id.id = strtoul(value.c_str(), &endPtr, 0);
if (endPtr == value.c_str())
continue;
SFXId::CurNameDB->rename(id, key);
int macroIdx = 0;
for (auto& sfxGrp : getProj().sfxGroups())
{
for (auto& sfx : sfxGrp.second->m_sfxEntries)
{
if (sfx.first == id)
{
ObjectId sfxObjId = sfx.second.objId;
if (sfxObjId == ObjectId() || sfxObjId.id & 0xc000)
continue;
_recursiveRenameMacro(sfxObjId, key, macroIdx, renamedMacroIDs);
}
}
}
begin = dirMatch.suffix().first;
}
}
static void WriteDefineLine(std::string& ret, std::string_view typeStr, std::string_view name, ObjectId id)
{
ret += "#define "sv;
ret += typeStr;
ret += name;
ret += ' ';
char idStr[16];
snprintf(idStr, 16, "%d", id.id);
ret += idStr;
ret += '\n';
}
std::string AudioGroupDatabase::exportCHeader(std::string_view projectName, std::string_view groupName) const
{
setIdDatabases();
std::string ret;
ret += "/* Auto-generated Amuse Defines\n"
" *\n"
" * Project: "sv;
ret += projectName;
ret += "\n"
" * Subproject: "sv;
ret += groupName;
ret += "\n"
" * Date: "sv;
time_t curTime = time(nullptr);
struct tm curTm;
localtime_r(&curTime, &curTm);
char curTmStr[26];
asctime_r(&curTm, curTmStr);
ret += curTmStr;
ret += "\n"
" */\n\n\n"sv;
for (const auto& sg : SortUnorderedMap(getProj().songGroups()))
{
auto name = amuse::GroupId::CurNameDB->resolveNameFromId(sg.first);
WriteDefineLine(ret, "GRP"sv, name, sg.first);
}
for (const auto& sg : SortUnorderedMap(getProj().sfxGroups()))
{
auto name = amuse::GroupId::CurNameDB->resolveNameFromId(sg.first);
WriteDefineLine(ret, "GRP"sv, name, sg.first);
}
ret += "\n\n"sv;
std::unordered_set<amuse::SongId> songIds;
for (const auto& sg : getProj().songGroups())
for (const auto& song : sg.second->m_midiSetups)
songIds.insert(song.first);
for (amuse::SongId id : SortUnorderedSet(songIds))
{
auto name = amuse::SongId::CurNameDB->resolveNameFromId(id);
WriteDefineLine(ret, "SNG"sv, name, id);
}
ret += "\n\n"sv;
for (const auto& sg : SortUnorderedMap(getProj().sfxGroups()))
{
for (const auto& sfx : SortUnorderedMap(sg.second.get()->m_sfxEntries))
{
auto name = amuse::SFXId::CurNameDB->resolveNameFromId(sfx.first);
WriteDefineLine(ret, "SFX"sv, name, sfx.first.id);
}
}
return ret;
}
}

View File

@ -279,12 +279,7 @@ AudioGroupPool AudioGroupPool::CreateAudioGroupPool(SystemStringView groupPath)
smOut = MakeObj<SoundMacro>();
size_t cmdCount;
if (auto __v = r.enterSubVector(sm.first.c_str(), cmdCount))
{
smOut->m_cmds.reserve(cmdCount);
for (int c = 0; c < cmdCount; ++c)
if (auto __r2 = r.enterSubRecord(nullptr))
smOut->m_cmds.push_back(SoundMacro::CmdDo<MakeCmdOp, std::unique_ptr<SoundMacro::ICmd>>(r));
}
smOut->fromYAML(r, cmdCount);
}
}
@ -416,6 +411,27 @@ void SoundMacro::buildFromPrototype(const SoundMacro& other)
m_cmds.push_back(CmdDo<MakeCopyCmdOp, std::unique_ptr<SoundMacro::ICmd>>(*cmd));
}
void SoundMacro::toYAML(athena::io::YAMLDocWriter& w) const
{
for (const auto& c : m_cmds)
{
if (auto __r2 = w.enterSubRecord(nullptr))
{
w.setStyle(athena::io::YAMLNodeStyle::Flow);
w.writeString("cmdOp", SoundMacro::CmdOpToStr(c->Isa()));
c->write(w);
}
}
}
void SoundMacro::fromYAML(athena::io::YAMLDocReader& r, size_t cmdCount)
{
m_cmds.reserve(cmdCount);
for (int c = 0; c < cmdCount; ++c)
if (auto __r2 = r.enterSubRecord(nullptr))
m_cmds.push_back(SoundMacro::CmdDo<MakeCmdOp, std::unique_ptr<SoundMacro::ICmd>>(r));
}
const SoundMacro* AudioGroupPool::soundMacro(ObjectId id) const
{
auto search = m_soundMacros.find(id);
@ -1002,15 +1018,7 @@ bool AudioGroupPool::toYAML(SystemStringView groupPath) const
{
if (auto __v = w.enterSubVector(SoundMacroId::CurNameDB->resolveNameFromId(p.first).data()))
{
for (const auto& c : p.second.get()->m_cmds)
{
if (auto __r2 = w.enterSubRecord(nullptr))
{
w.setStyle(athena::io::YAMLNodeStyle::Flow);
w.writeString("cmdOp", SoundMacro::CmdOpToStr(c->Isa()));
c->write(w);
}
}
p.second.get()->toYAML(w);
}
}
}

View File

@ -353,6 +353,59 @@ std::string ParseStringSlashId(const std::string& str, uint16_t& idOut)
return {str.begin(), str.begin() + slashPos};
}
void SongGroupIndex::fromYAML(athena::io::YAMLDocReader& r)
{
if (auto __v2 = r.enterSubRecord("normPages"))
{
m_normPages.reserve(r.getCurNode()->m_mapChildren.size());
for (const auto& pg : r.getCurNode()->m_mapChildren)
if (auto __r2 = r.enterSubRecord(pg.first.c_str()))
m_normPages[strtoul(pg.first.c_str(), nullptr, 0)].read(r);
}
if (auto __v2 = r.enterSubRecord("drumPages"))
{
m_drumPages.reserve(r.getCurNode()->m_mapChildren.size());
for (const auto& pg : r.getCurNode()->m_mapChildren)
if (auto __r2 = r.enterSubRecord(pg.first.c_str()))
m_drumPages[strtoul(pg.first.c_str(), nullptr, 0)].read(r);
}
if (auto __v2 = r.enterSubRecord("songs"))
{
m_midiSetups.reserve(r.getCurNode()->m_mapChildren.size());
for (const auto& song : r.getCurNode()->m_mapChildren)
{
size_t chanCount;
if (auto __v3 = r.enterSubVector(song.first.c_str(), chanCount))
{
uint16_t songId;
std::string songName = ParseStringSlashId(song.first, songId);
if (songName.empty() || songId == 0xffff)
continue;
SongId::CurNameDB->registerPair(songName, songId);
std::array<SongGroupIndex::MIDISetup, 16>& setup = m_midiSetups[songId];
for (int i = 0; i < 16 && i < chanCount; ++i)
if (auto __r2 = r.enterSubRecord(nullptr))
setup[i].read(r);
}
}
}
}
void SFXGroupIndex::fromYAML(athena::io::YAMLDocReader& r)
{
for (const auto& sfx : r.getCurNode()->m_mapChildren)
if (auto __r2 = r.enterSubRecord(sfx.first.c_str()))
{
uint16_t sfxId;
std::string sfxName = ParseStringSlashId(sfx.first, sfxId);
if (sfxName.empty() || sfxId == 0xffff)
continue;
SFXId::CurNameDB->registerPair(sfxName, sfxId);
m_sfxEntries[sfxId].read(r);
}
}
AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView groupPath)
{
AudioGroupProject ret;
@ -380,41 +433,7 @@ AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView gr
auto& idx = ret.m_songGroups[groupId];
idx = MakeObj<SongGroupIndex>();
if (auto __v2 = r.enterSubRecord("normPages"))
{
idx->m_normPages.reserve(r.getCurNode()->m_mapChildren.size());
for (const auto& pg : r.getCurNode()->m_mapChildren)
if (auto __r2 = r.enterSubRecord(pg.first.c_str()))
idx->m_normPages[strtoul(pg.first.c_str(), nullptr, 0)].read(r);
}
if (auto __v2 = r.enterSubRecord("drumPages"))
{
idx->m_drumPages.reserve(r.getCurNode()->m_mapChildren.size());
for (const auto& pg : r.getCurNode()->m_mapChildren)
if (auto __r2 = r.enterSubRecord(pg.first.c_str()))
idx->m_drumPages[strtoul(pg.first.c_str(), nullptr, 0)].read(r);
}
if (auto __v2 = r.enterSubRecord("songs"))
{
idx->m_midiSetups.reserve(r.getCurNode()->m_mapChildren.size());
for (const auto& song : r.getCurNode()->m_mapChildren)
{
size_t chanCount;
if (auto __v3 = r.enterSubVector(song.first.c_str(), chanCount))
{
uint16_t songId;
std::string songName = ParseStringSlashId(song.first, songId);
if (songName.empty() || songId == 0xffff)
continue;
SongId::CurNameDB->registerPair(songName, songId);
std::array<SongGroupIndex::MIDISetup, 16>& setup = idx->m_midiSetups[songId];
for (int i = 0; i < 16 && i < chanCount; ++i)
if (auto __r2 = r.enterSubRecord(nullptr))
setup[i].read(r);
}
}
}
idx->fromYAML(r);
}
}
}
@ -434,16 +453,7 @@ AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView gr
auto& idx = ret.m_sfxGroups[groupId];
idx = MakeObj<SFXGroupIndex>();
for (const auto& sfx : r.getCurNode()->m_mapChildren)
if (auto __r2 = r.enterSubRecord(sfx.first.c_str()))
{
uint16_t sfxId;
std::string sfxName = ParseStringSlashId(sfx.first, sfxId);
if (sfxName.empty() || sfxId == 0xffff)
continue;
SFXId::CurNameDB->registerPair(sfxName, sfxId);
idx->m_sfxEntries[sfxId].read(r);
}
idx->fromYAML(r);
}
}
}
@ -453,6 +463,42 @@ AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView gr
return ret;
}
static ObjectId RegisterDedupedName(ObjectId origId, amuse::NameDB* db, NameDB::Type tp)
{
std::string dupeName = std::string(db->resolveNameFromId(origId)) + "-copy";
std::string useName = dupeName;
int dupeIdx = 1;
while (db->m_stringToId.find(useName) != db->m_stringToId.cend())
{
char num[16];
snprintf(num, 16, "%d", dupeIdx++);
useName = dupeName + num;
}
ObjectId ret = db->generateId(tp);
db->registerPair(useName, ret);
return ret;
}
SFXGroupIndex::SFXGroupIndex(const SFXGroupIndex& other)
{
for (const auto& sfx : other.m_sfxEntries)
m_sfxEntries[RegisterDedupedName(sfx.first, SFXId::CurNameDB, NameDB::Type::SFX)] = sfx.second;
}
AudioGroupProject AudioGroupProject::CreateAudioGroupProject(const AudioGroupProject& oldProj)
{
AudioGroupProject ret;
for (const auto& grp : oldProj.songGroups())
ret.m_songGroups[RegisterDedupedName(grp.first, GroupId::CurNameDB, NameDB::Type::Group)] =
MakeObj<SongGroupIndex>(*grp.second);
for (const auto& grp : oldProj.sfxGroups())
ret.m_sfxGroups[RegisterDedupedName(grp.first, GroupId::CurNameDB, NameDB::Type::Group)] =
MakeObj<SFXGroupIndex>(*grp.second);
return ret;
}
void AudioGroupProject::BootstrapObjectIDs(athena::io::IStreamReader& r, GCNDataTag)
{
while (!AtEnd32(r))
@ -638,6 +684,76 @@ const SFXGroupIndex* AudioGroupProject::getSFXGroupIndex(int groupId) const
return nullptr;
}
void SongGroupIndex::toYAML(athena::io::YAMLDocWriter& w) const
{
if (!m_normPages.empty())
{
if (auto __v2 = w.enterSubRecord("normPages"))
{
for (const auto& pg : SortUnorderedMap(m_normPages))
{
char name[16];
snprintf(name, 16, "%d", pg.first);
if (auto __r2 = w.enterSubRecord(name))
{
w.setStyle(athena::io::YAMLNodeStyle::Flow);
pg.second.get().write(w);
}
}
}
}
if (!m_drumPages.empty())
{
if (auto __v2 = w.enterSubRecord("drumPages"))
{
for (const auto& pg : SortUnorderedMap(m_drumPages))
{
char name[16];
snprintf(name, 16, "%d", pg.first);
if (auto __r2 = w.enterSubRecord(name))
{
w.setStyle(athena::io::YAMLNodeStyle::Flow);
pg.second.get().write(w);
}
}
}
}
if (!m_midiSetups.empty())
{
if (auto __v2 = w.enterSubRecord("songs"))
{
for (const auto& song : SortUnorderedMap(m_midiSetups))
{
char songString[64];
snprintf(songString, 64, "%s/0x%04X",
SongId::CurNameDB->resolveNameFromId(song.first).data(), int(song.first.id));
if (auto __v3 = w.enterSubVector(songString))
for (int i = 0; i < 16; ++i)
if (auto __r2 = w.enterSubRecord(nullptr))
{
w.setStyle(athena::io::YAMLNodeStyle::Flow);
song.second.get()[i].write(w);
}
}
}
}
}
void SFXGroupIndex::toYAML(athena::io::YAMLDocWriter& w) const
{
for (const auto& sfx : SortUnorderedMap(m_sfxEntries))
{
char sfxString[64];
snprintf(sfxString, 64, "%s/0x%04X",
SFXId::CurNameDB->resolveNameFromId(sfx.first).data(), int(sfx.first.id));
if (auto __r2 = w.enterSubRecord(sfxString))
{
w.setStyle(athena::io::YAMLNodeStyle::Flow);
sfx.second.get().write(w);
}
}
}
bool AudioGroupProject::toYAML(SystemStringView groupPath) const
{
athena::io::YAMLDocWriter w("amuse::Project");
@ -652,56 +768,7 @@ bool AudioGroupProject::toYAML(SystemStringView groupPath) const
snprintf(groupString, 64, "%s/0x%04X", GroupId::CurNameDB->resolveNameFromId(p.first).data(), int(p.first.id));
if (auto __r = w.enterSubRecord(groupString))
{
if (!p.second.get()->m_normPages.empty())
{
if (auto __v2 = w.enterSubRecord("normPages"))
{
for (const auto& pg : SortUnorderedMap(p.second.get()->m_normPages))
{
char name[16];
snprintf(name, 16, "%d", pg.first);
if (auto __r2 = w.enterSubRecord(name))
{
w.setStyle(athena::io::YAMLNodeStyle::Flow);
pg.second.get().write(w);
}
}
}
}
if (!p.second.get()->m_drumPages.empty())
{
if (auto __v2 = w.enterSubRecord("drumPages"))
{
for (const auto& pg : SortUnorderedMap(p.second.get()->m_drumPages))
{
char name[16];
snprintf(name, 16, "%d", pg.first);
if (auto __r2 = w.enterSubRecord(name))
{
w.setStyle(athena::io::YAMLNodeStyle::Flow);
pg.second.get().write(w);
}
}
}
}
if (!p.second.get()->m_midiSetups.empty())
{
if (auto __v2 = w.enterSubRecord("songs"))
{
for (const auto& song : SortUnorderedMap(p.second.get()->m_midiSetups))
{
char songString[64];
snprintf(songString, 64, "%s/0x%04X", SongId::CurNameDB->resolveNameFromId(song.first).data(), int(song.first.id));
if (auto __v3 = w.enterSubVector(songString))
for (int i = 0; i < 16; ++i)
if (auto __r2 = w.enterSubRecord(nullptr))
{
w.setStyle(athena::io::YAMLNodeStyle::Flow);
song.second.get()[i].write(w);
}
}
}
}
p.second.get()->toYAML(w);
}
}
}
@ -717,16 +784,7 @@ bool AudioGroupProject::toYAML(SystemStringView groupPath) const
snprintf(groupString, 64, "%s/0x%04X", GroupId::CurNameDB->resolveNameFromId(p.first).data(), int(p.first.id));
if (auto __r = w.enterSubRecord(groupString))
{
for (const auto& sfx : SortUnorderedMap(p.second.get()->m_sfxEntries))
{
char sfxString[64];
snprintf(sfxString, 64, "%s/0x%04X", SFXId::CurNameDB->resolveNameFromId(sfx.first).data(), int(sfx.first.id));
if (auto __r2 = w.enterSubRecord(sfxString))
{
w.setStyle(athena::io::YAMLNodeStyle::Flow);
sfx.second.get().write(w);
}
}
p.second.get()->toYAML(w);
}
}
}

View File

@ -7,6 +7,35 @@ namespace amuse
{
static logvisor::Module Log("amuse");
bool Copy(const SystemChar* from, const SystemChar* to)
{
#if _WIN32
return CopyFileW(from, to, FALSE) != 0;
#else
FILE* fi = fopen(from, "rb");
if (!fi)
return false;
FILE* fo = fopen(to, "wb");
if (!fo)
{
fclose(fi);
return false;
}
std::unique_ptr<uint8_t[]> buf(new uint8_t[65536]);
size_t readSz = 0;
while ((readSz = fread(buf.get(), 1, 65536, fi)))
fwrite(buf.get(), 1, readSz, fo);
fclose(fi);
fclose(fo);
struct stat theStat;
if (::stat(from, &theStat))
return true;
struct timespec times[] = { theStat.st_atim, theStat.st_mtim };
utimensat(AT_FDCWD, to, times, 0);
return true;
#endif
}
#define DEFINE_ID_TYPE(type, typeName) \
thread_local NameDB* type::CurNameDB = nullptr; \
template<> template<> \

View File

@ -32,6 +32,8 @@ void Sequencer::ChannelState::_bringOutYourDead()
}
++it;
}
std::unordered_set<ObjToken<Voice>> newSet = m_keyoffVoxs;
m_keyoffVoxs = newSet;
}
void Sequencer::_bringOutYourDead()
@ -57,8 +59,8 @@ Sequencer::~Sequencer()
m_studio.reset();
}
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId, const SongGroupIndex* songGroup, int setupId,
ObjToken<Studio> studio)
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, GroupId groupId, const SongGroupIndex* songGroup,
SongId setupId, ObjToken<Studio> studio)
: Entity(engine, group, groupId), m_songGroup(songGroup), m_studio(studio)
{
auto it = m_songGroup->m_midiSetups.find(setupId);
@ -66,7 +68,7 @@ Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId, const
m_midiSetup = it->second.data();
}
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId, const SFXGroupIndex* sfxGroup,
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, GroupId groupId, const SFXGroupIndex* sfxGroup,
ObjToken<Studio> studio)
: Entity(engine, group, groupId), m_sfxGroup(sfxGroup), m_studio(studio)
{
@ -238,7 +240,7 @@ ObjToken<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
m_lastVoice.reset();
keySearch->second->keyOff();
keySearch->second->setPedal(false);
m_keyoffVoxs.emplace(std::move(keySearch->second));
m_keyoffVoxs.emplace(keySearch->second);
m_chanVoxs.erase(keySearch);
}
@ -310,7 +312,7 @@ void Sequencer::ChannelState::keyOff(uint8_t note, uint8_t velocity)
if ((m_lastVoice && m_lastVoice->isDestroyed()) || keySearch->second == m_lastVoice)
m_lastVoice.reset();
keySearch->second->keyOff();
m_keyoffVoxs.emplace(std::move(keySearch->second));
m_keyoffVoxs.emplace(keySearch->second);
m_chanVoxs.erase(keySearch);
}
@ -455,7 +457,7 @@ void Sequencer::ChannelState::allOff()
if (it->second == m_lastVoice)
m_lastVoice.reset();
it->second->keyOff();
m_keyoffVoxs.emplace(std::move(it->second));
m_keyoffVoxs.emplace(it->second);
it = m_chanVoxs.erase(it);
}
}
@ -517,7 +519,7 @@ void Sequencer::ChannelState::killKeygroup(uint8_t kg, bool now)
continue;
}
vox->keyOff();
m_keyoffVoxs.emplace(std::move(it->second));
m_keyoffVoxs.emplace(it->second);
it = m_chanVoxs.erase(it);
continue;
}

View File

@ -62,15 +62,15 @@ struct Event
uint8_t noteOrCtrl;
uint8_t velOrVal;
uint8_t program;
uint16_t length;
int length;
int pitchBend;
Event(NoteEvent, uint8_t chan, uint8_t note, uint8_t vel, uint16_t len)
Event(NoteEvent, uint8_t chan, uint8_t note, uint8_t vel, int len)
: m_type(Type::Note), channel(chan), noteOrCtrl(note), velOrVal(vel), length(len)
{
}
Event(CtrlEvent, uint8_t chan, uint8_t note, uint8_t vel, uint16_t len)
Event(CtrlEvent, uint8_t chan, uint8_t note, uint8_t vel, int len)
: m_type(Type::Control), channel(chan), noteOrCtrl(note), velOrVal(vel), length(len)
{
}
@ -83,16 +83,17 @@ struct Event
class MIDIDecoder
{
int m_tick = 0;
std::vector<std::pair<int, std::multimap<int, Event>>> m_results;
std::vector<std::pair<int, std::multimap<int, Event>>> m_results[16];
std::multimap<int, int> m_tempos;
std::array<std::multimap<int, Event>::iterator, 128> m_notes;
std::array<std::multimap<int, Event>::iterator, 128> m_notes[16];
void _addProgramChange(int prog)
void _addProgramChange(int chan, int prog)
{
m_results.emplace_back();
m_results.back().first = prog;
auto& results = m_results[chan];
results.emplace_back();
results.back().first = prog;
for (size_t i = 0; i < 128; ++i)
m_notes[i] = m_results.back().second.end();
m_notes[chan][i] = results.back().second.end();
}
uint8_t m_status = 0;
@ -128,154 +129,93 @@ public:
std::vector<uint8_t>::const_iterator end)
{
std::vector<uint8_t>::const_iterator it = begin;
if (it == end)
return begin;
uint32_t deltaTime;
_readContinuedValue(it, end, deltaTime);
m_tick += deltaTime;
uint8_t a = *it++;
uint8_t b;
if (a & 0x80)
m_status = a;
else
it--;
/* Not actually used as such for now */
if (m_results.empty())
_addProgramChange(0);
std::multimap<int, Event>& res = m_results.back().second;
if (m_status == 0xff)
while (it != end)
{
/* Meta events */
if (it == end)
return begin;
a = *it++;
uint32_t deltaTime;
_readContinuedValue(it, end, deltaTime);
m_tick += deltaTime;
uint32_t length;
_readContinuedValue(it, end, length);
uint8_t a = *it++;
uint8_t b;
if (a & 0x80)
m_status = a;
else
it--;
switch (a)
{
case 0x51:
{
uint32_t tempo = 0;
memcpy(&reinterpret_cast<uint8_t*>(&tempo)[1], &*it, 3);
m_tempos.emplace(m_tick, 60000000 / SBig(tempo));
}
default:
it += length;
}
}
else
{
uint8_t chan = m_status & 0xf;
switch (Status(m_status & 0xf0))
{
case Status::NoteOff:
if (m_status == 0xff)
{
/* Meta events */
if (it == end)
return begin;
a = *it++;
if (it == end)
return begin;
b = *it++;
uint8_t notenum = clamp7(a);
std::multimap<int, Event>::iterator note = m_notes[notenum];
if (note != res.end())
uint32_t length;
_readContinuedValue(it, end, length);
switch (a)
{
note->second.length = uint16_t(m_tick - note->first);
m_notes[notenum] = res.end();
case 0x51:
{
uint32_t tempo = 0;
memcpy(&reinterpret_cast<uint8_t*>(&tempo)[1], &*it, 3);
m_tempos.emplace(m_tick, 60000000 / SBig(tempo));
}
break;
}
case Status::NoteOn:
{
if (it == end)
return begin;
a = *it++;
if (it == end)
return begin;
b = *it++;
uint8_t notenum = clamp7(a);
uint8_t vel = clamp7(b);
std::multimap<int, Event>::iterator note = m_notes[notenum];
if (note != res.end())
note->second.length = uint16_t(m_tick - note->first);
m_notes[notenum] = res.emplace(m_tick, Event{NoteEvent{}, chan, notenum, vel, 0});
break;
}
case Status::NotePressure:
{
if (it == end)
return begin;
a = *it++;
if (it == end)
return begin;
b = *it++;
break;
}
case Status::ControlChange:
{
if (it == end)
return begin;
a = *it++;
if (it == end)
return begin;
b = *it++;
res.emplace(m_tick, Event{CtrlEvent{}, chan, clamp7(a), clamp7(b), 0});
break;
}
case Status::ProgramChange:
{
if (it == end)
return begin;
a = *it++;
res.emplace(m_tick, Event{ProgEvent{}, chan, a});
break;
}
case Status::ChannelPressure:
{
if (it == end)
return begin;
a = *it++;
break;
}
case Status::PitchBend:
{
if (it == end)
return begin;
a = *it++;
if (it == end)
return begin;
b = *it++;
res.emplace(m_tick, Event{PitchEvent{}, chan, clamp7(b) * 128 + clamp7(a)});
break;
}
case Status::SysEx:
{
switch (Status(m_status & 0xff))
{
case Status::SysEx:
{
uint32_t len;
if (!_readContinuedValue(it, end, len) || end - it < len)
return begin;
break;
default:
it += length;
}
case Status::TimecodeQuarterFrame:
} else
{
uint8_t chan = m_status & 0xf;
auto& results = m_results[chan];
/* Not actually used as such for now */
if (results.empty())
_addProgramChange(chan, 0);
std::multimap<int, Event>& res = results.back().second;
switch (Status(m_status & 0xf0))
{
case Status::NoteOff:
{
if (it == end)
return begin;
a = *it++;
if (it == end)
return begin;
b = *it++;
uint8_t notenum = clamp7(a);
std::multimap<int, Event>::iterator note = m_notes[chan][notenum];
if (note != res.end())
{
note->second.length = m_tick - note->first;
m_notes[chan][notenum] = res.end();
}
break;
}
case Status::SongPositionPointer:
case Status::NoteOn:
{
if (it == end)
return begin;
a = *it++;
if (it == end)
return begin;
b = *it++;
uint8_t notenum = clamp7(a);
uint8_t vel = clamp7(b);
std::multimap<int, Event>::iterator note = m_notes[chan][notenum];
if (note != res.end())
note->second.length = m_tick - note->first;
if (vel != 0)
m_notes[chan][notenum] = res.emplace(m_tick, Event{NoteEvent{}, chan, notenum, vel, 0});
else
m_notes[chan][notenum] = res.end();
break;
}
case Status::NotePressure:
{
if (it == end)
return begin;
@ -285,35 +225,100 @@ public:
b = *it++;
break;
}
case Status::SongSelect:
case Status::ControlChange:
{
if (it == end)
return begin;
a = *it++;
if (it == end)
return begin;
b = *it++;
res.emplace(m_tick, Event{CtrlEvent{}, chan, clamp7(a), clamp7(b), 0});
break;
}
case Status::ProgramChange:
{
if (it == end)
return begin;
a = *it++;
res.emplace(m_tick, Event{ProgEvent{}, chan, a});
break;
}
case Status::ChannelPressure:
{
if (it == end)
return begin;
a = *it++;
break;
}
case Status::TuneRequest:
case Status::Start:
case Status::Continue:
case Status::Stop:
case Status::Reset:
case Status::SysExTerm:
case Status::TimingClock:
case Status::ActiveSensing:
case Status::PitchBend:
{
if (it == end)
return begin;
a = *it++;
if (it == end)
return begin;
b = *it++;
res.emplace(m_tick, Event{PitchEvent{}, chan, clamp7(b) * 128 + clamp7(a)});
break;
}
case Status::SysEx:
{
switch (Status(m_status & 0xff))
{
case Status::SysEx:
{
uint32_t len;
if (!_readContinuedValue(it, end, len) || end - it < len)
return begin;
break;
}
case Status::TimecodeQuarterFrame:
{
if (it == end)
return begin;
a = *it++;
break;
}
case Status::SongPositionPointer:
{
if (it == end)
return begin;
a = *it++;
if (it == end)
return begin;
b = *it++;
break;
}
case Status::SongSelect:
{
if (it == end)
return begin;
a = *it++;
break;
}
case Status::TuneRequest:
case Status::Start:
case Status::Continue:
case Status::Stop:
case Status::Reset:
case Status::SysExTerm:
case Status::TimingClock:
case Status::ActiveSensing:
default:
break;
}
break;
}
default:
break;
}
break;
}
default:
break;
}
}
return it;
}
std::vector<std::pair<int, std::multimap<int, Event>>>& getResults() { return m_results; }
std::vector<std::pair<int, std::multimap<int, Event>>>& getResults(int chan) { return m_results[chan]; }
std::multimap<int, int>& getTempos() { return m_tempos; }
};
@ -1031,8 +1036,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
std::vector<uint8_t>::const_iterator end = it + length;
MIDIDecoder dec;
while (begin != end)
begin = dec.receiveBytes(begin, end);
dec.receiveBytes(begin, end);
std::multimap<int, int>& tempos = dec.getTempos();
if (tempos.size() == 1)
@ -1065,12 +1069,11 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
it = end;
MIDIDecoder dec;
while (begin != end)
begin = dec.receiveBytes(begin, end);
dec.receiveBytes(begin, end);
std::vector<std::pair<int, std::multimap<int, Event>>>& results = dec.getResults();
for (int c = 0; c < 16; ++c)
{
std::vector<std::pair<int, std::multimap<int, Event>>>& results = dec.getResults(c);
int lastTrackStartTick = 0;
bool didChanInit = false;
for (auto& prog : results)
@ -1177,20 +1180,21 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
case Event::Type::Pitch:
{
int newPitch = event.second.pitchBend - 0x2000;
EncodeDelta(region.modBuf, eventTick - lastPitchTick, newPitch - lastPitchVal);
EncodeDelta(region.pitchBuf, eventTick - lastPitchTick, newPitch - lastPitchVal);
lastPitchTick = eventTick;
lastPitchVal = newPitch;
break;
}
case Event::Type::Note:
{
int lenTicks = event.second.length * 384 / header.div;
if (version == 1)
{
EncodeTime(region.eventBuf, uint32_t(eventTick - lastEventTick));
lastEventTick = eventTick;
region.eventBuf.push_back(event.second.noteOrCtrl);
region.eventBuf.push_back(event.second.velOrVal);
uint16_t lenBig = SBig(uint16_t(event.second.length));
uint16_t lenBig = SBig(uint16_t(lenTicks));
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&lenBig)[0]);
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&lenBig)[1]);
}
@ -1201,7 +1205,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
uint32_t tickBig = SBig(uint32_t(eventTick - startTick));
for (int i = 0; i < 4; ++i)
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&tickBig)[i]);
uint16_t lenBig = SBig(uint16_t(event.second.length));
uint16_t lenBig = SBig(uint16_t(lenTicks));
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&lenBig)[0]);
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&lenBig)[1]);
region.eventBuf.push_back(event.second.noteOrCtrl);
@ -1212,7 +1216,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
uint32_t tick = uint32_t(eventTick - startTick);
for (int i = 0; i < 4; ++i)
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&tick)[i]);
uint16_t len = uint16_t(event.second.length);
uint16_t len = uint16_t(lenTicks);
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&len)[0]);
region.eventBuf.push_back(reinterpret_cast<const uint8_t*>(&len)[1]);
region.eventBuf.push_back(event.second.noteOrCtrl);

View File

@ -106,8 +106,6 @@ void SongState::Track::Header::swapBig()
SongState::Track::Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions)
: m_parent(&parent), m_midiChan(midiChan), m_curRegion(nullptr), m_nextRegion(regions)
{
for (int i = 0; i < 128; ++i)
m_remNoteLengths[i] = std::numeric_limits<decltype(m_remNoteLengths)::value_type>::min();
}
void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
@ -406,15 +404,11 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
/* Stop finished notes */
for (int i = 0; i < 128; ++i)
{
constexpr decltype(m_remNoteLengths)::value_type MIN = std::numeric_limits<decltype(MIN)>::min();
if (m_remNoteLengths[i] != MIN)
if (m_remNoteLengths[i] > 0)
{
m_remNoteLengths[i] -= ticks;
if (m_remNoteLengths[i] <= 0)
{
seq.keyOff(m_midiChan, i, 0);
m_remNoteLengths[i] = MIN;
}
}
}

View File

@ -280,7 +280,8 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdWaitTicks::Introspective =
CmdType::Structure,
"Wait Ticks"sv,
"Suspend SoundMacro execution for specified length of time. Value of 65535 "
"will wait indefinitely, relying on Key Off or Sample End to signal stop."sv,
"will wait indefinitely, relying on Key Off or Sample End to signal stop. "
"Absolute mode waits relative to the start of the SoundMacro."sv,
{
{
FIELD_HEAD(SoundMacro::CmdWaitTicks, keyOff),
@ -442,7 +443,8 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdWaitMs::Introspective =
CmdType::Structure,
"Wait Millisec"sv,
"Suspend SoundMacro execution for specified length of time. Value of 65535 "
"will wait indefinitely, relying on Key Off or Sample End to signal stop."sv,
"will wait indefinitely, relying on Key Off or Sample End to signal stop. "
"Absolute mode waits relative to the start of the SoundMacro."sv,
{
{
FIELD_HEAD(SoundMacro::CmdWaitMs, keyOff),
@ -1468,7 +1470,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSetPitchAdsr::Introspective =
{
CmdType::Pitch,
"Set Pitch ADSR"sv,
"Define the pitch ADSR from a pool object. The pitch range is "
"Define the pitch ADSR from a DLS ADSR pool object. The pitch range is "
"specified using Note and Cents parameters."sv,
{
{

View File

@ -117,6 +117,7 @@ bool Voice::_checkSamplePos(bool& looped)
void Voice::_doKeyOff()
{
m_voxState = VoiceState::KeyOff;
if (m_state.m_inWait && m_state.m_keyoffWait)
{
if (m_volAdsr.isAdsrSet() || m_state.m_useAdsrControllers)
@ -480,7 +481,7 @@ void Voice::preSupplyAudio(double dt)
}
if (m_pitchEnv)
{
newPitch *= m_pitchAdsr.advance(dt);
newPitch += m_pitchAdsr.advance(dt) * m_pitchEnvRange;
refresh = true;
}
@ -934,7 +935,6 @@ void Voice::_macroKeyOff()
m_sustainKeyOff = true;
else
_doKeyOff();
m_voxState = VoiceState::KeyOff;
}
}
@ -1456,10 +1456,7 @@ void Voice::_notifyCtrlChange(uint8_t ctrl, int8_t val)
{
if (ctrl == 0x40)
{
if (val >= 0x40)
setPedal(true);
else
setPedal(false);
setPedal(val >= 0x40);
}
else if (ctrl == 0x5b)
{