diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 9937e19..99410fa 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -1,7 +1,7 @@ find_package(Qt5) -find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets) -find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Multimedia) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Multimedia) set(PROJECT_SOURCES siview/chunkmodel.cpp @@ -19,11 +19,18 @@ set(PROJECT_SOURCES siview/siview.cpp siview/siview.h + viewer/bitmappanel.cpp + viewer/bitmappanel.h + viewer/wavpanel.cpp + viewer/wavpanel.h + main.cpp mainwindow.cpp mainwindow.h - #objectmodel.cpp - #objectmodel.h + model.cpp + model.h + objectmodel.cpp + objectmodel.h panel.cpp panel.h vector3edit.cpp @@ -43,7 +50,7 @@ else() ) endif() -target_link_libraries(si-edit PRIVATE Qt${QT_VERSION_MAJOR}::Widgets libweaver) +target_link_libraries(si-edit PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Multimedia libweaver) target_include_directories(si-edit PRIVATE "${CMAKE_SOURCE_DIR}/lib") set_target_properties(si-edit PROPERTIES diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index c6dd51c..725d91f 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -6,7 +6,6 @@ #include #include -#include "interleaf.h" #include "siview/siview.h" using namespace si; @@ -20,18 +19,45 @@ MainWindow::MainWindow(QWidget *parent) : this->setCentralWidget(splitter); tree_ = new QTreeView(); - //tree_->setModel(&chunk_model_); + tree_->setModel(&model_); tree_->setContextMenuPolicy(Qt::CustomContextMenu); connect(tree_->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &MainWindow::SelectionChanged); connect(tree_, &QTreeView::customContextMenuRequested, this, &MainWindow::ShowContextMenu); splitter->addWidget(tree_); + auto config_area = new QWidget(); + splitter->addWidget(config_area); + + auto config_layout = new QVBoxLayout(config_area); + + action_grp_ = new QGroupBox(); + config_layout->addWidget(action_grp_); + + auto action_layout = new QHBoxLayout(action_grp_); + + action_layout->addStretch(); + + auto extract_btn = new QPushButton(tr("Extract")); + action_layout->addWidget(extract_btn); + connect(extract_btn, &QPushButton::clicked, this, &MainWindow::ExtractClicked); + + auto replace_btn = new QPushButton(tr("Replace")); + action_layout->addWidget(replace_btn); + + action_layout->addStretch(); + config_stack_ = new QStackedWidget(); - splitter->addWidget(config_stack_); + config_layout->addWidget(config_stack_); panel_blank_ = new Panel(); config_stack_->addWidget(panel_blank_); + panel_wav_ = new WavPanel(); + config_stack_->addWidget(panel_wav_); + + panel_bmp_ = new BitmapPanel(); + config_stack_->addWidget(panel_bmp_); + InitializeMenuBar(); splitter->setSizes({99999, 99999}); @@ -43,8 +69,9 @@ void MainWindow::OpenFilename(const QString &s) if (si.Read(s.toStdString())) { SIViewDialog d(SIViewDialog::Import, &si, this); if (d.exec() == QDialog::Accepted) { - Interleaf interleaf; - interleaf.Parse(&si); + model_.SetCore(nullptr); + interleaf_.Parse(&si); + model_.SetCore(&interleaf_); } } else { QMessageBox::critical(this, QString(), tr("Failed to load Interleaf file.")); @@ -76,7 +103,7 @@ void MainWindow::InitializeMenuBar() setMenuBar(menubar); } -void MainWindow::SetPanel(Panel *panel, si::Chunk *chunk) +void MainWindow::SetPanel(Panel *panel, si::Object *chunk) { auto current = static_cast(config_stack_->currentWidget()); current->SetData(nullptr); @@ -84,6 +111,28 @@ void MainWindow::SetPanel(Panel *panel, si::Chunk *chunk) config_stack_->setCurrentWidget(panel); panel->SetData(chunk); last_set_data_ = chunk; + + action_grp_->setEnabled(chunk); +} + +void MainWindow::ExtractObject(si::Object *obj) +{ + QString filename = QString::fromStdString(obj->filename()); + if (filename.isEmpty()) { + filename = QString::fromStdString(obj->name()); + filename.append(QStringLiteral(".bin")); + } + + QString s = QFileDialog::getSaveFileName(this, tr("Export Object"), filename); + if (!s.isEmpty()) { + QFile f(s); + if (f.open(QFile::WriteOnly)) { + f.write(obj->data().data(), obj->data().size()); + f.close(); + } else { + QMessageBox::critical(this, QString(), tr("Failed to write to file \"%1\".").arg(s)); + } + } } void MainWindow::OpenFile() @@ -97,10 +146,17 @@ void MainWindow::OpenFile() void MainWindow::SelectionChanged(const QModelIndex &index) { Panel *p = panel_blank_; - Chunk *c = static_cast(index.internalPointer()); + Object *c = dynamic_cast(static_cast(index.internalPointer())); if (c) { - // HECK + switch (c->filetype()) { + case MxOb::WAV: + p = panel_wav_; + break; + case MxOb::STL: + p = panel_bmp_; + break; + } } if (p != config_stack_->currentWidget() || c != last_set_data_) { @@ -126,21 +182,13 @@ void MainWindow::ExtractSelectedItems() } for (const QModelIndex &i : selected) { - if (Chunk *chunk = static_cast(i.internalPointer())) { - QString filename(chunk->data("FileName")); - if (filename.isEmpty()) { - filename = QString(chunk->data("Name")); - filename.append(QStringLiteral(".bin")); - } - if (filename.isEmpty()) { - filename = QStringLiteral("%1_%2.bin").arg(QString::fromLatin1((const char *) &chunk->id(), sizeof(uint32_t)), - QString::number(chunk->offset(), 16)); - } - - QString s = QFileDialog::getSaveFileName(this, tr("Export Object"), filename); - if (!s.isEmpty()) { - //chunk->Export() - } + if (Object *obj = dynamic_cast(static_cast(i.internalPointer()))) { + ExtractObject(obj); } } } + +void MainWindow::ExtractClicked() +{ + ExtractObject(last_set_data_); +} diff --git a/app/mainwindow.h b/app/mainwindow.h index e017f29..2c2b1b9 100644 --- a/app/mainwindow.h +++ b/app/mainwindow.h @@ -1,12 +1,17 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H -#include +#include +#include +#include #include #include #include +#include "objectmodel.h" #include "panel.h" +#include "viewer/bitmappanel.h" +#include "viewer/wavpanel.h" class MainWindow : public QMainWindow { @@ -23,15 +28,24 @@ private: void InitializeMenuBar(); - void SetPanel(Panel *panel, si::Chunk *chunk); + void SetPanel(Panel *panel, si::Object *chunk); + + void ExtractObject(si::Object *obj); QStackedWidget *config_stack_; QTreeView *tree_; - Panel *panel_blank_; + QGroupBox *action_grp_; - si::Chunk *last_set_data_; + Panel *panel_blank_; + WavPanel *panel_wav_; + BitmapPanel *panel_bmp_; + + ObjectModel model_; + si::Interleaf interleaf_; + + si::Object *last_set_data_; private slots: void OpenFile(); @@ -44,6 +58,8 @@ private slots: void ExtractSelectedItems(); + void ExtractClicked(); + }; #endif // MAINWINDOW_H diff --git a/app/model.cpp b/app/model.cpp new file mode 100644 index 0000000..fc97758 --- /dev/null +++ b/app/model.cpp @@ -0,0 +1,67 @@ +#include "model.h" + +#define super QAbstractItemModel + +Model::Model(QObject *parent) : + super(parent), + core_(nullptr) +{ + +} + +void Model::SetCore(si::Core *c) +{ + beginResetModel(); + core_ = c; + endResetModel(); +} + +si::Core *Model::GetCoreFromIndex(const QModelIndex &index) const +{ + if (!index.isValid()) { + return core_; + } else { + return static_cast(index.internalPointer()); + } +} + +QModelIndex Model::index(int row, int column, const QModelIndex &parent) const +{ + si::Core *c = GetCoreFromIndex(parent); + if (!c) { + return QModelIndex(); + } + + return createIndex(row, column, c->GetChildAt(row)); +} + +QModelIndex Model::parent(const QModelIndex &index) const +{ + si::Core *child = GetCoreFromIndex(index); + if (!child) { + return QModelIndex(); + } + + si::Core *parent = child->GetParent(); + if (!parent) { + return QModelIndex(); + } + + si::Core *grandparent = parent->GetParent(); + if (!grandparent) { + return QModelIndex(); + } + + size_t row = grandparent->IndexOfChild(parent); + return createIndex(int(row), 0, parent); +} + +int Model::rowCount(const QModelIndex &parent) const +{ + si::Core *c = GetCoreFromIndex(parent); + if (!c) { + return 0; + } + + return int(c->GetChildCount()); +} diff --git a/app/model.h b/app/model.h new file mode 100644 index 0000000..6c6fd90 --- /dev/null +++ b/app/model.h @@ -0,0 +1,27 @@ +#ifndef MODEL_H +#define MODEL_H + +#include +#include + +class Model : public QAbstractItemModel +{ +public: + explicit Model(QObject *parent = nullptr); + + si::Core *GetCore() const { return core_; } + void SetCore(si::Core *c); + + virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + virtual QModelIndex parent(const QModelIndex &index) const override; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + +protected: + si::Core *GetCoreFromIndex(const QModelIndex &index) const; + +private: + si::Core *core_; + +}; + +#endif // MODEL_H diff --git a/app/objectmodel.cpp b/app/objectmodel.cpp index 315544c..a907cc5 100644 --- a/app/objectmodel.cpp +++ b/app/objectmodel.cpp @@ -1,8 +1,8 @@ #include "objectmodel.h" -#include +#include -#define super QAbstractItemModel +#define super Model using namespace si; @@ -16,49 +16,23 @@ int ObjectModel::columnCount(const QModelIndex &parent) const return kColCount; } -QModelIndex ObjectModel::index(int row, int column, const QModelIndex &parent) const -{ - return createIndex(row, column, GetItem(row)); -} - -QModelIndex ObjectModel::parent(const QModelIndex &index) const -{ - return QModelIndex(); -} - -int ObjectModel::rowCount(const QModelIndex &parent) const -{ - if (parent.isValid()) { - return 0; - } else { - Chunk *mxof = GetMxOf(); - if (!mxof) { - return 0; - } - - return int(mxof->data("Offsets").size() / sizeof(uint32_t)); - } -} - QVariant ObjectModel::data(const QModelIndex &index, int role) const { - uint32_t offset; - Chunk *c = GetItem(index.row(), &offset); + Core *c = GetCoreFromIndex(index); switch (role) { case Qt::DisplayRole: switch (index.column()) { case kColIndex: - return index.row(); - case kColOffset: - return QStringLiteral("0x%1").arg(QString::number(offset, 16).toUpper()); + if (Object *o = dynamic_cast(c)) { + return QString::number(o->id()); + } + //return index.row(); + break; case kColName: - if (c) { - Chunk *mxob = c->FindChildWithType(Chunk::TYPE_MxOb); - if (mxob) { - return QString(mxob->data("Name")); - } + if (Object *o = dynamic_cast(c)) { + return QString::fromStdString(o->name()); } break; } @@ -75,8 +49,6 @@ QVariant ObjectModel::headerData(int section, Qt::Orientation orientation, int r switch (section) { case kColIndex: return tr("Index"); - case kColOffset: - return tr("Offset"); case kColName: return tr("Name"); } diff --git a/app/objectmodel.h b/app/objectmodel.h index 28a8161..612f879 100644 --- a/app/objectmodel.h +++ b/app/objectmodel.h @@ -1,9 +1,11 @@ #ifndef OBJECTMODEL_H #define OBJECTMODEL_H -#include +#include -class ObjectModel : public QAbstractItemModel +#include "model.h" + +class ObjectModel : public Model { Q_OBJECT public: @@ -17,9 +19,6 @@ public: explicit ObjectModel(QObject *parent = nullptr); virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override; - virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - virtual QModelIndex parent(const QModelIndex &index) const override; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; diff --git a/app/panel.h b/app/panel.h index c431a64..1c60694 100644 --- a/app/panel.h +++ b/app/panel.h @@ -10,6 +10,7 @@ class Panel : public QWidget public: explicit Panel(QWidget *parent = nullptr); + void *GetData() const { return data_; } void SetData(void *data); signals: diff --git a/app/siview/chunkmodel.cpp b/app/siview/chunkmodel.cpp index 35b6904..f164be0 100644 --- a/app/siview/chunkmodel.cpp +++ b/app/siview/chunkmodel.cpp @@ -2,13 +2,12 @@ #include -#define super QAbstractItemModel +#define super Model using namespace si; ChunkModel::ChunkModel(QObject *parent) : - super{parent}, - chunk_(nullptr) + super{parent} { } @@ -17,50 +16,9 @@ int ChunkModel::columnCount(const QModelIndex &parent) const return kColCount; } -QModelIndex ChunkModel::index(int row, int column, const QModelIndex &parent) const -{ - Chunk *c = GetChunkFromIndex(parent); - if (!c) { - return QModelIndex(); - } - - return createIndex(row, column, c->GetChildAt(row)); -} - -QModelIndex ChunkModel::parent(const QModelIndex &index) const -{ - Chunk *child = GetChunkFromIndex(index); - if (!child) { - return QModelIndex(); - } - - Core *parent = child->GetParent(); - if (!parent) { - return QModelIndex(); - } - - Core *grandparent = parent->GetParent(); - if (!grandparent) { - return QModelIndex(); - } - - size_t row = grandparent->IndexOfChild(parent); - return createIndex(int(row), 0, parent); -} - -int ChunkModel::rowCount(const QModelIndex &parent) const -{ - Chunk *c = GetChunkFromIndex(parent); - if (!c) { - return 0; - } - - return int(c->GetChildCount()); -} - QVariant ChunkModel::data(const QModelIndex &index, int role) const { - Chunk *c = GetChunkFromIndex(index); + Chunk *c = static_cast(GetCoreFromIndex(index)); if (!c) { return QVariant(); } @@ -108,19 +66,3 @@ QVariant ChunkModel::headerData(int section, Qt::Orientation orientation, int ro return super::headerData(section, orientation, role); } - -void ChunkModel::SetChunk(si::Chunk *c) -{ - beginResetModel(); - chunk_ = c; - endResetModel(); -} - -si::Chunk *ChunkModel::GetChunkFromIndex(const QModelIndex &index) const -{ - if (!index.isValid()) { - return chunk_; - } else { - return static_cast(index.internalPointer()); - } -} diff --git a/app/siview/chunkmodel.h b/app/siview/chunkmodel.h index 23447f5..6b1160f 100644 --- a/app/siview/chunkmodel.h +++ b/app/siview/chunkmodel.h @@ -2,9 +2,10 @@ #define CHUNKMODEL_H #include -#include -class ChunkModel : public QAbstractItemModel +#include "model.h" + +class ChunkModel : public Model { Q_OBJECT public: @@ -21,22 +22,9 @@ public: explicit ChunkModel(QObject *parent = nullptr); virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override; - virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - virtual QModelIndex parent(const QModelIndex &index) const override; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - void SetChunk(si::Chunk *c); - -protected: - si::Chunk *GetChunkFromIndex(const QModelIndex &index) const; - - si::Chunk *root() const { return chunk_; } - -private: - si::Chunk *chunk_; - }; #endif // CHUNKMODEL_H diff --git a/app/siview/panels/mxch.cpp b/app/siview/panels/mxch.cpp index aaa6983..7e9c6f1 100644 --- a/app/siview/panels/mxch.cpp +++ b/app/siview/panels/mxch.cpp @@ -71,6 +71,17 @@ MxChPanel::MxChPanel(QWidget *parent) : data_sz_edit_->setMaximum(INT_MAX); layout()->addWidget(data_sz_edit_, row, 1); + row++; + + show_data_btn_ = new QPushButton(tr("Show Data")); + connect(show_data_btn_, &QPushButton::clicked, this, &MxChPanel::ShowDataField); + layout()->addWidget(show_data_btn_, row, 0, 1, 2); + + data_edit_ = new QTextEdit(); + data_edit_->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + layout()->addWidget(data_edit_, row, 0, 1, 2); + data_edit_->hide(); + FinishLayout(); } @@ -99,6 +110,10 @@ void MxChPanel::OnClosingData(void *data) chunk->data("Object") = u32(obj_edit_->value()); chunk->data("Time") = u32(ms_offset_edit_->value()); chunk->data("DataSize") = u32(data_sz_edit_->value());*/ + + show_data_btn_->show(); + data_edit_->clear(); + data_edit_->hide(); } void MxChPanel::FlagCheckBoxClicked(bool e) @@ -116,3 +131,15 @@ void MxChPanel::FlagCheckBoxClicked(bool e) flag_edit_->setText(QString::number(current, 16)); } } + +void MxChPanel::ShowDataField() +{ + show_data_btn_->hide(); + data_edit_->show(); + + Chunk *chunk = static_cast(GetData()); + + const Data &data = chunk->data("Data"); + QByteArray ba(data.data(), int(data.size())); + data_edit_->setPlainText(ba.toHex()); +} diff --git a/app/siview/panels/mxch.h b/app/siview/panels/mxch.h index 7cbb74f..8ab1a56 100644 --- a/app/siview/panels/mxch.h +++ b/app/siview/panels/mxch.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -23,12 +24,16 @@ private: QSpinBox *obj_edit_; QSpinBox *ms_offset_edit_; QSpinBox *data_sz_edit_; + QTextEdit *data_edit_; + QPushButton *show_data_btn_; QVector flag_checkboxes_; private slots: void FlagCheckBoxClicked(bool e); + void ShowDataField(); + }; #endif // MXCHPANEL_H diff --git a/app/siview/siview.cpp b/app/siview/siview.cpp index 7e6c8ca..44ebb36 100644 --- a/app/siview/siview.cpp +++ b/app/siview/siview.cpp @@ -20,7 +20,7 @@ SIViewDialog::SIViewDialog(Mode mode, Chunk *riff, QWidget *parent) : layout->addWidget(splitter); auto tree = new QTreeView(); - chunk_model_.SetChunk(riff); + chunk_model_.SetCore(riff); tree->setModel(&chunk_model_); tree->setContextMenuPolicy(Qt::CustomContextMenu); connect(tree->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &SIViewDialog::SelectionChanged); @@ -53,6 +53,7 @@ SIViewDialog::SIViewDialog(Mode mode, Chunk *riff, QWidget *parent) : btn_layout->addStretch(); auto accept_btn = new QPushButton(mode == Import ? tr("De-Weave") : tr("Weave")); + accept_btn->setDefault(true); connect(accept_btn, &QPushButton::clicked, this, &SIViewDialog::accept); btn_layout->addWidget(accept_btn); diff --git a/app/viewer/bitmappanel.cpp b/app/viewer/bitmappanel.cpp new file mode 100644 index 0000000..0bdabdc --- /dev/null +++ b/app/viewer/bitmappanel.cpp @@ -0,0 +1,39 @@ +#include "bitmappanel.h" + +#include +#include +#include + +BitmapPanel::BitmapPanel(QWidget *parent) +{ + int row = 0; + + auto preview_grp = new QGroupBox(tr("Bitmap")); + layout()->addWidget(preview_grp, row, 0, 1, 2); + + auto preview_layout = new QVBoxLayout(preview_grp); + + img_lbl_ = new QLabel(); + img_lbl_->setAlignment(Qt::AlignHCenter); + preview_layout->addWidget(img_lbl_); + + desc_lbl_ = new QLabel(); + desc_lbl_->setAlignment(Qt::AlignHCenter); + preview_layout->addWidget(desc_lbl_); + + FinishLayout(); +} + +void BitmapPanel::OnOpeningData(void *data) +{ + si::Object *o = static_cast(data); + + QByteArray b(o->data().data(), o->data().size()); + QBuffer buf(&b); + QImage img; + img.load(&buf, "BMP"); + + img_lbl_->setPixmap(QPixmap::fromImage(img)); + + desc_lbl_->setText(tr("%1x%2").arg(QString::number(img.width()), QString::number(img.height()))); +} diff --git a/app/viewer/bitmappanel.h b/app/viewer/bitmappanel.h new file mode 100644 index 0000000..6a258fd --- /dev/null +++ b/app/viewer/bitmappanel.h @@ -0,0 +1,24 @@ +#ifndef BITMAPPANEL_H +#define BITMAPPANEL_H + +#include +#include + +class BitmapPanel : public Panel +{ + Q_OBJECT +public: + BitmapPanel(QWidget *parent = nullptr); + +protected: + virtual void OnOpeningData(void *data) override; + //virtual void OnClosingData(void *data) override; + +private: + QLabel *img_lbl_; + + QLabel *desc_lbl_; + +}; + +#endif // BITMAPPANEL_H diff --git a/app/viewer/wavpanel.cpp b/app/viewer/wavpanel.cpp new file mode 100644 index 0000000..0b5a3b1 --- /dev/null +++ b/app/viewer/wavpanel.cpp @@ -0,0 +1,146 @@ +#include "wavpanel.h" + +#include +#include +#include +#include + +WavPanel::WavPanel(QWidget *parent) : + Panel(parent), + audio_out_(nullptr) +{ + int row = 0; + + auto wav_group = new QGroupBox(tr("Playback")); + layout()->addWidget(wav_group, row, 0, 1, 2); + + auto wav_layout = new QHBoxLayout(wav_group); + + playhead_slider_ = new QSlider(Qt::Horizontal); + playhead_slider_->setMinimum(0); + connect(playhead_slider_, &QSlider::valueChanged, this, &WavPanel::SliderMoved); + wav_layout->addWidget(playhead_slider_); + + play_btn_ = new QPushButton(tr("Play")); + play_btn_->setCheckable(true); + connect(play_btn_, &QPushButton::clicked, this, &WavPanel::Play); + wav_layout->addWidget(play_btn_); + + FinishLayout(); + + buffer_.setBuffer(&array_); + + playback_timer_ = new QTimer(this); + playback_timer_->setInterval(100); + connect(playback_timer_, &QTimer::timeout, this, &WavPanel::TimerUpdate); +} + +void WavPanel::OnOpeningData(void *data) +{ + si::Object *o = static_cast(data); + + // Find fmt and data + auto dat = o->data().data(); + for (size_t i=0; idata().size(); i++) { + if (!memcmp(dat+i, "fmt ", 4)) { + header_ = *(si::WAVFormatHeader *)(dat+i+8); + break; + } + } + for (size_t i=0; idata().size(); i++) { + if (!memcmp(dat+i, "data", 4)) { + uint32_t sz = *(uint32_t*)(dat+i+4); + playhead_slider_->setMaximum(sz/GetSampleSize()); + break; + } + } +} + +void WavPanel::OnClosingData(void *data) +{ + Play(false); + playhead_slider_->setValue(0); +} + +int WavPanel::GetSampleSize() const +{ + return (header_.BitsPerSample/8) * header_.Channels; +} + +void WavPanel::Play(bool e) +{ + if (audio_out_) { + audio_out_->stop(); + //audio_out_->deleteLater(); + delete audio_out_; + audio_out_ = nullptr; + } + buffer_.close(); + playback_timer_->stop(); + + if (e) { + const si::bytearray &lib = static_cast(GetData())->data(); + array_ = QByteArray(lib.data(), lib.size()); + buffer_.open(QBuffer::ReadOnly); + + size_t start = 0; + si::WAVFormatHeader header; + + // Find fmt and data + auto dat = lib.data(); + for (size_t i=0; ivalue() < playhead_slider_->maximum()) { + start += playhead_slider_->value() * GetSampleSize(); + } + + buffer_.seek(start); + + QAudioFormat audio_fmt; + audio_fmt.setSampleRate(header.SampleRate); + audio_fmt.setChannelCount(header.Channels); + audio_fmt.setSampleSize(header.BitsPerSample); + audio_fmt.setByteOrder(QAudioFormat::LittleEndian); + audio_fmt.setCodec(QStringLiteral("audio/pcm")); + audio_fmt.setSampleType(QAudioFormat::SignedInt); + + audio_out_ = new QAudioOutput(audio_fmt, this); + connect(audio_out_, &QAudioOutput::stateChanged, this, &WavPanel::OutputChanged); + audio_out_->start(&buffer_); + + playback_timer_->start(); + } +} + +void WavPanel::TimerUpdate() +{ + playhead_slider_->setValue((buffer_.pos() - buffer_start_) / GetSampleSize()); +} + +void WavPanel::OutputChanged(QAudio::State state) +{ + if (state != QAudio::ActiveState) { + Play(false); + play_btn_->setChecked(false); + } +} + +void WavPanel::SliderMoved(int i) +{ + if (buffer_.isOpen()) { + buffer_.seek(buffer_start_ + i * GetSampleSize()); + } +} diff --git a/app/viewer/wavpanel.h b/app/viewer/wavpanel.h new file mode 100644 index 0000000..3f943dd --- /dev/null +++ b/app/viewer/wavpanel.h @@ -0,0 +1,48 @@ +#ifndef WAVPANEL_H +#define WAVPANEL_H + +#include +#include +#include +#include +#include +#include +#include + +#include "panel.h" + +class WavPanel : public Panel +{ + Q_OBJECT +public: + WavPanel(QWidget *parent = nullptr); + +protected: + virtual void OnOpeningData(void *data) override; + virtual void OnClosingData(void *data) override; + +private: + int GetSampleSize() const; + + QSlider *playhead_slider_; + QPushButton *play_btn_; + + QAudioOutput *audio_out_; + QBuffer buffer_; + QByteArray array_; + size_t buffer_start_; + QTimer *playback_timer_; + si::WAVFormatHeader header_; + +private slots: + void Play(bool e); + + void TimerUpdate(); + + void OutputChanged(QAudio::State state); + + void SliderMoved(int i); + +}; + +#endif // WAVPANEL_H