mirror of https://github.com/AxioDL/amuse.git
Finish SampleEditor implementation
This commit is contained in:
parent
d062a087c5
commit
721dd361fa
|
@ -23,6 +23,8 @@ protected:
|
|||
enum class Id
|
||||
{
|
||||
SMChangeVal,
|
||||
SampLoop,
|
||||
SampPitch
|
||||
};
|
||||
public:
|
||||
EditorUndoCommand(amuse::ObjToken<ProjectModel::INode> node,
|
||||
|
|
|
@ -183,10 +183,9 @@ void MainWindow::updateWindowTitle()
|
|||
}
|
||||
|
||||
QDir dir(m_projectModel->path());
|
||||
if (m_ui.editorContents->currentWidget() != m_faceSvg)
|
||||
if (EditorWidget* w = getEditorWidget())
|
||||
{
|
||||
ProjectModel::BasePoolObjectNode* objNode = static_cast<ProjectModel::BasePoolObjectNode*>(
|
||||
static_cast<EditorWidget*>(m_ui.editorContents->currentWidget())->currentNode());
|
||||
ProjectModel::BasePoolObjectNode* objNode = static_cast<ProjectModel::BasePoolObjectNode*>(w->currentNode());
|
||||
setWindowTitle(tr("Amuse [%1/%2/%3]").arg(dir.dirName()).arg(
|
||||
m_projectModel->getGroupNode(objNode)->text()).arg(objNode->text()));
|
||||
return;
|
||||
|
@ -415,7 +414,7 @@ bool MainWindow::_setEditor(EditorWidget* editor)
|
|||
{
|
||||
if (editor != m_ui.editorContents->currentWidget() &&
|
||||
m_ui.editorContents->currentWidget() != m_faceSvg)
|
||||
static_cast<EditorWidget*>(m_ui.editorContents->currentWidget())->unloadData();
|
||||
getEditorWidget()->unloadData();
|
||||
if (!editor || !editor->valid())
|
||||
{
|
||||
m_ui.editorContents->setCurrentWidget(m_faceSvg);
|
||||
|
@ -499,9 +498,16 @@ void MainWindow::closeEditor()
|
|||
}
|
||||
|
||||
ProjectModel::INode* MainWindow::getEditorNode() const
|
||||
{
|
||||
if (EditorWidget* w = getEditorWidget())
|
||||
return w->currentNode();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EditorWidget* MainWindow::getEditorWidget() const
|
||||
{
|
||||
if (m_ui.editorContents->currentWidget() != m_faceSvg)
|
||||
return static_cast<EditorWidget*>(m_ui.editorContents->currentWidget())->currentNode();
|
||||
return static_cast<EditorWidget*>(m_ui.editorContents->currentWidget());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -553,7 +559,7 @@ void MainWindow::newAction()
|
|||
return;
|
||||
|
||||
m_projectModel->clearProjectData();
|
||||
m_projectModel->ensureModelData();
|
||||
m_ui.actionImport_Groups->setDisabled(m_projectModel->ensureModelData());
|
||||
}
|
||||
|
||||
bool MainWindow::openProject(const QString& path)
|
||||
|
@ -898,7 +904,11 @@ void MainWindow::setMIDIIO()
|
|||
void MainWindow::notePressed(int key)
|
||||
{
|
||||
if (m_engine)
|
||||
{
|
||||
if (m_lastSound)
|
||||
m_lastSound->keyOff();
|
||||
m_lastSound = startEditorVoice(key, m_velocity);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::noteReleased()
|
||||
|
@ -1054,7 +1064,7 @@ void MainWindow::onBackgroundTaskFinished()
|
|||
m_backgroundDialog = nullptr;
|
||||
m_backgroundTask->deleteLater();
|
||||
m_backgroundTask = nullptr;
|
||||
m_projectModel->ensureModelData();
|
||||
m_ui.actionImport_Groups->setDisabled(m_projectModel->ensureModelData());
|
||||
setEnabled(true);
|
||||
}
|
||||
|
||||
|
|
|
@ -144,6 +144,7 @@ public:
|
|||
void closeEditor();
|
||||
|
||||
ProjectModel::INode* getEditorNode() const;
|
||||
EditorWidget* getEditorWidget() const;
|
||||
amuse::ObjToken<amuse::Voice> startEditorVoice(uint8_t key, uint8_t vel);
|
||||
void pushUndoCommand(QUndoCommand* cmd);
|
||||
void aboutToDeleteNode(ProjectModel::INode* node);
|
||||
|
|
|
@ -144,8 +144,6 @@ bool ProjectModel::importGroupData(const QString& groupName, const amuse::AudioG
|
|||
m_projectDatabase.setIdDatabases();
|
||||
|
||||
amuse::AudioGroupDatabase& grp = m_groups.insert(std::make_pair(groupName, data)).first->second;
|
||||
//grp.setIdDatabases();
|
||||
//amuse::AudioGroupProject::BootstrapObjectIDs(data);
|
||||
|
||||
if (!MkPath(m_dir.path(), messenger))
|
||||
return false;
|
||||
|
@ -154,6 +152,7 @@ bool ProjectModel::importGroupData(const QString& groupName, const amuse::AudioG
|
|||
return false;
|
||||
|
||||
amuse::SystemString sysDir = QStringToSysString(dir.path());
|
||||
grp.setGroupPath(sysDir);
|
||||
switch (mode)
|
||||
{
|
||||
case ImportMode::Original:
|
||||
|
@ -289,13 +288,14 @@ void ProjectModel::_resetModelData()
|
|||
endResetModel();
|
||||
}
|
||||
|
||||
void ProjectModel::ensureModelData()
|
||||
bool ProjectModel::ensureModelData()
|
||||
{
|
||||
if (m_needsReset)
|
||||
{
|
||||
_resetModelData();
|
||||
m_needsReset = false;
|
||||
}
|
||||
return !m_groups.empty();
|
||||
}
|
||||
|
||||
QModelIndex ProjectModel::proxyCreateIndex(int arow, int acolumn, void *adata) const
|
||||
|
|
|
@ -249,7 +249,7 @@ public:
|
|||
ImportMode mode, UIMessenger& messenger);
|
||||
bool saveToFile(UIMessenger& messenger);
|
||||
|
||||
void ensureModelData();
|
||||
bool ensureModelData();
|
||||
|
||||
QModelIndex proxyCreateIndex(int arow, int acolumn, void *adata) const;
|
||||
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const;
|
||||
|
|
|
@ -335,6 +335,8 @@ void SampleView::mousePressEvent(QMouseEvent* ev)
|
|||
else if (endPass)
|
||||
m_dragState = DragState::End;
|
||||
|
||||
getEditor()->m_controls->setFileWrite(m_dragState == DragState::None);
|
||||
|
||||
mouseMoveEvent(ev);
|
||||
}
|
||||
}
|
||||
|
@ -342,6 +344,7 @@ void SampleView::mousePressEvent(QMouseEvent* ev)
|
|||
void SampleView::mouseReleaseEvent(QMouseEvent* ev)
|
||||
{
|
||||
m_dragState = DragState::None;
|
||||
getEditor()->m_controls->setFileWrite(true);
|
||||
}
|
||||
|
||||
void SampleView::mouseMoveEvent(QMouseEvent* ev)
|
||||
|
@ -361,12 +364,15 @@ void SampleView::wheelEvent(QWheelEvent* ev)
|
|||
}
|
||||
}
|
||||
|
||||
void SampleView::loadData(ProjectModel::SampleNode* node)
|
||||
bool SampleView::loadData(ProjectModel::SampleNode* node)
|
||||
{
|
||||
bool reset = m_node.get() != node;
|
||||
m_node = node;
|
||||
|
||||
ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(m_node.get());
|
||||
std::tie(m_sample, m_sampleData) = group->getAudioGroup()->getSampleData(m_node->id(), m_node->m_obj.get());
|
||||
resetZoom();
|
||||
if (reset)
|
||||
resetZoom();
|
||||
|
||||
m_playbackMacro = amuse::MakeObj<amuse::SoundMacro>();
|
||||
amuse::SoundMacro::CmdStartSample* startSample =
|
||||
|
@ -376,6 +382,8 @@ void SampleView::loadData(ProjectModel::SampleNode* node)
|
|||
m_playbackMacro->insertNewCmd(1, amuse::SoundMacro::CmdOp::End);
|
||||
|
||||
update();
|
||||
|
||||
return reset;
|
||||
}
|
||||
|
||||
void SampleView::unloadData()
|
||||
|
@ -440,85 +448,197 @@ void SampleControls::zoomSliderChanged(int val)
|
|||
editor->m_sampleView->setZoom(val);
|
||||
}
|
||||
|
||||
class SampLoopUndoCommand : public EditorUndoCommand
|
||||
{
|
||||
uint32_t m_redoStartVal, m_redoEndVal, m_undoStartVal, m_undoEndVal;
|
||||
int m_fieldIdx;
|
||||
bool m_undid = false;
|
||||
public:
|
||||
SampLoopUndoCommand(uint32_t redoStart, uint32_t redoEnd, const QString& fieldName,
|
||||
int fieldIdx, amuse::ObjToken<ProjectModel::SampleNode> node)
|
||||
: EditorUndoCommand(node.get(), QUndoStack::tr("Change %1").arg(fieldName)),
|
||||
m_redoStartVal(redoStart), m_redoEndVal(redoEnd), m_fieldIdx(fieldIdx) {}
|
||||
void undo()
|
||||
{
|
||||
m_undid = true;
|
||||
amuse::SampleEntryData* data = m_node.cast<ProjectModel::SampleNode>()->m_obj->m_data.get();
|
||||
data->setLoopStartSample(m_undoStartVal);
|
||||
data->setLoopEndSample(m_undoEndVal);
|
||||
EditorUndoCommand::undo();
|
||||
if (SampleEditor* e = static_cast<SampleEditor*>(g_MainWindow->getEditorWidget()))
|
||||
e->m_controls->doFileWrite();
|
||||
}
|
||||
void redo()
|
||||
{
|
||||
amuse::SampleEntryData* data = m_node.cast<ProjectModel::SampleNode>()->m_obj->m_data.get();
|
||||
m_undoStartVal = data->getLoopStartSample();
|
||||
m_undoEndVal = data->getLoopEndSample();
|
||||
data->setLoopStartSample(m_redoStartVal);
|
||||
data->setLoopEndSample(m_redoEndVal);
|
||||
if (m_undid)
|
||||
{
|
||||
EditorUndoCommand::redo();
|
||||
if (SampleEditor* e = static_cast<SampleEditor*>(g_MainWindow->getEditorWidget()))
|
||||
e->m_controls->doFileWrite();
|
||||
}
|
||||
}
|
||||
bool mergeWith(const QUndoCommand* other)
|
||||
{
|
||||
if (other->id() == id() && static_cast<const SampLoopUndoCommand*>(other)->m_fieldIdx == m_fieldIdx)
|
||||
{
|
||||
m_redoStartVal = static_cast<const SampLoopUndoCommand*>(other)->m_redoStartVal;
|
||||
m_redoEndVal = static_cast<const SampLoopUndoCommand*>(other)->m_redoEndVal;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
int id() const { return int(Id::SampLoop); }
|
||||
};
|
||||
|
||||
void SampleControls::loopStateChanged(int state)
|
||||
{
|
||||
if (m_updateFile)
|
||||
if (m_enableUpdate)
|
||||
{
|
||||
SampleEditor* editor = qobject_cast<SampleEditor*>(parentWidget());
|
||||
amuse::SampleEntryData* data = editor->m_sampleView->entryData();
|
||||
if (state == Qt::Checked)
|
||||
{
|
||||
g_MainWindow->pushUndoCommand(new SampLoopUndoCommand(atUint32(m_loopStart->value()),
|
||||
uint32_t(m_loopEnd->value()), tr("Loop"), 0, editor->m_sampleView->m_node));
|
||||
m_loopStart->setEnabled(true);
|
||||
m_loopEnd->setEnabled(true);
|
||||
if (m_loopStart->value() == 0 && m_loopEnd->value() == 0)
|
||||
{
|
||||
m_updateFile = false;
|
||||
m_enableUpdate = false;
|
||||
m_loopEnd->setValue(data->getNumSamples() - 1);
|
||||
m_loopStart->setMaximum(m_loopEnd->value());
|
||||
m_updateFile = true;
|
||||
m_enableUpdate = true;
|
||||
}
|
||||
data->m_loopStartSample = atUint32(m_loopStart->value());
|
||||
data->m_loopLengthSamples = m_loopEnd->value() + 1 - data->m_loopStartSample;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_MainWindow->pushUndoCommand(new SampLoopUndoCommand(0, 0, tr("Loop"), 0, editor->m_sampleView->m_node));
|
||||
m_loopStart->setEnabled(false);
|
||||
m_loopEnd->setEnabled(false);
|
||||
data->m_loopStartSample = 0;
|
||||
data->m_loopLengthSamples = 0;
|
||||
}
|
||||
editor->m_sampleView->update();
|
||||
doFileWrite();
|
||||
}
|
||||
}
|
||||
|
||||
void SampleControls::startValueChanged(int val)
|
||||
{
|
||||
if (m_updateFile)
|
||||
if (m_enableUpdate)
|
||||
{
|
||||
SampleEditor* editor = qobject_cast<SampleEditor*>(parentWidget());
|
||||
amuse::SampleEntryData* data = editor->m_sampleView->entryData();
|
||||
|
||||
int oldPos = data->getLoopStartSample();
|
||||
g_MainWindow->pushUndoCommand(new SampLoopUndoCommand(atUint32(val), data->getLoopEndSample(), tr("Loop Start"),
|
||||
1, editor->m_sampleView->m_node));
|
||||
|
||||
data->setLoopStartSample(atUint32(val));
|
||||
m_loopEnd->setMinimum(val);
|
||||
|
||||
editor->m_sampleView->updateSampleRange(oldPos, val);
|
||||
doFileWrite();
|
||||
}
|
||||
}
|
||||
|
||||
void SampleControls::endValueChanged(int val)
|
||||
{
|
||||
if (m_updateFile)
|
||||
if (m_enableUpdate)
|
||||
{
|
||||
SampleEditor* editor = qobject_cast<SampleEditor*>(parentWidget());
|
||||
amuse::SampleEntryData* data = editor->m_sampleView->entryData();
|
||||
|
||||
int oldPos = data->getLoopEndSample();
|
||||
g_MainWindow->pushUndoCommand(new SampLoopUndoCommand(data->getLoopStartSample(), atUint32(val), tr("Loop End"),
|
||||
2, editor->m_sampleView->m_node));
|
||||
|
||||
data->setLoopEndSample(atUint32(val));
|
||||
m_loopStart->setMaximum(val);
|
||||
|
||||
editor->m_sampleView->updateSampleRange(oldPos, val);
|
||||
doFileWrite();
|
||||
}
|
||||
}
|
||||
|
||||
class SampPitchUndoCommand : public EditorUndoCommand
|
||||
{
|
||||
uint8_t m_redoPitchVal, m_undoPitchVal;
|
||||
bool m_undid = false;
|
||||
public:
|
||||
SampPitchUndoCommand(atUint8 redoPitch, amuse::ObjToken<ProjectModel::SampleNode> node)
|
||||
: EditorUndoCommand(node.get(), QUndoStack::tr("Change Base Pitch")),
|
||||
m_redoPitchVal(redoPitch) {}
|
||||
void undo()
|
||||
{
|
||||
m_undid = true;
|
||||
amuse::SampleEntryData* data = m_node.cast<ProjectModel::SampleNode>()->m_obj->m_data.get();
|
||||
data->m_pitch = m_undoPitchVal;
|
||||
EditorUndoCommand::undo();
|
||||
if (SampleEditor* e = static_cast<SampleEditor*>(g_MainWindow->getEditorWidget()))
|
||||
e->m_controls->doFileWrite();
|
||||
}
|
||||
void redo()
|
||||
{
|
||||
amuse::SampleEntryData* data = m_node.cast<ProjectModel::SampleNode>()->m_obj->m_data.get();
|
||||
m_undoPitchVal = data->m_pitch;
|
||||
data->m_pitch = m_redoPitchVal;
|
||||
if (m_undid)
|
||||
{
|
||||
EditorUndoCommand::redo();
|
||||
if (SampleEditor* e = static_cast<SampleEditor*>(g_MainWindow->getEditorWidget()))
|
||||
e->m_controls->doFileWrite();
|
||||
}
|
||||
}
|
||||
bool mergeWith(const QUndoCommand* other)
|
||||
{
|
||||
if (other->id() == id())
|
||||
{
|
||||
m_redoPitchVal = static_cast<const SampPitchUndoCommand*>(other)->m_redoPitchVal;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
int id() const { return int(Id::SampPitch); }
|
||||
};
|
||||
|
||||
void SampleControls::pitchValueChanged(int val)
|
||||
{
|
||||
if (m_updateFile)
|
||||
if (m_enableUpdate)
|
||||
{
|
||||
SampleEditor* editor = qobject_cast<SampleEditor*>(parentWidget());
|
||||
amuse::SampleEntryData* data = editor->m_sampleView->entryData();
|
||||
|
||||
g_MainWindow->pushUndoCommand(new SampPitchUndoCommand(atUint8(val), editor->m_sampleView->m_node));
|
||||
|
||||
data->m_pitch = atUint8(val);
|
||||
doFileWrite();
|
||||
}
|
||||
}
|
||||
|
||||
void SampleControls::makeWAVVersion()
|
||||
{
|
||||
|
||||
SampleEditor* editor = qobject_cast<SampleEditor*>(parentWidget());
|
||||
ProjectModel::SampleNode* node = static_cast<ProjectModel::SampleNode*>(editor->currentNode());
|
||||
g_MainWindow->projectModel()->getGroupNode(node)->getAudioGroup()->
|
||||
makeWAVVersion(node->id(), node->m_obj.get());
|
||||
updateFileState();
|
||||
}
|
||||
|
||||
void SampleControls::makeCompressedVersion()
|
||||
{
|
||||
|
||||
SampleEditor* editor = qobject_cast<SampleEditor*>(parentWidget());
|
||||
ProjectModel::SampleNode* node = static_cast<ProjectModel::SampleNode*>(editor->currentNode());
|
||||
g_MainWindow->projectModel()->getGroupNode(node)->getAudioGroup()->
|
||||
makeCompressedVersion(node->id(), node->m_obj.get());
|
||||
updateFileState();
|
||||
}
|
||||
|
||||
void SampleControls::showInBrowser()
|
||||
|
@ -528,7 +648,7 @@ void SampleControls::showInBrowser()
|
|||
|
||||
void SampleControls::updateFileState()
|
||||
{
|
||||
m_updateFile = false;
|
||||
m_enableUpdate = false;
|
||||
|
||||
SampleEditor* editor = qobject_cast<SampleEditor*>(parentWidget());
|
||||
ProjectModel::SampleNode* node = static_cast<ProjectModel::SampleNode*>(editor->currentNode());
|
||||
|
@ -590,17 +710,35 @@ void SampleControls::updateFileState()
|
|||
m_showInBrowser->setDisabled(true);
|
||||
}
|
||||
|
||||
m_updateFile = true;
|
||||
m_enableUpdate = true;
|
||||
}
|
||||
|
||||
void SampleControls::loadData()
|
||||
void SampleControls::doFileWrite()
|
||||
{
|
||||
if (m_enableFileWrite)
|
||||
{
|
||||
SampleEditor* editor = qobject_cast<SampleEditor*>(parentWidget());
|
||||
ProjectModel::SampleNode* node = static_cast<ProjectModel::SampleNode*>(editor->currentNode());
|
||||
g_MainWindow->projectModel()->getGroupNode(node)->getAudioGroup()->
|
||||
patchSampleMetadata(node->id(), node->m_obj.get());
|
||||
}
|
||||
}
|
||||
|
||||
void SampleControls::setFileWrite(bool w)
|
||||
{
|
||||
m_enableFileWrite = w;
|
||||
doFileWrite();
|
||||
}
|
||||
|
||||
void SampleControls::loadData(bool reset)
|
||||
{
|
||||
m_zoomSlider->setDisabled(false);
|
||||
m_loopCheck->setDisabled(false);
|
||||
m_loopStart->setDisabled(false);
|
||||
m_loopEnd->setDisabled(false);
|
||||
m_basePitch->setDisabled(false);
|
||||
m_zoomSlider->setValue(0);
|
||||
if (reset)
|
||||
m_zoomSlider->setValue(0);
|
||||
updateFileState();
|
||||
}
|
||||
|
||||
|
@ -695,8 +833,7 @@ SampleControls::SampleControls(QWidget* parent)
|
|||
|
||||
bool SampleEditor::loadData(ProjectModel::SampleNode* node)
|
||||
{
|
||||
m_sampleView->loadData(node);
|
||||
m_controls->loadData();
|
||||
m_controls->loadData(m_sampleView->loadData(node));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ class SampleEditor;
|
|||
class SampleView : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class SampleControls;
|
||||
qreal m_baseSamplesPerPx = 100.0;
|
||||
qreal m_samplesPerPx = 100.0;
|
||||
qreal m_zoomFactor = 1.0;
|
||||
|
@ -38,7 +39,7 @@ class SampleView : public QWidget
|
|||
SampleEditor* getEditor() const;
|
||||
public:
|
||||
explicit SampleView(QWidget* parent = Q_NULLPTR);
|
||||
void loadData(ProjectModel::SampleNode* node);
|
||||
bool loadData(ProjectModel::SampleNode* node);
|
||||
void unloadData();
|
||||
ProjectModel::INode* currentNode() const;
|
||||
amuse::SampleEntryData* entryData() const;
|
||||
|
@ -68,9 +69,12 @@ class SampleControls : public QFrame
|
|||
QPushButton* m_makeOtherVersion;
|
||||
QMetaObject::Connection m_makeOtherConn;
|
||||
QPushButton* m_showInBrowser;
|
||||
bool m_updateFile = true;
|
||||
bool m_enableUpdate = true;
|
||||
bool m_enableFileWrite = true;
|
||||
public:
|
||||
SampleControls(QWidget* parent = Q_NULLPTR);
|
||||
void doFileWrite();
|
||||
void setFileWrite(bool w);
|
||||
public slots:
|
||||
void zoomSliderChanged(int val);
|
||||
void loopStateChanged(int state);
|
||||
|
@ -85,7 +89,7 @@ public slots:
|
|||
void setLoopStartSample(int sample) { m_loopStart->setValue(sample); }
|
||||
void setLoopEndSample(int sample) { m_loopEnd->setValue(sample); }
|
||||
|
||||
void loadData();
|
||||
void loadData(bool reset);
|
||||
void unloadData();
|
||||
};
|
||||
|
||||
|
@ -94,6 +98,8 @@ class SampleEditor : public EditorWidget
|
|||
Q_OBJECT
|
||||
friend class SampleView;
|
||||
friend class SampleControls;
|
||||
friend class SampLoopUndoCommand;
|
||||
friend class SampPitchUndoCommand;
|
||||
QScrollArea* m_scrollArea;
|
||||
SampleView* m_sampleView;
|
||||
SampleControls* m_controls;
|
||||
|
|
|
@ -677,14 +677,12 @@ public:
|
|||
void undo()
|
||||
{
|
||||
m_undid = true;
|
||||
static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
|
||||
m_obj->swapPositions(m_a, m_b);
|
||||
m_node.cast<ProjectModel::SoundMacroNode>()->m_obj->swapPositions(m_a, m_b);
|
||||
EditorUndoCommand::undo();
|
||||
}
|
||||
void redo()
|
||||
{
|
||||
static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
|
||||
m_obj->swapPositions(m_a, m_b);
|
||||
m_node.cast<ProjectModel::SoundMacroNode>()->m_obj->swapPositions(m_a, m_b);
|
||||
if (m_undid)
|
||||
EditorUndoCommand::redo();
|
||||
}
|
||||
|
@ -810,16 +808,14 @@ public:
|
|||
: EditorUndoCommand(node.get(), QUndoStack::tr("Insert %1").arg(text)), m_insertIdx(insertIdx) {}
|
||||
void undo()
|
||||
{
|
||||
m_cmd = static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
|
||||
m_obj->deleteCmd(m_insertIdx);
|
||||
m_cmd = m_node.cast<ProjectModel::SoundMacroNode>()->m_obj->deleteCmd(m_insertIdx);
|
||||
EditorUndoCommand::undo();
|
||||
}
|
||||
void redo()
|
||||
{
|
||||
if (!m_cmd)
|
||||
return;
|
||||
static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
|
||||
m_obj->insertCmd(m_insertIdx, std::move(m_cmd));
|
||||
m_node.cast<ProjectModel::SoundMacroNode>()->m_obj->insertCmd(m_insertIdx, std::move(m_cmd));
|
||||
m_cmd.reset();
|
||||
EditorUndoCommand::redo();
|
||||
}
|
||||
|
@ -866,15 +862,13 @@ public:
|
|||
void undo()
|
||||
{
|
||||
m_undid = true;
|
||||
static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
|
||||
m_obj->insertCmd(m_deleteIdx, std::move(m_cmd));
|
||||
m_node.cast<ProjectModel::SoundMacroNode>()->m_obj->insertCmd(m_deleteIdx, std::move(m_cmd));
|
||||
m_cmd.reset();
|
||||
EditorUndoCommand::undo();
|
||||
}
|
||||
void redo()
|
||||
{
|
||||
m_cmd = static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
|
||||
m_obj->deleteCmd(m_deleteIdx);
|
||||
m_cmd = m_node.cast<ProjectModel::SoundMacroNode>()->m_obj->deleteCmd(m_deleteIdx);
|
||||
if (m_undid)
|
||||
EditorUndoCommand::redo();
|
||||
}
|
||||
|
|
|
@ -190,7 +190,7 @@
|
|||
<translation>Verlassen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+160"/>
|
||||
<location line="+159"/>
|
||||
<source>The directory at '%1' must exist for the Amuse editor.</source>
|
||||
<translation>Das Verzeichnis unter '% 1' muss für den Amuse-Editor vorhanden sein.</translation>
|
||||
</message>
|
||||
|
@ -225,7 +225,7 @@
|
|||
<translation>Keine MIDI-Geräte gefunden</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+237"/>
|
||||
<location line="+244"/>
|
||||
<source>New Project</source>
|
||||
<translation>Neues Projekt</translation>
|
||||
</message>
|
||||
|
@ -240,12 +240,12 @@
|
|||
<translation>Das Verzeichnis '% 1' existiert nicht.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-504"/>
|
||||
<location line="-510"/>
|
||||
<source>Clear Recent Projects</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+124"/>
|
||||
<location line="+123"/>
|
||||
<source>Amuse [%1/%2/%3]</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -256,23 +256,23 @@
|
|||
</message>
|
||||
<message>
|
||||
<location line="+31"/>
|
||||
<location line="+338"/>
|
||||
<location line="+345"/>
|
||||
<source>The directory at '%1' must not be empty.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-337"/>
|
||||
<location line="+338"/>
|
||||
<location line="-344"/>
|
||||
<location line="+345"/>
|
||||
<source>Directory empty</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-216"/>
|
||||
<location line="-223"/>
|
||||
<source>SUSTAIN</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+222"/>
|
||||
<location line="+229"/>
|
||||
<source>Bad Directory</source>
|
||||
<translation>Schlechtes Verzeichnis</translation>
|
||||
</message>
|
||||
|
@ -396,7 +396,7 @@
|
|||
<context>
|
||||
<name>ProjectModel</name>
|
||||
<message>
|
||||
<location filename="../ProjectModel.cpp" line="+227"/>
|
||||
<location filename="../ProjectModel.cpp" line="+226"/>
|
||||
<source>Sound Macros</source>
|
||||
<translation>Sound-Makros</translation>
|
||||
</message>
|
||||
|
@ -457,23 +457,29 @@
|
|||
<context>
|
||||
<name>QUndoStack</name>
|
||||
<message>
|
||||
<location filename="../SampleEditor.cpp" line="+459"/>
|
||||
<location filename="../SoundMacroEditor.cpp" line="+350"/>
|
||||
<source>Change %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+326"/>
|
||||
<location line="+118"/>
|
||||
<source>Change Base Pitch</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../SoundMacroEditor.cpp" line="+326"/>
|
||||
<source>Reorder %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+134"/>
|
||||
<location line="+132"/>
|
||||
<source>Insert %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ProjectModel.cpp" line="+151"/>
|
||||
<location filename="../SoundMacroEditor.cpp" line="+55"/>
|
||||
<location filename="../ProjectModel.cpp" line="+152"/>
|
||||
<location filename="../SoundMacroEditor.cpp" line="+53"/>
|
||||
<source>Delete %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -481,7 +487,7 @@
|
|||
<context>
|
||||
<name>SampleControls</name>
|
||||
<message>
|
||||
<location filename="../SampleEditor.cpp" line="+546"/>
|
||||
<location filename="../SampleEditor.cpp" line="+89"/>
|
||||
<source>Make WAV Version</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -496,7 +502,7 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+56"/>
|
||||
<location line="+74"/>
|
||||
<location line="+66"/>
|
||||
<source>Nothing Loaded</source>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -507,12 +513,24 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+7"/>
|
||||
<location line="-264"/>
|
||||
<location line="+15"/>
|
||||
<location line="+256"/>
|
||||
<source>Loop</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<location line="-237"/>
|
||||
<source>Loop Start</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+19"/>
|
||||
<source>Loop End</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+227"/>
|
||||
<source>Start</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -530,7 +548,7 @@
|
|||
<context>
|
||||
<name>SoundMacroCatalogue</name>
|
||||
<message>
|
||||
<location filename="../SoundMacroEditor.cpp" line="+118"/>
|
||||
<location filename="../SoundMacroEditor.cpp" line="+116"/>
|
||||
<source>Control</source>
|
||||
<translation>Steuerung</translation>
|
||||
</message>
|
||||
|
@ -603,7 +621,7 @@
|
|||
<context>
|
||||
<name>SoundMacroDeleteButton</name>
|
||||
<message>
|
||||
<location line="-847"/>
|
||||
<location line="-841"/>
|
||||
<source>Delete this SoundMacro</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
|
|
@ -16,7 +16,7 @@ class AudioGroup
|
|||
AudioGroupPool m_pool;
|
||||
AudioGroupSampleDirectory m_sdir;
|
||||
const unsigned char* m_samp = nullptr;
|
||||
SystemString m_groupPath;
|
||||
SystemString m_groupPath; /* Typically only set by editor */
|
||||
bool m_valid;
|
||||
|
||||
SystemString getSampleBasePath(SampleId sfxId) const;
|
||||
|
@ -29,12 +29,16 @@ public:
|
|||
|
||||
void assign(const AudioGroupData& data);
|
||||
void assign(SystemStringView groupPath);
|
||||
void setGroupPath(SystemStringView groupPath) { m_groupPath = groupPath; }
|
||||
|
||||
const SampleEntry* getSample(SampleId sfxId) const;
|
||||
std::pair<ObjToken<SampleEntryData>, const unsigned char*>
|
||||
getSampleData(SampleId sfxId, const SampleEntry* sample) const;
|
||||
SampleFileState getSampleFileState(SampleId sfxId,
|
||||
const SampleEntry* sample, SystemString* pathOut = nullptr);
|
||||
const SampleEntry* sample, SystemString* pathOut = nullptr) const;
|
||||
void patchSampleMetadata(SampleId sfxId, const SampleEntry* sample) const;
|
||||
void makeWAVVersion(SampleId sfxId, const SampleEntry* sample) const;
|
||||
void makeCompressedVersion(SampleId sfxId, const SampleEntry* sample) const;
|
||||
const AudioGroupProject& getProj() const { return m_proj; }
|
||||
const AudioGroupPool& getPool() const { return m_pool; }
|
||||
const AudioGroupSampleDirectory& getSdir() const { return m_sdir; }
|
||||
|
|
|
@ -15,17 +15,17 @@ struct DSPADPCMHeader : BigDNA
|
|||
Value<atUint32> x0_num_samples;
|
||||
Value<atUint32> x4_num_nibbles;
|
||||
Value<atUint32> x8_sample_rate;
|
||||
Value<atUint16> xc_loop_flag;
|
||||
Value<atUint16> xc_loop_flag = 0;
|
||||
Value<atUint16> xe_format = 0; /* 0 for ADPCM */
|
||||
Value<atUint32> x10_loop_start_nibble = 0;
|
||||
Value<atUint32> x14_loop_end_nibble = 0;
|
||||
Value<atUint32> x18_ca = 0;
|
||||
Value<atInt16> x1c_coef[8][2];
|
||||
Value<atInt16> x3c_gain = 0;
|
||||
Value<atInt16> x3e_ps;
|
||||
Value<atInt16> x40_hist1;
|
||||
Value<atInt16> x42_hist2;
|
||||
Value<atInt16> x44_loop_ps;
|
||||
Value<atInt16> x3e_ps = 0;
|
||||
Value<atInt16> x40_hist1 = 0;
|
||||
Value<atInt16> x42_hist2 = 0;
|
||||
Value<atInt16> x44_loop_ps = 0;
|
||||
Value<atInt16> x46_loop_hist1 = 0;
|
||||
Value<atInt16> x48_loop_hist2 = 0;
|
||||
Value<atUint8> m_pitch = 0; // Stash this in the padding
|
||||
|
@ -62,8 +62,8 @@ struct WAVSampleLoop : LittleDNA
|
|||
AT_DECL_DNA
|
||||
Value<atUint32> cuePointId = 0;
|
||||
Value<atUint32> loopType = 0; // 0: forward loop
|
||||
Value<atUint32> start; // in bytes
|
||||
Value<atUint32> end; // in bytes
|
||||
Value<atUint32> start;
|
||||
Value<atUint32> end;
|
||||
Value<atUint32> fraction = 0;
|
||||
Value<atUint32> playCount = 0;
|
||||
};
|
||||
|
@ -225,11 +225,12 @@ public:
|
|||
return fmt == SampleFormat::DSP || fmt == SampleFormat::DSP_DRUM;
|
||||
}
|
||||
|
||||
void setLoopStartSample(atUint32 sample)
|
||||
void _setLoopStartSample(atUint32 sample)
|
||||
{
|
||||
m_loopLengthSamples += m_loopStartSample - sample;
|
||||
m_loopStartSample = sample;
|
||||
}
|
||||
void setLoopStartSample(atUint32 sample);
|
||||
atUint32 getLoopStartSample() const
|
||||
{
|
||||
return m_loopStartSample;
|
||||
|
@ -284,6 +285,9 @@ public:
|
|||
|
||||
void loadLooseDSP(SystemStringView dspPath);
|
||||
void loadLooseWAV(SystemStringView wavPath);
|
||||
|
||||
void patchMetadataDSP(SystemStringView dspPath);
|
||||
void patchMetadataWAV(SystemStringView wavPath);
|
||||
};
|
||||
/* This double-wrapper allows Voices to keep a strong reference on
|
||||
* a single instance of loaded loose data without being unexpectedly
|
||||
|
@ -315,6 +319,7 @@ public:
|
|||
|
||||
void loadLooseData(SystemStringView basePath);
|
||||
SampleFileState getFileState(SystemStringView basePath, SystemString* pathOut = nullptr) const;
|
||||
void patchSampleMetadata(SystemStringView basePath) const;
|
||||
};
|
||||
|
||||
private:
|
||||
|
@ -322,7 +327,7 @@ private:
|
|||
static void _extractWAV(SampleId id, const EntryData& ent, amuse::SystemStringView destDir,
|
||||
const unsigned char* samp);
|
||||
static void _extractCompressed(SampleId id, const EntryData& ent, amuse::SystemStringView destDir,
|
||||
const unsigned char* samp);
|
||||
const unsigned char* samp, bool compressWAV = false);
|
||||
|
||||
public:
|
||||
AudioGroupSampleDirectory() = default;
|
||||
|
|
|
@ -32,4 +32,8 @@ unsigned DSPDecompressFrameRangedStateOnly(const uint8_t* in,
|
|||
const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2,
|
||||
unsigned firstSample, unsigned lastSample);
|
||||
|
||||
void DSPCorrelateCoefs(const short* source, int samples, short coefsOut[8][2]);
|
||||
|
||||
void DSPEncodeFrame(short pcmInOut[16], int sampleCount, unsigned char adpcmOut[8], const short coefsIn[8][2]);
|
||||
|
||||
#endif // _DSPCODEC_h
|
||||
|
|
|
@ -53,7 +53,7 @@ std::pair<ObjToken<SampleEntryData>, const unsigned char*>
|
|||
return {{}, m_samp + sample->m_data->m_sampleOff};
|
||||
}
|
||||
|
||||
SampleFileState AudioGroup::getSampleFileState(SampleId sfxId, const SampleEntry* sample, SystemString* pathOut)
|
||||
SampleFileState AudioGroup::getSampleFileState(SampleId sfxId, const SampleEntry* sample, SystemString* pathOut) const
|
||||
{
|
||||
if (sample->m_data->m_looseData)
|
||||
{
|
||||
|
@ -65,4 +65,32 @@ SampleFileState AudioGroup::getSampleFileState(SampleId sfxId, const SampleEntry
|
|||
return SampleFileState::MemoryOnlyCompressed;
|
||||
return SampleFileState::MemoryOnlyWAV;
|
||||
}
|
||||
|
||||
void AudioGroup::patchSampleMetadata(SampleId sfxId, const SampleEntry* sample) const
|
||||
{
|
||||
if (sample->m_data->m_looseData)
|
||||
{
|
||||
setIdDatabases();
|
||||
SystemString basePath = getSampleBasePath(sfxId);
|
||||
sample->patchSampleMetadata(basePath);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroup::makeWAVVersion(SampleId sfxId, const SampleEntry* sample) const
|
||||
{
|
||||
if (sample->m_data->m_looseData)
|
||||
{
|
||||
setIdDatabases();
|
||||
m_sdir._extractWAV(sfxId, *sample->m_data, m_groupPath, sample->m_data->m_looseData.get());
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroup::makeCompressedVersion(SampleId sfxId, const SampleEntry* sample) const
|
||||
{
|
||||
if (sample->m_data->m_looseData)
|
||||
{
|
||||
setIdDatabases();
|
||||
m_sdir._extractCompressed(sfxId, *sample->m_data, m_groupPath, sample->m_data->m_looseData.get(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
#include "athena/FileWriter.hpp"
|
||||
#include "athena/FileReader.hpp"
|
||||
#include <cstring>
|
||||
#ifndef _WIN32
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
|
@ -149,6 +152,28 @@ static uint32_t DSPNibbleToSample(uint32_t nibble)
|
|||
return ret;
|
||||
}
|
||||
|
||||
void AudioGroupSampleDirectory::EntryData::setLoopStartSample(atUint32 sample)
|
||||
{
|
||||
_setLoopStartSample(sample);
|
||||
|
||||
if (m_looseData && isFormatDSP())
|
||||
{
|
||||
uint32_t block = m_loopStartSample / 14;
|
||||
uint32_t rem = m_loopStartSample % 14;
|
||||
int16_t prev1 = 0;
|
||||
int16_t prev2 = 0;
|
||||
for (uint32_t b = 0; b < block; ++b)
|
||||
DSPDecompressFrameStateOnly(m_looseData.get() + 8 * b, m_ADPCMParms.dsp.m_coefs,
|
||||
&prev1, &prev2, 14);
|
||||
if (rem)
|
||||
DSPDecompressFrameStateOnly(m_looseData.get() + 8 * block, m_ADPCMParms.dsp.m_coefs,
|
||||
&prev1, &prev2, rem);
|
||||
m_ADPCMParms.dsp.m_hist1 = prev1;
|
||||
m_ADPCMParms.dsp.m_hist2 = prev2;
|
||||
m_ADPCMParms.dsp.m_lps = m_looseData[8 * block];
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroupSampleDirectory::EntryData::loadLooseDSP(SystemStringView dspPath)
|
||||
{
|
||||
athena::io::FileReader r(dspPath);
|
||||
|
@ -161,8 +186,8 @@ void AudioGroupSampleDirectory::EntryData::loadLooseDSP(SystemStringView dspPath
|
|||
m_numSamples = header.x0_num_samples;
|
||||
if (header.xc_loop_flag)
|
||||
{
|
||||
m_loopStartSample = DSPNibbleToSample(header.x10_loop_start_nibble);
|
||||
m_loopLengthSamples = DSPNibbleToSample(header.x14_loop_end_nibble) - m_loopStartSample;
|
||||
_setLoopStartSample(DSPNibbleToSample(header.x10_loop_start_nibble));
|
||||
setLoopEndSample(DSPNibbleToSample(header.x14_loop_end_nibble));
|
||||
}
|
||||
m_ADPCMParms.dsp.m_ps = uint8_t(header.x3e_ps);
|
||||
m_ADPCMParms.dsp.m_lps = uint8_t(header.x44_loop_ps);
|
||||
|
@ -212,8 +237,8 @@ void AudioGroupSampleDirectory::EntryData::loadLooseWAV(SystemStringView wavPath
|
|||
{
|
||||
WAVSampleLoop loop;
|
||||
loop.read(r);
|
||||
m_loopStartSample = loop.start;
|
||||
m_loopLengthSamples = loop.end - loop.start + 1;
|
||||
_setLoopStartSample(loop.start);
|
||||
setLoopEndSample(loop.end);
|
||||
}
|
||||
}
|
||||
else if (chunkMagic == SBIG('data'))
|
||||
|
@ -301,6 +326,213 @@ SampleFileState AudioGroupSampleDirectory::Entry::getFileState(SystemStringView
|
|||
return SampleFileState::WAVNoCompressed;
|
||||
}
|
||||
|
||||
void AudioGroupSampleDirectory::EntryData::patchMetadataDSP(SystemStringView dspPath)
|
||||
{
|
||||
athena::io::FileReader r(dspPath);
|
||||
if (!r.hasError())
|
||||
{
|
||||
DSPADPCMHeader head;
|
||||
head.read(r);
|
||||
|
||||
if (m_loopLengthSamples != 0)
|
||||
{
|
||||
uint32_t block = getLoopStartSample() / 14;
|
||||
uint32_t rem = getLoopStartSample() % 14;
|
||||
int16_t prev1 = 0;
|
||||
int16_t prev2 = 0;
|
||||
uint8_t blockBuf[8];
|
||||
for (uint32_t b = 0; b < block; ++b)
|
||||
{
|
||||
r.readBytesToBuf(blockBuf, 8);
|
||||
DSPDecompressFrameStateOnly(blockBuf, head.x1c_coef, &prev1, &prev2, 14);
|
||||
}
|
||||
if (rem)
|
||||
{
|
||||
r.readBytesToBuf(blockBuf, 8);
|
||||
DSPDecompressFrameStateOnly(blockBuf, head.x1c_coef, &prev1, &prev2, rem);
|
||||
}
|
||||
head.xc_loop_flag = 1;
|
||||
head.x44_loop_ps = blockBuf[0];
|
||||
head.x46_loop_hist1 = prev1;
|
||||
head.x48_loop_hist2 = prev2;
|
||||
}
|
||||
else
|
||||
{
|
||||
head.xc_loop_flag = 0;
|
||||
head.x44_loop_ps = 0;
|
||||
head.x46_loop_hist1 = 0;
|
||||
head.x48_loop_hist2 = 0;
|
||||
}
|
||||
head.x10_loop_start_nibble = DSPSampleToNibble(getLoopStartSample());
|
||||
head.x14_loop_end_nibble = DSPSampleToNibble(getLoopEndSample());
|
||||
head.m_pitch = m_pitch;
|
||||
r.close();
|
||||
|
||||
athena::io::FileWriter w(dspPath, false);
|
||||
if (!w.hasError())
|
||||
{
|
||||
w.seek(0, athena::Begin);
|
||||
head.write(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroupSampleDirectory::EntryData::patchMetadataWAV(SystemStringView wavPath)
|
||||
{
|
||||
athena::io::FileReader r(wavPath);
|
||||
if (!r.hasError())
|
||||
{
|
||||
atUint32 riffMagic = r.readUint32Little();
|
||||
if (riffMagic == SBIG('RIFF'))
|
||||
{
|
||||
atUint32 wavChuckSize = r.readUint32Little();
|
||||
atUint32 wavMagic = r.readUint32Little();
|
||||
if (wavMagic == SBIG('WAVE'))
|
||||
{
|
||||
atInt64 smplOffset = -1;
|
||||
atInt64 loopOffset = -1;
|
||||
WAVFormatChunk fmt;
|
||||
int readSec = 0;
|
||||
|
||||
while (r.position() < wavChuckSize + 8)
|
||||
{
|
||||
atUint32 chunkMagic = r.readUint32Little();
|
||||
atUint32 chunkSize = r.readUint32Little();
|
||||
atUint64 startPos = r.position();
|
||||
if (chunkMagic == SBIG('fmt '))
|
||||
{
|
||||
fmt.read(r);
|
||||
++readSec;
|
||||
}
|
||||
else if (chunkMagic == SBIG('smpl'))
|
||||
{
|
||||
smplOffset = startPos;
|
||||
if (chunkSize >= 60)
|
||||
loopOffset = startPos + 36;
|
||||
++readSec;
|
||||
}
|
||||
r.seek(startPos + chunkSize, athena::Begin);
|
||||
}
|
||||
|
||||
if (smplOffset == -1 || loopOffset == -1)
|
||||
{
|
||||
/* Complete rewrite of RIFF layout - new smpl chunk */
|
||||
r.seek(12, athena::Begin);
|
||||
athena::io::FileWriter w(wavPath);
|
||||
if (!w.hasError())
|
||||
{
|
||||
w.writeUint32Little(SBIG('RIFF'));
|
||||
w.writeUint32Little(0);
|
||||
w.writeUint32Little(SBIG('WAVE'));
|
||||
|
||||
bool wroteSMPL = false;
|
||||
while (r.position() < wavChuckSize + 8)
|
||||
{
|
||||
atUint32 chunkMagic = r.readUint32Little();
|
||||
atUint32 chunkSize = r.readUint32Little();
|
||||
if (!wroteSMPL && (chunkMagic == SBIG('smpl') || chunkMagic == SBIG('data')))
|
||||
{
|
||||
wroteSMPL = true;
|
||||
w.writeUint32Little(SBIG('smpl'));
|
||||
w.writeUint32Little(60);
|
||||
WAVSampleChunk smpl;
|
||||
smpl.smplPeriod = 1000000000 / fmt.sampleRate;
|
||||
smpl.midiNote = m_pitch;
|
||||
if (m_loopLengthSamples != 0)
|
||||
{
|
||||
smpl.numSampleLoops = 1;
|
||||
smpl.additionalDataSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
smpl.numSampleLoops = 0;
|
||||
smpl.additionalDataSize = 24;
|
||||
}
|
||||
smpl.write(w);
|
||||
WAVSampleLoop loop;
|
||||
loop.start = getLoopStartSample();
|
||||
loop.end = getLoopEndSample();
|
||||
loop.write(w);
|
||||
if (chunkMagic == SBIG('smpl'))
|
||||
{
|
||||
r.seek(chunkSize, athena::Current);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
w.writeUint32Little(chunkMagic);
|
||||
w.writeUint32Little(chunkSize);
|
||||
w.writeUBytes(r.readUBytes(chunkSize).get(), chunkSize);
|
||||
}
|
||||
|
||||
atUint64 wavLen = w.position();
|
||||
w.seek(4, athena::Begin);
|
||||
w.writeUint32Little(wavLen - 8);
|
||||
}
|
||||
r.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* In-place patch of RIFF layout - edit smpl chunk */
|
||||
r.close();
|
||||
athena::io::FileWriter w(wavPath, false);
|
||||
if (!w.hasError())
|
||||
{
|
||||
w.seek(smplOffset, athena::Begin);
|
||||
WAVSampleChunk smpl;
|
||||
smpl.smplPeriod = 1000000000 / fmt.sampleRate;
|
||||
smpl.midiNote = m_pitch;
|
||||
if (m_loopLengthSamples != 0)
|
||||
{
|
||||
smpl.numSampleLoops = 1;
|
||||
smpl.additionalDataSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
smpl.numSampleLoops = 0;
|
||||
smpl.additionalDataSize = 24;
|
||||
}
|
||||
smpl.write(w);
|
||||
WAVSampleLoop loop;
|
||||
loop.start = getLoopStartSample();
|
||||
loop.end = getLoopEndSample();
|
||||
loop.write(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* File timestamps reflect actual audio content, not loop/pitch data */
|
||||
static void SetAudioFileTime(const SystemString& path, const Sstat& stat)
|
||||
{
|
||||
struct timespec times[] = { stat.st_atim, stat.st_mtim };
|
||||
utimensat(AT_FDCWD, path.c_str(), times, 0);
|
||||
}
|
||||
|
||||
void AudioGroupSampleDirectory::Entry::patchSampleMetadata(SystemStringView basePath) const
|
||||
{
|
||||
SystemString wavPath = SystemString(basePath) + _S(".wav");
|
||||
SystemString dspPath = SystemString(basePath) + _S(".dsp");
|
||||
Sstat wavStat, dspStat;
|
||||
bool wavValid = !Stat(wavPath.c_str(), &wavStat) && S_ISREG(wavStat.st_mode);
|
||||
bool dspValid = !Stat(dspPath.c_str(), &dspStat) && S_ISREG(dspStat.st_mode);
|
||||
|
||||
EntryData& curData = *m_data;
|
||||
|
||||
if (wavValid)
|
||||
{
|
||||
curData.patchMetadataWAV(wavPath);
|
||||
SetAudioFileTime(wavPath, wavStat);
|
||||
}
|
||||
|
||||
if (dspValid)
|
||||
{
|
||||
curData.patchMetadataDSP(dspPath);
|
||||
SetAudioFileTime(dspPath, dspStat);
|
||||
}
|
||||
}
|
||||
|
||||
AudioGroupSampleDirectory AudioGroupSampleDirectory::CreateAudioGroupSampleDirectory(SystemStringView groupPath)
|
||||
{
|
||||
AudioGroupSampleDirectory ret;
|
||||
|
@ -336,14 +568,16 @@ AudioGroupSampleDirectory AudioGroupSampleDirectory::CreateAudioGroupSampleDirec
|
|||
void AudioGroupSampleDirectory::_extractWAV(SampleId id, const EntryData& ent,
|
||||
amuse::SystemStringView destDir, const unsigned char* samp)
|
||||
{
|
||||
amuse::SystemString path(destDir);
|
||||
SystemString path(destDir);
|
||||
path += _S("/");
|
||||
#ifdef _WIN32
|
||||
path += athena::utility::utf8ToWide(SampleId::CurNameDB->resolveNameFromId(id));
|
||||
#else
|
||||
path += SampleId::CurNameDB->resolveNameFromId(id);
|
||||
#endif
|
||||
SystemString dspPath = path;
|
||||
path += _S(".wav");
|
||||
dspPath += _S(".dsp");
|
||||
athena::io::FileWriter w(path);
|
||||
|
||||
SampleFormat fmt = SampleFormat(ent.m_numSamples >> 24);
|
||||
|
@ -356,8 +590,8 @@ void AudioGroupSampleDirectory::_extractWAV(SampleId id, const EntryData& ent,
|
|||
header.smplChunk.smplPeriod = 1000000000u / ent.m_sampleRate;
|
||||
header.smplChunk.midiNote = ent.m_pitch;
|
||||
header.smplChunk.numSampleLoops = 1;
|
||||
header.sampleLoop.start = ent.m_loopStartSample;
|
||||
header.sampleLoop.end = ent.m_loopStartSample + ent.m_loopLengthSamples - 1;
|
||||
header.sampleLoop.start = ent.getLoopStartSample();
|
||||
header.sampleLoop.end = ent.getLoopEndSample();
|
||||
header.dataChunkSize = numSamples * 2u;
|
||||
header.wavChuckSize = 36 + 68 + header.dataChunkSize;
|
||||
header.write(w);
|
||||
|
@ -379,7 +613,7 @@ void AudioGroupSampleDirectory::_extractWAV(SampleId id, const EntryData& ent,
|
|||
{
|
||||
uint32_t remSamples = numSamples;
|
||||
uint32_t numFrames = (remSamples + 13) / 14;
|
||||
const unsigned char* cur = samp + ent.m_sampleOff;
|
||||
const unsigned char* cur = samp;
|
||||
int16_t prev1 = ent.m_ADPCMParms.dsp.m_hist1;
|
||||
int16_t prev2 = ent.m_ADPCMParms.dsp.m_hist2;
|
||||
for (uint32_t i = 0; i < numFrames; ++i)
|
||||
|
@ -392,13 +626,18 @@ void AudioGroupSampleDirectory::_extractWAV(SampleId id, const EntryData& ent,
|
|||
w.writeBytes(decomp, thisSamples * 2);
|
||||
}
|
||||
|
||||
w.close();
|
||||
Sstat dspStat;
|
||||
if (!Stat(dspPath.c_str(), &dspStat) && S_ISREG(dspStat.st_mode))
|
||||
SetAudioFileTime(path.c_str(), dspStat);
|
||||
|
||||
dataLen = (DSPSampleToNibble(numSamples) + 1) / 2;
|
||||
}
|
||||
else if (fmt == SampleFormat::N64)
|
||||
{
|
||||
uint32_t remSamples = numSamples;
|
||||
uint32_t numFrames = (remSamples + 31) / 32;
|
||||
const unsigned char* cur = samp + ent.m_sampleOff + sizeof(ADPCMParms::VADPCMParms);
|
||||
const unsigned char* cur = samp + sizeof(ADPCMParms::VADPCMParms);
|
||||
for (uint32_t i = 0; i < numFrames; ++i)
|
||||
{
|
||||
int16_t decomp[32] = {};
|
||||
|
@ -414,14 +653,14 @@ void AudioGroupSampleDirectory::_extractWAV(SampleId id, const EntryData& ent,
|
|||
else if (fmt == SampleFormat::PCM)
|
||||
{
|
||||
dataLen = numSamples * 2;
|
||||
const int16_t* cur = reinterpret_cast<const int16_t*>(samp + ent.m_sampleOff);
|
||||
const int16_t* cur = reinterpret_cast<const int16_t*>(samp);
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
w.writeInt16Big(cur[i]);
|
||||
}
|
||||
else // PCM_PC
|
||||
{
|
||||
dataLen = numSamples * 2;
|
||||
w.writeBytes(samp + ent.m_sampleOff, dataLen);
|
||||
w.writeBytes(samp, dataLen);
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t[]>& ld = const_cast<std::unique_ptr<uint8_t[]>&>(ent.m_looseData);
|
||||
|
@ -432,7 +671,7 @@ void AudioGroupSampleDirectory::_extractWAV(SampleId id, const EntryData& ent,
|
|||
|
||||
const_cast<time_t&>(ent.m_looseModTime) = theStat.st_mtime;
|
||||
ld.reset(new uint8_t[dataLen]);
|
||||
memcpy(ld.get(), samp + ent.m_sampleOff, dataLen);
|
||||
memcpy(ld.get(), samp, dataLen);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,20 +680,21 @@ void AudioGroupSampleDirectory::extractWAV(SampleId id, amuse::SystemStringView
|
|||
auto search = m_entries.find(id);
|
||||
if (search == m_entries.cend())
|
||||
return;
|
||||
_extractWAV(id, *search->second->m_data, destDir, samp);
|
||||
_extractWAV(id, *search->second->m_data, destDir, samp + search->second->m_data->m_sampleOff);
|
||||
}
|
||||
|
||||
void AudioGroupSampleDirectory::extractAllWAV(amuse::SystemStringView destDir, const unsigned char* samp) const
|
||||
{
|
||||
for (const auto& ent : m_entries)
|
||||
_extractWAV(ent.first, *ent.second->m_data, destDir, samp);
|
||||
_extractWAV(ent.first, *ent.second->m_data, destDir, samp + ent.second->m_data->m_sampleOff);
|
||||
}
|
||||
|
||||
void AudioGroupSampleDirectory::_extractCompressed(SampleId id, const EntryData& ent,
|
||||
amuse::SystemStringView destDir, const unsigned char* samp)
|
||||
amuse::SystemStringView destDir, const unsigned char* samp,
|
||||
bool compressWAV)
|
||||
{
|
||||
SampleFormat fmt = ent.getSampleFormat();
|
||||
if (fmt == SampleFormat::PCM || fmt == SampleFormat::PCM_PC)
|
||||
if (!compressWAV && (fmt == SampleFormat::PCM || fmt == SampleFormat::PCM_PC))
|
||||
{
|
||||
_extractWAV(id, ent, destDir, samp);
|
||||
return;
|
||||
|
@ -479,8 +719,8 @@ void AudioGroupSampleDirectory::_extractCompressed(SampleId id, const EntryData&
|
|||
header.xc_loop_flag = atUint16(ent.m_loopLengthSamples != 0);
|
||||
if (header.xc_loop_flag)
|
||||
{
|
||||
header.x10_loop_start_nibble = DSPSampleToNibble(ent.m_loopStartSample);
|
||||
header.x14_loop_end_nibble = DSPSampleToNibble(ent.m_loopStartSample + ent.m_loopLengthSamples);
|
||||
header.x10_loop_start_nibble = DSPSampleToNibble(ent.getLoopStartSample());
|
||||
header.x14_loop_end_nibble = DSPSampleToNibble(ent.getLoopEndSample());
|
||||
}
|
||||
for (int i = 0; i < 8; ++i)
|
||||
for (int j = 0; j < 2; ++j)
|
||||
|
@ -495,14 +735,77 @@ void AudioGroupSampleDirectory::_extractCompressed(SampleId id, const EntryData&
|
|||
athena::io::FileWriter w(path);
|
||||
header.write(w);
|
||||
dataLen = (header.x4_num_nibbles + 1) / 2;
|
||||
w.writeUBytes(samp + ent.m_sampleOff, dataLen);
|
||||
w.writeUBytes(samp, dataLen);
|
||||
}
|
||||
else if (fmt == SampleFormat::N64)
|
||||
{
|
||||
path += _S(".vadpcm");
|
||||
athena::io::FileWriter w(path);
|
||||
dataLen = sizeof(ADPCMParms::VADPCMParms) + (numSamples + 31) / 32 * 16;
|
||||
w.writeUBytes(samp + ent.m_sampleOff, dataLen);
|
||||
w.writeUBytes(samp, dataLen);
|
||||
}
|
||||
else if (fmt == SampleFormat::PCM_PC || fmt == SampleFormat::PCM)
|
||||
{
|
||||
const int16_t* samps = reinterpret_cast<const int16_t*>(samp);
|
||||
std::unique_ptr<int16_t[]> sampsSwapped;
|
||||
if (fmt == SampleFormat::PCM)
|
||||
{
|
||||
sampsSwapped.reset(new int16_t[numSamples]);
|
||||
for (uint32_t i = 0; i < numSamples; ++i)
|
||||
sampsSwapped[i] = SBig(samps[i]);
|
||||
samps = sampsSwapped.get();
|
||||
}
|
||||
|
||||
int32_t loopStartSample = ent.getLoopStartSample();
|
||||
int32_t loopEndSample = ent.getLoopEndSample();
|
||||
|
||||
DSPADPCMHeader header = {};
|
||||
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.m_pitch = ent.m_pitch;
|
||||
if (header.xc_loop_flag)
|
||||
{
|
||||
header.x10_loop_start_nibble = DSPSampleToNibble(loopStartSample);
|
||||
header.x14_loop_end_nibble = DSPSampleToNibble(loopEndSample);
|
||||
}
|
||||
DSPCorrelateCoefs(samps, numSamples, header.x1c_coef);
|
||||
|
||||
path += _S(".dsp");
|
||||
athena::io::FileWriter w(path);
|
||||
header.write(w);
|
||||
|
||||
uint32_t remSamples = numSamples;
|
||||
uint32_t curSample = 0;
|
||||
int16_t convSamps[16] = {};
|
||||
while (remSamples)
|
||||
{
|
||||
uint32_t sampleCount = std::min(14u, remSamples);
|
||||
convSamps[0] = convSamps[14];
|
||||
convSamps[1] = convSamps[15];
|
||||
memcpy(convSamps + 2, samps, sampleCount * 2);
|
||||
unsigned char adpcmOut[8];
|
||||
DSPEncodeFrame(convSamps, sampleCount, adpcmOut, header.x1c_coef);
|
||||
w.writeUBytes(adpcmOut, 8);
|
||||
if (curSample == 0)
|
||||
header.x3e_ps = adpcmOut[0];
|
||||
if (header.xc_loop_flag)
|
||||
{
|
||||
if (loopStartSample >= curSample && loopStartSample < curSample + 14)
|
||||
header.x44_loop_ps = adpcmOut[0];
|
||||
if (loopStartSample - 1 >= curSample && loopStartSample - 1 < curSample + 14)
|
||||
header.x46_loop_hist1 = convSamps[2 + (loopStartSample - 1 - curSample)];
|
||||
if (loopStartSample - 2 >= curSample && loopStartSample - 2 < curSample + 14)
|
||||
header.x48_loop_hist2 = convSamps[2 + (loopStartSample - 2 - curSample)];
|
||||
}
|
||||
remSamples -= sampleCount;
|
||||
curSample += sampleCount;
|
||||
samps += sampleCount;
|
||||
}
|
||||
|
||||
w.seek(0, athena::Begin);
|
||||
header.write(w);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -517,7 +820,7 @@ void AudioGroupSampleDirectory::_extractCompressed(SampleId id, const EntryData&
|
|||
|
||||
const_cast<time_t&>(ent.m_looseModTime) = theStat.st_mtime;
|
||||
ld.reset(new uint8_t[dataLen]);
|
||||
memcpy(ld.get(), samp + ent.m_sampleOff, dataLen);
|
||||
memcpy(ld.get(), samp, dataLen);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -527,14 +830,14 @@ void AudioGroupSampleDirectory::extractCompressed(SampleId id, amuse::SystemStri
|
|||
auto search = m_entries.find(id);
|
||||
if (search == m_entries.cend())
|
||||
return;
|
||||
_extractCompressed(id, *search->second->m_data, destDir, samp);
|
||||
_extractCompressed(id, *search->second->m_data, destDir, samp + search->second->m_data->m_sampleOff);
|
||||
}
|
||||
|
||||
void AudioGroupSampleDirectory::extractAllCompressed(amuse::SystemStringView destDir,
|
||||
const unsigned char* samp) const
|
||||
{
|
||||
for (const auto& ent : m_entries)
|
||||
_extractCompressed(ent.first, *ent.second->m_data, destDir, samp);
|
||||
_extractCompressed(ent.first, *ent.second->m_data, destDir, samp + ent.second->m_data->m_sampleOff);
|
||||
}
|
||||
|
||||
void AudioGroupSampleDirectory::reloadSampleData(SystemStringView groupPath)
|
||||
|
|
523
lib/DSPCodec.cpp
523
lib/DSPCodec.cpp
|
@ -1,5 +1,10 @@
|
|||
#include "amuse/DSPCodec.hpp"
|
||||
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
#pragma mark Decoder
|
||||
|
||||
static const int32_t NibbleToInt[16] = {0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1};
|
||||
|
||||
unsigned DSPDecompressFrame(int16_t* out, const uint8_t* in,
|
||||
|
@ -180,3 +185,521 @@ unsigned DSPDecompressFrameRangedStateOnly(const uint8_t* in,
|
|||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#pragma mark Encoder
|
||||
|
||||
/* Reference:
|
||||
* https://code.google.com/p/brawltools/source/browse/trunk/BrawlLib/Wii/Audio/AudioConverter.cs
|
||||
*/
|
||||
|
||||
/* Temporal Vector
|
||||
* A contiguous history of 3 samples starting with
|
||||
* 'current' and going 2 backwards
|
||||
*/
|
||||
typedef double tvec[3];
|
||||
|
||||
static inline void InnerProductMerge(tvec vecOut, short pcmBuf[14])
|
||||
{
|
||||
for (int i=0 ; i<=2 ; i++)
|
||||
{
|
||||
vecOut[i] = 0.0f;
|
||||
for (int x=0 ; x<14 ; x++)
|
||||
vecOut[i] -= pcmBuf[x-i] * pcmBuf[x];
|
||||
}
|
||||
}
|
||||
|
||||
static inline void OuterProductMerge(tvec mtxOut[3], short pcmBuf[14])
|
||||
{
|
||||
for (int x=1 ; x<=2 ; x++)
|
||||
for (int y=1 ; y<=2 ; y++)
|
||||
{
|
||||
mtxOut[x][y] = 0.0;
|
||||
for (int z=0 ; z<14 ; z++)
|
||||
mtxOut[x][y] += pcmBuf[z-x] * pcmBuf[z-y];
|
||||
}
|
||||
}
|
||||
|
||||
static bool AnalyzeRanges(tvec mtx[3], int* vecIdxsOut)
|
||||
{
|
||||
double recips[3];
|
||||
double val, tmp, min, max;
|
||||
|
||||
/* Get greatest distance from zero */
|
||||
for (int x=1 ; x<=2 ; x++)
|
||||
{
|
||||
val = std::max(fabs(mtx[x][1]), fabs(mtx[x][2]));
|
||||
if (val < DBL_EPSILON)
|
||||
return true;
|
||||
|
||||
recips[x] = 1.0 / val;
|
||||
}
|
||||
|
||||
int maxIndex = 0;
|
||||
for (int i=1 ; i<=2 ; i++)
|
||||
{
|
||||
for (int x=1 ; x<i ; x++)
|
||||
{
|
||||
tmp = mtx[x][i];
|
||||
for (int y=1 ; y<x ; y++)
|
||||
tmp -= mtx[x][y] * mtx[y][i];
|
||||
mtx[x][i] = tmp;
|
||||
}
|
||||
|
||||
val = 0.0;
|
||||
for (int x=i ; x<=2 ; x++)
|
||||
{
|
||||
tmp = mtx[x][i];
|
||||
for (int y=1 ; y<i ; y++)
|
||||
tmp -= mtx[x][y] * mtx[y][i];
|
||||
|
||||
mtx[x][i] = tmp;
|
||||
tmp = fabs(tmp) * recips[x];
|
||||
if (tmp >= val)
|
||||
{
|
||||
val = tmp;
|
||||
maxIndex = x;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxIndex != i)
|
||||
{
|
||||
for (int y=1 ; y<=2 ; y++)
|
||||
{
|
||||
tmp = mtx[maxIndex][y];
|
||||
mtx[maxIndex][y] = mtx[i][y];
|
||||
mtx[i][y] = tmp;
|
||||
}
|
||||
recips[maxIndex] = recips[i];
|
||||
}
|
||||
|
||||
vecIdxsOut[i] = maxIndex;
|
||||
|
||||
if (mtx[i][i] == 0.0)
|
||||
return true;
|
||||
|
||||
if (i != 2)
|
||||
{
|
||||
tmp = 1.0 / mtx[i][i];
|
||||
for (int x=i+1 ; x<=2 ; x++)
|
||||
mtx[x][i] *= tmp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get range */
|
||||
min = 1.0e10;
|
||||
max = 0.0;
|
||||
for (int i=1 ; i<=2 ; i++)
|
||||
{
|
||||
tmp = fabs(mtx[i][i]);
|
||||
if (tmp < min)
|
||||
min = tmp;
|
||||
if (tmp > max)
|
||||
max = tmp;
|
||||
}
|
||||
|
||||
if (min / max < 1.0e-10)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void BidirectionalFilter(tvec mtx[3], int* vecIdxs, tvec vecOut)
|
||||
{
|
||||
double tmp;
|
||||
|
||||
for (int i=1, x=0 ; i<=2 ; i++)
|
||||
{
|
||||
int index = vecIdxs[i];
|
||||
tmp = vecOut[index];
|
||||
vecOut[index] = vecOut[i];
|
||||
if (x != 0)
|
||||
for (int y=x ; y<=i-1 ; y++)
|
||||
tmp -= vecOut[y] * mtx[i][y];
|
||||
else if (tmp != 0.0)
|
||||
x = i;
|
||||
vecOut[i] = tmp;
|
||||
}
|
||||
|
||||
for (int i=2 ; i>0 ; i--)
|
||||
{
|
||||
tmp = vecOut[i];
|
||||
for (int y=i+1 ; y<=2 ; y++)
|
||||
tmp -= vecOut[y] * mtx[i][y];
|
||||
vecOut[i] = tmp / mtx[i][i];
|
||||
}
|
||||
|
||||
vecOut[0] = 1.0;
|
||||
}
|
||||
|
||||
static bool QuadraticMerge(tvec inOutVec)
|
||||
{
|
||||
double v0, v1, v2 = inOutVec[2];
|
||||
double tmp = 1.0 - (v2 * v2);
|
||||
|
||||
if (tmp == 0.0)
|
||||
return true;
|
||||
|
||||
v0 = (inOutVec[0] - (v2 * v2)) / tmp;
|
||||
v1 = (inOutVec[1] - (inOutVec[1] * v2)) / tmp;
|
||||
|
||||
inOutVec[0] = v0;
|
||||
inOutVec[1] = v1;
|
||||
|
||||
return fabs(v1) > 1.0;
|
||||
}
|
||||
|
||||
static void FinishRecord(tvec in, tvec out)
|
||||
{
|
||||
for (int z=1 ; z<=2 ; z++)
|
||||
{
|
||||
if (in[z] >= 1.0)
|
||||
in[z] = 0.9999999999;
|
||||
else if (in[z] <= -1.0)
|
||||
in[z] = -0.9999999999;
|
||||
}
|
||||
out[0] = 1.0;
|
||||
out[1] = (in[2] * in[1]) + in[1];
|
||||
out[2] = in[2];
|
||||
}
|
||||
|
||||
static void MatrixFilter(tvec src, tvec dst)
|
||||
{
|
||||
tvec mtx[3];
|
||||
|
||||
mtx[2][0] = 1.0;
|
||||
for (int i=1 ; i<=2 ; i++)
|
||||
mtx[2][i] = -src[i];
|
||||
|
||||
for (int i=2 ; i>0 ; i--)
|
||||
{
|
||||
double val = 1.0 - (mtx[i][i] * mtx[i][i]);
|
||||
for (int y = 1; y <= i; y++)
|
||||
mtx[i-1][y] = ((mtx[i][i] * mtx[i][y]) + mtx[i][y]) / val;
|
||||
}
|
||||
|
||||
dst[0] = 1.0;
|
||||
for (int i=1 ; i<=2 ; i++)
|
||||
{
|
||||
dst[i] = 0.0;
|
||||
for (int y=1 ; y<=i ; y++)
|
||||
dst[i] += mtx[i][y] * dst[i-y];
|
||||
}
|
||||
}
|
||||
|
||||
static void MergeFinishRecord(tvec src, tvec dst)
|
||||
{
|
||||
tvec tmp;
|
||||
double val = src[0];
|
||||
|
||||
dst[0] = 1.0;
|
||||
for (int i=1 ; i<=2 ; i++)
|
||||
{
|
||||
double v2 = 0.0;
|
||||
for (int y=1 ; y<i ; y++)
|
||||
v2 += dst[y] * src[i-y];
|
||||
|
||||
if (val > 0.0)
|
||||
dst[i] = -(v2 + src[i]) / val;
|
||||
else
|
||||
dst[i] = 0.0;
|
||||
|
||||
tmp[i] = dst[i];
|
||||
|
||||
for (int y=1 ; y<i ; y++)
|
||||
dst[y] += dst[i] * dst[i - y];
|
||||
|
||||
val *= 1.0 - (dst[i] * dst[i]);
|
||||
}
|
||||
|
||||
FinishRecord(tmp, dst);
|
||||
}
|
||||
|
||||
static double ContrastVectors(tvec source1, tvec source2)
|
||||
{
|
||||
double val = (-source2[2] * -source2[1] + -source2[1]) / (1.0 - source2[2] * source2[2]);
|
||||
double val1 = (source1[0] * source1[0]) + (source1[1] * source1[1]) + (source1[2] * source1[2]);
|
||||
double val2 = (source1[0] * source1[1]) + (source1[1] * source1[2]);
|
||||
double val3 = source1[0] * source1[2];
|
||||
return val1 + (2.0 * val * val2) + (2.0 * (-source2[1] * val + -source2[2]) * val3);
|
||||
}
|
||||
|
||||
static void FilterRecords(tvec vecBest[8], int exp, tvec records[], int recordCount)
|
||||
{
|
||||
tvec bufferList[8];
|
||||
|
||||
int buffer1[8];
|
||||
tvec buffer2;
|
||||
|
||||
int index;
|
||||
double value, tempVal = 0;
|
||||
|
||||
for (int x=0 ; x<2 ; x++)
|
||||
{
|
||||
for (int y=0 ; y<exp ; y++)
|
||||
{
|
||||
buffer1[y] = 0;
|
||||
for (int i=0 ; i<=2 ; i++)
|
||||
bufferList[y][i] = 0.0;
|
||||
}
|
||||
for (int z=0 ; z<recordCount ; z++)
|
||||
{
|
||||
index = 0;
|
||||
value= 1.0e30;
|
||||
for (int i=0 ; i<exp ; i++)
|
||||
{
|
||||
tempVal = ContrastVectors(vecBest[i], records[z]);
|
||||
if (tempVal < value)
|
||||
{
|
||||
value = tempVal;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
buffer1[index]++;
|
||||
MatrixFilter(records[z], buffer2);
|
||||
for (int i=0 ; i<=2 ; i++)
|
||||
bufferList[index][i] += buffer2[i];
|
||||
}
|
||||
|
||||
for (int i=0 ; i<exp ; i++)
|
||||
if (buffer1[i] > 0)
|
||||
for (int y=0 ; y<=2 ; y++)
|
||||
bufferList[i][y] /= buffer1[i];
|
||||
|
||||
for (int i=0 ; i<exp ; i++)
|
||||
MergeFinishRecord(bufferList[i], vecBest[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void DSPCorrelateCoefs(const short* source, int samples, short coefsOut[8][2])
|
||||
{
|
||||
int numFrames = (samples + 13) / 14;
|
||||
int frameSamples;
|
||||
|
||||
short* blockBuffer = (short*)calloc(sizeof(short), 0x3800);
|
||||
short pcmHistBuffer[2][14] = {};
|
||||
|
||||
tvec vec1;
|
||||
tvec vec2;
|
||||
|
||||
tvec mtx[3];
|
||||
int vecIdxs[3];
|
||||
|
||||
tvec* records = (tvec*)calloc(sizeof(tvec), numFrames * 2);
|
||||
int recordCount = 0;
|
||||
|
||||
tvec vecBest[8];
|
||||
|
||||
/* Iterate though 1024-block frames */
|
||||
for (int x=samples ; x>0 ;)
|
||||
{
|
||||
if (x > 0x3800) /* Full 1024-block frame */
|
||||
{
|
||||
frameSamples = 0x3800;
|
||||
x -= 0x3800;
|
||||
}
|
||||
else /* Partial frame */
|
||||
{
|
||||
/* Zero lingering block samples */
|
||||
frameSamples = x;
|
||||
for (int z=0 ; z<14 && z+frameSamples<0x3800 ; z++)
|
||||
blockBuffer[frameSamples+z] = 0;
|
||||
x = 0;
|
||||
}
|
||||
|
||||
/* Copy (potentially non-frame-aligned PCM samples into aligned buffer) */
|
||||
memcpy(blockBuffer, source, frameSamples * sizeof(short));
|
||||
source += frameSamples;
|
||||
|
||||
|
||||
for (int i=0 ; i<frameSamples ;)
|
||||
{
|
||||
for (int z=0 ; z<14 ; z++)
|
||||
pcmHistBuffer[0][z] = pcmHistBuffer[1][z];
|
||||
for (int z=0 ; z<14 ; z++)
|
||||
pcmHistBuffer[1][z] = blockBuffer[i++];
|
||||
|
||||
InnerProductMerge(vec1, pcmHistBuffer[1]);
|
||||
if (fabs(vec1[0]) > 10.0)
|
||||
{
|
||||
OuterProductMerge(mtx, pcmHistBuffer[1]);
|
||||
if (!AnalyzeRanges(mtx, vecIdxs))
|
||||
{
|
||||
BidirectionalFilter(mtx, vecIdxs, vec1);
|
||||
if (!QuadraticMerge(vec1))
|
||||
{
|
||||
FinishRecord(vec1, records[recordCount]);
|
||||
recordCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec1[0] = 1.0;
|
||||
vec1[1] = 0.0;
|
||||
vec1[2] = 0.0;
|
||||
|
||||
for (int z=0 ; z<recordCount ; z++)
|
||||
{
|
||||
MatrixFilter(records[z], vecBest[0]);
|
||||
for (int y=1 ; y<=2 ; y++)
|
||||
vec1[y] += vecBest[0][y];
|
||||
}
|
||||
for (int y=1 ; y<=2 ; y++)
|
||||
vec1[y] /= recordCount;
|
||||
|
||||
MergeFinishRecord(vec1, vecBest[0]);
|
||||
|
||||
|
||||
int exp = 1;
|
||||
for (int w=0 ; w<3 ;)
|
||||
{
|
||||
vec2[0] = 0.0;
|
||||
vec2[1] = -1.0;
|
||||
vec2[2] = 0.0;
|
||||
for (int i=0 ; i<exp ; i++)
|
||||
for (int y=0 ; y<=2 ; y++)
|
||||
vecBest[exp+i][y] = (0.01 * vec2[y]) + vecBest[i][y];
|
||||
++w;
|
||||
exp = 1 << w;
|
||||
FilterRecords(vecBest, exp, records, recordCount);
|
||||
}
|
||||
|
||||
/* Write output */
|
||||
for (int z=0 ; z<8 ; z++)
|
||||
{
|
||||
double d;
|
||||
d = -vecBest[z][1] * 2048.0;
|
||||
if (d > 0.0)
|
||||
coefsOut[z][0] = (d > 32767.0) ? (short)32767 : (short)lround(d);
|
||||
else
|
||||
coefsOut[z][0] = (d < -32768.0) ? (short)-32768 : (short)lround(d);
|
||||
|
||||
d = -vecBest[z][2] * 2048.0;
|
||||
if (d > 0.0)
|
||||
coefsOut[z][1] = (d > 32767.0) ? (short)32767 : (short)lround(d);
|
||||
else
|
||||
coefsOut[z][1] = (d < -32768.0) ? (short)-32768 : (short)lround(d);
|
||||
}
|
||||
|
||||
/* Free memory */
|
||||
free(records);
|
||||
free(blockBuffer);
|
||||
|
||||
}
|
||||
|
||||
/* Make sure source includes the yn values (16 samples total) */
|
||||
void DSPEncodeFrame(short pcmInOut[16], int sampleCount, unsigned char adpcmOut[8], const short coefsIn[8][2])
|
||||
{
|
||||
int inSamples[8][16];
|
||||
int outSamples[8][14];
|
||||
|
||||
int bestIndex = 0;
|
||||
|
||||
int scale[8];
|
||||
double distAccum[8];
|
||||
|
||||
/* Iterate through each coef set, finding the set with the smallest error */
|
||||
for (int i=0 ; i<8 ; i++)
|
||||
{
|
||||
int v1, v2, v3;
|
||||
int distance, index;
|
||||
|
||||
/* Set yn values */
|
||||
inSamples[i][0] = pcmInOut[0];
|
||||
inSamples[i][1] = pcmInOut[1];
|
||||
|
||||
/* Round and clamp samples for this coef set */
|
||||
distance = 0;
|
||||
for (int s=0 ; s<sampleCount ; s++)
|
||||
{
|
||||
/* Multiply previous samples by coefs */
|
||||
inSamples[i][s + 2] = v1 = ((pcmInOut[s] * coefsIn[i][1]) + (pcmInOut[s + 1] * coefsIn[i][0])) / 2048;
|
||||
/* Subtract from current sample */
|
||||
v2 = pcmInOut[s + 2] - v1;
|
||||
/* Clamp */
|
||||
v3 = (v2 >= 32767) ? 32767 : (v2 <= -32768) ? -32768 : v2;
|
||||
/* Compare distance */
|
||||
if (abs(v3) > abs(distance))
|
||||
distance = v3;
|
||||
}
|
||||
|
||||
/* Set initial scale */
|
||||
for (scale[i]=0; (scale[i]<=12) && ((distance>7) || (distance<-8)); scale[i]++, distance/=2) {}
|
||||
scale[i] = (scale[i] <= 1) ? -1 : scale[i] - 2;
|
||||
|
||||
do
|
||||
{
|
||||
scale[i]++;
|
||||
distAccum[i] = 0;
|
||||
index = 0;
|
||||
|
||||
for (int s=0 ; s<sampleCount ; s++)
|
||||
{
|
||||
/* Multiply previous */
|
||||
v1 = ((inSamples[i][s] * coefsIn[i][1]) + (inSamples[i][s + 1] * coefsIn[i][0]));
|
||||
/* Evaluate from real sample */
|
||||
v2 = ((pcmInOut[s + 2] << 11) - v1) / 2048;
|
||||
/* Round to nearest sample */
|
||||
v3 = (v2 > 0) ? (int)((double)v2 / (1 << scale[i]) + 0.4999999f) : (int)((double)v2 / (1 << scale[i]) - 0.4999999f);
|
||||
|
||||
/* Clamp sample and set index */
|
||||
if (v3 < -8)
|
||||
{
|
||||
if (index < (v3 = -8 - v3))
|
||||
index = v3;
|
||||
v3 = -8;
|
||||
}
|
||||
else if (v3 > 7)
|
||||
{
|
||||
if (index < (v3 -= 7))
|
||||
index = v3;
|
||||
v3 = 7;
|
||||
}
|
||||
|
||||
/* Store result */
|
||||
outSamples[i][s] = v3;
|
||||
|
||||
/* Round and expand */
|
||||
v1 = (v1 + ((v3 * (1 << scale[i])) << 11) + 1024) >> 11;
|
||||
/* Clamp and store */
|
||||
inSamples[i][s + 2] = v2 = (v1 >= 32767) ? 32767 : (v1 <= -32768) ? -32768 : v1;
|
||||
/* Accumulate distance */
|
||||
v3 = pcmInOut[s + 2] - v2;
|
||||
distAccum[i] += v3 * (double)v3;
|
||||
}
|
||||
|
||||
for (int x=index+8 ; x>256 ; x>>=1)
|
||||
if (++scale[i] >= 12)
|
||||
scale[i] = 11;
|
||||
|
||||
} while ((scale[i] < 12) && (index > 1));
|
||||
}
|
||||
|
||||
double min = DBL_MAX;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
if (distAccum[i] < min)
|
||||
{
|
||||
min = distAccum[i];
|
||||
bestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write converted samples */
|
||||
for (int s=0 ; s<sampleCount ; s++)
|
||||
pcmInOut[s + 2] = inSamples[bestIndex][s + 2];
|
||||
|
||||
/* Write ps */
|
||||
adpcmOut[0] = (char)((bestIndex << 4) | (scale[bestIndex] & 0xF));
|
||||
|
||||
/* Zero remaining samples */
|
||||
for (int s=sampleCount ; s<14 ; s++)
|
||||
outSamples[bestIndex][s] = 0;
|
||||
|
||||
/* Write output samples */
|
||||
for (int y=0; y<7; y++)
|
||||
{
|
||||
adpcmOut[y + 1] = (char)((outSamples[bestIndex][y * 2] << 4) | (outSamples[bestIndex][y * 2 + 1] & 0xF));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue