diff --git a/Editor/EditorWidget.hpp b/Editor/EditorWidget.hpp index f726145..4a6d463 100644 --- a/Editor/EditorWidget.hpp +++ b/Editor/EditorWidget.hpp @@ -17,6 +17,12 @@ public: virtual bool valid() const { return true; } virtual void unloadData() {} virtual ProjectModel::INode* currentNode() const { return nullptr; } +public slots: + virtual bool isItemEditEnabled() const { return false; } + virtual void itemCutAction() {} + virtual void itemCopyAction() {} + virtual void itemPasteAction() {} + virtual void itemDeleteAction() {} }; class EditorUndoCommand : public QUndoCommand diff --git a/Editor/KeymapEditor.cpp b/Editor/KeymapEditor.cpp index 6854ac5..4a78be4 100644 --- a/Editor/KeymapEditor.cpp +++ b/Editor/KeymapEditor.cpp @@ -483,7 +483,7 @@ int KeymapEditor::allocateConfigIdx(uint64_t key) ++search->second.second; return search->second.first; } - for (int i = 0; i < 128; ++i) + for (int i = 0; i < 129; ++i) if (!m_idxBitmap[i]) { m_configToIdx[key] = std::make_pair(i, 1); @@ -514,7 +514,7 @@ int KeymapEditor::getConfigIdx(uint64_t key) const auto search = m_configToIdx.find(key); if (search != m_configToIdx.end()) return search->second.first; - for (int i = 0; i < 128; ++i) + for (int i = 0; i < 129; ++i) if (!m_idxBitmap[i]) return i; Q_UNREACHABLE(); @@ -585,7 +585,7 @@ KeymapEditor::KeymapEditor(QWidget* parent) int k = 0; for (int i = 0; i < 13; ++i) - for (int j = 0; j < 10 && k < 128; ++j) + for (int j = 0; j < 10 && k < 129; ++j) m_paintPalette[k++].setHsv(HueTable[j], SaturationTable[i], ValueTable[i]); m_scrollArea->setWidget(m_kmView); diff --git a/Editor/KeymapEditor.hpp b/Editor/KeymapEditor.hpp index 01a8d05..fe93053 100644 --- a/Editor/KeymapEditor.hpp +++ b/Editor/KeymapEditor.hpp @@ -87,10 +87,10 @@ Q_OBJECT QScrollArea* m_scrollArea; KeymapView* m_kmView; KeymapControls* m_controls; - QColor m_paintPalette[128]; + QColor m_paintPalette[129]; amuse::Keymap m_controlKeymap; std::unordered_map> m_configToIdx; - std::bitset<128> m_idxBitmap; + std::bitset<129> m_idxBitmap; bool m_inPaint = false; void _touch(); void touchKey(int key, bool bulk = false); diff --git a/Editor/LayersEditor.cpp b/Editor/LayersEditor.cpp index 5feab55..2ecfa78 100644 --- a/Editor/LayersEditor.cpp +++ b/Editor/LayersEditor.cpp @@ -1,12 +1,470 @@ #include "LayersEditor.hpp" +#include "MainWindow.hpp" +#include +#include +#include -bool LayersEditor::loadData(ProjectModel::LayersNode* node) +QWidget* SignedValueFactory::createEditor(int userType, QWidget *parent) const { + QSpinBox* sb = new QSpinBox(parent); + sb->setFrame(false); + sb->setMinimum(-128); + sb->setMaximum(127); + return sb; +} + +QWidget* UnsignedValueFactory::createEditor(int userType, QWidget *parent) const +{ + QSpinBox* sb = new QSpinBox(parent); + sb->setFrame(false); + sb->setMinimum(0); + sb->setMaximum(127); + return sb; +} + +EditorFieldProjectNode::EditorFieldProjectNode(ProjectModel::CollectionNode* collection, QWidget* parent) +: FieldProjectNode(collection, parent) +{} + +SoundMacroDelegate::SoundMacroDelegate(QObject* parent) +: QStyledItemDelegate(parent) {} + +QWidget* SoundMacroDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + const LayersModel* model = static_cast(index.model()); + ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get()); + EditorFieldProjectNode* cb = + new EditorFieldProjectNode(group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro), parent); + connect(cb, SIGNAL(currentIndexChanged(int)), this, SLOT(smIndexChanged())); + return cb; +} + +void SoundMacroDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const +{ + const LayersModel* model = static_cast(index.model()); + const amuse::LayerMapping& layer = (*model->m_node->m_obj)[index.row()]; + ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get()); + ProjectModel::CollectionNode* smColl = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro); + static_cast(editor)->setCurrentIndex(smColl->indexOfId(layer.macro.id) + 1); + if (static_cast(editor)->shouldPopupOpen()) + static_cast(editor)->showPopup(); +} + +void SoundMacroDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const +{ + const LayersModel* model = static_cast(m); + amuse::LayerMapping& layer = (*model->m_node->m_obj)[index.row()]; + ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get()); + ProjectModel::CollectionNode* smColl = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro); + int idx = static_cast(editor)->currentIndex(); + if (idx == 0) + layer.macro.id = amuse::SoundMacroId(); + else + layer.macro.id = smColl->idOfIndex(idx - 1); +} + +void SoundMacroDelegate::smIndexChanged() +{ + emit commitData(static_cast(sender())); +} + +void LayersModel::loadData(ProjectModel::LayersNode* node) +{ + beginResetModel(); + m_node = node; + endResetModel(); +} + +void LayersModel::unloadData() +{ + beginResetModel(); + m_node.reset(); + endResetModel(); +} + +int LayersModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + if (!m_node) + return 0; + return int(m_node->m_obj->size()) + 1; +} + +int LayersModel::columnCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + return 8; +} + +QVariant LayersModel::data(const QModelIndex& index, int role) const +{ + if (!m_node) + return QVariant(); + if (index.row() == m_node->m_obj->size()) + return QVariant(); + const amuse::LayerMapping& layer = (*m_node->m_obj)[index.row()]; + + if (role == Qt::DisplayRole || role == Qt::EditRole) + { + switch (index.column()) + { + case 0: + { + ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(m_node.get()); + ProjectModel::CollectionNode* smColl = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro); + if (ProjectModel::BasePoolObjectNode* node = smColl->nodeOfId(layer.macro.id)) + return node->text(); + return QVariant(); + } + case 1: + return layer.keyLo; + case 2: + return layer.keyHi; + case 3: + return layer.transpose; + case 4: + return layer.volume; + case 5: + return layer.prioOffset; + case 6: + return layer.span; + case 7: + return layer.pan; + default: + break; + } + } + + return QVariant(); +} + +bool LayersModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!m_node || role != Qt::EditRole) + return false; + amuse::LayerMapping& layer = (*m_node->m_obj)[index.row()]; + + switch (index.column()) + { + case 1: + layer.keyLo = value.toInt(); + return true; + case 2: + layer.keyHi = value.toInt(); + return true; + case 3: + layer.transpose = value.toInt(); + return true; + case 4: + layer.volume = value.toInt(); + return true; + case 5: + layer.prioOffset = value.toInt(); + return true; + case 6: + layer.span = value.toInt(); + return true; + case 7: + layer.pan = value.toInt(); + return true; + default: + break; + } + return false; } -LayersEditor::LayersEditor(QWidget* parent) -: EditorWidget(parent) +QVariant LayersModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch (section) + { + case 0: + return tr("SoundMacro"); + case 1: + return tr("Key Lo"); + case 2: + return tr("Key Hi"); + case 3: + return tr("Transpose"); + case 4: + return tr("Volume"); + case 5: + return tr("Prio Off"); + case 6: + return tr("Span"); + case 7: + return tr("Pan"); + default: + break; + } + } + return QVariant(); +} + +Qt::ItemFlags LayersModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + if (index.row() == m_node->m_obj->size()) + return Qt::NoItemFlags; + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; +} + +Qt::DropActions LayersModel::supportedDropActions() const +{ + return Qt::MoveAction; +} + +Qt::DropActions LayersModel::supportedDragActions() const +{ + return Qt::MoveAction; +} + +bool LayersModel::dropMimeData(const QMimeData* data, Qt::DropAction action, + int row, int column, const QModelIndex& parent) +{ + // check if the action is supported + if (!data || action != Qt::MoveAction) + return false; + // check if the format is supported + QStringList types = mimeTypes(); + if (types.isEmpty()) + return false; + QString format = types.at(0); + if (!data->hasFormat(format)) + return false; + + // decode and insert + QByteArray encoded = data->data(format); + QDataStream stream(&encoded, QIODevice::ReadOnly); + + std::unordered_set rows; + int lastRow = -1; + + while (!stream.atEnd()) { + int r, c; + QMap v; + stream >> r >> c >> v; + rows.insert(r); + lastRow = r; + } + + if (lastRow == -1) + return false; + + int start = lastRow; + while (rows.find(start - 1) != rows.cend()) + start -= 1; + int count = lastRow - start + 1; + while (rows.find(start + count) != rows.cend()) + count += 1; + + int dest = parent.row(); + if (dest >= start) + { + if (dest - start < count) + return false; + dest += 1; + } + + moveRows(QModelIndex(), start, count, QModelIndex(), dest); + return true; +} + +bool LayersModel::insertRows(int row, int count, const QModelIndex& parent) +{ + if (!m_node) + return false; + beginInsertRows(parent, row, row + count - 1); + std::vector& layers = *m_node->m_obj; + layers.insert(layers.begin() + row, count, amuse::LayerMapping()); + endInsertRows(); + return true; +} + +bool LayersModel::moveRows(const QModelIndex& sourceParent, int sourceRow, int count, + const QModelIndex& destinationParent, int destinationChild) +{ + if (!m_node) + return false; + beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild); + std::vector& layers = *m_node->m_obj; + if (destinationChild < sourceRow) + { + for (int i = 0; i < count; ++i) + { + amuse::LayerMapping tmp = std::move(layers[sourceRow]); + for (int j = sourceRow; j != destinationChild; --j) + layers[j] = std::move(layers[j - 1]); + layers[destinationChild] = std::move(tmp); + ++sourceRow; + ++destinationChild; + } + } + else if (destinationChild > sourceRow) + { + for (int i = 0; i < count; ++i) + { + amuse::LayerMapping tmp = std::move(layers[sourceRow]); + for (int j = sourceRow; j != destinationChild - 1; ++j) + layers[j] = std::move(layers[j + 1]); + layers[destinationChild - 1] = std::move(tmp); + } + } + endMoveRows(); + return true; +} + +bool LayersModel::removeRows(int row, int count, const QModelIndex& parent) +{ + if (!m_node) + return false; + beginRemoveRows(parent, row, row + count - 1); + std::vector& layers = *m_node->m_obj; + layers.erase(layers.begin() + row, layers.begin() + row + count); + endRemoveRows(); + return true; +} + +LayersModel::LayersModel(QObject* parent) +: QAbstractTableModel(parent) +{} + +void LayersTableView::deleteSelection() +{ + QModelIndexList list; + while (!(list = selectionModel()->selectedRows()).isEmpty()) + model()->removeRow(list.back().row()); +} + +void LayersTableView::doItemsLayout() +{ + horizontalHeader()->setMinimumSectionSize(75); + horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed); + horizontalHeader()->resizeSection(1, 75); + horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); + horizontalHeader()->resizeSection(2, 75); + horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); + horizontalHeader()->resizeSection(3, 75); + horizontalHeader()->setSectionResizeMode(4, QHeaderView::Fixed); + horizontalHeader()->resizeSection(4, 75); + horizontalHeader()->setSectionResizeMode(5, QHeaderView::Fixed); + horizontalHeader()->resizeSection(5, 75); + horizontalHeader()->setSectionResizeMode(6, QHeaderView::Fixed); + horizontalHeader()->resizeSection(6, 75); + horizontalHeader()->setSectionResizeMode(7, QHeaderView::Fixed); + horizontalHeader()->resizeSection(7, 75); + QTableView::doItemsLayout(); +} + +LayersTableView::LayersTableView(QWidget* parent) +: QTableView(parent) +{ + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::ExtendedSelection); + setDragDropMode(QAbstractItemView::InternalMove); + setDefaultDropAction(Qt::MoveAction); + setDragEnabled(true); + setGridStyle(Qt::NoPen); +} + + +bool LayersEditor::loadData(ProjectModel::LayersNode* node) +{ + m_model.loadData(node); + return true; +} + +void LayersEditor::unloadData() +{ + m_model.unloadData(); +} + +ProjectModel::INode* LayersEditor::currentNode() const +{ + return m_model.m_node.get(); +} + +void LayersEditor::resizeEvent(QResizeEvent* ev) +{ + m_tableView.setGeometry(QRect({}, ev->size())); + m_addButton.move(0, ev->size().height() - 32); + m_removeButton.move(32, ev->size().height() - 32); +} + +void LayersEditor::doAdd() +{ + QModelIndex idx = m_tableView.selectionModel()->currentIndex(); + if (!idx.isValid()) + m_model.insertRow(m_model.rowCount() - 1); + else + m_model.insertRow(idx.row()); +} + +void LayersEditor::doSelectionChanged(const QItemSelection& selected) +{ + m_removeAction.setDisabled(selected.isEmpty()); + g_MainWindow->updateFocus(); +} + +bool LayersEditor::isItemEditEnabled() const +{ + return !m_tableView.selectionModel()->selectedRows().isEmpty(); +} + +void LayersEditor::itemCutAction() { } + +void LayersEditor::itemCopyAction() +{ + +} + +void LayersEditor::itemPasteAction() +{ + +} + +void LayersEditor::itemDeleteAction() +{ + m_tableView.deleteSelection(); +} + +LayersEditor::LayersEditor(QWidget* parent) +: EditorWidget(parent), m_tableView(this), + m_addAction(tr("Add Row")), m_addButton(this), m_removeAction(tr("Remove Row")), m_removeButton(this) +{ + m_signedDelegate.setItemEditorFactory(&m_signedFactory); + m_unsignedDelegate.setItemEditorFactory(&m_unsignedFactory); + + m_tableView.setItemDelegateForColumn(1, &m_unsignedDelegate); + m_tableView.setItemDelegateForColumn(2, &m_unsignedDelegate); + m_tableView.setItemDelegateForColumn(3, &m_signedDelegate); + m_tableView.setItemDelegateForColumn(4, &m_unsignedDelegate); + m_tableView.setItemDelegateForColumn(5, &m_signedDelegate); + m_tableView.setItemDelegateForColumn(6, &m_unsignedDelegate); + m_tableView.setItemDelegateForColumn(7, &m_unsignedDelegate); + + m_tableView.setModel(&m_model); + m_tableView.setItemDelegateForColumn(0, &m_smDelegate); + connect(m_tableView.selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, SLOT(doSelectionChanged(const QItemSelection&))); + + m_addAction.setIcon(QIcon(QStringLiteral(":/icons/IconAdd.svg"))); + m_addButton.setDefaultAction(&m_addAction); + m_addButton.setFixedSize(32, 32); + connect(&m_addAction, SIGNAL(triggered(bool)), this, SLOT(doAdd())); + + m_removeAction.setIcon(QIcon(QStringLiteral(":/icons/IconRemove.svg"))); + m_removeButton.setDefaultAction(&m_removeAction); + m_removeButton.setFixedSize(32, 32); + connect(&m_removeAction, SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction())); + m_removeAction.setEnabled(false); +} diff --git a/Editor/LayersEditor.hpp b/Editor/LayersEditor.hpp index 97e8eab..c7d28ec 100644 --- a/Editor/LayersEditor.hpp +++ b/Editor/LayersEditor.hpp @@ -2,14 +2,116 @@ #define AMUSE_LAYERS_EDITOR_HPP #include "EditorWidget.hpp" +#include +#include +#include +#include +#include +#include + +class SignedValueFactory : public QItemEditorFactory +{ +public: + QWidget* createEditor(int userType, QWidget *parent) const; +}; + +class UnsignedValueFactory : public QItemEditorFactory +{ +public: + QWidget* createEditor(int userType, QWidget *parent) const; +}; + +class EditorFieldProjectNode : public FieldProjectNode +{ +Q_OBJECT + bool m_deferPopupOpen = true; +public: + explicit EditorFieldProjectNode(ProjectModel::CollectionNode* collection = Q_NULLPTR, QWidget* parent = Q_NULLPTR); + bool shouldPopupOpen() + { + bool ret = m_deferPopupOpen; + m_deferPopupOpen = false; + return ret; + } +}; + +class SoundMacroDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + explicit SoundMacroDelegate(QObject* parent = Q_NULLPTR); + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const; + void setEditorData(QWidget* editor, const QModelIndex& index) const; + void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const; +private slots: + void smIndexChanged(); +}; + +class LayersModel : public QAbstractTableModel +{ + Q_OBJECT + friend class LayersEditor; + friend class SoundMacroDelegate; + amuse::ObjToken m_node; +public: + explicit LayersModel(QObject* parent = Q_NULLPTR); + void loadData(ProjectModel::LayersNode* node); + void unloadData(); + + int rowCount(const QModelIndex& parent = QModelIndex()) const; + int columnCount(const QModelIndex& parent = QModelIndex()) const; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + Qt::ItemFlags flags(const QModelIndex& index) const; + Qt::DropActions supportedDropActions() const; + Qt::DropActions supportedDragActions() const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + + bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()); + bool moveRows(const QModelIndex& sourceParent, int sourceRow, int count, + const QModelIndex& destinationParent, int destinationChild); + bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()); +}; + +class LayersTableView : public QTableView +{ + Q_OBJECT +public: + explicit LayersTableView(QWidget* parent = Q_NULLPTR); + void doItemsLayout(); + void deleteSelection(); +}; class LayersEditor : public EditorWidget { Q_OBJECT + LayersModel m_model; + SoundMacroDelegate m_smDelegate; + SignedValueFactory m_signedFactory; + UnsignedValueFactory m_unsignedFactory; + QStyledItemDelegate m_signedDelegate, m_unsignedDelegate; + LayersTableView m_tableView; + QAction m_addAction; + QToolButton m_addButton; + QAction m_removeAction; + QToolButton m_removeButton; public: explicit LayersEditor(QWidget* parent = Q_NULLPTR); bool loadData(ProjectModel::LayersNode* node); + void unloadData(); + ProjectModel::INode* currentNode() const; + void resizeEvent(QResizeEvent* ev); +public slots: + void doAdd(); + void doSelectionChanged(const QItemSelection& selected); + + bool isItemEditEnabled() const; + void itemCutAction(); + void itemCopyAction(); + void itemPasteAction(); + void itemDeleteAction(); }; - #endif //AMUSE_LAYERS_EDITOR_HPP diff --git a/Editor/MainWindow.cpp b/Editor/MainWindow.cpp index c2f1d75..4866386 100644 --- a/Editor/MainWindow.cpp +++ b/Editor/MainWindow.cpp @@ -256,7 +256,7 @@ bool MainWindow::setProjectPath(const QString& path) m_ui.actionExport_GameCube_Groups->setEnabled(true); setWindowFilePath(path); updateWindowTitle(); - onFocusChanged(nullptr, focusWidget()); + updateFocus(); m_undoStack->clear(); QSettings settings; @@ -314,7 +314,7 @@ void MainWindow::timerEvent(QTimerEvent* ev) if (m_voxEngine && m_engine) { m_voxEngine->pumpAndMixVoices(); - m_ui.statusbar->setVoiceCount(int(m_engine->getActiveVoices().size())); + m_ui.statusbar->setVoiceCount(int(m_engine->getNumTotalActiveVoices())); if (m_engine->getActiveVoices().empty() && m_uiDisabled) { m_ui.projectOutline->setEnabled(true); @@ -548,6 +548,11 @@ void MainWindow::pushUndoCommand(QUndoCommand* cmd) m_undoStack->push(cmd); } +void MainWindow::updateFocus() +{ + onFocusChanged(nullptr, focusWidget()); +} + void MainWindow::aboutToDeleteNode(ProjectModel::INode* node) { if (getEditorNode() == node) @@ -1000,7 +1005,7 @@ void MainWindow::onFocusChanged(QWidget* old, QWidget* now) if (now == m_ui.projectOutline || m_ui.projectOutline->isAncestorOf(now)) { - setOutlineEditEnabled(canEditOutline()); + setItemEditEnabled(canEditOutline()); if (m_projectModel) { m_cutConn = connect(m_ui.actionCut, SIGNAL(triggered()), this, SLOT(outlineCutAction())); @@ -1011,12 +1016,23 @@ void MainWindow::onFocusChanged(QWidget* old, QWidget* now) } else if (now == m_ui.editorContents || m_ui.editorContents->isAncestorOf(now)) { - setOutlineEditEnabled(false); + setItemEditEnabled(false); + if (EditorWidget* editor = getEditorWidget()) + { + if (editor->isItemEditEnabled()) + { + 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())); + } + } } } -void MainWindow::setOutlineEditEnabled(bool enabled) +void MainWindow::setItemEditEnabled(bool enabled) { m_ui.actionCut->setEnabled(enabled); m_ui.actionCopy->setEnabled(enabled); @@ -1040,10 +1056,10 @@ void MainWindow::onOutlineSelectionChanged(const QItemSelection& selected, const return; if (selected.indexes().empty()) { - setOutlineEditEnabled(false); + setItemEditEnabled(false); return; } - setOutlineEditEnabled(m_projectModel->canEdit(selected.indexes().front())); + setItemEditEnabled(m_projectModel->canEdit(selected.indexes().front())); } void MainWindow::onTextSelect() diff --git a/Editor/MainWindow.hpp b/Editor/MainWindow.hpp index 7b0efbc..353858c 100644 --- a/Editor/MainWindow.hpp +++ b/Editor/MainWindow.hpp @@ -147,6 +147,7 @@ public: EditorWidget* getEditorWidget() const; amuse::ObjToken startEditorVoice(uint8_t key, uint8_t vel); void pushUndoCommand(QUndoCommand* cmd); + void updateFocus(); void aboutToDeleteNode(ProjectModel::INode* node); ProjectModel* projectModel() const { return m_projectModel; } @@ -190,7 +191,7 @@ public slots: void outlineDeleteAction(); void onFocusChanged(QWidget* old, QWidget* now); - void setOutlineEditEnabled(bool enabled); + void setItemEditEnabled(bool enabled); bool canEditOutline(); void onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); void onTextSelect(); diff --git a/Editor/ProjectModel.cpp b/Editor/ProjectModel.cpp index 73933c6..e2c25f6 100644 --- a/Editor/ProjectModel.cpp +++ b/Editor/ProjectModel.cpp @@ -94,6 +94,14 @@ ProjectModel::BasePoolObjectNode* ProjectModel::CollectionNode::nodeOfIndex(int return static_cast(m_children[idx].get()); } +ProjectModel::BasePoolObjectNode* ProjectModel::CollectionNode::nodeOfId(amuse::ObjectId id) const +{ + int idx = indexOfId(id); + if (idx < 0) + return nullptr; + return nodeOfIndex(idx); +} + ProjectModel::ProjectModel(const QString& path, QObject* parent) : QAbstractItemModel(parent), m_dir(path), m_nullProxy(this) { diff --git a/Editor/ProjectModel.hpp b/Editor/ProjectModel.hpp index 233e837..d43614e 100644 --- a/Editor/ProjectModel.hpp +++ b/Editor/ProjectModel.hpp @@ -207,6 +207,7 @@ public: int indexOfId(amuse::ObjectId id) const; amuse::ObjectId idOfIndex(int idx) const; BasePoolObjectNode* nodeOfIndex(int idx) const; + BasePoolObjectNode* nodeOfId(amuse::ObjectId id) const; }; struct BasePoolObjectNode : INode { diff --git a/Editor/main.cpp b/Editor/main.cpp index c604aa2..4d1088b 100644 --- a/Editor/main.cpp +++ b/Editor/main.cpp @@ -6,6 +6,7 @@ #include "boo/IApplication.hpp" #include #include +#include using namespace std::literals; @@ -91,6 +92,9 @@ int main(int argc, char* argv[]) MacOSSetDarkAppearance(); #endif + logvisor::RegisterConsoleLogger(); + logvisor::RegisterStandardExceptions(); + BooInterface booApp; boo::APP = &booApp; diff --git a/Editor/resources/IconAdd.svg b/Editor/resources/IconAdd.svg new file mode 100644 index 0000000..453895c --- /dev/null +++ b/Editor/resources/IconAdd.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Editor/resources/IconRemove.svg b/Editor/resources/IconRemove.svg new file mode 100644 index 0000000..3083d80 --- /dev/null +++ b/Editor/resources/IconRemove.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Editor/resources/lang_de.ts b/Editor/resources/lang_de.ts index 7c175c7..d151fda 100644 --- a/Editor/resources/lang_de.ts +++ b/Editor/resources/lang_de.ts @@ -181,6 +181,62 @@ + + LayersEditor + + + Add Row + + + + + Remove Row + + + + + LayersModel + + + SoundMacro + + + + + Key Lo + + + + + Key Hi + + + + + Transpose + + + + + Volume + + + + + Prio Off + + + + + Span + + + + + Pan + + + MainWindow @@ -396,13 +452,13 @@ - + The directory at '%1' must not be empty. - + Directory empty @@ -447,122 +503,122 @@ - + New Project - + The directory at '%1' does not exist. - + Bad Directory - + Opening - - - - + + + + Scanning Project - + Opening %1 - + Open Project - + Reloading Samples - + Scanning %1 - + Import Project - + The file at '%1' could not be interpreted as a MusyX container. - + Unsupported MusyX Container - + Sample Import Mode - + Amuse can import samples as WAV files for ease of editing, import original compressed data for lossless repacking, or both. Exporting the project will prefer whichever version was modified most recently. - + Import Compressed - + Import WAVs - + Import Both - + Raw Import Mode - + Would you like to scan for all MusyX group files in this directory? - + Project Name - + What should this project be named? - - + + Importing - - + + Importing %1 @@ -594,37 +650,37 @@ ProjectModel - + Sound Macros - + ADSRs - + Curves - + Keymaps - + Layers - + Samples - + Delete %1 diff --git a/Editor/resources/resources.qrc b/Editor/resources/resources.qrc index ffa2eee..d5e9962 100644 --- a/Editor/resources/resources.qrc +++ b/Editor/resources/resources.qrc @@ -26,6 +26,8 @@ IconKill.svg IconSample.svg IconPaintbrush.svg + IconAdd.svg + IconRemove.svg FaceGrey.svg diff --git a/include/amuse/AudioGroupPool.hpp b/include/amuse/AudioGroupPool.hpp index 98b1395..9fc747b 100644 --- a/include/amuse/AudioGroupPool.hpp +++ b/include/amuse/AudioGroupPool.hpp @@ -1358,13 +1358,13 @@ struct LayerMapping : BigDNA { AT_DECL_DNA_YAML SoundMacroIdDNA macro; - Value keyLo; - Value keyHi; - Value transpose; - Value volume; - Value prioOffset; - Value span; - Value pan; + Value keyLo = 0; + Value keyHi = 127; + Value transpose = 0; + Value volume = 127; + Value prioOffset = 0; + Value span = 0; + Value pan = 64; LayerMapping() = default; diff --git a/include/amuse/Engine.hpp b/include/amuse/Engine.hpp index 7d57558..811b07c 100644 --- a/include/amuse/Engine.hpp +++ b/include/amuse/Engine.hpp @@ -162,6 +162,9 @@ public: /** Obtain list of active voices */ std::list>& getActiveVoices() { return m_activeVoices; } + /** Obtain total active voice count (including child voices) */ + size_t getNumTotalActiveVoices() const; + /** Obtain list of active sequencers */ std::list>& getActiveSequencers() { return m_activeSequencers; } diff --git a/include/amuse/Voice.hpp b/include/amuse/Voice.hpp index 2afbe7b..2abca4d 100644 --- a/include/amuse/Voice.hpp +++ b/include/amuse/Voice.hpp @@ -351,6 +351,8 @@ public: { m_ctrlValsSelf.reset(); m_extCtrlVals = cvs; + for (ObjToken& vox : m_childVoices) + vox->installCtrlValues(cvs); } /** Get MIDI pitch wheel value on voice */ diff --git a/lib/AudioGroupPool.cpp b/lib/AudioGroupPool.cpp index b53b8a8..8102bdc 100644 --- a/lib/AudioGroupPool.cpp +++ b/lib/AudioGroupPool.cpp @@ -353,11 +353,6 @@ AudioGroupPool AudioGroupPool::CreateAudioGroupPool(SystemStringView groupPath) } } - amuse::KeymapId id = 42; - amuse::KeymapId::CurNameDB->registerPair("test", id); - auto& kmOut = ret.m_keymaps[id]; - kmOut = MakeObj>(); - return ret; } diff --git a/lib/AudioGroupProject.cpp b/lib/AudioGroupProject.cpp index cf86dfe..8743d3c 100644 --- a/lib/AudioGroupProject.cpp +++ b/lib/AudioGroupProject.cpp @@ -35,11 +35,17 @@ static void ReadRangedObjectIds(NameDB* db, athena::io::IStreamReader& r, NameDB ObjectId useId = i; if (tp == NameDB::Type::Layer) useId.id |= 0x8000; + else if (tp == NameDB::Type::Keymap) + useId.id |= 0x4000; db->registerPair(NameDB::generateName(useId, tp), useId); } } else { + if (tp == NameDB::Type::Layer) + id |= 0x8000; + else if (tp == NameDB::Type::Keymap) + id |= 0x4000; db->registerPair(NameDB::generateName(id, tp), id); } } diff --git a/lib/Common.cpp b/lib/Common.cpp index 26c3f22..ee39536 100644 --- a/lib/Common.cpp +++ b/lib/Common.cpp @@ -1,6 +1,8 @@ #include "amuse/Common.hpp" #include "logvisor/logvisor.hpp" +using namespace std::literals; + namespace amuse { static logvisor::Module Log("amuse"); @@ -180,11 +182,16 @@ void PageObjectIdDNA::_write(athena::io::YAMLDocWriter& w) std::string_view name = LayersId::CurNameDB->resolveNameFromId(id); w.writeString(nullptr, name); } - else + else if (id.id & 0x4000) { std::string_view name = KeymapId::CurNameDB->resolveNameFromId(id); w.writeString(nullptr, name); } + else + { + std::string_view name = SoundMacroId::CurNameDB->resolveNameFromId(id); + w.writeString(nullptr, name); + } } template const char* PageObjectIdDNA::DNAType() @@ -307,7 +314,10 @@ std::string_view NameDB::resolveNameFromId(ObjectId id) const { auto search = m_idToString.find(id); if (search == m_idToString.cend()) - Log.report(logvisor::Fatal, "Unable to resolve ID 0x%04X", id.id); + { + Log.report(logvisor::Error, "Unable to resolve ID 0x%04X", id.id); + return ""sv; + } return search->second; } @@ -315,7 +325,10 @@ ObjectId NameDB::resolveIdFromName(std::string_view str) const { auto search = m_stringToId.find(std::string(str)); if (search == m_stringToId.cend()) - Log.report(logvisor::Fatal, "Unable to resolve name %s", str.data()); + { + Log.report(logvisor::Error, "Unable to resolve name %s", str.data()); + return {}; + } return search->second; } diff --git a/lib/Engine.cpp b/lib/Engine.cpp index d7109a9..e0283a1 100644 --- a/lib/Engine.cpp +++ b/lib/Engine.cpp @@ -496,4 +496,12 @@ void Engine::sendMacroMessage(ObjectId macroId, int32_t val) for (ObjToken& seq : m_activeSequencers) seq->sendMacroMessage(macroId, val); } + +size_t Engine::getNumTotalActiveVoices() const +{ + size_t ret = 0; + for (const auto& vox : m_activeVoices) + ret += vox->getTotalVoices(); + return ret; +} } diff --git a/lib/Voice.cpp b/lib/Voice.cpp index 2e26ac0..fe5d7fb 100644 --- a/lib/Voice.cpp +++ b/lib/Voice.cpp @@ -776,6 +776,8 @@ ObjToken Voice::_startChildMacro(ObjectId macroId, int macroStep, double (*vox)->setVolume(m_targetUserVol); (*vox)->setPan(m_curPan); (*vox)->setSurroundPan(m_curSpan); + if (m_extCtrlVals) + (*vox)->installCtrlValues(m_extCtrlVals); return *vox; } @@ -835,7 +837,7 @@ bool Voice::_loadLayer(const std::vector& layer, double ticksPerSe if (m_voxState != VoiceState::Playing) { ret |= loadMacroObject(mapping.macro.id, 0, ticksPerSec, mappingKey, midiVel, midiMod, pushPc); - m_curVol = mapping.volume / 127.f; + m_curUserVol = m_targetUserVol = mapping.volume / 127.f; _setPan((mapping.pan - 64) / 64.f); _setSurroundPan((mapping.span - 64) / 64.f); } @@ -845,7 +847,7 @@ bool Voice::_loadLayer(const std::vector& layer, double ticksPerSe _startChildMacro(mapping.macro.id, 0, ticksPerSec, mappingKey, midiVel, midiMod, pushPc); if (vox) { - vox->m_curVol = mapping.volume / 127.f; + vox->m_curUserVol = vox->m_targetUserVol = mapping.volume / 127.f; vox->_setPan((mapping.pan - 64) / 64.f); vox->_setSurroundPan((mapping.span - 64) / 64.f); ret = true; @@ -892,12 +894,18 @@ bool Voice::loadPageObject(ObjectId objectId, double ticksPerSec, uint8_t midiKe if (layer) return _loadLayer(*layer, ticksPerSec, midiKey, midiVel, midiMod); } - else + else if (objectId.id & 0x4000) { const Keymap* keymap = m_audioGroup.getPool().keymap(objectId); if (keymap) return _loadKeymap(keymap, ticksPerSec, midiKey, midiVel, midiMod); } + else + { + const SoundMacro* sm = m_audioGroup.getPool().soundMacro(objectId); + if (sm) + return _loadSoundMacro(objectId, sm, 0, ticksPerSec, midiKey, midiVel, midiMod); + } return false; }