diff data/model/WritableWaveFileModel.cpp @ 1527:710e6250a401 zoom

Merge from default branch
author Chris Cannam
date Mon, 17 Sep 2018 13:51:14 +0100
parents 954d0cf29ca7
children 70e172e6cc59
line wrap: on
line diff
--- a/data/model/WritableWaveFileModel.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/data/model/WritableWaveFileModel.cpp	Mon Sep 17 13:51:14 2018 +0100
@@ -36,57 +36,121 @@
 
 //#define DEBUG_WRITABLE_WAVE_FILE_MODEL 1
 
-WritableWaveFileModel::WritableWaveFileModel(sv_samplerate_t sampleRate,
-					     int channels,
-					     QString path) :
+WritableWaveFileModel::WritableWaveFileModel(QString path,
+                                             sv_samplerate_t sampleRate,
+                                             int channels,
+                                             Normalisation norm) :
     m_model(0),
-    m_writer(0),
+    m_temporaryWriter(0),
+    m_targetWriter(0),
     m_reader(0),
+    m_normalisation(norm),
     m_sampleRate(sampleRate),
     m_channels(channels),
     m_frameCount(0),
     m_startFrame(0),
     m_proportion(PROPORTION_UNKNOWN)
 {
+    init(path);
+}
+
+WritableWaveFileModel::WritableWaveFileModel(sv_samplerate_t sampleRate,
+                                             int channels,
+                                             Normalisation norm) :
+    m_model(0),
+    m_temporaryWriter(0),
+    m_targetWriter(0),
+    m_reader(0),
+    m_normalisation(norm),
+    m_sampleRate(sampleRate),
+    m_channels(channels),
+    m_frameCount(0),
+    m_startFrame(0),
+    m_proportion(PROPORTION_UNKNOWN)
+{
+    init();
+}
+
+WritableWaveFileModel::WritableWaveFileModel(sv_samplerate_t sampleRate,
+                                             int channels) :
+    m_model(0),
+    m_temporaryWriter(0),
+    m_targetWriter(0),
+    m_reader(0),
+    m_normalisation(Normalisation::None),
+    m_sampleRate(sampleRate),
+    m_channels(channels),
+    m_frameCount(0),
+    m_startFrame(0),
+    m_proportion(PROPORTION_UNKNOWN)
+{
+    init();
+}
+
+void
+WritableWaveFileModel::init(QString path)
+{
     if (path.isEmpty()) {
         try {
+            // Temp dir is exclusive to this run of the application,
+            // so the filename only needs to be unique within that -
+            // model ID should be ok
             QDir dir(TempDirectory::getInstance()->getPath());
-            path = dir.filePath(QString("written_%1.wav")
-                                .arg((intptr_t)this));
-        } catch (DirectoryCreationFailed f) {
-            cerr << "WritableWaveFileModel: Failed to create temporary directory" << endl;
+            path = dir.filePath(QString("written_%1.wav").arg(getId()));
+        } catch (const DirectoryCreationFailed &f) {
+            SVCERR << "WritableWaveFileModel: Failed to create temporary directory" << endl;
             return;
         }
     }
 
-    // Write directly to the target file, so that we can do
-    // incremental writes and concurrent reads
-    m_writer = new WavFileWriter(path, sampleRate, channels,
-                                 WavFileWriter::WriteToTarget);
-    if (!m_writer->isOK()) {
-        cerr << "WritableWaveFileModel: Error in creating WAV file writer: " << m_writer->getError() << endl;
-        delete m_writer; 
-        m_writer = 0;
+    m_targetPath = path;
+    m_temporaryPath = "";
+
+    // We don't delete or null-out writer/reader members after
+    // failures here - they are all deleted in the dtor, and the
+    // presence/existence of the model is what's used to determine
+    // whether to go ahead, not the writer/readers. If the model is
+    // non-null, then the necessary writer/readers must be OK, as the
+    // model is the last thing initialised
+    
+    m_targetWriter = new WavFileWriter(m_targetPath, m_sampleRate, m_channels,
+                                       WavFileWriter::WriteToTarget);
+    
+    if (!m_targetWriter->isOK()) {
+        SVCERR << "WritableWaveFileModel: Error in creating WAV file writer: " << m_targetWriter->getError() << endl;
         return;
     }
+    
+    if (m_normalisation != Normalisation::None) {
 
-    FileSource source(m_writer->getPath());
+        // Temp dir is exclusive to this run of the application, so
+        // the filename only needs to be unique within that
+        QDir dir(TempDirectory::getInstance()->getPath());
+        m_temporaryPath = dir.filePath(QString("prenorm_%1.wav").arg(getId()));
+
+        m_temporaryWriter = new WavFileWriter
+            (m_temporaryPath, m_sampleRate, m_channels,
+             WavFileWriter::WriteToTarget);
+    
+        if (!m_temporaryWriter->isOK()) {
+            SVCERR << "WritableWaveFileModel: Error in creating temporary WAV file writer: " << m_temporaryWriter->getError() << endl;
+            return;
+        }
+    }        
+
+    FileSource source(m_targetPath);
 
     m_reader = new WavFileReader(source, true);
     if (!m_reader->getError().isEmpty()) {
-        cerr << "WritableWaveFileModel: Error in creating wave file reader" << endl;
-        delete m_reader;
-        m_reader = 0;
+        SVCERR << "WritableWaveFileModel: Error in creating wave file reader: " << m_reader->getError() << endl;
         return;
     }
     
     m_model = new ReadOnlyWaveFileModel(source, m_reader);
     if (!m_model->isOK()) {
-        cerr << "WritableWaveFileModel: Error in creating wave file model" << endl;
+        SVCERR << "WritableWaveFileModel: Error in creating wave file model" << endl;
         delete m_model;
         m_model = 0;
-        delete m_reader;
-        m_reader = 0;
         return;
     }
     m_model->setStartFrame(m_startFrame);
@@ -99,7 +163,8 @@
 WritableWaveFileModel::~WritableWaveFileModel()
 {
     delete m_model;
-    delete m_writer;
+    delete m_targetWriter;
+    delete m_temporaryWriter;
     delete m_reader;
 }
 
@@ -107,49 +172,53 @@
 WritableWaveFileModel::setStartFrame(sv_frame_t startFrame)
 {
     m_startFrame = startFrame;
-    if (m_model) m_model->setStartFrame(startFrame);
+    if (m_model) {
+        m_model->setStartFrame(startFrame);
+    }
 }
 
 bool
-WritableWaveFileModel::addSamples(float **samples, sv_frame_t count)
+WritableWaveFileModel::addSamples(const float *const *samples, sv_frame_t count)
 {
-    if (!m_writer) return false;
+    if (!m_model) return false;
 
 #ifdef DEBUG_WRITABLE_WAVE_FILE_MODEL
 //    SVDEBUG << "WritableWaveFileModel::addSamples(" << count << ")" << endl;
 #endif
 
-    if (!m_writer->writeSamples(samples, count)) {
-        cerr << "ERROR: WritableWaveFileModel::addSamples: writer failed: " << m_writer->getError() << endl;
+    WavFileWriter *writer = m_targetWriter;
+    if (m_normalisation != Normalisation::None) {
+        writer = m_temporaryWriter;
+    }
+    
+    if (!writer->writeSamples(samples, count)) {
+        SVCERR << "ERROR: WritableWaveFileModel::addSamples: writer failed: " << writer->getError() << endl;
         return false;
     }
 
     m_frameCount += count;
 
-    static int updateCounter = 0;
-
-    if (m_reader && m_reader->getChannelCount() == 0) {
-#ifdef DEBUG_WRITABLE_WAVE_FILE_MODEL
-        SVDEBUG << "WritableWaveFileModel::addSamples(" << count << "): calling updateFrameCount (initial)" << endl;
-#endif
-        m_reader->updateFrameCount();
-    } else if (++updateCounter == 100) {
-#ifdef DEBUG_WRITABLE_WAVE_FILE_MODEL
-        SVDEBUG << "WritableWaveFileModel::addSamples(" << count << "): calling updateFrameCount (periodic)" << endl;
-#endif
-        if (m_reader) m_reader->updateFrameCount();
-        updateCounter = 0;
+    if (m_normalisation == Normalisation::None) {
+        if (m_reader->getChannelCount() == 0) {
+            m_reader->updateFrameCount();
+        }
     }
 
     return true;
 }
 
+void
+WritableWaveFileModel::updateModel()
+{
+    if (!m_model) return;
+    
+    m_reader->updateFrameCount();
+}
+
 bool
 WritableWaveFileModel::isOK() const
 {
-    bool ok = (m_writer && m_writer->isOK());
-//    SVDEBUG << "WritableWaveFileModel::isOK(): ok = " << ok << endl;
-    return ok;
+    return (m_model && m_model->isOK());
 }
 
 bool
@@ -176,11 +245,56 @@
 void
 WritableWaveFileModel::writeComplete()
 {
-    if (m_reader) m_reader->updateDone();
+    if (!m_model) return;
+
+    if (m_normalisation == Normalisation::None) {
+        m_targetWriter->close();
+    } else {
+        m_temporaryWriter->close();
+        normaliseToTarget();
+    }
+    
+    m_reader->updateDone();
     m_proportion = 100;
     emit modelChanged();
 }
 
+void
+WritableWaveFileModel::normaliseToTarget()
+{
+    if (m_temporaryPath == "") {
+        SVCERR << "WritableWaveFileModel::normaliseToTarget: No temporary path available" << endl;
+        return;
+    }
+    
+    WavFileReader normalisingReader(m_temporaryPath, false,
+                                    WavFileReader::Normalisation::Peak);
+
+    if (!normalisingReader.getError().isEmpty()) {
+        SVCERR << "WritableWaveFileModel: Error in creating normalising reader: " << normalisingReader.getError() << endl;
+        return;
+    }
+
+    sv_frame_t frame = 0;
+    sv_frame_t block = 65536;
+    sv_frame_t count = normalisingReader.getFrameCount();
+
+    while (frame < count) {
+        auto frames = normalisingReader.getInterleavedFrames(frame, block);
+        if (!m_targetWriter->putInterleavedFrames(frames)) {
+            SVCERR << "ERROR: WritableWaveFileModel::normaliseToTarget: writer failed: " << m_targetWriter->getError() << endl;
+            return;
+        }
+        frame += block;
+    }
+
+    m_targetWriter->close();
+
+    delete m_temporaryWriter;
+    m_temporaryWriter = 0;
+    QFile::remove(m_temporaryPath);
+}
+
 sv_frame_t
 WritableWaveFileModel::getFrameCount() const
 {
@@ -188,14 +302,14 @@
     return m_frameCount;
 }
 
-vector<float>
+floatvec_t
 WritableWaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) const
 {
     if (!m_model || m_model->getChannelCount() == 0) return {};
     return m_model->getData(channel, start, count);
 }
 
-vector<vector<float>>
+vector<floatvec_t>
 WritableWaveFileModel::getMultiChannelData(int fromchannel, int tochannel,
                                            sv_frame_t start, sv_frame_t count) const
 {
@@ -241,7 +355,7 @@
     Model::toXml
         (out, indent,
          QString("type=\"wavefile\" file=\"%1\" subtype=\"writable\" %2")
-         .arg(encodeEntities(m_writer->getPath()))
+         .arg(encodeEntities(m_targetPath))
          .arg(extraAttributes));
 }