From 8d24e19989624d7ef216bb6b14b4ca9ba7530502 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Sun, 29 Jul 2018 20:20:03 -1000 Subject: [PATCH] Initial SampleEditor --- Editor/EditorWidget.hpp | 4 +- Editor/MIDIReader.cpp | 5 +- Editor/MIDIReader.hpp | 1 + Editor/MainWindow.cpp | 6 + Editor/ProjectModel.cpp | 15 +- Editor/ProjectModel.hpp | 38 ++-- Editor/SampleEditor.cpp | 207 +++++++++++++++++++- Editor/SampleEditor.hpp | 28 +++ Editor/SoundMacroEditor.cpp | 22 ++- Editor/SoundMacroEditor.hpp | 2 +- Editor/resources/lang_de.ts | 32 +-- include/amuse/AudioGroupSampleDirectory.hpp | 12 +- include/amuse/Common.hpp | 93 ++++++--- include/amuse/DSPCodec.hpp | 4 + include/amuse/Studio.hpp | 26 +-- lib/AudioGroupSampleDirectory.cpp | 8 +- lib/DSPCodec.cpp | 29 +++ lib/Engine.cpp | 10 +- lib/Sequencer.cpp | 8 +- lib/SoundMacroState.cpp | 15 +- lib/Voice.cpp | 48 +++-- 21 files changed, 469 insertions(+), 144 deletions(-) diff --git a/Editor/EditorWidget.hpp b/Editor/EditorWidget.hpp index 49a006b..119e0bf 100644 --- a/Editor/EditorWidget.hpp +++ b/Editor/EditorWidget.hpp @@ -19,13 +19,13 @@ public: class EditorUndoCommand : public QUndoCommand { protected: - std::shared_ptr m_node; + amuse::ObjToken m_node; enum class Id { SMChangeVal, }; public: - EditorUndoCommand(std::shared_ptr node, + EditorUndoCommand(amuse::ObjToken node, const QString& text, QUndoCommand* parent = nullptr) : QUndoCommand(text, parent), m_node(node) {} void undo(); diff --git a/Editor/MIDIReader.cpp b/Editor/MIDIReader.cpp index d481f33..2ac8fb9 100644 --- a/Editor/MIDIReader.cpp +++ b/Editor/MIDIReader.cpp @@ -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) {} diff --git a/Editor/MIDIReader.hpp b/Editor/MIDIReader.hpp index 001d73b..a6bd9f1 100644 --- a/Editor/MIDIReader.hpp +++ b/Editor/MIDIReader.hpp @@ -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 diff --git a/Editor/MainWindow.cpp b/Editor/MainWindow.cpp index ed6566c..1a89551 100644 --- a/Editor/MainWindow.cpp +++ b/Editor/MainWindow.cpp @@ -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; diff --git a/Editor/ProjectModel.cpp b/Editor/ProjectModel.cpp index b00abbc..550efb7 100644 --- a/Editor/ProjectModel.cpp +++ b/Editor/ProjectModel.cpp @@ -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(this); + auto nullNode = amuse::MakeObj(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(); + m_root = amuse::MakeObj(); 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(); + m_root = amuse::MakeObj(); 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 m_node; + amuse::ObjToken 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&& n) +void ProjectModel::_undoDel(const QModelIndex& index, amuse::ObjToken n) { beginInsertRows(index.parent(), index.row(), index.row()); node(index.parent())->insertChild(index.row(), std::move(n)); endInsertRows(); } -std::shared_ptr ProjectModel::_redoDel(const QModelIndex& index) +amuse::ObjToken ProjectModel::_redoDel(const QModelIndex& index) { node(index)->depthTraverse([](INode* node) { @@ -457,7 +458,7 @@ std::shared_ptr ProjectModel::_redoDel(const QModelIndex& i return true; }); beginRemoveRows(index.parent(), index.row(), index.row()); - std::shared_ptr ret = node(index.parent())->removeChild(index.row()); + amuse::ObjToken ret = node(index.parent())->removeChild(index.row()); endRemoveRows(); return ret; } diff --git a/Editor/ProjectModel.hpp b/Editor/ProjectModel.hpp index 4243df0..83d9b9e 100644 --- a/Editor/ProjectModel.hpp +++ b/Editor/ProjectModel.hpp @@ -46,7 +46,7 @@ private: std::map m_groups; public: - class INode : public std::enable_shared_from_this + class INode : public amuse::IObj { public: enum class Type @@ -66,8 +66,8 @@ public: }; protected: INode* m_parent; - std::vector> m_children; - std::unique_ptr m_nullChild; + std::vector> m_children; + amuse::IObjToken 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&& n) + void insertChild(int row, amuse::ObjToken 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 removeChild(int row) + amuse::ObjToken removeChild(int row) { - std::shared_ptr ret = std::move(m_children[row]); + amuse::ObjToken ret = m_children[row].get(); m_children.erase(m_children.begin() + row); reindexRows(row); return ret; @@ -112,7 +112,8 @@ public: template T& makeChild(_Args&&... args) { - m_children.push_back(std::make_shared(this, m_children.size(), std::forward<_Args>(args)...)); + auto tok = amuse::MakeObj(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(*m_children.back()); } @@ -161,9 +162,6 @@ public: CollectionNode* getCollectionOfType(Type tp) const; amuse::AudioGroupDatabase* getAudioGroup() const { return &m_it->second; } - - std::shared_ptr shared_from_this() - { return std::static_pointer_cast(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 shared_from_this() - { return std::static_pointer_cast(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 shared_from_this() - { return std::static_pointer_cast(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 shared_from_this() - { return std::static_pointer_cast(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> shared_from_this() - { return std::static_pointer_cast>(INode::shared_from_this()); } }; using SoundMacroNode = PoolObjectNode; using ADSRNode = PoolObjectNode, INode::Type::ADSR>; @@ -248,7 +234,7 @@ public: using LayersNode = PoolObjectNode, INode::Type::Layer>; using SampleNode = PoolObjectNode; - std::shared_ptr m_root; + amuse::ObjToken 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&& node); - std::shared_ptr _redoDel(const QModelIndex& index); + void _undoDel(const QModelIndex& index, amuse::ObjToken node); + amuse::ObjToken _redoDel(const QModelIndex& index); void del(const QModelIndex& index); QString path() const { return m_dir.path(); } diff --git a/Editor/SampleEditor.cpp b/Editor/SampleEditor.cpp index 11fd44d..bfcc91c 100644 --- a/Editor/SampleEditor.cpp +++ b/Editor/SampleEditor.cpp @@ -1,12 +1,213 @@ #include "SampleEditor.hpp" +#include "MainWindow.hpp" +#include "amuse/DSPCodec.hpp" +#include +#include + +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> SampleView::iterateSampleInterval(qreal interval) +{ + std::pair, std::pair> 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(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); } diff --git a/Editor/SampleEditor.hpp b/Editor/SampleEditor.hpp index 55b4b40..0204d0b 100644 --- a/Editor/SampleEditor.hpp +++ b/Editor/SampleEditor.hpp @@ -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 m_node; + amuse::ObjToken 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> 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; }; diff --git a/Editor/SoundMacroEditor.cpp b/Editor/SoundMacroEditor.cpp index 55ea1e4..6c44725 100644 --- a/Editor/SoundMacroEditor.cpp +++ b/Editor/SoundMacroEditor.cpp @@ -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 node) - : EditorUndoCommand(node, QUndoStack::tr("Change %1").arg(fieldName)), + int redoVal, amuse::ObjToken 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(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 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 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 m_cmd; public: - InsertCommandUndoCommand(int insertIdx, const QString& text, std::shared_ptr node) - : EditorUndoCommand(node, QUndoStack::tr("Insert %1").arg(text)), m_insertIdx(insertIdx) {} + InsertCommandUndoCommand(int insertIdx, const QString& text, amuse::ObjToken node) + : EditorUndoCommand(node.get(), QUndoStack::tr("Insert %1").arg(text)), m_insertIdx(insertIdx) {} void undo() { m_cmd = static_cast(m_node.get())-> @@ -832,8 +836,8 @@ class DeleteCommandUndoCommand : public EditorUndoCommand std::unique_ptr m_cmd; bool m_undid = false; public: - DeleteCommandUndoCommand(int deleteIdx, const QString& text, std::shared_ptr node) - : EditorUndoCommand(node, QUndoStack::tr("Delete %1").arg(text)), m_deleteIdx(deleteIdx) {} + DeleteCommandUndoCommand(int deleteIdx, const QString& text, amuse::ObjToken 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) diff --git a/Editor/SoundMacroEditor.hpp b/Editor/SoundMacroEditor.hpp index 103454b..9d6faf4 100644 --- a/Editor/SoundMacroEditor.hpp +++ b/Editor/SoundMacroEditor.hpp @@ -127,7 +127,7 @@ class SoundMacroListing : public QWidget Q_OBJECT friend class CommandWidget; friend class SoundMacroEditor; - std::shared_ptr m_node; + amuse::ObjToken m_node; QVBoxLayout* m_layout; QLayoutItem* m_dragItem = nullptr; int m_origIdx = -1; diff --git a/Editor/resources/lang_de.ts b/Editor/resources/lang_de.ts index 86b4c75..1be9897 100644 --- a/Editor/resources/lang_de.ts +++ b/Editor/resources/lang_de.ts @@ -220,22 +220,22 @@ Keine MIDI-Geräte gefunden - + New Project Neues Projekt - + Open Project Offenes Projekt - + The directory at '%1' does not exist. Das Verzeichnis '% 1' existiert nicht. - + Clear Recent Projects @@ -251,46 +251,46 @@ - + The directory at '%1' must not be empty. - - + + Directory empty - + SUSTAIN - + Bad Directory Schlechtes Verzeichnis - + Opening Öffnung - + Scanning Project Projekt scannen - + Opening %1 Eröffnung% 1 - + Reloading Samples @@ -391,7 +391,7 @@ ProjectModel - + Sound Macros Sound-Makros @@ -442,7 +442,7 @@ - + Reorder %1 @@ -542,7 +542,7 @@ TargetButton - + Set step with target click diff --git a/include/amuse/AudioGroupSampleDirectory.hpp b/include/amuse/AudioGroupSampleDirectory.hpp index 4676485..4fc767e 100644 --- a/include/amuse/AudioGroupSampleDirectory.hpp +++ b/include/amuse/AudioGroupSampleDirectory.hpp @@ -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 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 diff --git a/include/amuse/Common.hpp b/include/amuse/Common.hpp index 6abfc64..0898e2b 100644 --- a/include/amuse/Common.hpp +++ b/include/amuse/Common.hpp @@ -150,45 +150,94 @@ public: }; template -class ObjToken; - -template -class ObjWrapper : IObj +class ObjWrapper : public IObj { - friend class ObjToken; SubCls m_obj; - SubCls* get() { return &m_obj; } - const SubCls* get() const { return &m_obj; } public: template ObjWrapper(_Args&&... args) : m_obj(std::forward<_Args>(args)...) {} + SubCls* get() { return &m_obj; } + const SubCls* get() const { return &m_obj; } }; template -class ObjToken +class ObjTokenBase { - ObjWrapper* 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* 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* 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 ObjToken : public ObjTokenBase +{ + IObj*& _obj() { return ObjTokenBase::m_obj; } + IObj*const& _obj() const { return ObjTokenBase::m_obj; } +public: + using ObjTokenBase::ObjTokenBase; + ObjToken(ObjWrapper* obj) : ObjTokenBase(obj) {} + ObjToken& operator=(ObjWrapper* obj) + { if (_obj()) _obj()->decrement(); _obj() = obj; if (_obj()) _obj()->increment(); return *this; } + SubCls* get() const { return static_cast*>(_obj())->get(); } + SubCls* operator->() const { return get(); } + SubCls& operator*() const { return *get(); } +}; + +template +class ObjToken>> : public ObjTokenBase +{ + IObj*& _obj() { return ObjTokenBase::m_obj; } + IObj*const& _obj() const { return ObjTokenBase::m_obj; } +public: + using ObjTokenBase::ObjTokenBase; + ObjToken(IObj* obj) : ObjTokenBase(obj) {} + ObjToken& operator=(IObj* obj) + { if (_obj()) _obj()->decrement(); _obj() = obj; if (_obj()) _obj()->increment(); return *this; } + SubCls* get() const { return static_cast(_obj()); } + SubCls* operator->() const { return get(); } + SubCls& operator*() const { return *get(); } + template + T* cast() const { return static_cast(_obj()); } +}; + +/* ONLY USE WITH CLASSES DERIVED FROM IOBJ! + * Bypasses type_traits tests for incomplete type definitions. */ +template +class IObjToken : public ObjTokenBase +{ + IObj*& _obj() { return ObjTokenBase::m_obj; } + IObj*const& _obj() const { return ObjTokenBase::m_obj; } +public: + using ObjTokenBase::ObjTokenBase; + IObjToken(IObj* obj) : ObjTokenBase(obj) {} + IObjToken& operator=(IObj* obj) + { if (_obj()) _obj()->decrement(); _obj() = obj; if (_obj()) _obj()->increment(); return *this; } + SubCls* get() const { return static_cast(_obj()); } + SubCls* operator->() const { return get(); } + SubCls& operator*() const { return *get(); } + template + T* cast() const { return static_cast(_obj()); } +}; + template -static inline ObjToken MakeObj(_Args&&... args) +inline typename std::enable_if_t, ObjToken> MakeObj(_Args&&... args) +{ + return new Tp(std::forward<_Args>(args)...); +} + +template +inline typename std::enable_if_t, ObjToken> MakeObj(_Args&&... args) { return new ObjWrapper(std::forward<_Args>(args)...); } diff --git a/include/amuse/DSPCodec.hpp b/include/amuse/DSPCodec.hpp index 0c0bc1c..a06d6c9 100644 --- a/include/amuse/DSPCodec.hpp +++ b/include/amuse/DSPCodec.hpp @@ -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 diff --git a/include/amuse/Studio.hpp b/include/amuse/Studio.hpp index 79341bd..941fdf4 100644 --- a/include/amuse/Studio.hpp +++ b/include/amuse/Studio.hpp @@ -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 m_targetStudio; - float m_dryLevel; - float m_auxALevel; - float m_auxBLevel; - StudioSend(ObjToken studio, float dry, float auxA, float auxB) - : m_targetStudio(studio), m_dryLevel(dry), m_auxALevel(auxA), m_auxBLevel(auxB) - { - } - }; + std::list m_studiosOut; #ifndef NDEBUG bool _cyclicCheck(Studio* leaf); @@ -48,6 +39,19 @@ public: Engine& getEngine() { return m_engine; } }; + +struct StudioSend +{ + ObjToken m_targetStudio; + float m_dryLevel; + float m_auxALevel; + float m_auxBLevel; + StudioSend(ObjToken studio, float dry, float auxA, float auxB) + : m_targetStudio(studio), m_dryLevel(dry), m_auxALevel(auxA), m_auxBLevel(auxB) + { + } +}; + } #endif // __AMUSE_STUDIO_HPP__ diff --git a/lib/AudioGroupSampleDirectory.cpp b/lib/AudioGroupSampleDirectory.cpp index 2122201..ebb9c72 100644 --- a/lib/AudioGroupSampleDirectory.cpp +++ b/lib/AudioGroupSampleDirectory.cpp @@ -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) { diff --git a/lib/DSPCodec.cpp b/lib/DSPCodec.cpp index 21f657b..8e98983 100644 --- a/lib/DSPCodec.cpp +++ b/lib/DSPCodec.cpp @@ -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>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; +} diff --git a/lib/Engine.cpp b/lib/Engine.cpp index c2f3173..f7e86f8 100644 --- a/lib/Engine.cpp +++ b/lib/Engine.cpp @@ -76,15 +76,15 @@ std::list>::iterator Engine::_allocateSequencer(const AudioG const SongGroupIndex* songGroup = group.getProj().getSongGroupIndex(groupId); if (songGroup) { - auto it = m_activeSequencers.emplace(m_activeSequencers.end(), - MakeObj(*this, group, groupId, songGroup, setupId, studio)); + amuse::ObjToken tok = MakeObj(*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(*this, group, groupId, sfxGroup, studio)); + amuse::ObjToken tok = MakeObj(*this, group, groupId, sfxGroup, studio); + auto it = m_activeSequencers.emplace(m_activeSequencers.end(), tok); return it; } return {}; @@ -306,6 +306,8 @@ ObjToken Engine::macroStart(const AudioGroup* group, SoundMacroId id, uin return {}; } + (*ret)->setVolume(1.f); + (*ret)->setPan(0.f); return *ret; } diff --git a/lib/Sequencer.cpp b/lib/Sequencer.cpp index f8402d9..c7ef424 100644 --- a/lib/Sequencer.cpp +++ b/lib/Sequencer.cpp @@ -215,7 +215,7 @@ ObjToken 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();) diff --git a/lib/SoundMacroState.cpp b/lib/SoundMacroState.cpp index 9fa56be..06c8e08 100644 --- a/lib/SoundMacroState.cpp +++ b/lib/SoundMacroState.cpp @@ -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; } } diff --git a/lib/Voice.cpp b/lib/Voice.cpp index cfa91f9..acca7d8 100644 --- a/lib/Voice.cpp +++ b/lib/Voice.cpp @@ -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& Voice::_ensureCtrlVals() std::list>::iterator Voice::_allocateVoice(double sampleRate, bool dynamicPitch) { - auto it = m_childVoices.emplace( - m_childVoices.end(), MakeObj(m_engine, m_audioGroup, m_groupId, m_engine.m_nextVid++, m_emitter, m_studio)); + amuse::ObjToken tok = + MakeObj(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 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 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 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; }