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

View File

@ -117,7 +117,9 @@ class MainWindow : public QMainWindow
LayersEditor* m_layersEditor = nullptr;
SampleEditor* m_sampleEditor = 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<VoiceAllocator> m_voxAllocator;
@ -189,7 +191,6 @@ public:
amuse::ObjToken<amuse::Sequencer> startSong(amuse::GroupId groupId, amuse::SongId songId,
const unsigned char* arrData);
void pushUndoCommand(EditorUndoCommand* cmd);
QFileDialog& fileDialog() { return m_fileDialog; }
void updateFocus();
void aboutToDeleteNode(ProjectModel::INode* node);
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. "
"Sample files will be permanently removed from the file system.</p>"
"<p><strong>This action cannot be undone!</strong></p><p>Continue?</p>").arg(n->name()),
QMessageBox::Yes, QMessageBox::No);
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (result == QMessageBox::No)
return;
NameUndoRegistry nameReg;
@ -2254,7 +2254,7 @@ void ProjectModel::del(const QModelIndex& index)
int result = g_MainWindow->uiMessenger().warning(tr("Delete Sample"),
tr("<p>The sample %1 will be permanently deleted from the file system. "
"<p><strong>This action cannot be undone!</strong></p><p>Continue?</p>").arg(n->name()),
QMessageBox::Yes, QMessageBox::No);
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (result == QMessageBox::No)
return;
NameUndoRegistry nameReg;

View File

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

View File

@ -913,16 +913,29 @@ QVariant SetupListModel::data(const QModelIndex& index, int role) const
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 (role == Qt::TextColorRole)
return QVariant();
g_MainWindow->projectModel()->setIdDatabases(m_node.get());
return amuse::SongId::CurNameDB->resolveNameFromId(entry->first.id).data();
}
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
{
if (!m_node)
return Qt::NoItemFlags;
if (index.row() == m_sorted.size())
return Qt::NoItemFlags;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
@ -1389,9 +1404,42 @@ ColoredTabWidget::ColoredTabWidget(QWidget* parent)
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()
{
if (!m_seq)
{
m_arrData = LoadSongFile(m_path);
if (!m_arrData.empty())
{
m_seq = g_MainWindow->startSong(m_groupId, m_songId, m_arrData.data());
if (m_seq)
@ -1401,6 +1449,11 @@ void MIDIPlayerWidget::clicked()
}
}
else
{
g_MainWindow->uiMessenger().critical(tr("Bad Song Data"), tr("Unable to load song data at %1").arg(m_path));
}
}
else
{
stopped();
}
@ -1444,9 +1497,9 @@ MIDIPlayerWidget::~MIDIPlayerWidget()
}
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),
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_button.setDefaultAction(&m_playAction);
@ -1619,36 +1672,6 @@ void SongGroupEditor::setupModelAboutToBeReset()
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()
{
int idx = 0;
@ -1670,18 +1693,11 @@ void SongGroupEditor::setupDataChanged()
MIDIPlayerWidget* w = qobject_cast<MIDIPlayerWidget*>(m_setupTable->m_listView->indexWidget(index));
if (!w || w->songId() != p.m_it->first)
{
std::vector<uint8_t> arrData = LoadSongFile(g_MainWindow->projectModel()->dir().absoluteFilePath(path));
if (!arrData.empty())
{
QString pathStr = g_MainWindow->projectModel()->dir().absoluteFilePath(path);
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);
}
else
{
m_setupTable->m_listView->setIndexWidget(index, nullptr);
}
}
}
++idx;
}

View File

@ -241,11 +241,12 @@ class MIDIPlayerWidget : public QWidget
QModelIndex m_index;
amuse::GroupId m_groupId;
amuse::SongId m_songId;
QString m_path;
std::vector<uint8_t> m_arrData;
amuse::ObjToken<amuse::Sequencer> m_seq;
public:
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();
amuse::SongId songId() const { return m_songId; }
amuse::Sequencer* sequencer() const { return m_seq.get(); }

View File

@ -427,13 +427,23 @@
<context>
<name>MIDIPlayerWidget</name>
<message>
<location filename="../SongGroupEditor.cpp" line="1399"/>
<location filename="../SongGroupEditor.cpp" line="1447"/>
<source>Stop</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1413"/>
<location filename="../SongGroupEditor.cpp" line="1448"/>
<location filename="../SongGroupEditor.cpp" line="1453"/>
<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>
<translation type="unfinished"></translation>
</message>
@ -666,344 +676,344 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="42"/>
<location filename="../MainWindow.cpp" line="56"/>
<source>Go Back</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="47"/>
<location filename="../MainWindow.cpp" line="61"/>
<source>Go Forward</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="96"/>
<location filename="../MainWindow.cpp" line="110"/>
<source>Clear Recent Projects</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="231"/>
<location filename="../MainWindow.cpp" line="245"/>
<source>Amuse[*]</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="239"/>
<location filename="../MainWindow.cpp" line="253"/>
<source>%1/%2/%3[*] - Amuse</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="244"/>
<location filename="../MainWindow.cpp" line="258"/>
<source>%1[*] - Amuse</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="273"/>
<location filename="../MainWindow.cpp" line="832"/>
<location filename="../MainWindow.cpp" line="287"/>
<location filename="../MainWindow.cpp" line="845"/>
<source>The directory at &apos;%1&apos; must not be empty.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="274"/>
<location filename="../MainWindow.cpp" line="833"/>
<location filename="../MainWindow.cpp" line="288"/>
<location filename="../MainWindow.cpp" line="846"/>
<source>Directory empty</source>
<translation type="unfinished"></translation>
</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>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="280"/>
<location filename="../MainWindow.cpp" line="294"/>
<source>Directory does not exist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="283"/>
<location filename="../MainWindow.cpp" line="297"/>
<source>__amuse_test__</source>
<translation type="unfinished"></translation>
</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>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="290"/>
<location filename="../MainWindow.cpp" line="304"/>
<source>Unable to write to directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="362"/>
<location filename="../MainWindow.cpp" line="378"/>
<source>No Audio Devices Found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="377"/>
<location filename="../MainWindow.cpp" line="393"/>
<source>Virtual MIDI-In</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="396"/>
<location filename="../MainWindow.cpp" line="412"/>
<source>No MIDI Devices Found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="464"/>
<location filename="../MainWindow.cpp" line="480"/>
<source>SUSTAIN</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="771"/>
<location filename="../MainWindow.cpp" line="939"/>
<location filename="../MainWindow.cpp" line="787"/>
<location filename="../MainWindow.cpp" line="949"/>
<source>Unsaved Changes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="771"/>
<location filename="../MainWindow.cpp" line="787"/>
<source>Save Changes in %1?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="804"/>
<location filename="../MainWindow.cpp" line="820"/>
<source>New Project</source>
<translation type="unfinished"></translation>
</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>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="839"/>
<location filename="../MainWindow.cpp" line="852"/>
<source>Bad Directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="854"/>
<location filename="../MainWindow.cpp" line="867"/>
<source>Opening</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="854"/>
<location filename="../MainWindow.cpp" line="968"/>
<location filename="../MainWindow.cpp" line="1062"/>
<location filename="../MainWindow.cpp" line="1108"/>
<location filename="../MainWindow.cpp" line="1163"/>
<location filename="../MainWindow.cpp" line="867"/>
<location filename="../MainWindow.cpp" line="978"/>
<location filename="../MainWindow.cpp" line="1069"/>
<location filename="../MainWindow.cpp" line="1115"/>
<location filename="../MainWindow.cpp" line="1167"/>
<source>Scanning Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="868"/>
<location filename="../MainWindow.cpp" line="881"/>
<source>Opening %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="884"/>
<location filename="../MainWindow.cpp" line="897"/>
<source>Open Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="939"/>
<location filename="../MainWindow.cpp" line="949"/>
<source>Discard Changes in %1?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="968"/>
<location filename="../MainWindow.cpp" line="978"/>
<source>Reloading Samples</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="982"/>
<location filename="../MainWindow.cpp" line="992"/>
<source>Scanning %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="992"/>
<location filename="../MainWindow.cpp" line="1002"/>
<source>Import Project</source>
<translation type="unfinished"></translation>
</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>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1011"/>
<location filename="../MainWindow.cpp" line="1018"/>
<source>Unsupported MusyX Container</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1016"/>
<location filename="../MainWindow.cpp" line="1023"/>
<source>Sample Import Mode</source>
<translation type="unfinished"></translation>
</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>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1021"/>
<location filename="../MainWindow.cpp" line="1028"/>
<source>Import Compressed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1021"/>
<location filename="../MainWindow.cpp" line="1028"/>
<source>Import WAVs</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1021"/>
<location filename="../MainWindow.cpp" line="1028"/>
<source>Import Both</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1037"/>
<location filename="../MainWindow.cpp" line="1044"/>
<source>Raw Import Mode</source>
<translation type="unfinished"></translation>
</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>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1048"/>
<location filename="../MainWindow.cpp" line="1055"/>
<source>Project Name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1048"/>
<location filename="../MainWindow.cpp" line="1055"/>
<source>What should this project be named?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1062"/>
<location filename="../MainWindow.cpp" line="1108"/>
<location filename="../MainWindow.cpp" line="1069"/>
<location filename="../MainWindow.cpp" line="1115"/>
<source>Importing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1074"/>
<location filename="../MainWindow.cpp" line="1117"/>
<location filename="../MainWindow.cpp" line="1081"/>
<location filename="../MainWindow.cpp" line="1124"/>
<source>Importing %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1134"/>
<location filename="../MainWindow.cpp" line="1141"/>
<source>Import Songs</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1163"/>
<location filename="../MainWindow.cpp" line="1167"/>
<source>Exporting</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1171"/>
<location filename="../MainWindow.cpp" line="1175"/>
<source>Exporting %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1186"/>
<location filename="../MainWindow.cpp" line="1201"/>
<location filename="../MainWindow.cpp" line="1190"/>
<location filename="../MainWindow.cpp" line="1205"/>
<source>Import C Headers</source>
<translation type="unfinished"></translation>
</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;
#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>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1229"/>
<location filename="../MainWindow.cpp" line="1230"/>
<source>Export C Headers</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1464"/>
<location filename="../MainWindow.cpp" line="1462"/>
<source>New Subproject</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1465"/>
<location filename="../MainWindow.cpp" line="1463"/>
<source>What should this subproject be named?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1480"/>
<location filename="../MainWindow.cpp" line="1478"/>
<source>New SFX Group</source>
<translation type="unfinished"></translation>
</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>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1499"/>
<location filename="../MainWindow.cpp" line="1497"/>
<source>New Song Group</source>
<translation type="unfinished"></translation>
</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>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1542"/>
<location filename="../MainWindow.cpp" line="1540"/>
<source>New ADSR</source>
<translation type="unfinished"></translation>
</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>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1561"/>
<location filename="../MainWindow.cpp" line="1559"/>
<source>New Curve</source>
<translation type="unfinished"></translation>
</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>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1580"/>
<location filename="../MainWindow.cpp" line="1578"/>
<source>New Keymap</source>
<translation type="unfinished"></translation>
</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>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1599"/>
<location filename="../MainWindow.cpp" line="1597"/>
<source>New Layers</source>
<translation type="unfinished"></translation>
</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>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1710"/>
<location filename="../MainWindow.cpp" line="1708"/>
<source>About Amuse</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="2005"/>
<location filename="../MainWindow.cpp" line="2003"/>
<source>Export Complete</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="2005"/>
<location filename="../MainWindow.cpp" line="2003"/>
<source>%1?</source>
<translation type="unfinished"></translation>
</message>
@ -1192,12 +1202,12 @@
<context>
<name>PageTableView</name>
<message>
<location filename="../SongGroupEditor.cpp" line="1257"/>
<location filename="../SongGroupEditor.cpp" line="1272"/>
<source>Delete Page Entries</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1257"/>
<location filename="../SongGroupEditor.cpp" line="1272"/>
<source>Delete Page Entry</source>
<translation type="unfinished"></translation>
</message>
@ -1563,7 +1573,7 @@
<context>
<name>QDialogButtonBox</name>
<message>
<location filename="../MainWindow.cpp" line="2036"/>
<location filename="../MainWindow.cpp" line="2034"/>
<source>OK</source>
<translation type="unfinished"></translation>
</message>
@ -1571,12 +1581,12 @@
<context>
<name>QMessageBox</name>
<message>
<location filename="../MainWindow.cpp" line="1693"/>
<location filename="../MainWindow.cpp" line="1691"/>
<source>&lt;h3&gt;About Amuse&lt;/h3&gt;</source>
<translation type="unfinished"></translation>
</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>
<translation type="unfinished"></translation>
</message>
@ -1748,27 +1758,32 @@
<context>
<name>SetupListModel</name>
<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>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="947"/>
<location filename="../SongGroupEditor.cpp" line="960"/>
<source>Song %1 is already defined in project</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="952"/>
<location filename="../SongGroupEditor.cpp" line="965"/>
<source>Change Song Name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="969"/>
<location filename="../SongGroupEditor.cpp" line="982"/>
<source>Song</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="971"/>
<location filename="../SongGroupEditor.cpp" line="984"/>
<source>MIDI File</source>
<translation type="unfinished"></translation>
</message>
@ -1776,32 +1791,32 @@
<context>
<name>SetupModel</name>
<message>
<location filename="../SongGroupEditor.cpp" line="1192"/>
<location filename="../SongGroupEditor.cpp" line="1207"/>
<source>Change %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1209"/>
<location filename="../SongGroupEditor.cpp" line="1224"/>
<source>Program</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1211"/>
<location filename="../SongGroupEditor.cpp" line="1226"/>
<source>Volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1213"/>
<location filename="../SongGroupEditor.cpp" line="1228"/>
<source>Panning</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1215"/>
<location filename="../SongGroupEditor.cpp" line="1230"/>
<source>Reverb</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1217"/>
<location filename="../SongGroupEditor.cpp" line="1232"/>
<source>Chorus</source>
<translation type="unfinished"></translation>
</message>
@ -1809,12 +1824,12 @@
<context>
<name>SetupTableView</name>
<message>
<location filename="../SongGroupEditor.cpp" line="1321"/>
<location filename="../SongGroupEditor.cpp" line="1336"/>
<source>Delete Setup Entries</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1321"/>
<location filename="../SongGroupEditor.cpp" line="1336"/>
<source>Delete Setup Entry</source>
<translation type="unfinished"></translation>
</message>
@ -1822,37 +1837,37 @@
<context>
<name>SongGroupEditor</name>
<message>
<location filename="../SongGroupEditor.cpp" line="1519"/>
<location filename="../SongGroupEditor.cpp" line="1572"/>
<source>Add Page Entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1531"/>
<location filename="../SongGroupEditor.cpp" line="1584"/>
<source>Add Setup Entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1712"/>
<location filename="../SongGroupEditor.cpp" line="1728"/>
<source>Normal Pages</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1713"/>
<location filename="../SongGroupEditor.cpp" line="1729"/>
<source>Drum Pages</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1714"/>
<location filename="../SongGroupEditor.cpp" line="1730"/>
<source>MIDI Setups</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1755"/>
<location filename="../SongGroupEditor.cpp" line="1771"/>
<source>Add new page entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SongGroupEditor.cpp" line="1757"/>
<location filename="../SongGroupEditor.cpp" line="1773"/>
<source>Remove selected page entries</source>
<translation type="unfinished"></translation>
</message>
@ -2026,52 +2041,52 @@
<context>
<name>TreeDelegate</name>
<message>
<location filename="../MainWindow.cpp" line="1269"/>
<location filename="../MainWindow.cpp" line="1267"/>
<source>Export GameCube Group</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1277"/>
<location filename="../MainWindow.cpp" line="1275"/>
<source>Find Usages</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1285"/>
<location filename="../MainWindow.cpp" line="1283"/>
<source>Cut</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1293"/>
<location filename="../MainWindow.cpp" line="1291"/>
<source>Copy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1301"/>
<location filename="../MainWindow.cpp" line="1299"/>
<source>Paste</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1309"/>
<location filename="../MainWindow.cpp" line="1307"/>
<source>Duplicate</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1314"/>
<location filename="../MainWindow.cpp" line="1312"/>
<source>Delete</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1322"/>
<location filename="../MainWindow.cpp" line="1320"/>
<source>Rename</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1353"/>
<location filename="../MainWindow.cpp" line="1351"/>
<source>Exporting</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../MainWindow.cpp" line="1353"/>
<location filename="../MainWindow.cpp" line="1351"/>
<source>Exporting %1</source>
<translation type="unfinished"></translation>
</message>

View File

@ -101,6 +101,7 @@ int main(int argc, const boo::SystemChar** argv)
m_args.reserve(argc);
double rate = NativeSampleRate;
int chCount = 2;
double volume = 1.0;
for (int i = 1; i < argc; ++i)
{
#if _WIN32
@ -124,6 +125,16 @@ int main(int argc, const boo::SystemChar** argv)
++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
m_args.push_back(argv[i]);
#else
@ -147,6 +158,16 @@ int main(int argc, const boo::SystemChar** argv)
++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
m_args.push_back(argv[i]);
#endif
@ -155,7 +176,7 @@ int main(int argc, const boo::SystemChar** argv)
/* Load data */
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;
}
@ -476,6 +497,7 @@ int main(int argc, const boo::SystemChar** argv)
std::unique_ptr<boo::IAudioVoiceEngine> voxEngine = boo::NewWAVAudioVoiceEngine(pathOut, rate, chCount);
amuse::BooBackendVoiceAllocator booBackend(*voxEngine);
amuse::Engine engine(booBackend, amuse::AmplitudeMode::PerSample);
engine.setVolume(float(amuse::clamp(0.0, volume, 1.0)));
/* Load group into engine */
const amuse::AudioGroup* group = engine.addAudioGroup(*selData);
@ -486,7 +508,7 @@ int main(int argc, const boo::SystemChar** argv)
}
/* 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;
signal(SIGINT, SIGINTHandler);
do

View File

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

View File

@ -148,17 +148,17 @@ public:
void removeListener(Listener* listener);
/** 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)
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, 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) */
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)
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, 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 */

View File

@ -34,7 +34,6 @@ class Sequencer : public Entity
const unsigned char* m_arrData = nullptr; /**< Current playing arrangement data */
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 */
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_curPan = 0.f; /**< Current panning of channel */
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();
size_t getVoiceCount() const;
@ -137,10 +137,11 @@ public:
void sendMacroMessage(ObjectId macroId, int32_t val);
/** Set tempo of sequencer and all voices in ticks per second */
void setTempo(uint8_t chan, double ticksPerSec);
void setTempo(double ticksPerSec);
/** 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 */
void stopSong(float fadeTime = 0.f, bool now = false);

View File

@ -30,9 +30,14 @@ class SongState
uint32_t m_regionIdxOff;
uint32_t m_chanMapOff;
uint32_t m_tempoTableOff;
uint32_t m_initialTempo;
uint32_t m_unkOff;
void swapBig();
uint32_t m_initialTempo; /* Top bit indicates per-channel looping */
uint32_t m_loopStartTicks[16];
uint32_t m_chanMapOff2;
void swapToBig();
void swapFromBig();
Header& operator=(const Header& other);
Header(const Header& other) { *this = other; }
Header() = default;
} m_header;
/** Track region ('clip' in an NLA representation) */
@ -42,9 +47,11 @@ class SongState
uint8_t m_progNum;
uint8_t m_unk1;
uint16_t m_unk2;
int16_t m_regionIndex;
int16_t m_unk3;
int16_t m_regionIndex; /* -1 to terminate song, -2 to loop to previous region */
int16_t m_loopToRegion;
bool indexDone(bool bigEndian, bool loop) const;
bool indexValid(bool bigEndian) const;
int indexLoop(bool bigEndian) const;
};
/** Tempo change entry */
@ -71,9 +78,17 @@ class SongState
};
SongState* m_parent = nullptr;
uint8_t m_midiChan; /**< MIDI channel number of song channel */
const TrackRegion* m_curRegion; /**< Pointer to currently-playing track region */
const TrackRegion* m_nextRegion; /**< Pointer to next-queued track region */
uint8_t m_midiChan = 0xff; /**< MIDI channel number of song channel */
const TrackRegion* m_initRegion = nullptr; /**< Pointer to first 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_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) */
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; }
void setRegion(Sequencer* seq, const TrackRegion* region);
void advanceRegion(Sequencer* seq);
bool advance(Sequencer& seq, int32_t ticks);
void setRegion(const TrackRegion* region);
void advanceRegion();
bool advance(Sequencer& seq, double dt);
void resetTempo();
};
std::array<Track, 64> m_tracks;
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 */
double m_curDt = 0.f; /**< Cumulative dt value for time-remainder tracking */
bool m_loop = true; /**< Enable looping */
public:
/** Determine SNG version
@ -115,15 +126,14 @@ public:
static int DetectVersion(const unsigned char* ptr, bool& isBig);
/** 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
* @return `true` if END reached
*/
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)
{
m_proj = AudioGroupProject::CreateAudioGroupProject(data);
m_pool = AudioGroupPool::CreateAudioGroupPool(data);
m_proj = AudioGroupProject::CreateAudioGroupProject(data);
m_sdir = AudioGroupSampleDirectory::CreateAudioGroupSampleDirectory(data);
m_samp = data.getSamp();
}

View File

@ -119,6 +119,9 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
ObjectHeader<DNAE> objHead;
atInt64 startPos = r.position();
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];
macro = MakeObj<SoundMacro>();
macro->template readCmds<DNAE>(r, objHead.size - 8);
@ -134,6 +137,9 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
ObjectHeader<DNAE> objHead;
atInt64 startPos = r.position();
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];
switch (objHead.size)
{
@ -163,6 +169,9 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
ObjectHeader<DNAE> objHead;
atInt64 startPos = r.position();
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];
km = MakeObj<std::array<Keymap, 128>>();
for (int i = 0; i < 128; ++i)
@ -183,6 +192,9 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
ObjectHeader<DNAE> objHead;
atInt64 startPos = r.position();
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];
lm = MakeObj<std::vector<LayerMapping>>();
uint32_t count;

View File

@ -103,6 +103,7 @@ AudioGroupProject::AudioGroupProject(athena::io::IStreamReader& r, GCNDataTag)
if (GroupId::CurNameDB)
GroupId::CurNameDB->registerPair(NameDB::generateName(header.groupId, NameDB::Type::Group), header.groupId);
#if 0
/* Sound Macros */
r.seek(header.soundMacroIdsOff, athena::Begin);
while (!AtEnd16(r))
@ -127,6 +128,7 @@ AudioGroupProject::AudioGroupProject(athena::io::IStreamReader& r, GCNDataTag)
r.seek(header.layerIdsOff, athena::Begin);
while (!AtEnd16(r))
ReadRangedObjectIds<athena::Big>(LayersId::CurNameDB, r, NameDB::Type::Layer);
#endif
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);
#if 0
/* Sound Macros */
r.seek(subDataOff + header.soundMacroIdsOff, athena::Begin);
while (!AtEnd16(r))
@ -227,6 +230,7 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade
r.seek(subDataOff + header.layerIdsOff, athena::Begin);
while (!AtEnd16(r))
ReadRangedObjectIds<DNAE>(LayersId::CurNameDB, r, NameDB::Type::Layer);
#endif
if (header.type == GroupType::Song)
{
@ -288,7 +292,7 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade
/* MIDI setups */
r.seek(subDataOff + header.midiSetupsOff, athena::Begin);
while (r.position() < groupBegin + header.groupEndOff)
while (r.position() + 4 < groupBegin + header.groupEndOff)
{
uint16_t songId;
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)
{
athena::io::FileReader r(wavPath);
@ -263,9 +286,11 @@ void AudioGroupSampleDirectory::Entry::loadLooseData(SystemStringView basePath)
{
SystemString wavPath = SystemString(basePath) + _S(".wav");
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 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)
{
@ -274,6 +299,20 @@ void AudioGroupSampleDirectory::Entry::loadLooseData(SystemStringView basePath)
else
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;
@ -283,6 +322,12 @@ void AudioGroupSampleDirectory::Entry::loadLooseData(SystemStringView basePath)
m_data->loadLooseDSP(dspPath);
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))
{
m_data = MakeObj<EntryData>();
@ -295,12 +340,14 @@ SampleFileState AudioGroupSampleDirectory::Entry::getFileState(SystemStringView
{
SystemString wavPath = SystemString(basePath) + _S(".wav");
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 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;
if (!wavValid && !dspValid)
if (!wavValid && !dspValid && !vadpcmValid)
{
if (!curData.m_looseData)
return SampleFileState::NoData;
@ -321,6 +368,30 @@ SampleFileState AudioGroupSampleDirectory::Entry::getFileState(SystemStringView
*pathOut = dspPath;
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)
{
@ -328,6 +399,12 @@ SampleFileState AudioGroupSampleDirectory::Entry::getFileState(SystemStringView
*pathOut = dspPath;
return SampleFileState::CompressedNoWAV;
}
if (vadpcmValid)
{
if (pathOut)
*pathOut = vadpcmPath;
return SampleFileState::CompressedNoWAV;
}
if (pathOut)
*pathOut = wavPath;
return SampleFileState::WAVNoCompressed;
@ -341,7 +418,7 @@ void AudioGroupSampleDirectory::EntryData::patchMetadataDSP(SystemStringView dsp
DSPADPCMHeader head;
head.read(r);
if (m_loopLengthSamples != 0)
if (isLooped())
{
uint32_t block = 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)
{
athena::io::FileReader r(wavPath);
@ -445,7 +538,7 @@ void AudioGroupSampleDirectory::EntryData::patchMetadataWAV(SystemStringView wav
WAVSampleChunk smpl;
smpl.smplPeriod = 1000000000 / fmt.sampleRate;
smpl.midiNote = m_pitch;
if (m_loopLengthSamples != 0)
if (isLooped())
{
smpl.numSampleLoops = 1;
smpl.additionalDataSize = 0;
@ -488,7 +581,7 @@ void AudioGroupSampleDirectory::EntryData::patchMetadataWAV(SystemStringView wav
WAVSampleChunk smpl;
smpl.smplPeriod = 1000000000 / fmt.sampleRate;
smpl.midiNote = m_pitch;
if (m_loopLengthSamples != 0)
if (isLooped())
{
smpl.numSampleLoops = 1;
smpl.additionalDataSize = 0;
@ -530,9 +623,11 @@ void AudioGroupSampleDirectory::Entry::patchSampleMetadata(SystemStringView base
{
SystemString wavPath = SystemString(basePath) + _S(".wav");
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 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;
@ -542,6 +637,12 @@ void AudioGroupSampleDirectory::Entry::patchSampleMetadata(SystemStringView base
SetAudioFileTime(wavPath, wavStat);
}
if (vadpcmValid)
{
curData.patchMetadataVADPCM(vadpcmPath);
SetAudioFileTime(vadpcmPath, vadpcmStat);
}
if (dspValid)
{
curData.patchMetadataDSP(dspPath);
@ -559,9 +660,19 @@ AudioGroupSampleDirectory AudioGroupSampleDirectory::CreateAudioGroupSampleDirec
if (ent.m_name.size() < 4)
continue;
SystemString baseName;
SystemString basePath;
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")))
{
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
continue;
@ -574,7 +685,6 @@ AudioGroupSampleDirectory AudioGroupSampleDirectory::CreateAudioGroupSampleDirec
auto& entry = ret.m_entries[sampleId];
entry = MakeObj<Entry>();
SystemString basePath = SystemString(ent.m_path.begin(), ent.m_path.begin() + ent.m_path.size() - 4);
entry->loadLooseData(basePath);
}
@ -598,7 +708,7 @@ void AudioGroupSampleDirectory::_extractWAV(SampleId id, const EntryData& ent,
SampleFormat fmt = SampleFormat(ent.m_numSamples >> 24);
uint32_t numSamples = ent.m_numSamples & 0xffffff;
if (ent.m_loopLengthSamples)
if (ent.isLooped())
{
WAVHeaderLoop header;
header.fmtChunk.sampleRate = ent.m_sampleRate;
@ -652,19 +762,19 @@ void AudioGroupSampleDirectory::_extractWAV(SampleId id, const EntryData& ent,
else if (fmt == SampleFormat::N64)
{
uint32_t remSamples = numSamples;
uint32_t numFrames = (remSamples + 31) / 32;
uint32_t numFrames = (remSamples + 63) / 64;
const unsigned char* cur = samp + sizeof(ADPCMParms::VADPCMParms);
for (uint32_t i = 0; i < numFrames; ++i)
{
int16_t decomp[32] = {};
unsigned thisSamples = std::min(remSamples, 32u);
int16_t decomp[64] = {};
unsigned thisSamples = std::min(remSamples, 64u);
N64MusyXDecompressFrame(decomp, cur, ent.m_ADPCMParms.vadpcm.m_coefs, thisSamples);
remSamples -= thisSamples;
cur += 16;
cur += 40;
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)
{
@ -732,7 +842,7 @@ void AudioGroupSampleDirectory::_extractCompressed(SampleId id, const EntryData&
header.x0_num_samples = numSamples;
header.x4_num_nibbles = DSPSampleToNibble(numSamples);
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)
{
header.x10_loop_start_nibble = DSPSampleToNibble(ent.getLoopStartSample());
@ -757,7 +867,14 @@ void AudioGroupSampleDirectory::_extractCompressed(SampleId id, const EntryData&
{
path += _S(".vadpcm");
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);
}
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.x4_num_nibbles = DSPSampleToNibble(numSamples);
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;
if (header.xc_loop_flag)
{
@ -864,9 +981,19 @@ void AudioGroupSampleDirectory::reloadSampleData(SystemStringView groupPath)
if (ent.m_name.size() < 4)
continue;
SystemString baseName;
SystemString basePath;
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")))
{
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
continue;
@ -883,7 +1010,6 @@ void AudioGroupSampleDirectory::reloadSampleData(SystemStringView groupPath)
auto& entry = m_entries[sampleId];
entry = MakeObj<Entry>();
SystemString basePath = SystemString(ent.m_path.begin(), ent.m_path.begin() + ent.m_path.size() - 4);
entry->loadLooseData(basePath);
}
}

View File

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

View File

@ -441,7 +441,7 @@ void Engine::removeListener(Listener* listener)
}
/** 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);
if (songGrp.second)
@ -451,7 +451,7 @@ ObjToken<Sequencer> Engine::seqPlay(GroupId groupId, SongId songId, const unsign
return {};
if (arrData)
(*ret)->playSong(arrData);
(*ret)->playSong(arrData, loop);
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,
const unsigned char* arrData, ObjToken<Studio> smx)
const unsigned char* arrData, bool loop, ObjToken<Studio> smx)
{
const SongGroupIndex* sgIdx = group->getProj().getSongGroupIndex(groupId);
if (sgIdx)
@ -478,7 +478,7 @@ ObjToken<Sequencer> Engine::seqPlay(const AudioGroup* group, GroupId groupId, So
return {};
if (arrData)
(*ret)->playSong(arrData);
(*ret)->playSong(arrData, loop);
return *ret;
}

View File

@ -257,7 +257,7 @@ ObjToken<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
if (m_parent->m_songGroup)
{
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())
{
@ -265,7 +265,7 @@ ObjToken<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
const SFXGroupIndex::SFXEntry* sfxEntry = m_parent->m_sfxMappings[lookupIdx];
oid = sfxEntry->objId;
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
return {};
@ -446,7 +446,16 @@ void Sequencer::setPitchWheel(uint8_t chan, float 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()
{
@ -598,12 +607,12 @@ void Sequencer::sendMacroMessage(ObjectId macroId, int32_t 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_dieOnEnd = dieOnEnd;
m_songState.initialize(arrData);
setTempo(m_songState.getTempo() * 384 / 60);
m_songState.initialize(arrData, loop);
setTempo(m_songState.getInitialTempo() * 384 / 60.0);
m_state = SequencerState::Playing;
}

View File

@ -83,17 +83,28 @@ struct Event
class MIDIDecoder
{
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::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];
results.reserve(2);
results.emplace_back();
results.back().first = prog;
if (results.size() == 1)
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;
@ -125,8 +136,15 @@ class MIDIDecoder
}
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 end)
std::vector<uint8_t>::const_iterator end,
int loopStart[16] = nullptr, int loopEnd[16] = nullptr)
{
std::vector<uint8_t>::const_iterator it = begin;
while (it != end)
@ -146,7 +164,7 @@ public:
{
/* Meta events */
if (it == end)
return begin;
break;
a = *it++;
uint32_t length;
@ -168,25 +186,36 @@ public:
uint8_t chan = m_status & 0xf;
auto& results = m_results[chan];
/* Not actually used as such for now */
if (results.empty())
_addProgramChange(chan, 0);
std::multimap<int, Event>& res = results.back().second;
if (loopEnd && loopEnd[chan] != INT_MAX && m_tick >= loopEnd[chan])
break;
/* 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))
{
case Status::NoteOff:
{
if (it == end)
return begin;
break;
a = *it++;
if (it == end)
return begin;
break;
b = *it++;
uint8_t notenum = clamp7(a);
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;
m_notes[chan][notenum] = res.end();
@ -196,16 +225,16 @@ public:
case Status::NoteOn:
{
if (it == end)
return begin;
break;
a = *it++;
if (it == end)
return begin;
break;
b = *it++;
uint8_t notenum = clamp7(a);
uint8_t vel = clamp7(b);
std::multimap<int, Event>::iterator note = m_notes[chan][notenum];
if (note != res.end())
if (!isEmptyIterator(chan, note))
note->second.length = m_tick - note->first;
if (vel != 0)
@ -218,28 +247,33 @@ public:
case Status::NotePressure:
{
if (it == end)
return begin;
break;
a = *it++;
if (it == end)
return begin;
break;
b = *it++;
break;
}
case Status::ControlChange:
{
if (it == end)
return begin;
break;
a = *it++;
if (it == end)
return begin;
break;
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});
break;
}
case Status::ProgramChange:
{
if (it == end)
return begin;
break;
a = *it++;
res.emplace(m_tick, Event{ProgEvent{}, chan, a});
break;
@ -247,17 +281,17 @@ public:
case Status::ChannelPressure:
{
if (it == end)
return begin;
break;
a = *it++;
break;
}
case Status::PitchBend:
{
if (it == end)
return begin;
break;
a = *it++;
if (it == end)
return begin;
break;
b = *it++;
res.emplace(m_tick, Event{PitchEvent{}, chan, clamp7(b) * 128 + clamp7(a)});
break;
@ -270,30 +304,30 @@ public:
{
uint32_t len;
if (!_readContinuedValue(it, end, len) || end - it < len)
return begin;
break;
break;
}
case Status::TimecodeQuarterFrame:
{
if (it == end)
return begin;
break;
a = *it++;
break;
}
case Status::SongPositionPointer:
{
if (it == end)
return begin;
break;
a = *it++;
if (it == end)
return begin;
break;
b = *it++;
break;
}
case Status::SongSelect:
{
if (it == end)
return begin;
break;
a = *it++;
break;
}
@ -315,11 +349,14 @@ public:
}
}
}
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; }
int getMinLoopStart(int chan) const { return m_minLoopStart[chan]; }
int getMinLoopEnd(int chan) const { return m_minLoopEnd[chan]; }
};
class MIDIEncoder
@ -653,7 +690,7 @@ std::vector<uint8_t> SongConverter::SongToMIDI(const unsigned char* data, int& v
ret.push_back(1);
SongState song;
if (!song.initialize(data))
if (!song.initialize(data, false))
return {};
versionOut = song.m_sngVersion;
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(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)
encoder.getResult().push_back(reinterpret_cast<uint8_t*>(&tempo24)[i]);
/* Write out tempo changes */
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)
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)
encoder.getResult().push_back(reinterpret_cast<uint8_t*>(&tempo24)[i]);
++song.m_tempoPtr;
++tempoPtr;
}
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());
}
bool loopsAdded = false;
/* Iterate each SNG track into type-1 MIDI track */
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))
{
std::multimap<int, Event> events;
trk.advanceRegion(nullptr);
trk.advanceRegion();
uint32_t regStart =
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 */
int lastTime = 0;
for (auto& pair : allEvents)
@ -1021,6 +1074,55 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
std::vector<Region> regions;
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)
{
if (memcmp(&*it, "MTrk", 4))
@ -1069,25 +1171,29 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
it = end;
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)
{
std::vector<std::pair<int, std::multimap<int, Event>>>& results = dec.getResults(c);
int lastTrackStartTick = 0;
std::vector<std::multimap<int, Event>>& results = dec.getResults(c);
bool didChanInit = false;
for (auto& prog : results)
int lastEventTick = 0;
for (auto& chanRegion : results)
{
bool didInit = false;
int startTick = 0;
int lastEventTick = 0;
lastEventTick = 0;
int lastPitchTick = 0;
int lastPitchVal = 0;
int lastModTick = 0;
int lastModVal = 0;
Region region;
for (auto& event : prog.second)
for (auto& event : chanRegion)
{
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;
startTick = eventTick;
lastTrackStartTick = startTick;
lastEventTick = startTick;
lastPitchTick = startTick;
lastPitchVal = 0;
@ -1322,7 +1427,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
reg.m_unk1 = 0xff;
reg.m_unk2 = 0;
reg.m_regionIndex = SBig(uint16_t(regIdx));
reg.m_unk3 = 0;
reg.m_loopToRegion = 0;
}
else
{
@ -1331,7 +1436,7 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
reg.m_unk1 = 0xff;
reg.m_unk2 = 0;
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 */
regionBuf.emplace_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)
{
reg.m_startTick = SBig(uint32_t(lastTrackStartTick));
reg.m_startTick = SBig(termStartTick);
reg.m_progNum = 0xff;
reg.m_unk1 = 0xff;
reg.m_unk2 = 0;
reg.m_regionIndex = -1;
reg.m_unk3 = 0;
reg.m_regionIndex = SBig(termRegionIdx);
reg.m_loopToRegion = SBig(termLoopToRegion);
}
else
{
reg.m_startTick = uint32_t(lastTrackStartTick);
reg.m_startTick = termStartTick;
reg.m_progNum = 0xff;
reg.m_unk1 = 0xff;
reg.m_unk2 = 0;
reg.m_regionIndex = -1;
reg.m_unk3 = 0;
reg.m_regionIndex = termRegionIdx;
reg.m_loopToRegion = termLoopToRegion;
}
}
}
@ -1366,17 +1485,29 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
if (version == 1)
{
SongState::Header head;
head.m_trackIdxOff = 0x18;
head.m_regionIdxOff = 0x18 + 4 * 64 + 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;
head.m_regionIdxOff = headSz + 4 * 64 + regionBuf.size() * 12;
head.m_chanMapOff = head.m_regionIdxOff + 4 * regionDataIdxArr.size() + curRegionOff;
head.m_tempoTableOff = tempoBuf.size() ? head.m_chanMapOff + 64 : 0;
head.m_initialTempo = initTempo;
head.m_unkOff = 0;
head.m_chanMapOff2 = head.m_chanMapOff;
uint32_t regIdxOff = head.m_regionIdxOff;
if (big)
head.swapBig();
*reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), 0x18, 0)) = head;
head.swapToBig();
*reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), headSz, 0)) = head;
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];
*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)
@ -1442,17 +1573,29 @@ std::vector<uint8_t> SongConverter::MIDIToSong(const std::vector<uint8_t>& data,
else
{
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_chanMapOff = head.m_trackIdxOff + 4 * 64;
head.m_tempoTableOff = tempoBuf.size() ? head.m_regionIdxOff + 4 * regionDataIdxArr.size() : 0;
head.m_initialTempo = initTempo;
head.m_unkOff = 0;
head.m_chanMapOff2 = head.m_chanMapOff;
uint32_t chanMapOff = head.m_chanMapOff;
if (big)
head.swapBig();
*reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), 0x18, 0)) = head;
head.swapToBig();
*reinterpret_cast<SongState::Header*>(&*ret.insert(ret.cend(), headSz, 0)) = head;
for (SongState::TrackRegion& reg : regionBuf)
*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];
*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);

View File

@ -75,14 +75,68 @@ static uint32_t DecodeTime(const unsigned char*& data)
return ret;
}
void SongState::Header::swapBig()
void SongState::Header::swapFromBig()
{
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);
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
@ -90,6 +144,13 @@ bool SongState::TrackRegion::indexValid(bool bigEndian) const
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()
{
m_tick = SBig(m_tick);
@ -103,12 +164,14 @@ void SongState::Track::Header::swapBig()
m_modOff = SBig(m_modOff);
}
SongState::Track::Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions)
: m_parent(&parent), m_midiChan(midiChan), m_curRegion(nullptr), m_nextRegion(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_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;
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_nextPitchTick = 0x7fffffff;
m_nextPitchDelta = 0;
m_pitchVal = 0;
if (header.m_pitchOff)
{
m_pitchWheelData = m_parent->m_songData + header.m_pitchOff;
if (m_pitchWheelData[0] != 0x80 || m_pitchWheelData[1] != 0x00)
{
auto delta = DecodeDelta(m_pitchWheelData);
m_nextPitchTick = m_parent->m_curTick + delta.first;
m_nextPitchTick = m_curTick + delta.first;
m_nextPitchDelta = delta.second;
}
}
@ -139,27 +203,23 @@ void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
m_modWheelData = nullptr;
m_nextModTick = 0x7fffffff;
m_nextModDelta = 0;
m_modVal = 0;
if (header.m_modOff)
{
m_modWheelData = m_parent->m_songData + header.m_modOff;
if (m_modWheelData[0] != 0x80 || m_modWheelData[1] != 0x00)
{
auto delta = DecodeDelta(m_modWheelData);
m_nextModTick = m_parent->m_curTick + delta.first;
m_nextModTick = m_curTick + delta.first;
m_nextModDelta = delta.second;
}
}
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)
{
m_eventWaitCountdown = int32_t(DecodeTime(m_data));
}
else
{
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)
{
isBig = ptr[0] == 0;
Header header = *reinterpret_cast<const Header*>(ptr);
if (isBig)
header.swapBig();
header.swapFromBig();
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);
@ -347,8 +407,9 @@ int SongState::DetectVersion(const unsigned char* ptr, bool& isBig)
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);
if (m_sngVersion < 0)
return false;
@ -356,7 +417,7 @@ bool SongState::initialize(const unsigned char* ptr)
m_songData = ptr;
m_header = *reinterpret_cast<const Header*>(ptr);
if (m_bigEndian)
m_header.swapBig();
m_header.swapFromBig();
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);
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 =
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
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;
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 */
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)
advanceRegion(&seq);
advanceRegion();
else
break;
}
@ -412,13 +507,12 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
}
}
if (!m_data)
return !m_nextRegion->indexValid(m_parent->m_bigEndian);
if (m_data)
{
/* Update continuous pitch data */
if (m_pitchWheelData)
{
int32_t pitchTick = m_parent->m_curTick;
int32_t pitchTick = m_curTick;
int32_t remPitchTicks = ticks;
while (pitchTick < endTick)
{
@ -448,7 +542,7 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
/* Update continuous modulation data */
if (m_modWheelData)
{
int32_t modTick = m_parent->m_curTick;
int32_t modTick = m_curTick;
int32_t remModTicks = ticks;
while (modTick < endTick)
{
@ -458,7 +552,7 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
{
/* Update modulation */
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)
{
auto delta = DecodeDelta(m_modWheelData);
@ -487,7 +581,7 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
m_eventWaitCountdown -= ticks;
ticks = 0;
if (m_eventWaitCountdown > 0)
return false;
break;
}
/* Load next command */
@ -495,7 +589,7 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
{
/* End of channel */
m_data = nullptr;
return !m_nextRegion->indexValid(m_parent->m_bigEndian);
break;
}
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 */
m_eventWaitCountdown += int32_t(DecodeTime(m_data));
}
}
else
} else
{
/* Legacy */
while (true)
@ -541,7 +634,7 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
m_eventWaitCountdown -= ticks;
ticks = 0;
if (m_eventWaitCountdown > 0)
return false;
break;
}
/* Load next command */
@ -549,7 +642,7 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
{
/* End of channel */
m_data = nullptr;
return !m_nextRegion->indexValid(m_parent->m_bigEndian);
break;
}
else
{
@ -589,6 +682,32 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
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;
}
@ -599,50 +718,11 @@ bool SongState::advance(Sequencer& seq, double dt)
if (m_songState == SongPlayState::Stopped)
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 */
bool done = true;
for (Track& trk : m_tracks)
if (trk)
done &= trk.advance(seq, remTicks);
m_curTick += remTicks;
if (m_tempo == 0)
m_curDt = 0.0;
else
m_curDt -= remTicks / ticksPerSecond;
}
done &= trk.advance(seq, dt);
if (done)
m_songState = SongPlayState::Stopped;

View File

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