/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /*************************************************************************** * audioextractor.cc * * Sat Nov 21 13:09:35 CET 2009 * Copyright 2009 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 "audioextractor.h" #include #include #include #include #include #include #include "project.h" #define INSTRUMENT_VERSION "2.0" typedef struct { SNDFILE *fh; float *data; } audiodata_t; AudioExtractor::AudioExtractor(Instrument& instrument, QObject *parent) : QObject(parent) , instrument(instrument) { } bool 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; emit progressUpdate(progress++); qApp->processEvents(); // Open all input audio files: auto audiofile_ids = instrument.getAudioFileList(); audiodata_t audiodata[audiofile_ids.size()]; int idx = 0; for(auto audiofile_id : audiofile_ids) { const auto& audiofile = instrument.getAudioFile(audiofile_id); QString file = audiofile.getAbsoluteFile(); SF_INFO sf_info; audiodata[idx].fh = sf_open(file.toStdString().c_str(), SFM_READ, &sf_info); if(!audiodata[idx].fh) { printf("AudioExtractor load error '%s'\n", file.toStdString().c_str()); return false; } if(samplerate == -1) { // Store the first samplerate we meet samplerate = sf_info.samplerate; } audiodata[idx].data = NULL; idx++; } idx = 1; QVector sels = selections.ids(); // Sort selections by velocity for(int v1 = 0; v1 < sels.size(); v1++) { for(int v2 = 0; v2 < sels.size(); v2++) { Selection sel1 = selections.get(sels[v1]); Selection sel2 = selections.get(sels[v2]); if(sel1.energy < sel2.energy) { sel_id_t vtmp = sels[v1]; sels[v1] = sels[v2]; sels[v2] = vtmp; } } } if(samplerate == -1) { // For some reason we never got a samplerate. Set it to 44k1Hz samplerate = 44100; } // Iterate and write audio files QVector::iterator si = sels.begin(); while(si != sels.end()) { Selection sel = selections.get(*si); size_t offset = sel.from; size_t size = sel.to - sel.from; size_t fadein = sel.fadein; size_t fadeout = sel.fadeout; // Read all input audio file chunks: for(int i = 0; i < audiofile_ids.size(); i++) { // Clear out old buffer (if one exists) if(audiodata[i].data) { delete audiodata[i].data; audiodata[i].data = NULL; } SNDFILE *fh = audiodata[i].fh; sf_seek(fh, offset, SEEK_SET); float *data = new float[size]; sf_read_float(fh, data, size); // Apply linear fadein for(size_t fi = 0; fi < fadein; fi++) { float val = ((float)fi / (float)fadein); if(fi < size) data[fi] *= val; } // Apply fadeout for(size_t fo = 0; fo < fadeout; fo++) { float val = 1.0 - ((float)fo / (float)fadeout); if( (((size - fadeout) + fo) < size) && (((size - fadeout) + fo) >= 0) ) { data[(size - fadeout) + fo] *= val; } } audiodata[i].data = data; } // Create output path: QString path = exportpath + QDir::separator() + prefix + QDir::separator() + "samples"; QDir d; d.mkpath(path); // Write all sample chunks to single output file: QString file = path + QDir::separator() + QString::number(idx) + "-" + prefix + ".wav"; SF_INFO sf_info; sf_info.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; sf_info.samplerate = samplerate; 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. %s\n", file.toStdString().c_str()); return false; } for(size_t ob = 0; ob < size; ob++) { 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, audiofile_ids.size()); } sf_close(ofh); idx++; si++; emit progressUpdate(progress++); qApp->processEvents(); } // Close all input audio files: for(int i = 0; i < audiofile_ids.size(); i++) { if(audiodata[i].data) { delete audiodata[i].data; audiodata[i].data = NULL; } sf_close(audiodata[i].fh); } // Ugly hack to ensure the xml attribute order is the same each time a save // or export is performed. qSetGlobalQHashSeed(0); QDomDocument doc; QDomProcessingInstruction header = doc.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'"); doc.appendChild(header); 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_node.appendChild(samples); { // Do the adding to the xml file one sample at the time. int index = 0; QVector::iterator si = sels.begin(); while(si != sels.end()) { index++; Selection i = selections.get(*si); i.name = prefix + "-" + QString::number(index); QDomElement sample = doc.createElement("sample"); sample.setAttribute("name", i.name); sample.setAttribute("power", QString::number(i.energy)); samples.appendChild(sample); selections.update(*si, i); int channelnum = 1; // Filechannel numbers are 1-based. for(auto audiofile_id : audiofile_ids) { 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++; } si++; } } QFile xmlfile(exportpath + QDir::separator() + prefix + QDir::separator() + prefix + ".xml"); if(!xmlfile.open(QIODevice::WriteOnly)) { return false; } xmlfile.write(doc.toByteArray()); xmlfile.close(); emit progressUpdate(progress++); qApp->processEvents(); return true; }