| Chris@147 | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| Chris@147 | 2 | 
| Chris@147 | 3 /* | 
| Chris@147 | 4     Sonic Visualiser | 
| Chris@147 | 5     An audio file viewer and annotation editor. | 
| Chris@147 | 6     Centre for Digital Music, Queen Mary, University of London. | 
| Chris@202 | 7     This file copyright 2006 Chris Cannam and QMUL. | 
| Chris@147 | 8 | 
| Chris@147 | 9     This program is free software; you can redistribute it and/or | 
| Chris@147 | 10     modify it under the terms of the GNU General Public License as | 
| Chris@147 | 11     published by the Free Software Foundation; either version 2 of the | 
| Chris@147 | 12     License, or (at your option) any later version.  See the file | 
| Chris@147 | 13     COPYING included with this distribution for more information. | 
| Chris@147 | 14 */ | 
| Chris@147 | 15 | 
| Chris@150 | 16 #include "WaveFileModel.h" | 
| Chris@147 | 17 | 
| Chris@147 | 18 #include "fileio/AudioFileReader.h" | 
| Chris@147 | 19 #include "fileio/AudioFileReaderFactory.h" | 
| Chris@147 | 20 | 
| Chris@150 | 21 #include "system/System.h" | 
| Chris@147 | 22 | 
| Chris@921 | 23 #include "base/Preferences.h" | 
| Chris@921 | 24 | 
| Chris@147 | 25 #include <QFileInfo> | 
| Chris@314 | 26 #include <QTextStream> | 
| Chris@147 | 27 | 
| Chris@147 | 28 #include <iostream> | 
| Chris@147 | 29 #include <unistd.h> | 
| Chris@572 | 30 #include <cmath> | 
| Chris@147 | 31 #include <sndfile.h> | 
| Chris@147 | 32 | 
| Chris@147 | 33 #include <cassert> | 
| Chris@147 | 34 | 
| Chris@1096 | 35 using namespace std; | 
| Chris@1096 | 36 | 
| Chris@236 | 37 //#define DEBUG_WAVE_FILE_MODEL 1 | 
| Chris@236 | 38 | 
| Chris@179 | 39 PowerOfSqrtTwoZoomConstraint | 
| Chris@179 | 40 WaveFileModel::m_zoomConstraint; | 
| Chris@179 | 41 | 
| Chris@1040 | 42 WaveFileModel::WaveFileModel(FileSource source, sv_samplerate_t targetRate) : | 
| Chris@316 | 43     m_source(source), | 
| Chris@316 | 44     m_path(source.getLocation()), | 
| Chris@971 | 45     m_reader(0), | 
| Chris@175 | 46     m_myReader(true), | 
| Chris@300 | 47     m_startFrame(0), | 
| Chris@147 | 48     m_fillThread(0), | 
| Chris@147 | 49     m_updateTimer(0), | 
| Chris@147 | 50     m_lastFillExtent(0), | 
| Chris@752 | 51     m_exiting(false), | 
| Chris@752 | 52     m_lastDirectReadStart(0), | 
| Chris@752 | 53     m_lastDirectReadCount(0) | 
| Chris@147 | 54 { | 
| Chris@316 | 55     m_source.waitForData(); | 
| Chris@316 | 56     if (m_source.isOK()) { | 
| Chris@921 | 57         bool normalise = Preferences::getInstance()->getNormaliseAudio(); | 
| Chris@327 | 58         m_reader = AudioFileReaderFactory::createThreadingReader | 
| Chris@921 | 59             (m_source, targetRate, normalise); | 
| Chris@316 | 60         if (m_reader) { | 
| Chris@690 | 61             SVDEBUG << "WaveFileModel::WaveFileModel: reader rate: " | 
| Chris@687 | 62                       << m_reader->getSampleRate() << endl; | 
| Chris@316 | 63         } | 
| Chris@316 | 64     } | 
| Chris@292 | 65     if (m_reader) setObjectName(m_reader->getTitle()); | 
| Chris@316 | 66     if (objectName() == "") setObjectName(QFileInfo(m_path).fileName()); | 
| Chris@175 | 67     if (isOK()) fillCache(); | 
| Chris@175 | 68 } | 
| Chris@175 | 69 | 
| Chris@317 | 70 WaveFileModel::WaveFileModel(FileSource source, AudioFileReader *reader) : | 
| Chris@316 | 71     m_source(source), | 
| Chris@316 | 72     m_path(source.getLocation()), | 
| Chris@971 | 73     m_reader(0), | 
| Chris@175 | 74     m_myReader(false), | 
| Chris@300 | 75     m_startFrame(0), | 
| Chris@175 | 76     m_fillThread(0), | 
| Chris@175 | 77     m_updateTimer(0), | 
| Chris@175 | 78     m_lastFillExtent(0), | 
| Chris@175 | 79     m_exiting(false) | 
| Chris@175 | 80 { | 
| Chris@175 | 81     m_reader = reader; | 
| Chris@292 | 82     if (m_reader) setObjectName(m_reader->getTitle()); | 
| Chris@316 | 83     if (objectName() == "") setObjectName(QFileInfo(m_path).fileName()); | 
| Chris@187 | 84     fillCache(); | 
| Chris@147 | 85 } | 
| Chris@147 | 86 | 
| Chris@147 | 87 WaveFileModel::~WaveFileModel() | 
| Chris@147 | 88 { | 
| Chris@147 | 89     m_exiting = true; | 
| Chris@147 | 90     if (m_fillThread) m_fillThread->wait(); | 
| Chris@175 | 91     if (m_myReader) delete m_reader; | 
| Chris@147 | 92     m_reader = 0; | 
| Chris@147 | 93 } | 
| Chris@147 | 94 | 
| Chris@147 | 95 bool | 
| Chris@147 | 96 WaveFileModel::isOK() const | 
| Chris@147 | 97 { | 
| Chris@147 | 98     return m_reader && m_reader->isOK(); | 
| Chris@147 | 99 } | 
| Chris@147 | 100 | 
| Chris@147 | 101 bool | 
| Chris@147 | 102 WaveFileModel::isReady(int *completion) const | 
| Chris@147 | 103 { | 
| Chris@147 | 104     bool ready = (isOK() && (m_fillThread == 0)); | 
| Chris@147 | 105     double c = double(m_lastFillExtent) / double(getEndFrame() - getStartFrame()); | 
| Chris@266 | 106     static int prevCompletion = 0; | 
| Chris@265 | 107     if (completion) { | 
| Chris@265 | 108         *completion = int(c * 100.0 + 0.01); | 
| Chris@265 | 109         if (m_reader) { | 
| Chris@265 | 110             int decodeCompletion = m_reader->getDecodeCompletion(); | 
| Chris@266 | 111             if (decodeCompletion < 90) *completion = decodeCompletion; | 
| Chris@1096 | 112             else *completion = min(*completion, decodeCompletion); | 
| Chris@265 | 113         } | 
| Chris@266 | 114         if (*completion != 0 && | 
| Chris@266 | 115             *completion != 100 && | 
| Chris@266 | 116             prevCompletion != 0 && | 
| Chris@266 | 117             prevCompletion > *completion) { | 
| Chris@266 | 118             // just to avoid completion going backwards | 
| Chris@266 | 119             *completion = prevCompletion; | 
| Chris@266 | 120         } | 
| Chris@266 | 121         prevCompletion = *completion; | 
| Chris@265 | 122     } | 
| Chris@236 | 123 #ifdef DEBUG_WAVE_FILE_MODEL | 
| Chris@690 | 124     SVDEBUG << "WaveFileModel::isReady(): ready = " << ready << ", completion = " << (completion ? *completion : -1) << endl; | 
| Chris@236 | 125 #endif | 
| Chris@147 | 126     return ready; | 
| Chris@147 | 127 } | 
| Chris@147 | 128 | 
| Chris@1038 | 129 sv_frame_t | 
| Chris@147 | 130 WaveFileModel::getFrameCount() const | 
| Chris@147 | 131 { | 
| Chris@147 | 132     if (!m_reader) return 0; | 
| Chris@147 | 133     return m_reader->getFrameCount(); | 
| Chris@147 | 134 } | 
| Chris@147 | 135 | 
| Chris@929 | 136 int | 
| Chris@147 | 137 WaveFileModel::getChannelCount() const | 
| Chris@147 | 138 { | 
| Chris@147 | 139     if (!m_reader) return 0; | 
| Chris@147 | 140     return m_reader->getChannelCount(); | 
| Chris@147 | 141 } | 
| Chris@147 | 142 | 
| Chris@1040 | 143 sv_samplerate_t | 
| Chris@147 | 144 WaveFileModel::getSampleRate() const | 
| Chris@147 | 145 { | 
| Chris@147 | 146     if (!m_reader) return 0; | 
| Chris@147 | 147     return m_reader->getSampleRate(); | 
| Chris@147 | 148 } | 
| Chris@147 | 149 | 
| Chris@1040 | 150 sv_samplerate_t | 
| Chris@297 | 151 WaveFileModel::getNativeRate() const | 
| Chris@297 | 152 { | 
| Chris@297 | 153     if (!m_reader) return 0; | 
| Chris@1040 | 154     sv_samplerate_t rate = m_reader->getNativeRate(); | 
| Chris@297 | 155     if (rate == 0) rate = getSampleRate(); | 
| Chris@297 | 156     return rate; | 
| Chris@297 | 157 } | 
| Chris@297 | 158 | 
| Chris@333 | 159 QString | 
| Chris@333 | 160 WaveFileModel::getTitle() const | 
| Chris@333 | 161 { | 
| Chris@333 | 162     QString title; | 
| Chris@333 | 163     if (m_reader) title = m_reader->getTitle(); | 
| Chris@333 | 164     if (title == "") title = objectName(); | 
| Chris@333 | 165     return title; | 
| Chris@333 | 166 } | 
| Chris@333 | 167 | 
| Chris@333 | 168 QString | 
| Chris@333 | 169 WaveFileModel::getMaker() const | 
| Chris@333 | 170 { | 
| Chris@333 | 171     if (m_reader) return m_reader->getMaker(); | 
| Chris@333 | 172     return ""; | 
| Chris@333 | 173 } | 
| Chris@345 | 174 | 
| Chris@345 | 175 QString | 
| Chris@345 | 176 WaveFileModel::getLocation() const | 
| Chris@345 | 177 { | 
| Chris@345 | 178     if (m_reader) return m_reader->getLocation(); | 
| Chris@345 | 179     return ""; | 
| Chris@345 | 180 } | 
| Chris@1010 | 181 | 
| Chris@1010 | 182 QString | 
| Chris@1010 | 183 WaveFileModel::getLocalFilename() const | 
| Chris@1010 | 184 { | 
| Chris@1010 | 185     if (m_reader) return m_reader->getLocalFilename(); | 
| Chris@1010 | 186     return ""; | 
| Chris@1010 | 187 } | 
| Chris@333 | 188 | 
| Chris@1096 | 189 vector<float> | 
| Chris@1096 | 190 WaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) const | 
| Chris@147 | 191 { | 
| Chris@147 | 192     // Always read these directly from the file. | 
| Chris@1096 | 193     // This is used for e.g. audio playback or input to transforms. | 
| Chris@147 | 194 | 
| Chris@429 | 195 #ifdef DEBUG_WAVE_FILE_MODEL | 
| Chris@843 | 196     cout << "WaveFileModel::getData[" << this << "]: " << channel << ", " << start << ", " << count << ", " << buffer << endl; | 
| Chris@429 | 197 #endif | 
| Chris@429 | 198 | 
| Chris@1096 | 199     if (!m_reader || !m_reader->isOK() || count == 0) { | 
| Chris@1096 | 200         return {}; | 
| Chris@1096 | 201     } | 
| Chris@1096 | 202 | 
| Chris@1096 | 203     if (channel != -1) { | 
| Chris@1096 | 204         // get a single channel | 
| Chris@1096 | 205         auto data = getMultiChannelData(channel, channel, start, count); | 
| Chris@1096 | 206         if (data.empty()) return {}; | 
| Chris@1096 | 207         else return data[0]; | 
| Chris@1096 | 208     } | 
| Chris@1096 | 209 | 
| Chris@1096 | 210     // channel == -1, mix down all channels | 
| Chris@1096 | 211 | 
| Chris@1096 | 212     auto all = getMultiChannelData(0, getChannelCount()-1, start, count); | 
| Chris@1096 | 213     if (all.empty()) return {}; | 
| Chris@1096 | 214 | 
| Chris@1096 | 215     sv_frame_t n = all[0].size(); | 
| Chris@1096 | 216     vector<float> result(n, 0.f); | 
| Chris@1096 | 217 | 
| Chris@1096 | 218     for (int c = 0; in_range_for(all, c); ++c) { | 
| Chris@1096 | 219         for (sv_frame_t i = 0; i < n; ++i) { | 
| Chris@1096 | 220             result[i] += all[c][i]; | 
| Chris@300 | 221         } | 
| Chris@147 | 222     } | 
| Chris@147 | 223 | 
| Chris@1096 | 224     return result; | 
| Chris@147 | 225 } | 
| Chris@147 | 226 | 
| Chris@1096 | 227 vector<vector<float>> | 
| Chris@1086 | 228 WaveFileModel::getMultiChannelData(int fromchannel, int tochannel, | 
| Chris@1096 | 229                                    sv_frame_t start, sv_frame_t count) const | 
| Chris@363 | 230 { | 
| Chris@429 | 231 #ifdef DEBUG_WAVE_FILE_MODEL | 
| Chris@843 | 232     cout << "WaveFileModel::getData[" << this << "]: " << fromchannel << "," << tochannel << ", " << start << ", " << count << ", " << buffer << endl; | 
| Chris@429 | 233 #endif | 
| Chris@429 | 234 | 
| Chris@929 | 235     int channels = getChannelCount(); | 
| Chris@363 | 236 | 
| Chris@363 | 237     if (fromchannel > tochannel) { | 
| Chris@843 | 238         cerr << "ERROR: WaveFileModel::getData: fromchannel (" | 
| Chris@363 | 239                   << fromchannel << ") > tochannel (" << tochannel << ")" | 
| Chris@843 | 240                   << endl; | 
| Chris@1096 | 241         return {}; | 
| Chris@363 | 242     } | 
| Chris@363 | 243 | 
| Chris@363 | 244     if (tochannel >= channels) { | 
| Chris@843 | 245         cerr << "ERROR: WaveFileModel::getData: tochannel (" | 
| Chris@363 | 246                   << tochannel << ") >= channel count (" << channels << ")" | 
| Chris@843 | 247                   << endl; | 
| Chris@1096 | 248         return {}; | 
| Chris@363 | 249     } | 
| Chris@363 | 250 | 
| Chris@1096 | 251     if (!m_reader || !m_reader->isOK() || count == 0) { | 
| Chris@1096 | 252         return {}; | 
| Chris@363 | 253     } | 
| Chris@363 | 254 | 
| Chris@929 | 255     int reqchannels = (tochannel - fromchannel) + 1; | 
| Chris@363 | 256 | 
| Chris@363 | 257     if (start >= m_startFrame) { | 
| Chris@363 | 258         start -= m_startFrame; | 
| Chris@363 | 259     } else { | 
| Chris@363 | 260         if (count <= m_startFrame - start) { | 
| Chris@1096 | 261             return {}; | 
| Chris@363 | 262         } else { | 
| Chris@363 | 263             count -= (m_startFrame - start); | 
| Chris@363 | 264             start = 0; | 
| Chris@363 | 265         } | 
| Chris@363 | 266     } | 
| Chris@363 | 267 | 
| Chris@1096 | 268     vector<float> interleaved = m_reader->getInterleavedFrames(start, count); | 
| Chris@1096 | 269     if (channels == 1) return { interleaved }; | 
| Chris@1096 | 270 | 
| Chris@1096 | 271     sv_frame_t obtained = interleaved.size() / channels; | 
| Chris@1096 | 272     vector<vector<float>> result(reqchannels, vector<float>(obtained, 0.f)); | 
| Chris@1096 | 273 | 
| Chris@1096 | 274     for (int c = fromchannel; c <= tochannel; ++c) { | 
| Chris@1096 | 275         int destc = c - fromchannel; | 
| Chris@1096 | 276         for (int i = 0; i < obtained; ++i) { | 
| Chris@1096 | 277             result[destc][i] = interleaved[i * channels + c]; | 
| Chris@363 | 278         } | 
| Chris@363 | 279     } | 
| Chris@1096 | 280 | 
| Chris@1096 | 281     return result; | 
| Chris@363 | 282 } | 
| Chris@363 | 283 | 
| Chris@929 | 284 int | 
| Chris@929 | 285 WaveFileModel::getSummaryBlockSize(int desired) const | 
| Chris@377 | 286 { | 
| Chris@377 | 287     int cacheType = 0; | 
| Chris@377 | 288     int power = m_zoomConstraint.getMinCachePower(); | 
| Chris@929 | 289     int roundedBlockSize = m_zoomConstraint.getNearestBlockSize | 
| Chris@377 | 290         (desired, cacheType, power, ZoomConstraint::RoundDown); | 
| Chris@377 | 291     if (cacheType != 0 && cacheType != 1) { | 
| Chris@377 | 292         // We will be reading directly from file, so can satisfy any | 
| Chris@377 | 293         // blocksize requirement | 
| Chris@377 | 294         return desired; | 
| Chris@377 | 295     } else { | 
| Chris@377 | 296         return roundedBlockSize; | 
| Chris@377 | 297     } | 
| Chris@377 | 298 } | 
| Chris@377 | 299 | 
| Chris@225 | 300 void | 
| Chris@1038 | 301 WaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count, | 
| Chris@929 | 302                             RangeBlock &ranges, int &blockSize) const | 
| Chris@147 | 303 { | 
| Chris@225 | 304     ranges.clear(); | 
| Chris@225 | 305     if (!isOK()) return; | 
| Chris@377 | 306     ranges.reserve((count / blockSize) + 1); | 
| Chris@147 | 307 | 
| Chris@300 | 308     if (start > m_startFrame) start -= m_startFrame; | 
| Chris@300 | 309     else if (count <= m_startFrame - start) return; | 
| Chris@300 | 310     else { | 
| Chris@300 | 311         count -= (m_startFrame - start); | 
| Chris@300 | 312         start = 0; | 
| Chris@147 | 313     } | 
| Chris@147 | 314 | 
| Chris@147 | 315     int cacheType = 0; | 
| Chris@179 | 316     int power = m_zoomConstraint.getMinCachePower(); | 
| Chris@929 | 317     int roundedBlockSize = m_zoomConstraint.getNearestBlockSize | 
| Chris@377 | 318         (blockSize, cacheType, power, ZoomConstraint::RoundDown); | 
| Chris@147 | 319 | 
| Chris@929 | 320     int channels = getChannelCount(); | 
| Chris@147 | 321 | 
| Chris@147 | 322     if (cacheType != 0 && cacheType != 1) { | 
| Chris@147 | 323 | 
| Chris@147 | 324 	// We need to read directly from the file.  We haven't got | 
| Chris@147 | 325 	// this cached.  Hope the requested area is small.  This is | 
| Chris@147 | 326 	// not optimal -- we'll end up reading the same frames twice | 
| Chris@147 | 327 	// for stereo files, in two separate calls to this method. | 
| Chris@147 | 328 	// We could fairly trivially handle this for most cases that | 
| Chris@147 | 329 	// matter by putting a single cache in getInterleavedFrames | 
| Chris@147 | 330 	// for short queries. | 
| Chris@147 | 331 | 
| Chris@377 | 332         m_directReadMutex.lock(); | 
| Chris@377 | 333 | 
| Chris@377 | 334         if (m_lastDirectReadStart != start || | 
| Chris@377 | 335             m_lastDirectReadCount != count || | 
| Chris@377 | 336             m_directRead.empty()) { | 
| Chris@377 | 337 | 
| Chris@1041 | 338             m_directRead = m_reader->getInterleavedFrames(start, count); | 
| Chris@377 | 339             m_lastDirectReadStart = start; | 
| Chris@377 | 340             m_lastDirectReadCount = count; | 
| Chris@377 | 341         } | 
| Chris@377 | 342 | 
| Chris@147 | 343 	float max = 0.0, min = 0.0, total = 0.0; | 
| Chris@1038 | 344 	sv_frame_t i = 0, got = 0; | 
| Chris@147 | 345 | 
| Chris@300 | 346 	while (i < count) { | 
| Chris@147 | 347 | 
| Chris@1038 | 348 	    sv_frame_t index = i * channels + channel; | 
| Chris@1038 | 349 	    if (index >= (sv_frame_t)m_directRead.size()) break; | 
| Chris@147 | 350 | 
| Chris@377 | 351 	    float sample = m_directRead[index]; | 
| Chris@300 | 352             if (sample > max || got == 0) max = sample; | 
| Chris@300 | 353 	    if (sample < min || got == 0) min = sample; | 
| Chris@147 | 354             total += fabsf(sample); | 
| Chris@838 | 355 | 
| Chris@147 | 356 	    ++i; | 
| Chris@300 | 357             ++got; | 
| Chris@147 | 358 | 
| Chris@300 | 359             if (got == blockSize) { | 
| Chris@1038 | 360                 ranges.push_back(Range(min, max, total / float(got))); | 
| Chris@147 | 361                 min = max = total = 0.0f; | 
| Chris@300 | 362                 got = 0; | 
| Chris@147 | 363 	    } | 
| Chris@147 | 364 	} | 
| Chris@147 | 365 | 
| Chris@377 | 366         m_directReadMutex.unlock(); | 
| Chris@377 | 367 | 
| Chris@300 | 368 	if (got > 0) { | 
| Chris@1038 | 369             ranges.push_back(Range(min, max, total / float(got))); | 
| Chris@147 | 370 	} | 
| Chris@147 | 371 | 
| Chris@225 | 372 	return; | 
| Chris@147 | 373 | 
| Chris@147 | 374     } else { | 
| Chris@147 | 375 | 
| Chris@147 | 376 	QMutexLocker locker(&m_mutex); | 
| Chris@147 | 377 | 
| Chris@147 | 378 	const RangeBlock &cache = m_cache[cacheType]; | 
| Chris@147 | 379 | 
| Chris@377 | 380         blockSize = roundedBlockSize; | 
| Chris@377 | 381 | 
| Chris@1038 | 382 	sv_frame_t cacheBlock, div; | 
| Chris@147 | 383 | 
| Chris@147 | 384 	if (cacheType == 0) { | 
| Chris@179 | 385 	    cacheBlock = (1 << m_zoomConstraint.getMinCachePower()); | 
| Chris@147 | 386             div = (1 << power) / cacheBlock; | 
| Chris@147 | 387 	} else { | 
| Chris@1038 | 388 	    cacheBlock = sv_frame_t((1 << m_zoomConstraint.getMinCachePower()) * sqrt(2.) + 0.01); | 
| Chris@1038 | 389             div = sv_frame_t(((1 << power) * sqrt(2.) + 0.01) / double(cacheBlock)); | 
| Chris@147 | 390 	} | 
| Chris@147 | 391 | 
| Chris@1038 | 392 	sv_frame_t startIndex = start / cacheBlock; | 
| Chris@1038 | 393 	sv_frame_t endIndex = (start + count) / cacheBlock; | 
| Chris@147 | 394 | 
| Chris@147 | 395 	float max = 0.0, min = 0.0, total = 0.0; | 
| Chris@1038 | 396 	sv_frame_t i = 0, got = 0; | 
| Chris@147 | 397 | 
| Chris@236 | 398 #ifdef DEBUG_WAVE_FILE_MODEL | 
| Chris@300 | 399 	cerr << "blockSize is " << blockSize << ", cacheBlock " << cacheBlock << ", start " << start << ", count " << count << " (frame count " << getFrameCount() << "), power is " << power << ", div is " << div << ", startIndex " << startIndex << ", endIndex " << endIndex << endl; | 
| Chris@236 | 400 #endif | 
| Chris@147 | 401 | 
| Chris@300 | 402 	for (i = 0; i <= endIndex - startIndex; ) { | 
| Chris@147 | 403 | 
| Chris@1038 | 404 	    sv_frame_t index = (i + startIndex) * channels + channel; | 
| Chris@1038 | 405 	    if (index >= (sv_frame_t)cache.size()) break; | 
| Chris@147 | 406 | 
| Chris@147 | 407             const Range &range = cache[index]; | 
| Chris@410 | 408             if (range.max() > max || got == 0) max = range.max(); | 
| Chris@410 | 409             if (range.min() < min || got == 0) min = range.min(); | 
| Chris@410 | 410             total += range.absmean(); | 
| Chris@147 | 411 | 
| Chris@147 | 412 	    ++i; | 
| Chris@300 | 413             ++got; | 
| Chris@147 | 414 | 
| Chris@300 | 415 	    if (got == div) { | 
| Chris@1038 | 416 		ranges.push_back(Range(min, max, total / float(got))); | 
| Chris@147 | 417                 min = max = total = 0.0f; | 
| Chris@300 | 418                 got = 0; | 
| Chris@147 | 419 	    } | 
| Chris@147 | 420 	} | 
| Chris@147 | 421 | 
| Chris@300 | 422 	if (got > 0) { | 
| Chris@1038 | 423             ranges.push_back(Range(min, max, total / float(got))); | 
| Chris@147 | 424 	} | 
| Chris@147 | 425     } | 
| Chris@147 | 426 | 
| Chris@236 | 427 #ifdef DEBUG_WAVE_FILE_MODEL | 
| Chris@690 | 428     SVDEBUG << "returning " << ranges.size() << " ranges" << endl; | 
| Chris@236 | 429 #endif | 
| Chris@225 | 430     return; | 
| Chris@147 | 431 } | 
| Chris@147 | 432 | 
| Chris@147 | 433 WaveFileModel::Range | 
| Chris@1038 | 434 WaveFileModel::getSummary(int channel, sv_frame_t start, sv_frame_t count) const | 
| Chris@147 | 435 { | 
| Chris@147 | 436     Range range; | 
| Chris@147 | 437     if (!isOK()) return range; | 
| Chris@147 | 438 | 
| Chris@300 | 439     if (start > m_startFrame) start -= m_startFrame; | 
| Chris@300 | 440     else if (count <= m_startFrame - start) return range; | 
| Chris@300 | 441     else { | 
| Chris@300 | 442         count -= (m_startFrame - start); | 
| Chris@300 | 443         start = 0; | 
| Chris@147 | 444     } | 
| Chris@147 | 445 | 
| Chris@929 | 446     int blockSize; | 
| Chris@300 | 447     for (blockSize = 1; blockSize <= count; blockSize *= 2); | 
| Chris@300 | 448     if (blockSize > 1) blockSize /= 2; | 
| Chris@147 | 449 | 
| Chris@147 | 450     bool first = false; | 
| Chris@147 | 451 | 
| Chris@1038 | 452     sv_frame_t blockStart = (start / blockSize) * blockSize; | 
| Chris@1038 | 453     sv_frame_t blockEnd = ((start + count) / blockSize) * blockSize; | 
| Chris@147 | 454 | 
| Chris@147 | 455     if (blockStart < start) blockStart += blockSize; | 
| Chris@147 | 456 | 
| Chris@147 | 457     if (blockEnd > blockStart) { | 
| Chris@225 | 458         RangeBlock ranges; | 
| Chris@300 | 459         getSummaries(channel, blockStart, blockEnd - blockStart, ranges, blockSize); | 
| Chris@929 | 460         for (int i = 0; i < (int)ranges.size(); ++i) { | 
| Chris@410 | 461             if (first || ranges[i].min() < range.min()) range.setMin(ranges[i].min()); | 
| Chris@410 | 462             if (first || ranges[i].max() > range.max()) range.setMax(ranges[i].max()); | 
| Chris@410 | 463             if (first || ranges[i].absmean() < range.absmean()) range.setAbsmean(ranges[i].absmean()); | 
| Chris@147 | 464             first = false; | 
| Chris@147 | 465         } | 
| Chris@147 | 466     } | 
| Chris@147 | 467 | 
| Chris@147 | 468     if (blockStart > start) { | 
| Chris@300 | 469         Range startRange = getSummary(channel, start, blockStart - start); | 
| Chris@1096 | 470         range.setMin(min(range.min(), startRange.min())); | 
| Chris@1096 | 471         range.setMax(max(range.max(), startRange.max())); | 
| Chris@1096 | 472         range.setAbsmean(min(range.absmean(), startRange.absmean())); | 
| Chris@147 | 473     } | 
| Chris@147 | 474 | 
| Chris@300 | 475     if (blockEnd < start + count) { | 
| Chris@300 | 476         Range endRange = getSummary(channel, blockEnd, start + count - blockEnd); | 
| Chris@1096 | 477         range.setMin(min(range.min(), endRange.min())); | 
| Chris@1096 | 478         range.setMax(max(range.max(), endRange.max())); | 
| Chris@1096 | 479         range.setAbsmean(min(range.absmean(), endRange.absmean())); | 
| Chris@147 | 480     } | 
| Chris@147 | 481 | 
| Chris@147 | 482     return range; | 
| Chris@147 | 483 } | 
| Chris@147 | 484 | 
| Chris@147 | 485 void | 
| Chris@147 | 486 WaveFileModel::fillCache() | 
| Chris@147 | 487 { | 
| Chris@147 | 488     m_mutex.lock(); | 
| Chris@188 | 489 | 
| Chris@147 | 490     m_updateTimer = new QTimer(this); | 
| Chris@147 | 491     connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut())); | 
| Chris@147 | 492     m_updateTimer->start(100); | 
| Chris@188 | 493 | 
| Chris@147 | 494     m_fillThread = new RangeCacheFillThread(*this); | 
| Chris@147 | 495     connect(m_fillThread, SIGNAL(finished()), this, SLOT(cacheFilled())); | 
| Chris@188 | 496 | 
| Chris@147 | 497     m_mutex.unlock(); | 
| Chris@147 | 498     m_fillThread->start(); | 
| Chris@188 | 499 | 
| Chris@236 | 500 #ifdef DEBUG_WAVE_FILE_MODEL | 
| Chris@690 | 501     SVDEBUG << "WaveFileModel::fillCache: started fill thread" << endl; | 
| Chris@236 | 502 #endif | 
| Chris@147 | 503 } | 
| Chris@147 | 504 | 
| Chris@147 | 505 void | 
| Chris@147 | 506 WaveFileModel::fillTimerTimedOut() | 
| Chris@147 | 507 { | 
| Chris@147 | 508     if (m_fillThread) { | 
| Chris@1038 | 509 	sv_frame_t fillExtent = m_fillThread->getFillExtent(); | 
| Chris@236 | 510 #ifdef DEBUG_WAVE_FILE_MODEL | 
| Chris@690 | 511         SVDEBUG << "WaveFileModel::fillTimerTimedOut: extent = " << fillExtent << endl; | 
| Chris@236 | 512 #endif | 
| Chris@147 | 513 	if (fillExtent > m_lastFillExtent) { | 
| Chris@931 | 514 	    emit modelChangedWithin(m_lastFillExtent, fillExtent); | 
| Chris@147 | 515 	    m_lastFillExtent = fillExtent; | 
| Chris@147 | 516 	} | 
| Chris@147 | 517     } else { | 
| Chris@236 | 518 #ifdef DEBUG_WAVE_FILE_MODEL | 
| Chris@690 | 519         SVDEBUG << "WaveFileModel::fillTimerTimedOut: no thread" << endl; | 
| Chris@236 | 520 #endif | 
| Chris@147 | 521 	emit modelChanged(); | 
| Chris@147 | 522     } | 
| Chris@147 | 523 } | 
| Chris@147 | 524 | 
| Chris@147 | 525 void | 
| Chris@147 | 526 WaveFileModel::cacheFilled() | 
| Chris@147 | 527 { | 
| Chris@147 | 528     m_mutex.lock(); | 
| Chris@147 | 529     delete m_fillThread; | 
| Chris@147 | 530     m_fillThread = 0; | 
| Chris@147 | 531     delete m_updateTimer; | 
| Chris@147 | 532     m_updateTimer = 0; | 
| Chris@147 | 533     m_mutex.unlock(); | 
| Chris@267 | 534     if (getEndFrame() > m_lastFillExtent) { | 
| Chris@931 | 535         emit modelChangedWithin(m_lastFillExtent, getEndFrame()); | 
| Chris@267 | 536     } | 
| Chris@147 | 537     emit modelChanged(); | 
| Chris@411 | 538     emit ready(); | 
| Chris@236 | 539 #ifdef DEBUG_WAVE_FILE_MODEL | 
| Chris@690 | 540     SVDEBUG << "WaveFileModel::cacheFilled" << endl; | 
| Chris@236 | 541 #endif | 
| Chris@175 | 542 } | 
| Chris@175 | 543 | 
| Chris@175 | 544 void | 
| Chris@147 | 545 WaveFileModel::RangeCacheFillThread::run() | 
| Chris@147 | 546 { | 
| Chris@929 | 547     int cacheBlockSize[2]; | 
| Chris@179 | 548     cacheBlockSize[0] = (1 << m_model.m_zoomConstraint.getMinCachePower()); | 
| Chris@1038 | 549     cacheBlockSize[1] = (int((1 << m_model.m_zoomConstraint.getMinCachePower()) * | 
| Chris@608 | 550                                         sqrt(2.) + 0.01)); | 
| Chris@147 | 551 | 
| Chris@1038 | 552     sv_frame_t frame = 0; | 
| Chris@1053 | 553     const sv_frame_t readBlockSize = 16384; | 
| Chris@1096 | 554     vector<float> block; | 
| Chris@147 | 555 | 
| Chris@147 | 556     if (!m_model.isOK()) return; | 
| Chris@147 | 557 | 
| Chris@929 | 558     int channels = m_model.getChannelCount(); | 
| Chris@187 | 559     bool updating = m_model.m_reader->isUpdating(); | 
| Chris@187 | 560 | 
| Chris@187 | 561     if (updating) { | 
| Chris@187 | 562         while (channels == 0 && !m_model.m_exiting) { | 
| Chris@690 | 563 //            SVDEBUG << "WaveFileModel::fill: Waiting for channels..." << endl; | 
| Chris@187 | 564             sleep(1); | 
| Chris@187 | 565             channels = m_model.getChannelCount(); | 
| Chris@187 | 566         } | 
| Chris@187 | 567     } | 
| Chris@147 | 568 | 
| Chris@147 | 569     Range *range = new Range[2 * channels]; | 
| Chris@410 | 570     float *means = new float[2 * channels]; | 
| Chris@929 | 571     int count[2]; | 
| Chris@147 | 572     count[0] = count[1] = 0; | 
| Chris@411 | 573     for (int i = 0; i < 2 * channels; ++i) { | 
| Chris@411 | 574         means[i] = 0.f; | 
| Chris@411 | 575     } | 
| Chris@176 | 576 | 
| Chris@176 | 577     bool first = true; | 
| Chris@176 | 578 | 
| Chris@176 | 579     while (first || updating) { | 
| Chris@176 | 580 | 
| Chris@176 | 581         updating = m_model.m_reader->isUpdating(); | 
| Chris@187 | 582         m_frameCount = m_model.getFrameCount(); | 
| Chris@175 | 583 | 
| Chris@690 | 584 //        SVDEBUG << "WaveFileModel::fill: frame = " << frame << ", count = " << m_frameCount << endl; | 
| Chris@147 | 585 | 
| Chris@176 | 586         while (frame < m_frameCount) { | 
| Chris@147 | 587 | 
| Chris@690 | 588 //            SVDEBUG << "WaveFileModel::fill inner loop: frame = " << frame << ", count = " << m_frameCount << ", blocksize " << readBlockSize << endl; | 
| Chris@265 | 589 | 
| Chris@176 | 590             if (updating && (frame + readBlockSize > m_frameCount)) break; | 
| Chris@176 | 591 | 
| Chris@1041 | 592             block = m_model.m_reader->getInterleavedFrames(frame, readBlockSize); | 
| Chris@176 | 593 | 
| Chris@843 | 594 //            cerr << "block is " << block.size() << endl; | 
| Chris@265 | 595 | 
| Chris@1038 | 596             for (sv_frame_t i = 0; i < readBlockSize; ++i) { | 
| Chris@147 | 597 | 
| Chris@929 | 598                 if (channels * i + channels > (int)block.size()) break; | 
| Chris@232 | 599 | 
| Chris@411 | 600                 for (int ch = 0; ch < channels; ++ch) { | 
| Chris@147 | 601 | 
| Chris@1038 | 602                     sv_frame_t index = channels * i + ch; | 
| Chris@176 | 603                     float sample = block[index]; | 
| Chris@176 | 604 | 
| Chris@1053 | 605                     for (int cacheType = 0; cacheType < 2; ++cacheType) { // cache type | 
| Chris@176 | 606 | 
| Chris@1053 | 607                         sv_frame_t rangeIndex = ch * 2 + cacheType; | 
| Chris@1053 | 608                         range[rangeIndex].sample(sample); | 
| Chris@410 | 609                         means[rangeIndex] += fabsf(sample); | 
| Chris@176 | 610                     } | 
| Chris@176 | 611                 } | 
| Chris@1042 | 612 | 
| Chris@1042 | 613                 //!!! this looks like a ludicrous way to do synchronisation | 
| Chris@176 | 614                 QMutexLocker locker(&m_model.m_mutex); | 
| Chris@232 | 615 | 
| Chris@1053 | 616                 for (int cacheType = 0; cacheType < 2; ++cacheType) { | 
| Chris@232 | 617 | 
| Chris@1053 | 618                     if (++count[cacheType] == cacheBlockSize[cacheType]) { | 
| Chris@410 | 619 | 
| Chris@929 | 620                         for (int ch = 0; ch < int(channels); ++ch) { | 
| Chris@1053 | 621                             int rangeIndex = ch * 2 + cacheType; | 
| Chris@1053 | 622                             means[rangeIndex] = means[rangeIndex] / float(count[cacheType]); | 
| Chris@410 | 623                             range[rangeIndex].setAbsmean(means[rangeIndex]); | 
| Chris@1053 | 624                             m_model.m_cache[cacheType].push_back(range[rangeIndex]); | 
| Chris@176 | 625                             range[rangeIndex] = Range(); | 
| Chris@411 | 626                             means[rangeIndex] = 0.f; | 
| Chris@176 | 627                         } | 
| Chris@232 | 628 | 
| Chris@1053 | 629                         count[cacheType] = 0; | 
| Chris@176 | 630                     } | 
| Chris@176 | 631                 } | 
| Chris@147 | 632 | 
| Chris@176 | 633                 ++frame; | 
| Chris@147 | 634             } | 
| Chris@147 | 635 | 
| Chris@176 | 636             if (m_model.m_exiting) break; | 
| Chris@176 | 637 | 
| Chris@176 | 638             m_fillExtent = frame; | 
| Chris@147 | 639         } | 
| Chris@147 | 640 | 
| Chris@843 | 641 //        cerr << "WaveFileModel: inner loop ended" << endl; | 
| Chris@265 | 642 | 
| Chris@176 | 643         first = false; | 
| Chris@177 | 644         if (m_model.m_exiting) break; | 
| Chris@187 | 645         if (updating) { | 
| Chris@843 | 646 //            cerr << "sleeping..." << endl; | 
| Chris@187 | 647             sleep(1); | 
| Chris@187 | 648         } | 
| Chris@147 | 649     } | 
| Chris@147 | 650 | 
| Chris@177 | 651     if (!m_model.m_exiting) { | 
| Chris@177 | 652 | 
| Chris@177 | 653         QMutexLocker locker(&m_model.m_mutex); | 
| Chris@232 | 654 | 
| Chris@1053 | 655         for (int cacheType = 0; cacheType < 2; ++cacheType) { | 
| Chris@232 | 656 | 
| Chris@1053 | 657             if (count[cacheType] > 0) { | 
| Chris@232 | 658 | 
| Chris@929 | 659                 for (int ch = 0; ch < int(channels); ++ch) { | 
| Chris@1053 | 660                     int rangeIndex = ch * 2 + cacheType; | 
| Chris@1053 | 661                     means[rangeIndex] = means[rangeIndex] / float(count[cacheType]); | 
| Chris@410 | 662                     range[rangeIndex].setAbsmean(means[rangeIndex]); | 
| Chris@1053 | 663                     m_model.m_cache[cacheType].push_back(range[rangeIndex]); | 
| Chris@177 | 664                     range[rangeIndex] = Range(); | 
| Chris@411 | 665                     means[rangeIndex] = 0.f; | 
| Chris@177 | 666                 } | 
| Chris@232 | 667 | 
| Chris@1053 | 668                 count[cacheType] = 0; | 
| Chris@147 | 669             } | 
| Chris@177 | 670 | 
| Chris@1053 | 671             const Range &rr = *m_model.m_cache[cacheType].begin(); | 
| Chris@1053 | 672             MUNLOCK(&rr, m_model.m_cache[cacheType].capacity() * sizeof(Range)); | 
| Chris@147 | 673         } | 
| Chris@147 | 674     } | 
| Chris@147 | 675 | 
| Chris@410 | 676     delete[] means; | 
| Chris@147 | 677     delete[] range; | 
| Chris@147 | 678 | 
| Chris@175 | 679     m_fillExtent = m_frameCount; | 
| Chris@236 | 680 | 
| Chris@236 | 681 #ifdef DEBUG_WAVE_FILE_MODEL | 
| Chris@1053 | 682     for (int cacheType = 0; cacheType < 2; ++cacheType) { | 
| Chris@1053 | 683         cerr << "Cache type " << cacheType << " now contains " << m_model.m_cache[cacheType].size() << " ranges" << endl; | 
| Chris@236 | 684     } | 
| Chris@236 | 685 #endif | 
| Chris@147 | 686 } | 
| Chris@147 | 687 | 
| Chris@163 | 688 void | 
| Chris@163 | 689 WaveFileModel::toXml(QTextStream &out, | 
| Chris@163 | 690                      QString indent, | 
| Chris@163 | 691                      QString extraAttributes) const | 
| Chris@163 | 692 { | 
| Chris@163 | 693     Model::toXml(out, indent, | 
| Chris@163 | 694                  QString("type=\"wavefile\" file=\"%1\" %2") | 
| Chris@279 | 695                  .arg(encodeEntities(m_path)).arg(extraAttributes)); | 
| Chris@163 | 696 } | 
| Chris@163 | 697 | 
| Chris@147 | 698 |