mirror of https://github.com/AxioDL/amuse.git
Implement amuse playback
This commit is contained in:
parent
cb24322fc1
commit
f5984141fd
|
@ -1 +0,0 @@
|
|||
#include "AudioGroupModel.hpp"
|
|
@ -1,21 +0,0 @@
|
|||
#ifndef AMUSE_AUDIO_GROUP_MODEL_HPP
|
||||
#define AMUSE_AUDIO_GROUP_MODEL_HPP
|
||||
|
||||
#include "amuse/AudioGroup.hpp"
|
||||
|
||||
class AudioGroupModel
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
class SFXGroupModel : public AudioGroupModel
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
class SongGroupModel : public AudioGroupModel
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
#endif //AMUSE_AUDIO_GROUP_MODEL_HPP
|
|
@ -47,7 +47,7 @@ add_executable(amuse-gui WIN32 MACOSX_BUNDLE
|
|||
SampleEditor.hpp SampleEditor.cpp
|
||||
SoundGroupEditor.hpp SoundGroupEditor.cpp
|
||||
SongGroupEditor.hpp SongGroupEditor.cpp
|
||||
AudioGroupModel.hpp AudioGroupModel.cpp
|
||||
MIDIReader.hpp MIDIReader.cpp
|
||||
resources/resources.qrc qrc_resources.cpp
|
||||
${QM_FILES} qrc_translation_res.cpp
|
||||
${PLAT_SRCS}
|
||||
|
|
|
@ -13,6 +13,7 @@ public:
|
|||
explicit EditorWidget(QWidget* parent = Q_NULLPTR);
|
||||
virtual bool valid() const { return true; }
|
||||
virtual void unloadData() {}
|
||||
virtual ProjectModel::INode* currentNode() const { return nullptr; }
|
||||
};
|
||||
|
||||
class EditorUndoCommand : public QUndoCommand
|
||||
|
|
|
@ -135,12 +135,12 @@ std::pair<int, int> KeyboardWidget::_getOctaveAndKey(QMouseEvent* event) const
|
|||
|
||||
void KeyboardWidget::_startKey(int octave, int key)
|
||||
{
|
||||
printf("START %d %d\n", octave, key);
|
||||
emit notePressed(octave * 12 + key);
|
||||
}
|
||||
|
||||
void KeyboardWidget::_stopKey()
|
||||
{
|
||||
printf("STOP\n");
|
||||
emit noteReleased();
|
||||
}
|
||||
|
||||
void KeyboardWidget::_moveOnKey(int octave, int key)
|
||||
|
@ -209,7 +209,75 @@ void KeyboardWidget::showEvent(QShowEvent* event)
|
|||
{
|
||||
if (QScrollArea* scroll = qobject_cast<QScrollArea*>(parentWidget()->parentWidget()))
|
||||
{
|
||||
/* Scroll to C2 */
|
||||
scroll->ensureVisible(141 * 3 + scroll->width(), 0, 0, 0);
|
||||
/* Scroll to C1 */
|
||||
scroll->ensureVisible(141 * 2 + scroll->width(), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
KeyboardSlider::KeyboardSlider(QWidget* parent)
|
||||
: QSlider(parent)
|
||||
{}
|
||||
|
||||
void KeyboardSlider::enterEvent(QEvent* event)
|
||||
{
|
||||
if (m_statusFocus)
|
||||
m_statusFocus->enter();
|
||||
}
|
||||
|
||||
void KeyboardSlider::leaveEvent(QEvent* event)
|
||||
{
|
||||
if (m_statusFocus)
|
||||
m_statusFocus->exit();
|
||||
}
|
||||
|
||||
void KeyboardSlider::setStatusFocus(StatusBarFocus* statusFocus)
|
||||
{
|
||||
m_statusFocus = statusFocus;
|
||||
QString str = stringOfValue(value());
|
||||
m_statusFocus->setMessage(str);
|
||||
setToolTip(str);
|
||||
}
|
||||
|
||||
void KeyboardSlider::sliderChange(SliderChange change)
|
||||
{
|
||||
QSlider::sliderChange(change);
|
||||
if (m_statusFocus && change == QAbstractSlider::SliderValueChange)
|
||||
{
|
||||
QString str = stringOfValue(value());
|
||||
m_statusFocus->setMessage(str);
|
||||
setToolTip(str);
|
||||
}
|
||||
}
|
||||
|
||||
VelocitySlider::VelocitySlider(QWidget* parent)
|
||||
: KeyboardSlider(parent)
|
||||
{}
|
||||
|
||||
QString VelocitySlider::stringOfValue(int value) const
|
||||
{
|
||||
return tr("Velocity: %1").arg(value);
|
||||
}
|
||||
|
||||
ModulationSlider::ModulationSlider(QWidget* parent)
|
||||
: KeyboardSlider(parent)
|
||||
{}
|
||||
|
||||
QString ModulationSlider::stringOfValue(int value) const
|
||||
{
|
||||
return tr("Modulation: %1").arg(value);
|
||||
}
|
||||
|
||||
PitchSlider::PitchSlider(QWidget* parent)
|
||||
: KeyboardSlider(parent)
|
||||
{}
|
||||
|
||||
QString PitchSlider::stringOfValue(int value) const
|
||||
{
|
||||
return tr("Pitch: %1").arg(value / 2048.0, 0, 'g', 2);
|
||||
}
|
||||
|
||||
void PitchSlider::mouseReleaseEvent(QMouseEvent *ev)
|
||||
{
|
||||
KeyboardSlider::mouseReleaseEvent(ev);
|
||||
setValue(0);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <QWidget>
|
||||
#include <QSvgWidget>
|
||||
#include <QSlider>
|
||||
#include <QWheelEvent>
|
||||
#include "StatusBarWidget.hpp"
|
||||
|
||||
class KeyboardWidget;
|
||||
|
@ -47,7 +49,51 @@ public:
|
|||
void leaveEvent(QEvent* event);
|
||||
void wheelEvent(QWheelEvent *event);
|
||||
void showEvent(QShowEvent *event);
|
||||
|
||||
signals:
|
||||
void notePressed(int key);
|
||||
void noteReleased();
|
||||
};
|
||||
|
||||
class KeyboardSlider : public QSlider
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
StatusBarFocus* m_statusFocus = nullptr;
|
||||
virtual QString stringOfValue(int value) const = 0;
|
||||
public:
|
||||
explicit KeyboardSlider(QWidget* parent = Q_NULLPTR);
|
||||
void enterEvent(QEvent* event);
|
||||
void leaveEvent(QEvent* event);
|
||||
void setStatusFocus(StatusBarFocus* statusFocus);
|
||||
void sliderChange(SliderChange change);
|
||||
};
|
||||
|
||||
class VelocitySlider : public KeyboardSlider
|
||||
{
|
||||
Q_OBJECT
|
||||
QString stringOfValue(int value) const;
|
||||
public:
|
||||
explicit VelocitySlider(QWidget* parent = Q_NULLPTR);
|
||||
|
||||
};
|
||||
|
||||
class ModulationSlider : public KeyboardSlider
|
||||
{
|
||||
Q_OBJECT
|
||||
QString stringOfValue(int value) const;
|
||||
public:
|
||||
explicit ModulationSlider(QWidget* parent = Q_NULLPTR);
|
||||
};
|
||||
|
||||
class PitchSlider : public KeyboardSlider
|
||||
{
|
||||
Q_OBJECT
|
||||
QString stringOfValue(int value) const;
|
||||
public:
|
||||
explicit PitchSlider(QWidget* parent = Q_NULLPTR);
|
||||
void mouseReleaseEvent(QMouseEvent *ev);
|
||||
void wheelEvent(QWheelEvent* ev) { ev->ignore(); }
|
||||
};
|
||||
|
||||
#endif //AMUSE_KEYBOARD_WIDGET_HPP
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
#include "MIDIReader.hpp"
|
||||
#include "MainWindow.hpp"
|
||||
|
||||
MIDIReader::MIDIReader(amuse::Engine& engine, const char* name, bool useLock)
|
||||
: amuse::BooBackendMIDIReader(engine, name, useLock) {}
|
||||
|
||||
void MIDIReader::noteOff(uint8_t chan, uint8_t key, uint8_t velocity)
|
||||
{
|
||||
auto keySearch = m_chanVoxs.find(key);
|
||||
if (keySearch == m_chanVoxs.cend())
|
||||
return;
|
||||
|
||||
if (keySearch->second == m_lastVoice.lock())
|
||||
m_lastVoice.reset();
|
||||
keySearch->second->keyOff();
|
||||
m_keyoffVoxs.emplace(std::move(keySearch->second));
|
||||
m_chanVoxs.erase(keySearch);
|
||||
}
|
||||
|
||||
void MIDIReader::noteOn(uint8_t chan, uint8_t key, uint8_t velocity)
|
||||
{
|
||||
/* If portamento is enabled for voice, pre-empt spawning new voices */
|
||||
if (std::shared_ptr<amuse::Voice> lastVoice = m_lastVoice.lock())
|
||||
{
|
||||
uint8_t lastNote = lastVoice->getLastNote();
|
||||
if (lastVoice->doPortamento(key))
|
||||
{
|
||||
m_chanVoxs.erase(lastNote);
|
||||
m_chanVoxs[key] = lastVoice;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure keyoff sent first */
|
||||
auto keySearch = m_chanVoxs.find(key);
|
||||
if (keySearch != m_chanVoxs.cend())
|
||||
{
|
||||
if (keySearch->second == m_lastVoice.lock())
|
||||
m_lastVoice.reset();
|
||||
keySearch->second->keyOff();
|
||||
keySearch->second->setPedal(false);
|
||||
m_keyoffVoxs.emplace(std::move(keySearch->second));
|
||||
m_chanVoxs.erase(keySearch);
|
||||
}
|
||||
|
||||
ProjectModel::INode* node = g_MainWindow->getEditorNode();
|
||||
if (node && node->type() == ProjectModel::INode::Type::SoundMacro)
|
||||
{
|
||||
ProjectModel::SoundMacroNode* cNode = static_cast<ProjectModel::SoundMacroNode*>(node);
|
||||
amuse::AudioGroupDatabase* group = g_MainWindow->projectModel()->getGroupNode(node)->getAudioGroup();
|
||||
std::shared_ptr<amuse::Voice>& vox = m_chanVoxs[key];
|
||||
vox = m_engine.macroStart(group, cNode->id(), key, velocity, g_MainWindow->m_modulation);
|
||||
vox->setPedal(g_MainWindow->m_sustain);
|
||||
vox->setPitchWheel(g_MainWindow->m_pitch);
|
||||
}
|
||||
}
|
||||
|
||||
void MIDIReader::notePressure(uint8_t /*chan*/, uint8_t /*key*/, uint8_t /*pressure*/) {}
|
||||
|
||||
void MIDIReader::controlChange(uint8_t chan, uint8_t control, uint8_t value)
|
||||
{
|
||||
if (control == 1)
|
||||
{
|
||||
g_MainWindow->m_ui.modulationSlider->setValue(int(value));
|
||||
}
|
||||
else if (control == 64)
|
||||
{
|
||||
g_MainWindow->setSustain(value >= 0x40);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto& v : m_engine.getActiveVoices())
|
||||
v->setCtrlValue(control, value);
|
||||
}
|
||||
}
|
||||
|
||||
void MIDIReader::programChange(uint8_t chan, uint8_t program) {}
|
||||
|
||||
void MIDIReader::channelPressure(uint8_t /*chan*/, uint8_t /*pressure*/) {}
|
||||
|
||||
void MIDIReader::pitchBend(uint8_t chan, int16_t pitch)
|
||||
{
|
||||
g_MainWindow->m_ui.pitchSlider->setValue(int((pitch - 0x2000) / float(0x2000) * 2048.f));
|
||||
}
|
||||
|
||||
void MIDIReader::allSoundOff(uint8_t chan)
|
||||
{
|
||||
for (auto& v : m_engine.getActiveVoices())
|
||||
v->kill();
|
||||
}
|
||||
|
||||
void MIDIReader::resetAllControllers(uint8_t /*chan*/) {}
|
||||
|
||||
void MIDIReader::localControl(uint8_t /*chan*/, bool /*on*/) {}
|
||||
|
||||
void MIDIReader::allNotesOff(uint8_t chan)
|
||||
{
|
||||
for (auto& v : m_engine.getActiveVoices())
|
||||
v->kill();
|
||||
}
|
||||
|
||||
void MIDIReader::omniMode(uint8_t /*chan*/, bool /*on*/) {}
|
||||
|
||||
void MIDIReader::polyMode(uint8_t /*chan*/, bool /*on*/) {}
|
||||
|
||||
void MIDIReader::sysex(const void* /*data*/, size_t /*len*/) {}
|
||||
|
||||
void MIDIReader::timeCodeQuarterFrame(uint8_t /*message*/, uint8_t /*value*/) {}
|
||||
|
||||
void MIDIReader::songPositionPointer(uint16_t /*pointer*/) {}
|
||||
|
||||
void MIDIReader::songSelect(uint8_t /*song*/) {}
|
||||
|
||||
void MIDIReader::tuneRequest() {}
|
||||
|
||||
void MIDIReader::startSeq() {}
|
||||
|
||||
void MIDIReader::continueSeq() {}
|
||||
|
||||
void MIDIReader::stopSeq() {}
|
||||
|
||||
void MIDIReader::reset() {}
|
||||
|
||||
VoiceAllocator::VoiceAllocator(boo::IAudioVoiceEngine& booEngine)
|
||||
: amuse::BooBackendVoiceAllocator(booEngine) {}
|
||||
|
||||
std::unique_ptr<amuse::IMIDIReader>
|
||||
VoiceAllocator::allocateMIDIReader(amuse::Engine& engine, const char* name)
|
||||
{
|
||||
std::unique_ptr<amuse::IMIDIReader> ret = std::make_unique<MIDIReader>(engine, name, m_booEngine.useMIDILock());
|
||||
if (!static_cast<MIDIReader&>(*ret).getMidiIn())
|
||||
return {};
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
#ifndef AMUSE_MIDI_READER_HPP
|
||||
#define AMUSE_MIDI_READER_HPP
|
||||
|
||||
#include "amuse/BooBackend.hpp"
|
||||
#include <unordered_set>
|
||||
|
||||
class MIDIReader : public amuse::BooBackendMIDIReader
|
||||
{
|
||||
std::unordered_map<uint8_t, std::shared_ptr<amuse::Voice>> m_chanVoxs;
|
||||
std::unordered_set<std::shared_ptr<amuse::Voice>> m_keyoffVoxs;
|
||||
std::weak_ptr<amuse::Voice> m_lastVoice;
|
||||
public:
|
||||
MIDIReader(amuse::Engine& engine, const char* name, bool useLock);
|
||||
boo::IMIDIIn* getMidiIn() const { return m_midiIn.get(); }
|
||||
|
||||
void noteOff(uint8_t chan, uint8_t key, uint8_t velocity);
|
||||
void noteOn(uint8_t chan, uint8_t key, uint8_t velocity);
|
||||
void notePressure(uint8_t chan, uint8_t key, uint8_t pressure);
|
||||
void controlChange(uint8_t chan, uint8_t control, uint8_t value);
|
||||
void programChange(uint8_t chan, uint8_t program);
|
||||
void channelPressure(uint8_t chan, uint8_t pressure);
|
||||
void pitchBend(uint8_t chan, int16_t pitch);
|
||||
|
||||
void allSoundOff(uint8_t chan);
|
||||
void resetAllControllers(uint8_t chan);
|
||||
void localControl(uint8_t chan, bool on);
|
||||
void allNotesOff(uint8_t chan);
|
||||
void omniMode(uint8_t chan, bool on);
|
||||
void polyMode(uint8_t chan, bool on);
|
||||
|
||||
void sysex(const void* data, size_t len);
|
||||
void timeCodeQuarterFrame(uint8_t message, uint8_t value);
|
||||
void songPositionPointer(uint16_t pointer);
|
||||
void songSelect(uint8_t song);
|
||||
void tuneRequest();
|
||||
|
||||
void startSeq();
|
||||
void continueSeq();
|
||||
void stopSeq();
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
class VoiceAllocator : public amuse::BooBackendVoiceAllocator
|
||||
{
|
||||
public:
|
||||
VoiceAllocator(boo::IAudioVoiceEngine& booEngine);
|
||||
std::unique_ptr<amuse::IMIDIReader> allocateMIDIReader(amuse::Engine& engine, const char* name = nullptr);
|
||||
};
|
||||
|
||||
#endif // AMUSE_MIDI_READER_HPP
|
|
@ -31,14 +31,40 @@ MainWindow::MainWindow(QWidget* parent)
|
|||
m_ui.projectOutline->setItemDelegate(&m_treeDelegate);
|
||||
connectMessenger(&m_mainMessenger, Qt::DirectConnection);
|
||||
|
||||
m_ui.statusbar->connectKillClicked(this, SLOT(killSounds()));
|
||||
|
||||
m_ui.keyboardContents->setStatusFocus(new StatusBarFocus(m_ui.statusbar));
|
||||
m_ui.velocitySlider->setStatusFocus(new StatusBarFocus(m_ui.statusbar));
|
||||
m_ui.modulationSlider->setStatusFocus(new StatusBarFocus(m_ui.statusbar));
|
||||
m_ui.pitchSlider->setStatusFocus(new StatusBarFocus(m_ui.statusbar));
|
||||
connect(m_ui.keyboardContents, SIGNAL(notePressed(int)), this, SLOT(notePressed(int)));
|
||||
connect(m_ui.keyboardContents, SIGNAL(noteReleased()), this, SLOT(noteReleased()));
|
||||
connect(m_ui.velocitySlider, SIGNAL(valueChanged(int)), this, SLOT(velocityChanged(int)));
|
||||
connect(m_ui.modulationSlider, SIGNAL(valueChanged(int)), this, SLOT(modulationChanged(int)));
|
||||
connect(m_ui.pitchSlider, SIGNAL(valueChanged(int)), this, SLOT(pitchChanged(int)));
|
||||
|
||||
m_ui.actionNew_Project->setShortcut(QKeySequence::New);
|
||||
connect(m_ui.actionNew_Project, SIGNAL(triggered()), this, SLOT(newAction()));
|
||||
m_ui.actionOpen_Project->setShortcut(QKeySequence::Open);
|
||||
connect(m_ui.actionOpen_Project, SIGNAL(triggered()), this, SLOT(openAction()));
|
||||
m_ui.actionSave_Project->setShortcut(QKeySequence::Save);
|
||||
connect(m_ui.actionSave_Project, SIGNAL(triggered()), this, SLOT(saveAction()));
|
||||
connect(m_ui.actionRevert_Project, SIGNAL(triggered()), this, SLOT(revertAction()));
|
||||
connect(m_ui.actionImport, SIGNAL(triggered()), this, SLOT(importAction()));
|
||||
connect(m_ui.actionExport_GameCube_Groups, SIGNAL(triggered()), this, SLOT(exportAction()));
|
||||
|
||||
for (int i = 0; i < MaxRecentFiles; ++i)
|
||||
{
|
||||
m_recentFileActs[i] = new QAction(this);
|
||||
m_recentFileActs[i]->setVisible(false);
|
||||
m_ui.menuRecent_Projects->addAction(m_recentFileActs[i]);
|
||||
connect(m_recentFileActs[i], SIGNAL(triggered()), this, SLOT(openRecentFileAction()));
|
||||
}
|
||||
m_ui.menuRecent_Projects->addSeparator();
|
||||
m_clearRecentFileAct = new QAction(tr("Clear Recent Projects"), this);
|
||||
connect(m_clearRecentFileAct, SIGNAL(triggered()), this, SLOT(clearRecentFilesAction()));
|
||||
m_ui.menuRecent_Projects->addAction(m_clearRecentFileAct);
|
||||
|
||||
#ifndef __APPLE__
|
||||
m_ui.menuFile->addSeparator();
|
||||
QAction* quitAction = m_ui.menuFile->addAction(tr("Quit"));
|
||||
|
@ -46,6 +72,8 @@ MainWindow::MainWindow(QWidget* parent)
|
|||
connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
|
||||
#endif
|
||||
|
||||
updateRecentFileActions();
|
||||
|
||||
QAction* undoAction = m_undoStack->createUndoAction(this);
|
||||
undoAction->setShortcut(QKeySequence::Undo);
|
||||
m_ui.menuEdit->insertAction(m_ui.actionCut, undoAction);
|
||||
|
@ -97,11 +125,11 @@ MainWindow::MainWindow(QWidget* parent)
|
|||
|
||||
connect(qApp, SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(onFocusChanged(QWidget*,QWidget*)));
|
||||
|
||||
setFocusAudioGroup(nullptr);
|
||||
|
||||
m_voxEngine = boo::NewAudioVoiceEngine();
|
||||
m_voxAllocator = std::make_unique<amuse::BooBackendVoiceAllocator>(*m_voxEngine);
|
||||
m_voxAllocator = std::make_unique<VoiceAllocator>(*m_voxEngine);
|
||||
m_engine = std::make_unique<amuse::Engine>(*m_voxAllocator);
|
||||
|
||||
startTimer(16);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
|
@ -139,13 +167,39 @@ void MainWindow::connectMessenger(UIMessenger* messenger, Qt::ConnectionType typ
|
|||
QMessageBox::StandardButton)), type);
|
||||
}
|
||||
|
||||
void MainWindow::updateRecentFileActions()
|
||||
{
|
||||
QSettings settings;
|
||||
QStringList files = settings.value("recentFileList").toStringList();
|
||||
|
||||
int numRecentFiles = std::min(files.size(), int(MaxRecentFiles));
|
||||
|
||||
for (int i = 0; i < numRecentFiles; ++i)
|
||||
{
|
||||
QString text = QStringLiteral("&%1 %2").arg(i + 1).arg(QDir(files[i]).dirName());
|
||||
m_recentFileActs[i]->setText(text);
|
||||
m_recentFileActs[i]->setData(files[i]);
|
||||
m_recentFileActs[i]->setVisible(true);
|
||||
}
|
||||
for (int j = numRecentFiles; j < MaxRecentFiles; ++j)
|
||||
m_recentFileActs[j]->setVisible(false);
|
||||
|
||||
m_ui.menuRecent_Projects->setEnabled(numRecentFiles > 0);
|
||||
}
|
||||
|
||||
bool MainWindow::setProjectPath(const QString& path)
|
||||
{
|
||||
if (m_projectModel && m_projectModel->path() == path)
|
||||
return true;
|
||||
|
||||
QDir dir(path);
|
||||
if (!dir.exists())
|
||||
if (dir.path().isEmpty() || dir.path() == QStringLiteral(".") || dir.path() == QStringLiteral(".."))
|
||||
{
|
||||
QString msg = QString(tr("The directory at '%1' must not be empty.")).arg(path);
|
||||
QMessageBox::critical(this, tr("Directory empty"), msg);
|
||||
return false;
|
||||
}
|
||||
else if (!dir.exists())
|
||||
{
|
||||
QString msg = QString(tr("The directory at '%1' must exist for the Amuse editor.")).arg(path);
|
||||
QMessageBox::critical(this, tr("Directory does not exist"), msg);
|
||||
|
@ -165,24 +219,30 @@ bool MainWindow::setProjectPath(const QString& path)
|
|||
m_projectModel->deleteLater();
|
||||
m_projectModel = new ProjectModel(path, this);
|
||||
m_ui.projectOutline->setModel(m_projectModel);
|
||||
connect(m_ui.projectOutline->selectionModel(),
|
||||
SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
|
||||
this, SLOT(onOutlineSelectionChanged(const QItemSelection&, const QItemSelection&)));
|
||||
m_ui.actionSave_Project->setEnabled(true);
|
||||
m_ui.actionRevert_Project->setEnabled(true);
|
||||
m_ui.actionExport_GameCube_Groups->setEnabled(true);
|
||||
setWindowFilePath(path);
|
||||
setWindowTitle(QString("Amuse [%1]").arg(dir.dirName()));
|
||||
setFocusAudioGroup(nullptr);
|
||||
onFocusChanged(nullptr, focusWidget());
|
||||
m_undoStack->clear();
|
||||
|
||||
QSettings settings;
|
||||
QStringList files = settings.value("recentFileList").toStringList();
|
||||
files.removeAll(dir.path());
|
||||
files.prepend(dir.path());
|
||||
while (files.size() > MaxRecentFiles)
|
||||
files.removeLast();
|
||||
settings.setValue("recentFileList", files);
|
||||
|
||||
updateRecentFileActions();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::setFocusAudioGroup(AudioGroupModel* group)
|
||||
{
|
||||
m_focusAudioGroup = group;
|
||||
bool active = m_focusAudioGroup != nullptr;
|
||||
m_ui.actionNew_Sound_Macro->setEnabled(active);
|
||||
m_ui.actionNew_Keymap->setEnabled(active);
|
||||
m_ui.actionNew_Layers->setEnabled(active);
|
||||
}
|
||||
|
||||
void MainWindow::refreshAudioIO()
|
||||
{
|
||||
QList<QAction*> audioActions = m_ui.menuAudio->actions();
|
||||
|
@ -220,6 +280,61 @@ void MainWindow::refreshMIDIIO()
|
|||
m_ui.menuMIDI->addAction(tr("No MIDI Devices Found"))->setEnabled(false);
|
||||
}
|
||||
|
||||
void MainWindow::timerEvent(QTimerEvent* ev)
|
||||
{
|
||||
if (m_voxEngine && m_engine)
|
||||
{
|
||||
m_voxEngine->pumpAndMixVoices();
|
||||
m_ui.statusbar->setVoiceCount(int(m_engine->getActiveVoices().size()));
|
||||
if (m_engine->getActiveVoices().empty() && m_uiDisabled)
|
||||
{
|
||||
m_ui.projectOutline->setEnabled(true);
|
||||
m_ui.editorContents->setEnabled(true);
|
||||
m_ui.menubar->setEnabled(true);
|
||||
m_uiDisabled = false;
|
||||
}
|
||||
else if (!m_engine->getActiveVoices().empty() && !m_uiDisabled)
|
||||
{
|
||||
m_ui.projectOutline->setEnabled(false);
|
||||
m_ui.editorContents->setEnabled(false);
|
||||
m_ui.menubar->setEnabled(false);
|
||||
m_uiDisabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::setSustain(bool sustain)
|
||||
{
|
||||
if (sustain && !m_sustain)
|
||||
{
|
||||
m_ui.statusbar->setNormalMessage(tr("SUSTAIN"));
|
||||
for (auto& v : m_engine->getActiveVoices())
|
||||
v->setPedal(true);
|
||||
m_sustain = true;
|
||||
}
|
||||
else if (!sustain && m_sustain)
|
||||
{
|
||||
m_ui.statusbar->setNormalMessage({});
|
||||
for (auto& v : m_engine->getActiveVoices())
|
||||
v->setPedal(false);
|
||||
m_sustain = false;
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::keyPressEvent(QKeyEvent* ev)
|
||||
{
|
||||
if (ev->key() == Qt::Key_Shift)
|
||||
setSustain(true);
|
||||
else if (ev->key() == Qt::Key_Escape)
|
||||
killSounds();
|
||||
}
|
||||
|
||||
void MainWindow::keyReleaseEvent(QKeyEvent* ev)
|
||||
{
|
||||
if (ev->key() == Qt::Key_Shift)
|
||||
setSustain(false);
|
||||
}
|
||||
|
||||
void MainWindow::startBackgroundTask(const QString& windowTitle, const QString& label,
|
||||
std::function<void(BackgroundTask&)>&& task)
|
||||
{
|
||||
|
@ -335,11 +450,24 @@ void MainWindow::closeEditor()
|
|||
_setEditor(nullptr);
|
||||
}
|
||||
|
||||
ProjectModel::INode* MainWindow::getEditorNode() const
|
||||
{
|
||||
if (m_ui.editorContents->currentWidget() != m_faceSvg)
|
||||
return static_cast<EditorWidget*>(m_ui.editorContents->currentWidget())->currentNode();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MainWindow::pushUndoCommand(QUndoCommand* cmd)
|
||||
{
|
||||
m_undoStack->push(cmd);
|
||||
}
|
||||
|
||||
void MainWindow::aboutToDeleteNode(ProjectModel::INode* node)
|
||||
{
|
||||
if (getEditorNode() == node)
|
||||
closeEditor();
|
||||
}
|
||||
|
||||
void MainWindow::newAction()
|
||||
{
|
||||
QString path = QFileDialog::getSaveFileName(this, tr("New Project"));
|
||||
|
@ -354,18 +482,20 @@ void MainWindow::newAction()
|
|||
m_projectModel->ensureModelData();
|
||||
}
|
||||
|
||||
void MainWindow::openAction()
|
||||
bool MainWindow::openProject(const QString& path)
|
||||
{
|
||||
QString path = QFileDialog::getExistingDirectory(this, tr("Open Project"));
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
|
||||
QDir dir(path);
|
||||
if (!dir.exists())
|
||||
if (dir.path().isEmpty() || dir.path() == QStringLiteral(".") || dir.path() == QStringLiteral(".."))
|
||||
{
|
||||
QString msg = QString(tr("The directory at '%1' must not be empty.")).arg(path);
|
||||
QMessageBox::critical(this, tr("Directory empty"), msg);
|
||||
return false;
|
||||
}
|
||||
else if (!dir.exists())
|
||||
{
|
||||
QString msg = QString(tr("The directory at '%1' does not exist.")).arg(path);
|
||||
QMessageBox::critical(this, tr("Bad Directory"), msg);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (QFileInfo(dir, QStringLiteral("!project.yaml")).exists() &&
|
||||
|
@ -373,7 +503,7 @@ void MainWindow::openAction()
|
|||
dir.cdUp();
|
||||
|
||||
if (!setProjectPath(dir.path()))
|
||||
return;
|
||||
return false;
|
||||
|
||||
ProjectModel* model = m_projectModel;
|
||||
startBackgroundTask(tr("Opening"), tr("Scanning Project"),
|
||||
|
@ -394,6 +524,50 @@ void MainWindow::openAction()
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::openAction()
|
||||
{
|
||||
QString path = QFileDialog::getExistingDirectory(this, tr("Open Project"));
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
openProject(path);
|
||||
}
|
||||
|
||||
void MainWindow::openRecentFileAction()
|
||||
{
|
||||
if (QAction *action = qobject_cast<QAction *>(sender()))
|
||||
if (!openProject(action->data().toString()))
|
||||
{
|
||||
QString path = action->data().toString();
|
||||
QSettings settings;
|
||||
QStringList files = settings.value("recentFileList").toStringList();
|
||||
files.removeAll(path);
|
||||
settings.setValue("recentFileList", files);
|
||||
updateRecentFileActions();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::clearRecentFilesAction()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.setValue("recentFileList", QStringList());
|
||||
updateRecentFileActions();
|
||||
}
|
||||
|
||||
void MainWindow::saveAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::revertAction()
|
||||
{
|
||||
QString path = m_projectModel->path();
|
||||
delete m_projectModel;
|
||||
m_projectModel = nullptr;
|
||||
openProject(path);
|
||||
}
|
||||
|
||||
void MainWindow::importAction()
|
||||
|
@ -610,6 +784,83 @@ void MainWindow::setMIDIIO()
|
|||
//qobject_cast<QAction*>(sender())->data().toString().toUtf8().constData();
|
||||
}
|
||||
|
||||
void MainWindow::notePressed(int key)
|
||||
{
|
||||
if (m_engine)
|
||||
{
|
||||
ProjectModel::INode* node = getEditorNode();
|
||||
if (node && node->type() == ProjectModel::INode::Type::SoundMacro)
|
||||
{
|
||||
ProjectModel::SoundMacroNode* cNode = static_cast<ProjectModel::SoundMacroNode*>(node);
|
||||
amuse::AudioGroupDatabase* group = m_projectModel->getGroupNode(node)->getAudioGroup();
|
||||
if (m_lastSound)
|
||||
m_lastSound->keyOff();
|
||||
m_lastSound = m_engine->macroStart(group, cNode->id(), key, m_velocity, m_modulation);
|
||||
m_lastSound->setPedal(m_sustain);
|
||||
m_lastSound->setPitchWheel(m_pitch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::noteReleased()
|
||||
{
|
||||
if (m_lastSound)
|
||||
{
|
||||
m_lastSound->keyOff();
|
||||
m_lastSound.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::velocityChanged(int vel)
|
||||
{
|
||||
m_velocity = vel;
|
||||
}
|
||||
|
||||
void MainWindow::modulationChanged(int mod)
|
||||
{
|
||||
m_modulation = mod;
|
||||
for (auto& v : m_engine->getActiveVoices())
|
||||
v->setCtrlValue(1, int8_t(m_modulation));
|
||||
}
|
||||
|
||||
void MainWindow::pitchChanged(int pitch)
|
||||
{
|
||||
m_pitch = pitch / 2048.f;
|
||||
for (auto& v : m_engine->getActiveVoices())
|
||||
v->setPitchWheel(m_pitch);
|
||||
}
|
||||
|
||||
void MainWindow::killSounds()
|
||||
{
|
||||
for (auto& v : m_engine->getActiveVoices())
|
||||
v->kill();
|
||||
}
|
||||
|
||||
void MainWindow::outlineCutAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::outlineCopyAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::outlinePasteAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::outlineDeleteAction()
|
||||
{
|
||||
if (!m_projectModel)
|
||||
return;
|
||||
QModelIndexList indexes = m_ui.projectOutline->selectionModel()->selectedIndexes();
|
||||
if (indexes.empty())
|
||||
return;
|
||||
m_projectModel->del(indexes.front());
|
||||
}
|
||||
|
||||
void MainWindow::onFocusChanged(QWidget* old, QWidget* now)
|
||||
{
|
||||
disconnect(m_cutConn);
|
||||
|
@ -634,24 +885,52 @@ void MainWindow::onFocusChanged(QWidget* old, QWidget* now)
|
|||
|
||||
if (now == m_ui.projectOutline || m_ui.projectOutline->isAncestorOf(now))
|
||||
{
|
||||
m_ui.actionCut->setEnabled(false);
|
||||
m_ui.actionCopy->setEnabled(false);
|
||||
m_ui.actionPaste->setEnabled(false);
|
||||
setOutlineEditEnabled(canEditOutline());
|
||||
if (m_projectModel)
|
||||
{
|
||||
m_deleteConn = connect(m_ui.actionDelete, SIGNAL(triggered()), m_projectModel, SLOT(del()));
|
||||
m_ui.actionDelete->setEnabled(m_projectModel->canDelete());
|
||||
m_canEditConn = connect(m_projectModel, SIGNAL(canDeleteChanged(bool)),
|
||||
m_ui.actionDelete, SLOT(setEnabled(bool)));
|
||||
m_cutConn = connect(m_ui.actionCut, SIGNAL(triggered()), this, SLOT(outlineCutAction()));
|
||||
m_copyConn = connect(m_ui.actionCopy, SIGNAL(triggered()), this, SLOT(outlineCopyAction()));
|
||||
m_pasteConn = connect(m_ui.actionPaste, SIGNAL(triggered()), this, SLOT(outlinePasteAction()));
|
||||
m_deleteConn = connect(m_ui.actionDelete, SIGNAL(triggered()), this, SLOT(outlineDeleteAction()));
|
||||
}
|
||||
}
|
||||
else if (now == m_ui.editorContents || m_ui.editorContents->isAncestorOf(now))
|
||||
{
|
||||
|
||||
setOutlineEditEnabled(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::setOutlineEditEnabled(bool enabled)
|
||||
{
|
||||
m_ui.actionCut->setEnabled(enabled);
|
||||
m_ui.actionCopy->setEnabled(enabled);
|
||||
m_ui.actionPaste->setEnabled(enabled);
|
||||
m_ui.actionDelete->setEnabled(enabled);
|
||||
}
|
||||
|
||||
bool MainWindow::canEditOutline()
|
||||
{
|
||||
if (!m_projectModel)
|
||||
return false;
|
||||
QModelIndexList indexes = m_ui.projectOutline->selectionModel()->selectedIndexes();
|
||||
if (indexes.empty())
|
||||
return false;
|
||||
return m_projectModel->canEdit(indexes.front());
|
||||
}
|
||||
|
||||
void MainWindow::onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
|
||||
{
|
||||
if (!m_projectModel)
|
||||
return;
|
||||
if (selected.indexes().empty())
|
||||
{
|
||||
setOutlineEditEnabled(false);
|
||||
return;
|
||||
}
|
||||
setOutlineEditEnabled(m_projectModel->canEdit(selected.indexes().front()));
|
||||
}
|
||||
|
||||
void MainWindow::onTextSelect()
|
||||
{
|
||||
if (QLineEdit* le = qobject_cast<QLineEdit*>(sender()))
|
||||
|
|
|
@ -12,13 +12,15 @@
|
|||
#include "boo/audiodev/IAudioVoiceEngine.hpp"
|
||||
#include "ProjectModel.hpp"
|
||||
#include "EditorWidget.hpp"
|
||||
#include "MIDIReader.hpp"
|
||||
|
||||
#define MaxRecentFiles 4
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
}
|
||||
|
||||
class MainWindow;
|
||||
class AudioGroupModel;
|
||||
class SongGroupEditor;
|
||||
class SoundGroupEditor;
|
||||
class SoundMacroEditor;
|
||||
|
@ -67,12 +69,14 @@ public:
|
|||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
friend class MIDIReader;
|
||||
Q_OBJECT
|
||||
Ui::MainWindow m_ui;
|
||||
QAction* m_clearRecentFileAct;
|
||||
QAction* m_recentFileActs[MaxRecentFiles];
|
||||
TreeDelegate m_treeDelegate;
|
||||
UIMessenger m_mainMessenger;
|
||||
ProjectModel* m_projectModel = nullptr;
|
||||
AudioGroupModel* m_focusAudioGroup = nullptr;
|
||||
QWidget* m_faceSvg;
|
||||
SongGroupEditor* m_songGroupEditor = nullptr;
|
||||
SoundGroupEditor* m_soundGroupEditor = nullptr;
|
||||
|
@ -83,8 +87,14 @@ class MainWindow : public QMainWindow
|
|||
LayersEditor* m_layersEditor = nullptr;
|
||||
|
||||
std::unique_ptr<boo::IAudioVoiceEngine> m_voxEngine;
|
||||
std::unique_ptr<amuse::BooBackendVoiceAllocator> m_voxAllocator;
|
||||
std::unique_ptr<VoiceAllocator> m_voxAllocator;
|
||||
std::unique_ptr<amuse::Engine> m_engine;
|
||||
std::shared_ptr<amuse::Voice> m_lastSound;
|
||||
int m_velocity = 90;
|
||||
int m_modulation = 0;
|
||||
float m_pitch = 0.f;
|
||||
bool m_sustain = false;
|
||||
bool m_uiDisabled = false;
|
||||
|
||||
QUndoStack* m_undoStack;
|
||||
|
||||
|
@ -100,10 +110,14 @@ class MainWindow : public QMainWindow
|
|||
|
||||
void connectMessenger(UIMessenger* messenger, Qt::ConnectionType type);
|
||||
|
||||
void updateRecentFileActions();
|
||||
bool setProjectPath(const QString& path);
|
||||
void setFocusAudioGroup(AudioGroupModel* group);
|
||||
void refreshAudioIO();
|
||||
void refreshMIDIIO();
|
||||
void timerEvent(QTimerEvent* ev);
|
||||
void setSustain(bool sustain);
|
||||
void keyPressEvent(QKeyEvent* ev);
|
||||
void keyReleaseEvent(QKeyEvent* ev);
|
||||
|
||||
void startBackgroundTask(const QString& windowTitle, const QString& label,
|
||||
std::function<void(BackgroundTask&)>&& task);
|
||||
|
@ -114,6 +128,8 @@ public:
|
|||
explicit MainWindow(QWidget* parent = Q_NULLPTR);
|
||||
~MainWindow();
|
||||
|
||||
bool openProject(const QString& path);
|
||||
|
||||
bool openEditor(ProjectModel::SongGroupNode* node);
|
||||
bool openEditor(ProjectModel::SoundGroupNode* node);
|
||||
bool openEditor(ProjectModel::SoundMacroNode* node);
|
||||
|
@ -124,11 +140,19 @@ public:
|
|||
bool openEditor(ProjectModel::INode* node);
|
||||
void closeEditor();
|
||||
|
||||
ProjectModel::INode* getEditorNode() const;
|
||||
void pushUndoCommand(QUndoCommand* cmd);
|
||||
void aboutToDeleteNode(ProjectModel::INode* node);
|
||||
|
||||
ProjectModel* projectModel() const { return m_projectModel; }
|
||||
|
||||
public slots:
|
||||
void newAction();
|
||||
void openAction();
|
||||
void openRecentFileAction();
|
||||
void clearRecentFilesAction();
|
||||
void saveAction();
|
||||
void revertAction();
|
||||
void importAction();
|
||||
void exportAction();
|
||||
|
||||
|
@ -147,7 +171,22 @@ public slots:
|
|||
void setAudioIO();
|
||||
void setMIDIIO();
|
||||
|
||||
void notePressed(int key);
|
||||
void noteReleased();
|
||||
void velocityChanged(int vel);
|
||||
void modulationChanged(int mod);
|
||||
void pitchChanged(int pitch);
|
||||
void killSounds();
|
||||
|
||||
void outlineCutAction();
|
||||
void outlineCopyAction();
|
||||
void outlinePasteAction();
|
||||
void outlineDeleteAction();
|
||||
|
||||
void onFocusChanged(QWidget* old, QWidget* now);
|
||||
void setOutlineEditEnabled(bool enabled);
|
||||
bool canEditOutline();
|
||||
void onOutlineSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
|
||||
void onTextSelect();
|
||||
void onTextDelete();
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1024</width>
|
||||
<width>1360</width>
|
||||
<height>768</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
@ -98,22 +98,22 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>764</width>
|
||||
<width>1100</width>
|
||||
<height>610</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QScrollArea" name="keyboardScrollArea">
|
||||
<widget class="QWidget" name="keyboard" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<width>0</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
|
@ -123,46 +123,157 @@
|
|||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="KeyboardWidget" name="keyboardContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1501</width>
|
||||
<height>85</height>
|
||||
</rect>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>1501</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
</widget>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="keyboardScrollArea">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="KeyboardWidget" name="keyboardContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1501</width>
|
||||
<height>85</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>1501</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="VelocitySlider" name="velocitySlider">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>127</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>90</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ModulationSlider" name="modulationSlider">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>127</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="PitchSlider" name="pitchSlider">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>-2048</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2048</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
|
@ -174,7 +285,7 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1024</width>
|
||||
<width>1360</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
@ -182,8 +293,20 @@
|
|||
<property name="title">
|
||||
<string>&File</string>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuRecent_Projects">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Recent &Projects</string>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="actionNew_Project"/>
|
||||
<addaction name="actionOpen_Project"/>
|
||||
<addaction name="menuRecent_Projects"/>
|
||||
<addaction name="actionSave_Project"/>
|
||||
<addaction name="actionRevert_Project"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionImport"/>
|
||||
<addaction name="actionExport_GameCube_Groups"/>
|
||||
</widget>
|
||||
|
@ -417,6 +540,22 @@
|
|||
<string>New &Curve</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSave_Project">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Save Project</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRevert_Project">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Revert Project</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
@ -430,6 +569,21 @@
|
|||
<extends>QStatusBar</extends>
|
||||
<header>StatusBarWidget.hpp</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ModulationSlider</class>
|
||||
<extends>QSlider</extends>
|
||||
<header>KeyboardWidget.hpp</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>PitchSlider</class>
|
||||
<extends>QSlider</extends>
|
||||
<header>KeyboardWidget.hpp</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>VelocitySlider</class>
|
||||
<extends>QSlider</extends>
|
||||
<header>KeyboardWidget.hpp</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
|
|
@ -3,13 +3,98 @@
|
|||
#include "ProjectModel.hpp"
|
||||
#include "Common.hpp"
|
||||
#include "athena/YAMLDocWriter.hpp"
|
||||
#include "MainWindow.hpp"
|
||||
#include <QUndoCommand>
|
||||
|
||||
QIcon ProjectModel::GroupNode::Icon;
|
||||
QIcon ProjectModel::SongGroupNode::Icon;
|
||||
QIcon ProjectModel::SoundGroupNode::Icon;
|
||||
|
||||
NullItemProxyModel::NullItemProxyModel(ProjectModel* source)
|
||||
: QIdentityProxyModel(source)
|
||||
{
|
||||
setSourceModel(source);
|
||||
}
|
||||
|
||||
QModelIndex NullItemProxyModel::mapFromSource(const QModelIndex& sourceIndex) const
|
||||
{
|
||||
if (!sourceIndex.isValid())
|
||||
return QModelIndex();
|
||||
if (sourceIndex.row() == sourceModel()->rowCount(sourceIndex.parent()))
|
||||
return createIndex(0, sourceIndex.column(), sourceIndex.internalPointer());
|
||||
return createIndex(sourceIndex.row() + 1, sourceIndex.column(), sourceIndex.internalPointer());
|
||||
}
|
||||
|
||||
QModelIndex NullItemProxyModel::mapToSource(const QModelIndex& proxyIndex) const
|
||||
{
|
||||
if (!proxyIndex.isValid())
|
||||
return QModelIndex();
|
||||
return static_cast<ProjectModel*>(sourceModel())->
|
||||
proxyCreateIndex(proxyIndex.row() - 1, proxyIndex.column(), proxyIndex.internalPointer());
|
||||
}
|
||||
|
||||
int NullItemProxyModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
return QIdentityProxyModel::rowCount(parent) + 1;
|
||||
}
|
||||
|
||||
QModelIndex NullItemProxyModel::index(int row, int column, const QModelIndex& parent) const
|
||||
{
|
||||
const QModelIndex sourceParent = mapToSource(parent);
|
||||
const QModelIndex sourceIndex = sourceModel()->index(row - 1, column, sourceParent);
|
||||
return mapFromSource(sourceIndex);
|
||||
}
|
||||
|
||||
QVariant NullItemProxyModel::data(const QModelIndex& proxyIndex, int role) const
|
||||
{
|
||||
if (!proxyIndex.isValid() || proxyIndex.row() == 0)
|
||||
return QVariant();
|
||||
return QIdentityProxyModel::data(proxyIndex, role);
|
||||
}
|
||||
|
||||
ProjectModel::INode::INode(INode* parent, int row) : m_parent(parent), m_row(row)
|
||||
{
|
||||
m_nullChild = std::make_unique<NullNode>(this);
|
||||
}
|
||||
|
||||
ProjectModel::CollectionNode* ProjectModel::GroupNode::getCollectionOfType(Type tp) const
|
||||
{
|
||||
for (auto it = m_children.rbegin(); it != m_children.rend(); ++it)
|
||||
{
|
||||
if ((*it)->type() == Type::Collection)
|
||||
{
|
||||
CollectionNode* col = static_cast<CollectionNode*>(it->get());
|
||||
if (col->collectionType() == tp)
|
||||
return col;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ProjectModel::CollectionNode::indexOfId(amuse::ObjectId id) const
|
||||
{
|
||||
int ret = 0;
|
||||
for (auto& n : m_children)
|
||||
{
|
||||
if (static_cast<BasePoolObjectNode*>(n.get())->id() == id)
|
||||
return ret;
|
||||
++ret;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
amuse::ObjectId ProjectModel::CollectionNode::idOfIndex(int idx) const
|
||||
{
|
||||
return static_cast<BasePoolObjectNode*>(m_children[idx].get())->id();
|
||||
}
|
||||
|
||||
ProjectModel::BasePoolObjectNode* ProjectModel::CollectionNode::nodeOfIndex(int idx) const
|
||||
{
|
||||
return static_cast<BasePoolObjectNode*>(m_children[idx].get());
|
||||
}
|
||||
|
||||
ProjectModel::ProjectModel(const QString& path, QObject* parent)
|
||||
: QAbstractItemModel(parent), m_dir(path)
|
||||
: QAbstractItemModel(parent), m_dir(path), m_nullProxy(this)
|
||||
{
|
||||
m_root = std::make_shared<RootNode>();
|
||||
|
||||
|
@ -43,8 +128,8 @@ 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);
|
||||
//grp.setIdDatabases();
|
||||
//amuse::AudioGroupProject::BootstrapObjectIDs(data);
|
||||
|
||||
if (!MkPath(m_dir.path(), messenger))
|
||||
return false;
|
||||
|
@ -120,15 +205,13 @@ void ProjectModel::_resetModelData()
|
|||
gn.makeChild<SongGroupNode>(grp.first, grp.second.get());
|
||||
for (const auto& grp : SortUnorderedMap(sfxGroups))
|
||||
gn.makeChild<SoundGroupNode>(grp.first, grp.second.get());
|
||||
if (soundMacros.size())
|
||||
{
|
||||
CollectionNode& col =
|
||||
gn.makeChild<CollectionNode>(tr("Sound Macros"), QIcon(":/icons/IconSoundMacro.svg"));
|
||||
gn.makeChild<CollectionNode>(tr("Sound Macros"), QIcon(":/icons/IconSoundMacro.svg"), INode::Type::SoundMacro);
|
||||
col.reserve(soundMacros.size());
|
||||
for (const auto& macro : SortUnorderedMap(soundMacros))
|
||||
col.makeChild<SoundMacroNode>(macro.first, macro.second.get());
|
||||
}
|
||||
if (tables.size())
|
||||
{
|
||||
auto tablesSort = SortUnorderedMap(tables);
|
||||
size_t ADSRCount = 0;
|
||||
|
@ -141,10 +224,9 @@ void ProjectModel::_resetModelData()
|
|||
else if (tp == amuse::ITable::Type::Curve)
|
||||
curveCount += 1;
|
||||
}
|
||||
if (ADSRCount)
|
||||
{
|
||||
CollectionNode& col =
|
||||
gn.makeChild<CollectionNode>(tr("ADSRs"), QIcon(":/icons/IconADSR.svg"));
|
||||
gn.makeChild<CollectionNode>(tr("ADSRs"), QIcon(":/icons/IconADSR.svg"), INode::Type::ADSR);
|
||||
col.reserve(ADSRCount);
|
||||
for (auto& t : tablesSort)
|
||||
{
|
||||
|
@ -153,10 +235,9 @@ void ProjectModel::_resetModelData()
|
|||
col.makeChild<ADSRNode>(t.first, t.second.get());
|
||||
}
|
||||
}
|
||||
if (curveCount)
|
||||
{
|
||||
CollectionNode& col =
|
||||
gn.makeChild<CollectionNode>(tr("Curves"), QIcon(":/icons/IconCurve.svg"));
|
||||
gn.makeChild<CollectionNode>(tr("Curves"), QIcon(":/icons/IconCurve.svg"), INode::Type::Curve);
|
||||
col.reserve(curveCount);
|
||||
for (auto& t : tablesSort)
|
||||
{
|
||||
|
@ -166,18 +247,16 @@ void ProjectModel::_resetModelData()
|
|||
}
|
||||
}
|
||||
}
|
||||
if (keymaps.size())
|
||||
{
|
||||
CollectionNode& col =
|
||||
gn.makeChild<CollectionNode>(tr("Keymaps"), QIcon(":/icons/IconKeymap.svg"));
|
||||
gn.makeChild<CollectionNode>(tr("Keymaps"), QIcon(":/icons/IconKeymap.svg"), INode::Type::Keymap);
|
||||
col.reserve(keymaps.size());
|
||||
for (auto& keymap : SortUnorderedMap(keymaps))
|
||||
col.makeChild<KeymapNode>(keymap.first, keymap.second.get());
|
||||
}
|
||||
if (layers.size())
|
||||
{
|
||||
CollectionNode& col =
|
||||
gn.makeChild<CollectionNode>(tr("Layers"), QIcon(":/icons/IconLayers.svg"));
|
||||
gn.makeChild<CollectionNode>(tr("Layers"), QIcon(":/icons/IconLayers.svg"), INode::Type::Layer);
|
||||
col.reserve(layers.size());
|
||||
for (auto& keymap : SortUnorderedMap(layers))
|
||||
col.makeChild<LayersNode>(keymap.first, keymap.second.get());
|
||||
|
@ -195,8 +274,30 @@ void ProjectModel::ensureModelData()
|
|||
}
|
||||
}
|
||||
|
||||
QModelIndex ProjectModel::proxyCreateIndex(int arow, int acolumn, void *adata) const
|
||||
{
|
||||
if (arow < 0)
|
||||
{
|
||||
INode* childItem = static_cast<INode*>(adata);
|
||||
return createIndex(childItem->parent()->childCount(), acolumn, adata);
|
||||
}
|
||||
return createIndex(arow, acolumn, adata);
|
||||
}
|
||||
|
||||
QModelIndex ProjectModel::index(int row, int column, const QModelIndex& parent) const
|
||||
{
|
||||
if (row < 0)
|
||||
{
|
||||
INode* parentItem;
|
||||
if (!parent.isValid())
|
||||
parentItem = m_root.get();
|
||||
else
|
||||
parentItem = static_cast<INode*>(parent.internalPointer());
|
||||
|
||||
INode* childItem = parentItem->nullChild();
|
||||
return createIndex(childItem->row(), column, childItem);
|
||||
}
|
||||
|
||||
if (!hasIndex(row, column, parent))
|
||||
return QModelIndex();
|
||||
|
||||
|
@ -213,6 +314,13 @@ QModelIndex ProjectModel::index(int row, int column, const QModelIndex& parent)
|
|||
return QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex ProjectModel::index(INode* node) const
|
||||
{
|
||||
if (node == m_root.get())
|
||||
return QModelIndex();
|
||||
return createIndex(node->row(), 0, node);
|
||||
}
|
||||
|
||||
QModelIndex ProjectModel::parent(const QModelIndex& index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
|
@ -267,22 +375,73 @@ Qt::ItemFlags ProjectModel::flags(const QModelIndex& index) const
|
|||
if (!index.isValid())
|
||||
return 0;
|
||||
|
||||
return QAbstractItemModel::flags(index);
|
||||
return static_cast<INode*>(index.internalPointer())->flags();
|
||||
}
|
||||
|
||||
ProjectModel::INode* ProjectModel::node(const QModelIndex& index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return nullptr;
|
||||
return m_root.get();
|
||||
return static_cast<INode*>(index.internalPointer());
|
||||
}
|
||||
|
||||
bool ProjectModel::canDelete() const
|
||||
ProjectModel::GroupNode* ProjectModel::getGroupNode(INode* node) const
|
||||
{
|
||||
return false;
|
||||
if (!node)
|
||||
return nullptr;
|
||||
if (node->type() == INode::Type::Group)
|
||||
return static_cast<GroupNode*>(node);
|
||||
return getGroupNode(node->parent());
|
||||
}
|
||||
|
||||
void ProjectModel::del()
|
||||
bool ProjectModel::canEdit(const QModelIndex& index) const
|
||||
{
|
||||
|
||||
if (!index.isValid())
|
||||
return false;
|
||||
return (static_cast<INode*>(index.internalPointer())->flags() & Qt::ItemIsSelectable) != Qt::NoItemFlags;
|
||||
}
|
||||
|
||||
class DeleteNodeUndoCommand : public QUndoCommand
|
||||
{
|
||||
QModelIndex m_deleteIdx;
|
||||
std::shared_ptr<ProjectModel::INode> m_node;
|
||||
public:
|
||||
DeleteNodeUndoCommand(const QModelIndex& index)
|
||||
: QUndoCommand(QUndoStack::tr("Delete %1").arg(index.data().toString())), m_deleteIdx(index) {}
|
||||
void undo()
|
||||
{
|
||||
g_MainWindow->projectModel()->_undoDel(m_deleteIdx, std::move(m_node));
|
||||
m_node.reset();
|
||||
}
|
||||
void redo()
|
||||
{
|
||||
m_node = g_MainWindow->projectModel()->_redoDel(m_deleteIdx);
|
||||
}
|
||||
};
|
||||
|
||||
void ProjectModel::_undoDel(const QModelIndex& index, std::shared_ptr<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)
|
||||
{
|
||||
node(index)->depthTraverse([](INode* node)
|
||||
{
|
||||
g_MainWindow->aboutToDeleteNode(node);
|
||||
return true;
|
||||
});
|
||||
beginRemoveRows(index.parent(), index.row(), index.row());
|
||||
std::shared_ptr<ProjectModel::INode> ret = node(index.parent())->removeChild(index.row());
|
||||
endRemoveRows();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ProjectModel::del(const QModelIndex& index)
|
||||
{
|
||||
if (!index.isValid())
|
||||
return;
|
||||
g_MainWindow->pushUndoCommand(new DeleteNodeUndoCommand(index));
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define AMUSE_PROJECT_MODEL_HPP
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QIdentityProxyModel>
|
||||
#include <QDir>
|
||||
#include <QIcon>
|
||||
#include <map>
|
||||
|
@ -12,6 +13,20 @@
|
|||
#include "amuse/AudioGroupPool.hpp"
|
||||
#include "amuse/AudioGroupSampleDirectory.hpp"
|
||||
|
||||
class ProjectModel;
|
||||
|
||||
class NullItemProxyModel : public QIdentityProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit NullItemProxyModel(ProjectModel* source);
|
||||
QModelIndex mapFromSource(const QModelIndex& sourceIndex) const;
|
||||
QModelIndex mapToSource(const QModelIndex& proxyIndex) const;
|
||||
int rowCount(const QModelIndex& parent) const;
|
||||
QModelIndex index(int row, int column, const QModelIndex& parent) const;
|
||||
QVariant data(const QModelIndex& proxyIndex, int role) const;
|
||||
};
|
||||
|
||||
class ProjectModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -25,6 +40,7 @@ public:
|
|||
|
||||
private:
|
||||
QDir m_dir;
|
||||
NullItemProxyModel m_nullProxy;
|
||||
|
||||
amuse::ProjectDatabase m_projectDatabase;
|
||||
std::map<QString, amuse::AudioGroupDatabase> m_groups;
|
||||
|
@ -35,6 +51,7 @@ public:
|
|||
public:
|
||||
enum class Type
|
||||
{
|
||||
Null,
|
||||
Root,
|
||||
Group, // Top-level group
|
||||
SongGroup,
|
||||
|
@ -46,30 +63,79 @@ public:
|
|||
Keymap,
|
||||
Layer
|
||||
};
|
||||
private:
|
||||
protected:
|
||||
INode* m_parent;
|
||||
std::vector<std::shared_ptr<INode>> m_children;
|
||||
std::unique_ptr<INode> m_nullChild;
|
||||
int m_row;
|
||||
public:
|
||||
virtual ~INode() = default;
|
||||
INode(INode* parent, int row) : m_parent(parent), m_row(row) {}
|
||||
INode(INode* parent) : m_parent(parent), m_row(0)
|
||||
{
|
||||
/* ONLY USED BY NULL NODE! */
|
||||
}
|
||||
INode(INode* parent, int row);
|
||||
|
||||
int childCount() const { return int(m_children.size()); }
|
||||
INode* child(int row) const { return m_children[row].get(); }
|
||||
INode* child(int row) const
|
||||
{
|
||||
if (row == m_children.size())
|
||||
return nullChild();
|
||||
return m_children[row].get();
|
||||
}
|
||||
INode* nullChild() const { return m_nullChild.get(); }
|
||||
INode* parent() const { return m_parent; }
|
||||
int row() const { return m_row; }
|
||||
|
||||
void reindexRows(int row)
|
||||
{
|
||||
for (auto it = m_children.begin() + row; it != m_children.end(); ++it)
|
||||
(*it)->m_row = row++;
|
||||
m_nullChild->m_row = row;
|
||||
}
|
||||
|
||||
void insertChild(int row, std::shared_ptr<INode>&& n)
|
||||
{
|
||||
m_children.insert(m_children.begin() + row, std::move(n));
|
||||
reindexRows(row);
|
||||
}
|
||||
std::shared_ptr<INode> removeChild(int row)
|
||||
{
|
||||
std::shared_ptr<INode> ret = std::move(m_children[row]);
|
||||
m_children.erase(m_children.begin() + row);
|
||||
reindexRows(row);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void reserve(size_t sz) { m_children.reserve(sz); }
|
||||
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)...));
|
||||
m_nullChild->m_row = int(m_children.size());
|
||||
return static_cast<T&>(*m_children.back());
|
||||
}
|
||||
|
||||
bool depthTraverse(const std::function<bool(INode* node)>& func)
|
||||
{
|
||||
for (auto& n : m_children)
|
||||
if (!n->depthTraverse(func))
|
||||
break;
|
||||
return func(this);
|
||||
}
|
||||
|
||||
virtual Type type() const = 0;
|
||||
virtual QString text() const = 0;
|
||||
virtual QIcon icon() const = 0;
|
||||
virtual Qt::ItemFlags flags() const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; }
|
||||
};
|
||||
struct NullNode : INode
|
||||
{
|
||||
NullNode(INode* parent) : INode(parent) {}
|
||||
|
||||
Type type() const { return Type::Null; }
|
||||
QString text() const { return {}; }
|
||||
QIcon icon() const { return {}; }
|
||||
};
|
||||
struct RootNode : INode
|
||||
{
|
||||
|
@ -78,7 +144,9 @@ public:
|
|||
Type type() const { return Type::Root; }
|
||||
QString text() const { return {}; }
|
||||
QIcon icon() const { return {}; }
|
||||
Qt::ItemFlags flags() const { return Qt::ItemIsEnabled; }
|
||||
};
|
||||
struct CollectionNode;
|
||||
struct GroupNode : INode
|
||||
{
|
||||
std::map<QString, amuse::AudioGroupDatabase>::iterator m_it;
|
||||
|
@ -90,6 +158,9 @@ public:
|
|||
QString text() const { return m_it->first; }
|
||||
QIcon icon() const { return Icon; }
|
||||
|
||||
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()); }
|
||||
};
|
||||
|
@ -125,28 +196,42 @@ public:
|
|||
std::shared_ptr<SoundGroupNode> shared_from_this()
|
||||
{ return std::static_pointer_cast<SoundGroupNode>(INode::shared_from_this()); }
|
||||
};
|
||||
struct BasePoolObjectNode;
|
||||
struct CollectionNode : INode
|
||||
{
|
||||
QString m_name;
|
||||
QIcon m_icon;
|
||||
CollectionNode(INode* parent, int row, const QString& name, const QIcon& icon)
|
||||
: INode(parent, row), m_name(name), m_icon(icon) {}
|
||||
Type m_collectionType;
|
||||
CollectionNode(INode* parent, int row, const QString& name, const QIcon& icon, Type collectionType)
|
||||
: INode(parent, row), m_name(name), m_icon(icon), m_collectionType(collectionType) {}
|
||||
|
||||
Type type() const { return Type::Collection; }
|
||||
QString text() const { return m_name; }
|
||||
QIcon icon() const { return m_icon; }
|
||||
Qt::ItemFlags flags() const { return Qt::ItemIsEnabled; }
|
||||
|
||||
Type collectionType() const { return m_collectionType; }
|
||||
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()); }
|
||||
};
|
||||
template <class ID, class T, INode::Type TP>
|
||||
struct PoolObjectNode : INode
|
||||
struct BasePoolObjectNode : INode
|
||||
{
|
||||
amuse::ObjectId m_id;
|
||||
BasePoolObjectNode(INode* parent, int row, amuse::ObjectId id)
|
||||
: INode(parent, row), m_id(id) {}
|
||||
amuse::ObjectId id() const { return m_id; }
|
||||
};
|
||||
template <class ID, class T, INode::Type TP>
|
||||
struct PoolObjectNode : BasePoolObjectNode
|
||||
{
|
||||
ID m_id;
|
||||
QString m_name;
|
||||
std::shared_ptr<T> m_obj;
|
||||
PoolObjectNode(INode* parent, int row, ID id, std::shared_ptr<T> obj)
|
||||
: INode(parent, row), m_id(id), m_name(ID::CurNameDB->resolveNameFromId(id).data()), m_obj(obj) {}
|
||||
: BasePoolObjectNode(parent, row, id), m_name(ID::CurNameDB->resolveNameFromId(id).data()), m_obj(obj) {}
|
||||
|
||||
Type type() const { return TP; }
|
||||
QString text() const { return m_name; }
|
||||
|
@ -177,22 +262,23 @@ public:
|
|||
|
||||
void ensureModelData();
|
||||
|
||||
QModelIndex proxyCreateIndex(int arow, int acolumn, void *adata) const;
|
||||
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const;
|
||||
QModelIndex index(INode* node) const;
|
||||
QModelIndex parent(const QModelIndex& child) const;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const;
|
||||
int columnCount(const QModelIndex& parent = QModelIndex()) const;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
|
||||
Qt::ItemFlags flags(const QModelIndex& index) const;
|
||||
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 del(const QModelIndex& index);
|
||||
|
||||
QString path() const { return m_dir.path(); }
|
||||
bool canDelete() const;
|
||||
|
||||
public slots:
|
||||
void del();
|
||||
|
||||
signals:
|
||||
void canDeleteChanged(bool canDelete);
|
||||
NullItemProxyModel* getNullProxy() { return &m_nullProxy; }
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -10,8 +10,124 @@
|
|||
#include <QApplication>
|
||||
#include <QCheckBox>
|
||||
|
||||
CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::CmdOp op, QWidget* parent)
|
||||
: QWidget(parent), m_cmd(cmd), m_introspection(amuse::SoundMacro::GetCmdIntrospection(op))
|
||||
FieldProjectNode::FieldProjectNode(ProjectModel::CollectionNode* collection, QWidget* parent)
|
||||
: FieldComboBox(parent), m_collection(collection)
|
||||
{
|
||||
ProjectModel* model = g_MainWindow->projectModel();
|
||||
setModel(model->getNullProxy());
|
||||
setRootModelIndex(model->getNullProxy()->mapFromSource(model->index(collection)));
|
||||
}
|
||||
|
||||
TargetButton::TargetButton(QWidget* parent)
|
||||
: QPushButton(parent)
|
||||
{
|
||||
QIcon targetIcon(QStringLiteral(":/icons/IconSoundMacroTarget.svg"));
|
||||
targetIcon.addFile(QStringLiteral(":/icons/IconSoundMacroTargetDisabled.svg"), QSize(), QIcon::Disabled);
|
||||
setIcon(targetIcon);
|
||||
setToolTip(tr("Set step with target click"));
|
||||
setFixedSize(29, 29);
|
||||
}
|
||||
|
||||
SoundMacroEditor* FieldSoundMacroStep::getEditor() const
|
||||
{
|
||||
return qobject_cast<SoundMacroEditor*>(
|
||||
parentWidget()->parentWidget()->parentWidget()->
|
||||
parentWidget()->parentWidget()->parentWidget()->parentWidget());
|
||||
}
|
||||
|
||||
SoundMacroListing* FieldSoundMacroStep::getListing() const
|
||||
{
|
||||
return qobject_cast<SoundMacroListing*>(
|
||||
parentWidget()->parentWidget()->parentWidget());
|
||||
}
|
||||
|
||||
void FieldSoundMacroStep::targetPressed()
|
||||
{
|
||||
ProjectModel::SoundMacroNode* node = nullptr;
|
||||
if (m_macroField)
|
||||
{
|
||||
int val = m_macroField->currentIndex();
|
||||
if (val != 0)
|
||||
node = static_cast<ProjectModel::SoundMacroNode*>(m_macroField->collection()->nodeOfIndex(val - 1));
|
||||
}
|
||||
|
||||
if (!m_macroField || node == getListing()->currentNode())
|
||||
if (SoundMacroEditor* editor = getEditor())
|
||||
editor->beginStepTarget(this);
|
||||
}
|
||||
|
||||
void FieldSoundMacroStep::updateMacroField()
|
||||
{
|
||||
if (!m_macroField)
|
||||
{
|
||||
int numCmds = int(static_cast<ProjectModel::SoundMacroNode*>(
|
||||
getListing()->currentNode())->m_obj->m_cmds.size());
|
||||
m_spinBox.setMaximum(numCmds - 1);
|
||||
m_spinBox.setDisabled(false);
|
||||
m_targetButton.setDisabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
int val = m_macroField->currentIndex();
|
||||
if (val == 0)
|
||||
{
|
||||
m_spinBox.setValue(0);
|
||||
m_spinBox.setDisabled(true);
|
||||
m_targetButton.setDisabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProjectModel::SoundMacroNode* node = static_cast<ProjectModel::SoundMacroNode*>(
|
||||
m_macroField->collection()->nodeOfIndex(val - 1));
|
||||
int numCmds = int(node->m_obj->m_cmds.size());
|
||||
m_spinBox.setMaximum(numCmds - 1);
|
||||
m_spinBox.setDisabled(false);
|
||||
m_targetButton.setEnabled(node == getListing()->currentNode());
|
||||
}
|
||||
}
|
||||
|
||||
void FieldSoundMacroStep::setIndex(int index)
|
||||
{
|
||||
m_targetButton.setDown(false);
|
||||
m_spinBox.setValue(index);
|
||||
if (SoundMacroEditor* editor = getEditor())
|
||||
editor->endStepTarget();
|
||||
}
|
||||
|
||||
void FieldSoundMacroStep::cancel()
|
||||
{
|
||||
m_targetButton.setDown(false);
|
||||
if (SoundMacroEditor* editor = getEditor())
|
||||
editor->endStepTarget();
|
||||
}
|
||||
|
||||
FieldSoundMacroStep::~FieldSoundMacroStep()
|
||||
{
|
||||
if (SoundMacroEditor* editor = getEditor())
|
||||
if (editor->m_targetField == this)
|
||||
editor->endStepTarget();
|
||||
}
|
||||
|
||||
FieldSoundMacroStep::FieldSoundMacroStep(FieldProjectNode* macroField, QWidget* parent)
|
||||
: QWidget(parent), m_macroField(macroField)
|
||||
{
|
||||
QHBoxLayout* layout = new QHBoxLayout;
|
||||
layout->setContentsMargins(QMargins());
|
||||
layout->setSpacing(0);
|
||||
layout->addWidget(&m_spinBox);
|
||||
layout->addWidget(&m_targetButton);
|
||||
m_spinBox.setMinimum(0);
|
||||
m_spinBox.setDisabled(true);
|
||||
m_targetButton.setDisabled(true);
|
||||
connect(&m_spinBox, SIGNAL(valueChanged(int)), this, SIGNAL(valueChanged(int)));
|
||||
connect(&m_targetButton, SIGNAL(pressed()), this, SLOT(targetPressed()));
|
||||
if (macroField)
|
||||
connect(macroField, SIGNAL(currentIndexChanged(int)), this, SLOT(updateMacroField()));
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::CmdOp op, SoundMacroListing* listing)
|
||||
: QWidget(nullptr), m_cmd(cmd), m_introspection(amuse::SoundMacro::GetCmdIntrospection(op))
|
||||
{
|
||||
QFont titleFont = m_titleLabel.font();
|
||||
titleFont.setWeight(QFont::Bold);
|
||||
|
@ -44,6 +160,7 @@ CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::Cm
|
|||
m_deleteButton.setFixedSize(21, 21);
|
||||
m_deleteButton.setIcon(QIcon(QStringLiteral(":/icons/IconSoundMacroDelete.svg")));
|
||||
m_deleteButton.setFlat(true);
|
||||
m_deleteButton.setToolTip(tr("Delete this SoundMacro"));
|
||||
connect(&m_deleteButton, SIGNAL(clicked(bool)), this, SLOT(deleteClicked()));
|
||||
headLayout->addWidget(&m_deleteButton);
|
||||
}
|
||||
|
@ -58,6 +175,7 @@ CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::Cm
|
|||
{
|
||||
m_titleLabel.setText(tr(m_introspection->m_name.data()));
|
||||
m_titleLabel.setToolTip(tr(m_introspection->m_description.data()));
|
||||
FieldProjectNode* nf = nullptr;
|
||||
for (int f = 0; f < 7; ++f)
|
||||
{
|
||||
const amuse::SoundMacro::CmdIntrospection::Field& field = m_introspection->m_fields[f];
|
||||
|
@ -89,6 +207,7 @@ CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::Cm
|
|||
sb->setProperty("fieldName", fieldName);
|
||||
sb->setMinimum(int(field.m_min));
|
||||
sb->setMaximum(int(field.m_max));
|
||||
sb->setToolTip(QStringLiteral("[%1,%2]").arg(int(field.m_min)).arg(int(field.m_max)));
|
||||
switch (field.m_tp)
|
||||
{
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::Int8:
|
||||
|
@ -116,6 +235,44 @@ CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::Cm
|
|||
layout->addWidget(sb, 1, f);
|
||||
break;
|
||||
}
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroId:
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::TableId:
|
||||
{
|
||||
ProjectModel::INode::Type collectionType;
|
||||
if (field.m_tp == amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroId)
|
||||
{
|
||||
collectionType = ProjectModel::INode::Type::SoundMacro;
|
||||
}
|
||||
else if (field.m_tp == amuse::SoundMacro::CmdIntrospection::Field::Type::TableId)
|
||||
{
|
||||
if (!field.m_name.compare("ADSR"))
|
||||
collectionType = ProjectModel::INode::Type::ADSR;
|
||||
else
|
||||
collectionType = ProjectModel::INode::Type::Curve;
|
||||
}
|
||||
auto* collection = g_MainWindow->projectModel()->getGroupNode(listing->currentNode())->
|
||||
getCollectionOfType(collectionType);
|
||||
nf = new FieldProjectNode(collection);
|
||||
nf->setProperty("fieldIndex", f);
|
||||
nf->setProperty("fieldName", fieldName);
|
||||
int index = collection->indexOfId(
|
||||
amuse::AccessField<amuse::SoundMacroIdDNA<athena::Little>>(m_cmd, field).id);
|
||||
nf->setCurrentIndex(index < 0 ? 0 : index + 1);
|
||||
connect(nf, SIGNAL(currentIndexChanged(int)), this, SLOT(nodeChanged(int)));
|
||||
layout->addWidget(nf, 1, f);
|
||||
break;
|
||||
}
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep:
|
||||
{
|
||||
FieldSoundMacroStep* sb = new FieldSoundMacroStep(nf);
|
||||
sb->setProperty("fieldIndex", f);
|
||||
sb->setProperty("fieldName", fieldName);
|
||||
sb->m_spinBox.setValue(amuse::AccessField<amuse::SoundMacroStepDNA<athena::Little>>(m_cmd, field).step);
|
||||
connect(sb, SIGNAL(valueChanged(int)), this, SLOT(numChanged(int)));
|
||||
layout->addWidget(sb, 1, f);
|
||||
m_stepField = sb;
|
||||
break;
|
||||
}
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::Choice:
|
||||
{
|
||||
FieldComboBox* cb = new FieldComboBox;
|
||||
|
@ -128,7 +285,7 @@ CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::Cm
|
|||
cb->addItem(tr(field.m_choices[j].data()));
|
||||
}
|
||||
cb->setCurrentIndex(int(amuse::AccessField<int8_t>(m_cmd, field)));
|
||||
connect(cb, SIGNAL(currentIndexChanged(int)), this, SLOT(choiceChanged(int)));
|
||||
connect(cb, SIGNAL(currentIndexChanged(int)), this, SLOT(numChanged(int)));
|
||||
layout->addWidget(cb, 1, f);
|
||||
break;
|
||||
}
|
||||
|
@ -144,11 +301,11 @@ CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::Cm
|
|||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, QWidget* parent)
|
||||
: CommandWidget(cmd, cmd->Isa(), parent) {}
|
||||
CommandWidget::CommandWidget(amuse::SoundMacro::ICmd* cmd, SoundMacroListing* listing)
|
||||
: CommandWidget(cmd, cmd->Isa(), listing) {}
|
||||
|
||||
CommandWidget::CommandWidget(amuse::SoundMacro::CmdOp op, QWidget* parent)
|
||||
: CommandWidget(nullptr, op, parent) {}
|
||||
CommandWidget::CommandWidget(amuse::SoundMacro::CmdOp op, SoundMacroListing* listing)
|
||||
: CommandWidget(nullptr, op, listing) {}
|
||||
|
||||
class ValChangedUndoCommand : public EditorUndoCommand
|
||||
{
|
||||
|
@ -189,6 +346,12 @@ public:
|
|||
case amuse::SoundMacro::CmdIntrospection::Field::Type::UInt32:
|
||||
amuse::AccessField<uint32_t>(m_cmd, m_field) = uint32_t(m_undoVal);
|
||||
break;
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroId:
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep:
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::TableId:
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::SampleId:
|
||||
amuse::AccessField<amuse::SoundMacroIdDNA<athena::Little>>(m_cmd, m_field).id = uint16_t(m_undoVal);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -227,6 +390,13 @@ public:
|
|||
m_undoVal = amuse::AccessField<uint32_t>(m_cmd, m_field);
|
||||
amuse::AccessField<uint32_t>(m_cmd, m_field) = uint32_t(m_redoVal);
|
||||
break;
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroId:
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep:
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::TableId:
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::SampleId:
|
||||
m_undoVal = amuse::AccessField<amuse::SoundMacroIdDNA<athena::Little>>(m_cmd, m_field).id;
|
||||
amuse::AccessField<amuse::SoundMacroIdDNA<athena::Little>>(m_cmd, m_field).id = uint16_t(m_redoVal);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -250,11 +420,10 @@ void CommandWidget::boolChanged(int state)
|
|||
{
|
||||
if (m_introspection)
|
||||
{
|
||||
QCheckBox* cb = static_cast<QCheckBox*>(sender());
|
||||
const amuse::SoundMacro::CmdIntrospection::Field& field =
|
||||
m_introspection->m_fields[cb->property("fieldIndex").toInt()];
|
||||
g_MainWindow->pushUndoCommand(new ValChangedUndoCommand(m_cmd, cb->property("fieldName").toString(), field,
|
||||
state == Qt::Checked, getParent()->m_node));
|
||||
m_introspection->m_fields[sender()->property("fieldIndex").toInt()];
|
||||
g_MainWindow->pushUndoCommand(new ValChangedUndoCommand(m_cmd, sender()->property("fieldName").toString(),
|
||||
field, state == Qt::Checked, getParent()->m_node));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -262,23 +431,23 @@ void CommandWidget::numChanged(int value)
|
|||
{
|
||||
if (m_introspection)
|
||||
{
|
||||
FieldSpinBox* sb = static_cast<FieldSpinBox*>(sender());
|
||||
const amuse::SoundMacro::CmdIntrospection::Field& field =
|
||||
m_introspection->m_fields[sb->property("fieldIndex").toInt()];
|
||||
g_MainWindow->pushUndoCommand(new ValChangedUndoCommand(m_cmd, sb->property("fieldName").toString(), field,
|
||||
value, getParent()->m_node));
|
||||
m_introspection->m_fields[sender()->property("fieldIndex").toInt()];
|
||||
g_MainWindow->pushUndoCommand(new ValChangedUndoCommand(m_cmd, sender()->property("fieldName").toString(),
|
||||
field, value, getParent()->m_node));
|
||||
}
|
||||
}
|
||||
|
||||
void CommandWidget::choiceChanged(int choice)
|
||||
void CommandWidget::nodeChanged(int value)
|
||||
{
|
||||
if (m_introspection)
|
||||
{
|
||||
FieldComboBox* cb = static_cast<FieldComboBox*>(sender());
|
||||
FieldProjectNode* fieldW = static_cast<FieldProjectNode*>(sender());
|
||||
int v = value == 0 ? 65535 : fieldW->collection()->idOfIndex(value - 1).id;
|
||||
const amuse::SoundMacro::CmdIntrospection::Field& field =
|
||||
m_introspection->m_fields[cb->property("fieldIndex").toInt()];
|
||||
g_MainWindow->pushUndoCommand(new ValChangedUndoCommand(m_cmd, cb->property("fieldName").toString(), field,
|
||||
choice, getParent()->m_node));
|
||||
m_introspection->m_fields[fieldW->property("fieldIndex").toInt()];
|
||||
g_MainWindow->pushUndoCommand(new ValChangedUndoCommand(m_cmd, fieldW->property("fieldName").toString(),
|
||||
field, v, getParent()->m_node));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,6 +462,8 @@ void CommandWidget::setIndex(int index)
|
|||
{
|
||||
m_index = index;
|
||||
m_numberText.setText(QString::number(index));
|
||||
if (m_stepField)
|
||||
m_stepField->updateMacroField();
|
||||
update();
|
||||
}
|
||||
|
||||
|
@ -393,10 +564,10 @@ CommandWidgetContainer::CommandWidgetContainer(CommandWidget* child, QWidget* pa
|
|||
{
|
||||
setMinimumHeight(100);
|
||||
setContentsMargins(QMargins());
|
||||
QBoxLayout* outerLayout = new QVBoxLayout;
|
||||
QVBoxLayout* outerLayout = new QVBoxLayout;
|
||||
outerLayout->setAlignment(Qt::AlignBottom);
|
||||
outerLayout->setContentsMargins(QMargins());
|
||||
outerLayout->setSpacing(0);
|
||||
outerLayout->addStretch();
|
||||
outerLayout->addWidget(child);
|
||||
setLayout(outerLayout);
|
||||
}
|
||||
|
@ -472,13 +643,13 @@ public:
|
|||
void undo()
|
||||
{
|
||||
m_undid = true;
|
||||
std::static_pointer_cast<ProjectModel::SoundMacroNode>(m_node)->
|
||||
static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
|
||||
m_obj->swapPositions(m_a, m_b);
|
||||
EditorUndoCommand::undo();
|
||||
}
|
||||
void redo()
|
||||
{
|
||||
std::static_pointer_cast<ProjectModel::SoundMacroNode>(m_node)->
|
||||
static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
|
||||
m_obj->swapPositions(m_a, m_b);
|
||||
if (m_undid)
|
||||
EditorUndoCommand::redo();
|
||||
|
@ -605,7 +776,7 @@ public:
|
|||
: EditorUndoCommand(node, QUndoStack::tr("Insert %1").arg(text)), m_insertIdx(insertIdx) {}
|
||||
void undo()
|
||||
{
|
||||
m_cmd = std::static_pointer_cast<ProjectModel::SoundMacroNode>(m_node)->
|
||||
m_cmd = static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
|
||||
m_obj->deleteCmd(m_insertIdx);
|
||||
EditorUndoCommand::undo();
|
||||
}
|
||||
|
@ -613,7 +784,7 @@ public:
|
|||
{
|
||||
if (!m_cmd)
|
||||
return;
|
||||
std::static_pointer_cast<ProjectModel::SoundMacroNode>(m_node)->
|
||||
static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
|
||||
m_obj->insertCmd(m_insertIdx, std::move(m_cmd));
|
||||
m_cmd.reset();
|
||||
EditorUndoCommand::redo();
|
||||
|
@ -644,7 +815,7 @@ void SoundMacroListing::insert(amuse::SoundMacro::CmdOp op, const QString& text)
|
|||
|
||||
g_MainWindow->pushUndoCommand(new InsertCommandUndoCommand(insertIdx, text, m_node));
|
||||
m_layout->insertWidget(insertIdx,
|
||||
new CommandWidgetContainer(new CommandWidget(m_node->m_obj->insertNewCmd(insertIdx, op))));
|
||||
new CommandWidgetContainer(new CommandWidget(m_node->m_obj->insertNewCmd(insertIdx, op), this)));
|
||||
|
||||
stopAutoscroll();
|
||||
reindex();
|
||||
|
@ -661,14 +832,14 @@ public:
|
|||
void undo()
|
||||
{
|
||||
m_undid = true;
|
||||
std::static_pointer_cast<ProjectModel::SoundMacroNode>(m_node)->
|
||||
static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
|
||||
m_obj->insertCmd(m_deleteIdx, std::move(m_cmd));
|
||||
m_cmd.reset();
|
||||
EditorUndoCommand::undo();
|
||||
}
|
||||
void redo()
|
||||
{
|
||||
m_cmd = std::static_pointer_cast<ProjectModel::SoundMacroNode>(m_node)->
|
||||
m_cmd = static_cast<ProjectModel::SoundMacroNode*>(m_node.get())->
|
||||
m_obj->deleteCmd(m_deleteIdx);
|
||||
if (m_undid)
|
||||
EditorUndoCommand::redo();
|
||||
|
@ -712,9 +883,10 @@ bool SoundMacroListing::loadData(ProjectModel::SoundMacroNode* node)
|
|||
{
|
||||
if (cmd->Isa() == amuse::SoundMacro::CmdOp::End)
|
||||
break;
|
||||
m_layout->insertWidget(i++, new CommandWidgetContainer(new CommandWidget(cmd.get())));
|
||||
m_layout->insertWidget(i++, new CommandWidgetContainer(new CommandWidget(cmd.get(), this)));
|
||||
}
|
||||
reindex();
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -723,12 +895,18 @@ void SoundMacroListing::unloadData()
|
|||
m_node.reset();
|
||||
clear();
|
||||
reindex();
|
||||
update();
|
||||
}
|
||||
|
||||
ProjectModel::INode* SoundMacroListing::currentNode() const
|
||||
{
|
||||
return m_node.get();
|
||||
}
|
||||
|
||||
SoundMacroListing::SoundMacroListing(QWidget* parent)
|
||||
: QWidget(parent), m_layout(new QVBoxLayout)
|
||||
{
|
||||
m_layout->addWidget(new CommandWidgetContainer(new CommandWidget(amuse::SoundMacro::CmdOp::End)));
|
||||
m_layout->addWidget(new CommandWidgetContainer(new CommandWidget(amuse::SoundMacro::CmdOp::End, this)));
|
||||
m_layout->addStretch();
|
||||
setLayout(m_layout);
|
||||
reindex();
|
||||
|
@ -855,6 +1033,21 @@ void SoundMacroEditor::beginCatalogueDrag(CatalogueItem* item, const QPoint& eve
|
|||
m_draggedItem->show();
|
||||
}
|
||||
|
||||
void SoundMacroEditor::beginStepTarget(FieldSoundMacroStep* stepField)
|
||||
{
|
||||
m_targetField = stepField;
|
||||
m_catalogue->setDisabled(true);
|
||||
m_listing->setDisabled(true);
|
||||
setFocus();
|
||||
}
|
||||
|
||||
void SoundMacroEditor::endStepTarget()
|
||||
{
|
||||
m_targetField = nullptr;
|
||||
m_catalogue->setDisabled(false);
|
||||
m_listing->setDisabled(false);
|
||||
}
|
||||
|
||||
void SoundMacroEditor::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
if (m_catalogue->geometry().contains(event->pos()))
|
||||
|
@ -884,11 +1077,19 @@ void SoundMacroEditor::mousePressEvent(QMouseEvent* event)
|
|||
ch = ch->parentWidget();
|
||||
if (child)
|
||||
{
|
||||
if (m_targetField)
|
||||
{
|
||||
m_targetField->setIndex(m_listing->layout()->indexOf(child->parentWidget()));
|
||||
return;
|
||||
}
|
||||
QPoint fromParent2 = child->mapFrom(m_listing, fromParent1);
|
||||
beginCommandDrag(child, event->pos(), fromParent2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_targetField)
|
||||
m_targetField->cancel();
|
||||
}
|
||||
|
||||
void SoundMacroEditor::mouseReleaseEvent(QMouseEvent* event)
|
||||
|
@ -964,6 +1165,10 @@ void SoundMacroEditor::keyPressEvent(QKeyEvent* event)
|
|||
m_listing->cancelDrag();
|
||||
m_draggedCmd = nullptr;
|
||||
}
|
||||
else if (m_targetField)
|
||||
{
|
||||
m_targetField->cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -979,14 +1184,21 @@ void SoundMacroEditor::catalogueDoubleClicked(QTreeWidgetItem* item, int column)
|
|||
|
||||
bool SoundMacroEditor::loadData(ProjectModel::SoundMacroNode* node)
|
||||
{
|
||||
endStepTarget();
|
||||
return m_listing->loadData(node);
|
||||
}
|
||||
|
||||
void SoundMacroEditor::unloadData()
|
||||
{
|
||||
endStepTarget();
|
||||
m_listing->unloadData();
|
||||
}
|
||||
|
||||
ProjectModel::INode* SoundMacroEditor::currentNode() const
|
||||
{
|
||||
return m_listing->currentNode();
|
||||
}
|
||||
|
||||
SoundMacroEditor::SoundMacroEditor(QWidget* parent)
|
||||
: EditorWidget(parent), m_splitter(new QSplitter),
|
||||
m_listing(new SoundMacroListing), m_catalogue(new SoundMacroCatalogue)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <QTreeWidget>
|
||||
#include <QPushButton>
|
||||
|
||||
class SoundMacroEditor;
|
||||
class SoundMacroListing;
|
||||
class CatalogueItem;
|
||||
|
||||
|
@ -20,7 +21,7 @@ class FieldSpinBox : public QSpinBox
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FieldSpinBox(QWidget* parent = Q_NULLPTR)
|
||||
explicit FieldSpinBox(QWidget* parent = Q_NULLPTR)
|
||||
: QSpinBox(parent) {}
|
||||
|
||||
/* Don't scroll */
|
||||
|
@ -29,15 +30,54 @@ public:
|
|||
|
||||
class FieldComboBox : public QComboBox
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
FieldComboBox(QWidget* parent = Q_NULLPTR)
|
||||
explicit FieldComboBox(QWidget* parent = Q_NULLPTR)
|
||||
: QComboBox(parent) {}
|
||||
|
||||
/* Don't scroll */
|
||||
void wheelEvent(QWheelEvent* event) { event->ignore(); }
|
||||
};
|
||||
|
||||
class FieldProjectNode : public FieldComboBox
|
||||
{
|
||||
Q_OBJECT
|
||||
ProjectModel::CollectionNode* m_collection;
|
||||
public:
|
||||
explicit FieldProjectNode(ProjectModel::CollectionNode* collection, QWidget* parent = Q_NULLPTR);
|
||||
ProjectModel::CollectionNode* collection() const { return m_collection; }
|
||||
};
|
||||
|
||||
class TargetButton : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TargetButton(QWidget* parent = Q_NULLPTR);
|
||||
void mouseReleaseEvent(QMouseEvent* event) { event->ignore(); }
|
||||
void mouseMoveEvent(QMouseEvent* event) { event->ignore(); }
|
||||
};
|
||||
|
||||
class FieldSoundMacroStep : public QWidget
|
||||
{
|
||||
friend class CommandWidget;
|
||||
Q_OBJECT
|
||||
FieldProjectNode* m_macroField;
|
||||
FieldSpinBox m_spinBox;
|
||||
TargetButton m_targetButton;
|
||||
SoundMacroEditor* getEditor() const;
|
||||
SoundMacroListing* getListing() const;
|
||||
signals:
|
||||
void valueChanged(int);
|
||||
public slots:
|
||||
void targetPressed();
|
||||
void updateMacroField();
|
||||
public:
|
||||
explicit FieldSoundMacroStep(FieldProjectNode* macroField = Q_NULLPTR, QWidget* parent = Q_NULLPTR);
|
||||
~FieldSoundMacroStep();
|
||||
void setIndex(int index);
|
||||
void cancel();
|
||||
};
|
||||
|
||||
class CommandWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -49,18 +89,19 @@ class CommandWidget : public QWidget
|
|||
int m_index = -1;
|
||||
amuse::SoundMacro::ICmd* m_cmd;
|
||||
const amuse::SoundMacro::CmdIntrospection* m_introspection;
|
||||
FieldSoundMacroStep* m_stepField = nullptr;
|
||||
void setIndex(int index);
|
||||
SoundMacroListing* getParent() const;
|
||||
private slots:
|
||||
void boolChanged(int);
|
||||
void numChanged(int);
|
||||
void choiceChanged(int);
|
||||
void nodeChanged(int);
|
||||
void deleteClicked();
|
||||
private:
|
||||
CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::CmdOp op, QWidget* parent = Q_NULLPTR);
|
||||
CommandWidget(amuse::SoundMacro::ICmd* cmd, amuse::SoundMacro::CmdOp op, SoundMacroListing* listing);
|
||||
public:
|
||||
CommandWidget(amuse::SoundMacro::ICmd* cmd, QWidget* parent = Q_NULLPTR);
|
||||
CommandWidget(amuse::SoundMacro::CmdOp op, QWidget* parent = Q_NULLPTR);
|
||||
CommandWidget(amuse::SoundMacro::ICmd* cmd, SoundMacroListing* listing);
|
||||
CommandWidget(amuse::SoundMacro::CmdOp op, SoundMacroListing* listing);
|
||||
void paintEvent(QPaintEvent* event);
|
||||
QString getText() const { return m_titleLabel.text(); }
|
||||
};
|
||||
|
@ -113,6 +154,7 @@ public:
|
|||
explicit SoundMacroListing(QWidget* parent = Q_NULLPTR);
|
||||
bool loadData(ProjectModel::SoundMacroNode* node);
|
||||
void unloadData();
|
||||
ProjectModel::INode* currentNode() const;
|
||||
void timerEvent(QTimerEvent* event);
|
||||
};
|
||||
|
||||
|
@ -144,19 +186,24 @@ class SoundMacroEditor : public EditorWidget
|
|||
{
|
||||
Q_OBJECT
|
||||
friend class SoundMacroCatalogue;
|
||||
friend class FieldSoundMacroStep;
|
||||
QSplitter* m_splitter;
|
||||
SoundMacroListing* m_listing;
|
||||
SoundMacroCatalogue* m_catalogue;
|
||||
CommandWidget* m_draggedCmd = nullptr;
|
||||
CatalogueItem* m_draggedItem = nullptr;
|
||||
FieldSoundMacroStep* m_targetField = nullptr;
|
||||
QPoint m_draggedPt;
|
||||
int m_dragInsertIdx = -1;
|
||||
void beginCommandDrag(CommandWidget* widget, const QPoint& eventPt, const QPoint& pt);
|
||||
void beginCatalogueDrag(CatalogueItem* item, const QPoint& eventPt, const QPoint& pt);
|
||||
void beginStepTarget(FieldSoundMacroStep* stepField);
|
||||
void endStepTarget();
|
||||
public:
|
||||
explicit SoundMacroEditor(QWidget* parent = Q_NULLPTR);
|
||||
bool loadData(ProjectModel::SoundMacroNode* node);
|
||||
void unloadData();
|
||||
ProjectModel::INode* currentNode() const;
|
||||
|
||||
void mousePressEvent(QMouseEvent* event);
|
||||
void mouseReleaseEvent(QMouseEvent* event);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <QStatusBar>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
||||
class StatusBarFocus;
|
||||
|
||||
|
@ -10,15 +11,35 @@ class StatusBarWidget : public QStatusBar
|
|||
{
|
||||
friend class StatusBarFocus;
|
||||
Q_OBJECT
|
||||
QLabel* m_normalMessage;
|
||||
QLabel m_normalMessage;
|
||||
QPushButton m_killButton;
|
||||
QLabel m_voiceCount;
|
||||
int m_cachedVoiceCount = -1;
|
||||
StatusBarFocus* m_curFocus = nullptr;
|
||||
void setKillVisible(bool vis) { m_killButton.setVisible(vis); m_voiceCount.setVisible(vis); }
|
||||
public:
|
||||
explicit StatusBarWidget(QWidget* parent = Q_NULLPTR) : QStatusBar(parent)
|
||||
{
|
||||
m_normalMessage = new QLabel(this);
|
||||
addWidget(m_normalMessage);
|
||||
addWidget(&m_normalMessage);
|
||||
m_killButton.setIcon(QIcon(QStringLiteral(":/icons/IconKill.svg")));
|
||||
m_killButton.setVisible(false);
|
||||
m_killButton.setToolTip(tr("Immediately kill active voices"));
|
||||
m_voiceCount.setVisible(false);
|
||||
addPermanentWidget(&m_voiceCount);
|
||||
addPermanentWidget(&m_killButton);
|
||||
}
|
||||
void setNormalMessage(const QString& message) { m_normalMessage->setText(message); }
|
||||
void setNormalMessage(const QString& message) { m_normalMessage.setText(message); }
|
||||
void setVoiceCount(int voices)
|
||||
{
|
||||
if (voices != m_cachedVoiceCount)
|
||||
{
|
||||
m_voiceCount.setText(QString::number(voices));
|
||||
m_cachedVoiceCount = voices;
|
||||
setKillVisible(voices != 0);
|
||||
}
|
||||
}
|
||||
void connectKillClicked(const QObject* receiver, const char* method)
|
||||
{ connect(&m_killButton, SIGNAL(clicked(bool)), receiver, method); }
|
||||
};
|
||||
|
||||
class StatusBarFocus : public QObject
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 5.2916665 5.2916668"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||
sodipodi:docname="IconKill.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#353535"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="39.226909"
|
||||
inkscape:cx="9.3355483"
|
||||
inkscape:cy="10.583863"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="1452"
|
||||
inkscape:window-height="1061"
|
||||
inkscape:window-x="651"
|
||||
inkscape:window-y="170"
|
||||
inkscape:window-maximized="0"
|
||||
gridtolerance="10"
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid817"
|
||||
empspacing="0"
|
||||
spacingx="0.52916666"
|
||||
spacingy="0.52916666"
|
||||
visible="false" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-291.70832)">
|
||||
<path
|
||||
style="fill:#fffff8;fill-opacity:1;stroke:#000000;stroke-width:0.30217573px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 1.4046674,296.70833 v -1.17708 c 0,0 -1.24116588,-0.58856 -1.24116588,-1.1771 0,-0.58853 0,-2.35417 2.48233178,-2.35417 2.4823318,0 2.4823318,1.76564 2.4823318,2.35417 0,0.58854 -1.2411659,1.1771 -1.2411659,1.1771 v 1.17708 z"
|
||||
id="path842"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#000006;fill-opacity:1;stroke:none;stroke-width:0.52916664;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.49803922"
|
||||
id="path844"
|
||||
sodipodi:type="arc"
|
||||
sodipodi:cx="1.8520833"
|
||||
sodipodi:cy="293.56039"
|
||||
sodipodi:rx="0.47706053"
|
||||
sodipodi:ry="0.43746531"
|
||||
sodipodi:start="0"
|
||||
sodipodi:end="5.866374"
|
||||
d="m 2.3291439,293.56039 a 0.47706053,0.43746531 0 0 1 -0.4274394,0.4351 0.47706053,0.43746531 0 0 1 -0.5163591,-0.34458 0.47706053,0.43746531 0 0 1 0.3200219,-0.50678 0.47706053,0.43746531 0 0 1 0.5829328,0.23916 l -0.4362168,0.1771 z" />
|
||||
<path
|
||||
d="m 3.9161899,293.56039 a 0.47706053,0.43746531 0 0 1 -0.4274394,0.4351 0.47706053,0.43746531 0 0 1 -0.5163591,-0.34458 0.47706053,0.43746531 0 0 1 0.3200219,-0.50678 0.47706053,0.43746531 0 0 1 0.5829328,0.23916 l -0.4362167,0.1771 z"
|
||||
sodipodi:end="5.866374"
|
||||
sodipodi:start="0"
|
||||
sodipodi:ry="0.43746531"
|
||||
sodipodi:rx="0.47706053"
|
||||
sodipodi:cy="293.56039"
|
||||
sodipodi:cx="3.4391294"
|
||||
sodipodi:type="arc"
|
||||
id="path846"
|
||||
style="fill:#000006;fill-opacity:1;stroke:none;stroke-width:0.52916664;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.49803922" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.29107958px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 2.6458333,295.41249 v 1.28091"
|
||||
id="path848"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.29184496px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 2.0289823,295.41249 v 1.28766"
|
||||
id="path850"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.29107958px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 3.2694292,295.41249 v 1.28091"
|
||||
id="path852"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.3 KiB |
|
@ -0,0 +1,80 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 5.2916665 5.2916668"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||
sodipodi:docname="IconSoundMacroTarget.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#353535"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="14.029515"
|
||||
inkscape:cx="4.9858624"
|
||||
inkscape:cy="10.149308"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2079"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="40"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid817"
|
||||
empspacing="0"
|
||||
spacingx="0.52916666"
|
||||
spacingy="0.52916666"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-291.70832)">
|
||||
<ellipse
|
||||
style="fill:none;fill-opacity:1;stroke:#a4a4a4;stroke-width:0.52916664;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path842"
|
||||
cx="2.6458333"
|
||||
cy="294.35416"
|
||||
rx="2.1166666"
|
||||
ry="2.1166699" />
|
||||
<ellipse
|
||||
style="fill:#a4a4a4;fill-opacity:1;stroke:none;stroke-width:0.52916664;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path844"
|
||||
cy="294.35416"
|
||||
cx="2.6458333"
|
||||
rx="1.0583333"
|
||||
ry="1.0583365" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1,80 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 5.2916665 5.2916668"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||
sodipodi:docname="IconSoundMacroTargetDisabled.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#353535"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="14.029515"
|
||||
inkscape:cx="4.9858624"
|
||||
inkscape:cy="10.149308"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2079"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="40"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid817"
|
||||
empspacing="0"
|
||||
spacingx="0.52916666"
|
||||
spacingy="0.52916666"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-291.70832)">
|
||||
<ellipse
|
||||
style="fill:none;fill-opacity:1;stroke:#a4a4a4;stroke-width:0.52916664;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.49803922"
|
||||
id="path842"
|
||||
cx="2.6458333"
|
||||
cy="294.35416"
|
||||
rx="2.1166666"
|
||||
ry="2.1166699" />
|
||||
<ellipse
|
||||
style="fill:#a4a4a4;fill-opacity:0.49803922;stroke:none;stroke-width:0.52916664;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path844"
|
||||
cy="294.35416"
|
||||
cx="2.6458333"
|
||||
rx="1.0583333"
|
||||
ry="1.0583365" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -1,6 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="de_DE">
|
||||
<context>
|
||||
<name>CommandWidget</name>
|
||||
<message>
|
||||
<location filename="../SoundMacroEditor.cpp" line="+163"/>
|
||||
<source>Delete this SoundMacro</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
<message>
|
||||
|
@ -9,12 +17,12 @@
|
|||
<translation>Amuse</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+169"/>
|
||||
<location line="+280"/>
|
||||
<source>&File</source>
|
||||
<translation>&Datei</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<location line="+21"/>
|
||||
<source>P&roject</source>
|
||||
<translation>Projekt</translation>
|
||||
</message>
|
||||
|
@ -52,7 +60,12 @@
|
|||
<translation type="vanished">& Wiederholen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+8"/>
|
||||
<location line="-65"/>
|
||||
<source>Recent &Projects</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+73"/>
|
||||
<source>&Cut</source>
|
||||
<translation>&Schnitt</translation>
|
||||
</message>
|
||||
|
@ -147,12 +160,22 @@
|
|||
<translation>Neu und Kurve</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.cpp" line="+44"/>
|
||||
<location line="+8"/>
|
||||
<source>&Save Project</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+8"/>
|
||||
<source>&Revert Project</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.cpp" line="+70"/>
|
||||
<source>Quit</source>
|
||||
<translation>Verlassen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+106"/>
|
||||
<location line="+134"/>
|
||||
<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>
|
||||
|
@ -177,7 +200,7 @@
|
|||
<translation>Es konnte nicht in das Verzeichnis geschrieben werden</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+38"/>
|
||||
<location line="+44"/>
|
||||
<source>No Audio Devices Found</source>
|
||||
<translation>Keine Audiogeräte gefunden</translation>
|
||||
</message>
|
||||
|
@ -187,22 +210,44 @@
|
|||
<translation>Keine MIDI-Geräte gefunden</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+125"/>
|
||||
<location line="+193"/>
|
||||
<source>New Project</source>
|
||||
<translation>Neues Projekt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+14"/>
|
||||
<location line="+60"/>
|
||||
<source>Open Project</source>
|
||||
<translation>Offenes Projekt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+7"/>
|
||||
<location line="-37"/>
|
||||
<source>The directory at '%1' does not exist.</source>
|
||||
<translation>Das Verzeichnis '% 1' existiert nicht.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<location line="-432"/>
|
||||
<source>Clear Recent Projects</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+134"/>
|
||||
<location line="+292"/>
|
||||
<source>The directory at '%1' must not be empty.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-291"/>
|
||||
<location line="+292"/>
|
||||
<source>Directory empty</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-181"/>
|
||||
<source>SUSTAIN</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+187"/>
|
||||
<source>Bad Directory</source>
|
||||
<translation>Schlechtes Verzeichnis</translation>
|
||||
</message>
|
||||
|
@ -213,18 +258,18 @@
|
|||
</message>
|
||||
<message>
|
||||
<location line="+0"/>
|
||||
<location line="+83"/>
|
||||
<location line="+127"/>
|
||||
<location line="+45"/>
|
||||
<source>Scanning Project</source>
|
||||
<translation>Projekt scannen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-116"/>
|
||||
<location line="-160"/>
|
||||
<source>Opening %1</source>
|
||||
<translation>Eröffnung% 1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+10"/>
|
||||
<location line="+54"/>
|
||||
<source>Import Project</source>
|
||||
<translation>Projekt importieren</translation>
|
||||
</message>
|
||||
|
@ -296,30 +341,46 @@
|
|||
<translation>Importieren von% 1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ModulationSlider</name>
|
||||
<message>
|
||||
<location filename="../KeyboardWidget.cpp" line="+267"/>
|
||||
<source>Modulation: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PitchSlider</name>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Pitch: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ProjectModel</name>
|
||||
<message>
|
||||
<location filename="../ProjectModel.cpp" line="+126"/>
|
||||
<location filename="../ProjectModel.cpp" line="+210"/>
|
||||
<source>Sound Macros</source>
|
||||
<translation>Sound-Makros</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+21"/>
|
||||
<location line="+19"/>
|
||||
<source>ADSRs</source>
|
||||
<translation>ADSRs</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+12"/>
|
||||
<location line="+11"/>
|
||||
<source>Curves</source>
|
||||
<translation>Kurven</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+13"/>
|
||||
<location line="+12"/>
|
||||
<source>Keymaps</source>
|
||||
<translation>Schlüsselkarten</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+8"/>
|
||||
<location line="+7"/>
|
||||
<source>Layers</source>
|
||||
<translation>Lagen</translation>
|
||||
</message>
|
||||
|
@ -340,12 +401,12 @@
|
|||
<context>
|
||||
<name>QUndoStack</name>
|
||||
<message>
|
||||
<location filename="../SoundMacroEditor.cpp" line="+163"/>
|
||||
<location filename="../SoundMacroEditor.cpp" line="+157"/>
|
||||
<source>Change %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+308"/>
|
||||
<location line="+322"/>
|
||||
<source>Reorder %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -355,7 +416,8 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+55"/>
|
||||
<location filename="../ProjectModel.cpp" line="+151"/>
|
||||
<location filename="../SoundMacroEditor.cpp" line="+55"/>
|
||||
<source>Delete %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -363,7 +425,7 @@
|
|||
<context>
|
||||
<name>SoundMacroCatalogue</name>
|
||||
<message>
|
||||
<location line="+111"/>
|
||||
<location filename="../SoundMacroEditor.cpp" line="+118"/>
|
||||
<source>Control</source>
|
||||
<translation>Steuerung</translation>
|
||||
</message>
|
||||
|
@ -433,4 +495,28 @@
|
|||
<translation>Befehle zum Steuern der Lautstärke der Stimme</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>StatusBarWidget</name>
|
||||
<message>
|
||||
<location filename="../StatusBarWidget.hpp" line="+26"/>
|
||||
<source>Immediately kill active voices</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TargetButton</name>
|
||||
<message>
|
||||
<location filename="../SoundMacroEditor.cpp" line="-939"/>
|
||||
<source>Set step with target click</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>VelocitySlider</name>
|
||||
<message>
|
||||
<location filename="../KeyboardWidget.cpp" line="-18"/>
|
||||
<source>Velocity: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
<file>IconCurve.svg</file>
|
||||
<file>IconNewCurve.svg</file>
|
||||
<file>IconSoundMacroDelete.svg</file>
|
||||
<file>IconSoundMacroTarget.svg</file>
|
||||
<file>IconSoundMacroTargetDisabled.svg</file>
|
||||
<file>IconKill.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/bg">
|
||||
<file>FaceGrey.svg</file>
|
||||
|
|
|
@ -36,20 +36,23 @@ public:
|
|||
AudioGroupProject& getProj() { return m_proj; }
|
||||
AudioGroupPool& getPool() { return m_pool; }
|
||||
AudioGroupSampleDirectory& getSdir() { return m_sdir; }
|
||||
|
||||
virtual void setIdDatabases() const {}
|
||||
};
|
||||
|
||||
class AudioGroupDatabase : public AudioGroup
|
||||
class AudioGroupDatabase final : public AudioGroup
|
||||
{
|
||||
amuse::NameDB m_soundMacroDb;
|
||||
amuse::NameDB m_sampleDb;
|
||||
amuse::NameDB m_tableDb;
|
||||
amuse::NameDB m_keymapDb;
|
||||
amuse::NameDB m_layersDb;
|
||||
NameDB m_soundMacroDb;
|
||||
NameDB m_sampleDb;
|
||||
NameDB m_tableDb;
|
||||
NameDB m_keymapDb;
|
||||
NameDB m_layersDb;
|
||||
|
||||
public:
|
||||
AudioGroupDatabase() = default;
|
||||
explicit AudioGroupDatabase(const AudioGroupData& data)
|
||||
{
|
||||
setIdDatabases();
|
||||
assign(data);
|
||||
}
|
||||
explicit AudioGroupDatabase(SystemStringView groupPath)
|
||||
|
@ -58,28 +61,28 @@ public:
|
|||
assign(groupPath);
|
||||
}
|
||||
|
||||
void setIdDatabases()
|
||||
void setIdDatabases() const
|
||||
{
|
||||
amuse::SoundMacroId::CurNameDB = &m_soundMacroDb;
|
||||
amuse::SampleId::CurNameDB = &m_sampleDb;
|
||||
amuse::TableId::CurNameDB = &m_tableDb;
|
||||
amuse::KeymapId::CurNameDB = &m_keymapDb;
|
||||
amuse::LayersId::CurNameDB = &m_layersDb;
|
||||
SoundMacroId::CurNameDB = const_cast<NameDB*>(&m_soundMacroDb);
|
||||
SampleId::CurNameDB = const_cast<NameDB*>(&m_sampleDb);
|
||||
TableId::CurNameDB = const_cast<NameDB*>(&m_tableDb);
|
||||
KeymapId::CurNameDB = const_cast<NameDB*>(&m_keymapDb);
|
||||
LayersId::CurNameDB = const_cast<NameDB*>(&m_layersDb);
|
||||
}
|
||||
};
|
||||
|
||||
class ProjectDatabase
|
||||
{
|
||||
amuse::NameDB m_songDb;
|
||||
amuse::NameDB m_sfxDb;
|
||||
amuse::NameDB m_groupDb;
|
||||
NameDB m_songDb;
|
||||
NameDB m_sfxDb;
|
||||
NameDB m_groupDb;
|
||||
|
||||
public:
|
||||
void setIdDatabases()
|
||||
void setIdDatabases() const
|
||||
{
|
||||
amuse::SongId::CurNameDB = &m_songDb;
|
||||
amuse::SFXId::CurNameDB = &m_sfxDb;
|
||||
amuse::GroupId::CurNameDB = &m_groupDb;
|
||||
SongId::CurNameDB = const_cast<NameDB*>(&m_songDb);
|
||||
SFXId::CurNameDB = const_cast<NameDB*>(&m_sfxDb);
|
||||
GroupId::CurNameDB = const_cast<NameDB*>(&m_groupDb);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -153,6 +153,7 @@ struct SoundMacro
|
|||
Int32,
|
||||
UInt32,
|
||||
SoundMacroId,
|
||||
SoundMacroStep,
|
||||
TableId,
|
||||
SampleId,
|
||||
Choice
|
||||
|
@ -200,7 +201,7 @@ struct SoundMacro
|
|||
static const CmdIntrospection Introspective;
|
||||
Value<atInt8> key;
|
||||
SoundMacroIdDNA<athena::Little> macro;
|
||||
Value<atUint16> macroStep;
|
||||
SoundMacroStepDNA<athena::Little> macroStep;
|
||||
bool Do(SoundMacroState& st, Voice& vox) const;
|
||||
CmdOp Isa() const { return CmdOp::SplitKey; }
|
||||
};
|
||||
|
@ -211,7 +212,7 @@ struct SoundMacro
|
|||
static const CmdIntrospection Introspective;
|
||||
Value<atInt8> velocity;
|
||||
SoundMacroIdDNA<athena::Little> macro;
|
||||
Value<atUint16> macroStep;
|
||||
SoundMacroStepDNA<athena::Little> macroStep;
|
||||
bool Do(SoundMacroState& st, Voice& vox) const;
|
||||
CmdOp Isa() const { return CmdOp::SplitVel; }
|
||||
};
|
||||
|
@ -237,7 +238,7 @@ struct SoundMacro
|
|||
Value<bool> keyOff;
|
||||
Value<bool> random;
|
||||
Value<bool> sampleEnd;
|
||||
Value<atUint16> macroStep;
|
||||
SoundMacroStepDNA<athena::Little> macroStep;
|
||||
Value<atUint16> times;
|
||||
bool Do(SoundMacroState& st, Voice& vox) const;
|
||||
CmdOp Isa() const { return CmdOp::Loop; }
|
||||
|
@ -249,7 +250,7 @@ struct SoundMacro
|
|||
static const CmdIntrospection Introspective;
|
||||
Seek<1, athena::SeekOrigin::Current> dummy;
|
||||
SoundMacroIdDNA<athena::Little> macro;
|
||||
Value<atUint16> macroStep;
|
||||
SoundMacroStepDNA<athena::Little> macroStep;
|
||||
bool Do(SoundMacroState& st, Voice& vox) const;
|
||||
CmdOp Isa() const { return CmdOp::Goto; }
|
||||
};
|
||||
|
@ -274,7 +275,7 @@ struct SoundMacro
|
|||
static const CmdIntrospection Introspective;
|
||||
Value<atInt8> addNote;
|
||||
SoundMacroIdDNA<athena::Little> macro;
|
||||
Value<atUint16> macroStep;
|
||||
SoundMacroStepDNA<athena::Little> macroStep;
|
||||
Value<atUint8> priority;
|
||||
Value<atUint8> maxVoices;
|
||||
bool Do(SoundMacroState& st, Voice& vox) const;
|
||||
|
@ -297,7 +298,7 @@ struct SoundMacro
|
|||
static const CmdIntrospection Introspective;
|
||||
Value<atInt8> modValue;
|
||||
SoundMacroIdDNA<athena::Little> macro;
|
||||
Value<atUint16> macroStep;
|
||||
SoundMacroStepDNA<athena::Little> macroStep;
|
||||
bool Do(SoundMacroState& st, Voice& vox) const;
|
||||
CmdOp Isa() const { return CmdOp::SplitMod; }
|
||||
};
|
||||
|
@ -398,7 +399,7 @@ struct SoundMacro
|
|||
static const CmdIntrospection Introspective;
|
||||
Value<atUint8> rnd;
|
||||
SoundMacroIdDNA<athena::Little> macro;
|
||||
Value<atUint16> macroStep;
|
||||
SoundMacroStepDNA<athena::Little> macroStep;
|
||||
bool Do(SoundMacroState& st, Voice& vox) const;
|
||||
CmdOp Isa() const { return CmdOp::SplitRnd; }
|
||||
};
|
||||
|
@ -623,7 +624,7 @@ struct SoundMacro
|
|||
static const CmdIntrospection Introspective;
|
||||
Seek<1, athena::SeekOrigin::Current> seek;
|
||||
SoundMacroIdDNA<athena::Little> macro;
|
||||
Value<atUint16> macroStep;
|
||||
SoundMacroStepDNA<athena::Little> macroStep;
|
||||
bool Do(SoundMacroState& st, Voice& vox) const;
|
||||
CmdOp Isa() const { return CmdOp::GoSub; }
|
||||
};
|
||||
|
@ -640,7 +641,7 @@ struct SoundMacro
|
|||
};
|
||||
Value<EventType> event;
|
||||
SoundMacroIdDNA<athena::Little> macro;
|
||||
Value<atUint16> macroStep;
|
||||
SoundMacroStepDNA<athena::Little> macroStep;
|
||||
bool Do(SoundMacroState& st, Voice& vox) const;
|
||||
CmdOp Isa() const { return CmdOp::TrapEvent; }
|
||||
};
|
||||
|
@ -1098,7 +1099,7 @@ struct SoundMacro
|
|||
Value<bool> varCtrlB;
|
||||
Value<atInt8> b;
|
||||
Value<bool> notEq;
|
||||
Value<atUint16> macroStep;
|
||||
SoundMacroStepDNA<athena::Little> macroStep;
|
||||
bool Do(SoundMacroState& st, Voice& vox) const;
|
||||
CmdOp Isa() const { return CmdOp::IfEqual; }
|
||||
};
|
||||
|
@ -1112,7 +1113,7 @@ struct SoundMacro
|
|||
Value<bool> varCtrlB;
|
||||
Value<atInt8> b;
|
||||
Value<bool> notLt;
|
||||
Value<atUint16> macroStep;
|
||||
SoundMacroStepDNA<athena::Little> macroStep;
|
||||
bool Do(SoundMacroState& st, Voice& vox) const;
|
||||
CmdOp Isa() const { return CmdOp::IfLess; }
|
||||
};
|
||||
|
|
|
@ -71,6 +71,7 @@ public:
|
|||
class BooBackendMIDIReader : public IMIDIReader, public boo::IMIDIReader
|
||||
{
|
||||
friend class BooBackendVoiceAllocator;
|
||||
protected:
|
||||
Engine& m_engine;
|
||||
std::unique_ptr<boo::IMIDIIn> m_midiIn;
|
||||
boo::MIDIDecoder m_decoder;
|
||||
|
@ -119,6 +120,7 @@ public:
|
|||
class BooBackendVoiceAllocator : public IBackendVoiceAllocator, public boo::IAudioVoiceEngineCallback
|
||||
{
|
||||
friend class BooBackendMIDIReader;
|
||||
protected:
|
||||
boo::IAudioVoiceEngine& m_booEngine;
|
||||
Engine* m_cbInterface = nullptr;
|
||||
|
||||
|
|
|
@ -104,6 +104,26 @@ PageObjectIdDNA : BigDNA
|
|||
operator ObjectId() const { return id; }
|
||||
};
|
||||
|
||||
struct SoundMacroStep
|
||||
{
|
||||
uint16_t step = 0;
|
||||
operator uint16_t() const { return step; }
|
||||
SoundMacroStep() = default;
|
||||
SoundMacroStep(uint16_t idIn) : step(idIn) {}
|
||||
SoundMacroStep& operator=(uint16_t idIn) { step = idIn; return *this; }
|
||||
};
|
||||
|
||||
template <athena::Endian DNAEn>
|
||||
struct AT_SPECIALIZE_PARMS(athena::Endian::Big, athena::Endian::Little)
|
||||
SoundMacroStepDNA : BigDNA
|
||||
{
|
||||
AT_DECL_EXPLICIT_DNA_YAML
|
||||
SoundMacroStep step;
|
||||
SoundMacroStepDNA() = default;
|
||||
SoundMacroStepDNA(SoundMacroStep idIn) : step(idIn) {}
|
||||
operator SoundMacroStep() const { return step; }
|
||||
};
|
||||
|
||||
struct LittleUInt24 : LittleDNA
|
||||
{
|
||||
AT_DECL_EXPLICIT_DNA_YAML
|
||||
|
|
|
@ -96,6 +96,15 @@ public:
|
|||
return fxStart(sfxId, vol, pan, m_defaultStudio);
|
||||
}
|
||||
|
||||
/** Start SoundMacro node playing directly (for editor use) */
|
||||
std::shared_ptr<Voice> macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key,
|
||||
uint8_t vel, uint8_t mod, std::weak_ptr<Studio> smx);
|
||||
std::shared_ptr<Voice> macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key,
|
||||
uint8_t vel, uint8_t mod)
|
||||
{
|
||||
return macroStart(group, id, key, vel, mod, m_defaultStudio);
|
||||
}
|
||||
|
||||
/** Start soundFX playing from loaded audio groups, attach to positional emitter */
|
||||
std::shared_ptr<Emitter> addEmitter(const float* pos, const float* dir, float maxDist, float falloff,
|
||||
int sfxId, float minVol, float maxVol, bool doppler,
|
||||
|
@ -136,6 +145,9 @@ public:
|
|||
/** Obtain next random number from engine's PRNG */
|
||||
uint32_t nextRandom() { return m_random(); }
|
||||
|
||||
/** Obtain list of active voices */
|
||||
std::list<std::shared_ptr<Voice>>& getActiveVoices() { return m_activeVoices; }
|
||||
|
||||
/** Obtain list of active sequencers */
|
||||
std::list<std::shared_ptr<Sequencer>>& getActiveSequencers() { return m_activeSequencers; }
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ void AudioGroup::assign(const AudioGroupData& data)
|
|||
void AudioGroup::assign(SystemStringView groupPath)
|
||||
{
|
||||
/* Reverse order when loading intermediates */
|
||||
m_groupPath = groupPath;
|
||||
m_sdir = AudioGroupSampleDirectory::CreateAudioGroupSampleDirectory(groupPath);
|
||||
m_pool = AudioGroupPool::CreateAudioGroupPool(groupPath);
|
||||
m_proj = AudioGroupProject::CreateAudioGroupProject(groupPath);
|
||||
|
@ -32,6 +33,7 @@ const unsigned char* AudioGroup::getSampleData(SampleId sfxId, const AudioGroupS
|
|||
{
|
||||
if (sample->m_looseData)
|
||||
{
|
||||
setIdDatabases();
|
||||
#if _WIN32
|
||||
SystemString basePath = m_groupPath + _S('/') +
|
||||
athena::utility::utf8ToWide(SampleId::CurNameDB->resolveNameFromId(sfxId));
|
||||
|
|
|
@ -61,6 +61,7 @@ struct MakeDefaultCmdOp
|
|||
AccessField<uint32_t>(ret.get(), field) = uint32_t(field.m_default);
|
||||
break;
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroId:
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep:
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::TableId:
|
||||
case amuse::SoundMacro::CmdIntrospection::Field::Type::SampleId:
|
||||
AccessField<SoundMacroIdDNA<athena::Little>>(ret.get(), field).id = uint16_t(field.m_default);
|
||||
|
@ -153,7 +154,8 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
|
|||
objHead.read(r);
|
||||
KeymapDNA<DNAE> kmData;
|
||||
kmData.read(r);
|
||||
*ret.m_keymaps[objHead.objectId.id] = kmData;
|
||||
auto& km = ret.m_keymaps[objHead.objectId.id];
|
||||
km = std::make_shared<Keymap>(kmData);
|
||||
r.seek(startPos + objHead.size, athena::Begin);
|
||||
}
|
||||
}
|
||||
|
@ -166,15 +168,16 @@ AudioGroupPool AudioGroupPool::_AudioGroupPool(athena::io::IStreamReader& r)
|
|||
ObjectHeader<DNAE> objHead;
|
||||
atInt64 startPos = r.position();
|
||||
objHead.read(r);
|
||||
std::vector<LayerMapping>& lm = *ret.m_layers[objHead.objectId.id];
|
||||
auto& lm = ret.m_layers[objHead.objectId.id];
|
||||
lm = std::make_shared<std::vector<LayerMapping>>();
|
||||
uint32_t count;
|
||||
athena::io::Read<athena::io::PropType::None>::Do<decltype(count), DNAE>({}, count, r);
|
||||
lm.reserve(count);
|
||||
lm->reserve(count);
|
||||
for (uint32_t i = 0; i < count; ++i)
|
||||
{
|
||||
LayerMappingDNA<DNAE> lmData;
|
||||
lmData.read(r);
|
||||
lm.push_back(lmData);
|
||||
lm->push_back(lmData);
|
||||
}
|
||||
r.seek(startPos + objHead.size, athena::Begin);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,29 @@ static bool AtEnd16(athena::io::IStreamReader& r)
|
|||
return v == 0xffff;
|
||||
}
|
||||
|
||||
template <athena::Endian DNAE>
|
||||
static void ReadRangedObjectIds(NameDB* db, athena::io::IStreamReader& r, NameDB::Type tp)
|
||||
{
|
||||
uint16_t id;
|
||||
athena::io::Read<athena::io::PropType::None>::Do<decltype(id), DNAE>({}, id, r);
|
||||
if ((id & 0x8000) == 0x8000)
|
||||
{
|
||||
uint16_t endId;
|
||||
athena::io::Read<athena::io::PropType::None>::Do<decltype(endId), DNAE>({}, endId, r);
|
||||
for (uint16_t i = uint16_t(id & 0x7fff); i <= uint16_t(endId & 0x7fff); ++i)
|
||||
{
|
||||
ObjectId useId = i;
|
||||
if (tp == NameDB::Type::Layer)
|
||||
useId.id |= 0x8000;
|
||||
db->registerPair(NameDB::generateName(useId, tp), useId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
db->registerPair(NameDB::generateName(id, tp), id);
|
||||
}
|
||||
}
|
||||
|
||||
AudioGroupProject::AudioGroupProject(athena::io::IStreamReader& r, GCNDataTag)
|
||||
{
|
||||
while (!AtEnd32(r))
|
||||
|
@ -28,6 +51,33 @@ AudioGroupProject::AudioGroupProject(athena::io::IStreamReader& r, GCNDataTag)
|
|||
GroupHeader<athena::Big> header;
|
||||
header.read(r);
|
||||
|
||||
GroupId::CurNameDB->registerPair(NameDB::generateName(header.groupId, NameDB::Type::Group), header.groupId);
|
||||
|
||||
/* Sound Macros */
|
||||
r.seek(header.soundMacroIdsOff, athena::Begin);
|
||||
while (!AtEnd16(r))
|
||||
ReadRangedObjectIds<athena::Big>(SoundMacroId::CurNameDB, r, NameDB::Type::SoundMacro);
|
||||
|
||||
/* Samples */
|
||||
r.seek(header.samplIdsOff, athena::Begin);
|
||||
while (!AtEnd16(r))
|
||||
ReadRangedObjectIds<athena::Big>(SampleId::CurNameDB, r, NameDB::Type::Sample);
|
||||
|
||||
/* Tables */
|
||||
r.seek(header.tableIdsOff, athena::Begin);
|
||||
while (!AtEnd16(r))
|
||||
ReadRangedObjectIds<athena::Big>(TableId::CurNameDB, r, NameDB::Type::Table);
|
||||
|
||||
/* Keymaps */
|
||||
r.seek(header.keymapIdsOff, athena::Begin);
|
||||
while (!AtEnd16(r))
|
||||
ReadRangedObjectIds<athena::Big>(KeymapId::CurNameDB, r, NameDB::Type::Keymap);
|
||||
|
||||
/* Layers */
|
||||
r.seek(header.layerIdsOff, athena::Begin);
|
||||
while (!AtEnd16(r))
|
||||
ReadRangedObjectIds<athena::Big>(LayersId::CurNameDB, r, NameDB::Type::Layer);
|
||||
|
||||
if (header.type == GroupType::Song)
|
||||
{
|
||||
auto& idx = m_songGroups[header.groupId];
|
||||
|
@ -60,6 +110,7 @@ AudioGroupProject::AudioGroupProject(athena::io::IStreamReader& r, GCNDataTag)
|
|||
std::array<SongGroupIndex::MIDISetup, 16>& setup = idx->m_midiSetups[songId];
|
||||
for (int i = 0; i < 16 ; ++i)
|
||||
setup[i].read(r);
|
||||
SongId::CurNameDB->registerPair(NameDB::generateName(songId, NameDB::Type::Song), songId);
|
||||
}
|
||||
}
|
||||
else if (header.type == GroupType::SFX)
|
||||
|
@ -77,6 +128,8 @@ AudioGroupProject::AudioGroupProject(athena::io::IStreamReader& r, GCNDataTag)
|
|||
SFXGroupIndex::SFXEntryDNA<athena::Big> entry;
|
||||
entry.read(r);
|
||||
idx->m_sfxEntries[entry.sfxId.id] = entry;
|
||||
SFXId::CurNameDB->registerPair(
|
||||
NameDB::generateName(entry.sfxId.id, NameDB::Type::SFX), entry.sfxId.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,6 +149,33 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade
|
|||
GroupHeader<DNAE> header;
|
||||
header.read(r);
|
||||
|
||||
GroupId::CurNameDB->registerPair(NameDB::generateName(header.groupId, NameDB::Type::Group), header.groupId);
|
||||
|
||||
/* Sound Macros */
|
||||
r.seek(subDataOff + header.soundMacroIdsOff, athena::Begin);
|
||||
while (!AtEnd16(r))
|
||||
ReadRangedObjectIds<DNAE>(SoundMacroId::CurNameDB, r, NameDB::Type::SoundMacro);
|
||||
|
||||
/* Samples */
|
||||
r.seek(subDataOff + header.samplIdsOff, athena::Begin);
|
||||
while (!AtEnd16(r))
|
||||
ReadRangedObjectIds<DNAE>(SampleId::CurNameDB, r, NameDB::Type::Sample);
|
||||
|
||||
/* Tables */
|
||||
r.seek(subDataOff + header.tableIdsOff, athena::Begin);
|
||||
while (!AtEnd16(r))
|
||||
ReadRangedObjectIds<DNAE>(TableId::CurNameDB, r, NameDB::Type::Table);
|
||||
|
||||
/* Keymaps */
|
||||
r.seek(subDataOff + header.keymapIdsOff, athena::Begin);
|
||||
while (!AtEnd16(r))
|
||||
ReadRangedObjectIds<DNAE>(KeymapId::CurNameDB, r, NameDB::Type::Keymap);
|
||||
|
||||
/* Layers */
|
||||
r.seek(subDataOff + header.layerIdsOff, athena::Begin);
|
||||
while (!AtEnd16(r))
|
||||
ReadRangedObjectIds<DNAE>(LayersId::CurNameDB, r, NameDB::Type::Layer);
|
||||
|
||||
if (header.type == GroupType::Song)
|
||||
{
|
||||
auto& idx = ret.m_songGroups[header.groupId];
|
||||
|
@ -131,6 +211,7 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade
|
|||
std::array<SongGroupIndex::MIDISetup, 16>& setup = idx->m_midiSetups[songId];
|
||||
for (int i = 0; i < 16 ; ++i)
|
||||
setup[i].read(r);
|
||||
SongId::CurNameDB->registerPair(NameDB::generateName(songId, NameDB::Type::Song), songId);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -167,6 +248,7 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade
|
|||
ent.read(r);
|
||||
setup[i] = ent;
|
||||
}
|
||||
SongId::CurNameDB->registerPair(NameDB::generateName(songId, NameDB::Type::Song), songId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -187,6 +269,8 @@ AudioGroupProject AudioGroupProject::_AudioGroupProject(athena::io::IStreamReade
|
|||
entry.read(r);
|
||||
r.seek(2, athena::Current);
|
||||
idx->m_sfxEntries[entry.sfxId.id] = entry;
|
||||
SFXId::CurNameDB->registerPair(
|
||||
NameDB::generateName(entry.sfxId.id, NameDB::Type::SFX), entry.sfxId.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,29 +407,6 @@ AudioGroupProject AudioGroupProject::CreateAudioGroupProject(SystemStringView gr
|
|||
return ret;
|
||||
}
|
||||
|
||||
template <athena::Endian DNAE>
|
||||
static void ReadRangedObjectIds(NameDB* db, athena::io::IStreamReader& r, NameDB::Type tp)
|
||||
{
|
||||
uint16_t id;
|
||||
athena::io::Read<athena::io::PropType::None>::Do<decltype(id), DNAE>({}, id, r);
|
||||
if ((id & 0x8000) == 0x8000)
|
||||
{
|
||||
uint16_t endId;
|
||||
athena::io::Read<athena::io::PropType::None>::Do<decltype(endId), DNAE>({}, endId, r);
|
||||
for (uint16_t i = uint16_t(id & 0x7fff); i <= uint16_t(endId & 0x7fff); ++i)
|
||||
{
|
||||
ObjectId useId = i;
|
||||
if (tp == NameDB::Type::Layer)
|
||||
useId.id |= 0x8000;
|
||||
db->registerPair(NameDB::generateName(useId, tp), useId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
db->registerPair(NameDB::generateName(id, tp), id);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroupProject::BootstrapObjectIDs(athena::io::IStreamReader& r, GCNDataTag)
|
||||
{
|
||||
while (!AtEnd32(r))
|
||||
|
|
|
@ -45,6 +45,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(athena::io::IStreamReader&
|
|||
EntryDNA<athena::Big> ent;
|
||||
ent.read(r);
|
||||
m_entries[ent.m_sfxId] = ent;
|
||||
SampleId::CurNameDB->registerPair(NameDB::generateName(ent.m_sfxId, NameDB::Type::Sample), ent.m_sfxId);
|
||||
}
|
||||
|
||||
for (auto& p : m_entries)
|
||||
|
@ -68,6 +69,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(athena::io::IStreamReader&
|
|||
MusyX1AbsSdirEntry<athena::Big> ent;
|
||||
ent.read(r);
|
||||
m_entries[ent.m_sfxId] = ent;
|
||||
SampleId::CurNameDB->registerPair(NameDB::generateName(ent.m_sfxId, NameDB::Type::Sample), ent.m_sfxId);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -77,6 +79,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(athena::io::IStreamReader&
|
|||
MusyX1SdirEntry<athena::Big> ent;
|
||||
ent.read(r);
|
||||
m_entries[ent.m_sfxId] = ent;
|
||||
SampleId::CurNameDB->registerPair(NameDB::generateName(ent.m_sfxId, NameDB::Type::Sample), ent.m_sfxId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,6 +101,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(athena::io::IStreamReader&
|
|||
Entry& store = m_entries[ent.m_sfxId];
|
||||
store = ent;
|
||||
store.m_numSamples |= atUint32(SampleFormat::PCM_PC) << 24;
|
||||
SampleId::CurNameDB->registerPair(NameDB::generateName(ent.m_sfxId, NameDB::Type::Sample), ent.m_sfxId);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -109,6 +113,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(athena::io::IStreamReader&
|
|||
Entry& store = m_entries[ent.m_sfxId];
|
||||
store = ent;
|
||||
store.m_numSamples |= atUint32(SampleFormat::PCM_PC) << 24;
|
||||
SampleId::CurNameDB->registerPair(NameDB::generateName(ent.m_sfxId, NameDB::Type::Sample), ent.m_sfxId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -194,6 +194,64 @@ const char* PageObjectIdDNA<DNAE>::DNAType()
|
|||
template struct PageObjectIdDNA<athena::Big>;
|
||||
template struct PageObjectIdDNA<athena::Little>;
|
||||
|
||||
template<> template<>
|
||||
void SoundMacroStepDNA<athena::Little>::Enumerate<BigDNA::Read>(athena::io::IStreamReader& reader)
|
||||
{
|
||||
step = reader.readUint16Little();
|
||||
}
|
||||
template<> template<>
|
||||
void SoundMacroStepDNA<athena::Little>::Enumerate<BigDNA::Write>(athena::io::IStreamWriter& writer)
|
||||
{
|
||||
writer.writeUint16Little(step);
|
||||
}
|
||||
template<> template<>
|
||||
void SoundMacroStepDNA<athena::Little>::Enumerate<BigDNA::BinarySize>(size_t& sz)
|
||||
{
|
||||
sz += 2;
|
||||
}
|
||||
template<> template<>
|
||||
void SoundMacroStepDNA<athena::Little>::Enumerate<BigDNA::ReadYaml>(athena::io::YAMLDocReader& reader)
|
||||
{
|
||||
step = reader.readUint16(nullptr);
|
||||
}
|
||||
template<> template<>
|
||||
void SoundMacroStepDNA<athena::Little>::Enumerate<BigDNA::WriteYaml>(athena::io::YAMLDocWriter& writer)
|
||||
{
|
||||
writer.writeUint16(nullptr, step);
|
||||
}
|
||||
template<> template<>
|
||||
void SoundMacroStepDNA<athena::Big>::Enumerate<BigDNA::Read>(athena::io::IStreamReader& reader)
|
||||
{
|
||||
step = reader.readUint16Big();
|
||||
}
|
||||
template<> template<>
|
||||
void SoundMacroStepDNA<athena::Big>::Enumerate<BigDNA::Write>(athena::io::IStreamWriter& writer)
|
||||
{
|
||||
writer.writeUint16Big(step);
|
||||
}
|
||||
template<> template<>
|
||||
void SoundMacroStepDNA<athena::Big>::Enumerate<BigDNA::BinarySize>(size_t& sz)
|
||||
{
|
||||
sz += 2;
|
||||
}
|
||||
template<> template<>
|
||||
void SoundMacroStepDNA<athena::Big>::Enumerate<BigDNA::ReadYaml>(athena::io::YAMLDocReader& reader)
|
||||
{
|
||||
step = reader.readUint16(nullptr);
|
||||
}
|
||||
template<> template<>
|
||||
void SoundMacroStepDNA<athena::Big>::Enumerate<BigDNA::WriteYaml>(athena::io::YAMLDocWriter& writer)
|
||||
{
|
||||
writer.writeUint16(nullptr, step);
|
||||
}
|
||||
template <athena::Endian DNAE>
|
||||
const char* SoundMacroStepDNA<DNAE>::DNAType()
|
||||
{
|
||||
return "amuse::SoundMacroStepDNA";
|
||||
}
|
||||
template struct SoundMacroStepDNA<athena::Big>;
|
||||
template struct SoundMacroStepDNA<athena::Little>;
|
||||
|
||||
ObjectId NameDB::generateId(Type tp) const
|
||||
{
|
||||
uint16_t maxMatch = uint16_t(tp == Type::Layer ? 0x8000 : 0);
|
||||
|
|
|
@ -280,12 +280,12 @@ std::shared_ptr<Voice> Engine::fxStart(int sfxId, float vol, float pan, std::wea
|
|||
{
|
||||
auto search = m_sfxLookup.find(sfxId);
|
||||
if (search == m_sfxLookup.end())
|
||||
return nullptr;
|
||||
return {};
|
||||
|
||||
AudioGroup* grp = std::get<0>(search->second);
|
||||
const SFXGroupIndex::SFXEntry* entry = std::get<2>(search->second);
|
||||
if (!grp)
|
||||
return nullptr;
|
||||
return {};
|
||||
|
||||
std::list<std::shared_ptr<Voice>>::iterator ret =
|
||||
_allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, false, smx);
|
||||
|
@ -301,6 +301,25 @@ std::shared_ptr<Voice> Engine::fxStart(int sfxId, float vol, float pan, std::wea
|
|||
return *ret;
|
||||
}
|
||||
|
||||
/** Start SoundMacro node playing directly (for editor use) */
|
||||
std::shared_ptr<Voice> Engine::macroStart(const AudioGroup* group, SoundMacroId id, uint8_t key, uint8_t vel,
|
||||
uint8_t mod, std::weak_ptr<Studio> smx)
|
||||
{
|
||||
if (!group)
|
||||
return {};
|
||||
|
||||
std::list<std::shared_ptr<Voice>>::iterator ret =
|
||||
_allocateVoice(*group, {}, NativeSampleRate, true, false, smx);
|
||||
|
||||
if (!(*ret)->loadMacroObject(id, 0, 1000.f, key, vel, mod))
|
||||
{
|
||||
_destroyVoice(ret);
|
||||
return {};
|
||||
}
|
||||
|
||||
return *ret;
|
||||
}
|
||||
|
||||
/** Start soundFX playing from loaded audio groups, attach to positional emitter */
|
||||
std::shared_ptr<Emitter> Engine::addEmitter(const float* pos, const float* dir, float maxDist, float falloff,
|
||||
int sfxId, float minVol, float maxVol, bool doppler,
|
||||
|
@ -308,12 +327,12 @@ std::shared_ptr<Emitter> Engine::addEmitter(const float* pos, const float* dir,
|
|||
{
|
||||
auto search = m_sfxLookup.find(sfxId);
|
||||
if (search == m_sfxLookup.end())
|
||||
return nullptr;
|
||||
return {};
|
||||
|
||||
AudioGroup* grp = std::get<0>(search->second);
|
||||
const SFXGroupIndex::SFXEntry* entry = std::get<2>(search->second);
|
||||
if (!grp)
|
||||
return nullptr;
|
||||
return {};
|
||||
|
||||
std::list<std::shared_ptr<Voice>>::iterator vox =
|
||||
_allocateVoice(*grp, std::get<1>(search->second), NativeSampleRate, true, true, smx);
|
||||
|
|
|
@ -169,6 +169,9 @@ template <>
|
|||
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<SoundMacroIdDNA<athena::Little>>()
|
||||
{ return SoundMacro::CmdIntrospection::Field::Type::SoundMacroId; }
|
||||
template <>
|
||||
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<SoundMacroStepDNA<athena::Little>>()
|
||||
{ return SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep; }
|
||||
template <>
|
||||
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<TableIdDNA<athena::Little>>()
|
||||
{ return SoundMacro::CmdIntrospection::Field::Type::TableId; }
|
||||
template <>
|
||||
|
@ -214,12 +217,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSplitKey::Introspective =
|
|||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSplitKey, macro),
|
||||
"SoundMacro"sv,
|
||||
0, 65535, 0
|
||||
"Macro"sv,
|
||||
0, 65535, 65535
|
||||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSplitKey, macroStep),
|
||||
"SoundMacro Step"sv,
|
||||
"Macro Step"sv,
|
||||
0, 65535, 0
|
||||
}
|
||||
}
|
||||
|
@ -230,9 +233,9 @@ bool SoundMacro::CmdSplitKey::Do(SoundMacroState& st, Voice& vox) const
|
|||
{
|
||||
/* Do Branch */
|
||||
if (macro.id == std::get<0>(st.m_pc.back()))
|
||||
st._setPC(macroStep);
|
||||
st._setPC(macroStep.step);
|
||||
else
|
||||
vox.loadMacroObject(macro.id, macroStep, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);
|
||||
vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -251,12 +254,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSplitVel::Introspective =
|
|||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSplitVel, macro),
|
||||
"SoundMacro"sv,
|
||||
0, 65535, 0
|
||||
"Macro"sv,
|
||||
0, 65535, 65535
|
||||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSplitVel, macroStep),
|
||||
"SoundMacro Step"sv,
|
||||
"Macro Step"sv,
|
||||
0, 65535, 0
|
||||
}
|
||||
}
|
||||
|
@ -267,9 +270,9 @@ bool SoundMacro::CmdSplitVel::Do(SoundMacroState& st, Voice& vox) const
|
|||
{
|
||||
/* Do Branch */
|
||||
if (macro.id == std::get<0>(st.m_pc.back()))
|
||||
st._setPC(macroStep);
|
||||
st._setPC(macroStep.step);
|
||||
else
|
||||
vox.loadMacroObject(macro.id, macroStep, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);
|
||||
vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -370,7 +373,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdLoop::Introspective =
|
|||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdLoop, macroStep),
|
||||
"SoundMacro Step"sv,
|
||||
"Macro Step"sv,
|
||||
0, 65535, 0
|
||||
},
|
||||
{
|
||||
|
@ -400,7 +403,7 @@ bool SoundMacro::CmdLoop::Do(SoundMacroState& st, Voice& vox) const
|
|||
{
|
||||
/* Loop back to step */
|
||||
--st.m_loopCountdown;
|
||||
st._setPC(macroStep);
|
||||
st._setPC(macroStep.step);
|
||||
}
|
||||
else /* Break out of loop */
|
||||
st.m_loopCountdown = -1;
|
||||
|
@ -416,12 +419,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdGoto::Introspective =
|
|||
{
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdGoto, macro),
|
||||
"SoundMacro"sv,
|
||||
0, 65535, 0
|
||||
"Macro"sv,
|
||||
0, 65535, 65535
|
||||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdGoto, macroStep),
|
||||
"SoundMacro Step"sv,
|
||||
"Macro Step"sv,
|
||||
0, 65535, 0
|
||||
}
|
||||
}
|
||||
|
@ -430,9 +433,9 @@ bool SoundMacro::CmdGoto::Do(SoundMacroState& st, Voice& vox) const
|
|||
{
|
||||
/* Do Branch */
|
||||
if (macro.id == std::get<0>(st.m_pc.back()))
|
||||
st._setPC(macroStep);
|
||||
st._setPC(macroStep.step);
|
||||
else
|
||||
vox.loadMacroObject(macro.id, macroStep, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);
|
||||
vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -516,12 +519,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdPlayMacro::Introspective =
|
|||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdPlayMacro, macro),
|
||||
"SoundMacro"sv,
|
||||
0, 65535, 0
|
||||
"Macro"sv,
|
||||
0, 65535, 65535
|
||||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdPlayMacro, macroStep),
|
||||
"SoundMacro Step"sv,
|
||||
"Macro Step"sv,
|
||||
0, 65535, 0
|
||||
},
|
||||
{
|
||||
|
@ -538,7 +541,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdPlayMacro::Introspective =
|
|||
};
|
||||
bool SoundMacro::CmdPlayMacro::Do(SoundMacroState& st, Voice& vox) const
|
||||
{
|
||||
std::shared_ptr<Voice> sibVox = vox.startChildMacro(addNote, macro.id, macroStep);
|
||||
std::shared_ptr<Voice> sibVox = vox.startChildMacro(addNote, macro.id, macroStep.step);
|
||||
if (sibVox)
|
||||
st.m_lastPlayMacroVid = sibVox->vid();
|
||||
|
||||
|
@ -597,12 +600,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSplitMod::Introspective =
|
|||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSplitMod, macro),
|
||||
"SoundMacro"sv,
|
||||
0, 65535, 0
|
||||
"Macro"sv,
|
||||
0, 65535, 65535
|
||||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSplitMod, macroStep),
|
||||
"SoundMacro Step"sv,
|
||||
"Macro Step"sv,
|
||||
0, 65535, 0
|
||||
}
|
||||
}
|
||||
|
@ -613,9 +616,9 @@ bool SoundMacro::CmdSplitMod::Do(SoundMacroState& st, Voice& vox) const
|
|||
{
|
||||
/* Do Branch */
|
||||
if (macro.id == std::get<0>(st.m_pc.back()))
|
||||
st._setPC(macroStep);
|
||||
st._setPC(macroStep.step);
|
||||
else
|
||||
vox.loadMacroObject(macro.id, macroStep, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);
|
||||
vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -663,7 +666,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSetAdsr::Introspective =
|
|||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSetAdsr, table),
|
||||
"ADSR"sv,
|
||||
0, 16383, 0
|
||||
0, 65535, 65535
|
||||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSetAdsr, dlsMode),
|
||||
|
@ -698,7 +701,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdScaleVolume::Introspective =
|
|||
{
|
||||
FIELD_HEAD(SoundMacro::CmdScaleVolume, table),
|
||||
"Curve"sv,
|
||||
0, 16383, 0
|
||||
0, 65535, 65535
|
||||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdScaleVolume, originalVol),
|
||||
|
@ -777,7 +780,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdEnvelope::Introspective =
|
|||
{
|
||||
FIELD_HEAD(SoundMacro::CmdEnvelope, table),
|
||||
"Curve"sv,
|
||||
0, 16383, 0
|
||||
0, 65535, 65535
|
||||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdEnvelope, msSwitch),
|
||||
|
@ -900,12 +903,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSplitRnd::Introspective =
|
|||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSplitRnd, macro),
|
||||
"SoundMacro"sv,
|
||||
0, 65535, 0
|
||||
"Macro"sv,
|
||||
0, 65535, 65535
|
||||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSplitRnd, macroStep),
|
||||
"SoundMacro Step"sv,
|
||||
"Macro Step"sv,
|
||||
0, 65535, 0
|
||||
},
|
||||
}
|
||||
|
@ -916,9 +919,9 @@ bool SoundMacro::CmdSplitRnd::Do(SoundMacroState& st, Voice& vox) const
|
|||
{
|
||||
/* Do branch */
|
||||
if (macro.id == std::get<0>(st.m_pc.back()))
|
||||
st._setPC(macroStep);
|
||||
st._setPC(macroStep.step);
|
||||
else
|
||||
vox.loadMacroObject(macro.id, macroStep, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);
|
||||
vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -944,7 +947,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdFadeIn::Introspective =
|
|||
{
|
||||
FIELD_HEAD(SoundMacro::CmdFadeIn, table),
|
||||
"Curve"sv,
|
||||
0, 16383, 0
|
||||
0, 65535, 65535
|
||||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdFadeIn, msSwitch),
|
||||
|
@ -1474,7 +1477,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSetPitchAdsr::Introspective =
|
|||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSetPitchAdsr, table),
|
||||
"ADSR"sv,
|
||||
0, 16383, 0,
|
||||
0, 65535, 65535,
|
||||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSetPitchAdsr, keys),
|
||||
|
@ -1592,12 +1595,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdGoSub::Introspective =
|
|||
{
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSplitRnd, macro),
|
||||
"SoundMacro"sv,
|
||||
0, 65535, 0
|
||||
"Macro"sv,
|
||||
0, 65535, 65535
|
||||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSplitRnd, macroStep),
|
||||
"SoundMacro Step"sv,
|
||||
"Macro Step"sv,
|
||||
0, 65535, 0
|
||||
},
|
||||
}
|
||||
|
@ -1606,9 +1609,9 @@ bool SoundMacro::CmdGoSub::Do(SoundMacroState& st, Voice& vox) const
|
|||
{
|
||||
if (macro.id == std::get<0>(st.m_pc.back()))
|
||||
st.m_pc.emplace_back(std::get<0>(st.m_pc.back()), std::get<1>(st.m_pc.back()),
|
||||
std::get<1>(st.m_pc.back())->assertPC(macroStep));
|
||||
std::get<1>(st.m_pc.back())->assertPC(macroStep.step));
|
||||
else
|
||||
vox.loadMacroObject(macro.id, macroStep, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod, true);
|
||||
vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod, true);
|
||||
|
||||
vox._setObjectId(std::get<0>(st.m_pc.back()));
|
||||
|
||||
|
@ -1633,12 +1636,12 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdTrapEvent::Introspective =
|
|||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdTrapEvent, macro),
|
||||
"SoundMacro"sv,
|
||||
0, 65535, 0
|
||||
"Macro"sv,
|
||||
0, 65535, 65535
|
||||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdTrapEvent, macroStep),
|
||||
"SoundMacro Step"sv,
|
||||
"Macro Step"sv,
|
||||
0, 65535, 0
|
||||
},
|
||||
}
|
||||
|
@ -1649,15 +1652,15 @@ bool SoundMacro::CmdTrapEvent::Do(SoundMacroState& st, Voice& vox) const
|
|||
{
|
||||
case EventType::KeyOff:
|
||||
vox.m_keyoffTrap.macroId = macro.id;
|
||||
vox.m_keyoffTrap.macroStep = macroStep;
|
||||
vox.m_keyoffTrap.macroStep = macroStep.step;
|
||||
break;
|
||||
case EventType::SampleEnd:
|
||||
vox.m_sampleEndTrap.macroId = macro.id;
|
||||
vox.m_sampleEndTrap.macroStep = macroStep;
|
||||
vox.m_sampleEndTrap.macroStep = macroStep.step;
|
||||
break;
|
||||
case EventType::MessageRecv:
|
||||
vox.m_messageTrap.macroId = macro.id;
|
||||
vox.m_messageTrap.macroStep = macroStep;
|
||||
vox.m_messageTrap.macroStep = macroStep.step;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -1722,8 +1725,8 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdSendMessage::Introspective =
|
|||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSendMessage, macro),
|
||||
"SoundMacro"sv,
|
||||
1, 16383, 1
|
||||
"Macro"sv,
|
||||
1, 65535, 65535
|
||||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdSendMessage, voiceVar),
|
||||
|
@ -3118,7 +3121,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdIfEqual::Introspective =
|
|||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdIfEqual, macroStep),
|
||||
"SoundMacro Step"sv,
|
||||
"Macro Step"sv,
|
||||
0, 65535, 0
|
||||
},
|
||||
}
|
||||
|
@ -3138,7 +3141,7 @@ bool SoundMacro::CmdIfEqual::Do(SoundMacroState& st, Voice& vox) const
|
|||
useB = st.m_variables[b & 0x1f];
|
||||
|
||||
if ((useA == useB) ^ notEq)
|
||||
st._setPC(macroStep);
|
||||
st._setPC(macroStep.step);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -3176,7 +3179,7 @@ const SoundMacro::CmdIntrospection SoundMacro::CmdIfLess::Introspective =
|
|||
},
|
||||
{
|
||||
FIELD_HEAD(SoundMacro::CmdIfLess, macroStep),
|
||||
"SoundMacro Step"sv,
|
||||
"Macro Step"sv,
|
||||
0, 65535, 0
|
||||
},
|
||||
}
|
||||
|
@ -3196,7 +3199,7 @@ bool SoundMacro::CmdIfLess::Do(SoundMacroState& st, Voice& vox) const
|
|||
useB = st.m_variables[b & 0x1f];
|
||||
|
||||
if ((useA < useB) ^ notLt)
|
||||
st._setPC(macroStep);
|
||||
st._setPC(macroStep.step);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue