From 8040f48974d88c6d30dff720a2ffb27e0cf54814 Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Sun, 23 Sep 2018 16:13:37 +0200 Subject: Integrate channel map into file list in instrument editor. --- src/Makefile.am | 8 + src/audioextractor.cc | 119 ++++-------- src/audioextractor.h | 15 +- src/canvas.cc | 5 +- src/canvas.h | 1 + src/canvastoolselections.cc | 4 +- src/channeldialog.cc | 77 ++++++++ src/channeldialog.h | 49 +++++ src/dgedit.cc | 19 +- src/filelist.cc | 450 +++++++++++++++++++++++++++++++++----------- src/filelist.h | 38 ++-- src/imageeditor.cc | 49 +++++ src/imageeditor.h | 40 ++++ src/instrumentwidget.cc | 20 +- src/localehandler.cc | 4 +- src/mainwindow.cc | 72 ++++++- src/mainwindow.h | 5 + src/player.cc | 5 +- src/player.h | 3 +- src/project.cc | 218 +++++++++++++++++++-- src/project.h | 74 +++++++- src/projectrenderer.cc | 124 ++++++++++++ src/projectrenderer.h | 54 ++++++ src/projectserialiser.cc | 69 +++++-- src/renderdialog.cc | 98 ++++++++++ src/renderdialog.h | 64 +++++++ 26 files changed, 1386 insertions(+), 298 deletions(-) create mode 100644 src/channeldialog.cc create mode 100644 src/channeldialog.h create mode 100644 src/imageeditor.cc create mode 100644 src/imageeditor.h create mode 100644 src/projectrenderer.cc create mode 100644 src/projectrenderer.h create mode 100644 src/renderdialog.cc create mode 100644 src/renderdialog.h diff --git a/src/Makefile.am b/src/Makefile.am index cefe0ff..43b87e3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -22,8 +22,10 @@ dgedit_SOURCES = \ canvastoolselections.cc \ canvastoolthreshold.cc \ canvaswidget.cc \ + channeldialog.cc \ channelswidget.cc \ filelist.cc \ + imageeditor.cc \ instrumentdialog.cc \ instrumentwidget.cc \ itemeditor.cc \ @@ -33,7 +35,9 @@ dgedit_SOURCES = \ player.cc \ project.cc \ projectdialog.cc \ + projectrenderer.cc \ projectserialiser.cc \ + renderdialog.cc \ samplesorter.cc \ selection.cc \ selectioneditor.cc \ @@ -49,8 +53,10 @@ EXTRA_DIST = \ canvastoolselections.h \ canvastoolthreshold.h \ canvaswidget.h \ + channeldialog.h \ channelswidget.h \ filelist.h \ + imageeditor.h \ instrumentdialog.h \ instrumentwidget.h \ itemeditor.h \ @@ -60,7 +66,9 @@ EXTRA_DIST = \ player.h \ project.h \ projectdialog.h \ + projectrenderer.h \ projectserialiser.h \ + renderdialog.h \ samplesorter.h \ selection.h \ selectioneditor.h \ diff --git a/src/audioextractor.cc b/src/audioextractor.cc index 2132cba..50f2947 100644 --- a/src/audioextractor.cc +++ b/src/audioextractor.cc @@ -43,17 +43,18 @@ typedef struct float *data; } audiodata_t; -AudioExtractor::AudioExtractor(Instrument& instrument, Selections &selections, - QObject *parent) +AudioExtractor::AudioExtractor(Instrument& instrument, QObject *parent) : QObject(parent) , instrument(instrument) - , selections(selections) { - audiofiles = instrument.getFileList(); } void AudioExtractor::exportSelections() { + auto selections = instrument.getSelections(); + auto exportpath = instrument.getProject().getExportPath(); + auto prefix = instrument.getPrefix(); + int samplerate = -1; emit setMaximumProgress(selections.ids().size() + 1/* for xml writing*/); int progress = 0; @@ -61,19 +62,20 @@ void AudioExtractor::exportSelections() qApp->processEvents(); // Open all input audio files: - audiodata_t audiodata[audiofiles.size()]; + auto audiofile_ids = instrument.getAudioFileList(); + audiodata_t audiodata[audiofile_ids.size()]; int idx = 0; - AudioFileList::iterator j = audiofiles.begin(); - while(j != audiofiles.end()) + for(auto audiofile_id : audiofile_ids) { - QString file = j->first; + const auto& audiofile = instrument.getAudioFile(audiofile_id); + QString file = audiofile.getFile(); SF_INFO sf_info; audiodata[idx].fh = sf_open(file.toStdString().c_str(), SFM_READ, &sf_info); if(!audiodata[idx].fh) { - printf("Load error '%s'\n", file.toStdString().c_str()); + printf("AudioExtractor load error '%s'\n", file.toStdString().c_str()); return; } @@ -85,7 +87,6 @@ void AudioExtractor::exportSelections() audiodata[idx].data = NULL; - j++; idx++; } @@ -127,7 +128,7 @@ void AudioExtractor::exportSelections() // Read all input audio file chunks: - for(int i = 0; i < audiofiles.size(); i++) + for(int i = 0; i < audiofile_ids.size(); i++) { // Clear out old buffer (if one exists) if(audiodata[i].data) @@ -175,23 +176,23 @@ void AudioExtractor::exportSelections() SF_INFO sf_info; sf_info.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; sf_info.samplerate = samplerate; - sf_info.channels = audiofiles.size(); + sf_info.channels = audiofile_ids.size(); SNDFILE *ofh = sf_open(file.toStdString().c_str(), SFM_WRITE, &sf_info); if(!ofh) { - printf("Open for write error...\n"); + printf("Open for write error. %s\n", file.toStdString().c_str()); return; } for(size_t ob = 0; ob < size; ob++) { - float obuf[audiofiles.size()]; - for(int ai = 0; ai < audiofiles.size(); ai++) + float obuf[audiofile_ids.size()]; + for(int ai = 0; ai < audiofile_ids.size(); ai++) { obuf[ai] = audiodata[ai].data[ob]; } - sf_write_float(ofh, obuf, audiofiles.size()); + sf_write_float(ofh, obuf, audiofile_ids.size()); } sf_close(ofh); @@ -203,7 +204,7 @@ void AudioExtractor::exportSelections() } // Close all input audio files: - for(int i = 0; i < audiofiles.size(); i++) + for(int i = 0; i < audiofile_ids.size(); i++) { if(audiodata[i].data) { @@ -219,13 +220,13 @@ void AudioExtractor::exportSelections() doc.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'"); doc.appendChild(header); - QDomElement instrument = doc.createElement("instrument"); - instrument.setAttribute("version", INSTRUMENT_VERSION); - instrument.setAttribute("name", prefix); - doc.appendChild(instrument); + QDomElement instrument_node = doc.createElement("instrument"); + instrument_node.setAttribute("version", INSTRUMENT_VERSION); + instrument_node.setAttribute("name", prefix); + doc.appendChild(instrument_node); QDomElement samples = doc.createElement("samples"); - instrument.appendChild(samples); + instrument_node.appendChild(samples); { // Do the adding to the xml file one sample at the time. @@ -246,20 +247,21 @@ void AudioExtractor::exportSelections() selections.update(*si, i); int channelnum = 1; // Filechannel numbers are 1-based. - AudioFileList::iterator j = audiofiles.begin(); - while(j != audiofiles.end()) + + for(auto audiofile_id : audiofile_ids) { - QString file = j->first; - QString name = j->second; - - QDomElement audiofile = doc.createElement("audiofile"); - audiofile.setAttribute("file", "samples/" + - QString::number(index) + "-" + prefix + ".wav"); - audiofile.setAttribute("channel", name); - audiofile.setAttribute("filechannel", QString::number(channelnum)); - sample.appendChild(audiofile); + const auto& audiofile = instrument.getAudioFile(audiofile_id); + + QString file = audiofile.getFile(); + QString name = audiofile.getName(); + + QDomElement audiofile_node = doc.createElement("audiofile"); + audiofile_node.setAttribute("file", "samples/" + + QString::number(index) + "-" + prefix + ".wav"); + audiofile_node.setAttribute("channel", name); + audiofile_node.setAttribute("filechannel", QString::number(channelnum)); + sample.appendChild(audiofile_node); channelnum++; - j++; } si++; @@ -274,52 +276,3 @@ void AudioExtractor::exportSelections() emit progressUpdate(progress++); qApp->processEvents(); } - -void AudioExtractor::addFile(QString file, QString name) -{ - QPair pair; - pair.first = file; - pair.second = name; - audiofiles.push_back(pair); - instrument.setFileList(audiofiles); -} - -void AudioExtractor::removeFile(QString file, QString name) -{ - AudioFileList::iterator j = audiofiles.begin(); - while(j != audiofiles.end()) - { - if(file == j->first/* && name == j->second*/) - { - audiofiles.erase(j); - break; - } - j++; - } - instrument.setFileList(audiofiles); -} - -void AudioExtractor::setOutputPrefix(const QString &p) -{ - prefix = p; -} - -void AudioExtractor::setExportPath(const QString &path) -{ - exportpath = path; -} - -void AudioExtractor::changeName(QString file, QString name) -{ - AudioFileList::iterator j = audiofiles.begin(); - while(j != audiofiles.end()) - { - if(file == j->first) - { - j->second = name; - break; - } - j++; - } - instrument.setFileList(audiofiles); -} diff --git a/src/audioextractor.h b/src/audioextractor.h index a38f706..35eb5ba 100644 --- a/src/audioextractor.h +++ b/src/audioextractor.h @@ -37,24 +37,15 @@ class Instrument; -typedef QLinkedList< QPair > AudioFileList; - class AudioExtractor : public QObject { Q_OBJECT public: - AudioExtractor(Instrument& instrument, Selections& selections, - QObject* parent); + AudioExtractor(Instrument& instrument, QObject* parent); public slots: - void addFile(QString file, QString name); - void changeName(QString file, QString name); - void removeFile(QString file, QString name); - void exportSelections(); - void setExportPath(const QString& path); - void setOutputPrefix(const QString& prefix); signals: void progressUpdate(int value); @@ -62,8 +53,4 @@ signals: private: Instrument& instrument; - Selections& selections; - AudioFileList audiofiles; - QString exportpath; - QString prefix; }; diff --git a/src/canvas.cc b/src/canvas.cc index f2956fc..b9356d6 100644 --- a/src/canvas.cc +++ b/src/canvas.cc @@ -96,11 +96,12 @@ void Canvas::load(QString file) SNDFILE *fh = sf_open(file.toStdString().c_str(), SFM_READ, &sf_info); if(!fh) { - printf("Load error...\n"); + printf("Canvas load error '%s'\n", file.toStdString().c_str()); return; } size = sf_info.frames; + samplerate = sf_info.samplerate; data = new float[size]; @@ -203,7 +204,7 @@ void Canvas::updateWav() painter.drawRect(0, 0, wav.width(), wav.height()); painter.setPen(colSec); - int step = 44100; + int step = samplerate; for(size_t i = 0; i < size; i += step) { painter.drawLine(mapX(i), mapY(1.0), mapX(i), mapY(-1.0)); diff --git a/src/canvas.h b/src/canvas.h index 425e38d..dbdd211 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -76,6 +76,7 @@ private: public: float* data; size_t size; + double samplerate{44100}; QVector tools; diff --git a/src/canvastoolselections.cc b/src/canvastoolselections.cc index 0012d8e..d3e90f1 100644 --- a/src/canvastoolselections.cc +++ b/src/canvastoolselections.cc @@ -293,7 +293,7 @@ void CanvasToolSelections::doAutoCreateSelections(bool preview) if(data[from] > 0.0) { - while(data[from] > data[from-1] && // Falling + while(/*data[from] > data[from-1] &&*/ // Falling data[from-1] > 0.0 // Not crossing zero ) { @@ -302,7 +302,7 @@ void CanvasToolSelections::doAutoCreateSelections(bool preview) } else if(data[from] < 0.0) { - while(data[from] < data[from-1] && // Rising + while(/*data[from] < data[from-1] &&*/ // Rising data[from-1] < 0.0 // Not crossing zero ) { diff --git a/src/channeldialog.cc b/src/channeldialog.cc new file mode 100644 index 0000000..440a37d --- /dev/null +++ b/src/channeldialog.cc @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * channeldialog.cc + * + * Fri Sep 21 17:27:08 CEST 2018 + * Copyright 2018 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include "channeldialog.h" + +#include + +#include +#include +#include + +#include +#include + +#include "project.h" + +ChannelDialog::ChannelDialog(QWidget* parent, Channel& channel) + : QDialog(parent) + , channel(channel) +{ + setWindowModality(Qt::ApplicationModal); + setWindowTitle(tr("Channel Dialog")); + setMinimumWidth(300); + + auto layout = new QGridLayout(); + setLayout(layout); + + int idx = 0; + + name = new QLineEdit(); + name->setText(channel.getChannelName()); + layout->addWidget(new QLabel(tr("Name of the channel:")), idx, 0, 1, 2); + idx++; + layout->addWidget(name, idx, 0); + idx++; + + auto buttons = + new QDialogButtonBox(QDialogButtonBox::Ok | + QDialogButtonBox::Cancel | + QDialogButtonBox::Apply); + connect(buttons, SIGNAL(accepted()), this, SLOT(apply())); + connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttons->button(QDialogButtonBox::Apply), SIGNAL(clicked()), + this, SLOT(apply())); + layout->addWidget(buttons, idx, 0, 1, 2); +} + +void ChannelDialog::apply() +{ + // Only send out one update signal + Project::RAIIBulkUpdate bulkUpdate(channel.getProject()); + channel.setChannelName(name->text()); +} diff --git a/src/channeldialog.h b/src/channeldialog.h new file mode 100644 index 0000000..a005a23 --- /dev/null +++ b/src/channeldialog.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * channeldialog.h + * + * Fri Sep 21 17:27:08 CEST 2018 + * Copyright 2018 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#pragma once + +#include +#include + +class Channel; + +class ChannelDialog + : public QDialog +{ + Q_OBJECT +public: + ChannelDialog(QWidget* parent, Channel& channel); + ~ChannelDialog() = default; + +private slots: + void apply(); + +private: + QLineEdit* name{nullptr}; + + Channel& channel; +}; diff --git a/src/dgedit.cc b/src/dgedit.cc index 3984f5a..218c73e 100644 --- a/src/dgedit.cc +++ b/src/dgedit.cc @@ -30,15 +30,30 @@ #include "settings.h" #include "localehandler.h" +extern Q_CORE_EXPORT QBasicAtomicInt qt_qhash_seed; + int main(int argc, char *argv[]) { QApplication app(argc, argv); + // Ugly hack to ensure the xml attribute order is the same each time a save + // or export is performed. + qt_qhash_seed.store(0); + LocaleHandler locale(app); Settings settings; - MainWindow wnd(settings); - wnd.show(); + MainWindow mainwindow(settings); + mainwindow.show(); + + if(argc > 1) + { + QFile file(argv[1]); + if(file.exists()) + { + mainwindow.loadProject(argv[1]); + } + } return app.exec(); } diff --git a/src/filelist.cc b/src/filelist.cc index 21b843e..c9e6a8a 100644 --- a/src/filelist.cc +++ b/src/filelist.cc @@ -35,35 +35,323 @@ #include "itemeditor.h" #include "project.h" -FileList::FileList(Instrument& instrument) - : instrument(instrument) +#include +#include + +#include + +#include +#include +#include +#include +#include + +class ChannelMapDeligate + : public QStyledItemDelegate { - setContextMenuPolicy(Qt::CustomContextMenu); +public: + ChannelMapDeligate(Instrument& instrument) + : instrument(instrument) + { + } - connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), - this, SLOT(popupMenu(const QPoint&))); + QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const override + { + // Name: + if(index.column() == 1) + { + auto w = new QLineEdit(parent); + return w; + } + + // Channel Map ID: + if(index.column() == 2) + { + auto w = new QComboBox(parent); + w->addItem(tr(""), -1); + auto channel_ids = instrument.getProject().getChannelList(); + for(auto channel_id : channel_ids) + { + const auto& channel = instrument.getProject().getChannel(channel_id); + w->addItem(channel.getChannelName(), channel.getId()); + } + return w; + } + + return QStyledItemDelegate::createEditor(parent, option, index); + } - connect(this, SIGNAL(itemDoubleClicked(QListWidgetItem*)), - this, SLOT(selectionChanged(QListWidgetItem*))); + void setEditorData(QWidget *editor, const QModelIndex &index) const override + { + // Name: + if(index.column() == 1) + { + auto w = static_cast(editor); + auto s = index.data(Qt::EditRole).toString(); + w->setText(s); + } + + // Channel Map ID: + if(index.column() == 2) + { + auto w = static_cast(editor); + auto i = w->findData(index.data(Qt::EditRole).toInt()); + w->setCurrentIndex(i); + } +} - createMenus(); + void setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const override + { // Name: + if(index.column() == 1) + { + model->setData(index, static_cast(editor)->text(), + Qt::EditRole); + } + + // Channel Map ID: + if(index.column() == 2) + { + model->setData(index, static_cast(editor)->currentData(), + Qt::EditRole); + } + } + +// void paint(QPainter * painter, +// const QStyleOptionViewItem & option, +// const QModelIndex & index ) const override +// { +// if(index.column() == 0) +// { +// painter->drawRect(option.rect); +// return; +// } +// QStyledItemDelegate::paint(painter, option, index); +// } + +private: + Instrument& instrument; +}; + +class FileDataModel + : public QAbstractItemModel +{ +public: + FileDataModel(Instrument& instrument) + : instrument(instrument) + { + } + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override + { + if(!hasIndex(row, column, parent)) + { + return QModelIndex(); + } + + auto audiofile_ids = instrument.getAudioFileList(); + if(!parent.isValid()) + { + if(row < audiofile_ids.size()) + { + return createIndex(row, column, &instrument); + } + else + { + return QModelIndex(); // row is out of bounds. + } + } + + return QModelIndex(); + } + + QModelIndex parent(const QModelIndex &index) const override + { + return QModelIndex(); // no parent + } - // Load files into list: - auto master_file = instrument.getMasterFile(); - auto files = instrument.getFileList(); - for(auto file : files) + int rowCount(const QModelIndex &parent = QModelIndex()) const override { - auto item = new QListWidgetItem(); - //item->setIcon(QPixmap(":icons/instrument.png")); - setItemFile(item, file.first); - setItemName(item, file.second); - setItemMaster(item, file.first == master_file); - addItem(item); + if(parent.column() > 0) // only return row count on first column + { + return 0; + } + + if(!parent.isValid()) + { + auto audiofile_ids = instrument.getAudioFileList(); + return audiofile_ids.size(); // root level + } + + return 0; // no children } + + int columnCount(const QModelIndex &parent = QModelIndex()) const override + { + return 4; + } + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override + { + if(!index.isValid()) + { + return QVariant(); + } + + auto audiofile_ids = instrument.getAudioFileList(); + + auto audiofile_id = audiofile_ids.begin() + index.row(); + const auto& audiofile = instrument.getAudioFile(*audiofile_id); + + if(role == Qt::DecorationRole ) + { + if(index.column() == 0) + { + if(audiofile.getAbsoluteFile() == instrument.getMasterFile()) + { + return QPixmap(":/icons/master.png"); + } + else + { + return QPixmap(":/icons/file.png"); + } + } + } + + if(role == Qt::DisplayRole) + { + switch(index.column()) + { + case 1: return audiofile.getName(); + case 2: + { + auto channelMapId = audiofile.getChannelMapId(); + if(channelMapId == -1) + { + return tr(""); + } + return instrument.getProject().getChannel(channelMapId).getChannelName(); + } + case 3: return audiofile.getFile(); + default: return QVariant(); + } + } + else if(role == Qt::EditRole) + { + switch(index.column()) + { + case 1: return audiofile.getName(); + case 2: return audiofile.getChannelMapId(); + case 3: return audiofile.getFile(); + default: return QVariant(); + } + } + else + + { + return QVariant(); + } + } + + QVariant headerData(int section, Qt::Orientation orientation, int role) const override + { + if(orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch(section) + { + case 0: return tr("M"); + case 1: return tr("Name"); + case 2: return tr("Kit Channel"); + case 3: return tr("Filename"); + default: return QVariant(); + } + } + + return QVariant(); + } + + Qt::ItemFlags flags(const QModelIndex &index) const override + { + if(!index.isValid()) + { + return 0; + } + + switch(index.column()) + { + case 0: + return QAbstractItemModel::flags(index); + case 1: + case 2: + return Qt::ItemIsEditable | QAbstractItemModel::flags(index); + case 3: + return QAbstractItemModel::flags(index); // only column 1 is editable + } + } + + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) override + { + auto audiofile_ids = instrument.getAudioFileList(); + + if(index.row() > audiofile_ids.size() || index.column() > 2) + { + return false; + } + + auto audiofile_id = audiofile_ids.begin() + index.row(); + auto& audiofile = instrument.getAudioFile(*audiofile_id); + + switch(index.column()) + { + case 0: break; + case 1: audiofile.setName(value.toString()); break; + case 2: audiofile.setChannelMapId(value.toInt()); break; + default: break; + } + + return true; + } + + void refresh() + { + beginResetModel(); + endResetModel(); + } + +private: + Instrument& instrument; +}; + +FileList::FileList(Instrument& instrument) + : instrument(instrument) +{ + model = new FileDataModel(instrument); + setModel(model); + setItemDelegate(new ChannelMapDeligate(instrument)); + setEditTriggers(QAbstractItemView::AllEditTriggers); // show list on click + + setContextMenuPolicy(Qt::CustomContextMenu); // enable context menu + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), + this, SLOT(onCustomContextMenu(const QPoint &))); + setRootIsDecorated(false); + header()->resizeSection(0, 24); + + connect(this, SIGNAL(doubleClicked(const QModelIndex&)), + this, SLOT(selectionChanged(const QModelIndex&))); + + createMenus(); } void FileList::addFiles() { + auto root = instrument.getProject().getRawFileRoot(); + if(path.isEmpty()) + { + path = root; + } + QStringList files = QFileDialog::getOpenFileNames(this, tr("Open file"), path, tr("Audio Files (*.wav)")); @@ -74,38 +362,36 @@ void FileList::addFiles() QFileInfo fi(file); QString name = fi.baseName(); path = fi.absolutePath(); + if(root == file.left(root.length())) + { + file = file.mid(root.length() + 1); + } - QListWidgetItem* item = new QListWidgetItem(); - setItemFile(item, file); - setItemName(item, name); - setItemMaster(item, false); - addItem(item); - - emit fileAdded(file, name); + auto id = instrument.createAudioFile(); + auto& audiofile = instrument.getAudioFile(id); + audiofile.setName(name); + audiofile.setFile(file); + audiofile.setChannelMapId(-1); i++; } + + model->refresh(); } -void FileList::setMasterFile(QListWidgetItem* i) +void FileList::selectionChanged(const QModelIndex &index) { - QString filename = itemFile(i); - - for(int idx = 0; idx < count(); idx++) - { - setItemMaster(item(idx), false); - } + auto audiofile_ids = instrument.getAudioFileList(); + auto audiofile_id = audiofile_ids.begin() + index.row(); + const auto& audiofile = instrument.getAudioFile(*audiofile_id); - setItemMaster(i, true); - emit masterFileChanged(filename); -} + instrument.setMasterFile(audiofile.getFile()); -void FileList::selectionChanged(QListWidgetItem* i) -{ - setMasterFile(i); + emit masterFileChanged(audiofile.getAbsoluteFile()); + //setMasterFile(i); + model->refresh(); } - void FileList::createMenus() { menu = new QMenu(); @@ -113,9 +399,6 @@ void FileList::createMenus() setMasterAction = new QAction(tr("Set as Master (dbl-click)"), this); connect(setMasterAction, SIGNAL(triggered()), this, SLOT(setMaster())); - editAction = new QAction(tr("Edit name"), this); - connect(editAction, SIGNAL(triggered()), this, SLOT(editName())); - removeAction = new QAction(tr("Remove"), this); connect(removeAction, SIGNAL(triggered()), this, SLOT(removeFile())); @@ -123,96 +406,43 @@ void FileList::createMenus() connect(removeAllAction, SIGNAL(triggered()), this, SLOT(removeAllFiles())); menu->addAction(setMasterAction); - menu->addAction(editAction); menu->addAction(removeAction); menu->addSeparator(); menu->addAction(removeAllAction); - } -void FileList::popupMenu(const QPoint& pos) +void FileList::onCustomContextMenu(const QPoint &point) { - activeItem = itemAt(pos); - if(!activeItem) + QModelIndex index = indexAt(point); + if(index.isValid()) { - return; + menu->popup(viewport()->mapToGlobal(point)); } - menu->popup(mapToGlobal(pos)); } void FileList::setMaster() { - setMasterFile(activeItem); +// setMasterFile(activeItem); } void FileList::removeFile() { - QString file = itemFile(activeItem); - QString name = itemName(activeItem); - - printf("Removing: %s\n", file.toStdString().c_str()); - delete activeItem;//takeItem(row(activeItem)); - activeItem = NULL; - setCurrentRow(-1); + auto audiofile_ids = instrument.getAudioFileList(); + auto audiofile_id = audiofile_ids.begin() + currentIndex().row(); + instrument.deleteAudioFile(*audiofile_id); - emit fileRemoved(file, name); + //emit fileRemoved(file, name); } void FileList::removeAllFiles() { - activeItem = NULL; - - clear(); - - emit allFilesRemoved(); -} - -void FileList::editName() -{ - ItemEditor* e = new ItemEditor(activeItem, itemName(activeItem)); - connect(e, SIGNAL(updateItem(QListWidgetItem*, QString)), - this, SLOT(setItemName(QListWidgetItem*, QString))); -} - - -// Item utility functions. -QString FileList::itemFile(QListWidgetItem* i) -{ - return i->data(Qt::ToolTipRole).toString(); -} - -void FileList::setItemFile(QListWidgetItem* i, QString file) -{ - i->setData(Qt::ToolTipRole, file); - i->setData(Qt::DisplayRole, itemName(i) + "\t" + file); -} - -QString FileList::itemName(QListWidgetItem* i) -{ - return i->data(Qt::UserRole).toString(); -} - -void FileList::setItemName(QListWidgetItem* i, QString name) -{ - QString oldname = itemName(i); - - i->setData(Qt::UserRole, name); - i->setData(Qt::DisplayRole, name + "\t" + itemFile(i)); - - if(oldname != "" && oldname != name) + instrument.setMasterFile(""); + auto audiofile_ids = instrument.getAudioFileList(); + for(auto audiofile_id : audiofile_ids) { - emit nameChanged(itemFile(i), name); + instrument.deleteAudioFile(audiofile_id); } -} + reset(); -void FileList::setItemMaster(QListWidgetItem* i, bool master) -{ - if(master) - { - i->setIcon(QPixmap(":icons/master.png")); - } - else - { - i->setIcon(QPixmap(":icons/file.png")); - } + emit allFilesRemoved(); } diff --git a/src/filelist.h b/src/filelist.h index e336bc1..165af83 100644 --- a/src/filelist.h +++ b/src/filelist.h @@ -26,15 +26,15 @@ */ #pragma once -#include -#include +#include #include #include class Instrument; +class FileDataModel; class FileList - : public QListWidget + : public QTreeView { Q_OBJECT public: @@ -44,30 +44,33 @@ public: signals: void masterFileChanged(QString filename); - void fileAdded(QString file, QString name); +// void fileAdded(QString file, QString name); void fileRemoved(QString file, QString name); void allFilesRemoved(); - void nameChanged(QString file, QString name); +// void nameChanged(QString file, QString name); public slots: void addFiles(); - void popupMenu(const QPoint& pos); private slots: - void selectionChanged(QListWidgetItem* item); + void selectionChanged(const QModelIndex &index); + void onCustomContextMenu(const QPoint &point); void setMaster(); void removeFile(); void removeAllFiles(); - void editName(); - void setItemName(QListWidgetItem* i, QString name); - +// void setItemName(QListWidgetItem* i, QString name); +// private: - QString itemFile(QListWidgetItem* i); - QString itemName(QListWidgetItem* i); - void setItemFile(QListWidgetItem* i, QString file); - void setItemMaster(QListWidgetItem* i, bool master); - - void setMasterFile(QListWidgetItem* i); +// QString itemFile(QListWidgetItem* i); +// void setItemFile(QListWidgetItem* i, QString file); +// +// QString itemName(QListWidgetItem* i); +// void setItemMaster(QListWidgetItem* i, bool master); +// +// int itemChannelMap(QListWidgetItem* i); +// void setItemChannelMap(QListWidgetItem* i, int id); +// +// void setMasterFile(QListWidgetItem* i); void createMenus(); QMenu* menu; @@ -76,6 +79,7 @@ private: QAction* removeAction; QAction* removeAllAction; - QListWidgetItem* activeItem; +// QListWidgetItem* activeItem; Instrument& instrument; + FileDataModel* model; }; diff --git a/src/imageeditor.cc b/src/imageeditor.cc new file mode 100644 index 0000000..556084b --- /dev/null +++ b/src/imageeditor.cc @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * imageeditor.cc + * + * Tue Jul 24 11:55:43 CEST 2018 + * Copyright 2018 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include "imageeditor.h" + +#include + +ImageEditor::ImageEditor(QWidget* parent) + : QWidget(parent) +{ +} + +ImageEditor::~ImageEditor() +{ +} + +void ImageEditor::setImage(QString filename) +{ + QLabel *image = new QLabel(this); + QPixmap pic(filename); + QPixmap scaled = pic.scaled(width(), height(), + Qt::KeepAspectRatio, Qt::FastTransformation); + image->setPixmap(scaled); + image->move(0, 0); + image->resize(width(), height()); +} diff --git a/src/imageeditor.h b/src/imageeditor.h new file mode 100644 index 0000000..c2df4dc --- /dev/null +++ b/src/imageeditor.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * imageeditor.h + * + * Tue Jul 24 11:55:43 CEST 2018 + * Copyright 2018 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#pragma once + +#include + +class ImageEditor + : public QWidget +{ + Q_OBJECT +public: + ImageEditor(QWidget* parent); + ~ImageEditor(); + + void setImage(QString filename); +}; diff --git a/src/instrumentwidget.cc b/src/instrumentwidget.cc index 0058f93..d7b0024 100644 --- a/src/instrumentwidget.cc +++ b/src/instrumentwidget.cc @@ -86,7 +86,7 @@ InstrumentWidget::InstrumentWidget(Settings& settings, Instrument& instrument) central->setLayout(lv); setCentralWidget(central); - extractor = new AudioExtractor(instrument, selections, this); + extractor = new AudioExtractor(instrument, this); canvaswidget = new CanvasWidget(this); QToolBar* toolbar = addToolBar(tr("Tools")); @@ -199,12 +199,6 @@ QWidget* InstrumentWidget::createFilesTab() connect(filelist, SIGNAL(masterFileChanged(QString)), this, SLOT(loadFile(QString))); connect(loadbtn, SIGNAL(clicked()), filelist, SLOT(addFiles())); - connect(filelist, SIGNAL(fileAdded(QString, QString)), - extractor, SLOT(addFile(QString, QString))); - connect(filelist, SIGNAL(fileRemoved(QString, QString)), - extractor, SLOT(removeFile(QString, QString))); - connect(filelist, SIGNAL(nameChanged(QString, QString)), - extractor, SLOT(changeName(QString, QString))); l->addWidget(filelist); if(!instrument.getMasterFile().isEmpty()) @@ -365,8 +359,6 @@ QWidget* InstrumentWidget::createExportTab() l->addWidget(new QLabel(tr("Prefix:"))); prefix = new QLineEdit(); prefix->setText(instrument.getPrefix()); - connect(prefix, SIGNAL(textChanged(const QString &)), - extractor, SLOT(setOutputPrefix(const QString &))); connect(prefix, SIGNAL(textChanged(const QString &)), this, SLOT(prefixChanged())); l->addWidget(prefix); @@ -374,9 +366,7 @@ QWidget* InstrumentWidget::createExportTab() l->addWidget(new QLabel(tr("Export path:"))); QHBoxLayout* lo_exportp = new QHBoxLayout(); lineed_exportp = new QLineEdit(); - lineed_exportp->setText(instrument.getExportPath()); - connect(lineed_exportp, SIGNAL(textChanged(const QString &)), - extractor, SLOT(setExportPath(const QString &))); + lineed_exportp->setText(instrument.getProject().getExportPath()); connect(lineed_exportp, SIGNAL(textChanged(const QString &)), this, SLOT(exportPathChanged())); lo_exportp->addWidget(lineed_exportp); @@ -409,7 +399,7 @@ void InstrumentWidget::prefixChanged() void InstrumentWidget::exportPathChanged() { - instrument.setExportPath(lineed_exportp->text()); + instrument.getProject().setExportPath(lineed_exportp->text()); } void InstrumentWidget::playSamples() @@ -486,12 +476,12 @@ void InstrumentWidget::loadFile(QString filename) qApp->processEvents(); sorter->setWavData(NULL, 0); - player.setPcmData(NULL, 0); + player.setPcmData(NULL, 0, 0); canvaswidget->canvas->load(filename); sorter->setWavData(canvaswidget->canvas->data, canvaswidget->canvas->size); - player.setPcmData(canvaswidget->canvas->data, canvaswidget->canvas->size); + player.setPcmData(canvaswidget->canvas->data, canvaswidget->canvas->size, canvaswidget->canvas->samplerate); statusBar()->showMessage(tr("Ready")); setCursor(Qt::ArrowCursor); diff --git a/src/localehandler.cc b/src/localehandler.cc index 6721492..9bc08c8 100644 --- a/src/localehandler.cc +++ b/src/localehandler.cc @@ -26,10 +26,8 @@ */ #include "localehandler.h" -#include - #include -//#include +#include #include #include #include diff --git a/src/mainwindow.cc b/src/mainwindow.cc index 8091d2e..4084df1 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -41,11 +41,15 @@ #include "settings.h" #include "projectdialog.h" +#include "renderdialog.h" +#include "channeldialog.h" #include "instrumentdialog.h" #include "projectserialiser.h" #include "instrumentwidget.h" #include "channelswidget.h" +#include "imageeditor.h" + #define MAXVAL 10000000L MainWindow::MainWindow(Settings& settings) @@ -82,6 +86,16 @@ MainWindow::MainWindow(Settings& settings) fileMenu->addAction(act_quit); connect(act_quit, SIGNAL(triggered()), this, SLOT(close())); + QMenu* projectMenu = menuBar()->addMenu(tr("&Project")); + QAction* act_render = new QAction(tr("&Render..."), this); + projectMenu->addAction(act_render); + connect(act_render, SIGNAL(triggered()), this, SLOT(render())); + + QMenu* testMenu = menuBar()->addMenu(tr("Test")); + QAction* act_test = new QAction(tr("Test"), this); + testMenu->addAction(act_test); + connect(act_test, SIGNAL(triggered()), this, SLOT(test())); + instruments_dock = new QDockWidget(tr("Instruments:"), this); instruments_dock->setObjectName("instruments_dock"); instruments_dock->setAllowedAreas(Qt::LeftDockWidgetArea); @@ -134,13 +148,13 @@ MainWindow::MainWindow(Settings& settings) channel_list = new QListWidget(); connect(channel_list, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(channelDoubleClicked(QListWidgetItem*))); - channel_list->addItems({"AmbL", "AmbR", "Kdrum_back", "Kdrum_front", - "Hihat", "OHL", "OHR", "Ride","Snare_bottom", - "Snare_top", "Tom1", "Tom2", "Tom3"}); - for(int i = 0; i < channel_list->count(); ++i) - { - channel_list->item(i)->setIcon(QPixmap(":icons/channel.png")); - } +// channel_list->addItems({"AmbL", "AmbR", "Kdrum_back", "Kdrum_front", +// "Hihat", "OHL", "OHR", "Ride","Snare_bottom", +// "Snare_top", "Tom1", "Tom2", "Tom3"}); +// for(int i = 0; i < channel_list->count(); ++i) +// { +// channel_list->item(i)->setIcon(QPixmap(":icons/channel.png")); +// } l->addWidget(tools); l->addWidget(channel_list); @@ -259,10 +273,17 @@ void MainWindow::instrumentDoubleClicked(QListWidgetItem *item) void MainWindow::addChannel() { + auto id = project.createChannel(); + auto& channel = project.getChannel(id); + + ChannelDialog dlg(this, channel); + dlg.show(); + dlg.exec(); + auto item = new QListWidgetItem(); item->setIcon(QPixmap(":icons/channel.png")); - item->setText("New Channel"); - //item->setData(Qt::UserRole, id); + item->setData(Qt::UserRole, id); + item->setText(channel.getChannelName()); channel_list->addItem(item); } @@ -271,6 +292,8 @@ void MainWindow::removeChannel() auto items = channel_list->selectedItems(); for(auto item : items) { + auto id = item->data(Qt::UserRole).toInt(); + project.deleteChannel(id); delete item; } } @@ -283,7 +306,6 @@ void MainWindow::channelDoubleClicked(QListWidgetItem *item) tr("Channels")); // Make new tab active tab_widget->setCurrentIndex(tab_widget->count() - 1); - } void MainWindow::updateWindowTitle() @@ -350,6 +372,11 @@ void MainWindow::loadProject() return; } + loadProject(filename); +} + +void MainWindow::loadProject(QString filename) +{ QFile file(filename); if(!file.open(QIODevice::ReadOnly)) { @@ -379,6 +406,18 @@ void MainWindow::loadProject() instrument_list->addItem(item); } + channel_list->clear(); + auto channel_ids = project.getChannelList(); + for(auto id : channel_ids) + { + auto& channel = project.getChannel(id); + auto item = new QListWidgetItem(); + item->setIcon(QPixmap(":icons/channel.png")); + item->setText(channel.getChannelName()); + item->setData(Qt::UserRole, id); + channel_list->addItem(item); + } + statusBar()->showMessage(tr("Loaded")); } @@ -436,3 +475,16 @@ void MainWindow::closeTab(int tab) { tab_widget->removeTab(tab); } + +void MainWindow::test() +{ + ImageEditor* e = new ImageEditor(nullptr); + e->setImage("/mnt/atuin/misc/stuff/deva/CrocellKit/crocellkit01.png"); + e->show(); +} + +void MainWindow::render() +{ + RenderDialog renderDialog(this, project); + renderDialog.exec(); +} diff --git a/src/mainwindow.h b/src/mainwindow.h index d9ab062..e4c5863 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -58,12 +58,17 @@ public slots: void newProject(); void loadProject(); + void loadProject(QString filename); void saveProject(); void saveProjectAs(); void projectChanged(); void closeTab(int tab); +private slots: + void test(); + void render(); + protected: void closeEvent(QCloseEvent*); diff --git a/src/player.cc b/src/player.cc index af3cd7c..d598d9f 100644 --- a/src/player.cc +++ b/src/player.cc @@ -67,7 +67,7 @@ void Player::run() ao_sample_format sf; memset(&sf, 0, sizeof(sf)); sf.bits = 16; - sf.rate = 44100; + sf.rate = pcm_samplerate; sf.channels = 1; sf.byte_format = AO_FMT_NATIVE; @@ -188,10 +188,11 @@ void Player::reportTimeout() peak = 0.0; } -void Player::setPcmData(float* data, size_t size) +void Player::setPcmData(float* data, size_t size, double samplerate) { pcm_data = data; pcm_size = size; + pcm_samplerate = samplerate; } void Player::setPosition(size_t position) diff --git a/src/player.h b/src/player.h index 8548c0a..0f5b780 100644 --- a/src/player.h +++ b/src/player.h @@ -53,7 +53,7 @@ public: public slots: //! Assign PCM data to the player. - void setPcmData(float *data, size_t num_samples); + void setPcmData(float *data, size_t num_samples, double samplerate); //! Set gain scalar. //! This value is multiplied with each sample before it is played. @@ -95,6 +95,7 @@ private: float *pcm_data; size_t pcm_size; + double pcm_samplerate{44100.0}; QTimer report_timer; diff --git a/src/project.cc b/src/project.cc index 5ad809b..86100bc 100644 --- a/src/project.cc +++ b/src/project.cc @@ -30,6 +30,82 @@ #include +AudioFile::AudioFile(Instrument& instrument, int id) + : id(id) + , instrument(instrument) +{ +} + +int AudioFile::getId() const +{ + return id; +} + +QString AudioFile::getFile() const +{ + return file; +} + +void AudioFile::setFile(const QString& file) +{ + if(this->file == file) + { + return; + } + + { + Project::RAIIBulkUpdate bulkUpdate(instrument.getProject()); + this->file = file; + } +} + +QString AudioFile::getAbsoluteFile() const +{ + if(file.left(1) != "/") + { + // Prepend root path + return instrument.getProject().getRawFileRoot() + "/" + file; + } + + return file; +} + +QString AudioFile::getName() const +{ + return name; +} + +void AudioFile::setName(const QString& name) +{ + if(this->name == name) + { + return; + } + + { + Project::RAIIBulkUpdate bulkUpdate(instrument.getProject()); + this->name = name; + } +} + +int AudioFile::getChannelMapId() const +{ + return channel_map_id; +} + +void AudioFile::setChannelMapId(int channel_map_id) +{ + if(this->channel_map_id == channel_map_id) + { + return; + } + + { + Project::RAIIBulkUpdate bulkUpdate(instrument.getProject()); + this->channel_map_id = channel_map_id; + } +} + Instrument::Instrument(Project& project, int id) : id(id) , project(project) @@ -81,22 +157,53 @@ void Instrument::setMasterFile(const QString& master_file) } } -AudioFileList Instrument::getFileList() const +AudioFile& Instrument::getAudioFile(int id) +{ + for(auto& audio_file : audio_files) + { + if(audio_file.getId() == id) + { + return audio_file; + } + } + + Q_ASSERT(false); // No such audio_file id. +} + +int Instrument::createAudioFile() { - return file_list; + Project::RAIIBulkUpdate bulkUpdate(project); + + audio_files.emplace_back(*this, project.next_id); + ++project.next_id; + + return audio_files.back().getId(); } -void Instrument::setFileList(const AudioFileList& file_list) +void Instrument::deleteAudioFile(int id) { - if(this->file_list == file_list) + Project::RAIIBulkUpdate bulkUpdate(project); + + for(auto it = audio_files.begin(); it != audio_files.end(); ++it) { - return; + if((*it).getId() == id) + { + audio_files.erase(it); + return; + } } + Q_ASSERT(false); // No such audio_file id. +} + +QList Instrument::getAudioFileList() const +{ + QList audio_file_list; + for(auto& audio_file : audio_files) { - Project::RAIIBulkUpdate bulkUpdate(project); - this->file_list = file_list; + audio_file_list.push_back(audio_file.getId()); } + return audio_file_list; } std::size_t Instrument::getAttackLength() const @@ -238,29 +345,46 @@ void Instrument::setPrefix(const QString& prefix) } } -QString Instrument::getExportPath() const +Project& Instrument::getProject() { - return export_path; + return project; } -void Instrument::setExportPath(const QString& export_path) +Channel::Channel(Project& project, int id) + : id(id) + , project(project) { - if(this->export_path == export_path) +} + +int Channel::getId() const +{ + return id; +} + +QString Channel::getChannelName() const +{ + return channel_name; +} + +void Channel::setChannelName(const QString& channel_name) +{ + if(this->channel_name == channel_name) { return; } { Project::RAIIBulkUpdate bulkUpdate(project); - this->export_path = export_path; + this->channel_name = channel_name; } } -Project& Instrument::getProject() +Project& Channel::getProject() { return project; } + void Project::bulkUpdateBegin() { ++update_count; @@ -323,6 +447,24 @@ void Project::setRawFileRoot(const QString& raw_file_root) } } +QString Project::getExportPath() const +{ + return export_path; +} + +void Project::setExportPath(const QString& export_path) +{ + if(this->export_path == export_path) + { + return; + } + + { + Project::RAIIBulkUpdate bulkUpdate(*this); + this->export_path = export_path; + } +} + Instrument& Project::getInstrument(int id) { for(auto& instrument : instruments) @@ -372,11 +514,61 @@ QList Project::getInstrumentList() const return instrument_list; } +Channel& Project::getChannel(int id) +{ + for(auto& channel : channels) + { + if(channel.getId() == id) + { + return channel; + } + } + + Q_ASSERT(false); // No such channel id. +} + +int Project::createChannel() +{ + RAIIBulkUpdate bulkUpdate(*this); + + channels.emplace_back(*this, next_id); + ++next_id; + + return channels.back().getId(); +} + +void Project::deleteChannel(int id) +{ + RAIIBulkUpdate bulkUpdate(*this); + + for(auto it = channels.begin(); it != channels.end(); ++it) + { + if((*it).getId() == id) + { + channels.erase(it); + return; + } + } + + Q_ASSERT(false); // No such channel id. +} + +QList Project::getChannelList() const +{ + QList channel_list; + for(auto& channel : channels) + { + channel_list.push_back(channel.getId()); + } + return channel_list; +} + void Project::reset() { RAIIBulkUpdate bulkUpdate(*this); setRawFileRoot(""); setProjectName(""); instruments.clear(); + channels.clear(); next_id = 0; } diff --git a/src/project.h b/src/project.h index 98be92a..b0f9060 100644 --- a/src/project.h +++ b/src/project.h @@ -35,6 +35,35 @@ #include "selection.h" class Project; +class Instrument; + +class AudioFile +{ +public: + AudioFile(Instrument& instrument, int id); + + int getId() const; + + QString getFile() const; + void setFile(const QString& file); + QString getAbsoluteFile() const; + + QString getName() const; + void setName(const QString& name); + + int getChannelMapId() const; + void setChannelMapId(int id); + +private: + friend class ProjectSerialiser; + + QString file; + QString name; + int channel_map_id; + + int id; + Instrument& instrument; +}; class Instrument { @@ -49,8 +78,10 @@ public: QString getMasterFile() const; void setMasterFile(const QString& master_file); - AudioFileList getFileList() const; - void setFileList(const AudioFileList& file_list); + AudioFile& getAudioFile(int id); + int createAudioFile(); + void deleteAudioFile(int id); + QList getAudioFileList() const; std::size_t getAttackLength() const; void setAttackLength(std::size_t attack_length); @@ -76,9 +107,6 @@ public: QString getPrefix() const; void setPrefix(const QString& prefix); - QString getExportPath() const; - void setExportPath(const QString& prefix); - Project& getProject(); private: @@ -89,7 +117,7 @@ private: // Files tab QString master_file; - AudioFileList file_list; + std::list audio_files; // Generate tab std::size_t attack_length{300}; @@ -106,7 +134,28 @@ private: // Export tab QString prefix; - QString export_path; + + Project& project; +}; + +class Channel +{ +public: + Channel(Project& project, int id); + + int getId() const; + + QString getChannelName() const; + void setChannelName(const QString& channel_name); + + Project& getProject(); + +private: + friend class ProjectSerialiser; + + int id; + + QString channel_name; Project& project; }; @@ -145,11 +194,19 @@ public: QString getRawFileRoot() const; void setRawFileRoot(const QString& raw_file_root); + QString getExportPath() const; + void setExportPath(const QString& prefix); + Instrument& getInstrument(int id); int createInstrument(); void deleteInstrument(int id); QList getInstrumentList() const; + Channel& getChannel(int id); + int createChannel(); + void deleteChannel(int id); + QList getChannelList() const; + void reset(); signals: @@ -157,12 +214,15 @@ signals: private: friend class ProjectSerialiser; + friend class Instrument; QString project_file; QString project_name; QString raw_file_root; + QString export_path; std::list instruments; + std::list channels; int next_id{0}; int update_count{0}; diff --git a/src/projectrenderer.cc b/src/projectrenderer.cc new file mode 100644 index 0000000..e1856cb --- /dev/null +++ b/src/projectrenderer.cc @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * projectrender.cc + * + * Sat Sep 22 10:35:01 CEST 2018 + * Copyright 2018 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include "projectrenderer.h" + +#include +#include +#include + +#include "project.h" +#include "audioextractor.h" + +ProjectRenderer::ProjectRenderer(Project& project) + : project(project) +{ +} + +void ProjectRenderer::render() +{ + QDomDocument doc; + QDomProcessingInstruction header = + doc.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'"); + doc.appendChild(header); + + QDomElement drumkit = doc.createElement("drumkit"); + drumkit.setAttribute("version", "1.0"); + drumkit.setAttribute("name", project.getProjectName()); +// drumkit.setAttribute("description", project.getProjectDescription()); + doc.appendChild(drumkit); + + QDomElement channels = doc.createElement("channels"); + drumkit.appendChild(channels); + + auto channel_ids = project.getChannelList(); + for(auto channel_id : channel_ids) + { + const auto& channel = project.getChannel(channel_id); + QDomElement channel_node = doc.createElement("channel"); + channel_node.setAttribute("name", channel.getChannelName()); + channels.appendChild(channel_node); + } + + QDomElement instruments = doc.createElement("instruments"); + drumkit.appendChild(instruments); + + auto instrument_ids = project.getInstrumentList(); + + emit progressStart(instrument_ids.count()); + qApp->processEvents(); + + for(auto instrument_id : instrument_ids) + { + auto& instrument = project.getInstrument(instrument_id); + emit progressTask(tr("Writing instrument: ") + + instrument.getInstrumentName()); + + { + auto selections = instrument.getSelections().ids(); + emit progressRenderStart(selections.count()); + qApp->processEvents(); + } + + AudioExtractor extractor(instrument, this); + connect(&extractor, SIGNAL(progressUpdate(int)), + this, SIGNAL(progressRenderTask(int))); + + extractor.exportSelections(); + + QDomElement instrument_node = doc.createElement("instrument"); + instrument_node.setAttribute("name", instrument.getInstrumentName()); + QString file = instrument.getPrefix() + "/" + instrument.getPrefix() + ".xml"; + instrument_node.setAttribute("file", file); + instruments.appendChild(instrument_node); + auto audiofile_ids = instrument.getAudioFileList(); + for(auto audiofile_id : audiofile_ids) + { + const auto& audiofile = instrument.getAudioFile(audiofile_id); + if(audiofile.getChannelMapId() == -1) + { + // Not mapped + continue; + } + QDomElement channelmap = doc.createElement("channelmap"); + channelmap.setAttribute("in", audiofile.getName()); + const auto& channel = project.getChannel(audiofile.getChannelMapId()); + channelmap.setAttribute("out", channel.getChannelName()); + instrument_node.appendChild(channelmap); + } + + emit progressRenderFinished(0); + qApp->processEvents(); + } + + QFile xmlfile(project.getExportPath() + "/drumkit.xml"); + xmlfile.open(QIODevice::WriteOnly); + xmlfile.write(doc.toByteArray()); + xmlfile.close(); + + emit progressFinished(0); + qApp->processEvents(); +} diff --git a/src/projectrenderer.h b/src/projectrenderer.h new file mode 100644 index 0000000..a92b049 --- /dev/null +++ b/src/projectrenderer.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * projectrender.h + * + * Sat Sep 22 10:35:00 CEST 2018 + * Copyright 2018 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#pragma once + +#include + +class Project; + +class ProjectRenderer + : public QObject +{ + Q_OBJECT +public: + ProjectRenderer(Project& project); + + void render(); + +signals: + void progressStart(int total); + void progressTask(QString text); + + void progressRenderStart(int tasktotal); + void progressRenderTask(int subtask); + void progressRenderFinished(int success); + + void progressFinished(int success); + +private: + Project& project; +}; diff --git a/src/projectserialiser.cc b/src/projectserialiser.cc index f80fa36..ced4636 100644 --- a/src/projectserialiser.cc +++ b/src/projectserialiser.cc @@ -122,6 +122,23 @@ QString ProjectSerialiser::serialise(const Project& project) raw_file_root.appendChild(doc.createTextNode(project.raw_file_root)); dgedit.appendChild(raw_file_root); + auto export_path = doc.createElement("export_path"); + export_path.appendChild(doc.createTextNode(project.export_path)); + dgedit.appendChild(export_path); + + dgedit.setAttribute("next_id", project.next_id); + + auto channels = doc.createElement("channels"); + dgedit.appendChild(channels); + + for(const auto& c : project.channels) + { + auto channel = doc.createElement("channel"); + channel.setAttribute("id", c.getId()); + channel.setAttribute("name", c.getChannelName()); + channels.appendChild(channel); + } + auto instruments = doc.createElement("instruments"); dgedit.appendChild(instruments); @@ -129,6 +146,8 @@ QString ProjectSerialiser::serialise(const Project& project) { auto instrument = doc.createElement("instrument"); + instrument.setAttribute("id", (int)i.getId()); + instrument.setAttribute("attack_length", (int)i.getAttackLength()); instrument.setAttribute("power_spread", (int)i.getPowerSpread()); instrument.setAttribute("minimum_size", (int)i.getMinimumSize()); @@ -137,12 +156,11 @@ QString ProjectSerialiser::serialise(const Project& project) instrument.setAttribute("threshold", i.getThreshold()); - instruments.appendChild(instrument); + auto prefix = doc.createElement("prefix"); + prefix.appendChild(doc.createTextNode(i.getPrefix())); + instrument.appendChild(prefix); - auto export_path = doc.createElement("export_path"); - export_path.setAttribute("prefix", i.getPrefix()); - export_path.appendChild(doc.createTextNode(i.export_path)); - instrument.appendChild(export_path); + instruments.appendChild(instrument); auto instrument_name = doc.createElement("instrument_name"); instrument_name.appendChild(doc.createTextNode(i.instrument_name)); @@ -151,12 +169,14 @@ QString ProjectSerialiser::serialise(const Project& project) auto file_list = doc.createElement("file_list"); instrument.appendChild(file_list); - for(const auto& f : i.file_list) + for(const auto& audiofile : i.audio_files) { auto file = doc.createElement("file"); - file.appendChild(doc.createTextNode(f.first)); - file.setAttribute("name", f.second); - file.setAttribute("master", i.master_file == f.first); + file.appendChild(doc.createTextNode(audiofile.getFile())); + file.setAttribute("id", audiofile.getId()); + file.setAttribute("name", audiofile.getName()); + file.setAttribute("channel_map_id", audiofile.getChannelMapId()); + file.setAttribute("master", i.master_file == audiofile.getAbsoluteFile()); file_list.appendChild(file); } @@ -201,31 +221,47 @@ bool ProjectSerialiser::deserialise(const QString& data, Project& project) return false; } + project.next_id = dom["next_id"].toInt(); project.project_name = dom("project_name").text(); project.raw_file_root = dom("raw_file_root").text(); + project.export_path = dom("export_path").text(); + + auto channels = dom("channels").children("channel"); + for(auto& channel : channels) + { + auto id = channel["id"].toInt(); + project.channels.emplace_back(Channel(project, id)); + + auto& ch = project.channels.back(); + ch.channel_name = channel["name"]; + } auto instruments = dom("instruments").children("instrument"); for(auto& instrument : instruments) { - project.instruments.emplace_back(Instrument(project, project.next_id)); - ++project.next_id; + auto id = instrument["id"].toInt(); + project.instruments.emplace_back(Instrument(project, id)); auto& instr = project.instruments.back(); instr.instrument_name = instrument("instrument_name").text(); QString master_file; - AudioFileList file_list; auto files = instrument("file_list").children("file"); for(auto& file : files) { + auto id = file["id"].toInt(); + instr.audio_files.emplace_back(AudioFile(instr, id)); + auto& audiofile = instr.audio_files.back(); + audiofile.file = file.text(); + audiofile.name = file["name"]; + audiofile.channel_map_id = file["channel_map_id"].toInt(); + if(file["master"] == "1") { - master_file = file.text(); + master_file = audiofile.getAbsoluteFile(); } - file_list.push_back(qMakePair(file.text(), file["name"])); } - instr.file_list = file_list; instr.master_file = master_file; instr.attack_length = instrument["attack_length"].toInt(); @@ -236,8 +272,7 @@ bool ProjectSerialiser::deserialise(const QString& data, Project& project) instr.threshold = instrument["threshold"].toFloat(); - instr.export_path = instrument("export_path").text(); - instr.prefix = instrument("export_path")["prefix"]; + instr.prefix = instrument("prefix").text(); auto selections = instrument("regions"); instr.selections.nextid = selections["nextid"].toInt(); diff --git a/src/renderdialog.cc b/src/renderdialog.cc new file mode 100644 index 0000000..9dc1c05 --- /dev/null +++ b/src/renderdialog.cc @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * renderdialog.cc + * + * Thu Sep 20 17:32:11 CEST 2018 + * Copyright 2018 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include "renderdialog.h" + +#include +#include + +RenderDialog::RenderDialog(QWidget* parent, Project& project) + : QDialog(parent) + , project(project) + , renderer(project) +{ + setLayout(new QVBoxLayout()); + + auto btn = new QPushButton(this); + btn->setText("Click me"); + connect(btn, SIGNAL(clicked()), this, SLOT(render())); + //connect(btn, SIGNAL(clicked()), this, SLOT(close())); + layout()->addWidget(btn); + + bar1 = new QProgressBar(this); + connect(&renderer, SIGNAL(progressStart(int)), + bar1, SLOT(setMaximum(int))); + layout()->addWidget(bar1); + + bar2 = new QProgressBar(this); + connect(&renderer, SIGNAL(progressRenderStart(int)), + bar2, SLOT(setMaximum(int))); + connect(&renderer, SIGNAL(progressRenderTask(int)), + bar2, SLOT(setValue(int))); + layout()->addWidget(bar2); + + tasks = new QListWidget(this); +// connect(&renderer, SIGNAL(progressTask(QString)), +// tasks, SLOT(addItem(QString))); + layout()->addWidget(tasks); + + connect(&renderer, SIGNAL(progressTask(QString)), + this, SLOT(progressTask(QString))); + connect(&renderer, SIGNAL(progressRenderFinished(int)), + this, SLOT(progressRenderFinished(int))); +} + +void RenderDialog::render() +{ + bar1->setValue(1); + renderer.render(); +} + +void RenderDialog::progressStart(int total) +{ +} + +void RenderDialog::progressTask(QString text) +{ + tasks->addItem(text); +} + +void RenderDialog::progressRenderStart(int tasktotal) +{ +} + +void RenderDialog::progressRenderTask(int subtask) +{ +} + +void RenderDialog::progressRenderFinished(int success) +{ + bar1->setValue(bar1->value() + 1); +} + +void RenderDialog::progressFinished(int success) +{ +} diff --git a/src/renderdialog.h b/src/renderdialog.h new file mode 100644 index 0000000..84f0b30 --- /dev/null +++ b/src/renderdialog.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * renderdialog.h + * + * Thu Sep 20 17:32:11 CEST 2018 + * Copyright 2018 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#pragma once + +#include +#include +#include + +#include "projectrenderer.h" + +class Project; + +class RenderDialog + : public QDialog +{ + Q_OBJECT +public: + RenderDialog(QWidget* parent, Project& project); + +private slots: + void render(); + + void progressStart(int total); + void progressTask(QString text); + + void progressRenderStart(int tasktotal); + void progressRenderTask(int subtask); + void progressRenderFinished(int success); + + void progressFinished(int success); + +private: + Project& project; + ProjectRenderer renderer; + + QProgressBar* bar1; + QProgressBar* bar2; + + QListWidget* tasks; +}; -- cgit v1.2.3