mirror of https://github.com/AxioDL/amuse.git
Looping SNG support; bug fixes
This commit is contained in:
parent
25aacc9511
commit
81f0a91569
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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,15 +1404,53 @@ 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_seq = g_MainWindow->startSong(m_groupId, m_songId, m_arrData.data());
|
||||
if (m_seq)
|
||||
m_arrData = LoadSongFile(m_path);
|
||||
if (!m_arrData.empty())
|
||||
{
|
||||
m_playAction.setText(tr("Stop"));
|
||||
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconStop.svg")));
|
||||
m_seq = g_MainWindow->startSong(m_groupId, m_songId, m_arrData.data());
|
||||
if (m_seq)
|
||||
{
|
||||
m_playAction.setText(tr("Stop"));
|
||||
m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconStop.svg")));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_MainWindow->uiMessenger().critical(tr("Bad Song Data"), tr("Unable to load song data at %1").arg(m_path));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -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,17 +1693,10 @@ 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())
|
||||
{
|
||||
MIDIPlayerWidget* newW = new MIDIPlayerWidget(index, m_setupList.m_node->m_id, p.m_it->first,
|
||||
std::move(arrData), m_setupTable->m_listView->viewport());
|
||||
m_setupTable->m_listView->setIndexWidget(index, newW);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_setupTable->m_listView->setIndexWidget(index, nullptr);
|
||||
}
|
||||
QString pathStr = g_MainWindow->projectModel()->dir().absoluteFilePath(path);
|
||||
MIDIPlayerWidget* newW = new MIDIPlayerWidget(index, m_setupList.m_node->m_id, p.m_it->first,
|
||||
pathStr, m_setupTable->m_listView->viewport());
|
||||
m_setupTable->m_listView->setIndexWidget(index, newW);
|
||||
}
|
||||
}
|
||||
++idx;
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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 '%1' 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 '%1' 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 '%1' 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 '%1' 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 '%1' 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><p>Importing names from C headers depends on up-to-date, consistent names relative to the sound group data.</p><p>Headers are imported on a per-subproject basis from a single directory. Headers must be named with the form <code>&lt;subproject&gt;.h</code>.</p><p>Group, Song and SFX definitions are matched according to the following forms:<pre>#define GRP&lt;name&gt; &lt;id&gt;
|
||||
#define SNG&lt;name&gt; &lt;id&gt;
|
||||
#define SFX&lt;name> &lt;id&gt;</pre></p><p><strong>This operation cannot be undone! It is recommended to make a backup of the project directory before proceeding.</strong></p><p>Continue?</p></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><h3>About Amuse</h3></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.cpp" line="1697"/>
|
||||
<location filename="../MainWindow.cpp" line="1695"/>
|
||||
<source><p>Amuse is an alternate editor and runtime library for MusyX sound groups.</p><p>MusyX originally served as a widely-deployed audio system for developing games on the Nintendo 64, GameCube, and GameBoy Advance.</p><p>Amuse is available under the MIT license.<br>Please see <a href="https://gitlab.axiodl.com/AxioDL/amuse/blob/master/LICENSE">https://gitlab.axiodl.com/AxioDL/amuse/blob/master/LICENSE</a> for futher information.</p><p>Copyright (C) 2015-2018 Antidote / Jackoalan.</p><p>MusyX is a trademark of Factor 5, LLC.</p><p>Nintendo 64, GameCube, and GameBoy Advance are trademarks of Nintendo Co., Ltd.</p></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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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; }
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
for (size_t i = 0; i < 128; ++i)
|
||||
m_notes[chan][i] = results.back().second.end();
|
||||
if (results.size() == 1)
|
||||
for (size_t i = 0; i < 128; ++i)
|
||||
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++;
|
||||
res.emplace(m_tick, Event{CtrlEvent{}, chan, clamp7(a), clamp7(b), 0});
|
||||
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);
|
||||
|
|
|
@ -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,184 +507,208 @@ bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
|
|||
}
|
||||
}
|
||||
|
||||
if (!m_data)
|
||||
return !m_nextRegion->indexValid(m_parent->m_bigEndian);
|
||||
|
||||
/* Update continuous pitch data */
|
||||
if (m_pitchWheelData)
|
||||
if (m_data)
|
||||
{
|
||||
int32_t pitchTick = m_parent->m_curTick;
|
||||
int32_t remPitchTicks = ticks;
|
||||
while (pitchTick < endTick)
|
||||
/* Update continuous pitch data */
|
||||
if (m_pitchWheelData)
|
||||
{
|
||||
/* See if there's an upcoming pitch change in this interval */
|
||||
int32_t nextTick = m_nextPitchTick;
|
||||
if (pitchTick + remPitchTicks > nextTick)
|
||||
int32_t pitchTick = m_curTick;
|
||||
int32_t remPitchTicks = ticks;
|
||||
while (pitchTick < endTick)
|
||||
{
|
||||
/* Update pitch */
|
||||
m_pitchVal += m_nextPitchDelta;
|
||||
seq.setPitchWheel(m_midiChan, clamp(-1.f, m_pitchVal / 8191.f, 1.f));
|
||||
if (m_pitchWheelData[0] != 0x80 || m_pitchWheelData[1] != 0x00)
|
||||
/* See if there's an upcoming pitch change in this interval */
|
||||
int32_t nextTick = m_nextPitchTick;
|
||||
if (pitchTick + remPitchTicks > nextTick)
|
||||
{
|
||||
auto delta = DecodeDelta(m_pitchWheelData);
|
||||
m_nextPitchTick += delta.first;
|
||||
m_nextPitchDelta = delta.second;
|
||||
/* Update pitch */
|
||||
m_pitchVal += m_nextPitchDelta;
|
||||
seq.setPitchWheel(m_midiChan, clamp(-1.f, m_pitchVal / 8191.f, 1.f));
|
||||
if (m_pitchWheelData[0] != 0x80 || m_pitchWheelData[1] != 0x00)
|
||||
{
|
||||
auto delta = DecodeDelta(m_pitchWheelData);
|
||||
m_nextPitchTick += delta.first;
|
||||
m_nextPitchDelta = delta.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nextPitchTick = 0x7fffffff;
|
||||
}
|
||||
}
|
||||
remPitchTicks -= (nextTick - pitchTick);
|
||||
pitchTick = nextTick;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update continuous modulation data */
|
||||
if (m_modWheelData)
|
||||
{
|
||||
int32_t modTick = m_curTick;
|
||||
int32_t remModTicks = ticks;
|
||||
while (modTick < endTick)
|
||||
{
|
||||
/* See if there's an upcoming modulation change in this interval */
|
||||
int32_t nextTick = m_nextModTick;
|
||||
if (modTick + remModTicks > nextTick)
|
||||
{
|
||||
/* Update modulation */
|
||||
m_modVal += m_nextModDelta;
|
||||
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);
|
||||
m_nextModTick += delta.first;
|
||||
m_nextModDelta = delta.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nextModTick = 0x7fffffff;
|
||||
}
|
||||
}
|
||||
remModTicks -= (nextTick - modTick);
|
||||
modTick = nextTick;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop through as many commands as we can for this time period */
|
||||
if (m_parent->m_sngVersion == 1)
|
||||
{
|
||||
/* Revision */
|
||||
while (true)
|
||||
{
|
||||
/* Advance wait timer if active, returning if waiting */
|
||||
if (m_eventWaitCountdown)
|
||||
{
|
||||
m_eventWaitCountdown -= ticks;
|
||||
ticks = 0;
|
||||
if (m_eventWaitCountdown > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Load next command */
|
||||
if (*reinterpret_cast<const uint16_t*>(m_data) == 0xffff)
|
||||
{
|
||||
/* End of channel */
|
||||
m_data = nullptr;
|
||||
break;
|
||||
}
|
||||
else if (m_data[0] & 0x80 && m_data[1] & 0x80)
|
||||
{
|
||||
/* Control change */
|
||||
uint8_t val = m_data[0] & 0x7f;
|
||||
uint8_t ctrl = m_data[1] & 0x7f;
|
||||
seq.setCtrlValue(m_midiChan, ctrl, val);
|
||||
m_data += 2;
|
||||
}
|
||||
else if (m_data[0] & 0x80)
|
||||
{
|
||||
/* Program change */
|
||||
uint8_t prog = m_data[0] & 0x7f;
|
||||
seq.setChanProgram(m_midiChan, prog);
|
||||
m_data += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nextPitchTick = 0x7fffffff;
|
||||
}
|
||||
}
|
||||
remPitchTicks -= (nextTick - pitchTick);
|
||||
pitchTick = nextTick;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update continuous modulation data */
|
||||
if (m_modWheelData)
|
||||
{
|
||||
int32_t modTick = m_parent->m_curTick;
|
||||
int32_t remModTicks = ticks;
|
||||
while (modTick < endTick)
|
||||
{
|
||||
/* See if there's an upcoming modulation change in this interval */
|
||||
int32_t nextTick = m_nextModTick;
|
||||
if (modTick + remModTicks > nextTick)
|
||||
{
|
||||
/* Update modulation */
|
||||
m_modVal += m_nextModDelta;
|
||||
seq.setCtrlValue(m_midiChan, 1, clamp(0, m_modVal / 128, 127));
|
||||
if (m_modWheelData[0] != 0x80 || m_modWheelData[1] != 0x00)
|
||||
{
|
||||
auto delta = DecodeDelta(m_modWheelData);
|
||||
m_nextModTick += delta.first;
|
||||
m_nextModDelta = delta.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nextModTick = 0x7fffffff;
|
||||
}
|
||||
}
|
||||
remModTicks -= (nextTick - modTick);
|
||||
modTick = nextTick;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop through as many commands as we can for this time period */
|
||||
if (m_parent->m_sngVersion == 1)
|
||||
{
|
||||
/* Revision */
|
||||
while (true)
|
||||
{
|
||||
/* Advance wait timer if active, returning if waiting */
|
||||
if (m_eventWaitCountdown)
|
||||
{
|
||||
m_eventWaitCountdown -= ticks;
|
||||
ticks = 0;
|
||||
if (m_eventWaitCountdown > 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Load next command */
|
||||
if (*reinterpret_cast<const uint16_t*>(m_data) == 0xffff)
|
||||
{
|
||||
/* End of channel */
|
||||
m_data = nullptr;
|
||||
return !m_nextRegion->indexValid(m_parent->m_bigEndian);
|
||||
}
|
||||
else if (m_data[0] & 0x80 && m_data[1] & 0x80)
|
||||
{
|
||||
/* Control change */
|
||||
uint8_t val = m_data[0] & 0x7f;
|
||||
uint8_t ctrl = m_data[1] & 0x7f;
|
||||
seq.setCtrlValue(m_midiChan, ctrl, val);
|
||||
m_data += 2;
|
||||
}
|
||||
else if (m_data[0] & 0x80)
|
||||
{
|
||||
/* Program change */
|
||||
uint8_t prog = m_data[0] & 0x7f;
|
||||
seq.setChanProgram(m_midiChan, prog);
|
||||
m_data += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Note */
|
||||
uint8_t note = m_data[0] & 0x7f;
|
||||
uint8_t vel = m_data[1] & 0x7f;
|
||||
uint16_t length = (m_parent->m_bigEndian ? SBig(*reinterpret_cast<const uint16_t*>(m_data + 2))
|
||||
: *reinterpret_cast<const uint16_t*>(m_data + 2));
|
||||
seq.keyOn(m_midiChan, note, vel);
|
||||
if (length == 0)
|
||||
seq.keyOff(m_midiChan, note, 0);
|
||||
m_remNoteLengths[note] = length;
|
||||
m_data += 4;
|
||||
}
|
||||
|
||||
/* Set next delta-time */
|
||||
m_eventWaitCountdown += int32_t(DecodeTime(m_data));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Legacy */
|
||||
while (true)
|
||||
{
|
||||
/* Advance wait timer if active, returning if waiting */
|
||||
if (m_eventWaitCountdown)
|
||||
{
|
||||
m_eventWaitCountdown -= ticks;
|
||||
ticks = 0;
|
||||
if (m_eventWaitCountdown > 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Load next command */
|
||||
if (*reinterpret_cast<const uint16_t*>(&m_data[2]) == 0xffff)
|
||||
{
|
||||
/* End of channel */
|
||||
m_data = nullptr;
|
||||
return !m_nextRegion->indexValid(m_parent->m_bigEndian);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((m_data[2] & 0x80) != 0x80)
|
||||
{
|
||||
/* Note */
|
||||
uint16_t length = (m_parent->m_bigEndian ? SBig(*reinterpret_cast<const uint16_t*>(m_data))
|
||||
: *reinterpret_cast<const uint16_t*>(m_data));
|
||||
uint8_t note = m_data[2] & 0x7f;
|
||||
uint8_t vel = m_data[3] & 0x7f;
|
||||
uint8_t note = m_data[0] & 0x7f;
|
||||
uint8_t vel = m_data[1] & 0x7f;
|
||||
uint16_t length = (m_parent->m_bigEndian ? SBig(*reinterpret_cast<const uint16_t*>(m_data + 2))
|
||||
: *reinterpret_cast<const uint16_t*>(m_data + 2));
|
||||
seq.keyOn(m_midiChan, note, vel);
|
||||
if (length == 0)
|
||||
seq.keyOff(m_midiChan, note, 0);
|
||||
m_remNoteLengths[note] = length;
|
||||
m_data += 4;
|
||||
}
|
||||
else if (m_data[2] & 0x80 && m_data[3] & 0x80)
|
||||
|
||||
/* Set next delta-time */
|
||||
m_eventWaitCountdown += int32_t(DecodeTime(m_data));
|
||||
}
|
||||
} else
|
||||
{
|
||||
/* Legacy */
|
||||
while (true)
|
||||
{
|
||||
/* Advance wait timer if active, returning if waiting */
|
||||
if (m_eventWaitCountdown)
|
||||
{
|
||||
/* Control change */
|
||||
uint8_t val = m_data[2] & 0x7f;
|
||||
uint8_t ctrl = m_data[3] & 0x7f;
|
||||
seq.setCtrlValue(m_midiChan, ctrl, val);
|
||||
m_eventWaitCountdown -= ticks;
|
||||
ticks = 0;
|
||||
if (m_eventWaitCountdown > 0)
|
||||
break;
|
||||
}
|
||||
else if (m_data[2] & 0x80)
|
||||
|
||||
/* Load next command */
|
||||
if (*reinterpret_cast<const uint16_t*>(&m_data[2]) == 0xffff)
|
||||
{
|
||||
/* Program change */
|
||||
uint8_t prog = m_data[2] & 0x7f;
|
||||
seq.setChanProgram(m_midiChan, prog);
|
||||
/* End of channel */
|
||||
m_data = nullptr;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((m_data[2] & 0x80) != 0x80)
|
||||
{
|
||||
/* Note */
|
||||
uint16_t length = (m_parent->m_bigEndian ? SBig(*reinterpret_cast<const uint16_t*>(m_data))
|
||||
: *reinterpret_cast<const uint16_t*>(m_data));
|
||||
uint8_t note = m_data[2] & 0x7f;
|
||||
uint8_t vel = m_data[3] & 0x7f;
|
||||
seq.keyOn(m_midiChan, note, vel);
|
||||
if (length == 0)
|
||||
seq.keyOff(m_midiChan, note, 0);
|
||||
m_remNoteLengths[note] = length;
|
||||
}
|
||||
else if (m_data[2] & 0x80 && m_data[3] & 0x80)
|
||||
{
|
||||
/* Control change */
|
||||
uint8_t val = m_data[2] & 0x7f;
|
||||
uint8_t ctrl = m_data[3] & 0x7f;
|
||||
seq.setCtrlValue(m_midiChan, ctrl, val);
|
||||
}
|
||||
else if (m_data[2] & 0x80)
|
||||
{
|
||||
/* Program change */
|
||||
uint8_t prog = m_data[2] & 0x7f;
|
||||
seq.setChanProgram(m_midiChan, prog);
|
||||
}
|
||||
m_data += 4;
|
||||
}
|
||||
|
||||
/* Set next delta-time */
|
||||
int32_t absTick = (m_parent->m_bigEndian ? SBig(*reinterpret_cast<const int32_t*>(m_data))
|
||||
: *reinterpret_cast<const int32_t*>(m_data));
|
||||
m_eventWaitCountdown += absTick - m_lastN64EventTick;
|
||||
m_lastN64EventTick = absTick;
|
||||
m_data += 4;
|
||||
}
|
||||
|
||||
/* Set next delta-time */
|
||||
int32_t absTick = (m_parent->m_bigEndian ? SBig(*reinterpret_cast<const int32_t*>(m_data))
|
||||
: *reinterpret_cast<const int32_t*>(m_data));
|
||||
m_eventWaitCountdown += absTick - m_lastN64EventTick;
|
||||
m_lastN64EventTick = absTick;
|
||||
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 */
|
||||
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;
|
||||
}
|
||||
/* Advance all tracks */
|
||||
bool done = true;
|
||||
for (Track& trk : m_tracks)
|
||||
if (trk)
|
||||
done &= trk.advance(seq, dt);
|
||||
|
||||
if (done)
|
||||
m_songState = SongPlayState::Stopped;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue