More undo commands and pitch/mod coding fix

This commit is contained in:
Jack Andersen 2018-08-13 22:36:02 -10:00
parent 277e78c14b
commit 5e89954094
21 changed files with 1747 additions and 631 deletions

View File

@ -37,7 +37,6 @@ QT5_WRAP_CPP(AMUSE_MOC
KeyboardWidget.hpp KeyboardWidget.hpp
StatusBarWidget.hpp StatusBarWidget.hpp
ProjectModel.hpp ProjectModel.hpp
ProjectStatistics.hpp
EditorWidget.hpp EditorWidget.hpp
SoundMacroEditor.hpp SoundMacroEditor.hpp
ADSREditor.hpp ADSREditor.hpp
@ -55,7 +54,6 @@ add_executable(amuse-gui WIN32 MACOSX_BUNDLE
KeyboardWidget.hpp KeyboardWidget.cpp KeyboardWidget.hpp KeyboardWidget.cpp
StatusBarWidget.hpp StatusBarWidget.hpp
ProjectModel.hpp ProjectModel.cpp ProjectModel.hpp ProjectModel.cpp
ProjectStatistics.hpp ProjectStatistics.cpp
EditorWidget.hpp EditorWidget.cpp EditorWidget.hpp EditorWidget.cpp
SoundMacroEditor.hpp SoundMacroEditor.cpp SoundMacroEditor.hpp SoundMacroEditor.cpp
ADSREditor.hpp ADSREditor.cpp ADSREditor.hpp ADSREditor.cpp

View File

@ -4,6 +4,99 @@
#include <QScrollBar> #include <QScrollBar>
#include <QMimeData> #include <QMimeData>
class LayerDataChangeUndoCommand : public EditorUndoCommand
{
QModelIndex m_index;
int m_undoVal, m_redoVal;
bool m_undid = false;
public:
explicit LayerDataChangeUndoCommand(ProjectModel::LayersNode* node, const QString& text,
QModelIndex index, int redoVal)
: EditorUndoCommand(node, text), m_index(index), m_redoVal(redoVal) {}
void undo()
{
m_undid = true;
amuse::LayerMapping& layer = (*static_cast<ProjectModel::LayersNode*>(m_node.get())->m_obj)[m_index.row()];
switch (m_index.column())
{
case 0:
layer.macro.id = m_undoVal;
break;
case 1:
layer.keyLo = m_undoVal;
break;
case 2:
layer.keyHi = m_undoVal;
break;
case 3:
layer.transpose = m_undoVal;
break;
case 4:
layer.volume = m_undoVal;
break;
case 5:
layer.prioOffset = m_undoVal;
break;
case 6:
layer.span = m_undoVal;
break;
case 7:
layer.pan = m_undoVal;
break;
default:
break;
}
EditorUndoCommand::undo();
}
void redo()
{
amuse::LayerMapping& layer = (*static_cast<ProjectModel::LayersNode*>(m_node.get())->m_obj)[m_index.row()];
switch (m_index.column())
{
case 0:
m_undoVal = layer.macro.id;
layer.macro.id = m_redoVal;
break;
case 1:
m_undoVal = layer.keyLo;
layer.keyLo = m_redoVal;
break;
case 2:
m_undoVal = layer.keyHi;
layer.keyHi = m_redoVal;
break;
case 3:
m_undoVal = layer.transpose;
layer.transpose = m_redoVal;
break;
case 4:
m_undoVal = layer.volume;
layer.volume = m_redoVal;
break;
case 5:
m_undoVal = layer.prioOffset;
layer.prioOffset = m_redoVal;
break;
case 6:
m_undoVal = layer.span;
layer.span = m_redoVal;
break;
case 7:
m_undoVal = layer.pan;
layer.pan = m_redoVal;
break;
default:
break;
}
if (m_undid)
EditorUndoCommand::redo();
}
};
SoundMacroDelegate::SoundMacroDelegate(QObject* parent) SoundMacroDelegate::SoundMacroDelegate(QObject* parent)
: QStyledItemDelegate(parent) {} : QStyledItemDelegate(parent) {}
@ -35,10 +128,13 @@ void SoundMacroDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, co
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get()); ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get());
ProjectModel::CollectionNode* smColl = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro); ProjectModel::CollectionNode* smColl = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro);
int idx = static_cast<EditorFieldProjectNode*>(editor)->currentIndex(); int idx = static_cast<EditorFieldProjectNode*>(editor)->currentIndex();
if (idx == 0) amuse::SoundMacroId id;
layer.macro.id = amuse::SoundMacroId(); if (idx != 0)
else id = smColl->idOfIndex(idx - 1);
layer.macro.id = smColl->idOfIndex(idx - 1); if (layer.macro.id == id)
return;
g_MainWindow->pushUndoCommand(new LayerDataChangeUndoCommand(model->m_node.get(),
tr("Change %1").arg(m->headerData(0, Qt::Horizontal).toString()), index, id.id));
emit m->dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); emit m->dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
} }
@ -123,42 +219,50 @@ bool LayersModel::setData(const QModelIndex& index, const QVariant& value, int r
{ {
if (!m_node || role != Qt::EditRole) if (!m_node || role != Qt::EditRole)
return false; return false;
amuse::LayerMapping& layer = (*m_node->m_obj)[index.row()]; const amuse::LayerMapping& layer = (*m_node->m_obj)[index.row()];
switch (index.column()) switch (index.column())
{ {
case 1: case 0:
layer.keyLo = value.toInt(); if (layer.macro.id == value.toInt())
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); return false;
return true;
case 2:
layer.keyHi = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 3:
layer.transpose = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 4:
layer.volume = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 5:
layer.prioOffset = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 6:
layer.span = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 7:
layer.pan = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
default:
break; break;
case 1:
if (layer.keyLo == value.toInt())
return false;
break;
case 2:
if (layer.keyHi == value.toInt())
return false;
break;
case 3:
if (layer.transpose == value.toInt())
return false;
break;
case 4:
if (layer.volume == value.toInt())
return false;
break;
case 5:
if (layer.prioOffset == value.toInt())
return false;
break;
case 6:
if (layer.span == value.toInt())
return false;
break;
case 7:
if (layer.pan == value.toInt())
return false;
break;
default:
return false;
} }
g_MainWindow->pushUndoCommand(new LayerDataChangeUndoCommand(m_node.get(),
tr("Change %1").arg(headerData(index.column(), Qt::Horizontal).toString()), index, value.toInt()));
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return false; return false;
} }
@ -210,6 +314,32 @@ Qt::DropActions LayersModel::supportedDragActions() const
return Qt::MoveAction; return Qt::MoveAction;
} }
class LayerRowMoveCommand : public EditorUndoCommand
{
LayersTableView* m_view;
int m_undoPos, m_redoPos, m_count;
bool m_undid = false;
public:
explicit LayerRowMoveCommand(ProjectModel::LayersNode* node, const QString& text, LayersTableView* view,
int undoPos, int redoPos, int count)
: EditorUndoCommand(node, text), m_view(view), m_undoPos(undoPos), m_redoPos(redoPos), m_count(count) {}
void undo()
{
m_undid = true;
EditorUndoCommand::undo();
if (m_redoPos > m_undoPos)
m_view->model()->moveRows(QModelIndex(), m_redoPos - 1, m_count, QModelIndex(), m_undoPos);
else
m_view->model()->moveRows(QModelIndex(), m_redoPos, m_count, QModelIndex(), m_undoPos + 1);
}
void redo()
{
if (m_undid)
EditorUndoCommand::redo();
m_view->model()->moveRows(QModelIndex(), m_undoPos, m_count, QModelIndex(), m_redoPos);
}
};
bool LayersModel::dropMimeData(const QMimeData* data, Qt::DropAction action, bool LayersModel::dropMimeData(const QMimeData* data, Qt::DropAction action,
int row, int column, const QModelIndex& parent) int row, int column, const QModelIndex& parent)
{ {
@ -257,10 +387,75 @@ bool LayersModel::dropMimeData(const QMimeData* data, Qt::DropAction action,
dest += 1; dest += 1;
} }
moveRows(QModelIndex(), start, count, QModelIndex(), dest); g_MainWindow->pushUndoCommand(new LayerRowMoveCommand(m_node.get(),
count > 1 ? tr("Move Layers") : tr("Move Layer"),
&static_cast<LayersEditor*>(QObject::parent())->m_tableView, start, dest, count));
return true; return true;
} }
class LayerRowUndoCommand : public EditorUndoCommand
{
protected:
LayersTableView* m_view;
std::vector<std::pair<amuse::LayerMapping, int>> m_data;
bool m_undid = false;
void add()
{
m_view->selectionModel()->clearSelection();
for (const auto& p : m_data)
{
static_cast<LayersModel*>(m_view->model())->_insertRow(p.second, p.first);
m_view->selectionModel()->select(QItemSelection(
m_view->model()->index(p.second, 0), m_view->model()->index(p.second, 7)),
QItemSelectionModel::SelectCurrent);
}
}
void del()
{
for (auto it = m_data.rbegin(); it != m_data.rend(); ++it)
{
it->first = static_cast<LayersModel*>(m_view->model())->_removeRow(it->second);
}
}
void undo()
{
m_undid = true;
EditorUndoCommand::undo();
}
void redo()
{
if (m_undid)
EditorUndoCommand::redo();
}
public:
explicit LayerRowUndoCommand(ProjectModel::LayersNode* node, const QString& text, LayersTableView* view,
std::vector<std::pair<amuse::LayerMapping, int>>&& data)
: EditorUndoCommand(node, text), m_view(view), m_data(std::move(data)) {}
};
class LayerRowAddUndoCommand : public LayerRowUndoCommand
{
using base = LayerRowUndoCommand;
public:
explicit LayerRowAddUndoCommand(ProjectModel::LayersNode* node, const QString& text, LayersTableView* view,
std::vector<std::pair<amuse::LayerMapping, int>>&& data)
: LayerRowUndoCommand(node, text, view, std::move(data)) {}
void undo() { base::undo(); base::del(); }
void redo() { base::redo(); base::add(); }
};
class LayerRowDelUndoCommand : public LayerRowUndoCommand
{
using base = LayerRowUndoCommand;
public:
explicit LayerRowDelUndoCommand(ProjectModel::LayersNode* node, const QString& text, LayersTableView* view,
std::vector<std::pair<amuse::LayerMapping, int>>&& data)
: LayerRowUndoCommand(node, text, view, std::move(data)) {}
void undo() { base::undo(); base::add(); }
void redo() { base::redo(); base::del(); }
};
bool LayersModel::insertRows(int row, int count, const QModelIndex& parent) bool LayersModel::insertRows(int row, int count, const QModelIndex& parent)
{ {
if (!m_node) if (!m_node)
@ -316,15 +511,45 @@ bool LayersModel::removeRows(int row, int count, const QModelIndex& parent)
return true; return true;
} }
void LayersModel::_insertRow(int row, const amuse::LayerMapping& data)
{
if (!m_node)
return;
beginInsertRows(QModelIndex(), row, row);
std::vector<amuse::LayerMapping>& layers = *m_node->m_obj;
layers.insert(layers.begin() + row, data);
endInsertRows();
}
amuse::LayerMapping LayersModel::_removeRow(int row)
{
if (!m_node)
return {};
beginRemoveRows(QModelIndex(), row, row);
std::vector<amuse::LayerMapping>& layers = *m_node->m_obj;
amuse::LayerMapping ret = layers[row];
layers.erase(layers.begin() + row);
endRemoveRows();
return ret;
}
LayersModel::LayersModel(QObject* parent) LayersModel::LayersModel(QObject* parent)
: QAbstractTableModel(parent) : QAbstractTableModel(parent)
{} {}
void LayersTableView::deleteSelection() void LayersTableView::deleteSelection()
{ {
QModelIndexList list; QModelIndexList list = selectionModel()->selectedRows();
while (!(list = selectionModel()->selectedRows()).isEmpty()) if (list.isEmpty())
model()->removeRow(list.back().row()); return;
std::vector<std::pair<amuse::LayerMapping, int>> data;
data.reserve(list.size());
for (QModelIndex idx : list)
data.push_back(std::make_pair(amuse::LayerMapping{}, idx.row()));
std::sort(data.begin(), data.end(), [](const auto& a, const auto& b) { return a.second < b.second; });
g_MainWindow->pushUndoCommand(
new LayerRowDelUndoCommand(static_cast<LayersModel*>(model())->m_node.get(),
data.size() > 1 ? tr("Delete Layers") : tr("Delete Layer"), this, std::move(data)));
} }
void LayersTableView::setModel(QAbstractItemModel* model) void LayersTableView::setModel(QAbstractItemModel* model)
@ -396,10 +621,13 @@ void LayersEditor::resizeEvent(QResizeEvent* ev)
void LayersEditor::doAdd() void LayersEditor::doAdd()
{ {
QModelIndex idx = m_tableView.selectionModel()->currentIndex(); QModelIndex idx = m_tableView.selectionModel()->currentIndex();
std::vector<std::pair<amuse::LayerMapping, int>> data;
if (!idx.isValid()) if (!idx.isValid())
m_model.insertRow(m_model.rowCount() - 1); data.push_back(std::make_pair(amuse::LayerMapping{}, m_model.rowCount() - 1));
else else
m_model.insertRow(idx.row()); data.push_back(std::make_pair(amuse::LayerMapping{}, idx.row()));
g_MainWindow->pushUndoCommand(new LayerRowAddUndoCommand(m_model.m_node.get(),
tr("Add Layer"), &m_tableView, std::move(data)));
} }
void LayersEditor::doSelectionChanged() void LayersEditor::doSelectionChanged()

View File

@ -25,6 +25,7 @@ class LayersModel : public QAbstractTableModel
Q_OBJECT Q_OBJECT
friend class LayersEditor; friend class LayersEditor;
friend class SoundMacroDelegate; friend class SoundMacroDelegate;
friend class LayersTableView;
amuse::ObjToken<ProjectModel::LayersNode> m_node; amuse::ObjToken<ProjectModel::LayersNode> m_node;
public: public:
explicit LayersModel(QObject* parent = Q_NULLPTR); explicit LayersModel(QObject* parent = Q_NULLPTR);
@ -46,6 +47,9 @@ public:
bool moveRows(const QModelIndex& sourceParent, int sourceRow, int count, bool moveRows(const QModelIndex& sourceParent, int sourceRow, int count,
const QModelIndex& destinationParent, int destinationChild); const QModelIndex& destinationParent, int destinationChild);
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()); bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex());
void _insertRow(int row, const amuse::LayerMapping& data);
amuse::LayerMapping _removeRow(int row);
}; };
class LayersTableView : public QTableView class LayersTableView : public QTableView
@ -64,6 +68,7 @@ public:
class LayersEditor : public EditorWidget class LayersEditor : public EditorWidget
{ {
Q_OBJECT Q_OBJECT
friend class LayersModel;
LayersModel m_model; LayersModel m_model;
LayersTableView m_tableView; LayersTableView m_tableView;
AddRemoveButtons m_addRemoveButtons; AddRemoveButtons m_addRemoveButtons;

View File

@ -69,6 +69,9 @@
<property name="placeholderText"> <property name="placeholderText">
<string>Filter</string> <string>Filter</string>
</property> </property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -85,6 +88,9 @@
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="focusPolicy">
<enum>Qt::WheelFocus</enum>
</property>
<property name="animated"> <property name="animated">
<bool>true</bool> <bool>true</bool>
</property> </property>

View File

@ -269,6 +269,33 @@ Qt::ItemFlags PageObjectProxyModel::flags(const QModelIndex& proxyIndex) const
return Qt::ItemIsEnabled | Qt::ItemIsSelectable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
} }
void ProjectModel::NameUndoRegistry::registerSongName(amuse::SongId id) const
{
auto search = m_songIDs.find(id);
if (search != m_songIDs.cend())
g_MainWindow->projectModel()->_allocateSongId(id, search->second);
}
void ProjectModel::NameUndoRegistry::unregisterSongName(amuse::SongId id)
{
auto search = amuse::SongId::CurNameDB->m_idToString.find(id);
if (search != amuse::SongId::CurNameDB->m_idToString.cend())
m_songIDs[id] = search->second;
g_MainWindow->projectModel()->deallocateSongId(id);
}
void ProjectModel::NameUndoRegistry::registerSFXName(amuse::SongId id) const
{
auto search = m_sfxIDs.find(id);
if (search != m_sfxIDs.cend())
amuse::SFXId::CurNameDB->registerPair(search->second, id);
}
void ProjectModel::NameUndoRegistry::unregisterSFXName(amuse::SongId id)
{
auto search = amuse::SFXId::CurNameDB->m_idToString.find(id);
if (search != amuse::SFXId::CurNameDB->m_idToString.cend())
m_sfxIDs[id] = search->second;
amuse::SFXId::CurNameDB->remove(id);
}
ProjectModel::INode::INode(const QString& name) ProjectModel::INode::INode(const QString& name)
: m_name(name) : m_name(name)
{ {
@ -401,7 +428,27 @@ bool ProjectModel::openSongsData()
amuse::SongId id = uint16_t(strtoul(p.first.c_str(), &endPtr, 0)); amuse::SongId id = uint16_t(strtoul(p.first.c_str(), &endPtr, 0));
if (endPtr == p.first.c_str() || id.id == 0xffff) if (endPtr == p.first.c_str() || id.id == 0xffff)
continue; continue;
m_midiFiles[id] = QString::fromStdString(p.second->m_scalarString);
m_midiFiles.clear();
QString path = QString::fromStdString(p.second->m_scalarString);
m_root->oneLevelTraverse([this, id, path](INode* n)
{
GroupNode* gn = static_cast<GroupNode*>(n);
amuse::AudioGroupDatabase* db = gn->getAudioGroup();
for (const auto& p : db->getProj().songGroups())
{
for (const auto& m : p.second->m_midiSetups)
{
if (id == m.first)
{
Song& song = m_midiFiles[id];
song.m_path = path;
++song.m_refCount;
}
}
}
return true;
});
} }
} }
} }
@ -489,7 +536,7 @@ bool ProjectModel::saveToFile(UIMessenger& messenger)
{ {
char id[16]; char id[16];
snprintf(id, 16, "%04X", p.first.id); snprintf(id, 16, "%04X", p.first.id);
dw.writeString(id, p.second.toUtf8().data()); dw.writeString(id, p.second.m_path.toUtf8().data());
} }
athena::io::FileWriter w(QStringToSysString(songsFile.path())); athena::io::FileWriter w(QStringToSysString(songsFile.path()));
if (!w.hasError()) if (!w.hasError())
@ -1182,37 +1229,77 @@ ProjectModel::GroupNode* ProjectModel::getGroupOfSfx(amuse::SFXId id) const
return ret; return ret;
} }
ProjectModel::GroupNode* ProjectModel::getGroupOfSong(amuse::SongId id) const
{
ProjectModel::GroupNode* ret = nullptr;
m_root->oneLevelTraverse([id, &ret](INode* n)
{
GroupNode* gn = static_cast<GroupNode*>(n);
amuse::AudioGroupDatabase* db = gn->getAudioGroup();
for (const auto& p : db->getProj().songGroups())
{
if (p.second->m_midiSetups.find(id) != p.second->m_midiSetups.cend())
{
ret = gn;
return false;
}
}
return true;
});
return ret;
}
QString ProjectModel::getMIDIPathOfSong(amuse::SongId id) const QString ProjectModel::getMIDIPathOfSong(amuse::SongId id) const
{ {
auto search = m_midiFiles.find(id); auto search = m_midiFiles.find(id);
if (search == m_midiFiles.cend()) if (search == m_midiFiles.cend())
return {}; return {};
return search->second; return search->second.m_path;
} }
void ProjectModel::setMIDIPathOfSong(amuse::SongId id, const QString& path) void ProjectModel::setMIDIPathOfSong(amuse::SongId id, const QString& path)
{ {
m_midiFiles[id] = path; m_midiFiles[id].m_path = path;
}
void ProjectModel::_allocateSongId(amuse::SongId id, std::string_view name)
{
m_projectDatabase.setIdDatabases();
amuse::SongId::CurNameDB->registerPair(name, id);
Song& song = m_midiFiles[id];
++song.m_refCount;
}
std::pair<amuse::SongId, std::string> ProjectModel::allocateSongId()
{
m_projectDatabase.setIdDatabases();
std::pair<amuse::SongId, std::string> ret;
ret.first = amuse::SongId::CurNameDB->generateId(amuse::NameDB::Type::Song);
ret.second = amuse::SongId::CurNameDB->generateName(ret.first, amuse::NameDB::Type::Song);
amuse::SongId::CurNameDB->registerPair(ret.second, ret.first);
m_midiFiles[ret.first] = {{}, 1};
return ret;
}
void ProjectModel::deallocateSongId(amuse::SongId oldId)
{
Song& oldSong = m_midiFiles[oldId];
--oldSong.m_refCount;
if (oldSong.m_refCount <= 0)
{
oldSong.m_refCount = 0;
amuse::SongId::CurNameDB->remove(oldId);
m_midiFiles.erase(oldId);
}
}
amuse::SongId ProjectModel::exchangeSongId(amuse::SongId oldId, std::string_view newName)
{
m_projectDatabase.setIdDatabases();
amuse::SongId newId;
auto search = amuse::SongId::CurNameDB->m_stringToId.find(newName.data());
if (search == amuse::SongId::CurNameDB->m_stringToId.cend())
{
newId = amuse::SongId::CurNameDB->generateId(amuse::NameDB::Type::Song);
amuse::SongId::CurNameDB->registerPair(newName, newId);
}
else
newId = search->second;
if (oldId == newId)
return newId;
Song& oldSong = m_midiFiles[oldId];
Song& newSong = m_midiFiles[newId];
++newSong.m_refCount;
if (newSong.m_path.isEmpty())
newSong.m_path = oldSong.m_path;
--oldSong.m_refCount;
if (oldSong.m_refCount <= 0)
{
oldSong.m_refCount = 0;
amuse::SongId::CurNameDB->remove(oldId);
m_midiFiles.erase(oldId);
}
return newId;
} }
void ProjectModel::setIdDatabases(INode* context) const void ProjectModel::setIdDatabases(INode* context) const

View File

@ -57,32 +57,10 @@ public:
{ {
std::unordered_map<amuse::SongId, std::string> m_songIDs; std::unordered_map<amuse::SongId, std::string> m_songIDs;
std::unordered_map<amuse::SFXId, std::string> m_sfxIDs; std::unordered_map<amuse::SFXId, std::string> m_sfxIDs;
void registerSongName(amuse::SongId id) const void registerSongName(amuse::SongId id) const;
{ void unregisterSongName(amuse::SongId id);
auto search = m_songIDs.find(id); void registerSFXName(amuse::SongId id) const;
if (search != m_songIDs.cend()) void unregisterSFXName(amuse::SongId id);
amuse::SongId::CurNameDB->registerPair(search->second, id);
}
void unregisterSongName(amuse::SongId id)
{
auto search = amuse::SongId::CurNameDB->m_idToString.find(id);
if (search != amuse::SongId::CurNameDB->m_idToString.cend())
m_songIDs[id] = search->second;
amuse::SongId::CurNameDB->remove(id);
}
void registerSFXName(amuse::SongId id) const
{
auto search = m_sfxIDs.find(id);
if (search != m_sfxIDs.cend())
amuse::SFXId::CurNameDB->registerPair(search->second, id);
}
void unregisterSFXName(amuse::SongId id)
{
auto search = amuse::SFXId::CurNameDB->m_idToString.find(id);
if (search != amuse::SFXId::CurNameDB->m_idToString.cend())
m_sfxIDs[id] = search->second;
amuse::SFXId::CurNameDB->remove(id);
}
void clear() void clear()
{ {
m_songIDs.clear(); m_songIDs.clear();
@ -98,7 +76,12 @@ private:
amuse::ProjectDatabase m_projectDatabase; amuse::ProjectDatabase m_projectDatabase;
std::unordered_map<QString, amuse::AudioGroupDatabase> m_groups; std::unordered_map<QString, amuse::AudioGroupDatabase> m_groups;
std::unordered_map<amuse::SongId, QString> m_midiFiles; struct Song
{
QString m_path;
int m_refCount = 0;
};
std::unordered_map<amuse::SongId, Song> m_midiFiles;
public: public:
class INode : public amuse::IObj class INode : public amuse::IObj
@ -436,9 +419,12 @@ public:
PageObjectProxyModel* getPageObjectProxy() { return &m_pageObjectProxy; } PageObjectProxyModel* getPageObjectProxy() { return &m_pageObjectProxy; }
GroupNode* getGroupOfSfx(amuse::SFXId id) const; GroupNode* getGroupOfSfx(amuse::SFXId id) const;
GroupNode* getGroupOfSong(amuse::SongId id) const;
QString getMIDIPathOfSong(amuse::SongId id) const; QString getMIDIPathOfSong(amuse::SongId id) const;
void setMIDIPathOfSong(amuse::SongId id, const QString& path); void setMIDIPathOfSong(amuse::SongId id, const QString& path);
void _allocateSongId(amuse::SongId id, std::string_view name);
std::pair<amuse::SongId, std::string> allocateSongId();
void deallocateSongId(amuse::SongId oldId);
amuse::SongId exchangeSongId(amuse::SongId oldId, std::string_view newName);
void setIdDatabases(INode* context) const; void setIdDatabases(INode* context) const;
}; };

View File

@ -1,7 +0,0 @@
#include "ProjectStatistics.hpp"
ProjectStatistics::ProjectStatistics(QObject* parent)
: QAbstractTableModel(parent)
{
}

View File

@ -1,14 +0,0 @@
#ifndef AMUSE_PROJECT_STATISTICS_HPP
#define AMUSE_PROJECT_STATISTICS_HPP
#include <QAbstractTableModel>
class ProjectStatistics : public QAbstractTableModel
{
Q_OBJECT
public:
explicit ProjectStatistics(QObject* parent = Q_NULLPTR);
};
#endif //AMUSE_PROJECT_STATISTICS_HPP

View File

@ -172,7 +172,17 @@ void SampleView::paintEvent(QPaintEvent* ev)
qreal scale = -sampleHeight / 2.0; qreal scale = -sampleHeight / 2.0;
qreal trans = sampleHeight / 2.0; qreal trans = sampleHeight / 2.0;
seekToSample(startSample); std::pair<std::pair<qreal, qreal>, std::pair<qreal, qreal>> lastAvgPeak;
if (startSample >= deviceSamplesPerPx)
{
seekToSample(startSample - deviceSamplesPerPx);
lastAvgPeak = iterateSampleInterval(deviceSamplesPerPx);
}
else
{
seekToSample(startSample);
lastAvgPeak = std::pair<std::pair<qreal, qreal>, std::pair<qreal, qreal>>{};
}
std::pair<std::pair<qreal, qreal>, std::pair<qreal, qreal>> avgPeak; std::pair<std::pair<qreal, qreal>, std::pair<qreal, qreal>> avgPeak;
for (qreal i = 0.0; i < deviceWidth; i += increment) for (qreal i = 0.0; i < deviceWidth; i += increment)
@ -183,12 +193,18 @@ void SampleView::paintEvent(QPaintEvent* ev)
avgPeak = iterateSampleInterval(deviceSamplesPerPx); avgPeak = iterateSampleInterval(deviceSamplesPerPx);
else else
m_curSamplePos += deviceSamplesPerPx; m_curSamplePos += deviceSamplesPerPx;
std::pair<std::pair<qreal, qreal>, std::pair<qreal, qreal>> drawAvgPeak = avgPeak;
if (lastAvgPeak.first.first > avgPeak.second.first)
drawAvgPeak.second.first = lastAvgPeak.first.first;
else if (lastAvgPeak.second.first < avgPeak.first.first)
drawAvgPeak.first.first = lastAvgPeak.second.first;
painter.setPen(peakPen); painter.setPen(peakPen);
painter.drawLine(QPointF(rectStart + i, avgPeak.first.second * scale + trans), painter.drawLine(QPointF(rectStart + i, drawAvgPeak.first.second * scale + trans),
QPointF(rectStart + i, avgPeak.second.second * scale + trans)); QPointF(rectStart + i, drawAvgPeak.second.second * scale + trans));
painter.setPen(avgPen); painter.setPen(avgPen);
painter.drawLine(QPointF(rectStart + i, avgPeak.first.first * scale + trans), painter.drawLine(QPointF(rectStart + i, drawAvgPeak.first.first * scale + trans),
QPointF(rectStart + i, avgPeak.second.first * scale + trans)); QPointF(rectStart + i, drawAvgPeak.second.first * scale + trans));
lastAvgPeak = avgPeak;
} }
} }

View File

@ -2,6 +2,232 @@
#include "MainWindow.hpp" #include "MainWindow.hpp"
#include "amuse/SongConverter.hpp" #include "amuse/SongConverter.hpp"
class PageDataChangeUndoCommand : public EditorUndoCommand
{
bool m_drum;
uint8_t m_prog;
int m_column;
int m_undoVal, m_redoVal;
bool m_undid = false;
public:
explicit PageDataChangeUndoCommand(ProjectModel::SongGroupNode* node, const QString& text,
bool drum, uint8_t prog, int column, int redoVal)
: EditorUndoCommand(node, text), m_drum(drum), m_prog(prog), m_column(column), m_redoVal(redoVal) {}
void undo()
{
m_undid = true;
amuse::SongGroupIndex& index = *static_cast<ProjectModel::SongGroupNode*>(m_node.get())->m_index;
auto& map = m_drum ? index.m_drumPages : index.m_normPages;
amuse::SongGroupIndex::PageEntry& entry = map[m_prog];
switch (m_column)
{
case 0:
{
auto nh = map.extract(m_prog);
nh.key() = m_undoVal;
m_prog = m_undoVal;
map.insert(std::move(nh));
break;
}
case 1:
entry.objId.id = m_undoVal;
break;
case 2:
entry.priority = m_undoVal;
break;
case 3:
entry.maxVoices = m_undoVal;
break;
default:
break;
}
EditorUndoCommand::undo();
}
void redo()
{
amuse::SongGroupIndex& index = *static_cast<ProjectModel::SongGroupNode*>(m_node.get())->m_index;
auto& map = m_drum ? index.m_drumPages : index.m_normPages;
amuse::SongGroupIndex::PageEntry& entry = map[m_prog];
switch (m_column)
{
case 0:
{
auto nh = map.extract(m_prog);
m_undoVal = m_prog;
nh.key() = m_redoVal;
m_prog = m_redoVal;
map.insert(std::move(nh));
break;
}
case 1:
m_undoVal = entry.objId.id;
entry.objId.id = m_redoVal;
break;
case 2:
m_undoVal = entry.priority;
entry.priority = m_redoVal;
break;
case 3:
m_undoVal = entry.maxVoices;
entry.maxVoices = m_redoVal;
break;
default:
break;
}
if (m_undid)
EditorUndoCommand::redo();
}
};
class SetupDataChangeUndoCommand : public EditorUndoCommand
{
amuse::SongId m_song;
int m_row, m_column;
int m_undoVal, m_redoVal;
bool m_undid = false;
public:
explicit SetupDataChangeUndoCommand(ProjectModel::SongGroupNode* node, const QString& text,
amuse::SongId song, int row, int column, int redoVal)
: EditorUndoCommand(node, text), m_song(song), m_row(row), m_column(column), m_redoVal(redoVal) {}
void undo()
{
m_undid = true;
amuse::SongGroupIndex& index = *static_cast<ProjectModel::SongGroupNode*>(m_node.get())->m_index;
auto& map = index.m_midiSetups;
std::array<amuse::SongGroupIndex::MIDISetup, 16>& entry = map[m_song];
switch (m_column)
{
case 0:
entry[m_row].programNo = m_undoVal;
break;
case 2:
entry[m_row].volume = m_undoVal;
break;
case 3:
entry[m_row].panning = m_undoVal;
break;
case 4:
entry[m_row].reverb = m_undoVal;
break;
case 5:
entry[m_row].chorus = m_undoVal;
break;
default:
break;
}
EditorUndoCommand::undo();
}
void redo()
{
amuse::SongGroupIndex& index = *static_cast<ProjectModel::SongGroupNode*>(m_node.get())->m_index;
auto& map = index.m_midiSetups;
std::array<amuse::SongGroupIndex::MIDISetup, 16>& entry = map[m_song];
switch (m_column)
{
case 0:
m_undoVal = entry[m_row].programNo;
entry[m_row].programNo = m_redoVal;
break;
case 2:
m_undoVal = entry[m_row].volume;
entry[m_row].volume = m_redoVal;
break;
case 3:
m_undoVal = entry[m_row].panning;
entry[m_row].panning = m_redoVal;
break;
case 4:
m_undoVal = entry[m_row].reverb;
entry[m_row].reverb = m_redoVal;
break;
case 5:
m_undoVal = entry[m_row].chorus;
entry[m_row].chorus = m_redoVal;
break;
default:
break;
}
if (m_undid)
EditorUndoCommand::redo();
}
};
class SongNameChangeUndoCommand : public EditorUndoCommand
{
amuse::SongId m_song;
std::string m_undoVal, m_redoVal;
bool m_undid = false;
public:
explicit SongNameChangeUndoCommand(ProjectModel::SongGroupNode* node, const QString& text,
amuse::SongId song, std::string_view redoVal)
: EditorUndoCommand(node, text), m_song(song), m_redoVal(redoVal) {}
void undo()
{
m_undid = true;
g_MainWindow->projectModel()->setIdDatabases(m_node.get());
amuse::SongGroupIndex& index = *static_cast<ProjectModel::SongGroupNode*>(m_node.get())->m_index;
auto& map = index.m_midiSetups;
amuse::SongId newId = g_MainWindow->projectModel()->exchangeSongId(m_song, m_undoVal);
auto nh = map.extract(m_song);
nh.key() = newId;
m_song = newId;
map.insert(std::move(nh));
EditorUndoCommand::undo();
}
void redo()
{
g_MainWindow->projectModel()->setIdDatabases(m_node.get());
amuse::SongGroupIndex& index = *static_cast<ProjectModel::SongGroupNode*>(m_node.get())->m_index;
auto& map = index.m_midiSetups;
m_undoVal = amuse::SongId::CurNameDB->resolveNameFromId(m_song);
amuse::SongId newId = g_MainWindow->projectModel()->exchangeSongId(m_song, m_redoVal);
auto nh = map.extract(m_song);
nh.key() = newId;
m_song = newId;
map.insert(std::move(nh));
if (m_undid)
EditorUndoCommand::redo();
}
};
class SongMIDIPathChangeUndoCommand : public EditorUndoCommand
{
amuse::SongId m_song;
QString m_undoVal, m_redoVal;
bool m_undid = false;
public:
explicit SongMIDIPathChangeUndoCommand(ProjectModel::SongGroupNode* node, const QString& text,
amuse::SongId song, QString redoVal)
: EditorUndoCommand(node, text), m_song(song), m_redoVal(redoVal) {}
void undo()
{
m_undid = true;
g_MainWindow->projectModel()->setMIDIPathOfSong(m_song, m_undoVal);
EditorUndoCommand::undo();
}
void redo()
{
m_undoVal = g_MainWindow->projectModel()->getMIDIPathOfSong(m_song);
g_MainWindow->projectModel()->setMIDIPathOfSong(m_song, m_redoVal);
if (m_undid)
EditorUndoCommand::redo();
}
};
PageObjectDelegate::PageObjectDelegate(QObject* parent) PageObjectDelegate::PageObjectDelegate(QObject* parent)
: QStyledItemDelegate(parent) {} : QStyledItemDelegate(parent) {}
@ -33,19 +259,23 @@ void PageObjectDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, co
const PageModel* model = static_cast<const PageModel*>(m); const PageModel* model = static_cast<const PageModel*>(m);
auto entry = model->m_sorted[index.row()]; auto entry = model->m_sorted[index.row()];
int idx = static_cast<EditorFieldPageObjectNode*>(editor)->currentIndex(); int idx = static_cast<EditorFieldPageObjectNode*>(editor)->currentIndex();
if (idx == 0) amuse::ObjectId id;
{ if (idx != 0)
entry->second.objId.id = amuse::ObjectId();
}
else
{ {
ProjectModel::BasePoolObjectNode* node = static_cast<ProjectModel::BasePoolObjectNode*>( ProjectModel::BasePoolObjectNode* node = static_cast<ProjectModel::BasePoolObjectNode*>(
g_MainWindow->projectModel()->node( g_MainWindow->projectModel()->node(
g_MainWindow->projectModel()->getPageObjectProxy()->mapToSource( g_MainWindow->projectModel()->getPageObjectProxy()->mapToSource(
g_MainWindow->projectModel()->getPageObjectProxy()->index(idx, 0, g_MainWindow->projectModel()->getPageObjectProxy()->index(idx, 0,
static_cast<EditorFieldPageObjectNode*>(editor)->rootModelIndex())))); static_cast<EditorFieldPageObjectNode*>(editor)->rootModelIndex()))));
entry->second.objId.id = node->id(); id = node->id();
} }
if (id == entry->second.objId.id)
{
emit m->dataChanged(index, index);
return;
}
g_MainWindow->pushUndoCommand(new PageDataChangeUndoCommand(model->m_node.get(),
tr("Change %1").arg(m->headerData(1, Qt::Horizontal).toString()), model->m_drum, entry->first, 1, id.id));
emit m->dataChanged(index, index); emit m->dataChanged(index, index);
} }
@ -106,7 +336,10 @@ void MIDIFileDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, cons
MIDIFileFieldWidget* widget = static_cast<MIDIFileFieldWidget*>(editor); MIDIFileFieldWidget* widget = static_cast<MIDIFileFieldWidget*>(editor);
const SetupListModel* model = static_cast<const SetupListModel*>(index.model()); const SetupListModel* model = static_cast<const SetupListModel*>(index.model());
auto entry = model->m_sorted[index.row()]; auto entry = model->m_sorted[index.row()];
g_MainWindow->projectModel()->setMIDIPathOfSong(entry->first, widget->path()); if (g_MainWindow->projectModel()->getMIDIPathOfSong(entry->first) == widget->path())
return;
g_MainWindow->pushUndoCommand(new SongMIDIPathChangeUndoCommand(model->m_node.get(),
tr("Change MIDI Path"), entry->first, widget->path()));
emit m->dataChanged(index, index); emit m->dataChanged(index, index);
} }
@ -237,9 +470,10 @@ bool PageModel::setData(const QModelIndex& index, const QVariant& value, int rol
return false; return false;
} }
emit layoutAboutToBeChanged(); emit layoutAboutToBeChanged();
auto nh = map.extract(entry->first);
nh.key() = value.toInt(); g_MainWindow->pushUndoCommand(new PageDataChangeUndoCommand(m_node.get(),
map.insert(std::move(nh)); tr("Change %1").arg(headerData(0, Qt::Horizontal).toString()), m_drum, entry->first, 0, value.toInt()));
_buildSortedList(); _buildSortedList();
QModelIndex newIndex = _indexOfProgram(value.toInt()); QModelIndex newIndex = _indexOfProgram(value.toInt());
changePersistentIndex(index, newIndex); changePersistentIndex(index, newIndex);
@ -247,19 +481,28 @@ bool PageModel::setData(const QModelIndex& index, const QVariant& value, int rol
emit dataChanged(newIndex, newIndex); emit dataChanged(newIndex, newIndex);
return true; return true;
} }
case 2: case 1:
entry->second.priority = value.toInt(); if (entry->second.objId.id == value.toInt())
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); return false;
return true;
case 3:
entry->second.maxVoices = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
default:
break; break;
case 2:
if (entry->second.priority == value.toInt())
return false;
break;
case 3:
if (entry->second.maxVoices == value.toInt())
return false;
break;
default:
return false;
} }
return false; g_MainWindow->pushUndoCommand(new PageDataChangeUndoCommand(m_node.get(),
tr("Change %1").arg(headerData(index.column(), Qt::Horizontal).toString()), m_drum,
entry->first, index.column(), value.toInt()));
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
} }
QVariant PageModel::headerData(int section, Qt::Orientation orientation, int role) const QVariant PageModel::headerData(int section, Qt::Orientation orientation, int role) const
@ -292,64 +535,99 @@ Qt::ItemFlags PageModel::flags(const QModelIndex& index) const
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
} }
bool PageModel::insertRows(int row, int count, const QModelIndex& parent) class PageRowUndoCommand : public EditorUndoCommand
{
protected:
PageTableView* m_view;
std::vector<std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>> m_data;
bool m_undid = false;
void add()
{
m_view->selectionModel()->clearSelection();
for (const auto& p : m_data)
{
int row = static_cast<PageModel*>(m_view->model())->_insertRow(p);
m_view->selectionModel()->select(QItemSelection(
m_view->model()->index(row, 0), m_view->model()->index(row, 3)),
QItemSelectionModel::SelectCurrent);
}
}
void del()
{
for (auto it = m_data.rbegin(); it != m_data.rend(); ++it)
{
*it = static_cast<PageModel*>(m_view->model())->_removeRow(it->first);
}
}
void undo()
{
m_undid = true;
EditorUndoCommand::undo();
}
void redo()
{
if (m_undid)
EditorUndoCommand::redo();
}
public:
explicit PageRowUndoCommand(ProjectModel::SongGroupNode* node, const QString& text, PageTableView* view,
std::vector<std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>>&& data)
: EditorUndoCommand(node, text), m_view(view), m_data(std::move(data)) {}
};
class PageRowAddUndoCommand : public PageRowUndoCommand
{
using base = PageRowUndoCommand;
public:
explicit PageRowAddUndoCommand(ProjectModel::SongGroupNode* node, const QString& text, PageTableView* view,
std::vector<std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>>&& data)
: PageRowUndoCommand(node, text, view, std::move(data)) {}
void undo() { base::undo(); base::del(); }
void redo() { base::redo(); base::add(); }
};
class PageRowDelUndoCommand : public PageRowUndoCommand
{
using base = PageRowUndoCommand;
public:
explicit PageRowDelUndoCommand(ProjectModel::SongGroupNode* node, const QString& text, PageTableView* view,
std::vector<std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>>&& data)
: PageRowUndoCommand(node, text, view, std::move(data)) {}
void undo() { base::undo(); base::add(); }
void redo() { base::redo(); base::del(); }
};
int PageModel::_insertRow(const std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>& data)
{ {
if (!m_node) if (!m_node)
return false; return 0;
if (m_sorted.size() >= 128)
return false;
auto& map = _getMap(); auto& map = _getMap();
if (m_sorted.empty()) int idx = _hypotheticalIndexOfProgram(data.first);
{ beginInsertRows(QModelIndex(), idx, idx);
beginInsertRows(parent, 0, count - 1); map.emplace(data);
for (int i = 0; i < count; ++i) _buildSortedList();
map.emplace(std::make_pair(i, amuse::SongGroupIndex::PageEntry{})); endInsertRows();
_buildSortedList(); return idx;
endInsertRows();
return true;
}
for (int i = 0; i < count; ++i)
{
int prog = -1;
if (row < m_sorted.size())
{
prog = m_sorted[row].m_it->first;
while (prog >= 0 && _indexOfProgram(prog).isValid())
--prog;
}
if (prog == -1)
{
prog = 0;
while (prog < 128 && _indexOfProgram(prog).isValid())
++prog;
}
if (prog == 128)
return true;
int insertIdx = _hypotheticalIndexOfProgram(prog);
beginInsertRows(parent, insertIdx, insertIdx);
map.emplace(std::make_pair(prog, amuse::SongGroupIndex::PageEntry{}));
_buildSortedList();
endInsertRows();
++row;
}
return true;
} }
bool PageModel::removeRows(int row, int count, const QModelIndex& parent) std::pair<uint8_t, amuse::SongGroupIndex::PageEntry> PageModel::_removeRow(uint8_t prog)
{ {
std::pair<uint8_t, amuse::SongGroupIndex::PageEntry> ret;
if (!m_node) if (!m_node)
return false; return ret;
auto& map = _getMap(); auto& map = _getMap();
beginRemoveRows(parent, row, row + count - 1); int idx = _hypotheticalIndexOfProgram(prog);
std::vector<uint8_t> removeProgs; beginRemoveRows(QModelIndex(), idx, idx);
removeProgs.reserve(count); ret.first = prog;
for (int i = 0; i < count; ++i) auto search = map.find(prog);
removeProgs.push_back(m_sorted[row+i].m_it->first); if (search != map.cend())
for (uint8_t prog : removeProgs) {
map.erase(prog); ret.second = search->second;
map.erase(search);
}
_buildSortedList(); _buildSortedList();
endRemoveRows(); endRemoveRows();
return true; return ret;
} }
PageModel::PageModel(bool drum, QObject* parent) PageModel::PageModel(bool drum, QObject* parent)
@ -392,7 +670,7 @@ void SetupListModel::loadData(ProjectModel::SongGroupNode* node)
{ {
beginResetModel(); beginResetModel();
m_node = node; m_node = node;
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases(); g_MainWindow->projectModel()->setIdDatabases(m_node.get());
_buildSortedList(); _buildSortedList();
endResetModel(); endResetModel();
} }
@ -434,7 +712,7 @@ QVariant SetupListModel::data(const QModelIndex& index, int role) const
{ {
if (index.column() == 0) if (index.column() == 0)
{ {
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases(); g_MainWindow->projectModel()->setIdDatabases(m_node.get());
return amuse::SongId::CurNameDB->resolveNameFromId(entry->first.id).data(); return amuse::SongId::CurNameDB->resolveNameFromId(entry->first.id).data();
} }
else if (index.column() == 1) else if (index.column() == 1)
@ -451,10 +729,9 @@ bool SetupListModel::setData(const QModelIndex& index, const QVariant& value, in
if (!m_node || role != Qt::EditRole || index.column() != 0) if (!m_node || role != Qt::EditRole || index.column() != 0)
return false; return false;
auto& map = _getMap();
auto entry = m_sorted[index.row()]; auto entry = m_sorted[index.row()];
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases(); g_MainWindow->projectModel()->setIdDatabases(m_node.get());
auto utf8key = value.toString().toUtf8(); auto utf8key = value.toString().toUtf8();
std::unordered_map<std::string, amuse::ObjectId>::iterator idIt; std::unordered_map<std::string, amuse::ObjectId>::iterator idIt;
if ((idIt = amuse::SongId::CurNameDB->m_stringToId.find(utf8key.data())) != amuse::SongId::CurNameDB->m_stringToId.cend()) if ((idIt = amuse::SongId::CurNameDB->m_stringToId.find(utf8key.data())) != amuse::SongId::CurNameDB->m_stringToId.cend())
@ -466,7 +743,8 @@ bool SetupListModel::setData(const QModelIndex& index, const QVariant& value, in
return false; return false;
} }
emit layoutAboutToBeChanged(); emit layoutAboutToBeChanged();
amuse::SongId::CurNameDB->rename(entry.m_it->first, utf8key.data()); g_MainWindow->pushUndoCommand(new SongNameChangeUndoCommand(m_node.get(),
tr("Change Song Name"), entry.m_it->first, utf8key.data()));
_buildSortedList(); _buildSortedList();
QModelIndex newIndex = _indexOfSong(entry.m_it->first); QModelIndex newIndex = _indexOfSong(entry.m_it->first);
changePersistentIndex(index, newIndex); changePersistentIndex(index, newIndex);
@ -500,46 +778,112 @@ Qt::ItemFlags SetupListModel::flags(const QModelIndex& index) const
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
} }
bool SetupListModel::insertRows(int row, int count, const QModelIndex& parent) class SetupRowUndoCommand : public EditorUndoCommand
{
protected:
SetupTableView* m_view;
std::vector<std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>> m_data;
bool m_undid = false;
void add()
{
QTableView* listView = m_view->m_listView;
listView->selectionModel()->clearSelection();
for (auto& p : m_data)
{
int row = static_cast<SetupListModel*>(m_view->m_listView->model())->_insertRow(p);
listView->selectionModel()->select(QItemSelection(
listView->model()->index(row, 0), listView->model()->index(row, 1)),
QItemSelectionModel::SelectCurrent);
}
}
void del()
{
QTableView* listView = m_view->m_listView;
for (auto it = m_data.rbegin(); it != m_data.rend(); ++it)
{
*it = static_cast<SetupListModel*>(listView->model())->_removeRow(std::get<0>(*it));
}
}
void undo()
{
m_undid = true;
EditorUndoCommand::undo();
}
void redo()
{
if (m_undid)
EditorUndoCommand::redo();
}
public:
explicit SetupRowUndoCommand(ProjectModel::SongGroupNode* node, const QString& text, SetupTableView* view,
std::vector<std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>>&& data)
: EditorUndoCommand(node, text), m_view(view), m_data(std::move(data)) {}
};
class SetupRowAddUndoCommand : public SetupRowUndoCommand
{
using base = SetupRowUndoCommand;
public:
explicit SetupRowAddUndoCommand(ProjectModel::SongGroupNode* node, const QString& text, SetupTableView* view,
std::vector<std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>>&& data)
: SetupRowUndoCommand(node, text, view, std::move(data)) {}
void undo() { base::undo(); base::del(); }
void redo() { base::redo(); base::add(); }
};
class SetupRowDelUndoCommand : public SetupRowUndoCommand
{
using base = SetupRowUndoCommand;
public:
explicit SetupRowDelUndoCommand(ProjectModel::SongGroupNode* node, const QString& text, SetupTableView* view,
std::vector<std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>>&& data)
: SetupRowUndoCommand(node, text, view, std::move(data)) {}
void undo() { base::undo(); base::add(); }
void redo() { base::redo(); base::del(); }
};
int SetupListModel::_insertRow(
std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>& data)
{ {
if (!m_node) if (!m_node)
return false; return 0;
auto& map = _getMap(); auto& map = _getMap();
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases(); g_MainWindow->projectModel()->setIdDatabases(m_node.get());
for (int i = 0; i < count; ++i) if (std::get<0>(data).id == 0xffff)
{ std::get<0>(data) = amuse::SongId::CurNameDB->generateId(amuse::NameDB::Type::Song);
amuse::ObjectId songId = amuse::SongId::CurNameDB->generateId(amuse::NameDB::Type::Song);
std::string songName = amuse::SongId::CurNameDB->generateName(songId, amuse::NameDB::Type::Song); g_MainWindow->projectModel()->_allocateSongId(std::get<0>(data), std::get<1>(data));
int insertIdx = _hypotheticalIndexOfSong(songName); int idx = _hypotheticalIndexOfSong(std::get<1>(data));
beginInsertRows(parent, insertIdx, insertIdx); beginInsertRows(QModelIndex(), idx, idx);
amuse::SongId::CurNameDB->registerPair(songName, songId); map[std::get<0>(data)] = std::get<2>(data);
map.emplace(std::make_pair(songId, std::array<amuse::SongGroupIndex::MIDISetup, 16>{})); _buildSortedList();
_buildSortedList(); endInsertRows();
endInsertRows(); return idx;
++row;
}
return true;
} }
bool SetupListModel::removeRows(int row, int count, const QModelIndex& parent) std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>
SetupListModel::_removeRow(amuse::SongId id)
{ {
std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>> ret = {};
if (!m_node) if (!m_node)
return false; return ret;
auto& map = _getMap(); auto& map = _getMap();
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases(); g_MainWindow->projectModel()->setIdDatabases(m_node.get());
beginRemoveRows(parent, row, row + count - 1); std::get<0>(ret) = id;
std::vector<amuse::SongId> removeSongs; std::get<1>(ret) = amuse::SongId::CurNameDB->resolveNameFromId(id);
removeSongs.reserve(count); int idx = _indexOfSong(id).row();
for (int i = 0; i < count; ++i) beginRemoveRows(QModelIndex(), idx, idx);
removeSongs.push_back(m_sorted[row+i].m_it->first); auto search = map.find(id);
for (amuse::SongId song : removeSongs) if (search != map.end())
{ {
amuse::SongId::CurNameDB->remove(song); std::get<2>(ret) = search->second;
map.erase(song); g_MainWindow->projectModel()->deallocateSongId(id);
map.erase(search);
} }
_buildSortedList(); _buildSortedList();
endRemoveRows(); endRemoveRows();
return true; return ret;
} }
SetupListModel::SetupListModel(QObject* parent) SetupListModel::SetupListModel(QObject* parent)
@ -615,30 +959,37 @@ bool SetupModel::setData(const QModelIndex& index, const QVariant& value, int ro
switch (index.column()) switch (index.column())
{ {
case 0: case 0:
entry.programNo = value.toInt(); if (entry.programNo == value.toInt())
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); return false;
return true;
case 1:
entry.volume = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 2:
entry.panning = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 3:
entry.reverb = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 4:
entry.chorus = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
default:
break; break;
case 1:
if (entry.volume == value.toInt())
return false;
break;
case 2:
if (entry.panning == value.toInt())
return false;
break;
case 3:
if (entry.reverb == value.toInt())
return false;
break;
case 4:
if (entry.chorus == value.toInt())
return false;
break;
default:
return false;
} }
return false; 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()));
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
} }
QVariant SetupModel::headerData(int section, Qt::Orientation orientation, int role) const QVariant SetupModel::headerData(int section, Qt::Orientation orientation, int role) const
@ -688,9 +1039,17 @@ SetupModel::SetupModel(QObject* parent)
void PageTableView::deleteSelection() void PageTableView::deleteSelection()
{ {
QModelIndexList list; QModelIndexList list = selectionModel()->selectedRows();
while (!(list = selectionModel()->selectedRows()).isEmpty()) if (list.isEmpty())
model()->removeRow(list.back().row()); return;
std::vector<std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>> data;
data.reserve(list.size());
for (QModelIndex idx : list)
data.push_back(std::make_pair(model()->data(idx).toInt(), amuse::SongGroupIndex::PageEntry{}));
std::sort(data.begin(), data.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
g_MainWindow->pushUndoCommand(
new PageRowDelUndoCommand(static_cast<PageModel*>(model())->m_node.get(),
data.size() > 1 ? tr("Delete Page Entries") : tr("Delete Page Entry"), this, std::move(data)));
} }
void PageTableView::setModel(QAbstractItemModel* model) void PageTableView::setModel(QAbstractItemModel* model)
@ -740,9 +1099,20 @@ void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* tabl
void SetupTableView::deleteSelection() void SetupTableView::deleteSelection()
{ {
QModelIndexList list; QModelIndexList list = m_listView->selectionModel()->selectedRows();
while (!(list = m_listView->selectionModel()->selectedRows()).isEmpty()) if (list.isEmpty())
m_listView->model()->removeRow(list.back().row()); return;
std::sort(list.begin(), list.end(), [](const auto& a, const auto& b) { return a.row() < b.row(); });
std::vector<std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>> data;
data.reserve(list.size());
for (QModelIndex idx : list)
{
auto& entry = *static_cast<SetupListModel*>(m_listView->model())->m_sorted[idx.row()].m_it;
data.push_back({entry.first, {}, {}});
}
g_MainWindow->pushUndoCommand(
new SetupRowDelUndoCommand(static_cast<SetupListModel*>(m_listView->model())->m_node.get(),
data.size() > 1 ? tr("Delete Setup Entries") : tr("Delete Setup Entry"), this, std::move(data)));
} }
void SetupTableView::showEvent(QShowEvent* event) void SetupTableView::showEvent(QShowEvent* event)
@ -898,20 +1268,49 @@ void SongGroupEditor::doAdd()
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget())) if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
{ {
QModelIndex idx = table->selectionModel()->currentIndex(); QModelIndex idx = table->selectionModel()->currentIndex();
int row;
if (!idx.isValid()) if (!idx.isValid())
table->model()->insertRow(table->model()->rowCount() - 1); row = table->model()->rowCount() - 1;
else else
table->model()->insertRow(idx.row()); row = idx.row();
if (PageTableView* ctable = qobject_cast<PageTableView*>(table))
m_addRemoveButtons.addAction()->setDisabled(ctable->model()->rowCount() >= 128); PageModel* model = static_cast<PageModel*>(table->model());
int prog = 0;
if (!model->m_sorted.empty())
{
prog = -1;
if (row < model->m_sorted.size())
{
prog = model->m_sorted[row].m_it->first;
while (prog >= 0 && model->_indexOfProgram(prog).isValid())
--prog;
}
if (prog == -1)
{
prog = 0;
while (prog < 128 && model->_indexOfProgram(prog).isValid())
++prog;
}
if (prog == 128)
return;
}
std::vector<std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>> data;
data.push_back(std::make_pair(prog, amuse::SongGroupIndex::PageEntry{}));
g_MainWindow->pushUndoCommand(
new PageRowAddUndoCommand(model->m_node.get(), tr("Add Page Entry"), table, std::move(data)));
m_addRemoveButtons.addAction()->setDisabled(table->model()->rowCount() >= 128);
} }
else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget())) else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
{ {
QModelIndex idx = table->m_listView->selectionModel()->currentIndex(); SetupListModel* model = static_cast<SetupListModel*>(table->m_listView->model());
if (!idx.isValid()) g_MainWindow->projectModel()->setIdDatabases(model->m_node.get());
table->m_listView->model()->insertRow(table->m_listView->model()->rowCount() - 1); std::vector<std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>> data;
else auto songId = g_MainWindow->projectModel()->allocateSongId();
table->m_listView->model()->insertRow(idx.row()); data.push_back(std::make_tuple(songId.first, songId.second, std::array<amuse::SongGroupIndex::MIDISetup, 16>{}));
g_MainWindow->pushUndoCommand(
new SetupRowAddUndoCommand(model->m_node.get(), tr("Add Setup Entry"), table, std::move(data)));
} }
} }

View File

@ -62,6 +62,7 @@ class PageModel : public QAbstractTableModel
Q_OBJECT Q_OBJECT
friend class SongGroupEditor; friend class SongGroupEditor;
friend class PageObjectDelegate; friend class PageObjectDelegate;
friend class PageTableView;
amuse::ObjToken<ProjectModel::SongGroupNode> m_node; amuse::ObjToken<ProjectModel::SongGroupNode> m_node;
struct Iterator struct Iterator
{ {
@ -90,8 +91,8 @@ public:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex& index) const; Qt::ItemFlags flags(const QModelIndex& index) const;
bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()); int _insertRow(const std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>& data);
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()); std::pair<uint8_t, amuse::SongGroupIndex::PageEntry> _removeRow(uint8_t prog);
}; };
class SetupListModel : public QAbstractTableModel class SetupListModel : public QAbstractTableModel
@ -99,6 +100,8 @@ class SetupListModel : public QAbstractTableModel
Q_OBJECT Q_OBJECT
friend class SongGroupEditor; friend class SongGroupEditor;
friend class MIDIFileDelegate; friend class MIDIFileDelegate;
friend class SetupTableView;
friend class SetupModel;
amuse::ObjToken<ProjectModel::SongGroupNode> m_node; amuse::ObjToken<ProjectModel::SongGroupNode> m_node;
struct Iterator struct Iterator
{ {
@ -138,8 +141,10 @@ public:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex& index) const; Qt::ItemFlags flags(const QModelIndex& index) const;
bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()); int _insertRow(
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()); std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>& data);
std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>
_removeRow(amuse::SongId id);
}; };
class SetupModel : public QAbstractTableModel class SetupModel : public QAbstractTableModel
@ -177,6 +182,7 @@ class SetupTableView : public QSplitter
{ {
Q_OBJECT Q_OBJECT
friend class SongGroupEditor; friend class SongGroupEditor;
friend class SetupRowUndoCommand;
QTableView* m_listView; QTableView* m_listView;
QTableView* m_tableView; QTableView* m_tableView;
MIDIFileDelegate m_midiDelegate; MIDIFileDelegate m_midiDelegate;
@ -239,6 +245,7 @@ public slots:
class SongGroupEditor : public EditorWidget class SongGroupEditor : public EditorWidget
{ {
Q_OBJECT Q_OBJECT
friend class SetupModel;
PageModel m_normPages; PageModel m_normPages;
PageModel m_drumPages; PageModel m_drumPages;
SetupListModel m_setupList; SetupListModel m_setupList;

View File

@ -1,6 +1,124 @@
#include "SoundGroupEditor.hpp" #include "SoundGroupEditor.hpp"
#include "MainWindow.hpp" #include "MainWindow.hpp"
class SFXDataChangeUndoCommand : public EditorUndoCommand
{
amuse::SFXId m_sfx;
int m_column;
int m_undoVal, m_redoVal;
bool m_undid = false;
public:
explicit SFXDataChangeUndoCommand(ProjectModel::SoundGroupNode* node, const QString& text,
amuse::SFXId sfx, int column, int redoVal)
: EditorUndoCommand(node, text), m_sfx(sfx), m_column(column), m_redoVal(redoVal) {}
void undo()
{
m_undid = true;
amuse::SFXGroupIndex& index = *static_cast<ProjectModel::SoundGroupNode*>(m_node.get())->m_index;
auto& map = index.m_sfxEntries;
amuse::SFXGroupIndex::SFXEntry& entry = map[m_sfx];
switch (m_column)
{
case 1:
entry.objId.id = m_undoVal;
break;
case 2:
entry.priority = m_undoVal;
break;
case 3:
entry.maxVoices = m_undoVal;
break;
case 4:
entry.defVel = m_undoVal;
break;
case 5:
entry.panning = m_undoVal;
break;
case 6:
entry.defKey = m_undoVal;
break;
default:
break;
}
EditorUndoCommand::undo();
}
void redo()
{
amuse::SFXGroupIndex& index = *static_cast<ProjectModel::SoundGroupNode*>(m_node.get())->m_index;
auto& map = index.m_sfxEntries;
amuse::SFXGroupIndex::SFXEntry& entry = map[m_sfx];
switch (m_column)
{
case 1:
m_undoVal = entry.objId.id;
entry.objId.id = m_redoVal;
break;
case 2:
m_undoVal = entry.priority;
entry.priority = m_redoVal;
break;
case 3:
m_undoVal = entry.maxVoices;
entry.maxVoices = m_redoVal;
break;
case 4:
m_undoVal = entry.defVel;
entry.defVel = m_redoVal;
break;
case 5:
m_undoVal = entry.panning;
entry.panning = m_redoVal;
break;
case 6:
m_undoVal = entry.defKey;
entry.defKey = m_redoVal;
break;
default:
break;
}
if (m_undid)
EditorUndoCommand::redo();
}
};
class SFXNameChangeUndoCommand : public EditorUndoCommand
{
amuse::SFXId m_sfx;
std::string m_undoVal, m_redoVal;
bool m_undid = false;
public:
explicit SFXNameChangeUndoCommand(ProjectModel::SoundGroupNode* node, const QString& text,
amuse::SFXId sfx, std::string_view redoVal)
: EditorUndoCommand(node, text), m_sfx(sfx), m_redoVal(redoVal) {}
void undo()
{
m_undid = true;
g_MainWindow->projectModel()->setIdDatabases(m_node.get());
amuse::SFXGroupIndex& index = *static_cast<ProjectModel::SoundGroupNode*>(m_node.get())->m_index;
auto& map = index.m_sfxEntries;
amuse::SFXId::CurNameDB->rename(m_sfx, m_undoVal);
EditorUndoCommand::undo();
}
void redo()
{
g_MainWindow->projectModel()->setIdDatabases(m_node.get());
amuse::SFXGroupIndex& index = *static_cast<ProjectModel::SoundGroupNode*>(m_node.get())->m_index;
auto& map = index.m_sfxEntries;
m_undoVal = amuse::SFXId::CurNameDB->resolveNameFromId(m_sfx);
amuse::SFXId::CurNameDB->rename(m_sfx, m_redoVal);
if (m_undid)
EditorUndoCommand::redo();
}
};
SFXObjectDelegate::SFXObjectDelegate(QObject* parent) SFXObjectDelegate::SFXObjectDelegate(QObject* parent)
: QStyledItemDelegate(parent) {} : QStyledItemDelegate(parent) {}
@ -32,19 +150,23 @@ void SFXObjectDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, con
const SFXModel* model = static_cast<const SFXModel*>(m); const SFXModel* model = static_cast<const SFXModel*>(m);
auto entry = model->m_sorted[index.row()]; auto entry = model->m_sorted[index.row()];
int idx = static_cast<EditorFieldPageObjectNode*>(editor)->currentIndex(); int idx = static_cast<EditorFieldPageObjectNode*>(editor)->currentIndex();
if (idx == 0) amuse::ObjectId id;
{ if (idx != 0)
entry->second.objId.id = amuse::ObjectId();
}
else
{ {
ProjectModel::BasePoolObjectNode* node = static_cast<ProjectModel::BasePoolObjectNode*>( ProjectModel::BasePoolObjectNode* node = static_cast<ProjectModel::BasePoolObjectNode*>(
g_MainWindow->projectModel()->node( g_MainWindow->projectModel()->node(
g_MainWindow->projectModel()->getPageObjectProxy()->mapToSource( g_MainWindow->projectModel()->getPageObjectProxy()->mapToSource(
g_MainWindow->projectModel()->getPageObjectProxy()->index(idx, 0, g_MainWindow->projectModel()->getPageObjectProxy()->index(idx, 0,
static_cast<EditorFieldPageObjectNode*>(editor)->rootModelIndex())))); static_cast<EditorFieldPageObjectNode*>(editor)->rootModelIndex()))));
entry->second.objId.id = node->id(); id = node->id();
} }
if (id == entry->second.objId.id)
{
emit m->dataChanged(index, index);
return;
}
g_MainWindow->pushUndoCommand(new SFXDataChangeUndoCommand(model->m_node.get(),
tr("Change %1").arg(m->headerData(1, Qt::Horizontal).toString()), entry->first, 1, id.id));
emit m->dataChanged(index, index); emit m->dataChanged(index, index);
} }
@ -132,7 +254,7 @@ QVariant SFXModel::data(const QModelIndex& index, int role) const
{ {
case 0: case 0:
{ {
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases(); g_MainWindow->projectModel()->setIdDatabases(m_node.get());
return amuse::SFXId::CurNameDB->resolveNameFromId(entry->first.id).data(); return amuse::SFXId::CurNameDB->resolveNameFromId(entry->first.id).data();
} }
case 1: case 1:
@ -172,7 +294,7 @@ bool SFXModel::setData(const QModelIndex& index, const QVariant& value, int role
{ {
case 0: case 0:
{ {
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases(); g_MainWindow->projectModel()->setIdDatabases(m_node.get());
auto utf8key = value.toString().toUtf8(); auto utf8key = value.toString().toUtf8();
std::unordered_map<std::string, amuse::ObjectId>::iterator idIt; std::unordered_map<std::string, amuse::ObjectId>::iterator idIt;
if ((idIt = amuse::SFXId::CurNameDB->m_stringToId.find(utf8key.data())) != amuse::SFXId::CurNameDB->m_stringToId.cend()) if ((idIt = amuse::SFXId::CurNameDB->m_stringToId.find(utf8key.data())) != amuse::SFXId::CurNameDB->m_stringToId.cend())
@ -184,39 +306,45 @@ bool SFXModel::setData(const QModelIndex& index, const QVariant& value, int role
return false; return false;
} }
emit layoutAboutToBeChanged(); emit layoutAboutToBeChanged();
amuse::SFXId::CurNameDB->rename(entry.m_it->first, utf8key.data()); g_MainWindow->pushUndoCommand(new SFXNameChangeUndoCommand(m_node.get(),
tr("Change SFX Name"), entry->first, utf8key.data()));
_buildSortedList(); _buildSortedList();
QModelIndex newIndex = _indexOfSFX(entry.m_it->first); QModelIndex newIndex = _indexOfSFX(entry->first);
changePersistentIndex(index, newIndex); changePersistentIndex(index, newIndex);
emit layoutChanged(); emit layoutChanged();
emit dataChanged(newIndex, newIndex, {Qt::DisplayRole, Qt::EditRole}); emit dataChanged(newIndex, newIndex, {Qt::DisplayRole, Qt::EditRole});
return true; return true;
} }
case 2: case 2:
entry->second.priority = value.toInt(); if (entry->second.priority == value.toInt())
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); return false;
return true;
case 3:
entry->second.maxVoices = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 4:
entry->second.defVel = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 5:
entry->second.panning = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 6:
entry->second.defKey = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
default:
break; break;
case 3:
if (entry->second.maxVoices == value.toInt())
return false;
break;
case 4:
if (entry->second.defVel == value.toInt())
return false;
break;
case 5:
if (entry->second.panning == value.toInt())
return false;
break;
case 6:
if (entry->second.defKey == value.toInt())
return false;
break;
default:
return false;
} }
return false; g_MainWindow->pushUndoCommand(new SFXDataChangeUndoCommand(m_node.get(),
tr("Change %1").arg(headerData(index.column(), Qt::Horizontal).toString()),
entry->first, index.column(), value.toInt()));
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
} }
QVariant SFXModel::headerData(int section, Qt::Orientation orientation, int role) const QVariant SFXModel::headerData(int section, Qt::Orientation orientation, int role) const
@ -255,42 +383,104 @@ Qt::ItemFlags SFXModel::flags(const QModelIndex& index) const
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
} }
bool SFXModel::insertRows(int row, int count, const QModelIndex& parent) class SFXRowUndoCommand : public EditorUndoCommand
{
protected:
SFXTableView* m_view;
std::vector<std::tuple<amuse::SFXId, std::string, amuse::SFXGroupIndex::SFXEntry>> m_data;
bool m_undid = false;
void add()
{
m_view->selectionModel()->clearSelection();
for (auto& p : m_data)
{
int row = static_cast<SFXModel*>(m_view->model())->_insertRow(p);
m_view->selectionModel()->select(QItemSelection(
m_view->model()->index(row, 0), m_view->model()->index(row, 6)),
QItemSelectionModel::SelectCurrent);
}
}
void del()
{
for (auto it = m_data.rbegin(); it != m_data.rend(); ++it)
{
*it = static_cast<SFXModel*>(m_view->model())->_removeRow(std::get<0>(*it));
}
}
void undo()
{
m_undid = true;
EditorUndoCommand::undo();
}
void redo()
{
if (m_undid)
EditorUndoCommand::redo();
}
public:
explicit SFXRowUndoCommand(ProjectModel::SoundGroupNode* node, const QString& text, SFXTableView* view,
std::vector<std::tuple<amuse::SFXId, std::string, amuse::SFXGroupIndex::SFXEntry>>&& data)
: EditorUndoCommand(node, text), m_view(view), m_data(std::move(data)) {}
};
class SFXRowAddUndoCommand : public SFXRowUndoCommand
{
using base = SFXRowUndoCommand;
public:
explicit SFXRowAddUndoCommand(ProjectModel::SoundGroupNode* node, const QString& text, SFXTableView* view,
std::vector<std::tuple<amuse::SFXId, std::string, amuse::SFXGroupIndex::SFXEntry>>&& data)
: SFXRowUndoCommand(node, text, view, std::move(data)) {}
void undo() { base::undo(); base::del(); }
void redo() { base::redo(); base::add(); }
};
class SFXRowDelUndoCommand : public SFXRowUndoCommand
{
using base = SFXRowUndoCommand;
public:
explicit SFXRowDelUndoCommand(ProjectModel::SoundGroupNode* node, const QString& text, SFXTableView* view,
std::vector<std::tuple<amuse::SFXId, std::string, amuse::SFXGroupIndex::SFXEntry>>&& data)
: SFXRowUndoCommand(node, text, view, std::move(data)) {}
void undo() { base::undo(); base::add(); }
void redo() { base::redo(); base::del(); }
};
int SFXModel::_insertRow(const std::tuple<amuse::SFXId, std::string, amuse::SFXGroupIndex::SFXEntry>& data)
{ {
if (!m_node) if (!m_node)
return false; return 0;
auto& map = _getMap(); auto& map = _getMap();
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases(); g_MainWindow->projectModel()->setIdDatabases(m_node.get());
for (int i = 0; i < count; ++i) amuse::SFXId::CurNameDB->registerPair(std::get<1>(data), std::get<0>(data));
{ int idx = _hypotheticalIndexOfSFX(std::get<1>(data));
amuse::ObjectId sfxId = amuse::SFXId::CurNameDB->generateId(amuse::NameDB::Type::SFX); beginInsertRows(QModelIndex(), idx, idx);
std::string sfxName = amuse::SFXId::CurNameDB->generateName(sfxId, amuse::NameDB::Type::SFX); map.emplace(std::make_pair(std::get<0>(data), std::get<2>(data)));
int insertIdx = _hypotheticalIndexOfSFX(sfxName); _buildSortedList();
beginInsertRows(parent, insertIdx, insertIdx); endInsertRows();
amuse::SFXId::CurNameDB->registerPair(sfxName, sfxId); return idx;
map.emplace(std::make_pair(sfxId, amuse::SFXGroupIndex::SFXEntry{}));
_buildSortedList();
endInsertRows();
++row;
}
return true;
} }
bool SFXModel::removeRows(int row, int count, const QModelIndex& parent) std::tuple<amuse::SFXId, std::string, amuse::SFXGroupIndex::SFXEntry> SFXModel::_removeRow(amuse::SFXId sfx)
{ {
std::tuple<amuse::SFXId, std::string, amuse::SFXGroupIndex::SFXEntry> ret;
if (!m_node) if (!m_node)
return false; return ret;
auto& map = _getMap(); auto& map = _getMap();
beginRemoveRows(parent, row, row + count - 1); g_MainWindow->projectModel()->setIdDatabases(m_node.get());
std::vector<amuse::SFXId> removeSFXs; int idx = _indexOfSFX(sfx).row();
removeSFXs.reserve(count); beginRemoveRows(QModelIndex(), idx, idx);
for (int i = 0; i < count; ++i) std::get<0>(ret) = sfx;
removeSFXs.push_back(m_sorted[row+i].m_it->first); std::get<1>(ret) = amuse::SFXId::CurNameDB->resolveNameFromId(sfx);
for (amuse::SFXId sfx : removeSFXs) auto search = map.find(sfx);
map.erase(sfx); if (search != map.cend())
{
std::get<2>(ret) = search->second;
amuse::SFXId::CurNameDB->remove(sfx);
map.erase(search);
}
_buildSortedList(); _buildSortedList();
endRemoveRows(); endRemoveRows();
return true; return ret;
} }
SFXModel::SFXModel(QObject* parent) SFXModel::SFXModel(QObject* parent)
@ -299,9 +489,20 @@ SFXModel::SFXModel(QObject* parent)
void SFXTableView::deleteSelection() void SFXTableView::deleteSelection()
{ {
QModelIndexList list; QModelIndexList list = selectionModel()->selectedRows();
while (!(list = selectionModel()->selectedRows()).isEmpty()) if (list.isEmpty())
model()->removeRow(list.back().row()); return;
std::sort(list.begin(), list.end(), [](const auto& a, const auto& b) { return a.row() < b.row(); });
std::vector<std::tuple<amuse::SFXId, std::string, amuse::SFXGroupIndex::SFXEntry>> data;
data.reserve(list.size());
for (QModelIndex idx : list)
{
auto& entry = *static_cast<SFXModel*>(model())->m_sorted[idx.row()].m_it;
data.push_back({entry.first, {}, {}});
}
g_MainWindow->pushUndoCommand(
new SFXRowDelUndoCommand(static_cast<SFXModel*>(model())->m_node.get(),
data.size() > 1 ? tr("Delete SFX Entries") : tr("Delete SFX Entry"), this, std::move(data)));
} }
void SFXTableView::setModel(QAbstractItemModel* model) void SFXTableView::setModel(QAbstractItemModel* model)
@ -420,11 +621,13 @@ bool SoundGroupEditor::isItemEditEnabled() const
void SoundGroupEditor::doAdd() void SoundGroupEditor::doAdd()
{ {
QModelIndex idx = m_sfxTable->selectionModel()->currentIndex(); g_MainWindow->projectModel()->setIdDatabases(m_sfxs.m_node.get());
if (!idx.isValid()) std::vector<std::tuple<amuse::SFXId, std::string, amuse::SFXGroupIndex::SFXEntry>> data;
m_sfxTable->model()->insertRow(m_sfxTable->model()->rowCount() - 1); amuse::SFXId sfxId = amuse::SFXId::CurNameDB->generateId(amuse::NameDB::Type::SFX);
else std::string sfxName = amuse::SFXId::CurNameDB->generateName(sfxId, amuse::NameDB::Type::SFX);
m_sfxTable->model()->insertRow(idx.row()); data.push_back(std::make_tuple(sfxId, sfxName, amuse::SFXGroupIndex::SFXEntry{}));
g_MainWindow->pushUndoCommand(
new SFXRowAddUndoCommand(m_sfxs.m_node.get(), tr("Add SFX Entry"), m_sfxTable, std::move(data)));
} }
void SoundGroupEditor::doSelectionChanged() void SoundGroupEditor::doSelectionChanged()
@ -440,9 +643,9 @@ void SoundGroupEditor::sfxDataChanged()
{ {
QModelIndex index = m_sfxs.index(idx, 1); QModelIndex index = m_sfxs.index(idx, 1);
SFXPlayerWidget* w = qobject_cast<SFXPlayerWidget*>(m_sfxTable->indexWidget(index)); SFXPlayerWidget* w = qobject_cast<SFXPlayerWidget*>(m_sfxTable->indexWidget(index));
if (!w || w->sfxId() != p.m_it->first) if (!w || w->sfxId() != p->first)
{ {
SFXPlayerWidget* newW = new SFXPlayerWidget(index, m_sfxs.m_node->m_id, p.m_it->first); SFXPlayerWidget* newW = new SFXPlayerWidget(index, m_sfxs.m_node->m_id, p->first);
m_sfxTable->setIndexWidget(index, newW); m_sfxTable->setIndexWidget(index, newW);
} }
++idx; ++idx;

View File

@ -23,13 +23,14 @@ class SFXModel : public QAbstractTableModel
Q_OBJECT Q_OBJECT
friend class SoundGroupEditor; friend class SoundGroupEditor;
friend class SFXObjectDelegate; friend class SFXObjectDelegate;
friend class SFXTableView;
amuse::ObjToken<ProjectModel::SoundGroupNode> m_node; amuse::ObjToken<ProjectModel::SoundGroupNode> m_node;
struct Iterator struct Iterator
{ {
using ItTp = std::unordered_map<amuse::SFXId, amuse::SFXGroupIndex::SFXEntry>::iterator; using ItTp = std::unordered_map<amuse::SFXId, amuse::SFXGroupIndex::SFXEntry>::iterator;
ItTp m_it; ItTp m_it;
Iterator(ItTp it) : m_it(it) {} Iterator(ItTp it) : m_it(it) {}
ItTp::pointer operator->() { return m_it.operator->(); } ItTp::pointer operator->() const { return m_it.operator->(); }
bool operator<(const Iterator& other) const bool operator<(const Iterator& other) const
{ {
return amuse::SFXId::CurNameDB->resolveNameFromId(m_it->first) < return amuse::SFXId::CurNameDB->resolveNameFromId(m_it->first) <
@ -62,8 +63,8 @@ public:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex& index) const; Qt::ItemFlags flags(const QModelIndex& index) const;
bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()); int _insertRow(const std::tuple<amuse::SFXId, std::string, amuse::SFXGroupIndex::SFXEntry>& data);
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()); std::tuple<amuse::SFXId, std::string, amuse::SFXGroupIndex::SFXEntry> _removeRow(amuse::SFXId sfx);
}; };
class SFXTableView : public QTableView class SFXTableView : public QTableView

View File

@ -197,12 +197,17 @@
<context> <context>
<name>LayersEditor</name> <name>LayersEditor</name>
<message> <message>
<location filename="../LayersEditor.cpp" line="443"/> <location filename="../LayersEditor.cpp" line="630"/>
<source>Add Layer</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="671"/>
<source>Add new layer mapping</source> <source>Add new layer mapping</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../LayersEditor.cpp" line="445"/> <location filename="../LayersEditor.cpp" line="673"/>
<source>Remove selected layer mappings</source> <source>Remove selected layer mappings</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -210,55 +215,91 @@
<context> <context>
<name>LayersModel</name> <name>LayersModel</name>
<message> <message>
<location filename="../LayersEditor.cpp" line="172"/> <location filename="../LayersEditor.cpp" line="263"/>
<source>Change %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="276"/>
<source>SoundMacro</source> <source>SoundMacro</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../LayersEditor.cpp" line="174"/> <location filename="../LayersEditor.cpp" line="278"/>
<source>Key Lo</source> <source>Key Lo</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../LayersEditor.cpp" line="176"/> <location filename="../LayersEditor.cpp" line="280"/>
<source>Key Hi</source> <source>Key Hi</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../LayersEditor.cpp" line="178"/> <location filename="../LayersEditor.cpp" line="282"/>
<source>Transpose</source> <source>Transpose</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../LayersEditor.cpp" line="180"/> <location filename="../LayersEditor.cpp" line="284"/>
<source>Volume</source> <source>Volume</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../LayersEditor.cpp" line="182"/> <location filename="../LayersEditor.cpp" line="286"/>
<source>Prio Off</source> <source>Prio Off</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../LayersEditor.cpp" line="184"/> <location filename="../LayersEditor.cpp" line="288"/>
<source>Span</source> <source>Span</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../LayersEditor.cpp" line="186"/> <location filename="../LayersEditor.cpp" line="290"/>
<source>Pan</source> <source>Pan</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../LayersEditor.cpp" line="391"/>
<source>Move Layers</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="391"/>
<source>Move Layer</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>LayersTableView</name>
<message>
<location filename="../LayersEditor.cpp" line="552"/>
<source>Delete Layers</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../LayersEditor.cpp" line="552"/>
<source>Delete Layer</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MIDIFileDelegate</name>
<message>
<location filename="../SongGroupEditor.cpp" line="342"/>
<source>Change MIDI Path</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>MIDIFileFieldWidget</name> <name>MIDIFileFieldWidget</name>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="73"/> <location filename="../SongGroupEditor.cpp" line="303"/>
<source>Browse</source> <source>Browse</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="74"/> <location filename="../SongGroupEditor.cpp" line="304"/>
<source>Open Song File</source> <source>Open Song File</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -266,13 +307,13 @@
<context> <context>
<name>MIDIPlayerWidget</name> <name>MIDIPlayerWidget</name>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="823"/> <location filename="../SongGroupEditor.cpp" line="1193"/>
<source>Stop</source> <source>Stop</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="837"/> <location filename="../SongGroupEditor.cpp" line="1207"/>
<location filename="../SongGroupEditor.cpp" line="860"/> <location filename="../SongGroupEditor.cpp" line="1230"/>
<source>Play</source> <source>Play</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -291,152 +332,152 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="296"/> <location filename="../MainWindow.ui" line="302"/>
<source>&amp;File</source> <source>&amp;File</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="303"/> <location filename="../MainWindow.ui" line="309"/>
<source>Recent &amp;Projects</source> <source>Recent &amp;Projects</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="319"/> <location filename="../MainWindow.ui" line="325"/>
<source>Pro&amp;ject</source> <source>Pro&amp;ject</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="334"/> <location filename="../MainWindow.ui" line="340"/>
<source>&amp;Audio</source> <source>&amp;Audio</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="341"/> <location filename="../MainWindow.ui" line="347"/>
<source>&amp;MIDI</source> <source>&amp;MIDI</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="348"/> <location filename="../MainWindow.ui" line="354"/>
<source>&amp;Edit</source> <source>&amp;Edit</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="364"/> <location filename="../MainWindow.ui" line="370"/>
<source>&amp;New Project</source> <source>&amp;New Project</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="369"/> <location filename="../MainWindow.ui" line="375"/>
<source>&amp;Open Project</source> <source>&amp;Open Project</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="377"/> <location filename="../MainWindow.ui" line="383"/>
<source>&amp;Cut</source> <source>&amp;Cut</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="385"/> <location filename="../MainWindow.ui" line="391"/>
<source>C&amp;opy</source> <source>C&amp;opy</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="393"/> <location filename="../MainWindow.ui" line="399"/>
<source>&amp;Paste</source> <source>&amp;Paste</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="401"/> <location filename="../MainWindow.ui" line="407"/>
<source>&amp;Delete</source> <source>&amp;Delete</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="406"/> <location filename="../MainWindow.ui" line="412"/>
<source>&amp;Import Groups</source> <source>&amp;Import Groups</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="409"/> <location filename="../MainWindow.ui" line="415"/>
<source>Ctrl+I</source> <source>Ctrl+I</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="421"/> <location filename="../MainWindow.ui" line="427"/>
<source>New SF&amp;X Group</source> <source>New SF&amp;X Group</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="433"/> <location filename="../MainWindow.ui" line="439"/>
<source>New Son&amp;g Group</source> <source>New Son&amp;g Group</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="445"/> <location filename="../MainWindow.ui" line="451"/>
<source>New Sound &amp;Macro</source> <source>New Sound &amp;Macro</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="457"/> <location filename="../MainWindow.ui" line="463"/>
<source>New &amp;Keymap</source> <source>New &amp;Keymap</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="469"/> <location filename="../MainWindow.ui" line="475"/>
<source>New &amp;Layers</source> <source>New &amp;Layers</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="477"/> <location filename="../MainWindow.ui" line="483"/>
<source>&amp;Output Device:</source> <source>&amp;Output Device:</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="485"/> <location filename="../MainWindow.ui" line="491"/>
<source>&amp;Input Device:</source> <source>&amp;Input Device:</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="493"/> <location filename="../MainWindow.ui" line="499"/>
<source>&amp;Export GameCube Groups</source> <source>&amp;Export GameCube Groups</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="496"/> <location filename="../MainWindow.ui" line="502"/>
<source>Ctrl+E</source> <source>Ctrl+E</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="508"/> <location filename="../MainWindow.ui" line="514"/>
<source>&amp;New Subproject</source> <source>&amp;New Subproject</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="520"/> <location filename="../MainWindow.ui" line="526"/>
<source>New &amp;ADSR</source> <source>New &amp;ADSR</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="532"/> <location filename="../MainWindow.ui" line="538"/>
<source>New &amp;Curve</source> <source>New &amp;Curve</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="540"/> <location filename="../MainWindow.ui" line="546"/>
<source>&amp;Save Project</source> <source>&amp;Save Project</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="548"/> <location filename="../MainWindow.ui" line="554"/>
<source>&amp;Revert Project</source> <source>&amp;Revert Project</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="556"/> <location filename="../MainWindow.ui" line="562"/>
<source>Reload Sample &amp;Data</source> <source>Reload Sample &amp;Data</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.ui" line="564"/> <location filename="../MainWindow.ui" line="570"/>
<source>I&amp;mport Songs</source> <source>I&amp;mport Songs</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -862,36 +903,50 @@
<context> <context>
<name>PageModel</name> <name>PageModel</name>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="235"/> <location filename="../SongGroupEditor.cpp" line="468"/>
<source>Program Conflict</source> <source>Program Conflict</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="236"/> <location filename="../SongGroupEditor.cpp" line="469"/>
<source>Program %1 is already defined in table</source> <source>Program %1 is already defined in table</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="272"/> <location filename="../SongGroupEditor.cpp" line="475"/>
<location filename="../SongGroupEditor.cpp" line="501"/>
<source>Change %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="515"/>
<source>Program</source> <source>Program</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="274"/> <location filename="../SongGroupEditor.cpp" line="517"/>
<source>Object</source> <source>Object</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="276"/> <location filename="../SongGroupEditor.cpp" line="519"/>
<source>Priority</source> <source>Priority</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="278"/> <location filename="../SongGroupEditor.cpp" line="521"/>
<source>Max Voices</source> <source>Max Voices</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>PageObjectDelegate</name>
<message>
<location filename="../SongGroupEditor.cpp" line="278"/>
<source>Change %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>PageObjectProxyModel</name> <name>PageObjectProxyModel</name>
<message> <message>
@ -910,6 +965,19 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>PageTableView</name>
<message>
<location filename="../SongGroupEditor.cpp" line="1052"/>
<source>Delete Page Entries</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1052"/>
<source>Delete Page Entry</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>PaintButton</name> <name>PaintButton</name>
<message> <message>
@ -929,188 +997,188 @@
<context> <context>
<name>ProjectModel</name> <name>ProjectModel</name>
<message> <message>
<location filename="../ProjectModel.cpp" line="519"/> <location filename="../ProjectModel.cpp" line="566"/>
<source>Sound Macros</source> <source>Sound Macros</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="538"/> <location filename="../ProjectModel.cpp" line="585"/>
<source>ADSRs</source> <source>ADSRs</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="549"/> <location filename="../ProjectModel.cpp" line="596"/>
<source>Curves</source> <source>Curves</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="561"/> <location filename="../ProjectModel.cpp" line="608"/>
<source>Keymaps</source> <source>Keymaps</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="568"/> <location filename="../ProjectModel.cpp" line="615"/>
<source>Layers</source> <source>Layers</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="575"/> <location filename="../ProjectModel.cpp" line="622"/>
<source>Samples</source> <source>Samples</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="840"/> <location filename="../ProjectModel.cpp" line="887"/>
<source>Subproject Conflict</source> <source>Subproject Conflict</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="840"/> <location filename="../ProjectModel.cpp" line="887"/>
<source>The subproject %1 is already defined</source> <source>The subproject %1 is already defined</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="845"/> <location filename="../ProjectModel.cpp" line="892"/>
<source>Add Subproject %1</source> <source>Add Subproject %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="895"/> <location filename="../ProjectModel.cpp" line="942"/>
<source>Sound Group Conflict</source> <source>Sound Group Conflict</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="895"/> <location filename="../ProjectModel.cpp" line="942"/>
<location filename="../ProjectModel.cpp" line="920"/> <location filename="../ProjectModel.cpp" line="967"/>
<source>The group %1 is already defined</source> <source>The group %1 is already defined</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="899"/> <location filename="../ProjectModel.cpp" line="946"/>
<source>Add Sound Group %1</source> <source>Add Sound Group %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="920"/> <location filename="../ProjectModel.cpp" line="967"/>
<source>Song Group Conflict</source> <source>Song Group Conflict</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="924"/> <location filename="../ProjectModel.cpp" line="971"/>
<source>Add Song Group %1</source> <source>Add Song Group %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1005"/> <location filename="../ProjectModel.cpp" line="1052"/>
<source>Sound Macro Conflict</source> <source>Sound Macro Conflict</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1005"/> <location filename="../ProjectModel.cpp" line="1052"/>
<source>The macro %1 is already defined</source> <source>The macro %1 is already defined</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1015"/> <location filename="../ProjectModel.cpp" line="1062"/>
<source>Add Sound Macro %1</source> <source>Add Sound Macro %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1036"/> <location filename="../ProjectModel.cpp" line="1083"/>
<source>ADSR Conflict</source> <source>ADSR Conflict</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1036"/> <location filename="../ProjectModel.cpp" line="1083"/>
<source>The ADSR %1 is already defined</source> <source>The ADSR %1 is already defined</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1042"/> <location filename="../ProjectModel.cpp" line="1089"/>
<source>Add ADSR %1</source> <source>Add ADSR %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1063"/> <location filename="../ProjectModel.cpp" line="1110"/>
<source>Curve Conflict</source> <source>Curve Conflict</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1063"/> <location filename="../ProjectModel.cpp" line="1110"/>
<source>The Curve %1 is already defined</source> <source>The Curve %1 is already defined</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1069"/> <location filename="../ProjectModel.cpp" line="1116"/>
<source>Add Curve %1</source> <source>Add Curve %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1090"/> <location filename="../ProjectModel.cpp" line="1137"/>
<source>Keymap Conflict</source> <source>Keymap Conflict</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1090"/> <location filename="../ProjectModel.cpp" line="1137"/>
<source>The Keymap %1 is already defined</source> <source>The Keymap %1 is already defined</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1095"/> <location filename="../ProjectModel.cpp" line="1142"/>
<source>Add Keymap %1</source> <source>Add Keymap %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1116"/> <location filename="../ProjectModel.cpp" line="1163"/>
<source>Layers Conflict</source> <source>Layers Conflict</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1116"/> <location filename="../ProjectModel.cpp" line="1163"/>
<source>Layers %1 is already defined</source> <source>Layers %1 is already defined</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1121"/> <location filename="../ProjectModel.cpp" line="1168"/>
<source>Add Layers %1</source> <source>Add Layers %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1135"/> <location filename="../ProjectModel.cpp" line="1182"/>
<source>Delete Subproject %1</source> <source>Delete Subproject %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1138"/> <location filename="../ProjectModel.cpp" line="1185"/>
<source>Delete SongGroup %1</source> <source>Delete SongGroup %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1141"/> <location filename="../ProjectModel.cpp" line="1188"/>
<source>Delete SFXGroup %1</source> <source>Delete SFXGroup %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1144"/> <location filename="../ProjectModel.cpp" line="1191"/>
<source>Delete SoundMacro %1</source> <source>Delete SoundMacro %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1147"/> <location filename="../ProjectModel.cpp" line="1194"/>
<source>Delete ADSR %1</source> <source>Delete ADSR %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1150"/> <location filename="../ProjectModel.cpp" line="1197"/>
<source>Delete Curve %1</source> <source>Delete Curve %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1153"/> <location filename="../ProjectModel.cpp" line="1200"/>
<source>Delete Keymap %1</source> <source>Delete Keymap %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ProjectModel.cpp" line="1156"/> <location filename="../ProjectModel.cpp" line="1203"/>
<source>Delete Layers %1</source> <source>Delete Layers %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1118,132 +1186,163 @@
<context> <context>
<name>SFXModel</name> <name>SFXModel</name>
<message> <message>
<location filename="../SoundGroupEditor.cpp" line="182"/> <location filename="../SoundGroupEditor.cpp" line="304"/>
<source>SFX Conflict</source> <source>SFX Conflict</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SoundGroupEditor.cpp" line="183"/> <location filename="../SoundGroupEditor.cpp" line="305"/>
<source>SFX %1 is already defined in project</source> <source>SFX %1 is already defined in project</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SoundGroupEditor.cpp" line="229"/> <location filename="../SoundGroupEditor.cpp" line="310"/>
<source>Change SFX Name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SoundGroupEditor.cpp" line="343"/>
<source>Change %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SoundGroupEditor.cpp" line="357"/>
<source>SFX</source> <source>SFX</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SoundGroupEditor.cpp" line="231"/> <location filename="../SoundGroupEditor.cpp" line="359"/>
<source>Object</source> <source>Object</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SoundGroupEditor.cpp" line="233"/> <location filename="../SoundGroupEditor.cpp" line="361"/>
<source>Priority</source> <source>Priority</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SoundGroupEditor.cpp" line="235"/> <location filename="../SoundGroupEditor.cpp" line="363"/>
<source>Max Voices</source> <source>Max Voices</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SoundGroupEditor.cpp" line="237"/> <location filename="../SoundGroupEditor.cpp" line="365"/>
<source>Velocity</source> <source>Velocity</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SoundGroupEditor.cpp" line="239"/> <location filename="../SoundGroupEditor.cpp" line="367"/>
<source>Panning</source> <source>Panning</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SoundGroupEditor.cpp" line="241"/> <location filename="../SoundGroupEditor.cpp" line="369"/>
<source>Key</source> <source>Key</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>SFXObjectDelegate</name>
<message>
<location filename="../SoundGroupEditor.cpp" line="169"/>
<source>Change %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>SFXPlayerWidget</name> <name>SFXPlayerWidget</name>
<message> <message>
<location filename="../SoundGroupEditor.cpp" line="350"/> <location filename="../SoundGroupEditor.cpp" line="551"/>
<source>Stop</source> <source>Stop</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SoundGroupEditor.cpp" line="364"/> <location filename="../SoundGroupEditor.cpp" line="565"/>
<location filename="../SoundGroupEditor.cpp" line="386"/> <location filename="../SoundGroupEditor.cpp" line="587"/>
<source>Play</source> <source>Play</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>SFXTableView</name>
<message>
<location filename="../SoundGroupEditor.cpp" line="505"/>
<source>Delete SFX Entries</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SoundGroupEditor.cpp" line="505"/>
<source>Delete SFX Entry</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>SampleControls</name> <name>SampleControls</name>
<message> <message>
<location filename="../SampleEditor.cpp" line="459"/> <location filename="../SampleEditor.cpp" line="475"/>
<source>Change %1</source> <source>Change %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SampleEditor.cpp" line="507"/> <location filename="../SampleEditor.cpp" line="523"/>
<location filename="../SampleEditor.cpp" line="522"/> <location filename="../SampleEditor.cpp" line="538"/>
<location filename="../SampleEditor.cpp" line="781"/> <location filename="../SampleEditor.cpp" line="797"/>
<source>Loop</source> <source>Loop</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SampleEditor.cpp" line="541"/> <location filename="../SampleEditor.cpp" line="557"/>
<source>Loop Start</source> <source>Loop Start</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SampleEditor.cpp" line="560"/> <location filename="../SampleEditor.cpp" line="576"/>
<source>Loop End</source> <source>Loop End</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SampleEditor.cpp" line="577"/> <location filename="../SampleEditor.cpp" line="593"/>
<source>Change Base Pitch</source> <source>Change Base Pitch</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SampleEditor.cpp" line="666"/> <location filename="../SampleEditor.cpp" line="682"/>
<source>Make WAV Version</source> <source>Make WAV Version</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SampleEditor.cpp" line="673"/> <location filename="../SampleEditor.cpp" line="689"/>
<source>Make Compressed Version</source> <source>Make Compressed Version</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SampleEditor.cpp" line="678"/> <location filename="../SampleEditor.cpp" line="694"/>
<source>Up To Date</source> <source>Up To Date</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SampleEditor.cpp" line="752"/> <location filename="../SampleEditor.cpp" line="768"/>
<location filename="../SampleEditor.cpp" line="822"/> <location filename="../SampleEditor.cpp" line="838"/>
<source>Nothing Loaded</source> <source>Nothing Loaded</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SampleEditor.cpp" line="774"/> <location filename="../SampleEditor.cpp" line="790"/>
<source>Zoom</source> <source>Zoom</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SampleEditor.cpp" line="788"/> <location filename="../SampleEditor.cpp" line="804"/>
<source>Start</source> <source>Start</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SampleEditor.cpp" line="795"/> <location filename="../SampleEditor.cpp" line="811"/>
<source>End</source> <source>End</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SampleEditor.cpp" line="802"/> <location filename="../SampleEditor.cpp" line="818"/>
<source>Base Pitch</source> <source>Base Pitch</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1251,22 +1350,27 @@
<context> <context>
<name>SetupListModel</name> <name>SetupListModel</name>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="464"/> <location filename="../SongGroupEditor.cpp" line="741"/>
<source>Song Conflict</source> <source>Song Conflict</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="465"/> <location filename="../SongGroupEditor.cpp" line="742"/>
<source>Song %1 is already defined in project</source> <source>Song %1 is already defined in project</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="486"/> <location filename="../SongGroupEditor.cpp" line="747"/>
<source>Change Song Name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="764"/>
<source>Song</source> <source>Song</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="488"/> <location filename="../SongGroupEditor.cpp" line="766"/>
<source>MIDI File</source> <source>MIDI File</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1274,55 +1378,83 @@
<context> <context>
<name>SetupModel</name> <name>SetupModel</name>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="653"/> <location filename="../SongGroupEditor.cpp" line="987"/>
<source>Change %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1004"/>
<source>Program</source> <source>Program</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="655"/> <location filename="../SongGroupEditor.cpp" line="1006"/>
<source>Volume</source> <source>Volume</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="657"/> <location filename="../SongGroupEditor.cpp" line="1008"/>
<source>Panning</source> <source>Panning</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="659"/> <location filename="../SongGroupEditor.cpp" line="1010"/>
<source>Reverb</source> <source>Reverb</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="661"/> <location filename="../SongGroupEditor.cpp" line="1012"/>
<source>Chorus</source> <source>Chorus</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>SetupTableView</name>
<message>
<location filename="../SongGroupEditor.cpp" line="1115"/>
<source>Delete Setup Entries</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1115"/>
<source>Delete Setup Entry</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>SongGroupEditor</name> <name>SongGroupEditor</name>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1103"/> <location filename="../SongGroupEditor.cpp" line="1502"/>
<source>Add new page entry</source> <source>Add new page entry</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1105"/> <location filename="../SongGroupEditor.cpp" line="1504"/>
<source>Remove selected page entries</source> <source>Remove selected page entries</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1072"/> <location filename="../SongGroupEditor.cpp" line="1471"/>
<source>Normal Pages</source> <source>Normal Pages</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1073"/> <location filename="../SongGroupEditor.cpp" line="1301"/>
<source>Add Page Entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1313"/>
<source>Add Setup Entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1472"/>
<source>Drum Pages</source> <source>Drum Pages</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1074"/> <location filename="../SongGroupEditor.cpp" line="1473"/>
<source>MIDI Setups</source> <source>MIDI Setups</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1330,12 +1462,17 @@
<context> <context>
<name>SoundGroupEditor</name> <name>SoundGroupEditor</name>
<message> <message>
<location filename="../SoundGroupEditor.cpp" line="490"/> <location filename="../SoundGroupEditor.cpp" line="630"/>
<source>Add SFX Entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SoundGroupEditor.cpp" line="693"/>
<source>Add new SFX entry</source> <source>Add new SFX entry</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SoundGroupEditor.cpp" line="492"/> <location filename="../SoundGroupEditor.cpp" line="695"/>
<source>Remove selected SFX entries</source> <source>Remove selected SFX entries</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1413,6 +1550,14 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>SoundMacroDelegate</name>
<message>
<location filename="../LayersEditor.cpp" line="137"/>
<source>Change %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>SoundMacroDeleteButton</name> <name>SoundMacroDeleteButton</name>
<message> <message>

View File

@ -63,9 +63,11 @@ class Sequencer : public Entity
ObjToken<Voice> m_lastVoice; ObjToken<Voice> m_lastVoice;
int8_t m_ctrlVals[128] = {}; /**< MIDI controller values */ int8_t m_ctrlVals[128] = {}; /**< MIDI controller values */
float m_curPitchWheel = 0.f; /**< MIDI pitch-wheel */ float m_curPitchWheel = 0.f; /**< MIDI pitch-wheel */
int8_t m_pitchWheelRange = -1; /**< Pitch wheel range settable by RPN 0 */
int8_t m_curProgram = 0; /**< MIDI program number */ int8_t m_curProgram = 0; /**< MIDI program number */
float m_curVol = 1.f; /**< Current volume of channel */ float m_curVol = 1.f; /**< Current volume of channel */
float m_curPan = 0.f; /**< Current panning of channel */ float m_curPan = 0.f; /**< Current panning of channel */
uint16_t m_rpn = 0; /**< Current RPN (only pitch-range 0x0000 supported) */
void _bringOutYourDead(); void _bringOutYourDead();
size_t getVoiceCount() const; size_t getVoiceCount() const;

View File

@ -78,10 +78,12 @@ class SongState
const unsigned char* m_data = nullptr; /**< Pointer to upcoming command data */ const unsigned char* m_data = nullptr; /**< Pointer to upcoming command data */
const unsigned char* m_pitchWheelData = nullptr; /**< Pointer to upcoming pitch data */ const unsigned char* m_pitchWheelData = nullptr; /**< Pointer to upcoming pitch data */
const unsigned char* m_modWheelData = nullptr; /**< Pointer to upcoming modulation data */ const unsigned char* m_modWheelData = nullptr; /**< Pointer to upcoming modulation data */
uint32_t m_lastPitchTick = 0; /**< Last position of pitch wheel change */ int32_t m_pitchVal = 0; /**< Accumulated value of pitch */
int32_t m_lastPitchVal = 0; /**< Last value of pitch */ uint32_t m_nextPitchTick = 0; /**< Upcoming position of pitch wheel change */
uint32_t m_lastModTick = 0; /**< Last position of mod wheel change */ int32_t m_nextPitchDelta = 0; /**< Upcoming delta value of pitch */
int32_t m_lastModVal = 0; /**< Last value of mod */ 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_eventWaitCountdown = 0; /**< Current wait in ticks */

View File

@ -86,8 +86,8 @@ class Voice : public Entity
float m_curPan = 0.f; /**< Current pan of voice */ float m_curPan = 0.f; /**< Current pan of voice */
float m_curSpan = -1.f; /**< Current surround pan of voice */ float m_curSpan = -1.f; /**< Current surround pan of voice */
float m_curPitchWheel = 0.f; /**< Current normalized wheel value for control */ float m_curPitchWheel = 0.f; /**< Current normalized wheel value for control */
int32_t m_pitchWheelUp = 600; /**< Up range for pitchwheel control in cents */ int32_t m_pitchWheelUp = 200; /**< Up range for pitchwheel control in cents */
int32_t m_pitchWheelDown = 600; /**< Down range for pitchwheel control in cents */ int32_t m_pitchWheelDown = 200; /**< Down range for pitchwheel control in cents */
int32_t m_pitchWheelVal = 0; /**< Current resolved pitchwheel delta for control */ int32_t m_pitchWheelVal = 0; /**< Current resolved pitchwheel delta for control */
int32_t m_curPitch; /**< Current base pitch in cents */ int32_t m_curPitch; /**< Current base pitch in cents */
bool m_pitchDirty = true; /**< m_curPitch has been updated and needs sending to voice */ bool m_pitchDirty = true; /**< m_curPitch has been updated and needs sending to voice */
@ -139,6 +139,8 @@ class Voice : public Entity
std::unique_ptr<int8_t[]> m_ctrlValsSelf; /**< Self-owned MIDI Controller values */ std::unique_ptr<int8_t[]> m_ctrlValsSelf; /**< Self-owned MIDI Controller values */
int8_t* m_extCtrlVals = nullptr; /**< MIDI Controller values (external storage) */ int8_t* m_extCtrlVals = nullptr; /**< MIDI Controller values (external storage) */
uint16_t m_rpn = 0; /**< Current RPN (only pitch-range 0x0000 supported) */
void _destroy(); void _destroy();
bool _checkSamplePos(bool& looped); bool _checkSamplePos(bool& looped);
void _doKeyOff(); void _doKeyOff();

View File

@ -278,6 +278,8 @@ ObjToken<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
(*ret)->setAuxBVol(m_ctrlVals[0x5d] / 127.f); (*ret)->setAuxBVol(m_ctrlVals[0x5d] / 127.f);
(*ret)->setPan(m_curPan); (*ret)->setPan(m_curPan);
(*ret)->setPitchWheel(m_curPitchWheel); (*ret)->setPitchWheel(m_curPitchWheel);
if (m_pitchWheelRange != -1)
(*ret)->setPitchWheelRange(m_pitchWheelRange, m_pitchWheelRange);
if (m_ctrlVals[64] > 64) if (m_ctrlVals[64] > 64)
(*ret)->setPedal(true); (*ret)->setPedal(true);
@ -336,6 +338,23 @@ void Sequencer::ChannelState::setCtrlValue(uint8_t ctrl, int8_t val)
case 10: case 10:
setPan(val / 64.f - 1.f); setPan(val / 64.f - 1.f);
break; break;
case 98:
// RPN LSB
m_rpn &= ~0x7f;
m_rpn |= val;
case 99:
// RPN MSB
m_rpn &= ~0x3f80;
m_rpn |= val << 7;
case 6:
if (m_rpn == 0)
m_pitchWheelRange = val;
case 96:
if (m_rpn == 0)
m_pitchWheelRange += 1;
case 97:
if (m_rpn == 0)
m_pitchWheelRange -= 1;
default: default:
break; break;
} }

View File

@ -513,68 +513,63 @@ public:
std::vector<uint8_t>& getResult() { return m_result; } std::vector<uint8_t>& getResult() { return m_result; }
}; };
static uint32_t DecodeRLE(const unsigned char*& data) static uint16_t DecodeUnsignedValue(const unsigned char*& data)
{ {
uint32_t ret = 0; uint16_t ret;
if (data[0] & 0x80)
while (true)
{ {
uint32_t thisPart = *data & 0x7f; ret = data[1] | ((data[0] & 0x7f) << 8);
if (*data & 0x80) data += 2;
{
++data;
thisPart = thisPart * 256 + *data;
if (thisPart == 0)
return -1;
}
if (thisPart == 32767)
{
ret += 32767;
data += 2;
continue;
}
ret += thisPart;
data += 1;
break;
}
return ret;
}
static void EncodeRLE(std::vector<uint8_t>& vecOut, uint32_t val)
{
while (val >= 32767)
{
vecOut.push_back(0xff);
vecOut.push_back(0xff);
vecOut.push_back(0);
val -= 32767;
}
if (val >= 128)
{
vecOut.push_back(uint8_t(val / 256) | 0x80);
vecOut.push_back(uint8_t(val % 256));
} }
else else
vecOut.push_back(uint8_t(val)); {
} ret = data[0];
data += 1;
static int32_t DecodeContinuousRLE(const unsigned char*& data) }
{
int32_t ret = int32_t(DecodeRLE(data));
if (ret >= 16384)
return ret - 32767;
return ret; return ret;
} }
static void EncodeContinuousRLE(std::vector<uint8_t>& vecOut, int32_t val) static void EncodeUnsignedValue(std::vector<uint8_t>& vecOut, uint16_t val)
{ {
if (val < 0) if (val >= 128)
val += 32767; {
EncodeRLE(vecOut, uint32_t(val)); vecOut.push_back(0x80 | ((val >> 8) & 0x7f));
vecOut.push_back(val & 0xff);
}
else
{
vecOut.push_back(val & 0x7f);
}
}
static int16_t DecodeSignedValue(const unsigned char*& data)
{
int16_t ret;
if (data[0] & 0x80)
{
ret = data[1] | ((data[0] & 0x7f) << 8);
ret |= ((ret << 1) & 0x8000);
data += 2;
}
else
{
ret = int8_t(data[0] | ((data[0] << 1) & 0x80));
data += 1;
}
return ret;
}
static void EncodeSignedValue(std::vector<uint8_t>& vecOut, int16_t val)
{
if (val >= 64 || val < -64)
{
vecOut.push_back(0x80 | ((val >> 8) & 0x7f));
vecOut.push_back(val & 0xff);
}
else
{
vecOut.push_back(val & 0x7f);
}
} }
static uint32_t DecodeTimeRLE(const unsigned char*& data) static uint32_t DecodeTimeRLE(const unsigned char*& data)
@ -719,22 +714,20 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
{ {
while (true) while (true)
{ {
/* See if there's an upcoming pitch change in this interval */ /* Update pitch */
const unsigned char* ptr = trk.m_pitchWheelData; trk.m_pitchVal += trk.m_nextPitchDelta;
uint32_t deltaTicks = DecodeRLE(ptr); events.emplace(regStart + trk.m_nextPitchTick,
if (deltaTicks != 0xffffffff) Event{PitchEvent{}, trk.m_midiChan,
clamp(0, trk.m_pitchVal + 0x2000, 0x4000)});
if (trk.m_pitchWheelData[0] != 0x80 || trk.m_pitchWheelData[1] != 0x00)
{ {
int32_t nextTick = trk.m_lastPitchTick + deltaTicks; trk.m_nextPitchTick += DecodeUnsignedValue(trk.m_pitchWheelData);
int32_t pitchDelta = DecodeContinuousRLE(ptr); trk.m_nextPitchDelta = DecodeSignedValue(trk.m_pitchWheelData);
trk.m_lastPitchVal += pitchDelta;
trk.m_pitchWheelData = ptr;
trk.m_lastPitchTick = nextTick;
events.emplace(regStart + nextTick,
Event{PitchEvent{}, trk.m_midiChan,
clamp(0, trk.m_lastPitchVal / 2 + 0x2000, 0x4000)});
} }
else else
{
break; break;
}
} }
} }
@ -743,22 +736,20 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
{ {
while (true) while (true)
{ {
/* See if there's an upcoming modulation change in this interval */ /* Update modulation */
const unsigned char* ptr = trk.m_modWheelData; trk.m_modVal += trk.m_nextModDelta;
uint32_t deltaTicks = DecodeRLE(ptr); events.emplace(regStart + trk.m_nextModTick,
if (deltaTicks != 0xffffffff) Event{CtrlEvent{}, trk.m_midiChan, 1,
uint8_t(clamp(0, trk.m_modVal / 128, 127)), 0});
if (trk.m_modWheelData[0] != 0x80 || trk.m_modWheelData[1] != 0x00)
{ {
int32_t nextTick = trk.m_lastModTick + deltaTicks; trk.m_nextModTick += DecodeUnsignedValue(trk.m_modWheelData);
int32_t modDelta = DecodeContinuousRLE(ptr); trk.m_nextModDelta = DecodeSignedValue(trk.m_modWheelData);
trk.m_lastModVal += modDelta;
trk.m_modWheelData = ptr;
trk.m_lastModTick = nextTick;
events.emplace(regStart + nextTick,
Event{CtrlEvent{}, trk.m_midiChan, 1,
uint8_t(clamp(0, trk.m_lastModVal * 128 / 16384, 127)), 0});
} }
else else
{
break; break;
}
} }
} }
@ -1088,10 +1079,10 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
{ {
if (event.second.noteOrCtrl == 1) if (event.second.noteOrCtrl == 1)
{ {
EncodeRLE(region.modBuf, uint32_t(eventTick - lastModTick)); EncodeUnsignedValue(region.modBuf, uint32_t(eventTick - lastModTick));
lastModTick = eventTick; lastModTick = eventTick;
int newMod = event.second.velOrVal * 16384 / 128; int newMod = event.second.velOrVal * 128;
EncodeContinuousRLE(region.modBuf, newMod - lastModVal); EncodeSignedValue(region.modBuf, newMod - lastModVal);
lastModVal = newMod; lastModVal = newMod;
} }
else else
@ -1157,10 +1148,10 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
} }
case Event::Type::Pitch: case Event::Type::Pitch:
{ {
EncodeRLE(region.pitchBuf, uint32_t(eventTick - lastPitchTick)); EncodeUnsignedValue(region.pitchBuf, uint32_t(eventTick - lastPitchTick));
lastPitchTick = eventTick; lastPitchTick = eventTick;
int newPitch = (event.second.pitchBend - 0x2000) * 2; int newPitch = event.second.pitchBend - 0x2000;
EncodeContinuousRLE(region.pitchBuf, newPitch - lastPitchVal); EncodeSignedValue(region.pitchBuf, newPitch - lastPitchVal);
lastPitchVal = newPitch; lastPitchVal = newPitch;
break; break;
} }

View File

@ -6,44 +6,36 @@
namespace amuse namespace amuse
{ {
static uint32_t DecodeRLE(const unsigned char*& data) static uint16_t DecodeUnsignedValue(const unsigned char*& data)
{ {
uint32_t ret = 0; uint16_t ret;
if (data[0] & 0x80)
while (true)
{ {
uint32_t thisPart = *data & 0x7f; ret = data[1] | ((data[0] & 0x7f) << 8);
if (*data & 0x80) data += 2;
{ }
++data; else
thisPart = thisPart * 256 + *data; {
if (thisPart == 0) ret = data[0];
{ data += 1;
++data;
return -1;
}
}
if (thisPart == 32767)
{
ret += 32767;
data += 2;
continue;
}
ret += thisPart;
data += 1;
break;
} }
return ret; return ret;
} }
static int32_t DecodeContinuousRLE(const unsigned char*& data) static int16_t DecodeSignedValue(const unsigned char*& data)
{ {
int32_t ret = int32_t(DecodeRLE(data)); int16_t ret;
if (ret >= 16384) if (data[0] & 0x80)
return ret - 32767; {
ret = data[1] | ((data[0] & 0x7f) << 8);
ret |= ((ret << 1) & 0x8000);
data += 2;
}
else
{
ret = int8_t(data[0] | ((data[0] << 1) & 0x80));
data += 1;
}
return ret; return ret;
} }
@ -118,20 +110,39 @@ void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
header.swapBig(); header.swapBig();
m_data += 12; m_data += 12;
m_pitchWheelData = nullptr;
m_nextPitchTick = 0x7fffffff;
m_nextPitchDelta = 0;
if (header.m_pitchOff) if (header.m_pitchOff)
{
m_pitchWheelData = m_parent->m_songData + header.m_pitchOff; m_pitchWheelData = m_parent->m_songData + header.m_pitchOff;
if (m_pitchWheelData[0] != 0x80 || m_pitchWheelData[1] != 0x00)
{
m_nextPitchTick = m_parent->m_curTick + DecodeUnsignedValue(m_pitchWheelData);
m_nextPitchDelta = DecodeSignedValue(m_pitchWheelData);
}
}
m_modWheelData = nullptr;
m_nextModTick = 0x7fffffff;
m_nextModDelta = 0;
if (header.m_modOff) if (header.m_modOff)
{
m_modWheelData = m_parent->m_songData + header.m_modOff; m_modWheelData = m_parent->m_songData + header.m_modOff;
if (m_modWheelData[0] != 0x80 || m_modWheelData[1] != 0x00)
{
m_nextModTick = m_parent->m_curTick + DecodeUnsignedValue(m_modWheelData);
m_nextModDelta = DecodeSignedValue(m_modWheelData);
}
}
m_eventWaitCountdown = 0; m_eventWaitCountdown = 0;
m_lastPitchTick = m_parent->m_curTick; m_pitchVal = 0;
m_lastPitchVal = 0; m_modVal = 0;
m_lastModTick = m_parent->m_curTick;
m_lastModVal = 0;
if (seq) if (seq)
{ {
seq->setPitchWheel(m_midiChan, clamp(-1.f, m_lastPitchVal / 32768.f, 1.f)); seq->setPitchWheel(m_midiChan, clamp(-1.f, m_pitchVal / 32768.f, 1.f));
seq->setCtrlValue(m_midiChan, 1, clamp(0, m_lastModVal * 128 / 16384, 127)); seq->setCtrlValue(m_midiChan, 1, clamp(0, m_modVal * 128 / 16384, 127));
} }
if (m_parent->m_sngVersion == 1) if (m_parent->m_sngVersion == 1)
m_eventWaitCountdown = int32_t(DecodeTimeRLE(m_data)); m_eventWaitCountdown = int32_t(DecodeTimeRLE(m_data));
@ -219,10 +230,12 @@ int SongState::DetectVersion(const unsigned char* ptr, bool& isBig)
if (header.m_pitchOff) if (header.m_pitchOff)
{ {
const unsigned char* dptr = ptr + header.m_pitchOff; const unsigned char* dptr = ptr + header.m_pitchOff;
while (DecodeRLE(dptr) != 0xffffffff) while (dptr[0] != 0x80 || dptr[1] != 0x00)
{ {
DecodeContinuousRLE(dptr); DecodeUnsignedValue(dptr);
DecodeSignedValue(dptr);
} }
dptr += 2;
if (dptr >= (expectedEnd - 4) && (dptr <= expectedEnd)) if (dptr >= (expectedEnd - 4) && (dptr <= expectedEnd))
continue; continue;
} }
@ -231,10 +244,12 @@ int SongState::DetectVersion(const unsigned char* ptr, bool& isBig)
if (header.m_modOff) if (header.m_modOff)
{ {
const unsigned char* dptr = ptr + header.m_modOff; const unsigned char* dptr = ptr + header.m_modOff;
while (DecodeRLE(dptr) != 0xffffffff) while (dptr[0] != 0x80 || dptr[1] != 0x00)
{ {
DecodeContinuousRLE(dptr); DecodeUnsignedValue(dptr);
DecodeSignedValue(dptr);
} }
dptr += 2;
if (dptr >= (expectedEnd - 4) && (dptr <= expectedEnd)) if (dptr >= (expectedEnd - 4) && (dptr <= expectedEnd))
continue; continue;
} }
@ -404,28 +419,24 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
while (pitchTick < endTick) while (pitchTick < endTick)
{ {
/* See if there's an upcoming pitch change in this interval */ /* See if there's an upcoming pitch change in this interval */
const unsigned char* ptr = m_pitchWheelData; int32_t nextTick = m_nextPitchTick;
uint32_t deltaTicks = DecodeRLE(ptr); if (pitchTick + remPitchTicks > nextTick)
if (deltaTicks != 0xffffffff)
{ {
int32_t nextTick = m_lastPitchTick + deltaTicks; /* Update pitch */
if (pitchTick + remPitchTicks > nextTick) m_pitchVal += m_nextPitchDelta;
seq.setPitchWheel(m_midiChan, clamp(-1.f, m_pitchVal / 8191.f, 1.f));
if (m_pitchWheelData[0] != 0x80 || m_pitchWheelData[1] != 0x00)
{ {
/* Update pitch */ m_nextPitchTick += DecodeUnsignedValue(m_pitchWheelData);
int32_t pitchDelta = DecodeContinuousRLE(ptr); m_nextPitchDelta = DecodeSignedValue(m_pitchWheelData);
m_lastPitchVal += pitchDelta; }
m_pitchWheelData = ptr; else
m_lastPitchTick = nextTick; {
remPitchTicks -= (nextTick - pitchTick); m_nextPitchTick = 0x7fffffff;
pitchTick = nextTick;
seq.setPitchWheel(m_midiChan, clamp(-1.f, m_lastPitchVal / 32768.f, 1.f));
continue;
} }
remPitchTicks -= (nextTick - pitchTick);
pitchTick = nextTick;
} }
else remPitchTicks -= (nextTick - pitchTick);
break; pitchTick = nextTick;
} }
} }
@ -437,28 +448,24 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
while (modTick < endTick) while (modTick < endTick)
{ {
/* See if there's an upcoming modulation change in this interval */ /* See if there's an upcoming modulation change in this interval */
const unsigned char* ptr = m_modWheelData; int32_t nextTick = m_nextModTick;
uint32_t deltaTicks = DecodeRLE(ptr); if (modTick + remModTicks > nextTick)
if (deltaTicks != 0xffffffff)
{ {
int32_t nextTick = m_lastModTick + deltaTicks; /* Update modulation */
if (modTick + remModTicks > nextTick) m_modVal += m_nextModDelta;
seq.setCtrlValue(m_midiChan, 1, clamp(0, m_modVal / 128, 127));
if (m_modWheelData[0] != 0x80 || m_modWheelData[1] != 0x00)
{ {
/* Update modulation */ m_nextModTick += DecodeUnsignedValue(m_modWheelData);
int32_t modDelta = DecodeContinuousRLE(ptr); m_nextModDelta = DecodeSignedValue(m_modWheelData);
m_lastModVal += modDelta; }
m_modWheelData = ptr; else
m_lastModTick = nextTick; {
remModTicks -= (nextTick - modTick); m_nextModTick = 0x7fffffff;
modTick = nextTick;
seq.setCtrlValue(m_midiChan, 1, clamp(0, m_lastModVal * 128 / 16384, 127));
continue;
} }
remModTicks -= (nextTick - modTick);
modTick = nextTick;
} }
else remModTicks -= (nextTick - modTick);
break; modTick = nextTick;
} }
} }

View File

@ -1458,6 +1458,39 @@ void Voice::_notifyCtrlChange(uint8_t ctrl, int8_t val)
{ {
m_state.m_curMod = uint8_t(val); m_state.m_curMod = uint8_t(val);
} }
else if (ctrl == 0x64)
{
// RPN LSB
m_rpn &= ~0x7f;
m_rpn |= val;
}
else if (ctrl == 0x65)
{
// RPN MSB
m_rpn &= ~0x3f80;
m_rpn |= val << 7;
}
else if (ctrl == 0x6)
{
if (m_rpn == 0)
m_pitchWheelUp = m_pitchWheelDown = val * 100;
}
else if (ctrl == 0x60)
{
if (m_rpn == 0)
{
m_pitchWheelUp += 100;
m_pitchWheelDown += 100;
}
}
else if (ctrl == 0x61)
{
if (m_rpn == 0)
{
m_pitchWheelUp -= 100;
m_pitchWheelDown -= 100;
}
}
for (ObjToken<Voice>& vox : m_childVoices) for (ObjToken<Voice>& vox : m_childVoices)
vox->_notifyCtrlChange(ctrl, val); vox->_notifyCtrlChange(ctrl, val);