Chris@87
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@87
|
2
|
Chris@87
|
3 /*
|
Chris@87
|
4 Sonic Visualiser
|
Chris@87
|
5 An audio file viewer and annotation editor.
|
Chris@87
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@87
|
7 This file copyright 2006 Chris Cannam.
|
Chris@87
|
8
|
Chris@87
|
9 This program is free software; you can redistribute it and/or
|
Chris@87
|
10 modify it under the terms of the GNU General Public License as
|
Chris@87
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@87
|
12 License, or (at your option) any later version. See the file
|
Chris@87
|
13 COPYING included with this distribution for more information.
|
Chris@87
|
14 */
|
Chris@87
|
15
|
Chris@87
|
16 #include "MatrixFileCache.h"
|
Chris@87
|
17 #include "base/TempDirectory.h"
|
Chris@90
|
18 #include "base/System.h"
|
Chris@87
|
19
|
Chris@87
|
20 #include <sys/types.h>
|
Chris@87
|
21 #include <sys/stat.h>
|
Chris@87
|
22 #include <fcntl.h>
|
Chris@87
|
23 #include <unistd.h>
|
Chris@87
|
24
|
Chris@87
|
25 #include <iostream>
|
Chris@87
|
26
|
Chris@87
|
27 #include <cstdio>
|
Chris@87
|
28
|
Chris@87
|
29 #include <QFileInfo>
|
Chris@91
|
30 #include <QFile>
|
Chris@87
|
31 #include <QDir>
|
Chris@87
|
32
|
Chris@91
|
33 std::map<QString, int> MatrixFileCache::m_refcount;
|
Chris@91
|
34 QMutex MatrixFileCache::m_refcountMutex;
|
Chris@91
|
35
|
Chris@90
|
36 //!!! This class is a work in progress -- it does only as much as we
|
Chris@90
|
37 // need for the current SpectrogramLayer. Slated for substantial
|
Chris@90
|
38 // refactoring and extension.
|
Chris@90
|
39
|
Chris@87
|
40 MatrixFileCache::MatrixFileCache(QString fileBase, Mode mode) :
|
Chris@87
|
41 m_fd(-1),
|
Chris@87
|
42 m_mode(mode),
|
Chris@87
|
43 m_width(0),
|
Chris@87
|
44 m_height(0),
|
Chris@90
|
45 m_headerSize(2 * sizeof(size_t)),
|
Chris@91
|
46 m_autoRegionWidth(256),
|
Chris@90
|
47 m_off(-1),
|
Chris@87
|
48 m_rx(0),
|
Chris@87
|
49 m_rw(0),
|
Chris@90
|
50 m_userRegion(false),
|
Chris@93
|
51 m_region(0)
|
Chris@87
|
52 {
|
Chris@90
|
53 // Ensure header size is a multiple of the size of our data (for
|
Chris@90
|
54 // alignment purposes)
|
Chris@90
|
55 size_t hs = ((m_headerSize / sizeof(float)) * sizeof(float));
|
Chris@90
|
56 if (hs != m_headerSize) m_headerSize = hs + sizeof(float);
|
Chris@90
|
57
|
Chris@87
|
58 QDir tempDir(TempDirectory::instance()->getPath());
|
Chris@87
|
59 QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase)));
|
Chris@87
|
60 bool newFile = !QFileInfo(fileName).exists();
|
Chris@87
|
61
|
Chris@87
|
62 if (newFile && mode == ReadOnly) {
|
Chris@87
|
63 std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: Read-only mode "
|
Chris@87
|
64 << "specified, but cache file does not exist" << std::endl;
|
Chris@87
|
65 return;
|
Chris@87
|
66 }
|
Chris@87
|
67
|
Chris@87
|
68 int flags = 0;
|
Chris@87
|
69 mode_t fmode = S_IRUSR | S_IWUSR;
|
Chris@87
|
70
|
Chris@87
|
71 if (mode == ReadWrite) {
|
Chris@87
|
72 flags = O_RDWR | O_CREAT;
|
Chris@87
|
73 } else {
|
Chris@87
|
74 flags = O_RDONLY;
|
Chris@87
|
75 }
|
Chris@87
|
76
|
Chris@90
|
77 if ((m_fd = ::open(fileName.toLocal8Bit(), flags, fmode)) < 0) {
|
Chris@87
|
78 ::perror("Open failed");
|
Chris@87
|
79 std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: "
|
Chris@87
|
80 << "Failed to open cache file \""
|
Chris@87
|
81 << fileName.toStdString() << "\"";
|
Chris@87
|
82 if (mode == ReadWrite) std::cerr << " for writing";
|
Chris@87
|
83 std::cerr << std::endl;
|
Chris@91
|
84 return;
|
Chris@87
|
85 }
|
Chris@87
|
86
|
Chris@87
|
87 if (newFile) {
|
Chris@87
|
88 resize(0, 0); // write header
|
Chris@87
|
89 } else {
|
Chris@87
|
90 size_t header[2];
|
Chris@90
|
91 if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) {
|
Chris@87
|
92 perror("Read failed");
|
Chris@87
|
93 std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: "
|
Chris@90
|
94 << "Failed to read header (fd " << m_fd << ", file \""
|
Chris@90
|
95 << fileName.toStdString() << "\")" << std::endl;
|
Chris@87
|
96 return;
|
Chris@87
|
97 }
|
Chris@87
|
98 m_width = header[0];
|
Chris@87
|
99 m_height = header[1];
|
Chris@87
|
100 seekTo(0, 0);
|
Chris@87
|
101 }
|
Chris@87
|
102
|
Chris@91
|
103 m_fileName = fileName;
|
Chris@91
|
104 QMutexLocker locker(&m_refcountMutex);
|
Chris@91
|
105 ++m_refcount[fileName];
|
Chris@91
|
106
|
Chris@90
|
107 std::cerr << "MatrixFileCache::MatrixFileCache: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl;
|
Chris@87
|
108
|
Chris@87
|
109 }
|
Chris@87
|
110
|
Chris@87
|
111 MatrixFileCache::~MatrixFileCache()
|
Chris@87
|
112 {
|
Chris@90
|
113 if (m_rw > 0) {
|
Chris@93
|
114 delete[] m_region;
|
Chris@90
|
115 }
|
Chris@90
|
116
|
Chris@87
|
117 if (m_fd >= 0) {
|
Chris@87
|
118 if (::close(m_fd) < 0) {
|
Chris@87
|
119 ::perror("MatrixFileCache::~MatrixFileCache: close failed");
|
Chris@87
|
120 }
|
Chris@87
|
121 }
|
Chris@90
|
122
|
Chris@91
|
123 if (m_fileName != "") {
|
Chris@91
|
124 QMutexLocker locker(&m_refcountMutex);
|
Chris@91
|
125 if (--m_refcount[m_fileName] == 0) {
|
Chris@91
|
126 if (!QFile(m_fileName).remove()) {
|
Chris@91
|
127 std::cerr << "WARNING: MatrixFileCache::~MatrixFileCache: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl;
|
Chris@91
|
128 } else {
|
Chris@91
|
129 std::cerr << "deleted " << m_fileName.toStdString() << std::endl;
|
Chris@91
|
130 }
|
Chris@91
|
131 }
|
Chris@91
|
132 }
|
Chris@87
|
133 }
|
Chris@87
|
134
|
Chris@87
|
135 size_t
|
Chris@87
|
136 MatrixFileCache::getWidth() const
|
Chris@87
|
137 {
|
Chris@87
|
138 return m_width;
|
Chris@87
|
139 }
|
Chris@87
|
140
|
Chris@87
|
141 size_t
|
Chris@87
|
142 MatrixFileCache::getHeight() const
|
Chris@87
|
143 {
|
Chris@87
|
144 return m_height;
|
Chris@87
|
145 }
|
Chris@87
|
146
|
Chris@87
|
147 void
|
Chris@87
|
148 MatrixFileCache::resize(size_t w, size_t h)
|
Chris@87
|
149 {
|
Chris@87
|
150 if (m_mode != ReadWrite) {
|
Chris@87
|
151 std::cerr << "ERROR: MatrixFileCache::resize called on read-only cache"
|
Chris@87
|
152 << std::endl;
|
Chris@87
|
153 return;
|
Chris@87
|
154 }
|
Chris@87
|
155
|
Chris@87
|
156 off_t off = m_headerSize + (w * h * sizeof(float));
|
Chris@87
|
157
|
Chris@87
|
158 if (w * h > m_width * m_height) {
|
Chris@87
|
159
|
Chris@93
|
160 /*!!!
|
Chris@90
|
161 // If we're going to mmap the file, we need to ensure it's long
|
Chris@90
|
162 // enough beforehand
|
Chris@90
|
163
|
Chris@90
|
164 if (m_preferMmap) {
|
Chris@90
|
165
|
Chris@90
|
166 if (::lseek(m_fd, off - sizeof(float), SEEK_SET) == (off_t)-1) {
|
Chris@90
|
167 ::perror("Seek failed");
|
Chris@90
|
168 std::cerr << "ERROR: MatrixFileCache::resize(" << w << ", "
|
Chris@90
|
169 << h << "): seek failed, cannot resize" << std::endl;
|
Chris@90
|
170 return;
|
Chris@90
|
171 }
|
Chris@90
|
172
|
Chris@90
|
173 // guess this requires efficient support for sparse files
|
Chris@90
|
174
|
Chris@90
|
175 float f(0);
|
Chris@90
|
176 if (::write(m_fd, &f, sizeof(float)) != sizeof(float)) {
|
Chris@90
|
177 ::perror("WARNING: MatrixFileCache::resize: write failed");
|
Chris@90
|
178 }
|
Chris@87
|
179 }
|
Chris@93
|
180 */
|
Chris@87
|
181 } else {
|
Chris@87
|
182
|
Chris@87
|
183 if (::ftruncate(m_fd, off) < 0) {
|
Chris@90
|
184 ::perror("WARNING: MatrixFileCache::resize: ftruncate failed");
|
Chris@87
|
185 }
|
Chris@87
|
186 }
|
Chris@87
|
187
|
Chris@87
|
188 m_width = 0;
|
Chris@87
|
189 m_height = 0;
|
Chris@87
|
190 m_off = 0;
|
Chris@87
|
191
|
Chris@87
|
192 if (::lseek(m_fd, 0, SEEK_SET) == (off_t)-1) {
|
Chris@87
|
193 ::perror("ERROR: MatrixFileCache::resize: Seek to write header failed");
|
Chris@87
|
194 return;
|
Chris@87
|
195 }
|
Chris@87
|
196
|
Chris@87
|
197 size_t header[2];
|
Chris@87
|
198 header[0] = w;
|
Chris@87
|
199 header[1] = h;
|
Chris@87
|
200 if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) {
|
Chris@87
|
201 ::perror("ERROR: MatrixFileCache::resize: Failed to write header");
|
Chris@87
|
202 return;
|
Chris@87
|
203 }
|
Chris@87
|
204
|
Chris@87
|
205 m_width = w;
|
Chris@87
|
206 m_height = h;
|
Chris@87
|
207
|
Chris@87
|
208 seekTo(0, 0);
|
Chris@87
|
209 }
|
Chris@87
|
210
|
Chris@87
|
211 void
|
Chris@87
|
212 MatrixFileCache::reset()
|
Chris@87
|
213 {
|
Chris@87
|
214 if (m_mode != ReadWrite) {
|
Chris@87
|
215 std::cerr << "ERROR: MatrixFileCache::reset called on read-only cache"
|
Chris@87
|
216 << std::endl;
|
Chris@87
|
217 return;
|
Chris@87
|
218 }
|
Chris@87
|
219
|
Chris@90
|
220 float *emptyCol = new float[m_height];
|
Chris@90
|
221 for (size_t y = 0; y < m_height; ++y) emptyCol[y] = 0.f;
|
Chris@90
|
222
|
Chris@90
|
223 seekTo(0, 0);
|
Chris@90
|
224 for (size_t x = 0; x < m_width; ++x) setColumnAt(x, emptyCol);
|
Chris@90
|
225
|
Chris@90
|
226 delete[] emptyCol;
|
Chris@87
|
227 }
|
Chris@87
|
228
|
Chris@87
|
229 void
|
Chris@90
|
230 MatrixFileCache::setRegionOfInterest(size_t x, size_t width)
|
Chris@87
|
231 {
|
Chris@90
|
232 setRegion(x, width, true);
|
Chris@90
|
233 }
|
Chris@90
|
234
|
Chris@90
|
235 void
|
Chris@90
|
236 MatrixFileCache::clearRegionOfInterest()
|
Chris@90
|
237 {
|
Chris@90
|
238 m_userRegion = false;
|
Chris@87
|
239 }
|
Chris@87
|
240
|
Chris@87
|
241 float
|
Chris@87
|
242 MatrixFileCache::getValueAt(size_t x, size_t y) const
|
Chris@87
|
243 {
|
Chris@87
|
244 if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) {
|
Chris@90
|
245 float *rp = getRegionPtr(x, y);
|
Chris@90
|
246 if (rp) return *rp;
|
Chris@90
|
247 } else if (!m_userRegion) {
|
Chris@90
|
248 if (autoSetRegion(x)) {
|
Chris@90
|
249 float *rp = getRegionPtr(x, y);
|
Chris@90
|
250 if (rp) return *rp;
|
Chris@91
|
251 else return 0.f;
|
Chris@90
|
252 }
|
Chris@87
|
253 }
|
Chris@87
|
254
|
Chris@87
|
255 if (!seekTo(x, y)) return 0.f;
|
Chris@87
|
256 float value;
|
Chris@90
|
257 ssize_t r = ::read(m_fd, &value, sizeof(float));
|
Chris@91
|
258 if (r < 0) {
|
Chris@91
|
259 ::perror("MatrixFileCache::getValueAt: Read failed");
|
Chris@91
|
260 }
|
Chris@90
|
261 if (r != sizeof(float)) {
|
Chris@90
|
262 value = 0.f;
|
Chris@87
|
263 }
|
Chris@90
|
264 if (r > 0) m_off += r;
|
Chris@87
|
265 return value;
|
Chris@87
|
266 }
|
Chris@87
|
267
|
Chris@87
|
268 void
|
Chris@87
|
269 MatrixFileCache::getColumnAt(size_t x, float *values) const
|
Chris@87
|
270 {
|
Chris@87
|
271 if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) {
|
Chris@90
|
272 float *rp = getRegionPtr(x, 0);
|
Chris@90
|
273 if (rp) {
|
Chris@90
|
274 for (size_t y = 0; y < m_height; ++y) {
|
Chris@90
|
275 values[y] = rp[y];
|
Chris@90
|
276 }
|
Chris@90
|
277 }
|
Chris@91
|
278 return;
|
Chris@90
|
279 } else if (!m_userRegion) {
|
Chris@90
|
280 if (autoSetRegion(x)) {
|
Chris@90
|
281 float *rp = getRegionPtr(x, 0);
|
Chris@90
|
282 if (rp) {
|
Chris@90
|
283 for (size_t y = 0; y < m_height; ++y) {
|
Chris@90
|
284 values[y] = rp[y];
|
Chris@90
|
285 }
|
Chris@90
|
286 return;
|
Chris@90
|
287 }
|
Chris@87
|
288 }
|
Chris@87
|
289 }
|
Chris@87
|
290
|
Chris@87
|
291 if (!seekTo(x, 0)) return;
|
Chris@90
|
292 ssize_t r = ::read(m_fd, values, m_height * sizeof(float));
|
Chris@91
|
293 if (r < 0) {
|
Chris@87
|
294 ::perror("MatrixFileCache::getColumnAt: read failed");
|
Chris@87
|
295 }
|
Chris@90
|
296 if (r > 0) m_off += r;
|
Chris@87
|
297 }
|
Chris@87
|
298
|
Chris@87
|
299 void
|
Chris@87
|
300 MatrixFileCache::setValueAt(size_t x, size_t y, float value)
|
Chris@87
|
301 {
|
Chris@87
|
302 if (m_mode != ReadWrite) {
|
Chris@87
|
303 std::cerr << "ERROR: MatrixFileCache::setValueAt called on read-only cache"
|
Chris@87
|
304 << std::endl;
|
Chris@87
|
305 return;
|
Chris@87
|
306 }
|
Chris@87
|
307
|
Chris@87
|
308 if (!seekTo(x, y)) return;
|
Chris@90
|
309 ssize_t w = ::write(m_fd, &value, sizeof(float));
|
Chris@90
|
310 if (w != sizeof(float)) {
|
Chris@87
|
311 ::perror("WARNING: MatrixFileCache::setValueAt: write failed");
|
Chris@87
|
312 }
|
Chris@90
|
313 if (w > 0) m_off += w;
|
Chris@87
|
314
|
Chris@90
|
315 //... update region as appropriate
|
Chris@87
|
316 }
|
Chris@87
|
317
|
Chris@87
|
318 void
|
Chris@87
|
319 MatrixFileCache::setColumnAt(size_t x, float *values)
|
Chris@87
|
320 {
|
Chris@87
|
321 if (m_mode != ReadWrite) {
|
Chris@87
|
322 std::cerr << "ERROR: MatrixFileCache::setColumnAt called on read-only cache"
|
Chris@87
|
323 << std::endl;
|
Chris@87
|
324 return;
|
Chris@87
|
325 }
|
Chris@87
|
326
|
Chris@87
|
327 if (!seekTo(x, 0)) return;
|
Chris@90
|
328 ssize_t w = ::write(m_fd, values, m_height * sizeof(float));
|
Chris@91
|
329 if (w != ssize_t(m_height * sizeof(float))) {
|
Chris@87
|
330 ::perror("WARNING: MatrixFileCache::setColumnAt: write failed");
|
Chris@87
|
331 }
|
Chris@90
|
332 if (w > 0) m_off += w;
|
Chris@87
|
333
|
Chris@90
|
334 //... update region as appropriate
|
Chris@90
|
335 }
|
Chris@90
|
336
|
Chris@90
|
337 float *
|
Chris@90
|
338 MatrixFileCache::getRegionPtr(size_t x, size_t y) const
|
Chris@90
|
339 {
|
Chris@90
|
340 if (m_rw == 0) return 0;
|
Chris@90
|
341
|
Chris@90
|
342 float *region = m_region;
|
Chris@90
|
343
|
Chris@90
|
344 float *ptr = &(region[(x - m_rx) * m_height + y]);
|
Chris@90
|
345
|
Chris@90
|
346 // std::cerr << "getRegionPtr(" << x << "," << y << "): region is " << m_region << ", returning " << ptr << std::endl;
|
Chris@90
|
347 return ptr;
|
Chris@90
|
348 }
|
Chris@90
|
349
|
Chris@90
|
350 bool
|
Chris@90
|
351 MatrixFileCache::autoSetRegion(size_t x) const
|
Chris@90
|
352 {
|
Chris@90
|
353 size_t rx = x;
|
Chris@90
|
354 size_t rw = m_autoRegionWidth;
|
Chris@90
|
355 size_t left = rw / 4;
|
Chris@90
|
356 if (x < m_rx) left = (rw * 3) / 4;
|
Chris@90
|
357 if (rx > left) rx -= left;
|
Chris@90
|
358 else rx = 0;
|
Chris@90
|
359 if (rx + rw > m_width) rw = m_width - rx;
|
Chris@90
|
360 return setRegion(rx, rw, false);
|
Chris@90
|
361 }
|
Chris@90
|
362
|
Chris@90
|
363 bool
|
Chris@90
|
364 MatrixFileCache::setRegion(size_t x, size_t width, bool user) const
|
Chris@90
|
365 {
|
Chris@90
|
366 if (!user && m_userRegion) return false;
|
Chris@90
|
367 if (m_rw > 0 && x >= m_rx && x + width <= m_rx + m_rw) return true;
|
Chris@90
|
368
|
Chris@90
|
369 if (m_rw > 0) {
|
Chris@93
|
370 delete[] m_region;
|
Chris@90
|
371 m_region = 0;
|
Chris@90
|
372 m_rw = 0;
|
Chris@90
|
373 }
|
Chris@90
|
374
|
Chris@90
|
375 if (width == 0) {
|
Chris@90
|
376 return true;
|
Chris@90
|
377 }
|
Chris@90
|
378
|
Chris@90
|
379 if (!seekTo(x, 0)) return false;
|
Chris@90
|
380
|
Chris@90
|
381 m_region = new float[width * m_height];
|
Chris@91
|
382 MUNLOCK(m_region, width * m_height * sizeof(float));
|
Chris@90
|
383
|
Chris@90
|
384 ssize_t r = ::read(m_fd, m_region, width * m_height * sizeof(float));
|
Chris@90
|
385 if (r < 0) {
|
Chris@90
|
386 ::perror("Read failed");
|
Chris@90
|
387 std::cerr << "ERROR: MatrixFileCache::setRegion(" << x << ", " << width
|
Chris@90
|
388 << ") failed" << std::endl;
|
Chris@90
|
389 delete[] m_region;
|
Chris@90
|
390 m_region = 0;
|
Chris@90
|
391 return false;
|
Chris@90
|
392 }
|
Chris@90
|
393
|
Chris@90
|
394 m_off += r;
|
Chris@90
|
395
|
Chris@91
|
396 if (r < ssize_t(width * m_height * sizeof(float))) {
|
Chris@90
|
397 // didn't manage to read the whole thing, but did get something
|
Chris@90
|
398 std::cerr << "WARNING: MatrixFileCache::setRegion(" << x << ", " << width
|
Chris@90
|
399 << "): ";
|
Chris@90
|
400 width = r / (m_height * sizeof(float));
|
Chris@90
|
401 std::cerr << "Only got " << width << " columns" << std::endl;
|
Chris@90
|
402 }
|
Chris@90
|
403
|
Chris@90
|
404 m_rx = x;
|
Chris@90
|
405 m_rw = width;
|
Chris@90
|
406 if (m_rw == 0) {
|
Chris@90
|
407 delete[] m_region;
|
Chris@90
|
408 m_region = 0;
|
Chris@90
|
409 }
|
Chris@90
|
410
|
Chris@90
|
411 std::cerr << "MatrixFileCache::setRegion: set region to " << x << ", " << width << std::endl;
|
Chris@90
|
412
|
Chris@90
|
413 if (user) m_userRegion = true;
|
Chris@90
|
414 return true;
|
Chris@87
|
415 }
|
Chris@87
|
416
|
Chris@87
|
417 bool
|
Chris@87
|
418 MatrixFileCache::seekTo(size_t x, size_t y) const
|
Chris@87
|
419 {
|
Chris@87
|
420 off_t off = m_headerSize + (x * m_height + y) * sizeof(float);
|
Chris@87
|
421 if (off == m_off) return true;
|
Chris@87
|
422
|
Chris@90
|
423 if (m_mode == ReadWrite) {
|
Chris@90
|
424 std::cerr << "writer: ";
|
Chris@90
|
425 std::cerr << "seek required (from " << m_off << " to " << off << ")" << std::endl;
|
Chris@90
|
426 }
|
Chris@90
|
427
|
Chris@87
|
428 if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) {
|
Chris@87
|
429 ::perror("Seek failed");
|
Chris@87
|
430 std::cerr << "ERROR: MatrixFileCache::seekTo(" << x << ", " << y
|
Chris@87
|
431 << ") failed" << std::endl;
|
Chris@87
|
432 return false;
|
Chris@87
|
433 }
|
Chris@87
|
434
|
Chris@87
|
435 m_off = off;
|
Chris@87
|
436 return true;
|
Chris@87
|
437 }
|
Chris@87
|
438
|