Initial SampleEditor

This commit is contained in:
Jack Andersen 2018-07-29 20:20:03 -10:00
parent 16745c9bf8
commit 8d24e19989
21 changed files with 469 additions and 144 deletions

View File

@ -19,13 +19,13 @@ public:
class EditorUndoCommand : public QUndoCommand
{
protected:
std::shared_ptr<ProjectModel::INode> m_node;
amuse::ObjToken<ProjectModel::INode> m_node;
enum class Id
{
SMChangeVal,
};
public:
EditorUndoCommand(std::shared_ptr<ProjectModel::INode> node,
EditorUndoCommand(amuse::ObjToken<ProjectModel::INode> node,
const QString& text, QUndoCommand* parent = nullptr)
: QUndoCommand(text, parent), m_node(node) {}
void undo();

View File

@ -10,7 +10,7 @@ void MIDIReader::noteOff(uint8_t chan, uint8_t key, uint8_t velocity)
if (keySearch == m_chanVoxs.cend())
return;
if (m_lastVoice->isDestroyed() || keySearch->second == m_lastVoice)
if ((m_lastVoice && m_lastVoice->isDestroyed()) || keySearch->second == m_lastVoice)
m_lastVoice.reset();
keySearch->second->keyOff();
m_keyoffVoxs.emplace(std::move(keySearch->second));
@ -19,7 +19,7 @@ void MIDIReader::noteOff(uint8_t chan, uint8_t key, uint8_t velocity)
void MIDIReader::noteOn(uint8_t chan, uint8_t key, uint8_t velocity)
{
if (m_lastVoice->isDestroyed())
if (m_lastVoice && m_lastVoice->isDestroyed())
m_lastVoice.reset();
/* If portamento is enabled for voice, pre-empt spawning new voices */
@ -76,6 +76,7 @@ void MIDIReader::controlChange(uint8_t chan, uint8_t control, uint8_t value)
for (auto& v : m_engine.getActiveVoices())
v->setCtrlValue(control, value);
}
g_MainWindow->m_ctrlVals[control] = value;
}
void MIDIReader::programChange(uint8_t chan, uint8_t program) {}

View File

@ -1,6 +1,7 @@
#ifndef AMUSE_MIDI_READER_HPP
#define AMUSE_MIDI_READER_HPP
#include "amuse/Voice.hpp"
#include "amuse/BooBackend.hpp"
#include "amuse/Common.hpp"
#include <unordered_set>

View File

@ -410,6 +410,7 @@ bool MainWindow::_setEditor(EditorWidget* editor)
if (!editor || !editor->valid())
{
m_ui.editorContents->setCurrentWidget(m_faceSvg);
updateWindowTitle();
return false;
}
m_ui.editorContents->setCurrentWidget(editor);
@ -540,6 +541,9 @@ bool MainWindow::openProject(const QString& path)
QFileInfo(dir, QStringLiteral("!pool.yaml")).exists())
dir.cdUp();
if (m_projectModel && m_projectModel->path() == dir.path())
return true;
if (!setProjectPath(dir.path()))
return false;
@ -612,6 +616,8 @@ void MainWindow::revertAction()
void MainWindow::reloadSampleDataAction()
{
closeEditor();
ProjectModel* model = m_projectModel;
if (!m_projectModel)
return;

View File

@ -54,7 +54,8 @@ QVariant NullItemProxyModel::data(const QModelIndex& proxyIndex, int role) const
ProjectModel::INode::INode(INode* parent, int row) : m_parent(parent), m_row(row)
{
m_nullChild = std::make_unique<NullNode>(this);
auto nullNode = amuse::MakeObj<NullNode>(this);
m_nullChild = nullNode.get();
}
ProjectModel::CollectionNode* ProjectModel::GroupNode::getCollectionOfType(Type tp) const
@ -96,7 +97,7 @@ ProjectModel::BasePoolObjectNode* ProjectModel::CollectionNode::nodeOfIndex(int
ProjectModel::ProjectModel(const QString& path, QObject* parent)
: QAbstractItemModel(parent), m_dir(path), m_nullProxy(this)
{
m_root = std::make_shared<RootNode>();
m_root = amuse::MakeObj<RootNode>();
GroupNode::Icon = QIcon(":/icons/IconGroup.svg");
SongGroupNode::Icon = QIcon(":/icons/IconSongGroup.svg");
@ -202,7 +203,7 @@ void ProjectModel::_resetModelData()
{
beginResetModel();
m_projectDatabase.setIdDatabases();
m_root = std::make_shared<RootNode>();
m_root = amuse::MakeObj<RootNode>();
m_root->reserve(m_groups.size());
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it)
{
@ -427,7 +428,7 @@ bool ProjectModel::canEdit(const QModelIndex& index) const
class DeleteNodeUndoCommand : public QUndoCommand
{
QModelIndex m_deleteIdx;
std::shared_ptr<ProjectModel::INode> m_node;
amuse::ObjToken<ProjectModel::INode> m_node;
public:
DeleteNodeUndoCommand(const QModelIndex& index)
: QUndoCommand(QUndoStack::tr("Delete %1").arg(index.data().toString())), m_deleteIdx(index) {}
@ -442,14 +443,14 @@ public:
}
};
void ProjectModel::_undoDel(const QModelIndex& index, std::shared_ptr<ProjectModel::INode>&& n)
void ProjectModel::_undoDel(const QModelIndex& index, amuse::ObjToken<ProjectModel::INode> n)
{
beginInsertRows(index.parent(), index.row(), index.row());
node(index.parent())->insertChild(index.row(), std::move(n));
endInsertRows();
}
std::shared_ptr<ProjectModel::INode> ProjectModel::_redoDel(const QModelIndex& index)
amuse::ObjToken<ProjectModel::INode> ProjectModel::_redoDel(const QModelIndex& index)
{
node(index)->depthTraverse([](INode* node)
{
@ -457,7 +458,7 @@ std::shared_ptr<ProjectModel::INode> ProjectModel::_redoDel(const QModelIndex& i
return true;
});
beginRemoveRows(index.parent(), index.row(), index.row());
std::shared_ptr<ProjectModel::INode> ret = node(index.parent())->removeChild(index.row());
amuse::ObjToken<ProjectModel::INode> ret = node(index.parent())->removeChild(index.row());
endRemoveRows();
return ret;
}

View File

@ -46,7 +46,7 @@ private:
std::map<QString, amuse::AudioGroupDatabase> m_groups;
public:
class INode : public std::enable_shared_from_this<INode>
class INode : public amuse::IObj
{
public:
enum class Type
@ -66,8 +66,8 @@ public:
};
protected:
INode* m_parent;
std::vector<std::shared_ptr<INode>> m_children;
std::unique_ptr<INode> m_nullChild;
std::vector<amuse::IObjToken<INode>> m_children;
amuse::IObjToken<INode> m_nullChild;
int m_row;
public:
virtual ~INode() = default;
@ -95,14 +95,14 @@ public:
m_nullChild->m_row = row;
}
void insertChild(int row, std::shared_ptr<INode>&& n)
void insertChild(int row, amuse::ObjToken<INode> n)
{
m_children.insert(m_children.begin() + row, std::move(n));
m_children.insert(m_children.begin() + row, n.get());
reindexRows(row);
}
std::shared_ptr<INode> removeChild(int row)
amuse::ObjToken<INode> removeChild(int row)
{
std::shared_ptr<INode> ret = std::move(m_children[row]);
amuse::ObjToken<INode> ret = m_children[row].get();
m_children.erase(m_children.begin() + row);
reindexRows(row);
return ret;
@ -112,7 +112,8 @@ public:
template<class T, class... _Args>
T& makeChild(_Args&&... args)
{
m_children.push_back(std::make_shared<T>(this, m_children.size(), std::forward<_Args>(args)...));
auto tok = amuse::MakeObj<T>(this, m_children.size(), std::forward<_Args>(args)...);
m_children.push_back(tok.get());
m_nullChild->m_row = int(m_children.size());
return static_cast<T&>(*m_children.back());
}
@ -161,9 +162,6 @@ public:
CollectionNode* getCollectionOfType(Type tp) const;
amuse::AudioGroupDatabase* getAudioGroup() const { return &m_it->second; }
std::shared_ptr<GroupNode> shared_from_this()
{ return std::static_pointer_cast<GroupNode>(INode::shared_from_this()); }
};
struct SongGroupNode : INode
{
@ -177,9 +175,6 @@ public:
Type type() const { return Type::SongGroup; }
QString text() const { return m_name; }
QIcon icon() const { return Icon; }
std::shared_ptr<SongGroupNode> shared_from_this()
{ return std::static_pointer_cast<SongGroupNode>(INode::shared_from_this()); }
};
struct SoundGroupNode : INode
{
@ -193,9 +188,6 @@ public:
Type type() const { return Type::SoundGroup; }
QString text() const { return m_name; }
QIcon icon() const { return Icon; }
std::shared_ptr<SoundGroupNode> shared_from_this()
{ return std::static_pointer_cast<SoundGroupNode>(INode::shared_from_this()); }
};
struct BasePoolObjectNode;
struct CollectionNode : INode
@ -215,9 +207,6 @@ public:
int indexOfId(amuse::ObjectId id) const;
amuse::ObjectId idOfIndex(int idx) const;
BasePoolObjectNode* nodeOfIndex(int idx) const;
std::shared_ptr<CollectionNode> shared_from_this()
{ return std::static_pointer_cast<CollectionNode>(INode::shared_from_this()); }
};
struct BasePoolObjectNode : INode
{
@ -237,9 +226,6 @@ public:
: BasePoolObjectNode(parent, row, id, ID::CurNameDB->resolveNameFromId(id).data()), m_obj(obj) {}
Type type() const { return TP; }
std::shared_ptr<PoolObjectNode<ID, T, TP>> shared_from_this()
{ return std::static_pointer_cast<PoolObjectNode<ID, T, TP>>(INode::shared_from_this()); }
};
using SoundMacroNode = PoolObjectNode<amuse::SoundMacroId, amuse::SoundMacro, INode::Type::SoundMacro>;
using ADSRNode = PoolObjectNode<amuse::TableId, std::unique_ptr<amuse::ITable>, INode::Type::ADSR>;
@ -248,7 +234,7 @@ public:
using LayersNode = PoolObjectNode<amuse::LayersId, std::vector<amuse::LayerMapping>, INode::Type::Layer>;
using SampleNode = PoolObjectNode<amuse::SampleId, amuse::SampleEntry, INode::Type::Sample>;
std::shared_ptr<RootNode> m_root;
amuse::ObjToken<RootNode> m_root;
bool m_needsReset = false;
void _resetModelData();
@ -276,8 +262,8 @@ public:
INode* node(const QModelIndex& index) const;
GroupNode* getGroupNode(INode* node) const;
bool canEdit(const QModelIndex& index) const;
void _undoDel(const QModelIndex& index, std::shared_ptr<ProjectModel::INode>&& node);
std::shared_ptr<ProjectModel::INode> _redoDel(const QModelIndex& index);
void _undoDel(const QModelIndex& index, amuse::ObjToken<ProjectModel::INode> node);
amuse::ObjToken<ProjectModel::INode> _redoDel(const QModelIndex& index);
void del(const QModelIndex& index);
QString path() const { return m_dir.path(); }

View File

@ -1,12 +1,213 @@
#include "SampleEditor.hpp"
#include "MainWindow.hpp"
#include "amuse/DSPCodec.hpp"
#include <QPainter>
#include <QPaintEvent>
void SampleView::seekToSample(qreal sample)
{
sample = std::min(sample, qreal(m_sample->getNumSamples()));
if (m_sample->isFormatDSP())
{
if (sample < m_curSamplePos)
{
m_prev1 = m_prev2 = 0;
m_curSamplePos = 0.0;
}
uint32_t startBlock = uint32_t(m_curSamplePos) / 14;
uint32_t startRem = uint32_t(m_curSamplePos) % 14;
uint32_t endBlock = uint32_t(sample) / 14;
uint32_t endRem = uint32_t(sample) % 14;
if (startRem)
DSPDecompressFrameRangedStateOnly(m_sampleData + 8 * startBlock, m_sample->m_ADPCMParms.dsp.m_coefs,
&m_prev1, &m_prev2, startRem, startBlock == endBlock ? endRem : 14);
for (uint32_t b = startBlock; b < endBlock; ++b)
DSPDecompressFrameStateOnly(m_sampleData + 8 * b, m_sample->m_ADPCMParms.dsp.m_coefs,
&m_prev1, &m_prev2, 14);
if (endRem)
DSPDecompressFrameStateOnly(m_sampleData + 8 * endBlock, m_sample->m_ADPCMParms.dsp.m_coefs,
&m_prev1, &m_prev2, endRem);
}
m_curSamplePos = sample;
}
std::pair<std::pair<qreal, qreal>, std::pair<qreal, qreal>> SampleView::iterateSampleInterval(qreal interval)
{
std::pair<std::pair<qreal, qreal>, std::pair<qreal, qreal>> ret = {{0.0, 1.0}, {0.0, -1.0}}; // min,max avg,peak
qreal avg = 0.0;
qreal div = 0.0;
qreal endSample = std::min(m_curSamplePos + interval, qreal(m_sample->getNumSamples()));
auto accumulate = [&ret, &avg, &div](int16_t sample)
{
qreal sampleF = sample / 32768.0;
avg += sampleF;
div += 1.0;
ret.first.second = std::min(ret.first.second, sampleF);
ret.second.second = std::max(ret.second.second, sampleF);
};
if (m_sample->isFormatDSP())
{
uint32_t startBlock = uint32_t(m_curSamplePos) / 14;
uint32_t startRem = uint32_t(m_curSamplePos) % 14;
uint32_t endBlock = uint32_t(endSample) / 14;
uint32_t endRem = uint32_t(endSample) % 14;
int16_t sampleBlock[14];
if (startRem)
{
uint32_t end = startBlock == endBlock ? endRem : 14;
DSPDecompressFrameRanged(sampleBlock, m_sampleData + 8 * startBlock, m_sample->m_ADPCMParms.dsp.m_coefs,
&m_prev1, &m_prev2, startRem, end);
for (int s = 0; s < end - startRem; ++s)
accumulate(sampleBlock[s]);
}
for (uint32_t b = startBlock; b < endBlock; ++b)
{
DSPDecompressFrame(sampleBlock, m_sampleData + 8 * b, m_sample->m_ADPCMParms.dsp.m_coefs,
&m_prev1, &m_prev2, 14);
for (int s = 0; s < 14; ++s)
accumulate(sampleBlock[s]);
}
if (endRem)
{
DSPDecompressFrame(sampleBlock, m_sampleData + 8 * endBlock, m_sample->m_ADPCMParms.dsp.m_coefs,
&m_prev1, &m_prev2, endRem);
for (int s = 0; s < endRem; ++s)
accumulate(sampleBlock[s]);
}
}
else if (m_sample->getSampleFormat() == amuse::SampleFormat::PCM_PC)
{
for (uint32_t s = uint32_t(m_curSamplePos); s < uint32_t(endSample); ++s)
accumulate(reinterpret_cast<const int16_t*>(m_sampleData)[s]);
}
m_curSamplePos = endSample;
if (div == 0.0)
return {};
avg /= div;
if (avg > 0.0)
{
ret.first.first = ret.first.second;
ret.second.first = avg;
}
else
{
ret.first.first = avg;
ret.second.first = ret.second.second;
}
return ret;
}
void SampleView::paintEvent(QPaintEvent* ev)
{
QPainter painter(this);
if (m_sample)
{
m_samplesPerPx = m_sample->getNumSamples() / (width() * devicePixelRatioF());
qreal rectStart = ev->rect().x();
qreal startSample = rectStart * m_samplesPerPx;
qreal deviceWidth = ev->rect().width() * devicePixelRatioF();
qreal increment = 1.0 / devicePixelRatioF();
qreal deviceSamplesPerPx = m_samplesPerPx / devicePixelRatioF();
QPen peakPen(QColor(255, 147, 41), increment);
QPen avgPen(QColor(254, 177, 68), increment);
qreal scale = -height() / 2.0;
qreal trans = height() / 2.0;
seekToSample(startSample);
for (qreal i = 0.0; i < deviceWidth; i += increment)
{
if (m_curSamplePos + deviceSamplesPerPx > m_sample->getNumSamples())
break;
auto avgPeak = iterateSampleInterval(deviceSamplesPerPx);
painter.setPen(peakPen);
painter.drawLine(QPointF(rectStart + i, avgPeak.first.second * scale + trans),
QPointF(rectStart + i, avgPeak.second.second * scale + trans));
painter.setPen(avgPen);
painter.drawLine(QPointF(rectStart + i, avgPeak.first.first * scale + trans),
QPointF(rectStart + i, avgPeak.second.first * scale + trans));
}
}
}
void SampleView::mousePressEvent(QMouseEvent* ev)
{
}
void SampleView::mouseReleaseEvent(QMouseEvent* ev)
{
}
void SampleView::mouseMoveEvent(QMouseEvent* ev)
{
}
void SampleView::loadData(ProjectModel::SampleNode* 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());
update();
}
void SampleView::unloadData()
{
m_sample.reset();
update();
}
ProjectModel::INode* SampleView::currentNode() const
{
return m_node.get();
}
SampleView::SampleView(QWidget* parent)
: QWidget(parent)
{}
bool SampleEditor::loadData(ProjectModel::SampleNode* node)
{
return false;
m_sampleView->loadData(node);
return true;
}
void SampleEditor::unloadData()
{
m_sampleView->unloadData();
}
ProjectModel::INode* SampleEditor::currentNode() const
{
return m_sampleView->currentNode();
}
SampleEditor::SampleEditor(QWidget* parent)
: EditorWidget(parent)
: EditorWidget(parent), m_sampleView(new SampleView)
{
QVBoxLayout* layout = new QVBoxLayout;
layout->addWidget(m_sampleView);
setLayout(layout);
}

View File

@ -2,13 +2,41 @@
#define AMUSE_SAMPLE_EDITOR_HPP
#include "EditorWidget.hpp"
#include "ProjectModel.hpp"
class SampleView : public QWidget
{
Q_OBJECT
qreal m_samplesPerPx = 100.0;
amuse::ObjToken<ProjectModel::SampleNode> m_node;
amuse::ObjToken<amuse::SampleEntryData> m_sample;
const unsigned char* m_sampleData = nullptr;
qreal m_curSamplePos = 0.0;
int16_t m_prev1 = 0;
int16_t m_prev2 = 0;
void seekToSample(qreal sample);
std::pair<std::pair<qreal, qreal>, std::pair<qreal, qreal>> iterateSampleInterval(qreal interval);
public:
explicit SampleView(QWidget* parent = Q_NULLPTR);
void loadData(ProjectModel::SampleNode* node);
void unloadData();
ProjectModel::INode* currentNode() const;
void paintEvent(QPaintEvent* ev);
void mousePressEvent(QMouseEvent* ev);
void mouseReleaseEvent(QMouseEvent* ev);
void mouseMoveEvent(QMouseEvent* ev);
};
class SampleEditor : public EditorWidget
{
Q_OBJECT
SampleView* m_sampleView;
public:
explicit SampleEditor(QWidget* parent = Q_NULLPTR);
bool loadData(ProjectModel::SampleNode* node);
void unloadData();
ProjectModel::INode* currentNode() const;
};

View File

@ -321,8 +321,8 @@ class ValChangedUndoCommand : public EditorUndoCommand
public:
ValChangedUndoCommand(amuse::SoundMacro::ICmd* cmd, const QString& fieldName,
const amuse::SoundMacro::CmdIntrospection::Field& field,
int redoVal, std::shared_ptr<ProjectModel::SoundMacroNode> node)
: EditorUndoCommand(node, QUndoStack::tr("Change %1").arg(fieldName)),
int redoVal, amuse::ObjToken<ProjectModel::SoundMacroNode> node)
: EditorUndoCommand(node.get(), QUndoStack::tr("Change %1").arg(fieldName)),
m_cmd(cmd), m_field(field), m_redoVal(redoVal) {}
void undo()
{
@ -434,6 +434,8 @@ void CommandWidget::boolChanged(int state)
void CommandWidget::numChanged(int value)
{
if (value < 0)
return;
if (m_introspection)
{
const amuse::SoundMacro::CmdIntrospection::Field& field =
@ -445,6 +447,8 @@ void CommandWidget::numChanged(int value)
void CommandWidget::nodeChanged(int value)
{
if (value < 0)
return;
if (m_introspection)
{
FieldProjectNode* fieldW = static_cast<FieldProjectNode*>(sender());
@ -643,8 +647,8 @@ class ReorderCommandsUndoCommand : public EditorUndoCommand
int m_a, m_b;
bool m_undid = false;
public:
ReorderCommandsUndoCommand(int a, int b, const QString& text, std::shared_ptr<ProjectModel::SoundMacroNode> node)
: EditorUndoCommand(node, QUndoStack::tr("Reorder %1").arg(text)), m_a(a), m_b(b) {}
ReorderCommandsUndoCommand(int a, int b, const QString& text, amuse::ObjToken<ProjectModel::SoundMacroNode> node)
: EditorUndoCommand(node.get(), QUndoStack::tr("Reorder %1").arg(text)), m_a(a), m_b(b) {}
void undo()
{
m_undid = true;
@ -777,8 +781,8 @@ class InsertCommandUndoCommand : public EditorUndoCommand
int m_insertIdx;
std::unique_ptr<amuse::SoundMacro::ICmd> m_cmd;
public:
InsertCommandUndoCommand(int insertIdx, const QString& text, std::shared_ptr<ProjectModel::SoundMacroNode> node)
: EditorUndoCommand(node, QUndoStack::tr("Insert %1").arg(text)), m_insertIdx(insertIdx) {}
InsertCommandUndoCommand(int insertIdx, const QString& text, amuse::ObjToken<ProjectModel::SoundMacroNode> node)
: EditorUndoCommand(node.get(), QUndoStack::tr("Insert %1").arg(text)), m_insertIdx(insertIdx) {}
void undo()
{
m_cmd = static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
@ -832,8 +836,8 @@ class DeleteCommandUndoCommand : public EditorUndoCommand
std::unique_ptr<amuse::SoundMacro::ICmd> m_cmd;
bool m_undid = false;
public:
DeleteCommandUndoCommand(int deleteIdx, const QString& text, std::shared_ptr<ProjectModel::SoundMacroNode> node)
: EditorUndoCommand(node, QUndoStack::tr("Delete %1").arg(text)), m_deleteIdx(deleteIdx) {}
DeleteCommandUndoCommand(int deleteIdx, const QString& text, amuse::ObjToken<ProjectModel::SoundMacroNode> node)
: EditorUndoCommand(node.get(), QUndoStack::tr("Delete %1").arg(text)), m_deleteIdx(deleteIdx) {}
void undo()
{
m_undid = true;
@ -881,7 +885,7 @@ void SoundMacroListing::clear()
bool SoundMacroListing::loadData(ProjectModel::SoundMacroNode* node)
{
m_node = node->shared_from_this();
m_node = node;
clear();
int i = 0;
for (auto& cmd : node->m_obj->m_cmds)

View File

@ -127,7 +127,7 @@ class SoundMacroListing : public QWidget
Q_OBJECT
friend class CommandWidget;
friend class SoundMacroEditor;
std::shared_ptr<ProjectModel::SoundMacroNode> m_node;
amuse::ObjToken<ProjectModel::SoundMacroNode> m_node;
QVBoxLayout* m_layout;
QLayoutItem* m_dragItem = nullptr;
int m_origIdx = -1;

View File

@ -220,22 +220,22 @@
<translation>Keine MIDI-Geräte gefunden</translation>
</message>
<message>
<location line="+201"/>
<location line="+202"/>
<source>New Project</source>
<translation>Neues Projekt</translation>
</message>
<message>
<location line="+60"/>
<location line="+63"/>
<source>Open Project</source>
<translation>Offenes Projekt</translation>
</message>
<message>
<location line="-37"/>
<location line="-40"/>
<source>The directory at &apos;%1&apos; does not exist.</source>
<translation>Das Verzeichnis &apos;% 1&apos; existiert nicht.</translation>
</message>
<message>
<location line="-468"/>
<location line="-469"/>
<source>Clear Recent Projects</source>
<translation type="unfinished"></translation>
</message>
@ -251,46 +251,46 @@
</message>
<message>
<location line="+31"/>
<location line="+302"/>
<location line="+303"/>
<source>The directory at &apos;%1&apos; must not be empty.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="-301"/>
<location line="+302"/>
<location line="-302"/>
<location line="+303"/>
<source>Directory empty</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="-189"/>
<location line="-190"/>
<source>SUSTAIN</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="+195"/>
<location line="+196"/>
<source>Bad Directory</source>
<translation>Schlechtes Verzeichnis</translation>
</message>
<message>
<location line="+12"/>
<location line="+15"/>
<source>Opening</source>
<translation>Öffnung</translation>
</message>
<message>
<location line="+0"/>
<location line="+76"/>
<location line="+78"/>
<location line="+83"/>
<location line="+45"/>
<source>Scanning Project</source>
<translation>Projekt scannen</translation>
</message>
<message>
<location line="-192"/>
<location line="-194"/>
<source>Opening %1</source>
<translation>Eröffnung% 1</translation>
</message>
<message>
<location line="+64"/>
<location line="+66"/>
<source>Reloading Samples</source>
<translation type="unfinished"></translation>
</message>
@ -391,7 +391,7 @@
<context>
<name>ProjectModel</name>
<message>
<location filename="../ProjectModel.cpp" line="+226"/>
<location filename="../ProjectModel.cpp" line="+227"/>
<source>Sound Macros</source>
<translation>Sound-Makros</translation>
</message>
@ -442,7 +442,7 @@
<translation type="unfinished"></translation>
</message>
<message>
<location line="+322"/>
<location line="+326"/>
<source>Reorder %1</source>
<translation type="unfinished"></translation>
</message>
@ -542,7 +542,7 @@
<context>
<name>TargetButton</name>
<message>
<location filename="../SoundMacroEditor.cpp" line="-944"/>
<location filename="../SoundMacroEditor.cpp" line="-948"/>
<source>Set step with target click</source>
<translation type="unfinished"></translation>
</message>

View File

@ -188,7 +188,7 @@ public:
{
atUint32 m_sampleOff = 0;
atUint32 m_unk = 0;
atUint8 m_pitch = 60;
atUint8 m_pitch = 0;
atUint16 m_sampleRate = 0;
atUint32 m_numSamples = 0; // Top 8 bits is SampleFormat
atUint32 m_loopStartSample = 0;
@ -204,6 +204,16 @@ public:
time_t m_looseModTime = 0;
std::unique_ptr<uint8_t[]> m_looseData;
/* Use middle C when pitch is (impossibly low) default */
atUint8 getPitch() const { return m_pitch == 0 ? atUint8(60) : m_pitch; }
atUint32 getNumSamples() const { return m_numSamples & 0xffffff; }
SampleFormat getSampleFormat() const { return SampleFormat(m_numSamples >> 24); }
bool isFormatDSP() const
{
SampleFormat fmt = getSampleFormat();
return fmt == SampleFormat::DSP || fmt == SampleFormat::DSP_DRUM;
}
EntryData() = default;
template <athena::Endian DNAE>

View File

@ -150,45 +150,94 @@ public:
};
template<class SubCls>
class ObjToken;
template<class SubCls>
class ObjWrapper : IObj
class ObjWrapper : public IObj
{
friend class ObjToken<SubCls>;
SubCls m_obj;
SubCls* get() { return &m_obj; }
const SubCls* get() const { return &m_obj; }
public:
template <class... _Args>
ObjWrapper(_Args&&... args) : m_obj(std::forward<_Args>(args)...) {}
SubCls* get() { return &m_obj; }
const SubCls* get() const { return &m_obj; }
};
template<class SubCls>
class ObjToken
class ObjTokenBase
{
ObjWrapper<SubCls>* m_obj = nullptr;
protected:
IObj* m_obj = nullptr;
ObjTokenBase(IObj* obj) : m_obj(obj) { if (m_obj) m_obj->increment(); }
public:
ObjToken() = default;
ObjToken(ObjWrapper<SubCls>* obj) : m_obj(obj) { if (m_obj) m_obj->increment(); }
ObjToken(const ObjToken& other) : m_obj(other.m_obj) { if (m_obj) m_obj->increment(); }
ObjToken(ObjToken&& other) : m_obj(other.m_obj) { other.m_obj = nullptr; }
ObjToken& operator=(ObjWrapper<SubCls>* obj)
{ if (m_obj) m_obj->decrement(); m_obj = obj; if (m_obj) m_obj->increment(); return *this; }
ObjToken& operator=(const ObjToken& other)
ObjTokenBase() = default;
ObjTokenBase(const ObjTokenBase& other) : m_obj(other.m_obj) { if (m_obj) m_obj->increment(); }
ObjTokenBase(ObjTokenBase&& other) : m_obj(other.m_obj) { other.m_obj = nullptr; }
ObjTokenBase& operator=(const ObjTokenBase& other)
{ if (m_obj) m_obj->decrement(); m_obj = other.m_obj; if (m_obj) m_obj->increment(); return *this; }
ObjToken& operator=(ObjToken&& other)
ObjTokenBase& operator=(ObjTokenBase&& other)
{ if (m_obj) m_obj->decrement(); m_obj = other.m_obj; other.m_obj = nullptr; return *this; }
~ObjToken() { if (m_obj) m_obj->decrement(); }
SubCls* get() const { return m_obj->get(); }
SubCls* operator->() const { return m_obj->get(); }
SubCls& operator*() const { return *m_obj->get(); }
~ObjTokenBase() { if (m_obj) m_obj->decrement(); }
operator bool() const { return m_obj != nullptr; }
void reset() { if (m_obj) m_obj->decrement(); m_obj = nullptr; }
};
template<class SubCls, class Enable = void>
class ObjToken : public ObjTokenBase<SubCls>
{
IObj*& _obj() { return ObjTokenBase<SubCls>::m_obj; }
IObj*const& _obj() const { return ObjTokenBase<SubCls>::m_obj; }
public:
using ObjTokenBase<SubCls>::ObjTokenBase;
ObjToken(ObjWrapper<SubCls>* obj) : ObjTokenBase<SubCls>(obj) {}
ObjToken& operator=(ObjWrapper<SubCls>* obj)
{ if (_obj()) _obj()->decrement(); _obj() = obj; if (_obj()) _obj()->increment(); return *this; }
SubCls* get() const { return static_cast<ObjWrapper<SubCls>*>(_obj())->get(); }
SubCls* operator->() const { return get(); }
SubCls& operator*() const { return *get(); }
};
template<class SubCls>
class ObjToken<SubCls, typename std::enable_if_t<std::is_base_of_v<IObj, SubCls>>> : public ObjTokenBase<SubCls>
{
IObj*& _obj() { return ObjTokenBase<SubCls>::m_obj; }
IObj*const& _obj() const { return ObjTokenBase<SubCls>::m_obj; }
public:
using ObjTokenBase<SubCls>::ObjTokenBase;
ObjToken(IObj* obj) : ObjTokenBase<SubCls>(obj) {}
ObjToken& operator=(IObj* obj)
{ if (_obj()) _obj()->decrement(); _obj() = obj; if (_obj()) _obj()->increment(); return *this; }
SubCls* get() const { return static_cast<SubCls*>(_obj()); }
SubCls* operator->() const { return get(); }
SubCls& operator*() const { return *get(); }
template <class T>
T* cast() const { return static_cast<T*>(_obj()); }
};
/* ONLY USE WITH CLASSES DERIVED FROM IOBJ!
* Bypasses type_traits tests for incomplete type definitions. */
template<class SubCls>
class IObjToken : public ObjTokenBase<SubCls>
{
IObj*& _obj() { return ObjTokenBase<SubCls>::m_obj; }
IObj*const& _obj() const { return ObjTokenBase<SubCls>::m_obj; }
public:
using ObjTokenBase<SubCls>::ObjTokenBase;
IObjToken(IObj* obj) : ObjTokenBase<SubCls>(obj) {}
IObjToken& operator=(IObj* obj)
{ if (_obj()) _obj()->decrement(); _obj() = obj; if (_obj()) _obj()->increment(); return *this; }
SubCls* get() const { return static_cast<SubCls*>(_obj()); }
SubCls* operator->() const { return get(); }
SubCls& operator*() const { return *get(); }
template <class T>
T* cast() const { return static_cast<T*>(_obj()); }
};
template <class Tp, class... _Args>
static inline ObjToken<Tp> MakeObj(_Args&&... args)
inline typename std::enable_if_t<std::is_base_of_v<IObj, Tp>, ObjToken<Tp>> MakeObj(_Args&&... args)
{
return new Tp(std::forward<_Args>(args)...);
}
template <class Tp, class... _Args>
inline typename std::enable_if_t<!std::is_base_of_v<IObj, Tp>, ObjToken<Tp>> MakeObj(_Args&&... args)
{
return new ObjWrapper<Tp>(std::forward<_Args>(args)...);
}

View File

@ -28,4 +28,8 @@ unsigned DSPDecompressFrameStateOnly(const uint8_t* in,
const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2,
unsigned lastSample);
unsigned DSPDecompressFrameRangedStateOnly(const uint8_t* in,
const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2,
unsigned firstSample, unsigned lastSample);
#endif // _DSPCODEC_h

View File

@ -9,6 +9,7 @@
namespace amuse
{
struct StudioSend;
class Studio
{
@ -17,17 +18,7 @@ class Studio
Submix m_master;
Submix m_auxA;
Submix m_auxB;
struct StudioSend
{
ObjToken<Studio> m_targetStudio;
float m_dryLevel;
float m_auxALevel;
float m_auxBLevel;
StudioSend(ObjToken<Studio> studio, float dry, float auxA, float auxB)
: m_targetStudio(studio), m_dryLevel(dry), m_auxALevel(auxA), m_auxBLevel(auxB)
{
}
};
std::list<StudioSend> m_studiosOut;
#ifndef NDEBUG
bool _cyclicCheck(Studio* leaf);
@ -48,6 +39,19 @@ public:
Engine& getEngine() { return m_engine; }
};
struct StudioSend
{
ObjToken<Studio> m_targetStudio;
float m_dryLevel;
float m_auxALevel;
float m_auxBLevel;
StudioSend(ObjToken<Studio> studio, float dry, float auxA, float auxB)
: m_targetStudio(studio), m_dryLevel(dry), m_auxALevel(auxA), m_auxBLevel(auxB)
{
}
};
}
#endif // __AMUSE_STUDIO_HPP__

View File

@ -157,8 +157,6 @@ void AudioGroupSampleDirectory::EntryData::loadLooseDSP(SystemStringView dspPath
DSPADPCMHeader header;
header.read(r);
m_pitch = header.m_pitch;
if (m_pitch == 0)
m_pitch = 60;
m_sampleRate = atUint16(header.x8_sample_rate);
m_numSamples = header.x0_num_samples;
if (header.xc_loop_flag)
@ -209,8 +207,6 @@ void AudioGroupSampleDirectory::EntryData::loadLooseWAV(SystemStringView wavPath
WAVSampleChunk smpl;
smpl.read(r);
m_pitch = atUint8(smpl.midiNote);
if (m_pitch == 0)
m_pitch = 60;
if (smpl.numSampleLoops)
{
@ -415,7 +411,7 @@ void AudioGroupSampleDirectory::extractAllWAV(amuse::SystemStringView destDir, c
void AudioGroupSampleDirectory::_extractCompressed(SampleId id, const EntryData& ent,
amuse::SystemStringView destDir, const unsigned char* samp)
{
SampleFormat fmt = SampleFormat(ent.m_numSamples >> 24);
SampleFormat fmt = ent.getSampleFormat();
if (fmt == SampleFormat::PCM || fmt == SampleFormat::PCM_PC)
{
_extractWAV(id, ent, destDir, samp);
@ -430,7 +426,7 @@ void AudioGroupSampleDirectory::_extractCompressed(SampleId id, const EntryData&
path += SampleId::CurNameDB->resolveNameFromId(id);
#endif
uint32_t numSamples = ent.m_numSamples & 0xffffff;
uint32_t numSamples = ent.getNumSamples();
atUint64 dataLen;
if (fmt == SampleFormat::DSP || fmt == SampleFormat::DSP_DRUM)
{

View File

@ -151,3 +151,32 @@ unsigned DSPDecompressFrameStateOnly(const uint8_t* in,
}
return ret;
}
unsigned DSPDecompressFrameRangedStateOnly(const uint8_t* in,
const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2,
unsigned firstSample, unsigned lastSample)
{
uint8_t cIdx = (in[0]>>4) & 0xf;
int32_t factor1 = coefs[cIdx][0];
int32_t factor2 = coefs[cIdx][1];
uint8_t exp = in[0] & 0xf;
unsigned ret = 0;
for (unsigned s=firstSample ; s<14 && s<lastSample ; ++s)
{
int32_t sampleData = (s&1)?
NibbleToInt[(in[s/2+1])&0xf]:
NibbleToInt[(in[s/2+1]>>4)&0xf];
sampleData <<= exp;
sampleData <<= 11;
sampleData += 1024;
sampleData +=
factor1 * ((int32_t)(*prev1)) +
factor2 * ((int32_t)(*prev2));
sampleData >>= 11;
sampleData = DSPSampClamp(sampleData);
*prev2 = *prev1;
*prev1 = sampleData;
++ret;
}
return ret;
}

View File

@ -76,15 +76,15 @@ std::list<ObjToken<Sequencer>>::iterator Engine::_allocateSequencer(const AudioG
const SongGroupIndex* songGroup = group.getProj().getSongGroupIndex(groupId);
if (songGroup)
{
auto it = m_activeSequencers.emplace(m_activeSequencers.end(),
MakeObj<Sequencer>(*this, group, groupId, songGroup, setupId, studio));
amuse::ObjToken<Sequencer> tok = MakeObj<Sequencer>(*this, group, groupId, songGroup, setupId, studio);
auto it = m_activeSequencers.emplace(m_activeSequencers.end(), tok);
return it;
}
const SFXGroupIndex* sfxGroup = group.getProj().getSFXGroupIndex(groupId);
if (sfxGroup)
{
auto it = m_activeSequencers.emplace(m_activeSequencers.end(),
MakeObj<Sequencer>(*this, group, groupId, sfxGroup, studio));
amuse::ObjToken<Sequencer> tok = MakeObj<Sequencer>(*this, group, groupId, sfxGroup, studio);
auto it = m_activeSequencers.emplace(m_activeSequencers.end(), tok);
return it;
}
return {};
@ -306,6 +306,8 @@ ObjToken<Voice> Engine::macroStart(const AudioGroup* group, SoundMacroId id, uin
return {};
}
(*ret)->setVolume(1.f);
(*ret)->setPan(0.f);
return *ret;
}

View File

@ -215,7 +215,7 @@ ObjToken<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
if (m_parent->m_songGroup && !m_page)
return {};
if (m_lastVoice->isDestroyed())
if (m_lastVoice && m_lastVoice->isDestroyed())
m_lastVoice.reset();
/* If portamento is enabled for voice, pre-empt spawning new voices */
@ -304,7 +304,7 @@ void Sequencer::ChannelState::keyOff(uint8_t note, uint8_t velocity)
if (keySearch == m_chanVoxs.cend())
return;
if (m_lastVoice->isDestroyed() || keySearch->second == m_lastVoice)
if ((m_lastVoice && m_lastVoice->isDestroyed()) || keySearch->second == m_lastVoice)
m_lastVoice.reset();
keySearch->second->keyOff();
m_keyoffVoxs.emplace(std::move(keySearch->second));
@ -428,7 +428,7 @@ void Sequencer::setTempo(double ticksPerSec) { m_ticksPerSec = ticksPerSec; }
void Sequencer::ChannelState::allOff()
{
if (m_lastVoice->isDestroyed())
if (m_lastVoice && m_lastVoice->isDestroyed())
m_lastVoice.reset();
for (auto it = m_chanVoxs.begin(); it != m_chanVoxs.end();)
{
@ -481,7 +481,7 @@ void Sequencer::allOff(uint8_t chan, bool now)
void Sequencer::ChannelState::killKeygroup(uint8_t kg, bool now)
{
if (m_lastVoice->isDestroyed())
if (m_lastVoice && m_lastVoice->isDestroyed())
m_lastVoice.reset();
for (auto it = m_chanVoxs.begin(); it != m_chanVoxs.end();)

View File

@ -40,25 +40,25 @@ float SoundMacroState::Evaluator::evaluate(double time, const Voice& vox, const
{
case 128:
/* Pitchbend */
thisValue = vox.getPitchWheel();
thisValue = (vox.getPitchWheel() * 0.5f + 0.5f) * 127.f;
break;
case 129:
/* Aftertouch */
thisValue = vox.getAftertouch() * (2.f / 127.f);
thisValue = vox.getAftertouch();
break;
case 130:
/* LFO1 */
if (vox.m_lfoPeriods[0])
thisValue = std::sin(time / vox.m_lfoPeriods[0] * 2.f * M_PIF);
thisValue = (std::sin(time / vox.m_lfoPeriods[0] * 2.f * M_PIF) * 0.5f + 0.5f) * 127.f;
break;
case 131:
/* LFO2 */
if (vox.m_lfoPeriods[1])
thisValue = std::sin(time / vox.m_lfoPeriods[1] * 2.f * M_PIF);
thisValue = (std::sin(time / vox.m_lfoPeriods[1] * 2.f * M_PIF) * 0.5f + 0.5f) * 127.f;
break;
case 132:
/* Surround panning */
thisValue = vox.m_curSpan;
thisValue = (vox.m_curSpan * 0.5f + 0.5f) * 127.f;
break;
case 133:
/* Macro-starting key */
@ -73,10 +73,7 @@ float SoundMacroState::Evaluator::evaluate(double time, const Voice& vox, const
thisValue = clamp(0.f, float(st.m_execTime * 1000.f), 16383.f);
break;
default:
if (comp.m_midiCtrl == 10) /* Centered pan computation */
thisValue = vox.getCtrlValue(comp.m_midiCtrl) * (2.f / 127.f) - 1.f;
else
thisValue = vox.getCtrlValue(comp.m_midiCtrl) * (2.f / 127.f);
thisValue = vox.getCtrlValue(comp.m_midiCtrl);
break;
}
}

View File

@ -119,7 +119,7 @@ void Voice::_doKeyOff()
void Voice::_setTotalPitch(int32_t cents, bool slew)
{
// fprintf(stderr, "PITCH %d %d \n", cents, slew);
int32_t interval = clamp(0, cents, 12700) - m_curSample->m_pitch * 100;
int32_t interval = clamp(0, cents, 12700) - m_curSample->getPitch() * 100;
double ratio = std::exp2(interval / 1200.0) * m_dopplerRatio;
m_sampleRate = m_curSample->m_sampleRate * ratio;
m_backendVoice->setPitchRatio(ratio, slew);
@ -176,8 +176,9 @@ std::unique_ptr<int8_t[]>& Voice::_ensureCtrlVals()
std::list<ObjToken<Voice>>::iterator Voice::_allocateVoice(double sampleRate, bool dynamicPitch)
{
auto it = m_childVoices.emplace(
m_childVoices.end(), MakeObj<Voice>(m_engine, m_audioGroup, m_groupId, m_engine.m_nextVid++, m_emitter, m_studio));
amuse::ObjToken<Voice> tok =
MakeObj<Voice>(m_engine, m_audioGroup, m_groupId, m_engine.m_nextVid++, m_emitter, m_studio);
auto it = m_childVoices.emplace(m_childVoices.end(), tok);
m_childVoices.back()->m_backendVoice =
m_engine.getBackend().allocateVoice(*m_childVoices.back(), sampleRate, dynamicPitch);
return it;
@ -290,11 +291,11 @@ void Voice::_procSamplePre(int16_t& samp)
/* Apply tremolo */
if (m_state.m_tremoloSel && (m_tremoloScale || m_tremoloModScale))
{
float t = m_state.m_tremoloSel.evaluate(m_voiceTime, *this, m_state) / 2.f;
float t = m_state.m_tremoloSel.evaluate(m_voiceTime, *this, m_state) / 127.f;
if (m_tremoloScale && m_tremoloModScale)
{
float fac = (1.0f - t) + (m_tremoloScale * t);
float modT = m_state.m_modWheelSel ? (m_state.m_modWheelSel.evaluate(m_voiceTime, *this, m_state) / 2.f)
float modT = m_state.m_modWheelSel ? (m_state.m_modWheelSel.evaluate(m_voiceTime, *this, m_state) / 127.f)
: (getCtrlValue(1) / 127.f);
float modFac = (1.0f - modT) + (m_tremoloModScale * modT);
m_nextLevel *= fac * modFac;
@ -306,7 +307,7 @@ void Voice::_procSamplePre(int16_t& samp)
}
else if (m_tremoloModScale)
{
float modT = m_state.m_modWheelSel ? (m_state.m_modWheelSel.evaluate(m_voiceTime, *this, m_state) / 2.f)
float modT = m_state.m_modWheelSel ? (m_state.m_modWheelSel.evaluate(m_voiceTime, *this, m_state) / 127.f)
: (getCtrlValue(1) / 127.f);
float modFac = (1.0f - modT) + (m_tremoloModScale * modT);
m_nextLevel *= modFac;
@ -322,25 +323,25 @@ void Voice::_procSamplePre(int16_t& samp)
template <typename T>
T Voice::_procSampleMaster(double time, T samp)
{
float evalVol = m_state.m_volumeSel ? (m_state.m_volumeSel.evaluate(time, *this, m_state) / 2.f) : 1.f;
float evalVol = m_state.m_volumeSel ? (m_state.m_volumeSel.evaluate(time, *this, m_state) / 127.f) : 1.f;
return ApplyVolume(clamp(0.f, evalVol, 1.f), samp);
}
template <typename T>
T Voice::_procSampleAuxA(double time, T samp)
{
float evalVol = m_state.m_volumeSel ? (m_state.m_volumeSel.evaluate(time, *this, m_state) / 2.f) : 1.f;
evalVol *= m_state.m_reverbSel ? (m_state.m_reverbSel.evaluate(time, *this, m_state) / 2.f) : m_curReverbVol;
evalVol += m_state.m_preAuxASel ? (m_state.m_preAuxASel.evaluate(time, *this, m_state) / 2.f) : 0.f;
float evalVol = m_state.m_volumeSel ? (m_state.m_volumeSel.evaluate(time, *this, m_state) / 127.f) : 1.f;
evalVol *= m_state.m_reverbSel ? (m_state.m_reverbSel.evaluate(time, *this, m_state) / 127.f) : m_curReverbVol;
evalVol += m_state.m_preAuxASel ? (m_state.m_preAuxASel.evaluate(time, *this, m_state) / 127.f) : 0.f;
return ApplyVolume(clamp(0.f, evalVol, 1.f), samp);
}
template <typename T>
T Voice::_procSampleAuxB(double time, T samp)
{
float evalVol = m_state.m_volumeSel ? (m_state.m_volumeSel.evaluate(time, *this, m_state) / 2.f) : 1.f;
evalVol *= m_state.m_postAuxB ? (m_state.m_postAuxB.evaluate(time, *this, m_state) / 2.f) : m_curAuxBVol;
evalVol += m_state.m_preAuxBSel ? (m_state.m_preAuxBSel.evaluate(time, *this, m_state) / 2.f) : 0.f;
float evalVol = m_state.m_volumeSel ? (m_state.m_volumeSel.evaluate(time, *this, m_state) / 127.f) : 1.f;
evalVol *= m_state.m_postAuxB ? (m_state.m_postAuxB.evaluate(time, *this, m_state) / 127.f) : m_curAuxBVol;
evalVol += m_state.m_preAuxBSel ? (m_state.m_preAuxBSel.evaluate(time, *this, m_state) / 127.f) : 0.f;
return ApplyVolume(clamp(0.f, evalVol, 1.f), samp);
}
@ -376,7 +377,7 @@ void Voice::preSupplyAudio(double dt)
/* Process per-block evaluators here */
if (m_state.m_pedalSel)
{
bool pedal = m_state.m_pedalSel.evaluate(m_voiceTime, *this, m_state) >= 1.f;
bool pedal = m_state.m_pedalSel.evaluate(m_voiceTime, *this, m_state) >= 64.f;
if (pedal != m_sustained)
setPedal(pedal);
}
@ -384,7 +385,8 @@ void Voice::preSupplyAudio(double dt)
bool panDirty = false;
if (m_state.m_panSel)
{
float evalPan = m_state.m_panSel.evaluate(m_voiceTime, *this, m_state);
float evalPan = (m_state.m_panSel.evaluate(m_voiceTime, *this, m_state) - 64.f) / 63.f;
evalPan = clamp(-1.f, evalPan, 1.f);
if (evalPan != m_curPan)
{
m_curPan = evalPan;
@ -393,7 +395,8 @@ void Voice::preSupplyAudio(double dt)
}
if (m_state.m_spanSel)
{
float evalSpan = m_state.m_spanSel.evaluate(m_voiceTime, *this, m_state);
float evalSpan = (m_state.m_spanSel.evaluate(m_voiceTime, *this, m_state) - 64.f) / 63.f;
evalSpan = clamp(-1.f, evalSpan, 1.f);
if (evalSpan != m_curSpan)
{
m_curSpan = evalSpan;
@ -404,7 +407,10 @@ void Voice::preSupplyAudio(double dt)
_setPan(m_curPan);
if (m_state.m_pitchWheelSel)
_setPitchWheel(m_state.m_pitchWheelSel.evaluate(m_voiceTime, *this, m_state));
{
float evalPWheel = (m_state.m_pitchWheelSel.evaluate(m_voiceTime, *this, m_state) - 64.f) / 63.f;
_setPitchWheel(clamp(-1.f, evalPWheel, 1.f));
}
/* Process active pan-sweep */
bool refresh = false;
@ -939,13 +945,13 @@ void Voice::startSample(SampleId sampId, int32_t offset)
std::tie(m_curSample, m_curSampleData) = m_audioGroup.getSampleData(sampId, sample);
m_sampleRate = m_curSample->m_sampleRate;
m_curPitch = m_curSample->m_pitch;
m_curPitch = m_curSample->getPitch();
m_pitchDirty = true;
_setPitchWheel(m_curPitchWheel);
m_backendVoice->resetSampleRate(m_curSample->m_sampleRate);
m_needsSlew = false;
int32_t numSamples = m_curSample->m_numSamples & 0xffffff;
int32_t numSamples = m_curSample->getNumSamples();
if (offset)
{
if (m_curSample->m_loopLengthSamples)
@ -962,7 +968,7 @@ void Voice::startSample(SampleId sampId, int32_t offset)
m_prev1 = 0;
m_prev2 = 0;
m_curFormat = SampleFormat(m_curSample->m_numSamples >> 24);
m_curFormat = m_curSample->getSampleFormat();
if (m_curFormat == SampleFormat::DSP_DRUM)
m_curFormat = SampleFormat::DSP;
@ -1389,7 +1395,7 @@ bool Voice::doPortamento(uint8_t newNote)
pState = true;
break;
case SoundMacro::CmdPortamento::PortState::MIDIControlled:
pState = m_state.m_portamentoSel ? (m_state.m_portamentoSel.evaluate(m_voiceTime, *this, m_state) >= 1.f)
pState = m_state.m_portamentoSel ? (m_state.m_portamentoSel.evaluate(m_voiceTime, *this, m_state) >= 64.f)
: (getCtrlValue(65) >= 64);
break;
}