Looping SNG support; bug fixes

This commit is contained in:
Jack Andersen 2018-09-08 11:34:01 -10:00
parent 25aacc9511
commit 81f0a91569
22 changed files with 1035 additions and 579 deletions

View File

@ -25,12 +25,26 @@ MainWindow::MainWindow(QWidget* parent)
m_navIt(m_navList.begin()), m_navIt(m_navList.begin()),
m_treeDelegate(*this, this), m_treeDelegate(*this, this),
m_mainMessenger(this), m_mainMessenger(this),
m_fileDialog(this), m_openDirectoryDialog(this),
m_openFileDialog(this),
m_newFileDialog(this),
m_undoStack(new QUndoStack(this)), m_undoStack(new QUndoStack(this)),
m_backgroundThread(this) m_backgroundThread(this)
{ {
m_backgroundThread.start(); m_backgroundThread.start();
m_newFileDialog.setAcceptMode(QFileDialog::AcceptSave);
m_newFileDialog.setFileMode(QFileDialog::AnyFile);
m_newFileDialog.setOption(QFileDialog::ShowDirsOnly, false);
m_openDirectoryDialog.setAcceptMode(QFileDialog::AcceptOpen);
m_openDirectoryDialog.setFileMode(QFileDialog::Directory);
m_openDirectoryDialog.setOption(QFileDialog::ShowDirsOnly, true);
m_openFileDialog.setAcceptMode(QFileDialog::AcceptOpen);
m_openFileDialog.setFileMode(QFileDialog::ExistingFile);
m_openFileDialog.setOption(QFileDialog::ShowDirsOnly, false);
m_ui.setupUi(this); m_ui.setupUi(this);
m_ui.splitter->setCollapsible(1, false); m_ui.splitter->setCollapsible(1, false);
QPalette palette = m_ui.projectOutlineFilter->palette(); QPalette palette = m_ui.projectOutlineFilter->palette();
@ -303,7 +317,9 @@ bool MainWindow::setProjectPath(const QString& path)
connect(m_ui.projectOutline->selectionModel(), connect(m_ui.projectOutline->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
this, SLOT(onOutlineSelectionChanged(const QItemSelection&, const QItemSelection&))); this, SLOT(onOutlineSelectionChanged(const QItemSelection&, const QItemSelection&)));
m_fileDialog.setDirectory(path); m_openDirectoryDialog.setDirectory(path);
m_openFileDialog.setDirectory(path);
m_newFileDialog.setDirectory(path);
m_ui.actionSave_Project->setEnabled(true); m_ui.actionSave_Project->setEnabled(true);
m_ui.actionRevert_Project->setEnabled(true); m_ui.actionRevert_Project->setEnabled(true);
m_ui.actionReload_Sample_Data->setEnabled(true); m_ui.actionReload_Sample_Data->setEnabled(true);
@ -801,16 +817,13 @@ void MainWindow::newAction()
if (!askAboutSave()) if (!askAboutSave())
return; return;
m_fileDialog.setWindowTitle(tr("New Project")); m_newFileDialog.setWindowTitle(tr("New Project"));
m_fileDialog.setAcceptMode(QFileDialog::AcceptSave); m_newFileDialog.open(this, SLOT(_newAction(const QString&)));
m_fileDialog.setFileMode(QFileDialog::AnyFile);
m_fileDialog.setOption(QFileDialog::ShowDirsOnly, false);
m_fileDialog.open(this, SLOT(_newAction(const QString&)));
} }
void MainWindow::_newAction(const QString& path) void MainWindow::_newAction(const QString& path)
{ {
m_fileDialog.close(); m_newFileDialog.close();
if (path.isEmpty()) if (path.isEmpty())
return; return;
if (!MkPath(path, m_mainMessenger)) if (!MkPath(path, m_mainMessenger))
@ -881,16 +894,13 @@ void MainWindow::openAction()
if (!askAboutSave()) if (!askAboutSave())
return; return;
m_fileDialog.setWindowTitle(tr("Open Project")); m_openDirectoryDialog.setWindowTitle(tr("Open Project"));
m_fileDialog.setAcceptMode(QFileDialog::AcceptOpen); m_openDirectoryDialog.open(this, SLOT(_openAction(const QString&)));
m_fileDialog.setFileMode(QFileDialog::Directory);
m_fileDialog.setOption(QFileDialog::ShowDirsOnly, true);
m_fileDialog.open(this, SLOT(_openAction(const QString&)));
} }
void MainWindow::_openAction(const QString& path) void MainWindow::_openAction(const QString& path)
{ {
m_fileDialog.close(); m_openDirectoryDialog.close();
if (path.isEmpty()) if (path.isEmpty())
return; return;
openProject(path); openProject(path);
@ -989,16 +999,13 @@ void MainWindow::reloadSampleDataAction()
void MainWindow::importAction() void MainWindow::importAction()
{ {
m_fileDialog.setWindowTitle(tr("Import Project")); m_openFileDialog.setWindowTitle(tr("Import Project"));
m_fileDialog.setAcceptMode(QFileDialog::AcceptOpen); m_openFileDialog.open(this, SLOT(_importAction(const QString&)));
m_fileDialog.setFileMode(QFileDialog::ExistingFile);
m_fileDialog.setOption(QFileDialog::ShowDirsOnly, false);
m_fileDialog.open(this, SLOT(_importAction(const QString&)));
} }
void MainWindow::_importAction(const QString& path) void MainWindow::_importAction(const QString& path)
{ {
m_fileDialog.close(); m_openFileDialog.close();
if (path.isEmpty()) if (path.isEmpty())
return; return;
@ -1131,16 +1138,13 @@ void MainWindow::importSongsAction()
if (!m_projectModel) if (!m_projectModel)
return; return;
m_fileDialog.setWindowTitle(tr("Import Songs")); m_openFileDialog.setWindowTitle(tr("Import Songs"));
m_fileDialog.setAcceptMode(QFileDialog::AcceptOpen); m_openFileDialog.open(this, SLOT(_importSongsAction(const QString&)));
m_fileDialog.setFileMode(QFileDialog::ExistingFile);
m_fileDialog.setOption(QFileDialog::ShowDirsOnly, false);
m_fileDialog.open(this, SLOT(_importSongsAction(const QString&)));
} }
void MainWindow::_importSongsAction(const QString& path) void MainWindow::_importSongsAction(const QString& path)
{ {
m_fileDialog.close(); m_openFileDialog.close();
if (path.isEmpty()) if (path.isEmpty())
return; return;
@ -1198,16 +1202,13 @@ void MainWindow::importHeadersAction()
if (confirm == QMessageBox::No) if (confirm == QMessageBox::No)
return; return;
m_fileDialog.setWindowTitle(tr("Import C Headers")); m_openDirectoryDialog.setWindowTitle(tr("Import C Headers"));
m_fileDialog.setAcceptMode(QFileDialog::AcceptOpen); m_openDirectoryDialog.open(this, SLOT(_importHeadersAction(const QString&)));
m_fileDialog.setFileMode(QFileDialog::Directory);
m_fileDialog.setOption(QFileDialog::ShowDirsOnly, true);
m_fileDialog.open(this, SLOT(_importHeadersAction(const QString&)));
} }
void MainWindow::_importHeadersAction(const QString& path) void MainWindow::_importHeadersAction(const QString& path)
{ {
m_fileDialog.close(); m_openDirectoryDialog.close();
if (path.isEmpty()) if (path.isEmpty())
return; return;
@ -1226,16 +1227,13 @@ void MainWindow::exportHeadersAction()
if (!m_projectModel) if (!m_projectModel)
return; return;
m_fileDialog.setWindowTitle(tr("Export C Headers")); m_openDirectoryDialog.setWindowTitle(tr("Export C Headers"));
m_fileDialog.setAcceptMode(QFileDialog::AcceptOpen); m_openDirectoryDialog.open(this, SLOT(_exportHeadersAction(const QString&)));
m_fileDialog.setFileMode(QFileDialog::Directory);
m_fileDialog.setOption(QFileDialog::ShowDirsOnly, true);
m_fileDialog.open(this, SLOT(_exportHeadersAction(const QString&)));
} }
void MainWindow::_exportHeadersAction(const QString& path) void MainWindow::_exportHeadersAction(const QString& path)
{ {
m_fileDialog.close(); m_openDirectoryDialog.close();
if (path.isEmpty()) if (path.isEmpty())
return; return;

View File

@ -117,7 +117,9 @@ class MainWindow : public QMainWindow
LayersEditor* m_layersEditor = nullptr; LayersEditor* m_layersEditor = nullptr;
SampleEditor* m_sampleEditor = nullptr; SampleEditor* m_sampleEditor = nullptr;
StudioSetupWidget* m_studioSetup = nullptr; StudioSetupWidget* m_studioSetup = nullptr;
QFileDialog m_fileDialog; QFileDialog m_openDirectoryDialog;
QFileDialog m_openFileDialog;
QFileDialog m_newFileDialog;
std::unique_ptr<boo::IAudioVoiceEngine> m_voxEngine; std::unique_ptr<boo::IAudioVoiceEngine> m_voxEngine;
std::unique_ptr<VoiceAllocator> m_voxAllocator; std::unique_ptr<VoiceAllocator> m_voxAllocator;
@ -189,7 +191,6 @@ public:
amuse::ObjToken<amuse::Sequencer> startSong(amuse::GroupId groupId, amuse::SongId songId, amuse::ObjToken<amuse::Sequencer> startSong(amuse::GroupId groupId, amuse::SongId songId,
const unsigned char* arrData); const unsigned char* arrData);
void pushUndoCommand(EditorUndoCommand* cmd); void pushUndoCommand(EditorUndoCommand* cmd);
QFileDialog& fileDialog() { return m_fileDialog; }
void updateFocus(); void updateFocus();
void aboutToDeleteNode(ProjectModel::INode* node); void aboutToDeleteNode(ProjectModel::INode* node);
bool askAboutSave(); bool askAboutSave();

View File

@ -2219,7 +2219,7 @@ void ProjectModel::del(const QModelIndex& index)
tr("<p>The subproject %1 will be permanently deleted from the project. " tr("<p>The subproject %1 will be permanently deleted from the project. "
"Sample files will be permanently removed from the file system.</p>" "Sample files will be permanently removed from the file system.</p>"
"<p><strong>This action cannot be undone!</strong></p><p>Continue?</p>").arg(n->name()), "<p><strong>This action cannot be undone!</strong></p><p>Continue?</p>").arg(n->name()),
QMessageBox::Yes, QMessageBox::No); QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (result == QMessageBox::No) if (result == QMessageBox::No)
return; return;
NameUndoRegistry nameReg; NameUndoRegistry nameReg;
@ -2254,7 +2254,7 @@ void ProjectModel::del(const QModelIndex& index)
int result = g_MainWindow->uiMessenger().warning(tr("Delete Sample"), int result = g_MainWindow->uiMessenger().warning(tr("Delete Sample"),
tr("<p>The sample %1 will be permanently deleted from the file system. " tr("<p>The sample %1 will be permanently deleted from the file system. "
"<p><strong>This action cannot be undone!</strong></p><p>Continue?</p>").arg(n->name()), "<p><strong>This action cannot be undone!</strong></p><p>Continue?</p>").arg(n->name()),
QMessageBox::Yes, QMessageBox::No); QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (result == QMessageBox::No) if (result == QMessageBox::No)
return; return;
NameUndoRegistry nameReg; NameUndoRegistry nameReg;

View File

@ -253,7 +253,7 @@ void SampleView::paintEvent(QPaintEvent* ev)
if (m_sample) if (m_sample)
{ {
if (m_sample->m_loopLengthSamples != 0) if (m_sample->isLooped())
{ {
int loopStart = m_sample->m_loopStartSample; int loopStart = m_sample->m_loopStartSample;
int loopEnd = loopStart + m_sample->m_loopLengthSamples - 1; int loopEnd = loopStart + m_sample->m_loopLengthSamples - 1;
@ -320,7 +320,7 @@ void SampleView::showEvent(QShowEvent* ev)
void SampleView::mousePressEvent(QMouseEvent* ev) void SampleView::mousePressEvent(QMouseEvent* ev)
{ {
if (m_sample && m_sample->m_loopLengthSamples != 0) if (m_sample && m_sample->isLooped())
{ {
int loopStart = m_sample->m_loopStartSample; int loopStart = m_sample->m_loopStartSample;
int startPos = int(loopStart / m_samplesPerPx); int startPos = int(loopStart / m_samplesPerPx);
@ -697,11 +697,11 @@ void SampleControls::updateFileState()
} }
amuse::SampleEntryData* data = editor->m_sampleView->entryData(); amuse::SampleEntryData* data = editor->m_sampleView->entryData();
m_loopCheck->setChecked(data->m_loopLengthSamples != 0); m_loopCheck->setChecked(data->isLooped());
int loopStart = 0; int loopStart = 0;
int loopEnd = 0; int loopEnd = 0;
int loopMax = data->getNumSamples() - 1; int loopMax = data->getNumSamples() - 1;
if (data->m_loopLengthSamples != 0) if (data->isLooped())
{ {
loopStart = data->m_loopStartSample; loopStart = data->m_loopStartSample;
loopEnd = loopStart + data->m_loopLengthSamples - 1; loopEnd = loopStart + data->m_loopLengthSamples - 1;
@ -712,8 +712,8 @@ void SampleControls::updateFileState()
m_loopStart->setValue(loopStart); m_loopStart->setValue(loopStart);
m_loopEnd->setValue(loopEnd); m_loopEnd->setValue(loopEnd);
m_basePitch->setValue(data->m_pitch); m_basePitch->setValue(data->m_pitch);
m_loopStart->setEnabled(data->m_loopLengthSamples != 0); m_loopStart->setEnabled(data->isLooped());
m_loopEnd->setEnabled(data->m_loopLengthSamples != 0); m_loopEnd->setEnabled(data->isLooped());
if (!path.empty()) if (!path.empty())
{ {

View File

@ -913,16 +913,29 @@ QVariant SetupListModel::data(const QModelIndex& index, int role) const
auto entry = m_sorted[index.row()]; auto entry = m_sorted[index.row()];
if (role == Qt::DisplayRole || role == Qt::EditRole) if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::TextColorRole)
{ {
if (index.column() == 0) if (index.column() == 0)
{ {
if (role == Qt::TextColorRole)
return QVariant();
g_MainWindow->projectModel()->setIdDatabases(m_node.get()); g_MainWindow->projectModel()->setIdDatabases(m_node.get());
return amuse::SongId::CurNameDB->resolveNameFromId(entry->first.id).data(); return amuse::SongId::CurNameDB->resolveNameFromId(entry->first.id).data();
} }
else if (index.column() == 1) else if (index.column() == 1)
{ {
return g_MainWindow->projectModel()->getMIDIPathOfSong(entry.m_it->first); QString songPath = g_MainWindow->projectModel()->getMIDIPathOfSong(entry.m_it->first);
if (songPath.isEmpty())
{
if (role == Qt::TextColorRole)
return g_MainWindow->palette().color(QPalette::Disabled, QPalette::Text);
else if (role == Qt::EditRole)
return QVariant();
return tr("Double-click to select file");
}
if (role == Qt::TextColorRole)
return QVariant();
return songPath;
} }
} }
@ -978,6 +991,8 @@ QVariant SetupListModel::headerData(int section, Qt::Orientation orientation, in
Qt::ItemFlags SetupListModel::flags(const QModelIndex& index) const Qt::ItemFlags SetupListModel::flags(const QModelIndex& index) const
{ {
if (!m_node)
return Qt::NoItemFlags;
if (index.row() == m_sorted.size()) if (index.row() == m_sorted.size())
return Qt::NoItemFlags; return Qt::NoItemFlags;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
@ -1389,9 +1404,42 @@ ColoredTabWidget::ColoredTabWidget(QWidget* parent)
setTabBar(&m_tabBar); setTabBar(&m_tabBar);
} }
static std::vector<uint8_t> LoadSongFile(QString path)
{
QFileInfo fi(path);
if (!fi.isFile())
return {};
std::vector<uint8_t> data;
{
QFile f(path);
if (!f.open(QFile::ReadOnly))
return {};
auto d = f.readAll();
data.resize(d.size());
memcpy(&data[0], d.data(), d.size());
}
if (!memcmp(data.data(), "MThd", 4))
{
data = amuse::SongConverter::MIDIToSong(data, 1, true);
}
else
{
bool isBig;
if (amuse::SongState::DetectVersion(data.data(), isBig) < 0)
return {};
}
return data;
}
void MIDIPlayerWidget::clicked() void MIDIPlayerWidget::clicked()
{ {
if (!m_seq) if (!m_seq)
{
m_arrData = LoadSongFile(m_path);
if (!m_arrData.empty())
{ {
m_seq = g_MainWindow->startSong(m_groupId, m_songId, m_arrData.data()); m_seq = g_MainWindow->startSong(m_groupId, m_songId, m_arrData.data());
if (m_seq) if (m_seq)
@ -1401,6 +1449,11 @@ void MIDIPlayerWidget::clicked()
} }
} }
else else
{
g_MainWindow->uiMessenger().critical(tr("Bad Song Data"), tr("Unable to load song data at %1").arg(m_path));
}
}
else
{ {
stopped(); stopped();
} }
@ -1444,9 +1497,9 @@ MIDIPlayerWidget::~MIDIPlayerWidget()
} }
MIDIPlayerWidget::MIDIPlayerWidget(QModelIndex index, amuse::GroupId gid, amuse::SongId id, MIDIPlayerWidget::MIDIPlayerWidget(QModelIndex index, amuse::GroupId gid, amuse::SongId id,
std::vector<uint8_t>&& arrData, QWidget* parent) const QString& path, QWidget* parent)
: QWidget(parent), m_button(this), m_playAction(tr("Play")), m_index(index), : QWidget(parent), m_button(this), m_playAction(tr("Play")), m_index(index),
m_groupId(gid), m_songId(id), m_arrData(std::move(arrData)) m_groupId(gid), m_songId(id), m_path(path)
{ {
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconSoundMacro.svg"))); m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconSoundMacro.svg")));
m_button.setDefaultAction(&m_playAction); m_button.setDefaultAction(&m_playAction);
@ -1619,36 +1672,6 @@ void SongGroupEditor::setupModelAboutToBeReset()
m_setup.unloadData(); m_setup.unloadData();
} }
static std::vector<uint8_t> LoadSongFile(QString path)
{
QFileInfo fi(path);
if (!fi.isFile())
return {};
std::vector<uint8_t> data;
{
QFile f(path);
if (!f.open(QFile::ReadOnly))
return {};
auto d = f.readAll();
data.resize(d.size());
memcpy(&data[0], d.data(), d.size());
}
if (!memcmp(data.data(), "MThd", 4))
{
data = amuse::SongConverter::MIDIToSong(data, 1, true);
}
else
{
bool isBig;
if (amuse::SongState::DetectVersion(data.data(), isBig) < 0)
return {};
}
return data;
}
void SongGroupEditor::setupDataChanged() void SongGroupEditor::setupDataChanged()
{ {
int idx = 0; int idx = 0;
@ -1670,18 +1693,11 @@ void SongGroupEditor::setupDataChanged()
MIDIPlayerWidget* w = qobject_cast<MIDIPlayerWidget*>(m_setupTable->m_listView->indexWidget(index)); MIDIPlayerWidget* w = qobject_cast<MIDIPlayerWidget*>(m_setupTable->m_listView->indexWidget(index));
if (!w || w->songId() != p.m_it->first) if (!w || w->songId() != p.m_it->first)
{ {
std::vector<uint8_t> arrData = LoadSongFile(g_MainWindow->projectModel()->dir().absoluteFilePath(path)); QString pathStr = g_MainWindow->projectModel()->dir().absoluteFilePath(path);
if (!arrData.empty())
{
MIDIPlayerWidget* newW = new MIDIPlayerWidget(index, m_setupList.m_node->m_id, p.m_it->first, MIDIPlayerWidget* newW = new MIDIPlayerWidget(index, m_setupList.m_node->m_id, p.m_it->first,
std::move(arrData), m_setupTable->m_listView->viewport()); pathStr, m_setupTable->m_listView->viewport());
m_setupTable->m_listView->setIndexWidget(index, newW); m_setupTable->m_listView->setIndexWidget(index, newW);
} }
else
{
m_setupTable->m_listView->setIndexWidget(index, nullptr);
}
}
} }
++idx; ++idx;
} }

View File

@ -241,11 +241,12 @@ class MIDIPlayerWidget : public QWidget
QModelIndex m_index; QModelIndex m_index;
amuse::GroupId m_groupId; amuse::GroupId m_groupId;
amuse::SongId m_songId; amuse::SongId m_songId;
QString m_path;
std::vector<uint8_t> m_arrData; std::vector<uint8_t> m_arrData;
amuse::ObjToken<amuse::Sequencer> m_seq; amuse::ObjToken<amuse::Sequencer> m_seq;
public: public:
explicit MIDIPlayerWidget(QModelIndex index, amuse::GroupId gid, amuse::SongId id, explicit MIDIPlayerWidget(QModelIndex index, amuse::GroupId gid, amuse::SongId id,
std::vector<uint8_t>&& arrData, QWidget* parent = Q_NULLPTR); const QString& path, QWidget* parent = Q_NULLPTR);
~MIDIPlayerWidget(); ~MIDIPlayerWidget();
amuse::SongId songId() const { return m_songId; } amuse::SongId songId() const { return m_songId; }
amuse::Sequencer* sequencer() const { return m_seq.get(); } amuse::Sequencer* sequencer() const { return m_seq.get(); }

View File

@ -427,13 +427,23 @@
<context> <context>
<name>MIDIPlayerWidget</name> <name>MIDIPlayerWidget</name>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1399"/> <location filename="../SongGroupEditor.cpp" line="1447"/>
<source>Stop</source> <source>Stop</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1413"/> <location filename="../SongGroupEditor.cpp" line="1453"/>
<location filename="../SongGroupEditor.cpp" line="1448"/> <source>Bad Song Data</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1453"/>
<source>Unable to load song data at %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1466"/>
<location filename="../SongGroupEditor.cpp" line="1501"/>
<source>Play</source> <source>Play</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -666,344 +676,344 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="42"/> <location filename="../MainWindow.cpp" line="56"/>
<source>Go Back</source> <source>Go Back</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="47"/> <location filename="../MainWindow.cpp" line="61"/>
<source>Go Forward</source> <source>Go Forward</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="96"/> <location filename="../MainWindow.cpp" line="110"/>
<source>Clear Recent Projects</source> <source>Clear Recent Projects</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="231"/> <location filename="../MainWindow.cpp" line="245"/>
<source>Amuse[*]</source> <source>Amuse[*]</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="239"/> <location filename="../MainWindow.cpp" line="253"/>
<source>%1/%2/%3[*] - Amuse</source> <source>%1/%2/%3[*] - Amuse</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="244"/> <location filename="../MainWindow.cpp" line="258"/>
<source>%1[*] - Amuse</source> <source>%1[*] - Amuse</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="273"/> <location filename="../MainWindow.cpp" line="287"/>
<location filename="../MainWindow.cpp" line="832"/> <location filename="../MainWindow.cpp" line="845"/>
<source>The directory at &apos;%1&apos; must not be empty.</source> <source>The directory at &apos;%1&apos; must not be empty.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="274"/> <location filename="../MainWindow.cpp" line="288"/>
<location filename="../MainWindow.cpp" line="833"/> <location filename="../MainWindow.cpp" line="846"/>
<source>Directory empty</source> <source>Directory empty</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="279"/> <location filename="../MainWindow.cpp" line="293"/>
<source>The directory at &apos;%1&apos; must exist for the Amuse editor.</source> <source>The directory at &apos;%1&apos; must exist for the Amuse editor.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="280"/> <location filename="../MainWindow.cpp" line="294"/>
<source>Directory does not exist</source> <source>Directory does not exist</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="283"/> <location filename="../MainWindow.cpp" line="297"/>
<source>__amuse_test__</source> <source>__amuse_test__</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="288"/> <location filename="../MainWindow.cpp" line="302"/>
<source>The directory at &apos;%1&apos; must be writable for the Amuse editor: %2</source> <source>The directory at &apos;%1&apos; must be writable for the Amuse editor: %2</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="290"/> <location filename="../MainWindow.cpp" line="304"/>
<source>Unable to write to directory</source> <source>Unable to write to directory</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="362"/> <location filename="../MainWindow.cpp" line="378"/>
<source>No Audio Devices Found</source> <source>No Audio Devices Found</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="377"/> <location filename="../MainWindow.cpp" line="393"/>
<source>Virtual MIDI-In</source> <source>Virtual MIDI-In</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="396"/> <location filename="../MainWindow.cpp" line="412"/>
<source>No MIDI Devices Found</source> <source>No MIDI Devices Found</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="464"/> <location filename="../MainWindow.cpp" line="480"/>
<source>SUSTAIN</source> <source>SUSTAIN</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="771"/> <location filename="../MainWindow.cpp" line="787"/>
<location filename="../MainWindow.cpp" line="939"/> <location filename="../MainWindow.cpp" line="949"/>
<source>Unsaved Changes</source> <source>Unsaved Changes</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="771"/> <location filename="../MainWindow.cpp" line="787"/>
<source>Save Changes in %1?</source> <source>Save Changes in %1?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="804"/> <location filename="../MainWindow.cpp" line="820"/>
<source>New Project</source> <source>New Project</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="838"/> <location filename="../MainWindow.cpp" line="851"/>
<source>The directory at &apos;%1&apos; does not exist.</source> <source>The directory at &apos;%1&apos; does not exist.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="839"/> <location filename="../MainWindow.cpp" line="852"/>
<source>Bad Directory</source> <source>Bad Directory</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="854"/> <location filename="../MainWindow.cpp" line="867"/>
<source>Opening</source> <source>Opening</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="854"/> <location filename="../MainWindow.cpp" line="867"/>
<location filename="../MainWindow.cpp" line="968"/> <location filename="../MainWindow.cpp" line="978"/>
<location filename="../MainWindow.cpp" line="1062"/> <location filename="../MainWindow.cpp" line="1069"/>
<location filename="../MainWindow.cpp" line="1108"/> <location filename="../MainWindow.cpp" line="1115"/>
<location filename="../MainWindow.cpp" line="1163"/> <location filename="../MainWindow.cpp" line="1167"/>
<source>Scanning Project</source> <source>Scanning Project</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="868"/> <location filename="../MainWindow.cpp" line="881"/>
<source>Opening %1</source> <source>Opening %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="884"/> <location filename="../MainWindow.cpp" line="897"/>
<source>Open Project</source> <source>Open Project</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="939"/> <location filename="../MainWindow.cpp" line="949"/>
<source>Discard Changes in %1?</source> <source>Discard Changes in %1?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="968"/> <location filename="../MainWindow.cpp" line="978"/>
<source>Reloading Samples</source> <source>Reloading Samples</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="982"/> <location filename="../MainWindow.cpp" line="992"/>
<source>Scanning %1</source> <source>Scanning %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="992"/> <location filename="../MainWindow.cpp" line="1002"/>
<source>Import Project</source> <source>Import Project</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1010"/> <location filename="../MainWindow.cpp" line="1017"/>
<source>The file at &apos;%1&apos; could not be interpreted as a MusyX container.</source> <source>The file at &apos;%1&apos; could not be interpreted as a MusyX container.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1011"/> <location filename="../MainWindow.cpp" line="1018"/>
<source>Unsupported MusyX Container</source> <source>Unsupported MusyX Container</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1016"/> <location filename="../MainWindow.cpp" line="1023"/>
<source>Sample Import Mode</source> <source>Sample Import Mode</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1017"/> <location filename="../MainWindow.cpp" line="1024"/>
<source>Amuse can import samples as WAV files for ease of editing, import original compressed data for lossless repacking, or both. Exporting the project will prefer whichever version was modified most recently.</source> <source>Amuse can import samples as WAV files for ease of editing, import original compressed data for lossless repacking, or both. Exporting the project will prefer whichever version was modified most recently.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1021"/> <location filename="../MainWindow.cpp" line="1028"/>
<source>Import Compressed</source> <source>Import Compressed</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1021"/> <location filename="../MainWindow.cpp" line="1028"/>
<source>Import WAVs</source> <source>Import WAVs</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1021"/> <location filename="../MainWindow.cpp" line="1028"/>
<source>Import Both</source> <source>Import Both</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1037"/> <location filename="../MainWindow.cpp" line="1044"/>
<source>Raw Import Mode</source> <source>Raw Import Mode</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1038"/> <location filename="../MainWindow.cpp" line="1045"/>
<source>Would you like to scan for all MusyX group files in this directory?</source> <source>Would you like to scan for all MusyX group files in this directory?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1048"/> <location filename="../MainWindow.cpp" line="1055"/>
<source>Project Name</source> <source>Project Name</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1048"/> <location filename="../MainWindow.cpp" line="1055"/>
<source>What should this project be named?</source> <source>What should this project be named?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1062"/> <location filename="../MainWindow.cpp" line="1069"/>
<location filename="../MainWindow.cpp" line="1108"/> <location filename="../MainWindow.cpp" line="1115"/>
<source>Importing</source> <source>Importing</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1074"/> <location filename="../MainWindow.cpp" line="1081"/>
<location filename="../MainWindow.cpp" line="1117"/> <location filename="../MainWindow.cpp" line="1124"/>
<source>Importing %1</source> <source>Importing %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1134"/> <location filename="../MainWindow.cpp" line="1141"/>
<source>Import Songs</source> <source>Import Songs</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1163"/> <location filename="../MainWindow.cpp" line="1167"/>
<source>Exporting</source> <source>Exporting</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1171"/> <location filename="../MainWindow.cpp" line="1175"/>
<source>Exporting %1</source> <source>Exporting %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1186"/> <location filename="../MainWindow.cpp" line="1190"/>
<location filename="../MainWindow.cpp" line="1201"/> <location filename="../MainWindow.cpp" line="1205"/>
<source>Import C Headers</source> <source>Import C Headers</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1187"/> <location filename="../MainWindow.cpp" line="1191"/>
<source>&lt;p&gt;Importing names from C headers depends on up-to-date, consistent names relative to the sound group data.&lt;/p&gt;&lt;p&gt;Headers are imported on a per-subproject basis from a single directory. Headers must be named with the form &lt;code&gt;&amp;lt;subproject&amp;gt;.h&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Group, Song and SFX definitions are matched according to the following forms:&lt;pre&gt;#define GRP&amp;lt;name&amp;gt; &amp;lt;id&amp;gt; <source>&lt;p&gt;Importing names from C headers depends on up-to-date, consistent names relative to the sound group data.&lt;/p&gt;&lt;p&gt;Headers are imported on a per-subproject basis from a single directory. Headers must be named with the form &lt;code&gt;&amp;lt;subproject&amp;gt;.h&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Group, Song and SFX definitions are matched according to the following forms:&lt;pre&gt;#define GRP&amp;lt;name&amp;gt; &amp;lt;id&amp;gt;
#define SNG&amp;lt;name&amp;gt; &amp;lt;id&amp;gt; #define SNG&amp;lt;name&amp;gt; &amp;lt;id&amp;gt;
#define SFX&amp;lt;name&gt; &amp;lt;id&amp;gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;This operation cannot be undone! It is recommended to make a backup of the project directory before proceeding.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Continue?&lt;/p&gt;</source> #define SFX&amp;lt;name&gt; &amp;lt;id&amp;gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;This operation cannot be undone! It is recommended to make a backup of the project directory before proceeding.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Continue?&lt;/p&gt;</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1229"/> <location filename="../MainWindow.cpp" line="1230"/>
<source>Export C Headers</source> <source>Export C Headers</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1464"/> <location filename="../MainWindow.cpp" line="1462"/>
<source>New Subproject</source> <source>New Subproject</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1465"/> <location filename="../MainWindow.cpp" line="1463"/>
<source>What should this subproject be named?</source> <source>What should this subproject be named?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1480"/> <location filename="../MainWindow.cpp" line="1478"/>
<source>New SFX Group</source> <source>New SFX Group</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1481"/> <location filename="../MainWindow.cpp" line="1479"/>
<source>What should the new SFX group in %1 be named?</source> <source>What should the new SFX group in %1 be named?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1499"/> <location filename="../MainWindow.cpp" line="1497"/>
<source>New Song Group</source> <source>New Song Group</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1500"/> <location filename="../MainWindow.cpp" line="1498"/>
<source>What should the new Song group in %1 be named?</source> <source>What should the new Song group in %1 be named?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1542"/> <location filename="../MainWindow.cpp" line="1540"/>
<source>New ADSR</source> <source>New ADSR</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1543"/> <location filename="../MainWindow.cpp" line="1541"/>
<source>What should the new ADSR in %1 be named?</source> <source>What should the new ADSR in %1 be named?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1561"/> <location filename="../MainWindow.cpp" line="1559"/>
<source>New Curve</source> <source>New Curve</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1562"/> <location filename="../MainWindow.cpp" line="1560"/>
<source>What should the new Curve in %1 be named?</source> <source>What should the new Curve in %1 be named?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1580"/> <location filename="../MainWindow.cpp" line="1578"/>
<source>New Keymap</source> <source>New Keymap</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1581"/> <location filename="../MainWindow.cpp" line="1579"/>
<source>What should the new Keymap in %1 be named?</source> <source>What should the new Keymap in %1 be named?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1599"/> <location filename="../MainWindow.cpp" line="1597"/>
<source>New Layers</source> <source>New Layers</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1600"/> <location filename="../MainWindow.cpp" line="1598"/>
<source>What should the new Layers in %1 be named?</source> <source>What should the new Layers in %1 be named?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1710"/> <location filename="../MainWindow.cpp" line="1708"/>
<source>About Amuse</source> <source>About Amuse</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="2005"/> <location filename="../MainWindow.cpp" line="2003"/>
<source>Export Complete</source> <source>Export Complete</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="2005"/> <location filename="../MainWindow.cpp" line="2003"/>
<source>%1?</source> <source>%1?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1192,12 +1202,12 @@
<context> <context>
<name>PageTableView</name> <name>PageTableView</name>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1257"/> <location filename="../SongGroupEditor.cpp" line="1272"/>
<source>Delete Page Entries</source> <source>Delete Page Entries</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1257"/> <location filename="../SongGroupEditor.cpp" line="1272"/>
<source>Delete Page Entry</source> <source>Delete Page Entry</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1563,7 +1573,7 @@
<context> <context>
<name>QDialogButtonBox</name> <name>QDialogButtonBox</name>
<message> <message>
<location filename="../MainWindow.cpp" line="2036"/> <location filename="../MainWindow.cpp" line="2034"/>
<source>OK</source> <source>OK</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1571,12 +1581,12 @@
<context> <context>
<name>QMessageBox</name> <name>QMessageBox</name>
<message> <message>
<location filename="../MainWindow.cpp" line="1693"/> <location filename="../MainWindow.cpp" line="1691"/>
<source>&lt;h3&gt;About Amuse&lt;/h3&gt;</source> <source>&lt;h3&gt;About Amuse&lt;/h3&gt;</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1697"/> <location filename="../MainWindow.cpp" line="1695"/>
<source>&lt;p&gt;Amuse is an alternate editor and runtime library for MusyX sound groups.&lt;/p&gt;&lt;p&gt;MusyX originally served as a widely-deployed audio system for developing games on the Nintendo 64, GameCube, and GameBoy Advance.&lt;/p&gt;&lt;p&gt;Amuse is available under the MIT license.&lt;br&gt;Please see &lt;a href=&quot;https://gitlab.axiodl.com/AxioDL/amuse/blob/master/LICENSE&quot;&gt;https://gitlab.axiodl.com/AxioDL/amuse/blob/master/LICENSE&lt;/a&gt; for futher information.&lt;/p&gt;&lt;p&gt;Copyright (C) 2015-2018 Antidote / Jackoalan.&lt;/p&gt;&lt;p&gt;MusyX is a trademark of Factor 5, LLC.&lt;/p&gt;&lt;p&gt;Nintendo 64, GameCube, and GameBoy Advance are trademarks of Nintendo Co., Ltd.&lt;/p&gt;</source> <source>&lt;p&gt;Amuse is an alternate editor and runtime library for MusyX sound groups.&lt;/p&gt;&lt;p&gt;MusyX originally served as a widely-deployed audio system for developing games on the Nintendo 64, GameCube, and GameBoy Advance.&lt;/p&gt;&lt;p&gt;Amuse is available under the MIT license.&lt;br&gt;Please see &lt;a href=&quot;https://gitlab.axiodl.com/AxioDL/amuse/blob/master/LICENSE&quot;&gt;https://gitlab.axiodl.com/AxioDL/amuse/blob/master/LICENSE&lt;/a&gt; for futher information.&lt;/p&gt;&lt;p&gt;Copyright (C) 2015-2018 Antidote / Jackoalan.&lt;/p&gt;&lt;p&gt;MusyX is a trademark of Factor 5, LLC.&lt;/p&gt;&lt;p&gt;Nintendo 64, GameCube, and GameBoy Advance are trademarks of Nintendo Co., Ltd.&lt;/p&gt;</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1748,27 +1758,32 @@
<context> <context>
<name>SetupListModel</name> <name>SetupListModel</name>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="946"/> <location filename="../SongGroupEditor.cpp" line="934"/>
<source>Double-click to select file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="959"/>
<source>Song Conflict</source> <source>Song Conflict</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="947"/> <location filename="../SongGroupEditor.cpp" line="960"/>
<source>Song %1 is already defined in project</source> <source>Song %1 is already defined in project</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="952"/> <location filename="../SongGroupEditor.cpp" line="965"/>
<source>Change Song Name</source> <source>Change Song Name</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="969"/> <location filename="../SongGroupEditor.cpp" line="982"/>
<source>Song</source> <source>Song</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="971"/> <location filename="../SongGroupEditor.cpp" line="984"/>
<source>MIDI File</source> <source>MIDI File</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1776,32 +1791,32 @@
<context> <context>
<name>SetupModel</name> <name>SetupModel</name>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1192"/> <location filename="../SongGroupEditor.cpp" line="1207"/>
<source>Change %1</source> <source>Change %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1209"/> <location filename="../SongGroupEditor.cpp" line="1224"/>
<source>Program</source> <source>Program</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1211"/> <location filename="../SongGroupEditor.cpp" line="1226"/>
<source>Volume</source> <source>Volume</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1213"/> <location filename="../SongGroupEditor.cpp" line="1228"/>
<source>Panning</source> <source>Panning</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1215"/> <location filename="../SongGroupEditor.cpp" line="1230"/>
<source>Reverb</source> <source>Reverb</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1217"/> <location filename="../SongGroupEditor.cpp" line="1232"/>
<source>Chorus</source> <source>Chorus</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1809,12 +1824,12 @@
<context> <context>
<name>SetupTableView</name> <name>SetupTableView</name>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1321"/> <location filename="../SongGroupEditor.cpp" line="1336"/>
<source>Delete Setup Entries</source> <source>Delete Setup Entries</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1321"/> <location filename="../SongGroupEditor.cpp" line="1336"/>
<source>Delete Setup Entry</source> <source>Delete Setup Entry</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1822,37 +1837,37 @@
<context> <context>
<name>SongGroupEditor</name> <name>SongGroupEditor</name>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1519"/> <location filename="../SongGroupEditor.cpp" line="1572"/>
<source>Add Page Entry</source> <source>Add Page Entry</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1531"/> <location filename="../SongGroupEditor.cpp" line="1584"/>
<source>Add Setup Entry</source> <source>Add Setup Entry</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1712"/> <location filename="../SongGroupEditor.cpp" line="1728"/>
<source>Normal Pages</source> <source>Normal Pages</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1713"/> <location filename="../SongGroupEditor.cpp" line="1729"/>
<source>Drum Pages</source> <source>Drum Pages</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1714"/> <location filename="../SongGroupEditor.cpp" line="1730"/>
<source>MIDI Setups</source> <source>MIDI Setups</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1755"/> <location filename="../SongGroupEditor.cpp" line="1771"/>
<source>Add new page entry</source> <source>Add new page entry</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../SongGroupEditor.cpp" line="1757"/> <location filename="../SongGroupEditor.cpp" line="1773"/>
<source>Remove selected page entries</source> <source>Remove selected page entries</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -2026,52 +2041,52 @@
<context> <context>
<name>TreeDelegate</name> <name>TreeDelegate</name>
<message> <message>
<location filename="../MainWindow.cpp" line="1269"/> <location filename="../MainWindow.cpp" line="1267"/>
<source>Export GameCube Group</source> <source>Export GameCube Group</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1277"/> <location filename="../MainWindow.cpp" line="1275"/>
<source>Find Usages</source> <source>Find Usages</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1285"/> <location filename="../MainWindow.cpp" line="1283"/>
<source>Cut</source> <source>Cut</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1293"/> <location filename="../MainWindow.cpp" line="1291"/>
<source>Copy</source> <source>Copy</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1301"/> <location filename="../MainWindow.cpp" line="1299"/>
<source>Paste</source> <source>Paste</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1309"/> <location filename="../MainWindow.cpp" line="1307"/>
<source>Duplicate</source> <source>Duplicate</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1314"/> <location filename="../MainWindow.cpp" line="1312"/>
<source>Delete</source> <source>Delete</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1322"/> <location filename="../MainWindow.cpp" line="1320"/>
<source>Rename</source> <source>Rename</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1353"/> <location filename="../MainWindow.cpp" line="1351"/>
<source>Exporting</source> <source>Exporting</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../MainWindow.cpp" line="1353"/> <location filename="../MainWindow.cpp" line="1351"/>
<source>Exporting %1</source> <source>Exporting %1</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>

View File

@ -101,6 +101,7 @@ int main(int argc, const boo::SystemChar** argv)
m_args.reserve(argc); m_args.reserve(argc);
double rate = NativeSampleRate; double rate = NativeSampleRate;
int chCount = 2; int chCount = 2;
double volume = 1.0;
for (int i = 1; i < argc; ++i) for (int i = 1; i < argc; ++i)
{ {
#if _WIN32 #if _WIN32
@ -124,6 +125,16 @@ int main(int argc, const boo::SystemChar** argv)
++i; ++i;
} }
} }
else if (!wcsncmp(argv[i], L"-v", 2))
{
if (argv[i][2])
volume = wcstod(&argv[i][2], nullptr);
else if (argc > (i + 1))
{
volume = wcstod(argv[i + 1], nullptr);
++i;
}
}
else else
m_args.push_back(argv[i]); m_args.push_back(argv[i]);
#else #else
@ -147,6 +158,16 @@ int main(int argc, const boo::SystemChar** argv)
++i; ++i;
} }
} }
else if (!strncmp(argv[i], "-v", 2))
{
if (argv[i][2])
volume = strtod(&argv[i][2], nullptr);
else if (argc > (i + 1))
{
volume = strtod(argv[i + 1], nullptr);
++i;
}
}
else else
m_args.push_back(argv[i]); m_args.push_back(argv[i]);
#endif #endif
@ -155,7 +176,7 @@ int main(int argc, const boo::SystemChar** argv)
/* Load data */ /* Load data */
if (m_args.size() < 1) if (m_args.size() < 1)
{ {
Log.report(logvisor::Error, "Usage: amuserender <group-file> [<songs-file>] [-r <sample-rate>] [-c <channel-count>]"); Log.report(logvisor::Error, "Usage: amuserender <group-file> [<songs-file>] [-r <sample-rate>] [-c <channel-count>] [-v <volume 0.0-1.0>]");
return 1; return 1;
} }
@ -476,6 +497,7 @@ int main(int argc, const boo::SystemChar** argv)
std::unique_ptr<boo::IAudioVoiceEngine> voxEngine = boo::NewWAVAudioVoiceEngine(pathOut, rate, chCount); std::unique_ptr<boo::IAudioVoiceEngine> voxEngine = boo::NewWAVAudioVoiceEngine(pathOut, rate, chCount);
amuse::BooBackendVoiceAllocator booBackend(*voxEngine); amuse::BooBackendVoiceAllocator booBackend(*voxEngine);
amuse::Engine engine(booBackend, amuse::AmplitudeMode::PerSample); amuse::Engine engine(booBackend, amuse::AmplitudeMode::PerSample);
engine.setVolume(float(amuse::clamp(0.0, volume, 1.0)));
/* Load group into engine */ /* Load group into engine */
const amuse::AudioGroup* group = engine.addAudioGroup(*selData); const amuse::AudioGroup* group = engine.addAudioGroup(*selData);
@ -486,7 +508,7 @@ int main(int argc, const boo::SystemChar** argv)
} }
/* Enter playback loop */ /* Enter playback loop */
amuse::ObjToken<amuse::Sequencer> seq = engine.seqPlay(m_groupId, m_setupId, m_arrData->m_data.get()); amuse::ObjToken<amuse::Sequencer> seq = engine.seqPlay(m_groupId, m_setupId, m_arrData->m_data.get(), false);
size_t wroteFrames = 0; size_t wroteFrames = 0;
signal(SIGINT, SIGINTHandler); signal(SIGINT, SIGINTHandler);
do do

View File

@ -33,6 +33,15 @@ struct DSPADPCMHeader : BigDNA
Seek<21, athena::Current> pad; Seek<21, athena::Current> pad;
}; };
struct VADPCMHeader : BigDNA
{
AT_DECL_DNA
Value<uint32_t> m_pitchSampleRate;
Value<uint32_t> m_numSamples;
Value<uint32_t> m_loopStartSample;
Value<uint32_t> m_loopLengthSamples;
};
struct WAVFormatChunk : LittleDNA struct WAVFormatChunk : LittleDNA
{ {
AT_DECL_DNA AT_DECL_DNA
@ -236,6 +245,8 @@ public:
return fmt == SampleFormat::DSP || fmt == SampleFormat::DSP_DRUM; return fmt == SampleFormat::DSP || fmt == SampleFormat::DSP_DRUM;
} }
bool isLooped() const { return m_loopLengthSamples != 0 && m_loopStartSample != 0xffffffff; }
void _setLoopStartSample(atUint32 sample) void _setLoopStartSample(atUint32 sample)
{ {
m_loopLengthSamples += m_loopStartSample - sample; m_loopLengthSamples += m_loopStartSample - sample;
@ -295,9 +306,11 @@ public:
} }
void loadLooseDSP(SystemStringView dspPath); void loadLooseDSP(SystemStringView dspPath);
void loadLooseVADPCM(SystemStringView vadpcmPath);
void loadLooseWAV(SystemStringView wavPath); void loadLooseWAV(SystemStringView wavPath);
void patchMetadataDSP(SystemStringView dspPath); void patchMetadataDSP(SystemStringView dspPath);
void patchMetadataVADPCM(SystemStringView vadpcmPath);
void patchMetadataWAV(SystemStringView wavPath); void patchMetadataWAV(SystemStringView wavPath);
}; };
/* This double-wrapper allows Voices to keep a strong reference on /* This double-wrapper allows Voices to keep a strong reference on

View File

@ -148,17 +148,17 @@ public:
void removeListener(Listener* listener); void removeListener(Listener* listener);
/** Start song playing from loaded audio groups */ /** Start song playing from loaded audio groups */
ObjToken<Sequencer> seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData, ObjToken<Studio> smx); ObjToken<Sequencer> seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData, bool loop, ObjToken<Studio> smx);
ObjToken<Sequencer> seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData) ObjToken<Sequencer> seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData, bool loop = true)
{ {
return seqPlay(groupId, songId, arrData, m_defaultStudio); return seqPlay(groupId, songId, arrData, loop, m_defaultStudio);
} }
/** Start song playing from explicit group data (for editor use) */ /** Start song playing from explicit group data (for editor use) */
ObjToken<Sequencer> seqPlay(const AudioGroup* group, GroupId groupId, SongId songId, const unsigned char* arrData, ObjToken<Studio> smx); ObjToken<Sequencer> seqPlay(const AudioGroup* group, GroupId groupId, SongId songId, const unsigned char* arrData, bool loop, ObjToken<Studio> smx);
ObjToken<Sequencer> seqPlay(const AudioGroup* group, GroupId groupId, SongId songId, const unsigned char* arrData) ObjToken<Sequencer> seqPlay(const AudioGroup* group, GroupId groupId, SongId songId, const unsigned char* arrData, bool loop = true)
{ {
return seqPlay(group, groupId, songId, arrData, m_defaultStudio); return seqPlay(group, groupId, songId, arrData, loop, m_defaultStudio);
} }
/** Set total volume of engine */ /** Set total volume of engine */

View File

@ -34,7 +34,6 @@ class Sequencer : public Entity
const unsigned char* m_arrData = nullptr; /**< Current playing arrangement data */ const unsigned char* m_arrData = nullptr; /**< Current playing arrangement data */
SongState m_songState; /**< State of current arrangement playback */ SongState m_songState; /**< State of current arrangement playback */
double m_ticksPerSec = 1000.0; /**< Current ticks per second (tempo) for arrangement data */
SequencerState m_state = SequencerState::Interactive; /**< Current high-level state of sequencer */ SequencerState m_state = SequencerState::Interactive; /**< Current high-level state of sequencer */
bool m_dieOnEnd = false; /**< Sequencer will be killed when current arrangement completes */ bool m_dieOnEnd = false; /**< Sequencer will be killed when current arrangement completes */
@ -68,6 +67,7 @@ class Sequencer : public Entity
float m_curVol = 1.f; /**< Current volume of channel */ float m_curVol = 1.f; /**< Current volume of channel */
float m_curPan = 0.f; /**< Current panning of channel */ float m_curPan = 0.f; /**< Current panning of channel */
uint16_t m_rpn = 0; /**< Current RPN (only pitch-range 0x0000 supported) */ uint16_t m_rpn = 0; /**< Current RPN (only pitch-range 0x0000 supported) */
double m_ticksPerSec = 1000.0; /**< Current ticks per second (tempo) for channel */
void _bringOutYourDead(); void _bringOutYourDead();
size_t getVoiceCount() const; size_t getVoiceCount() const;
@ -137,10 +137,11 @@ public:
void sendMacroMessage(ObjectId macroId, int32_t val); void sendMacroMessage(ObjectId macroId, int32_t val);
/** Set tempo of sequencer and all voices in ticks per second */ /** Set tempo of sequencer and all voices in ticks per second */
void setTempo(uint8_t chan, double ticksPerSec);
void setTempo(double ticksPerSec); void setTempo(double ticksPerSec);
/** Play MIDI arrangement */ /** Play MIDI arrangement */
void playSong(const unsigned char* arrData, bool dieOnEnd = true); void playSong(const unsigned char* arrData, bool loop = true, bool dieOnEnd = true);
/** Stop current MIDI arrangement */ /** Stop current MIDI arrangement */
void stopSong(float fadeTime = 0.f, bool now = false); void stopSong(float fadeTime = 0.f, bool now = false);

View File

@ -30,9 +30,14 @@ class SongState
uint32_t m_regionIdxOff; uint32_t m_regionIdxOff;
uint32_t m_chanMapOff; uint32_t m_chanMapOff;
uint32_t m_tempoTableOff; uint32_t m_tempoTableOff;
uint32_t m_initialTempo; uint32_t m_initialTempo; /* Top bit indicates per-channel looping */
uint32_t m_unkOff; uint32_t m_loopStartTicks[16];
void swapBig(); uint32_t m_chanMapOff2;
void swapToBig();
void swapFromBig();
Header& operator=(const Header& other);
Header(const Header& other) { *this = other; }
Header() = default;
} m_header; } m_header;
/** Track region ('clip' in an NLA representation) */ /** Track region ('clip' in an NLA representation) */
@ -42,9 +47,11 @@ class SongState
uint8_t m_progNum; uint8_t m_progNum;
uint8_t m_unk1; uint8_t m_unk1;
uint16_t m_unk2; uint16_t m_unk2;
int16_t m_regionIndex; int16_t m_regionIndex; /* -1 to terminate song, -2 to loop to previous region */
int16_t m_unk3; int16_t m_loopToRegion;
bool indexDone(bool bigEndian, bool loop) const;
bool indexValid(bool bigEndian) const; bool indexValid(bool bigEndian) const;
int indexLoop(bool bigEndian) const;
}; };
/** Tempo change entry */ /** Tempo change entry */
@ -71,9 +78,17 @@ class SongState
}; };
SongState* m_parent = nullptr; SongState* m_parent = nullptr;
uint8_t m_midiChan; /**< MIDI channel number of song channel */ uint8_t m_midiChan = 0xff; /**< MIDI channel number of song channel */
const TrackRegion* m_curRegion; /**< Pointer to currently-playing track region */ const TrackRegion* m_initRegion = nullptr; /**< Pointer to first track region */
const TrackRegion* m_nextRegion; /**< Pointer to next-queued track region */ const TrackRegion* m_curRegion = nullptr; /**< Pointer to currently-playing track region */
const TrackRegion* m_nextRegion = nullptr; /**< Pointer to next-queued track region */
double m_remDt = 0.0; /**< Remaining dt for keeping remainder between cycles */
uint32_t m_curTick = 0; /**< Current playback position for this track */
uint32_t m_loopStartTick = 0; /**< Tick to loop back to */
/** Current pointer to tempo control, iterated over playback */
const TempoChange* m_tempoPtr;
uint32_t m_tempo; /**< Current tempo (beats per minute) */
const unsigned char* m_data = nullptr; /**< Pointer to upcoming command data */ const unsigned char* m_data = nullptr; /**< Pointer to upcoming command data */
const unsigned char* m_pitchWheelData = nullptr; /**< Pointer to upcoming pitch data */ const unsigned char* m_pitchWheelData = nullptr; /**< Pointer to upcoming pitch data */
@ -91,22 +106,18 @@ class SongState
0; /**< Last command time on this channel (for computing delta times from absolute times in N64 songs) */ 0; /**< Last command time on this channel (for computing delta times from absolute times in N64 songs) */
Track() = default; Track() = default;
Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions); Track(SongState& parent, uint8_t midiChan, uint32_t loopStart, const TrackRegion* regions, uint32_t tempo);
operator bool() const { return m_parent != nullptr; } operator bool() const { return m_parent != nullptr; }
void setRegion(Sequencer* seq, const TrackRegion* region); void setRegion(const TrackRegion* region);
void advanceRegion(Sequencer* seq); void advanceRegion();
bool advance(Sequencer& seq, int32_t ticks); bool advance(Sequencer& seq, double dt);
void resetTempo();
}; };
std::array<Track, 64> m_tracks; std::array<Track, 64> m_tracks;
const uint32_t* m_regionIdx; /**< Table of offsets to song-region data */ const uint32_t* m_regionIdx; /**< Table of offsets to song-region data */
/** Current pointer to tempo control, iterated over playback */
const TempoChange* m_tempoPtr = nullptr;
uint32_t m_tempo = 120; /**< Current tempo (beats per minute) */
uint32_t m_curTick = 0; /**< Current playback position for all channels */
SongPlayState m_songState = SongPlayState::Playing; /**< High-level state of Song playback */ SongPlayState m_songState = SongPlayState::Playing; /**< High-level state of Song playback */
double m_curDt = 0.f; /**< Cumulative dt value for time-remainder tracking */ bool m_loop = true; /**< Enable looping */
public: public:
/** Determine SNG version /** Determine SNG version
@ -115,15 +126,14 @@ public:
static int DetectVersion(const unsigned char* ptr, bool& isBig); static int DetectVersion(const unsigned char* ptr, bool& isBig);
/** initialize state for Song data at `ptr` */ /** initialize state for Song data at `ptr` */
bool initialize(const unsigned char* ptr); bool initialize(const unsigned char* ptr, bool loop);
uint32_t getInitialTempo() const { return m_header.m_initialTempo & 0x7fffffff; }
/** advances `dt` seconds worth of commands in the Song /** advances `dt` seconds worth of commands in the Song
* @return `true` if END reached * @return `true` if END reached
*/ */
bool advance(Sequencer& seq, double dt); bool advance(Sequencer& seq, double dt);
/** Get current song tempo in BPM */
uint32_t getTempo() const { return m_tempo; }
}; };
} }

View File

@ -10,8 +10,8 @@ namespace amuse
void AudioGroup::assign(const AudioGroupData& data) void AudioGroup::assign(const AudioGroupData& data)
{ {
m_proj = AudioGroupProject::CreateAudioGroupProject(data);
m_pool = AudioGroupPool::CreateAudioGroupPool(data); m_pool = AudioGroupPool::CreateAudioGroupPool(data);
m_proj = AudioGroupProject::CreateAudioGroupProject(data);
m_sdir = AudioGroupSampleDirectory::CreateAudioGroupSampleDirectory(data); m_sdir = AudioGroupSampleDirectory::CreateAudioGroupSampleDirectory(data);
m_samp = data.getSamp(); m_samp = data.getSamp();
} }

View File

@ -119,6 +119,9 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
ObjectHeader<DNAE> objHead; ObjectHeader<DNAE> objHead;
atInt64 startPos = r.position(); atInt64 startPos = r.position();
objHead.read(r); objHead.read(r);
if (SoundMacroId::CurNameDB)
SoundMacroId::CurNameDB->registerPair(
NameDB::generateName(objHead.objectId, NameDB::Type::SoundMacro), objHead.objectId);
auto& macro = ret.m_soundMacros[objHead.objectId.id]; auto& macro = ret.m_soundMacros[objHead.objectId.id];
macro = MakeObj<SoundMacro>(); macro = MakeObj<SoundMacro>();
macro->template readCmds<DNAE>(r, objHead.size - 8); macro->template readCmds<DNAE>(r, objHead.size - 8);
@ -134,6 +137,9 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
ObjectHeader<DNAE> objHead; ObjectHeader<DNAE> objHead;
atInt64 startPos = r.position(); atInt64 startPos = r.position();
objHead.read(r); objHead.read(r);
if (TableId::CurNameDB)
TableId::CurNameDB->registerPair(
NameDB::generateName(objHead.objectId, NameDB::Type::Table), objHead.objectId);
auto& ptr = ret.m_tables[objHead.objectId.id]; auto& ptr = ret.m_tables[objHead.objectId.id];
switch (objHead.size) switch (objHead.size)
{ {
@ -163,6 +169,9 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
ObjectHeader<DNAE> objHead; ObjectHeader<DNAE> objHead;
atInt64 startPos = r.position(); atInt64 startPos = r.position();
objHead.read(r); objHead.read(r);
if (KeymapId::CurNameDB)
KeymapId::CurNameDB->registerPair(
NameDB::generateName(objHead.objectId, NameDB::Type::Keymap), objHead.objectId);
auto& km = ret.m_keymaps[objHead.objectId.id]; auto& km = ret.m_keymaps[objHead.objectId.id];
km = MakeObj<std::array<Keymap, 128>>(); km = MakeObj<std::array<Keymap, 128>>();
for (int i = 0; i < 128; ++i) for (int i = 0; i < 128; ++i)
@ -183,6 +192,9 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
ObjectHeader<DNAE> objHead; ObjectHeader<DNAE> objHead;
atInt64 startPos = r.position(); atInt64 startPos = r.position();
objHead.read(r); objHead.read(r);
if (LayersId::CurNameDB)
LayersId::CurNameDB->registerPair(
NameDB::generateName(objHead.objectId, NameDB::Type::Layer), objHead.objectId);
auto& lm = ret.m_layers[objHead.objectId.id]; auto& lm = ret.m_layers[objHead.objectId.id];
lm = MakeObj<std::vector<LayerMapping>>(); lm = MakeObj<std::vector<LayerMapping>>();
uint32_t count; uint32_t count;

View File

@ -103,6 +103,7 @@ AudioGroupProject::AudioGroupProject(athena::io::IStreamReader& r, GCNDataTag)
if (GroupId::CurNameDB) if (GroupId::CurNameDB)
GroupId::CurNameDB->registerPair(NameDB::generateName(header.groupId, NameDB::Type::Group), header.groupId); GroupId::CurNameDB->registerPair(NameDB::generateName(header.groupId, NameDB::Type::Group), header.groupId);
#if 0
/* Sound Macros */ /* Sound Macros */
r.seek(header.soundMacroIdsOff, athena::Begin); r.seek(header.soundMacroIdsOff, athena::Begin);
while (!AtEnd16(r)) while (!AtEnd16(r))
@ -127,6 +128,7 @@ AudioGroupProject::AudioGroupProject(athena::io::IStreamReader& r, GCNDataTag)
r.seek(header.layerIdsOff, athena::Begin); r.seek(header.layerIdsOff, athena::Begin);
while (!AtEnd16(r)) while (!AtEnd16(r))
ReadRangedObjectIds<athena::Big>(LayersId::CurNameDB, r, NameDB::Type::Layer); ReadRangedObjectIds<athena::Big>(LayersId::CurNameDB, r, NameDB::Type::Layer);
#endif
if (header.type == GroupType::Song) if (header.type == GroupType::Song)
{ {
@ -203,6 +205,7 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade
GroupId::CurNameDB->registerPair(NameDB::generateName(header.groupId, NameDB::Type::Group), header.groupId); GroupId::CurNameDB->registerPair(NameDB::generateName(header.groupId, NameDB::Type::Group), header.groupId);
#if 0
/* Sound Macros */ /* Sound Macros */
r.seek(subDataOff + header.soundMacroIdsOff, athena::Begin); r.seek(subDataOff + header.soundMacroIdsOff, athena::Begin);
while (!AtEnd16(r)) while (!AtEnd16(r))
@ -227,6 +230,7 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade
r.seek(subDataOff + header.layerIdsOff, athena::Begin); r.seek(subDataOff + header.layerIdsOff, athena::Begin);
while (!AtEnd16(r)) while (!AtEnd16(r))
ReadRangedObjectIds<DNAE>(LayersId::CurNameDB, r, NameDB::Type::Layer); ReadRangedObjectIds<DNAE>(LayersId::CurNameDB, r, NameDB::Type::Layer);
#endif
if (header.type == GroupType::Song) if (header.type == GroupType::Song)
{ {
@ -288,7 +292,7 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade
/* MIDI setups */ /* MIDI setups */
r.seek(subDataOff + header.midiSetupsOff, athena::Begin); r.seek(subDataOff + header.midiSetupsOff, athena::Begin);
while (r.position() < groupBegin + header.groupEndOff) while (r.position() + 4 < groupBegin + header.groupEndOff)
{ {
uint16_t songId; uint16_t songId;
athena::io::Read<athena::io::PropType::None>::Do<decltype(songId), DNAE>({}, songId, r); athena::io::Read<athena::io::PropType::None>::Do<decltype(songId), DNAE>({}, songId, r);

View File

@ -210,6 +210,29 @@ void AudioGroupSampleDirectory::EntryData::loadLooseDSP(SystemStringView dspPath
} }
} }
void AudioGroupSampleDirectory::EntryData::loadLooseVADPCM(SystemStringView vadpcmPath)
{
athena::io::FileReader r(vadpcmPath);
if (!r.hasError())
{
VADPCMHeader header;
header.read(r);
m_pitch = header.m_pitchSampleRate >> 24;
m_sampleRate = header.m_pitchSampleRate & 0xffff;
m_numSamples = header.m_numSamples & 0xffff;
m_numSamples |= atUint32(SampleFormat::N64) << 24;
m_loopStartSample = header.m_loopStartSample;
m_loopLengthSamples = header.m_loopLengthSamples;
uint32_t dataLen = 256 + (m_numSamples + 63) / 64 * 40;
m_looseData.reset(new uint8_t[dataLen]);
r.readUBytesToBuf(m_looseData.get(), dataLen);
memcpy(&m_ADPCMParms, m_looseData.get(), 256);
m_ADPCMParms.swapBigVADPCM();
}
}
void AudioGroupSampleDirectory::EntryData::loadLooseWAV(SystemStringView wavPath) void AudioGroupSampleDirectory::EntryData::loadLooseWAV(SystemStringView wavPath)
{ {
athena::io::FileReader r(wavPath); athena::io::FileReader r(wavPath);
@ -263,9 +286,11 @@ void AudioGroupSampleDirectory::Entry::loadLooseData(SystemStringView basePath)
{ {
SystemString wavPath = SystemString(basePath) + _S(".wav"); SystemString wavPath = SystemString(basePath) + _S(".wav");
SystemString dspPath = SystemString(basePath) + _S(".dsp"); SystemString dspPath = SystemString(basePath) + _S(".dsp");
Sstat wavStat, dspStat; SystemString vadpcmPath = SystemString(basePath) + _S(".vadpcm");
Sstat wavStat, dspStat, vadpcmStat;
bool wavValid = !Stat(wavPath.c_str(), &wavStat) && S_ISREG(wavStat.st_mode); bool wavValid = !Stat(wavPath.c_str(), &wavStat) && S_ISREG(wavStat.st_mode);
bool dspValid = !Stat(dspPath.c_str(), &dspStat) && S_ISREG(dspStat.st_mode); bool dspValid = !Stat(dspPath.c_str(), &dspStat) && S_ISREG(dspStat.st_mode);
bool vadpcmValid = !Stat(vadpcmPath.c_str(), &vadpcmStat) && S_ISREG(vadpcmStat.st_mode);
if (wavValid && dspValid) if (wavValid && dspValid)
{ {
@ -274,6 +299,20 @@ void AudioGroupSampleDirectory::Entry::loadLooseData(SystemStringView basePath)
else else
wavValid = false; wavValid = false;
} }
if (wavValid && vadpcmValid)
{
if (wavStat.st_mtime > vadpcmStat.st_mtime)
vadpcmValid = false;
else
wavValid = false;
}
if (dspValid && vadpcmValid)
{
if (dspStat.st_mtime > vadpcmStat.st_mtime)
vadpcmValid = false;
else
dspValid = false;
}
EntryData& curData = *m_data; EntryData& curData = *m_data;
@ -283,6 +322,12 @@ void AudioGroupSampleDirectory::Entry::loadLooseData(SystemStringView basePath)
m_data->loadLooseDSP(dspPath); m_data->loadLooseDSP(dspPath);
m_data->m_looseModTime = dspStat.st_mtime; m_data->m_looseModTime = dspStat.st_mtime;
} }
else if (vadpcmValid && (!curData.m_looseData || vadpcmStat.st_mtime > curData.m_looseModTime))
{
m_data = MakeObj<EntryData>();
m_data->loadLooseVADPCM(vadpcmPath);
m_data->m_looseModTime = vadpcmStat.st_mtime;
}
else if (wavValid && (!curData.m_looseData || wavStat.st_mtime > curData.m_looseModTime)) else if (wavValid && (!curData.m_looseData || wavStat.st_mtime > curData.m_looseModTime))
{ {
m_data = MakeObj<EntryData>(); m_data = MakeObj<EntryData>();
@ -295,12 +340,14 @@ SampleFileState AudioGroupSampleDirectory::Entry::getFileState(SystemStringView
{ {
SystemString wavPath = SystemString(basePath) + _S(".wav"); SystemString wavPath = SystemString(basePath) + _S(".wav");
SystemString dspPath = SystemString(basePath) + _S(".dsp"); SystemString dspPath = SystemString(basePath) + _S(".dsp");
Sstat wavStat, dspStat; SystemString vadpcmPath = SystemString(basePath) + _S(".vadpcm");
Sstat wavStat, dspStat, vadpcmStat;
bool wavValid = !Stat(wavPath.c_str(), &wavStat) && S_ISREG(wavStat.st_mode); bool wavValid = !Stat(wavPath.c_str(), &wavStat) && S_ISREG(wavStat.st_mode);
bool dspValid = !Stat(dspPath.c_str(), &dspStat) && S_ISREG(dspStat.st_mode); bool dspValid = !Stat(dspPath.c_str(), &dspStat) && S_ISREG(dspStat.st_mode);
bool vadpcmValid = !Stat(vadpcmPath.c_str(), &vadpcmStat) && S_ISREG(vadpcmStat.st_mode);
EntryData& curData = *m_data; EntryData& curData = *m_data;
if (!wavValid && !dspValid) if (!wavValid && !dspValid && !vadpcmValid)
{ {
if (!curData.m_looseData) if (!curData.m_looseData)
return SampleFileState::NoData; return SampleFileState::NoData;
@ -321,6 +368,30 @@ SampleFileState AudioGroupSampleDirectory::Entry::getFileState(SystemStringView
*pathOut = dspPath; *pathOut = dspPath;
return SampleFileState::CompressedRecent; return SampleFileState::CompressedRecent;
} }
if (wavValid && vadpcmValid)
{
if (wavStat.st_mtime > vadpcmStat.st_mtime)
{
if (pathOut)
*pathOut = wavPath;
return SampleFileState::WAVRecent;
}
if (pathOut)
*pathOut = vadpcmPath;
return SampleFileState::CompressedRecent;
}
if (dspValid && vadpcmValid)
{
if (dspStat.st_mtime > vadpcmStat.st_mtime)
{
if (pathOut)
*pathOut = dspPath;
return SampleFileState::CompressedNoWAV;
}
if (pathOut)
*pathOut = vadpcmPath;
return SampleFileState::CompressedNoWAV;
}
if (dspValid) if (dspValid)
{ {
@ -328,6 +399,12 @@ SampleFileState AudioGroupSampleDirectory::Entry::getFileState(SystemStringView
*pathOut = dspPath; *pathOut = dspPath;
return SampleFileState::CompressedNoWAV; return SampleFileState::CompressedNoWAV;
} }
if (vadpcmValid)
{
if (pathOut)
*pathOut = vadpcmPath;
return SampleFileState::CompressedNoWAV;
}
if (pathOut) if (pathOut)
*pathOut = wavPath; *pathOut = wavPath;
return SampleFileState::WAVNoCompressed; return SampleFileState::WAVNoCompressed;
@ -341,7 +418,7 @@ void AudioGroupSampleDirectory::EntryData::patchMetadataDSP(SystemStringView dsp
DSPADPCMHeader head; DSPADPCMHeader head;
head.read(r); head.read(r);
if (m_loopLengthSamples != 0) if (isLooped())
{ {
uint32_t block = getLoopStartSample() / 14; uint32_t block = getLoopStartSample() / 14;
uint32_t rem = getLoopStartSample() % 14; uint32_t rem = getLoopStartSample() % 14;
@ -384,6 +461,22 @@ void AudioGroupSampleDirectory::EntryData::patchMetadataDSP(SystemStringView dsp
} }
} }
void AudioGroupSampleDirectory::EntryData::patchMetadataVADPCM(SystemStringView vadpcmPath)
{
athena::io::FileWriter w(vadpcmPath, false);
if (!w.hasError())
{
w.seek(0, athena::Begin);
VADPCMHeader header;
header.m_pitchSampleRate = m_pitch << 24;
header.m_pitchSampleRate |= m_sampleRate & 0xffff;
header.m_numSamples = m_numSamples;
header.m_loopStartSample = m_loopStartSample;
header.m_loopLengthSamples = m_loopLengthSamples;
header.write(w);
}
}
void AudioGroupSampleDirectory::EntryData::patchMetadataWAV(SystemStringView wavPath) void AudioGroupSampleDirectory::EntryData::patchMetadataWAV(SystemStringView wavPath)
{ {
athena::io::FileReader r(wavPath); athena::io::FileReader r(wavPath);
@ -445,7 +538,7 @@ void AudioGroupSampleDirectory::EntryData::patchMetadataWAV(SystemStringView wav
WAVSampleChunk smpl; WAVSampleChunk smpl;
smpl.smplPeriod = 1000000000 / fmt.sampleRate; smpl.smplPeriod = 1000000000 / fmt.sampleRate;
smpl.midiNote = m_pitch; smpl.midiNote = m_pitch;
if (m_loopLengthSamples != 0) if (isLooped())
{ {
smpl.numSampleLoops = 1; smpl.numSampleLoops = 1;
smpl.additionalDataSize = 0; smpl.additionalDataSize = 0;
@ -488,7 +581,7 @@ void AudioGroupSampleDirectory::EntryData::patchMetadataWAV(SystemStringView wav
WAVSampleChunk smpl; WAVSampleChunk smpl;
smpl.smplPeriod = 1000000000 / fmt.sampleRate; smpl.smplPeriod = 1000000000 / fmt.sampleRate;
smpl.midiNote = m_pitch; smpl.midiNote = m_pitch;
if (m_loopLengthSamples != 0) if (isLooped())
{ {
smpl.numSampleLoops = 1; smpl.numSampleLoops = 1;
smpl.additionalDataSize = 0; smpl.additionalDataSize = 0;
@ -530,9 +623,11 @@ void AudioGroupSampleDirectory::Entry::patchSampleMetadata(SystemStringView base
{ {
SystemString wavPath = SystemString(basePath) + _S(".wav"); SystemString wavPath = SystemString(basePath) + _S(".wav");
SystemString dspPath = SystemString(basePath) + _S(".dsp"); SystemString dspPath = SystemString(basePath) + _S(".dsp");
Sstat wavStat, dspStat; SystemString vadpcmPath = SystemString(basePath) + _S(".vadpcm");
Sstat wavStat, dspStat, vadpcmStat;
bool wavValid = !Stat(wavPath.c_str(), &wavStat) && S_ISREG(wavStat.st_mode); bool wavValid = !Stat(wavPath.c_str(), &wavStat) && S_ISREG(wavStat.st_mode);
bool dspValid = !Stat(dspPath.c_str(), &dspStat) && S_ISREG(dspStat.st_mode); bool dspValid = !Stat(dspPath.c_str(), &dspStat) && S_ISREG(dspStat.st_mode);
bool vadpcmValid = !Stat(vadpcmPath.c_str(), &vadpcmStat) && S_ISREG(vadpcmStat.st_mode);
EntryData& curData = *m_data; EntryData& curData = *m_data;
@ -542,6 +637,12 @@ void AudioGroupSampleDirectory::Entry::patchSampleMetadata(SystemStringView base
SetAudioFileTime(wavPath, wavStat); SetAudioFileTime(wavPath, wavStat);
} }
if (vadpcmValid)
{
curData.patchMetadataVADPCM(vadpcmPath);
SetAudioFileTime(vadpcmPath, vadpcmStat);
}
if (dspValid) if (dspValid)
{ {
curData.patchMetadataDSP(dspPath); curData.patchMetadataDSP(dspPath);
@ -559,9 +660,19 @@ AudioGroupSampleDirectory AudioGroupSampleDirectory::CreateAudioGroupSampleDirec
if (ent.m_name.size() < 4) if (ent.m_name.size() < 4)
continue; continue;
SystemString baseName; SystemString baseName;
SystemString basePath;
if (!CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 4, _S(".dsp")) || if (!CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 4, _S(".dsp")) ||
!CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 4, _S(".wav"))) !CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 4, _S(".wav")))
{
baseName = SystemString(ent.m_name.begin(), ent.m_name.begin() + ent.m_name.size() - 4); baseName = SystemString(ent.m_name.begin(), ent.m_name.begin() + ent.m_name.size() - 4);
basePath = SystemString(ent.m_path.begin(), ent.m_path.begin() + ent.m_path.size() - 4);
}
else if (ent.m_name.size() > 7 &&
!CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 7, _S(".vadpcm")))
{
baseName = SystemString(ent.m_name.begin(), ent.m_name.begin() + ent.m_name.size() - 7);
basePath = SystemString(ent.m_path.begin(), ent.m_path.begin() + ent.m_path.size() - 7);
}
else else
continue; continue;
@ -574,7 +685,6 @@ AudioGroupSampleDirectory AudioGroupSampleDirectory::CreateAudioGroupSampleDirec
auto& entry = ret.m_entries[sampleId]; auto& entry = ret.m_entries[sampleId];
entry = MakeObj<Entry>(); entry = MakeObj<Entry>();
SystemString basePath = SystemString(ent.m_path.begin(), ent.m_path.begin() + ent.m_path.size() - 4);
entry->loadLooseData(basePath); entry->loadLooseData(basePath);
} }
@ -598,7 +708,7 @@ void AudioGroupSampleDirectory::_extractWAV(SampleId id, const EntryData& ent,
SampleFormat fmt = SampleFormat(ent.m_numSamples >> 24); SampleFormat fmt = SampleFormat(ent.m_numSamples >> 24);
uint32_t numSamples = ent.m_numSamples & 0xffffff; uint32_t numSamples = ent.m_numSamples & 0xffffff;
if (ent.m_loopLengthSamples) if (ent.isLooped())
{ {
WAVHeaderLoop header; WAVHeaderLoop header;
header.fmtChunk.sampleRate = ent.m_sampleRate; header.fmtChunk.sampleRate = ent.m_sampleRate;
@ -652,19 +762,19 @@ void AudioGroupSampleDirectory::_extractWAV(SampleId id, const EntryData& ent,
else if (fmt == SampleFormat::N64) else if (fmt == SampleFormat::N64)
{ {
uint32_t remSamples = numSamples; uint32_t remSamples = numSamples;
uint32_t numFrames = (remSamples + 31) / 32; uint32_t numFrames = (remSamples + 63) / 64;
const unsigned char* cur = samp + sizeof(ADPCMParms::VADPCMParms); const unsigned char* cur = samp + sizeof(ADPCMParms::VADPCMParms);
for (uint32_t i = 0; i < numFrames; ++i) for (uint32_t i = 0; i < numFrames; ++i)
{ {
int16_t decomp[32] = {}; int16_t decomp[64] = {};
unsigned thisSamples = std::min(remSamples, 32u); unsigned thisSamples = std::min(remSamples, 64u);
N64MusyXDecompressFrame(decomp, cur, ent.m_ADPCMParms.vadpcm.m_coefs, thisSamples); N64MusyXDecompressFrame(decomp, cur, ent.m_ADPCMParms.vadpcm.m_coefs, thisSamples);
remSamples -= thisSamples; remSamples -= thisSamples;
cur += 16; cur += 40;
w.writeBytes(decomp, thisSamples * 2); w.writeBytes(decomp, thisSamples * 2);
} }
dataLen = sizeof(ADPCMParms::VADPCMParms) + (numSamples + 31) / 32 * 16; dataLen = sizeof(ADPCMParms::VADPCMParms) + (numSamples + 63) / 64 * 40;
} }
else if (fmt == SampleFormat::PCM) else if (fmt == SampleFormat::PCM)
{ {
@ -732,7 +842,7 @@ void AudioGroupSampleDirectory::_extractCompressed(SampleId id, const EntryData&
header.x0_num_samples = numSamples; header.x0_num_samples = numSamples;
header.x4_num_nibbles = DSPSampleToNibble(numSamples); header.x4_num_nibbles = DSPSampleToNibble(numSamples);
header.x8_sample_rate = ent.m_sampleRate; header.x8_sample_rate = ent.m_sampleRate;
header.xc_loop_flag = atUint16(ent.m_loopLengthSamples != 0); header.xc_loop_flag = atUint16(ent.isLooped());
if (header.xc_loop_flag) if (header.xc_loop_flag)
{ {
header.x10_loop_start_nibble = DSPSampleToNibble(ent.getLoopStartSample()); header.x10_loop_start_nibble = DSPSampleToNibble(ent.getLoopStartSample());
@ -757,7 +867,14 @@ void AudioGroupSampleDirectory::_extractCompressed(SampleId id, const EntryData&
{ {
path += _S(".vadpcm"); path += _S(".vadpcm");
athena::io::FileWriter w(path); athena::io::FileWriter w(path);
dataLen = sizeof(ADPCMParms::VADPCMParms) + (numSamples + 31) / 32 * 16; VADPCMHeader header;
header.m_pitchSampleRate = ent.m_pitch << 24;
header.m_pitchSampleRate |= ent.m_sampleRate & 0xffff;
header.m_numSamples = ent.m_numSamples;
header.m_loopStartSample = ent.m_loopStartSample;
header.m_loopLengthSamples = ent.m_loopLengthSamples;
header.write(w);
dataLen = 256 + (numSamples + 63) / 64 * 40;
w.writeUBytes(samp, dataLen); w.writeUBytes(samp, dataLen);
} }
else if (fmt == SampleFormat::PCM_PC || fmt == SampleFormat::PCM) else if (fmt == SampleFormat::PCM_PC || fmt == SampleFormat::PCM)
@ -779,7 +896,7 @@ void AudioGroupSampleDirectory::_extractCompressed(SampleId id, const EntryData&
header.x0_num_samples = numSamples; header.x0_num_samples = numSamples;
header.x4_num_nibbles = DSPSampleToNibble(numSamples); header.x4_num_nibbles = DSPSampleToNibble(numSamples);
header.x8_sample_rate = ent.m_sampleRate; header.x8_sample_rate = ent.m_sampleRate;
header.xc_loop_flag = atUint16(ent.m_loopLengthSamples != 0); header.xc_loop_flag = atUint16(ent.isLooped());
header.m_pitch = ent.m_pitch; header.m_pitch = ent.m_pitch;
if (header.xc_loop_flag) if (header.xc_loop_flag)
{ {
@ -864,9 +981,19 @@ void AudioGroupSampleDirectory::reloadSampleData(SystemStringView groupPath)
if (ent.m_name.size() < 4) if (ent.m_name.size() < 4)
continue; continue;
SystemString baseName; SystemString baseName;
SystemString basePath;
if (!CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 4, _S(".dsp")) || if (!CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 4, _S(".dsp")) ||
!CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 4, _S(".wav"))) !CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 4, _S(".wav")))
{
baseName = SystemString(ent.m_name.begin(), ent.m_name.begin() + ent.m_name.size() - 4); baseName = SystemString(ent.m_name.begin(), ent.m_name.begin() + ent.m_name.size() - 4);
basePath = SystemString(ent.m_path.begin(), ent.m_path.begin() + ent.m_path.size() - 4);
}
else if (ent.m_name.size() > 7 &&
!CompareCaseInsensitive(ent.m_name.data() + ent.m_name.size() - 7, _S(".vadpcm")))
{
baseName = SystemString(ent.m_name.begin(), ent.m_name.begin() + ent.m_name.size() - 7);
basePath = SystemString(ent.m_path.begin(), ent.m_path.begin() + ent.m_path.size() - 7);
}
else else
continue; continue;
@ -883,7 +1010,6 @@ void AudioGroupSampleDirectory::reloadSampleData(SystemStringView groupPath)
auto& entry = m_entries[sampleId]; auto& entry = m_entries[sampleId];
entry = MakeObj<Entry>(); entry = MakeObj<Entry>();
SystemString basePath = SystemString(ent.m_path.begin(), ent.m_path.begin() + ent.m_path.size() - 4);
entry->loadLooseData(basePath); entry->loadLooseData(basePath);
} }
} }

View File

@ -1960,8 +1960,11 @@ std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> ContainerRegistry:
{ {
std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> ret; std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> ret;
const SystemChar* sep = std::max(StrRChr(path, _S('/')), StrRChr(path, _S('\\'))); SystemString baseName;
SystemString baseName(sep + 1, dot - sep - 1); if (const SystemChar* sep = std::max(StrRChr(path, _S('/')), StrRChr(path, _S('\\'))))
baseName = SystemString(sep + 1, dot - sep - 1);
else
baseName = SystemString(path, dot - path);
/* Project */ /* Project */
SystemChar projPath[1024]; SystemChar projPath[1024];

View File

@ -441,7 +441,7 @@ void Engine::removeListener(Listener* listener)
} }
/** Start song playing from loaded audio groups */ /** Start song playing from loaded audio groups */
ObjToken<Sequencer> Engine::seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData, ObjToken<Studio> smx) ObjToken<Sequencer> Engine::seqPlay(GroupId groupId, SongId songId, const unsigned char* arrData, bool loop, ObjToken<Studio> smx)
{ {
std::pair<AudioGroup*, const SongGroupIndex*> songGrp = _findSongGroup(groupId); std::pair<AudioGroup*, const SongGroupIndex*> songGrp = _findSongGroup(groupId);
if (songGrp.second) if (songGrp.second)
@ -451,7 +451,7 @@ ObjToken<Sequencer> Engine::seqPlay(GroupId groupId, SongId songId, const unsign
return {}; return {};
if (arrData) if (arrData)
(*ret)->playSong(arrData); (*ret)->playSong(arrData, loop);
return *ret; return *ret;
} }
@ -468,7 +468,7 @@ ObjToken<Sequencer> Engine::seqPlay(GroupId groupId, SongId songId, const unsign
} }
ObjToken<Sequencer> Engine::seqPlay(const AudioGroup* group, GroupId groupId, SongId songId, ObjToken<Sequencer> Engine::seqPlay(const AudioGroup* group, GroupId groupId, SongId songId,
const unsigned char* arrData, ObjToken<Studio> smx) const unsigned char* arrData, bool loop, ObjToken<Studio> smx)
{ {
const SongGroupIndex* sgIdx = group->getProj().getSongGroupIndex(groupId); const SongGroupIndex* sgIdx = group->getProj().getSongGroupIndex(groupId);
if (sgIdx) if (sgIdx)
@ -478,7 +478,7 @@ ObjToken<Sequencer> Engine::seqPlay(const AudioGroup* group, GroupId groupId, So
return {}; return {};
if (arrData) if (arrData)
(*ret)->playSong(arrData); (*ret)->playSong(arrData, loop);
return *ret; return *ret;
} }

View File

@ -257,7 +257,7 @@ ObjToken<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
if (m_parent->m_songGroup) if (m_parent->m_songGroup)
{ {
oid = m_page->objId; oid = m_page->objId;
res = (*ret)->loadPageObject(oid, m_parent->m_ticksPerSec, note, velocity, m_ctrlVals[1]); res = (*ret)->loadPageObject(oid, m_ticksPerSec, note, velocity, m_ctrlVals[1]);
} }
else if (m_parent->m_sfxMappings.size()) else if (m_parent->m_sfxMappings.size())
{ {
@ -265,7 +265,7 @@ ObjToken<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
const SFXGroupIndex::SFXEntry* sfxEntry = m_parent->m_sfxMappings[lookupIdx]; const SFXGroupIndex::SFXEntry* sfxEntry = m_parent->m_sfxMappings[lookupIdx];
oid = sfxEntry->objId; oid = sfxEntry->objId;
note = sfxEntry->defKey; note = sfxEntry->defKey;
res = (*ret)->loadPageObject(oid, m_parent->m_ticksPerSec, note, velocity, m_ctrlVals[1]); res = (*ret)->loadPageObject(oid, m_ticksPerSec, note, velocity, m_ctrlVals[1]);
} }
else else
return {}; return {};
@ -446,7 +446,16 @@ void Sequencer::setPitchWheel(uint8_t chan, float pitchWheel)
m_chanStates[chan].setPitchWheel(pitchWheel); m_chanStates[chan].setPitchWheel(pitchWheel);
} }
void Sequencer::setTempo(double ticksPerSec) { m_ticksPerSec = ticksPerSec; } void Sequencer::setTempo(uint8_t chan, double ticksPerSec)
{
m_chanStates[chan].m_ticksPerSec = ticksPerSec;
}
void Sequencer::setTempo(double ticksPerSec)
{
for (auto& c : m_chanStates)
c.m_ticksPerSec = ticksPerSec;
}
void Sequencer::ChannelState::allOff() void Sequencer::ChannelState::allOff()
{ {
@ -598,12 +607,12 @@ void Sequencer::sendMacroMessage(ObjectId macroId, int32_t val)
chan.sendMacroMessage(macroId, val); chan.sendMacroMessage(macroId, val);
} }
void Sequencer::playSong(const unsigned char* arrData, bool dieOnEnd) void Sequencer::playSong(const unsigned char* arrData, bool loop, bool dieOnEnd)
{ {
m_arrData = arrData; m_arrData = arrData;
m_dieOnEnd = dieOnEnd; m_dieOnEnd = dieOnEnd;
m_songState.initialize(arrData); m_songState.initialize(arrData, loop);
setTempo(m_songState.getTempo() * 384 / 60); setTempo(m_songState.getInitialTempo() * 384 / 60.0);
m_state = SequencerState::Playing; m_state = SequencerState::Playing;
} }

View File

@ -83,17 +83,28 @@ struct Event
class MIDIDecoder class MIDIDecoder
{ {
int m_tick = 0; int m_tick = 0;
std::vector<std::pair<int, std::multimap<int, Event>>> m_results[16]; std::vector<std::multimap<int, Event>> m_results[16];
std::multimap<int, int> m_tempos; std::multimap<int, int> m_tempos;
std::array<std::multimap<int, Event>::iterator, 128> m_notes[16]; std::array<std::multimap<int, Event>::iterator, 128> m_notes[16];
int m_minLoopStart[16];
int m_minLoopEnd[16];
void _addProgramChange(int chan, int prog) bool isEmptyIterator(int chan, std::multimap<int, Event>::iterator it) const
{
for (const auto& res : m_results[chan])
if (res.end() == it)
return true;
return false;
}
void _addRegionChange(int chan)
{ {
auto& results = m_results[chan]; auto& results = m_results[chan];
results.reserve(2);
results.emplace_back(); results.emplace_back();
results.back().first = prog; if (results.size() == 1)
for (size_t i = 0; i < 128; ++i) for (size_t i = 0; i < 128; ++i)
m_notes[chan][i] = results.back().second.end(); m_notes[chan][i] = results.back().end();
} }
uint8_t m_status = 0; uint8_t m_status = 0;
@ -125,8 +136,15 @@ class MIDIDecoder
} }
public: public:
MIDIDecoder()
{
std::fill(std::begin(m_minLoopStart), std::end(m_minLoopStart), INT_MAX);
std::fill(std::begin(m_minLoopEnd), std::end(m_minLoopEnd), INT_MAX);
}
std::vector<uint8_t>::const_iterator receiveBytes(std::vector<uint8_t>::const_iterator begin, std::vector<uint8_t>::const_iterator receiveBytes(std::vector<uint8_t>::const_iterator begin,
std::vector<uint8_t>::const_iterator end) std::vector<uint8_t>::const_iterator end,
int loopStart[16] = nullptr, int loopEnd[16] = nullptr)
{ {
std::vector<uint8_t>::const_iterator it = begin; std::vector<uint8_t>::const_iterator it = begin;
while (it != end) while (it != end)
@ -146,7 +164,7 @@ public:
{ {
/* Meta events */ /* Meta events */
if (it == end) if (it == end)
return begin; break;
a = *it++; a = *it++;
uint32_t length; uint32_t length;
@ -168,25 +186,36 @@ public:
uint8_t chan = m_status & 0xf; uint8_t chan = m_status & 0xf;
auto& results = m_results[chan]; auto& results = m_results[chan];
/* Not actually used as such for now */ if (loopEnd && loopEnd[chan] != INT_MAX && m_tick >= loopEnd[chan])
if (results.empty()) break;
_addProgramChange(chan, 0);
std::multimap<int, Event>& res = results.back().second; /* Split region at loop start point */
if (loopStart && loopStart[chan] != INT_MAX && m_tick >= loopStart[chan])
{
_addRegionChange(chan);
loopStart[chan] = INT_MAX;
}
else if (results.empty())
{
_addRegionChange(chan);
}
std::multimap<int, Event>& res = results.back();
switch (Status(m_status & 0xf0)) switch (Status(m_status & 0xf0))
{ {
case Status::NoteOff: case Status::NoteOff:
{ {
if (it == end) if (it == end)
return begin; break;
a = *it++; a = *it++;
if (it == end) if (it == end)
return begin; break;
b = *it++; b = *it++;
uint8_t notenum = clamp7(a); uint8_t notenum = clamp7(a);
std::multimap<int, Event>::iterator note = m_notes[chan][notenum]; std::multimap<int, Event>::iterator note = m_notes[chan][notenum];
if (note != res.end()) if (!isEmptyIterator(chan, note))
{ {
note->second.length = m_tick - note->first; note->second.length = m_tick - note->first;
m_notes[chan][notenum] = res.end(); m_notes[chan][notenum] = res.end();
@ -196,16 +225,16 @@ public:
case Status::NoteOn: case Status::NoteOn:
{ {
if (it == end) if (it == end)
return begin; break;
a = *it++; a = *it++;
if (it == end) if (it == end)
return begin; break;
b = *it++; b = *it++;
uint8_t notenum = clamp7(a); uint8_t notenum = clamp7(a);
uint8_t vel = clamp7(b); uint8_t vel = clamp7(b);
std::multimap<int, Event>::iterator note = m_notes[chan][notenum]; std::multimap<int, Event>::iterator note = m_notes[chan][notenum];
if (note != res.end()) if (!isEmptyIterator(chan, note))
note->second.length = m_tick - note->first; note->second.length = m_tick - note->first;
if (vel != 0) if (vel != 0)
@ -218,28 +247,33 @@ public:
case Status::NotePressure: case Status::NotePressure:
{ {
if (it == end) if (it == end)
return begin; break;
a = *it++; a = *it++;
if (it == end) if (it == end)
return begin; break;
b = *it++; b = *it++;
break; break;
} }
case Status::ControlChange: case Status::ControlChange:
{ {
if (it == end) if (it == end)
return begin; break;
a = *it++; a = *it++;
if (it == end) if (it == end)
return begin; break;
b = *it++; b = *it++;
if (a == 0x66)
m_minLoopStart[chan] = std::min(m_tick, m_minLoopStart[chan]);
else if (a == 0x67)
m_minLoopEnd[chan] = std::min(m_tick, m_minLoopEnd[chan]);
else
res.emplace(m_tick, Event{CtrlEvent{}, chan, clamp7(a), clamp7(b), 0}); res.emplace(m_tick, Event{CtrlEvent{}, chan, clamp7(a), clamp7(b), 0});
break; break;
} }
case Status::ProgramChange: case Status::ProgramChange:
{ {
if (it == end) if (it == end)
return begin; break;
a = *it++; a = *it++;
res.emplace(m_tick, Event{ProgEvent{}, chan, a}); res.emplace(m_tick, Event{ProgEvent{}, chan, a});
break; break;
@ -247,17 +281,17 @@ public:
case Status::ChannelPressure: case Status::ChannelPressure:
{ {
if (it == end) if (it == end)
return begin; break;
a = *it++; a = *it++;
break; break;
} }
case Status::PitchBend: case Status::PitchBend:
{ {
if (it == end) if (it == end)
return begin; break;
a = *it++; a = *it++;
if (it == end) if (it == end)
return begin; break;
b = *it++; b = *it++;
res.emplace(m_tick, Event{PitchEvent{}, chan, clamp7(b) * 128 + clamp7(a)}); res.emplace(m_tick, Event{PitchEvent{}, chan, clamp7(b) * 128 + clamp7(a)});
break; break;
@ -270,30 +304,30 @@ public:
{ {
uint32_t len; uint32_t len;
if (!_readContinuedValue(it, end, len) || end - it < len) if (!_readContinuedValue(it, end, len) || end - it < len)
return begin; break;
break; break;
} }
case Status::TimecodeQuarterFrame: case Status::TimecodeQuarterFrame:
{ {
if (it == end) if (it == end)
return begin; break;
a = *it++; a = *it++;
break; break;
} }
case Status::SongPositionPointer: case Status::SongPositionPointer:
{ {
if (it == end) if (it == end)
return begin; break;
a = *it++; a = *it++;
if (it == end) if (it == end)
return begin; break;
b = *it++; b = *it++;
break; break;
} }
case Status::SongSelect: case Status::SongSelect:
{ {
if (it == end) if (it == end)
return begin; break;
a = *it++; a = *it++;
break; break;
} }
@ -315,11 +349,14 @@ public:
} }
} }
} }
return it; return it;
} }
std::vector<std::pair<int, std::multimap<int, Event>>>& getResults(int chan) { return m_results[chan]; } std::vector<std::multimap<int, Event>>& getResults(int chan) { return m_results[chan]; }
std::multimap<int, int>& getTempos() { return m_tempos; } std::multimap<int, int>& getTempos() { return m_tempos; }
int getMinLoopStart(int chan) const { return m_minLoopStart[chan]; }
int getMinLoopEnd(int chan) const { return m_minLoopEnd[chan]; }
}; };
class MIDIEncoder class MIDIEncoder
@ -653,7 +690,7 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
ret.push_back(1); ret.push_back(1);
SongState song; SongState song;
if (!song.initialize(data)) if (!song.initialize(data, false))
return {}; return {};
versionOut = song.m_sngVersion; versionOut = song.m_sngVersion;
isBig = song.m_bigEndian; isBig = song.m_bigEndian;
@ -681,15 +718,18 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
encoder.getResult().push_back(0x51); encoder.getResult().push_back(0x51);
encoder.getResult().push_back(3); encoder.getResult().push_back(3);
uint32_t tempo24 = SBig(60000000 / song.m_tempo); uint32_t tempo24 = SBig(60000000 / (song.m_header.m_initialTempo & 0x7fffffff));
for (int i = 1; i < 4; ++i) for (int i = 1; i < 4; ++i)
encoder.getResult().push_back(reinterpret_cast<uint8_t*>(&tempo24)[i]); encoder.getResult().push_back(reinterpret_cast<uint8_t*>(&tempo24)[i]);
/* Write out tempo changes */ /* Write out tempo changes */
int lastTick = 0; int lastTick = 0;
while (song.m_tempoPtr && song.m_tempoPtr->m_tick != 0xffffffff) const SongState::TempoChange* tempoPtr = nullptr;
if (song.m_header.m_tempoTableOff)
tempoPtr = reinterpret_cast<const SongState::TempoChange*>(song.m_songData + song.m_header.m_tempoTableOff);
while (tempoPtr && tempoPtr->m_tick != 0xffffffff)
{ {
SongState::TempoChange change = *song.m_tempoPtr; SongState::TempoChange change = *tempoPtr;
if (song.m_bigEndian) if (song.m_bigEndian)
change.swapBig(); change.swapBig();
@ -703,7 +743,7 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
for (int i = 1; i < 4; ++i) for (int i = 1; i < 4; ++i)
encoder.getResult().push_back(reinterpret_cast<uint8_t*>(&tempo24)[i]); encoder.getResult().push_back(reinterpret_cast<uint8_t*>(&tempo24)[i]);
++song.m_tempoPtr; ++tempoPtr;
} }
encoder.getResult().push_back(0); encoder.getResult().push_back(0);
@ -721,6 +761,8 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
ret.insert(ret.cend(), encoder.getResult().begin(), encoder.getResult().end()); ret.insert(ret.cend(), encoder.getResult().begin(), encoder.getResult().end());
} }
bool loopsAdded = false;
/* Iterate each SNG track into type-1 MIDI track */ /* Iterate each SNG track into type-1 MIDI track */
for (SongState::Track& trk : song.m_tracks) for (SongState::Track& trk : song.m_tracks)
{ {
@ -733,7 +775,7 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
while (trk.m_nextRegion->indexValid(song.m_bigEndian)) while (trk.m_nextRegion->indexValid(song.m_bigEndian))
{ {
std::multimap<int, Event> events; std::multimap<int, Event> events;
trk.advanceRegion(nullptr); trk.advanceRegion();
uint32_t regStart = uint32_t regStart =
song.m_bigEndian ? SBig(trk.m_curRegion->m_startTick) : trk.m_curRegion->m_startTick; song.m_bigEndian ? SBig(trk.m_curRegion->m_startTick) : trk.m_curRegion->m_startTick;
@ -900,6 +942,17 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
} }
} }
/* Add loop events */
if (!loopsAdded && trk.m_nextRegion->indexLoop(song.m_bigEndian) != -1)
{
uint32_t loopEnd =
song.m_bigEndian ? SBig(trk.m_nextRegion->m_startTick) : trk.m_nextRegion->m_startTick;
allEvents.emplace(trk.m_loopStartTick, Event{CtrlEvent{}, trk.m_midiChan, 0x66, 0, 0});
allEvents.emplace(loopEnd, Event{CtrlEvent{}, trk.m_midiChan, 0x67, 0, 0});
if (!(song.m_header.m_initialTempo & 0x80000000))
loopsAdded = true;
}
/* Emit MIDI events */ /* Emit MIDI events */
int lastTime = 0; int lastTime = 0;
for (auto& pair : allEvents) for (auto& pair : allEvents)
@ -1021,6 +1074,55 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
std::vector<Region> regions; std::vector<Region> regions;
int curRegionOff = 0; int curRegionOff = 0;
/* Pre-iterate to extract loop events */
int loopStart[16];
int loopEnd[16];
int loopChanCount = 0;
{
int loopChanIdx = -1;
for (int c = 0; c < 16; ++c)
{
loopStart[c] = INT_MAX;
loopEnd[c] = INT_MAX;
std::vector<uint8_t>::const_iterator tmpIt = it;
for (int i = 0; i < header.count; ++i)
{
if (memcmp(&*tmpIt, "MTrk", 4))
return {};
tmpIt += 4;
uint32_t length = SBig(*reinterpret_cast<const uint32_t*>(&*tmpIt));
tmpIt += 4;
std::vector<uint8_t>::const_iterator begin = tmpIt;
std::vector<uint8_t>::const_iterator end = tmpIt + length;
tmpIt = end;
MIDIDecoder dec;
dec.receiveBytes(begin, end);
loopStart[c] = std::min(dec.getMinLoopStart(c), loopStart[c]);
loopEnd[c] = std::min(dec.getMinLoopEnd(c), loopEnd[c]);
}
if (loopStart[c] == INT_MAX || loopEnd[c] == INT_MAX)
{
loopStart[c] = INT_MAX;
loopEnd[c] = INT_MAX;
}
else
{
++loopChanCount;
loopChanIdx = c;
}
}
if (loopChanCount == 1)
{
for (int c = 0; c < 16; ++c)
{
loopStart[c] = loopStart[loopChanIdx];
loopEnd[c] = loopEnd[loopChanIdx];
}
}
}
for (int i = 0; i < header.count; ++i) for (int i = 0; i < header.count; ++i)
{ {
if (memcmp(&*it, "MTrk", 4)) if (memcmp(&*it, "MTrk", 4))
@ -1069,25 +1171,29 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
it = end; it = end;
MIDIDecoder dec; MIDIDecoder dec;
dec.receiveBytes(begin, end); int tmpLoopStart[16];
int tmpLoopEnd[16];
std::copy(std::begin(loopStart), std::end(loopStart), std::begin(tmpLoopStart));
std::copy(std::begin(loopEnd), std::end(loopEnd), std::begin(tmpLoopEnd));
dec.receiveBytes(begin, end, tmpLoopStart, tmpLoopEnd);
for (int c = 0; c < 16; ++c) for (int c = 0; c < 16; ++c)
{ {
std::vector<std::pair<int, std::multimap<int, Event>>>& results = dec.getResults(c); std::vector<std::multimap<int, Event>>& results = dec.getResults(c);
int lastTrackStartTick = 0;
bool didChanInit = false; bool didChanInit = false;
for (auto& prog : results) int lastEventTick = 0;
for (auto& chanRegion : results)
{ {
bool didInit = false; bool didInit = false;
int startTick = 0; int startTick = 0;
int lastEventTick = 0; lastEventTick = 0;
int lastPitchTick = 0; int lastPitchTick = 0;
int lastPitchVal = 0; int lastPitchVal = 0;
int lastModTick = 0; int lastModTick = 0;
int lastModVal = 0; int lastModVal = 0;
Region region; Region region;
for (auto& event : prog.second) for (auto& event : chanRegion)
{ {
uint32_t eventTick = event.first * 384 / header.div; uint32_t eventTick = event.first * 384 / header.div;
@ -1097,7 +1203,6 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
{ {
didInit = true; didInit = true;
startTick = eventTick; startTick = eventTick;
lastTrackStartTick = startTick;
lastEventTick = startTick; lastEventTick = startTick;
lastPitchTick = startTick; lastPitchTick = startTick;
lastPitchVal = 0; lastPitchVal = 0;
@ -1322,7 +1427,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
reg.m_unk1 = 0xff; reg.m_unk1 = 0xff;
reg.m_unk2 = 0; reg.m_unk2 = 0;
reg.m_regionIndex = SBig(uint16_t(regIdx)); reg.m_regionIndex = SBig(uint16_t(regIdx));
reg.m_unk3 = 0; reg.m_loopToRegion = 0;
} }
else else
{ {
@ -1331,7 +1436,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
reg.m_unk1 = 0xff; reg.m_unk1 = 0xff;
reg.m_unk2 = 0; reg.m_unk2 = 0;
reg.m_regionIndex = uint16_t(regIdx); reg.m_regionIndex = uint16_t(regIdx);
reg.m_unk3 = 0; reg.m_loopToRegion = 0;
} }
} }
} }
@ -1341,23 +1446,37 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
/* Terminating region header */ /* Terminating region header */
regionBuf.emplace_back(); regionBuf.emplace_back();
SongState::TrackRegion& reg = regionBuf.back(); SongState::TrackRegion& reg = regionBuf.back();
uint32_t termStartTick = 0;
int16_t termRegionIdx = -1;
int16_t termLoopToRegion = 0;
if (loopEnd[c] != INT_MAX)
{
termStartTick = loopEnd[c];
if (lastEventTick >= loopStart[c])
{
termRegionIdx = -2;
termLoopToRegion = results.size() - 1;
}
}
if (big) if (big)
{ {
reg.m_startTick = SBig(uint32_t(lastTrackStartTick)); reg.m_startTick = SBig(termStartTick);
reg.m_progNum = 0xff; reg.m_progNum = 0xff;
reg.m_unk1 = 0xff; reg.m_unk1 = 0xff;
reg.m_unk2 = 0; reg.m_unk2 = 0;
reg.m_regionIndex = -1; reg.m_regionIndex = SBig(termRegionIdx);
reg.m_unk3 = 0; reg.m_loopToRegion = SBig(termLoopToRegion);
} }
else else
{ {
reg.m_startTick = uint32_t(lastTrackStartTick); reg.m_startTick = termStartTick;
reg.m_progNum = 0xff; reg.m_progNum = 0xff;
reg.m_unk1 = 0xff; reg.m_unk1 = 0xff;
reg.m_unk2 = 0; reg.m_unk2 = 0;
reg.m_regionIndex = -1; reg.m_regionIndex = termRegionIdx;
reg.m_unk3 = 0; reg.m_loopToRegion = termLoopToRegion;
} }
} }
} }
@ -1366,17 +1485,29 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
if (version == 1) if (version == 1)
{ {
SongState::Header head; SongState::Header head;
head.m_trackIdxOff = 0x18; head.m_initialTempo = initTempo;
head.m_regionIdxOff = 0x18 + 4 * 64 + regionBuf.size() * 12; head.m_loopStartTicks[0] = 0;
if (loopChanCount == 1)
{
head.m_loopStartTicks[0] = loopStart[0] == INT_MAX ? 0 : loopStart[0];
}
else if (loopChanCount > 1)
{
for (int i = 0; i < 16; ++i)
head.m_loopStartTicks[i] = loopStart[i] == INT_MAX ? 0 : loopStart[i];
head.m_initialTempo |= 0x80000000;
}
size_t headSz = (head.m_initialTempo & 0x80000000) ? 0x58 : 0x18;
head.m_trackIdxOff = headSz;
head.m_regionIdxOff = headSz + 4 * 64 + regionBuf.size() * 12;
head.m_chanMapOff = head.m_regionIdxOff + 4 * regionDataIdxArr.size() + curRegionOff; head.m_chanMapOff = head.m_regionIdxOff + 4 * regionDataIdxArr.size() + curRegionOff;
head.m_tempoTableOff = tempoBuf.size() ? head.m_chanMapOff + 64 : 0; head.m_tempoTableOff = tempoBuf.size() ? head.m_chanMapOff + 64 : 0;
head.m_initialTempo = initTempo; head.m_chanMapOff2 = head.m_chanMapOff;
head.m_unkOff = 0;
uint32_t regIdxOff = head.m_regionIdxOff; uint32_t regIdxOff = head.m_regionIdxOff;
if (big) if (big)
head.swapBig(); head.swapToBig();
*reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), 0x18, 0)) = head; *reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), headSz, 0)) = head;
for (int i = 0; i < 64; ++i) for (int i = 0; i < 64; ++i)
{ {
@ -1388,7 +1519,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
uint32_t idx = trackRegionIdxArr[i]; uint32_t idx = trackRegionIdxArr[i];
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = *reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) =
big ? SBig(uint32_t(0x18 + 4 * 64 + idx * 12)) : uint32_t(0x18 + 4 * 64 + idx * 12); big ? SBig(uint32_t(headSz + 4 * 64 + idx * 12)) : uint32_t(headSz + 4 * 64 + idx * 12);
} }
for (SongState::TrackRegion& reg : regionBuf) for (SongState::TrackRegion& reg : regionBuf)
@ -1442,17 +1573,29 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
else else
{ {
SongState::Header head; SongState::Header head;
head.m_trackIdxOff = 0x18 + regionBuf.size() * 12; head.m_initialTempo = initTempo;
head.m_loopStartTicks[0] = 0;
if (loopChanCount == 1)
{
head.m_loopStartTicks[0] = loopStart[0] == INT_MAX ? 0 : loopStart[0];
}
else if (loopChanCount > 1)
{
for (int i = 0; i < 16; ++i)
head.m_loopStartTicks[i] = loopStart[i] == INT_MAX ? 0 : loopStart[i];
head.m_initialTempo |= 0x80000000;
}
size_t headSz = (head.m_initialTempo & 0x80000000) ? 0x58 : 0x18;
head.m_trackIdxOff = headSz + regionBuf.size() * 12;
head.m_regionIdxOff = head.m_trackIdxOff + 4 * 64 + 64 + curRegionOff; head.m_regionIdxOff = head.m_trackIdxOff + 4 * 64 + 64 + curRegionOff;
head.m_chanMapOff = head.m_trackIdxOff + 4 * 64; head.m_chanMapOff = head.m_trackIdxOff + 4 * 64;
head.m_tempoTableOff = tempoBuf.size() ? head.m_regionIdxOff + 4 * regionDataIdxArr.size() : 0; head.m_tempoTableOff = tempoBuf.size() ? head.m_regionIdxOff + 4 * regionDataIdxArr.size() : 0;
head.m_initialTempo = initTempo; head.m_chanMapOff2 = head.m_chanMapOff;
head.m_unkOff = 0;
uint32_t chanMapOff = head.m_chanMapOff; uint32_t chanMapOff = head.m_chanMapOff;
if (big) if (big)
head.swapBig(); head.swapToBig();
*reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), 0x18, 0)) = head; *reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), headSz, 0)) = head;
for (SongState::TrackRegion& reg : regionBuf) for (SongState::TrackRegion& reg : regionBuf)
*reinterpret_cast<SongState::TrackRegion*>(&*ret.insert(ret.cend(), 12, 0)) = reg; *reinterpret_cast<SongState::TrackRegion*>(&*ret.insert(ret.cend(), 12, 0)) = reg;
@ -1467,7 +1610,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
uint32_t idx = trackRegionIdxArr[i]; uint32_t idx = trackRegionIdxArr[i];
*reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) = *reinterpret_cast<uint32_t*>(&*ret.insert(ret.cend(), 4, 0)) =
big ? SBig(uint32_t(0x18 + 4 * 64 + idx * 12)) : uint32_t(0x18 + 4 * 64 + idx * 12); big ? SBig(uint32_t(headSz + 4 * 64 + idx * 12)) : uint32_t(headSz + 4 * 64 + idx * 12);
} }
memmove(&*ret.insert(ret.cend(), 64, 0), chanMap.data(), 64); memmove(&*ret.insert(ret.cend(), 64, 0), chanMap.data(), 64);

View File

@ -75,14 +75,68 @@ static uint32_t DecodeTime(const unsigned char*& data)
return ret; return ret;
} }
void SongState::Header::swapBig() void SongState::Header::swapFromBig()
{ {
m_trackIdxOff = SBig(m_trackIdxOff); m_trackIdxOff = SBig(m_trackIdxOff);
m_regionIdxOff = SBig(m_regionIdxOff); m_regionIdxOff = SBig(m_regionIdxOff);
m_chanMapOff = SBig(m_chanMapOff); m_chanMapOff = SBig(m_chanMapOff);
m_tempoTableOff = SBig(m_tempoTableOff); m_tempoTableOff = SBig(m_tempoTableOff);
m_initialTempo = SBig(m_initialTempo); m_initialTempo = SBig(m_initialTempo);
m_unkOff = SBig(m_unkOff); if (m_initialTempo & 0x80000000)
{
for (int i = 0; i < 16; ++i)
m_loopStartTicks[i] = SBig(m_loopStartTicks[i]);
m_chanMapOff2 = SBig(m_chanMapOff2);
}
else
{
m_loopStartTicks[0] = SBig(m_loopStartTicks[0]);
}
}
void SongState::Header::swapToBig()
{
m_trackIdxOff = SBig(m_trackIdxOff);
m_regionIdxOff = SBig(m_regionIdxOff);
m_chanMapOff = SBig(m_chanMapOff);
m_tempoTableOff = SBig(m_tempoTableOff);
m_initialTempo = SBig(m_initialTempo);
if (m_initialTempo & 0x00000080)
{
for (int i = 0; i < 16; ++i)
m_loopStartTicks[i] = SBig(m_loopStartTicks[i]);
m_chanMapOff2 = SBig(m_chanMapOff2);
}
else
{
m_loopStartTicks[0] = SBig(m_loopStartTicks[0]);
}
}
SongState::Header& SongState::Header::operator=(const Header& other)
{
m_trackIdxOff = other.m_trackIdxOff;
m_regionIdxOff = other.m_regionIdxOff;
m_chanMapOff = other.m_chanMapOff;
m_tempoTableOff = other.m_tempoTableOff;
m_initialTempo = other.m_initialTempo;
if (SBig(m_initialTempo) & 0x80000000)
{
for (int i = 0; i < 16; ++i)
m_loopStartTicks[i] = other.m_loopStartTicks[i];
m_chanMapOff2 = other.m_chanMapOff2;
}
else
{
m_loopStartTicks[0] = other.m_loopStartTicks[0];
}
return *this;
}
bool SongState::TrackRegion::indexDone(bool bigEndian, bool loop) const
{
int16_t idx = (bigEndian ? SBig(m_regionIndex) : m_regionIndex);
return loop ? (idx == -1) : (idx < 0);
} }
bool SongState::TrackRegion::indexValid(bool bigEndian) const bool SongState::TrackRegion::indexValid(bool bigEndian) const
@ -90,6 +144,13 @@ bool SongState::TrackRegion::indexValid(bool bigEndian) const
return (bigEndian ? SBig(m_regionIndex) : m_regionIndex) >= 0; return (bigEndian ? SBig(m_regionIndex) : m_regionIndex) >= 0;
} }
int SongState::TrackRegion::indexLoop(bool bigEndian) const
{
if ((bigEndian ? SBig(m_regionIndex) : m_regionIndex) != -2)
return -1;
return (bigEndian ? SBig(m_loopToRegion) : m_loopToRegion);
}
void SongState::TempoChange::swapBig() void SongState::TempoChange::swapBig()
{ {
m_tick = SBig(m_tick); m_tick = SBig(m_tick);
@ -103,12 +164,14 @@ void SongState::Track::Header::swapBig()
m_modOff = SBig(m_modOff); m_modOff = SBig(m_modOff);
} }
SongState::Track::Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions) SongState::Track::Track(SongState& parent, uint8_t midiChan, uint32_t loopStart, const TrackRegion* regions, uint32_t tempo)
: m_parent(&parent), m_midiChan(midiChan), m_curRegion(nullptr), m_nextRegion(regions) : m_parent(&parent), m_midiChan(midiChan), m_initRegion(regions), m_curRegion(nullptr),
m_nextRegion(regions), m_loopStartTick(loopStart), m_tempo(tempo)
{ {
resetTempo();
} }
void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region) void SongState::Track::setRegion(const TrackRegion* region)
{ {
m_curRegion = region; m_curRegion = region;
uint32_t regionIdx = (m_parent->m_bigEndian ? SBig(m_curRegion->m_regionIndex) : m_curRegion->m_regionIndex); uint32_t regionIdx = (m_parent->m_bigEndian ? SBig(m_curRegion->m_regionIndex) : m_curRegion->m_regionIndex);
@ -125,13 +188,14 @@ void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
m_pitchWheelData = nullptr; m_pitchWheelData = nullptr;
m_nextPitchTick = 0x7fffffff; m_nextPitchTick = 0x7fffffff;
m_nextPitchDelta = 0; m_nextPitchDelta = 0;
m_pitchVal = 0;
if (header.m_pitchOff) if (header.m_pitchOff)
{ {
m_pitchWheelData = m_parent->m_songData + header.m_pitchOff; m_pitchWheelData = m_parent->m_songData + header.m_pitchOff;
if (m_pitchWheelData[0] != 0x80 || m_pitchWheelData[1] != 0x00) if (m_pitchWheelData[0] != 0x80 || m_pitchWheelData[1] != 0x00)
{ {
auto delta = DecodeDelta(m_pitchWheelData); auto delta = DecodeDelta(m_pitchWheelData);
m_nextPitchTick = m_parent->m_curTick + delta.first; m_nextPitchTick = m_curTick + delta.first;
m_nextPitchDelta = delta.second; m_nextPitchDelta = delta.second;
} }
} }
@ -139,27 +203,23 @@ void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
m_modWheelData = nullptr; m_modWheelData = nullptr;
m_nextModTick = 0x7fffffff; m_nextModTick = 0x7fffffff;
m_nextModDelta = 0; m_nextModDelta = 0;
m_modVal = 0;
if (header.m_modOff) if (header.m_modOff)
{ {
m_modWheelData = m_parent->m_songData + header.m_modOff; m_modWheelData = m_parent->m_songData + header.m_modOff;
if (m_modWheelData[0] != 0x80 || m_modWheelData[1] != 0x00) if (m_modWheelData[0] != 0x80 || m_modWheelData[1] != 0x00)
{ {
auto delta = DecodeDelta(m_modWheelData); auto delta = DecodeDelta(m_modWheelData);
m_nextModTick = m_parent->m_curTick + delta.first; m_nextModTick = m_curTick + delta.first;
m_nextModDelta = delta.second; m_nextModDelta = delta.second;
} }
} }
m_eventWaitCountdown = 0; m_eventWaitCountdown = 0;
m_pitchVal = 0;
m_modVal = 0;
if (seq)
{
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) if (m_parent->m_sngVersion == 1)
{
m_eventWaitCountdown = int32_t(DecodeTime(m_data)); m_eventWaitCountdown = int32_t(DecodeTime(m_data));
}
else else
{ {
int32_t absTick = (m_parent->m_bigEndian ? SBig(*reinterpret_cast<const int32_t*>(m_data)) int32_t absTick = (m_parent->m_bigEndian ? SBig(*reinterpret_cast<const int32_t*>(m_data))
@ -170,14 +230,14 @@ void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
} }
} }
void SongState::Track::advanceRegion(Sequencer* seq) { setRegion(seq, m_nextRegion); } void SongState::Track::advanceRegion() { setRegion(m_nextRegion); }
int SongState::DetectVersion(const unsigned char* ptr, bool& isBig) int SongState::DetectVersion(const unsigned char* ptr, bool& isBig)
{ {
isBig = ptr[0] == 0; isBig = ptr[0] == 0;
Header header = *reinterpret_cast<const Header*>(ptr); Header header = *reinterpret_cast<const Header*>(ptr);
if (isBig) if (isBig)
header.swapBig(); header.swapFromBig();
const uint32_t* trackIdx = reinterpret_cast<const uint32_t*>(ptr + header.m_trackIdxOff); const uint32_t* trackIdx = reinterpret_cast<const uint32_t*>(ptr + header.m_trackIdxOff);
const uint32_t* regionIdxTable = reinterpret_cast<const uint32_t*>(ptr + header.m_regionIdxOff); const uint32_t* regionIdxTable = reinterpret_cast<const uint32_t*>(ptr + header.m_regionIdxOff);
@ -347,8 +407,9 @@ int SongState::DetectVersion(const unsigned char* ptr, bool& isBig)
return v; return v;
} }
bool SongState::initialize(const unsigned char* ptr) bool SongState::initialize(const unsigned char* ptr, bool loop)
{ {
m_loop = loop;
m_sngVersion = DetectVersion(ptr, m_bigEndian); m_sngVersion = DetectVersion(ptr, m_bigEndian);
if (m_sngVersion < 0) if (m_sngVersion < 0)
return false; return false;
@ -356,7 +417,7 @@ bool SongState::initialize(const unsigned char* ptr)
m_songData = ptr; m_songData = ptr;
m_header = *reinterpret_cast<const Header*>(ptr); m_header = *reinterpret_cast<const Header*>(ptr);
if (m_bigEndian) if (m_bigEndian)
m_header.swapBig(); m_header.swapFromBig();
const uint32_t* trackIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_trackIdxOff); const uint32_t* trackIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_trackIdxOff);
m_regionIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_regionIdxOff); m_regionIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_regionIdxOff);
const uint8_t* chanMap = reinterpret_cast<const uint8_t*>(ptr + m_header.m_chanMapOff); const uint8_t* chanMap = reinterpret_cast<const uint8_t*>(ptr + m_header.m_chanMapOff);
@ -368,35 +429,69 @@ bool SongState::initialize(const unsigned char* ptr)
{ {
const TrackRegion* region = const TrackRegion* region =
reinterpret_cast<const TrackRegion*>(ptr + (m_bigEndian ? SBig(trackIdx[i]) : trackIdx[i])); reinterpret_cast<const TrackRegion*>(ptr + (m_bigEndian ? SBig(trackIdx[i]) : trackIdx[i]));
m_tracks[i] = Track(*this, chanMap[i], region); uint8_t chan = chanMap[i];
uint32_t loopStart =
(m_header.m_initialTempo & 0x80000000) ? m_header.m_loopStartTicks[chan] : m_header.m_loopStartTicks[0];
m_tracks[i] = Track(*this, chan, loopStart, region, m_header.m_initialTempo & 0x7fffffff);
} }
else else
m_tracks[i] = Track(); m_tracks[i] = Track();
} }
/* Initialize tempo */
if (m_header.m_tempoTableOff)
m_tempoPtr = reinterpret_cast<const TempoChange*>(ptr + m_header.m_tempoTableOff);
else
m_tempoPtr = nullptr;
m_tempo = m_header.m_initialTempo & 0x7fffffff;
m_curTick = 0;
m_songState = SongPlayState::Playing; m_songState = SongPlayState::Playing;
return true; return true;
} }
bool SongState::Track::advance(Sequencer& seq, int32_t ticks) void SongState::Track::resetTempo()
{ {
int32_t endTick = m_parent->m_curTick + ticks; if (m_parent->m_header.m_tempoTableOff)
m_tempoPtr = reinterpret_cast<const TempoChange*>(m_parent->m_songData + m_parent->m_header.m_tempoTableOff);
else
m_tempoPtr = nullptr;
}
bool SongState::Track::advance(Sequencer& seq, double dt)
{
m_remDt += dt;
/* Compute ticks to compute based on current tempo */
double ticksPerSecond = m_tempo * 384 / 60;
uint32_t ticks = uint32_t(std::floor(m_remDt * ticksPerSecond));
/* See if there's an upcoming tempo change in this interval */
while (m_tempoPtr && m_tempoPtr->m_tick != 0xffffffff)
{
TempoChange change = *m_tempoPtr;
if (m_parent->m_bigEndian)
change.swapBig();
if (m_curTick + ticks > change.m_tick)
ticks = change.m_tick - m_curTick;
if (ticks <= 0)
{
/* Turn over tempo */
m_tempo = change.m_tempo & 0x7fffffff;
ticksPerSecond = m_tempo * 384 / 60;
ticks = uint32_t(std::floor(m_remDt * ticksPerSecond));
seq.setTempo(m_midiChan, m_tempo * 384 / 60.0);
++m_tempoPtr;
continue;
}
break;
}
m_remDt -= ticks / ticksPerSecond;
uint32_t endTick = m_curTick + ticks;
/* Advance region if needed */ /* Advance region if needed */
while (m_nextRegion->indexValid(m_parent->m_bigEndian)) while (m_nextRegion->indexValid(m_parent->m_bigEndian))
{ {
uint32_t nextRegTick = (m_parent->m_bigEndian ? SBig(m_nextRegion->m_startTick) : m_nextRegion->m_startTick); uint32_t nextRegTick = (m_parent->m_bigEndian ?
SBig(m_nextRegion->m_startTick) : m_nextRegion->m_startTick);
if (uint32_t(endTick) > nextRegTick) if (uint32_t(endTick) > nextRegTick)
advanceRegion(&seq); advanceRegion();
else else
break; break;
} }
@ -412,13 +507,12 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
} }
} }
if (!m_data) if (m_data)
return !m_nextRegion->indexValid(m_parent->m_bigEndian); {
/* Update continuous pitch data */ /* Update continuous pitch data */
if (m_pitchWheelData) if (m_pitchWheelData)
{ {
int32_t pitchTick = m_parent->m_curTick; int32_t pitchTick = m_curTick;
int32_t remPitchTicks = ticks; int32_t remPitchTicks = ticks;
while (pitchTick < endTick) while (pitchTick < endTick)
{ {
@ -448,7 +542,7 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
/* Update continuous modulation data */ /* Update continuous modulation data */
if (m_modWheelData) if (m_modWheelData)
{ {
int32_t modTick = m_parent->m_curTick; int32_t modTick = m_curTick;
int32_t remModTicks = ticks; int32_t remModTicks = ticks;
while (modTick < endTick) while (modTick < endTick)
{ {
@ -458,7 +552,7 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
{ {
/* Update modulation */ /* Update modulation */
m_modVal += m_nextModDelta; m_modVal += m_nextModDelta;
seq.setCtrlValue(m_midiChan, 1, clamp(0, m_modVal / 128, 127)); seq.setCtrlValue(m_midiChan, 1, int8_t(clamp(0, m_modVal / 127, 127)));
if (m_modWheelData[0] != 0x80 || m_modWheelData[1] != 0x00) if (m_modWheelData[0] != 0x80 || m_modWheelData[1] != 0x00)
{ {
auto delta = DecodeDelta(m_modWheelData); auto delta = DecodeDelta(m_modWheelData);
@ -487,7 +581,7 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
m_eventWaitCountdown -= ticks; m_eventWaitCountdown -= ticks;
ticks = 0; ticks = 0;
if (m_eventWaitCountdown > 0) if (m_eventWaitCountdown > 0)
return false; break;
} }
/* Load next command */ /* Load next command */
@ -495,7 +589,7 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
{ {
/* End of channel */ /* End of channel */
m_data = nullptr; m_data = nullptr;
return !m_nextRegion->indexValid(m_parent->m_bigEndian); break;
} }
else if (m_data[0] & 0x80 && m_data[1] & 0x80) else if (m_data[0] & 0x80 && m_data[1] & 0x80)
{ {
@ -529,8 +623,7 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
/* Set next delta-time */ /* Set next delta-time */
m_eventWaitCountdown += int32_t(DecodeTime(m_data)); m_eventWaitCountdown += int32_t(DecodeTime(m_data));
} }
} } else
else
{ {
/* Legacy */ /* Legacy */
while (true) while (true)
@ -541,7 +634,7 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
m_eventWaitCountdown -= ticks; m_eventWaitCountdown -= ticks;
ticks = 0; ticks = 0;
if (m_eventWaitCountdown > 0) if (m_eventWaitCountdown > 0)
return false; break;
} }
/* Load next command */ /* Load next command */
@ -549,7 +642,7 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
{ {
/* End of channel */ /* End of channel */
m_data = nullptr; m_data = nullptr;
return !m_nextRegion->indexValid(m_parent->m_bigEndian); break;
} }
else else
{ {
@ -589,6 +682,32 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
m_data += 4; m_data += 4;
} }
} }
}
m_curTick = endTick;
/* Handle loop end */
if (m_parent->m_loop)
{
int loopTo;
if ((loopTo = m_nextRegion->indexLoop(m_parent->m_bigEndian)) != -1)
{
uint32_t loopEndTick = (m_parent->m_bigEndian ?
SBig(m_nextRegion->m_startTick) : m_nextRegion->m_startTick);
if (uint32_t(endTick) > loopEndTick)
{
m_nextRegion = &m_initRegion[loopTo];
m_curRegion = nullptr;
m_data = nullptr;
m_curTick = m_loopStartTick;
resetTempo();
return false;
}
}
}
if (!m_data)
return m_nextRegion->indexDone(m_parent->m_bigEndian, m_parent->m_loop);
return false; return false;
} }
@ -599,50 +718,11 @@ bool SongState::advance(Sequencer& seq, double dt)
if (m_songState == SongPlayState::Stopped) if (m_songState == SongPlayState::Stopped)
return true; return true;
bool done = false;
m_curDt += dt;
while (m_curDt > 0.0)
{
done = true;
/* Compute ticks to compute based on current tempo */
double ticksPerSecond = m_tempo * 384 / 60;
int32_t remTicks = std::ceil(m_curDt * ticksPerSecond);
if (!remTicks)
break;
/* See if there's an upcoming tempo change in this interval */
if (m_tempoPtr && m_tempoPtr->m_tick != 0xffffffff)
{
TempoChange change = *m_tempoPtr;
if (m_bigEndian)
change.swapBig();
if (m_curTick + remTicks > change.m_tick)
remTicks = change.m_tick - m_curTick;
if (remTicks <= 0)
{
/* Turn over tempo */
m_tempo = change.m_tempo & 0x7fffffff;
seq.setTempo(m_tempo * 384 / 60);
++m_tempoPtr;
continue;
}
}
/* Advance all tracks */ /* Advance all tracks */
bool done = true;
for (Track& trk : m_tracks) for (Track& trk : m_tracks)
if (trk) if (trk)
done &= trk.advance(seq, remTicks); done &= trk.advance(seq, dt);
m_curTick += remTicks;
if (m_tempo == 0)
m_curDt = 0.0;
else
m_curDt -= remTicks / ticksPerSecond;
}
if (done) if (done)
m_songState = SongPlayState::Stopped; m_songState = SongPlayState::Stopped;

View File

@ -84,7 +84,7 @@ bool Voice::_checkSamplePos(bool& looped)
if (m_curSamplePos >= m_lastSamplePos) if (m_curSamplePos >= m_lastSamplePos)
{ {
if (m_curSample->m_loopLengthSamples) if (m_curSample->isLooped())
{ {
/* Turn over looped sample */ /* Turn over looped sample */
m_curSamplePos = m_curSample->m_loopStartSample; m_curSamplePos = m_curSample->m_loopStartSample;
@ -493,7 +493,10 @@ void Voice::preSupplyAudio(double dt)
m_vibratoTime += dt; m_vibratoTime += dt;
float vibrato = TriangleWave(m_vibratoTime / m_vibratoPeriod); float vibrato = TriangleWave(m_vibratoTime / m_vibratoPeriod);
if (m_vibratoModWheel) if (m_vibratoModWheel)
newPitch += m_vibratoModLevel * vibrato * (m_state.m_curMod / 127.f); {
int32_t range = m_vibratoModLevel ? m_vibratoModLevel : m_vibratoLevel;
newPitch += range * vibrato * (m_state.m_curMod / 127.f);
}
else else
newPitch += m_vibratoLevel * vibrato; newPitch += m_vibratoLevel * vibrato;
refresh = true; refresh = true;
@ -1002,7 +1005,7 @@ void Voice::startSample(SampleId sampId, int32_t offset)
int32_t numSamples = m_curSample->getNumSamples(); int32_t numSamples = m_curSample->getNumSamples();
if (offset) if (offset)
{ {
if (m_curSample->m_loopLengthSamples) if (m_curSample->isLooped())
{ {
if (offset > int32_t(m_curSample->m_loopStartSample)) if (offset > int32_t(m_curSample->m_loopStartSample))
offset = offset =
@ -1020,9 +1023,8 @@ void Voice::startSample(SampleId sampId, int32_t offset)
if (m_curFormat == SampleFormat::DSP_DRUM) if (m_curFormat == SampleFormat::DSP_DRUM)
m_curFormat = SampleFormat::DSP; m_curFormat = SampleFormat::DSP;
m_lastSamplePos = m_curSample->m_loopLengthSamples m_lastSamplePos = m_curSample->isLooped()
? (m_curSample->m_loopStartSample + m_curSample->m_loopLengthSamples) ? (m_curSample->m_loopStartSample + m_curSample->m_loopLengthSamples) : numSamples;
: numSamples;
if (m_lastSamplePos) if (m_lastSamplePos)
--m_lastSamplePos; --m_lastSamplePos;