Chris@148: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@148: Chris@148: /* Chris@148: Sonic Visualiser Chris@148: An audio file viewer and annotation editor. Chris@148: Centre for Digital Music, Queen Mary, University of London. Chris@202: This file copyright 2006 Chris Cannam and QMUL. Chris@148: Chris@148: This program is free software; you can redistribute it and/or Chris@148: modify it under the terms of the GNU General Public License as Chris@148: published by the Free Software Foundation; either version 2 of the Chris@148: License, or (at your option) any later version. See the file Chris@148: COPYING included with this distribution for more information. Chris@148: */ Chris@148: Chris@148: #include "WavFileWriter.h" Chris@148: Chris@148: #include "model/DenseTimeValueModel.h" Chris@148: #include "base/Selection.h" Chris@674: #include "base/TempWriteFile.h" Chris@674: #include "base/Exceptions.h" Chris@1428: #include "base/Debug.h" Chris@148: Chris@1520: #include Chris@1520: #include Chris@1520: Chris@148: #include Chris@148: Chris@148: #include Chris@1055: #include Chris@1428: #include Chris@148: Chris@1096: using namespace std; Chris@1096: Chris@148: WavFileWriter::WavFileWriter(QString path, Chris@1350: sv_samplerate_t sampleRate, Chris@929: int channels, Chris@684: FileWriteMode mode) : Chris@148: m_path(path), Chris@148: m_sampleRate(sampleRate), Chris@174: m_channels(channels), Chris@1582: m_temp(nullptr), Chris@1582: m_file(nullptr) Chris@148: { Chris@174: SF_INFO fileInfo; Chris@1040: Chris@1040: int fileRate = int(round(m_sampleRate)); Chris@1040: if (m_sampleRate != sv_samplerate_t(fileRate)) { Chris@1428: SVCERR << "WavFileWriter: WARNING: Non-integer sample rate " Chris@1350: << m_sampleRate << " presented, rounding to " << fileRate Chris@1350: << endl; Chris@1040: } Chris@1040: fileInfo.samplerate = fileRate; Chris@174: fileInfo.channels = m_channels; Chris@174: fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; Chris@674: Chris@674: try { Chris@1350: QString writePath = m_path; Chris@684: if (mode == WriteToTemporary) { Chris@684: m_temp = new TempWriteFile(m_path); Chris@1350: writePath = m_temp->getTemporaryFilename(); Chris@1348: } Chris@1350: #ifdef Q_OS_WIN Chris@1350: m_file = sf_wchar_open((LPCWSTR)writePath.utf16(), SFM_WRITE, &fileInfo); Chris@1350: #else Chris@1350: m_file = sf_open(writePath.toLocal8Bit(), SFM_WRITE, &fileInfo); Chris@1350: #endif Chris@1350: if (!m_file) { Chris@1508: SVCERR << "WavFileWriter: Failed to create float-WAV file of " Chris@1508: << m_channels << " channels at rate " << fileRate << " (" Chris@1508: << sf_strerror(m_file) << ")" << endl; Chris@1348: m_error = QString("Failed to open audio file '%1' for writing") Chris@1350: .arg(writePath); Chris@1508: if (m_temp) { Chris@1508: delete m_temp; Chris@1582: m_temp = nullptr; Chris@1508: } Chris@1348: } Chris@674: } catch (FileOperationFailed &f) { Chris@674: m_error = f.what(); Chris@1582: m_temp = nullptr; Chris@1582: m_file = nullptr; Chris@174: } Chris@148: } Chris@148: Chris@148: WavFileWriter::~WavFileWriter() Chris@148: { Chris@1350: if (m_file) close(); Chris@148: } Chris@148: Chris@148: bool Chris@148: WavFileWriter::isOK() const Chris@148: { Chris@148: return (m_error.isEmpty()); Chris@148: } Chris@148: Chris@148: QString Chris@148: WavFileWriter::getError() const Chris@148: { Chris@148: return m_error; Chris@148: } Chris@148: Chris@684: QString Chris@684: WavFileWriter::getWriteFilename() const Chris@684: { Chris@684: if (m_temp) { Chris@684: return m_temp->getTemporaryFilename(); Chris@684: } else { Chris@684: return m_path; Chris@684: } Chris@684: } Chris@684: Chris@174: bool Chris@174: WavFileWriter::writeModel(DenseTimeValueModel *source, Chris@174: MultiSelection *selection) Chris@148: { Chris@174: if (source->getChannelCount() != m_channels) { Chris@690: SVDEBUG << "WavFileWriter::writeModel: Wrong number of channels (" Chris@174: << source->getChannelCount() << " != " << m_channels << ")" Chris@687: << endl; Chris@174: m_error = QString("Failed to write model to audio file '%1'") Chris@684: .arg(getWriteFilename()); Chris@174: return false; Chris@148: } Chris@148: Chris@1350: if (!m_file) { Chris@174: m_error = QString("Failed to write model to audio file '%1': File not open") Chris@684: .arg(getWriteFilename()); Chris@1350: return false; Chris@174: } Chris@148: Chris@174: bool ownSelection = false; Chris@174: if (!selection) { Chris@1350: selection = new MultiSelection; Chris@1350: selection->setSelection(Selection(source->getStartFrame(), Chris@1350: source->getEndFrame())); Chris@174: ownSelection = true; Chris@148: } Chris@148: Chris@1038: sv_frame_t bs = 2048; Chris@148: Chris@148: for (MultiSelection::SelectionList::iterator i = Chris@1350: selection->getSelections().begin(); Chris@1350: i != selection->getSelections().end(); ++i) { Chris@1429: Chris@1350: sv_frame_t f0(i->getStartFrame()), f1(i->getEndFrame()); Chris@148: Chris@1350: for (sv_frame_t f = f0; f < f1; f += bs) { Chris@1429: Chris@1350: sv_frame_t n = min(bs, f1 - f); Chris@1326: floatvec_t interleaved(n * m_channels, 0.f); Chris@148: Chris@1350: for (int c = 0; c < int(m_channels); ++c) { Chris@1326: auto chanbuf = source->getData(c, f, n); Chris@1350: for (int i = 0; in_range_for(chanbuf, i); ++i) { Chris@1350: interleaved[i * m_channels + c] = chanbuf[i]; Chris@1350: } Chris@1350: } Chris@148: Chris@1350: sf_count_t written = sf_writef_float(m_file, interleaved.data(), n); Chris@148: Chris@1350: if (written < n) { Chris@1350: m_error = QString("Only wrote %1 of %2 frames at file frame %3") Chris@1350: .arg(written).arg(n).arg(f); Chris@1350: break; Chris@1350: } Chris@1350: } Chris@148: } Chris@148: Chris@174: if (ownSelection) delete selection; Chris@174: Chris@174: return isOK(); Chris@174: } Chris@1429: Chris@174: bool Chris@1325: WavFileWriter::writeSamples(const float *const *samples, sv_frame_t count) Chris@174: { Chris@1350: if (!m_file) { Chris@174: m_error = QString("Failed to write model to audio file '%1': File not open") Chris@684: .arg(getWriteFilename()); Chris@1350: return false; Chris@174: } Chris@174: Chris@174: float *b = new float[count * m_channels]; Chris@1038: for (sv_frame_t i = 0; i < count; ++i) { Chris@929: for (int c = 0; c < int(m_channels); ++c) { Chris@174: b[i * m_channels + c] = samples[c][i]; Chris@174: } Chris@174: } Chris@174: Chris@1350: sv_frame_t written = sf_writef_float(m_file, b, count); Chris@174: Chris@174: delete[] b; Chris@174: Chris@1038: if (written < count) { Chris@174: m_error = QString("Only wrote %1 of %2 frames") Chris@174: .arg(written).arg(count); Chris@174: } Chris@174: Chris@174: return isOK(); Chris@174: } Chris@1520: Chris@1520: bool Chris@1520: WavFileWriter::putInterleavedFrames(const floatvec_t &frames) Chris@1520: { Chris@1520: sv_frame_t count = frames.size() / m_channels; Chris@1526: float **samples = Chris@1526: breakfastquay::allocate_channels(m_channels, count); Chris@1526: breakfastquay::v_deinterleave Chris@1526: (samples, frames.data(), m_channels, int(count)); Chris@1520: bool result = writeSamples(samples, count); Chris@1520: breakfastquay::deallocate_channels(samples, m_channels); Chris@1520: return result; Chris@1520: } Chris@1520: Chris@174: bool Chris@174: WavFileWriter::close() Chris@174: { Chris@1350: if (m_file) { Chris@1350: sf_close(m_file); Chris@1582: m_file = nullptr; Chris@174: } Chris@684: if (m_temp) { Chris@684: m_temp->moveToTarget(); Chris@684: delete m_temp; Chris@1582: m_temp = nullptr; Chris@684: } Chris@174: return true; Chris@148: } Chris@148: