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
StatusBarWidget.hpp
ProjectModel.hpp
ProjectStatistics.hpp
EditorWidget.hpp
SoundMacroEditor.hpp
ADSREditor.hpp
@ -55,7 +54,6 @@ add_executable(amuse-gui WIN32 MACOSX_BUNDLE
KeyboardWidget.hpp KeyboardWidget.cpp
StatusBarWidget.hpp
ProjectModel.hpp ProjectModel.cpp
ProjectStatistics.hpp ProjectStatistics.cpp
EditorWidget.hpp EditorWidget.cpp
SoundMacroEditor.hpp SoundMacroEditor.cpp
ADSREditor.hpp ADSREditor.cpp

View File

@ -4,6 +4,99 @@
#include <QScrollBar>
#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)
: 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::CollectionNode* smColl = group->getCollectionOfType(ProjectModel::INode::Type::SoundMacro);
int idx = static_cast<EditorFieldProjectNode*>(editor)->currentIndex();
if (idx == 0)
layer.macro.id = amuse::SoundMacroId();
else
layer.macro.id = smColl->idOfIndex(idx - 1);
amuse::SoundMacroId id;
if (idx != 0)
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});
}
@ -123,42 +219,50 @@ bool LayersModel::setData(const QModelIndex& index, const QVariant& value, int r
{
if (!m_node || role != Qt::EditRole)
return false;
amuse::LayerMapping& layer = (*m_node->m_obj)[index.row()];
const amuse::LayerMapping& layer = (*m_node->m_obj)[index.row()];
switch (index.column())
{
case 1:
layer.keyLo = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
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:
case 0:
if (layer.macro.id == value.toInt())
return false;
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;
}
@ -210,6 +314,32 @@ Qt::DropActions LayersModel::supportedDragActions() const
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,
int row, int column, const QModelIndex& parent)
{
@ -257,10 +387,75 @@ bool LayersModel::dropMimeData(const QMimeData* data, Qt::DropAction action,
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;
}
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)
{
if (!m_node)
@ -316,15 +511,45 @@ bool LayersModel::removeRows(int row, int count, const QModelIndex& parent)
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)
: QAbstractTableModel(parent)
{}
void LayersTableView::deleteSelection()
{
QModelIndexList list;
while (!(list = selectionModel()->selectedRows()).isEmpty())
model()->removeRow(list.back().row());
QModelIndexList list = selectionModel()->selectedRows();
if (list.isEmpty())
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)
@ -396,10 +621,13 @@ void LayersEditor::resizeEvent(QResizeEvent* ev)
void LayersEditor::doAdd()
{
QModelIndex idx = m_tableView.selectionModel()->currentIndex();
std::vector<std::pair<amuse::LayerMapping, int>> data;
if (!idx.isValid())
m_model.insertRow(m_model.rowCount() - 1);
data.push_back(std::make_pair(amuse::LayerMapping{}, m_model.rowCount() - 1));
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()

View File

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

View File

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

View File

@ -269,6 +269,33 @@ Qt::ItemFlags PageObjectProxyModel::flags(const QModelIndex& proxyIndex) const
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)
: m_name(name)
{
@ -401,7 +428,27 @@ bool ProjectModel::openSongsData()
amuse::SongId id = uint16_t(strtoul(p.first.c_str(), &endPtr, 0));
if (endPtr == p.first.c_str() || id.id == 0xffff)
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];
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()));
if (!w.hasError())
@ -1182,37 +1229,77 @@ ProjectModel::GroupNode* ProjectModel::getGroupOfSfx(amuse::SFXId id) const
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
{
auto search = m_midiFiles.find(id);
if (search == m_midiFiles.cend())
return {};
return search->second;
return search->second.m_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

View File

@ -57,32 +57,10 @@ public:
{
std::unordered_map<amuse::SongId, std::string> m_songIDs;
std::unordered_map<amuse::SFXId, std::string> m_sfxIDs;
void registerSongName(amuse::SongId id) const
{
auto search = m_songIDs.find(id);
if (search != m_songIDs.cend())
amuse::SongId::CurNameDB->registerPair(search->second, id);
}
void unregisterSongName(amuse::SongId id)
{
auto search = amuse::SongId::CurNameDB->m_idToString.find(id);
if (search != amuse::SongId::CurNameDB->m_idToString.cend())
m_songIDs[id] = search->second;
amuse::SongId::CurNameDB->remove(id);
}
void registerSFXName(amuse::SongId id) const
{
auto search = m_sfxIDs.find(id);
if (search != m_sfxIDs.cend())
amuse::SFXId::CurNameDB->registerPair(search->second, id);
}
void unregisterSFXName(amuse::SongId id)
{
auto search = amuse::SFXId::CurNameDB->m_idToString.find(id);
if (search != amuse::SFXId::CurNameDB->m_idToString.cend())
m_sfxIDs[id] = search->second;
amuse::SFXId::CurNameDB->remove(id);
}
void registerSongName(amuse::SongId id) const;
void unregisterSongName(amuse::SongId id);
void registerSFXName(amuse::SongId id) const;
void unregisterSFXName(amuse::SongId id);
void clear()
{
m_songIDs.clear();
@ -98,7 +76,12 @@ private:
amuse::ProjectDatabase m_projectDatabase;
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:
class INode : public amuse::IObj
@ -436,9 +419,12 @@ public:
PageObjectProxyModel* getPageObjectProxy() { return &m_pageObjectProxy; }
GroupNode* getGroupOfSfx(amuse::SFXId id) const;
GroupNode* getGroupOfSong(amuse::SongId id) const;
QString getMIDIPathOfSong(amuse::SongId id) const;
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;
};

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 trans = sampleHeight / 2.0;
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;
for (qreal i = 0.0; i < deviceWidth; i += increment)
@ -183,12 +193,18 @@ void SampleView::paintEvent(QPaintEvent* ev)
avgPeak = iterateSampleInterval(deviceSamplesPerPx);
else
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.drawLine(QPointF(rectStart + i, avgPeak.first.second * scale + trans),
QPointF(rectStart + i, avgPeak.second.second * scale + trans));
painter.drawLine(QPointF(rectStart + i, drawAvgPeak.first.second * scale + trans),
QPointF(rectStart + i, drawAvgPeak.second.second * scale + trans));
painter.setPen(avgPen);
painter.drawLine(QPointF(rectStart + i, avgPeak.first.first * scale + trans),
QPointF(rectStart + i, avgPeak.second.first * scale + trans));
painter.drawLine(QPointF(rectStart + i, drawAvgPeak.first.first * scale + trans),
QPointF(rectStart + i, drawAvgPeak.second.first * scale + trans));
lastAvgPeak = avgPeak;
}
}

View File

@ -2,6 +2,232 @@
#include "MainWindow.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)
: QStyledItemDelegate(parent) {}
@ -33,19 +259,23 @@ void PageObjectDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, co
const PageModel* model = static_cast<const PageModel*>(m);
auto entry = model->m_sorted[index.row()];
int idx = static_cast<EditorFieldPageObjectNode*>(editor)->currentIndex();
if (idx == 0)
{
entry->second.objId.id = amuse::ObjectId();
}
else
amuse::ObjectId id;
if (idx != 0)
{
ProjectModel::BasePoolObjectNode* node = static_cast<ProjectModel::BasePoolObjectNode*>(
g_MainWindow->projectModel()->node(
g_MainWindow->projectModel()->getPageObjectProxy()->mapToSource(
g_MainWindow->projectModel()->getPageObjectProxy()->index(idx, 0,
static_cast<EditorFieldPageObjectNode*>(editor)->rootModelIndex()))));
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);
}
@ -106,7 +336,10 @@ void MIDIFileDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, cons
MIDIFileFieldWidget* widget = static_cast<MIDIFileFieldWidget*>(editor);
const SetupListModel* model = static_cast<const SetupListModel*>(index.model());
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);
}
@ -237,9 +470,10 @@ bool PageModel::setData(const QModelIndex& index, const QVariant& value, int rol
return false;
}
emit layoutAboutToBeChanged();
auto nh = map.extract(entry->first);
nh.key() = value.toInt();
map.insert(std::move(nh));
g_MainWindow->pushUndoCommand(new PageDataChangeUndoCommand(m_node.get(),
tr("Change %1").arg(headerData(0, Qt::Horizontal).toString()), m_drum, entry->first, 0, value.toInt()));
_buildSortedList();
QModelIndex newIndex = _indexOfProgram(value.toInt());
changePersistentIndex(index, newIndex);
@ -247,19 +481,28 @@ bool PageModel::setData(const QModelIndex& index, const QVariant& value, int rol
emit dataChanged(newIndex, newIndex);
return true;
}
case 2:
entry->second.priority = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 3:
entry->second.maxVoices = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
default:
case 1:
if (entry->second.objId.id == value.toInt())
return false;
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
@ -292,64 +535,99 @@ Qt::ItemFlags PageModel::flags(const QModelIndex& index) const
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)
return false;
if (m_sorted.size() >= 128)
return false;
return 0;
auto& map = _getMap();
if (m_sorted.empty())
{
beginInsertRows(parent, 0, count - 1);
for (int i = 0; i < count; ++i)
map.emplace(std::make_pair(i, amuse::SongGroupIndex::PageEntry{}));
int idx = _hypotheticalIndexOfProgram(data.first);
beginInsertRows(QModelIndex(), idx, idx);
map.emplace(data);
_buildSortedList();
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;
return idx;
}
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)
return false;
return ret;
auto& map = _getMap();
beginRemoveRows(parent, row, row + count - 1);
std::vector<uint8_t> removeProgs;
removeProgs.reserve(count);
for (int i = 0; i < count; ++i)
removeProgs.push_back(m_sorted[row+i].m_it->first);
for (uint8_t prog : removeProgs)
map.erase(prog);
int idx = _hypotheticalIndexOfProgram(prog);
beginRemoveRows(QModelIndex(), idx, idx);
ret.first = prog;
auto search = map.find(prog);
if (search != map.cend())
{
ret.second = search->second;
map.erase(search);
}
_buildSortedList();
endRemoveRows();
return true;
return ret;
}
PageModel::PageModel(bool drum, QObject* parent)
@ -392,7 +670,7 @@ void SetupListModel::loadData(ProjectModel::SongGroupNode* node)
{
beginResetModel();
m_node = node;
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
g_MainWindow->projectModel()->setIdDatabases(m_node.get());
_buildSortedList();
endResetModel();
}
@ -434,7 +712,7 @@ QVariant SetupListModel::data(const QModelIndex& index, int role) const
{
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();
}
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)
return false;
auto& map = _getMap();
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();
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())
@ -466,7 +743,8 @@ bool SetupListModel::setData(const QModelIndex& index, const QVariant& value, in
return false;
}
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();
QModelIndex newIndex = _indexOfSong(entry.m_it->first);
changePersistentIndex(index, newIndex);
@ -500,46 +778,112 @@ Qt::ItemFlags SetupListModel::flags(const QModelIndex& index) const
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)
return false;
return 0;
auto& map = _getMap();
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
for (int i = 0; i < count; ++i)
{
amuse::ObjectId songId = amuse::SongId::CurNameDB->generateId(amuse::NameDB::Type::Song);
std::string songName = amuse::SongId::CurNameDB->generateName(songId, amuse::NameDB::Type::Song);
int insertIdx = _hypotheticalIndexOfSong(songName);
beginInsertRows(parent, insertIdx, insertIdx);
amuse::SongId::CurNameDB->registerPair(songName, songId);
map.emplace(std::make_pair(songId, std::array<amuse::SongGroupIndex::MIDISetup, 16>{}));
g_MainWindow->projectModel()->setIdDatabases(m_node.get());
if (std::get<0>(data).id == 0xffff)
std::get<0>(data) = amuse::SongId::CurNameDB->generateId(amuse::NameDB::Type::Song);
g_MainWindow->projectModel()->_allocateSongId(std::get<0>(data), std::get<1>(data));
int idx = _hypotheticalIndexOfSong(std::get<1>(data));
beginInsertRows(QModelIndex(), idx, idx);
map[std::get<0>(data)] = std::get<2>(data);
_buildSortedList();
endInsertRows();
++row;
}
return true;
return idx;
}
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)
return false;
return ret;
auto& map = _getMap();
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
beginRemoveRows(parent, row, row + count - 1);
std::vector<amuse::SongId> removeSongs;
removeSongs.reserve(count);
for (int i = 0; i < count; ++i)
removeSongs.push_back(m_sorted[row+i].m_it->first);
for (amuse::SongId song : removeSongs)
g_MainWindow->projectModel()->setIdDatabases(m_node.get());
std::get<0>(ret) = id;
std::get<1>(ret) = amuse::SongId::CurNameDB->resolveNameFromId(id);
int idx = _indexOfSong(id).row();
beginRemoveRows(QModelIndex(), idx, idx);
auto search = map.find(id);
if (search != map.end())
{
amuse::SongId::CurNameDB->remove(song);
map.erase(song);
std::get<2>(ret) = search->second;
g_MainWindow->projectModel()->deallocateSongId(id);
map.erase(search);
}
_buildSortedList();
endRemoveRows();
return true;
return ret;
}
SetupListModel::SetupListModel(QObject* parent)
@ -615,30 +959,37 @@ bool SetupModel::setData(const QModelIndex& index, const QVariant& value, int ro
switch (index.column())
{
case 0:
entry.programNo = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
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:
if (entry.programNo == value.toInt())
return false;
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
@ -688,9 +1039,17 @@ SetupModel::SetupModel(QObject* parent)
void PageTableView::deleteSelection()
{
QModelIndexList list;
while (!(list = selectionModel()->selectedRows()).isEmpty())
model()->removeRow(list.back().row());
QModelIndexList list = selectionModel()->selectedRows();
if (list.isEmpty())
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)
@ -740,9 +1099,20 @@ void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* tabl
void SetupTableView::deleteSelection()
{
QModelIndexList list;
while (!(list = m_listView->selectionModel()->selectedRows()).isEmpty())
m_listView->model()->removeRow(list.back().row());
QModelIndexList list = m_listView->selectionModel()->selectedRows();
if (list.isEmpty())
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)
@ -898,20 +1268,49 @@ void SongGroupEditor::doAdd()
if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
{
QModelIndex idx = table->selectionModel()->currentIndex();
int row;
if (!idx.isValid())
table->model()->insertRow(table->model()->rowCount() - 1);
row = table->model()->rowCount() - 1;
else
table->model()->insertRow(idx.row());
if (PageTableView* ctable = qobject_cast<PageTableView*>(table))
m_addRemoveButtons.addAction()->setDisabled(ctable->model()->rowCount() >= 128);
row = idx.row();
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()))
{
QModelIndex idx = table->m_listView->selectionModel()->currentIndex();
if (!idx.isValid())
table->m_listView->model()->insertRow(table->m_listView->model()->rowCount() - 1);
else
table->m_listView->model()->insertRow(idx.row());
SetupListModel* model = static_cast<SetupListModel*>(table->m_listView->model());
g_MainWindow->projectModel()->setIdDatabases(model->m_node.get());
std::vector<std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>> data;
auto songId = g_MainWindow->projectModel()->allocateSongId();
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
friend class SongGroupEditor;
friend class PageObjectDelegate;
friend class PageTableView;
amuse::ObjToken<ProjectModel::SongGroupNode> m_node;
struct Iterator
{
@ -90,8 +91,8 @@ public:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex& index) const;
bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex());
int _insertRow(const std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>& data);
std::pair<uint8_t, amuse::SongGroupIndex::PageEntry> _removeRow(uint8_t prog);
};
class SetupListModel : public QAbstractTableModel
@ -99,6 +100,8 @@ class SetupListModel : public QAbstractTableModel
Q_OBJECT
friend class SongGroupEditor;
friend class MIDIFileDelegate;
friend class SetupTableView;
friend class SetupModel;
amuse::ObjToken<ProjectModel::SongGroupNode> m_node;
struct Iterator
{
@ -138,8 +141,10 @@ public:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex& index) const;
bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex());
int _insertRow(
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
@ -177,6 +182,7 @@ class SetupTableView : public QSplitter
{
Q_OBJECT
friend class SongGroupEditor;
friend class SetupRowUndoCommand;
QTableView* m_listView;
QTableView* m_tableView;
MIDIFileDelegate m_midiDelegate;
@ -239,6 +245,7 @@ public slots:
class SongGroupEditor : public EditorWidget
{
Q_OBJECT
friend class SetupModel;
PageModel m_normPages;
PageModel m_drumPages;
SetupListModel m_setupList;

View File

@ -1,6 +1,124 @@
#include "SoundGroupEditor.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)
: QStyledItemDelegate(parent) {}
@ -32,19 +150,23 @@ void SFXObjectDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, con
const SFXModel* model = static_cast<const SFXModel*>(m);
auto entry = model->m_sorted[index.row()];
int idx = static_cast<EditorFieldPageObjectNode*>(editor)->currentIndex();
if (idx == 0)
{
entry->second.objId.id = amuse::ObjectId();
}
else
amuse::ObjectId id;
if (idx != 0)
{
ProjectModel::BasePoolObjectNode* node = static_cast<ProjectModel::BasePoolObjectNode*>(
g_MainWindow->projectModel()->node(
g_MainWindow->projectModel()->getPageObjectProxy()->mapToSource(
g_MainWindow->projectModel()->getPageObjectProxy()->index(idx, 0,
static_cast<EditorFieldPageObjectNode*>(editor)->rootModelIndex()))));
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);
}
@ -132,7 +254,7 @@ QVariant SFXModel::data(const QModelIndex& index, int role) const
{
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();
}
case 1:
@ -172,7 +294,7 @@ bool SFXModel::setData(const QModelIndex& index, const QVariant& value, int role
{
case 0:
{
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
g_MainWindow->projectModel()->setIdDatabases(m_node.get());
auto utf8key = value.toString().toUtf8();
std::unordered_map<std::string, amuse::ObjectId>::iterator idIt;
if ((idIt = amuse::SFXId::CurNameDB->m_stringToId.find(utf8key.data())) != amuse::SFXId::CurNameDB->m_stringToId.cend())
@ -184,39 +306,45 @@ bool SFXModel::setData(const QModelIndex& index, const QVariant& value, int role
return false;
}
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();
QModelIndex newIndex = _indexOfSFX(entry.m_it->first);
QModelIndex newIndex = _indexOfSFX(entry->first);
changePersistentIndex(index, newIndex);
emit layoutChanged();
emit dataChanged(newIndex, newIndex, {Qt::DisplayRole, Qt::EditRole});
return true;
}
case 2:
entry->second.priority = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 3:
entry->second.maxVoices = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 4:
entry->second.defVel = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 5:
entry->second.panning = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
case 6:
entry->second.defKey = value.toInt();
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
default:
if (entry->second.priority == value.toInt())
return false;
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
@ -255,42 +383,104 @@ Qt::ItemFlags SFXModel::flags(const QModelIndex& index) const
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)
return false;
return 0;
auto& map = _getMap();
g_MainWindow->projectModel()->getGroupNode(m_node.get())->getAudioGroup()->setIdDatabases();
for (int i = 0; i < count; ++i)
{
amuse::ObjectId sfxId = amuse::SFXId::CurNameDB->generateId(amuse::NameDB::Type::SFX);
std::string sfxName = amuse::SFXId::CurNameDB->generateName(sfxId, amuse::NameDB::Type::SFX);
int insertIdx = _hypotheticalIndexOfSFX(sfxName);
beginInsertRows(parent, insertIdx, insertIdx);
amuse::SFXId::CurNameDB->registerPair(sfxName, sfxId);
map.emplace(std::make_pair(sfxId, amuse::SFXGroupIndex::SFXEntry{}));
g_MainWindow->projectModel()->setIdDatabases(m_node.get());
amuse::SFXId::CurNameDB->registerPair(std::get<1>(data), std::get<0>(data));
int idx = _hypotheticalIndexOfSFX(std::get<1>(data));
beginInsertRows(QModelIndex(), idx, idx);
map.emplace(std::make_pair(std::get<0>(data), std::get<2>(data)));
_buildSortedList();
endInsertRows();
++row;
}
return true;
return idx;
}
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)
return false;
return ret;
auto& map = _getMap();
beginRemoveRows(parent, row, row + count - 1);
std::vector<amuse::SFXId> removeSFXs;
removeSFXs.reserve(count);
for (int i = 0; i < count; ++i)
removeSFXs.push_back(m_sorted[row+i].m_it->first);
for (amuse::SFXId sfx : removeSFXs)
map.erase(sfx);
g_MainWindow->projectModel()->setIdDatabases(m_node.get());
int idx = _indexOfSFX(sfx).row();
beginRemoveRows(QModelIndex(), idx, idx);
std::get<0>(ret) = sfx;
std::get<1>(ret) = amuse::SFXId::CurNameDB->resolveNameFromId(sfx);
auto search = map.find(sfx);
if (search != map.cend())
{
std::get<2>(ret) = search->second;
amuse::SFXId::CurNameDB->remove(sfx);
map.erase(search);
}
_buildSortedList();
endRemoveRows();
return true;
return ret;
}
SFXModel::SFXModel(QObject* parent)
@ -299,9 +489,20 @@ SFXModel::SFXModel(QObject* parent)
void SFXTableView::deleteSelection()
{
QModelIndexList list;
while (!(list = selectionModel()->selectedRows()).isEmpty())
model()->removeRow(list.back().row());
QModelIndexList list = selectionModel()->selectedRows();
if (list.isEmpty())
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)
@ -420,11 +621,13 @@ bool SoundGroupEditor::isItemEditEnabled() const
void SoundGroupEditor::doAdd()
{
QModelIndex idx = m_sfxTable->selectionModel()->currentIndex();
if (!idx.isValid())
m_sfxTable->model()->insertRow(m_sfxTable->model()->rowCount() - 1);
else
m_sfxTable->model()->insertRow(idx.row());
g_MainWindow->projectModel()->setIdDatabases(m_sfxs.m_node.get());
std::vector<std::tuple<amuse::SFXId, std::string, amuse::SFXGroupIndex::SFXEntry>> data;
amuse::SFXId sfxId = amuse::SFXId::CurNameDB->generateId(amuse::NameDB::Type::SFX);
std::string sfxName = amuse::SFXId::CurNameDB->generateName(sfxId, amuse::NameDB::Type::SFX);
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()
@ -440,9 +643,9 @@ void SoundGroupEditor::sfxDataChanged()
{
QModelIndex index = m_sfxs.index(idx, 1);
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);
}
++idx;

View File

@ -23,13 +23,14 @@ class SFXModel : public QAbstractTableModel
Q_OBJECT
friend class SoundGroupEditor;
friend class SFXObjectDelegate;
friend class SFXTableView;
amuse::ObjToken<ProjectModel::SoundGroupNode> m_node;
struct Iterator
{
using ItTp = std::unordered_map<amuse::SFXId, amuse::SFXGroupIndex::SFXEntry>::iterator;
ItTp m_it;
Iterator(ItTp it) : m_it(it) {}
ItTp::pointer operator->() { return m_it.operator->(); }
ItTp::pointer operator->() const { return m_it.operator->(); }
bool operator<(const Iterator& other) const
{
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;
Qt::ItemFlags flags(const QModelIndex& index) const;
bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex());
int _insertRow(const std::tuple<amuse::SFXId, std::string, amuse::SFXGroupIndex::SFXEntry>& data);
std::tuple<amuse::SFXId, std::string, amuse::SFXGroupIndex::SFXEntry> _removeRow(amuse::SFXId sfx);
};
class SFXTableView : public QTableView

View File

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

View File

@ -63,9 +63,11 @@ class Sequencer : public Entity
ObjToken<Voice> m_lastVoice;
int8_t m_ctrlVals[128] = {}; /**< MIDI controller values */
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 */
float m_curVol = 1.f; /**< Current volume 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();
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_pitchWheelData = nullptr; /**< Pointer to upcoming pitch 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_lastPitchVal = 0; /**< Last value of pitch */
uint32_t m_lastModTick = 0; /**< Last position of mod wheel change */
int32_t m_lastModVal = 0; /**< Last value of mod */
int32_t m_pitchVal = 0; /**< Accumulated value of pitch */
uint32_t m_nextPitchTick = 0; /**< Upcoming position of pitch wheel change */
int32_t m_nextPitchDelta = 0; /**< Upcoming delta value of pitch */
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 */
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_curSpan = -1.f; /**< Current surround pan of voice */
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_pitchWheelDown = 600; /**< Down range for pitchwheel control in cents */
int32_t m_pitchWheelUp = 200; /**< Up 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_curPitch; /**< Current base pitch in cents */
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 */
int8_t* m_extCtrlVals = nullptr; /**< MIDI Controller values (external storage) */
uint16_t m_rpn = 0; /**< Current RPN (only pitch-range 0x0000 supported) */
void _destroy();
bool _checkSamplePos(bool& looped);
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)->setPan(m_curPan);
(*ret)->setPitchWheel(m_curPitchWheel);
if (m_pitchWheelRange != -1)
(*ret)->setPitchWheelRange(m_pitchWheelRange, m_pitchWheelRange);
if (m_ctrlVals[64] > 64)
(*ret)->setPedal(true);
@ -336,6 +338,23 @@ void Sequencer::ChannelState::setCtrlValue(uint8_t ctrl, int8_t val)
case 10:
setPan(val / 64.f - 1.f);
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:
break;
}

View File

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

View File

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

View File

@ -1458,6 +1458,39 @@ void Voice::_notifyCtrlChange(uint8_t ctrl, int8_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)
vox->_notifyCtrlChange(ctrl, val);