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@148: 
Chris@148: #include <QFileInfo>
Chris@148: 
Chris@148: #include <iostream>
Chris@1055: #include <cmath>
Chris@148: 
Chris@1096: using namespace std;
Chris@1096: 
Chris@148: WavFileWriter::WavFileWriter(QString path,
Chris@1040: 			     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@674:     m_temp(0),
Chris@174:     m_file(0)
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@1040:         cerr << "WavFileWriter: WARNING: Non-integer sample rate "
Chris@1040:              << m_sampleRate << " presented, rounding to " << fileRate
Chris@1040:              << 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@684:         if (mode == WriteToTemporary) {
Chris@684:             m_temp = new TempWriteFile(m_path);
Chris@684:             m_file = sf_open(m_temp->getTemporaryFilename().toLocal8Bit(),
Chris@684:                              SFM_WRITE, &fileInfo);
Chris@684:             if (!m_file) {
Chris@843:                 cerr << "WavFileWriter: Failed to open file ("
Chris@843:                           << sf_strerror(m_file) << ")" << endl;
Chris@684:                 m_error = QString("Failed to open audio file '%1' for writing")
Chris@684:                     .arg(m_temp->getTemporaryFilename());
Chris@684:             }
Chris@684:         } else {
Chris@684:             m_file = sf_open(m_path.toLocal8Bit(), SFM_WRITE, &fileInfo);
Chris@684:             if (!m_file) {
Chris@843:                 cerr << "WavFileWriter: Failed to open file ("
Chris@843:                           << sf_strerror(m_file) << ")" << endl;
Chris@684:                 m_error = QString("Failed to open audio file '%1' for writing")
Chris@684:                     .arg(m_path);
Chris@684:             }
Chris@684:         }            
Chris@674:     } catch (FileOperationFailed &f) {
Chris@674:         m_error = f.what();
Chris@674:         m_temp = 0;
Chris@674:         m_file = 0;
Chris@174:     }
Chris@148: }
Chris@148: 
Chris@148: WavFileWriter::~WavFileWriter()
Chris@148: {
Chris@174:     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@174:     if (!m_file) {
Chris@174:         m_error = QString("Failed to write model to audio file '%1': File not open")
Chris@684:             .arg(getWriteFilename());
Chris@174: 	return false;
Chris@174:     }
Chris@148: 
Chris@174:     bool ownSelection = false;
Chris@174:     if (!selection) {
Chris@148: 	selection = new MultiSelection;
Chris@174: 	selection->setSelection(Selection(source->getStartFrame(),
Chris@174: 					  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@148: 	     selection->getSelections().begin();
Chris@148: 	 i != selection->getSelections().end(); ++i) {
Chris@148: 	
Chris@1038: 	sv_frame_t f0(i->getStartFrame()), f1(i->getEndFrame());
Chris@148: 
Chris@1038: 	for (sv_frame_t f = f0; f < f1; f += bs) {
Chris@148: 	    
Chris@1096: 	    sv_frame_t n = min(bs, f1 - f);
Chris@1096:             vector<float> interleaved(n * m_channels, 0.f);
Chris@148: 
Chris@174: 	    for (int c = 0; c < int(m_channels); ++c) {
Chris@1096:                 vector<float> chanbuf = source->getData(c, f, n);
Chris@1096: 		for (int i = 0; in_range_for(chanbuf, i); ++i) {
Chris@1096: 		    interleaved[i * m_channels + c] = chanbuf[i];
Chris@148: 		}
Chris@148: 	    }	    
Chris@148: 
Chris@1096: 	    sf_count_t written = sf_writef_float(m_file, interleaved.data(), n);
Chris@148: 
Chris@148: 	    if (written < n) {
Chris@148: 		m_error = QString("Only wrote %1 of %2 frames at file frame %3")
Chris@148: 		    .arg(written).arg(n).arg(f);
Chris@148: 		break;
Chris@148: 	    }
Chris@148: 	}
Chris@148:     }
Chris@148: 
Chris@174:     if (ownSelection) delete selection;
Chris@174: 
Chris@174:     return isOK();
Chris@174: }
Chris@174: 	
Chris@174: bool
Chris@1038: WavFileWriter::writeSamples(float **samples, sv_frame_t count)
Chris@174: {
Chris@174:     if (!m_file) {
Chris@174:         m_error = QString("Failed to write model to audio file '%1': File not open")
Chris@684:             .arg(getWriteFilename());
Chris@174: 	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@1038:     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@174:     
Chris@174: bool
Chris@174: WavFileWriter::close()
Chris@174: {
Chris@174:     if (m_file) {
Chris@174:         sf_close(m_file);
Chris@174:         m_file = 0;
Chris@174:     }
Chris@684:     if (m_temp) {
Chris@684:         m_temp->moveToTarget();
Chris@684:         delete m_temp;
Chris@684:         m_temp = 0;
Chris@684:     }
Chris@174:     return true;
Chris@148: }
Chris@148: