Mercurial > hg > svcore
comparison data/model/WaveFileModel.cpp @ 147:3a13b0d4934e
* Reorganising code base. This revision will not compile.
author | Chris Cannam |
---|---|
date | Mon, 31 Jul 2006 11:44:37 +0000 |
parents | |
children | 4b2ea82fd0ed |
comparison
equal
deleted
inserted
replaced
146:f90fad823cea | 147:3a13b0d4934e |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 Sonic Visualiser | |
5 An audio file viewer and annotation editor. | |
6 Centre for Digital Music, Queen Mary, University of London. | |
7 This file copyright 2006 Chris Cannam. | |
8 | |
9 This program is free software; you can redistribute it and/or | |
10 modify it under the terms of the GNU General Public License as | |
11 published by the Free Software Foundation; either version 2 of the | |
12 License, or (at your option) any later version. See the file | |
13 COPYING included with this distribution for more information. | |
14 */ | |
15 | |
16 #include "model/WaveFileModel.h" | |
17 | |
18 #include "fileio/AudioFileReader.h" | |
19 #include "fileio/AudioFileReaderFactory.h" | |
20 | |
21 #include "base/System.h" | |
22 | |
23 #include <QMessageBox> | |
24 #include <QFileInfo> | |
25 | |
26 #include <iostream> | |
27 #include <unistd.h> | |
28 #include <math.h> | |
29 #include <sndfile.h> | |
30 | |
31 #include <cassert> | |
32 | |
33 using std::cerr; | |
34 using std::endl; | |
35 | |
36 WaveFileModel::WaveFileModel(QString path) : | |
37 m_path(path), | |
38 m_fillThread(0), | |
39 m_updateTimer(0), | |
40 m_lastFillExtent(0), | |
41 m_exiting(false) | |
42 { | |
43 m_reader = AudioFileReaderFactory::createReader(path); | |
44 setObjectName(QFileInfo(path).fileName()); | |
45 if (isOK()) fillCache(); | |
46 } | |
47 | |
48 WaveFileModel::~WaveFileModel() | |
49 { | |
50 m_exiting = true; | |
51 if (m_fillThread) m_fillThread->wait(); | |
52 delete m_reader; | |
53 m_reader = 0; | |
54 } | |
55 | |
56 bool | |
57 WaveFileModel::isOK() const | |
58 { | |
59 return m_reader && m_reader->isOK(); | |
60 } | |
61 | |
62 bool | |
63 WaveFileModel::isReady(int *completion) const | |
64 { | |
65 bool ready = (isOK() && (m_fillThread == 0)); | |
66 double c = double(m_lastFillExtent) / double(getEndFrame() - getStartFrame()); | |
67 if (completion) *completion = int(c * 100.0 + 0.01); | |
68 return ready; | |
69 } | |
70 | |
71 Model * | |
72 WaveFileModel::clone() const | |
73 { | |
74 WaveFileModel *model = new WaveFileModel(m_path); | |
75 return model; | |
76 } | |
77 | |
78 size_t | |
79 WaveFileModel::getFrameCount() const | |
80 { | |
81 if (!m_reader) return 0; | |
82 return m_reader->getFrameCount(); | |
83 } | |
84 | |
85 size_t | |
86 WaveFileModel::getChannelCount() const | |
87 { | |
88 if (!m_reader) return 0; | |
89 return m_reader->getChannelCount(); | |
90 } | |
91 | |
92 size_t | |
93 WaveFileModel::getSampleRate() const | |
94 { | |
95 if (!m_reader) return 0; | |
96 return m_reader->getSampleRate(); | |
97 } | |
98 | |
99 size_t | |
100 WaveFileModel::getValues(int channel, size_t start, size_t end, | |
101 float *buffer) const | |
102 { | |
103 // Always read these directly from the file. | |
104 // This is used for e.g. audio playback. | |
105 // Could be much more efficient (although compiler optimisation will help) | |
106 | |
107 if (end < start) { | |
108 std::cerr << "ERROR: WaveFileModel::getValues[float]: end < start (" | |
109 << end << " < " << start << ")" << std::endl; | |
110 assert(end >= start); | |
111 } | |
112 | |
113 if (!m_reader || !m_reader->isOK()) return 0; | |
114 | |
115 SampleBlock frames; | |
116 m_reader->getInterleavedFrames(start, end - start, frames); | |
117 | |
118 size_t i = 0; | |
119 | |
120 int ch0 = channel, ch1 = channel, channels = getChannelCount(); | |
121 if (channel == -1) { | |
122 ch0 = 0; | |
123 ch1 = channels - 1; | |
124 } | |
125 | |
126 while (i < end - start) { | |
127 | |
128 buffer[i] = 0.0; | |
129 | |
130 for (int ch = ch0; ch <= ch1; ++ch) { | |
131 | |
132 size_t index = i * channels + ch; | |
133 if (index >= frames.size()) break; | |
134 | |
135 float sample = frames[index]; | |
136 buffer[i] += sample; | |
137 } | |
138 | |
139 ++i; | |
140 } | |
141 | |
142 return i; | |
143 } | |
144 | |
145 size_t | |
146 WaveFileModel::getValues(int channel, size_t start, size_t end, | |
147 double *buffer) const | |
148 { | |
149 if (end < start) { | |
150 std::cerr << "ERROR: WaveFileModel::getValues[double]: end < start (" | |
151 << end << " < " << start << ")" << std::endl; | |
152 assert(end >= start); | |
153 } | |
154 | |
155 if (!m_reader || !m_reader->isOK()) return 0; | |
156 | |
157 SampleBlock frames; | |
158 m_reader->getInterleavedFrames(start, end - start, frames); | |
159 | |
160 size_t i = 0; | |
161 | |
162 int ch0 = channel, ch1 = channel, channels = getChannelCount(); | |
163 if (channel == -1) { | |
164 ch0 = 0; | |
165 ch1 = channels - 1; | |
166 } | |
167 | |
168 while (i < end - start) { | |
169 | |
170 buffer[i] = 0.0; | |
171 | |
172 for (int ch = ch0; ch <= ch1; ++ch) { | |
173 | |
174 size_t index = i * channels + ch; | |
175 if (index >= frames.size()) break; | |
176 | |
177 float sample = frames[index]; | |
178 buffer[i] += sample; | |
179 } | |
180 | |
181 ++i; | |
182 } | |
183 | |
184 return i; | |
185 } | |
186 | |
187 WaveFileModel::RangeBlock | |
188 WaveFileModel::getRanges(size_t channel, size_t start, size_t end, | |
189 size_t &blockSize) const | |
190 { | |
191 RangeBlock ranges; | |
192 if (!isOK()) return ranges; | |
193 | |
194 if (end <= start) { | |
195 std::cerr << "WARNING: Internal error: end <= start in WaveFileModel::getRanges (end = " << end << ", start = " << start << ", blocksize = " << blockSize << ")" << std::endl; | |
196 return ranges; | |
197 } | |
198 | |
199 int cacheType = 0; | |
200 int power = getMinCachePower(); | |
201 blockSize = getNearestBlockSize(blockSize, cacheType, power, | |
202 ZoomConstraint::RoundUp); | |
203 | |
204 size_t channels = getChannelCount(); | |
205 | |
206 if (cacheType != 0 && cacheType != 1) { | |
207 | |
208 // We need to read directly from the file. We haven't got | |
209 // this cached. Hope the requested area is small. This is | |
210 // not optimal -- we'll end up reading the same frames twice | |
211 // for stereo files, in two separate calls to this method. | |
212 // We could fairly trivially handle this for most cases that | |
213 // matter by putting a single cache in getInterleavedFrames | |
214 // for short queries. | |
215 | |
216 SampleBlock frames; | |
217 m_reader->getInterleavedFrames(start, end - start, frames); | |
218 float max = 0.0, min = 0.0, total = 0.0; | |
219 size_t i = 0, count = 0; | |
220 | |
221 while (i < end - start) { | |
222 | |
223 size_t index = i * channels + channel; | |
224 if (index >= frames.size()) break; | |
225 | |
226 float sample = frames[index]; | |
227 if (sample > max || count == 0) max = sample; | |
228 if (sample < min || count == 0) min = sample; | |
229 total += fabsf(sample); | |
230 | |
231 ++i; | |
232 ++count; | |
233 | |
234 if (count == blockSize) { | |
235 ranges.push_back(Range(min, max, total / count)); | |
236 min = max = total = 0.0f; | |
237 count = 0; | |
238 } | |
239 } | |
240 | |
241 if (count > 0) { | |
242 ranges.push_back(Range(min, max, total / count)); | |
243 } | |
244 | |
245 return ranges; | |
246 | |
247 } else { | |
248 | |
249 QMutexLocker locker(&m_mutex); | |
250 | |
251 const RangeBlock &cache = m_cache[cacheType]; | |
252 | |
253 size_t cacheBlock, div; | |
254 | |
255 if (cacheType == 0) { | |
256 cacheBlock = (1 << getMinCachePower()); | |
257 div = (1 << power) / cacheBlock; | |
258 } else { | |
259 cacheBlock = ((unsigned int)((1 << getMinCachePower()) * sqrt(2) + 0.01)); | |
260 div = ((unsigned int)((1 << power) * sqrt(2) + 0.01)) / cacheBlock; | |
261 } | |
262 | |
263 size_t startIndex = start / cacheBlock; | |
264 size_t endIndex = end / cacheBlock; | |
265 | |
266 float max = 0.0, min = 0.0, total = 0.0; | |
267 size_t i = 0, count = 0; | |
268 | |
269 //cerr << "blockSize is " << blockSize << ", cacheBlock " << cacheBlock << ", start " << start << ", end " << end << ", power is " << power << ", div is " << div << ", startIndex " << startIndex << ", endIndex " << endIndex << endl; | |
270 | |
271 for (i = 0; i < endIndex - startIndex; ) { | |
272 | |
273 size_t index = (i + startIndex) * channels + channel; | |
274 if (index >= cache.size()) break; | |
275 | |
276 const Range &range = cache[index]; | |
277 if (range.max > max || count == 0) max = range.max; | |
278 if (range.min < min || count == 0) min = range.min; | |
279 total += range.absmean; | |
280 | |
281 ++i; | |
282 ++count; | |
283 | |
284 if (count == div) { | |
285 ranges.push_back(Range(min, max, total / count)); | |
286 min = max = total = 0.0f; | |
287 count = 0; | |
288 } | |
289 } | |
290 | |
291 if (count > 0) { | |
292 ranges.push_back(Range(min, max, total / count)); | |
293 } | |
294 } | |
295 | |
296 //cerr << "returning " << ranges.size() << " ranges" << endl; | |
297 return ranges; | |
298 } | |
299 | |
300 WaveFileModel::Range | |
301 WaveFileModel::getRange(size_t channel, size_t start, size_t end) const | |
302 { | |
303 Range range; | |
304 if (!isOK()) return range; | |
305 | |
306 if (end <= start) { | |
307 std::cerr << "WARNING: Internal error: end <= start in WaveFileModel::getRange (end = " << end << ", start = " << start << ")" << std::endl; | |
308 return range; | |
309 } | |
310 | |
311 size_t blockSize; | |
312 for (blockSize = 1; blockSize <= end - start; blockSize *= 2); | |
313 blockSize /= 2; | |
314 | |
315 bool first = false; | |
316 | |
317 size_t blockStart = (start / blockSize) * blockSize; | |
318 size_t blockEnd = (end / blockSize) * blockSize; | |
319 | |
320 if (blockStart < start) blockStart += blockSize; | |
321 | |
322 if (blockEnd > blockStart) { | |
323 RangeBlock ranges = getRanges(channel, blockStart, blockEnd, blockSize); | |
324 for (size_t i = 0; i < ranges.size(); ++i) { | |
325 if (first || ranges[i].min < range.min) range.min = ranges[i].min; | |
326 if (first || ranges[i].max > range.max) range.max = ranges[i].max; | |
327 if (first || ranges[i].absmean < range.absmean) range.absmean = ranges[i].absmean; | |
328 first = false; | |
329 } | |
330 } | |
331 | |
332 if (blockStart > start) { | |
333 Range startRange = getRange(channel, start, blockStart); | |
334 range.min = std::min(range.min, startRange.min); | |
335 range.max = std::max(range.max, startRange.max); | |
336 range.absmean = std::min(range.absmean, startRange.absmean); | |
337 } | |
338 | |
339 if (blockEnd < end) { | |
340 Range endRange = getRange(channel, blockEnd, end); | |
341 range.min = std::min(range.min, endRange.min); | |
342 range.max = std::max(range.max, endRange.max); | |
343 range.absmean = std::min(range.absmean, endRange.absmean); | |
344 } | |
345 | |
346 return range; | |
347 } | |
348 | |
349 void | |
350 WaveFileModel::fillCache() | |
351 { | |
352 m_mutex.lock(); | |
353 m_updateTimer = new QTimer(this); | |
354 connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut())); | |
355 m_updateTimer->start(100); | |
356 m_fillThread = new RangeCacheFillThread(*this); | |
357 connect(m_fillThread, SIGNAL(finished()), this, SLOT(cacheFilled())); | |
358 m_mutex.unlock(); | |
359 m_fillThread->start(); | |
360 } | |
361 | |
362 void | |
363 WaveFileModel::fillTimerTimedOut() | |
364 { | |
365 if (m_fillThread) { | |
366 size_t fillExtent = m_fillThread->getFillExtent(); | |
367 if (fillExtent > m_lastFillExtent) { | |
368 emit modelChanged(m_lastFillExtent, fillExtent); | |
369 m_lastFillExtent = fillExtent; | |
370 } | |
371 } else { | |
372 emit modelChanged(); | |
373 } | |
374 } | |
375 | |
376 void | |
377 WaveFileModel::cacheFilled() | |
378 { | |
379 m_mutex.lock(); | |
380 delete m_fillThread; | |
381 m_fillThread = 0; | |
382 delete m_updateTimer; | |
383 m_updateTimer = 0; | |
384 m_mutex.unlock(); | |
385 emit modelChanged(); | |
386 // cerr << "WaveFileModel::cacheFilled" << endl; | |
387 } | |
388 | |
389 void | |
390 WaveFileModel::RangeCacheFillThread::run() | |
391 { | |
392 size_t cacheBlockSize[2]; | |
393 cacheBlockSize[0] = (1 << m_model.getMinCachePower()); | |
394 cacheBlockSize[1] = ((unsigned int)((1 << m_model.getMinCachePower()) * | |
395 sqrt(2) + 0.01)); | |
396 | |
397 size_t frame = 0; | |
398 size_t readBlockSize = 16384; | |
399 SampleBlock block; | |
400 | |
401 if (!m_model.isOK()) return; | |
402 | |
403 size_t channels = m_model.getChannelCount(); | |
404 size_t frames = m_model.getFrameCount(); | |
405 | |
406 Range *range = new Range[2 * channels]; | |
407 size_t count[2]; | |
408 count[0] = count[1] = 0; | |
409 | |
410 while (frame < frames) { | |
411 | |
412 m_model.m_reader->getInterleavedFrames(frame, readBlockSize, block); | |
413 | |
414 for (size_t i = 0; i < readBlockSize; ++i) { | |
415 | |
416 for (size_t ch = 0; ch < size_t(channels); ++ch) { | |
417 | |
418 size_t index = channels * i + ch; | |
419 if (index >= block.size()) continue; | |
420 float sample = block[index]; | |
421 | |
422 for (size_t ct = 0; ct < 2; ++ct) { | |
423 | |
424 size_t rangeIndex = ch * 2 + ct; | |
425 | |
426 if (sample > range[rangeIndex].max || count[ct] == 0) { | |
427 range[rangeIndex].max = sample; | |
428 } | |
429 if (sample < range[rangeIndex].min || count[ct] == 0) { | |
430 range[rangeIndex].min = sample; | |
431 } | |
432 range[rangeIndex].absmean += fabsf(sample); | |
433 } | |
434 } | |
435 | |
436 QMutexLocker locker(&m_model.m_mutex); | |
437 for (size_t ct = 0; ct < 2; ++ct) { | |
438 if (++count[ct] == cacheBlockSize[ct]) { | |
439 for (size_t ch = 0; ch < size_t(channels); ++ch) { | |
440 size_t rangeIndex = ch * 2 + ct; | |
441 range[rangeIndex].absmean /= count[ct]; | |
442 m_model.m_cache[ct].push_back(range[rangeIndex]); | |
443 range[rangeIndex] = Range(); | |
444 } | |
445 count[ct] = 0; | |
446 } | |
447 } | |
448 | |
449 ++frame; | |
450 } | |
451 | |
452 if (m_model.m_exiting) break; | |
453 | |
454 m_fillExtent = frame; | |
455 } | |
456 | |
457 QMutexLocker locker(&m_model.m_mutex); | |
458 for (size_t ct = 0; ct < 2; ++ct) { | |
459 if (count[ct] > 0) { | |
460 for (size_t ch = 0; ch < size_t(channels); ++ch) { | |
461 size_t rangeIndex = ch * 2 + ct; | |
462 range[rangeIndex].absmean /= count[ct]; | |
463 m_model.m_cache[ct].push_back(range[rangeIndex]); | |
464 range[rangeIndex] = Range(); | |
465 } | |
466 count[ct] = 0; | |
467 } | |
468 | |
469 const Range &rr = *m_model.m_cache[ct].begin(); | |
470 MUNLOCK(&rr, m_model.m_cache[ct].capacity() * sizeof(Range)); | |
471 } | |
472 | |
473 delete[] range; | |
474 | |
475 m_fillExtent = frames; | |
476 | |
477 // for (size_t ct = 0; ct < 2; ++ct) { | |
478 // cerr << "Cache type " << ct << " now contains " << m_model.m_cache[ct].size() << " ranges" << endl; | |
479 // } | |
480 } | |
481 | |
482 QString | |
483 WaveFileModel::toXmlString(QString indent, | |
484 QString extraAttributes) const | |
485 { | |
486 return Model::toXmlString(indent, | |
487 QString("type=\"wavefile\" file=\"%1\" %2") | |
488 .arg(m_path).arg(extraAttributes)); | |
489 } | |
490 | |
491 | |
492 #ifdef INCLUDE_MOCFILES | |
493 #ifdef INCLUDE_MOCFILES | |
494 #include "WaveFileModel.moc.cpp" | |
495 #endif | |
496 #endif | |
497 |