kwave  18.07.70
OverViewCache.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  OverViewCache.cpp - fast cache for sample data overview
3  -------------------
4  begin : Mon May 20 2002
5  copyright : (C) 2000 by Thomas Eschenbacher
6  email : Thomas.Eschenbacher@gmx.de
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "config.h"
19 #include "math.h"
20 
21 #include <QColor>
22 #include <QPainter>
23 
25 #include "libkwave/Sample.h"
26 #include "libkwave/SampleReader.h"
27 #include "libkwave/SignalManager.h"
28 #include "libkwave/String.h"
29 #include "libkwave/Track.h"
30 #include "libkwave/Utils.h"
31 
32 #include "libgui/OverViewCache.h"
33 
34 #define CACHE_SIZE 8192
36 //***************************************************************************
38  sample_index_t src_offset,
39  sample_index_t src_length,
40  const QList<unsigned int> *src_tracks)
41  :m_signal(signal),
42  m_selection(&signal, src_offset, src_length, src_tracks),
43  m_min(), m_max(), m_state(), m_minmax(),
44  m_scale(1),
45  m_lock(QMutex::Recursive)
46 {
47 
48  connect(&m_selection, SIGNAL(sigTrackInserted(QUuid)),
49  this, SLOT(slotTrackInserted(QUuid)));
50  connect(&m_selection, SIGNAL(sigTrackDeleted(QUuid)),
51  this, SLOT(slotTrackDeleted(QUuid)));
52  connect(&m_selection, SIGNAL(sigLengthChanged(sample_index_t)),
53  this, SLOT(slotLengthChanged(sample_index_t)));
54  connect(
55  &m_selection,
56  SIGNAL(sigInvalidated(const QUuid*,sample_index_t,sample_index_t)),
57  this,
59  );
60 
61  // take over the initial list of tracks
62  foreach (const QUuid &uuid, m_selection.allTracks())
63  slotTrackInserted(uuid);
64 }
65 
66 //***************************************************************************
68 {
69  QMutexLocker lock(&m_lock);
70 
71  m_state.clear();
72  m_min.clear();
73  m_max.clear();
74 }
75 
76 //***************************************************************************
78 {
79  Q_ASSERT(m_scale);
80  if (!m_scale) return;
81 
82  // calculate the new scale
83  const sample_index_t len = m_selection.length();
84  unsigned int shrink = Kwave::toUint(len / (m_scale * CACHE_SIZE));
85  while (len > CACHE_SIZE * m_scale * shrink) {
86  shrink++;
87  }
88  if (shrink <= 1) return; // nothing to shrink, just ignore new scale
89 
90  // loop over all tracks
91  foreach (const QUuid &uuid, m_state.keys()) {
92  unsigned int dst = 0;
93  unsigned int count = CACHE_SIZE / shrink;
94  Q_ASSERT(count <= CACHE_SIZE);
95 
96  // source pointers
97  sample_t *smin = m_min[uuid].data();
98  sample_t *smax = m_max[uuid].data();
99  CacheState *sstate = m_state[uuid].data();
100 
101  // destination pointers
102  sample_t *dmin = smin;
103  sample_t *dmax = smax;
104  CacheState *dstate = sstate;
105 
106  // loop over all entries to be shrinked
107  while (dst < count) {
108  sample_t min = SAMPLE_MAX;
109  sample_t max = SAMPLE_MIN;
110  CacheState state = Unused;
111  for (unsigned int i = 0; i < shrink; ++i) {
112  if (*smin < min) min = *smin;
113  if (*smax > max) max = *smax;
114  if (*sstate < state) state = *sstate;
115  ++smin;
116  ++smax;
117  ++sstate;
118  }
119  *dmin = min;
120  *dmax = max;
121  *dstate = state;
122  ++dmin;
123  ++dmax;
124  ++dstate;
125  ++dst;
126  }
127 
128  // the rest will be unused
129  while (dst++ < CACHE_SIZE) {
130  *dstate = Unused;
131  dstate++;
132  }
133  }
134 
135  m_scale *= shrink;
136 }
137 
138 //***************************************************************************
140 {
141  const sample_index_t len = m_selection.length();
142  quint64 new_scale = static_cast<quint64>(rint(ceil(len / CACHE_SIZE)));
143  if (!new_scale) new_scale = 1;
144  if (m_scale == new_scale) return;
145 
146  m_scale = new_scale;
147  invalidateCache(Q_NULLPTR, 0, CACHE_SIZE - 1);
148 }
149 
150 //***************************************************************************
151 void Kwave::OverViewCache::invalidateCache(const QUuid *track_id,
152  unsigned int first,
153  unsigned int last)
154 {
155  if (track_id) {
156  // invalidate a single track
157  Q_ASSERT(m_state.contains(*track_id));
158  if (!m_state.contains(*track_id)) return;
159 
160  if (last >= CACHE_SIZE) last = CACHE_SIZE - 1;
161 
162 // qDebug("OverViewCache[%p]::invalidateCache(%s, %u, %u)",
163 // static_cast<void *>(this), DBG(track_id->toString()),
164 // first, last);
165 
166  for (unsigned int pos = first; pos <= last; ++pos)
167  m_state[*track_id][pos] = Invalid;
168  } else {
169  // invalidate all tracks
170  foreach (const QUuid &uuid, m_state.keys())
171  invalidateCache(&uuid, first, last);
172  }
173 }
174 
175 //***************************************************************************
176 void Kwave::OverViewCache::slotTrackInserted(const QUuid &track_id)
177 {
178  QMutexLocker lock(&m_lock);
179 
180  // just to be sure: check scale again, maybe it was the first track
181  if ((m_selection.length() / m_scale) > CACHE_SIZE)
182  scaleUp();
183  if ((m_selection.length() / m_scale) < (CACHE_SIZE / 4))
184  scaleDown();
185 
186  QVector<CacheState> state(CACHE_SIZE);
187  QVector<sample_t> min(CACHE_SIZE);
188  QVector<sample_t> max(CACHE_SIZE);
189 
190  min.fill(SAMPLE_MAX);
191  max.fill(SAMPLE_MIN);
192  state.fill(Unused);
193 
194  m_min.insert(track_id, min);
195  m_max.insert(track_id, max);
196  m_state.insert(track_id, state);
197 
198  // mark the new cache content as invalid
199  invalidateCache(&track_id, 0, CACHE_SIZE - 1);
200 
201  emit changed();
202 }
203 
204 //***************************************************************************
205 void Kwave::OverViewCache::slotTrackDeleted(const QUuid &track_id)
206 {
207  QMutexLocker lock(&m_lock);
208 
209  m_min.remove(track_id);
210  m_max.remove(track_id);
211  m_state.remove(track_id);
212 
213  emit changed();
214 }
215 
216 //***************************************************************************
217 void Kwave::OverViewCache::slotInvalidated(const QUuid *track_id,
218  sample_index_t first,
219  sample_index_t last)
220 {
221  QMutexLocker lock(&m_lock);
222 
223  // adjust offsets, absolute -> relative
224  sample_index_t offset = m_selection.offset();
225  Q_ASSERT(first >= offset);
226  Q_ASSERT(last >= offset);
227  Q_ASSERT(last >= first);
228  first -= offset;
229  last -= offset;
230 
231  unsigned int first_idx = Kwave::toUint(first / m_scale);
232  unsigned int last_idx;
233  if (last >= (SAMPLE_INDEX_MAX - (m_scale - 1)))
234  last_idx = CACHE_SIZE - 1;
235  else
236  last_idx = Kwave::toUint(
237  qMin(Kwave::round_up(last, m_scale) / m_scale,
238  quint64(CACHE_SIZE - 1))
239  );
240 
241  invalidateCache(track_id, first_idx, last_idx);
242  emit changed();
243 }
244 
245 //***************************************************************************
247 {
248  QMutexLocker lock(&m_lock);
249 
250  // just to be sure: check scale again, maybe it was the first track
251  if ((new_length / m_scale) > CACHE_SIZE)
252  scaleUp();
253  if ((new_length / m_scale) < (CACHE_SIZE / 4))
254  scaleDown();
255 }
256 
257 //***************************************************************************
259 {
260  QMutexLocker lock(&m_lock);
261  int retval = 0;
262 
263  const sample_index_t first = m_selection.offset();
264  const sample_index_t last = m_selection.last();
265  const sample_index_t length = m_selection.length();
266  if (!length)
267  return 0;
268 
269  // resize the target buffer if necessary
270  if (minmax.count() < width)
271  minmax.resize(width);
272  if (minmax.count() < width) // truncated, OOM
273  width = minmax.count();
274 
275  QList<unsigned int> track_list;
276  const QList<QUuid> selected_tracks = m_selection.allTracks();
277  foreach (unsigned int track, m_signal.allTracks())
278  if (selected_tracks.contains(m_signal.uuidOfTrack(track)))
279  track_list.append(track);
280  if (track_list.isEmpty())
281  return 0;
282 
285  m_signal, track_list, first, last
286  );
287  Q_ASSERT(m_min.count() == m_max.count());
288  Q_ASSERT(m_min.count() == m_state.count());
289 
290  if ((length / m_scale < 2) || src.isEmpty() || !m_state.count())
291  return 0; // empty ?
292 
293  // loop over all min/max buffers and make their content valid
294  for (int index = 0; index < track_list.count(); ++index) {
295  unsigned int count = qBound<unsigned int>(
296  1, Kwave::toUint(length / m_scale), CACHE_SIZE);
297 
298  QUuid uuid = m_signal.uuidOfTrack(track_list[index]);
299  if (uuid.isNull()) continue; // track has just been deleted
300 
301  // check: maybe slotTrackInserted has not yet been called
302  // or slotTrackDeleted has just been called
303  if (!m_state.keys().contains(uuid))
304  continue;
305 
306  sample_t *min = m_min[uuid].data();
307  sample_t *max = m_max[uuid].data();
308  CacheState *state = m_state[uuid].data();
309  Q_ASSERT(min && max && state);
310  Kwave::SampleReader *reader = src[index];
311  Q_ASSERT(reader);
312 
313  if (!reader || !min || !max || !state) continue;
314 
315  for (unsigned int ofs = 0; ofs < count; ++ofs) {
316  if (state[ofs] == Valid) continue;
317  if (state[ofs] == Unused) continue;
318 
319  // get min/max
320  sample_index_t first_idx = m_selection.offset() + (ofs * m_scale);
321  sample_index_t last_idx = first_idx + m_scale - 1;
322  reader->minMax(first_idx, last_idx, min[ofs], max[ofs]);
323  state[ofs] = Valid;
324  }
325  }
326 
327  // loop over all min/max buffers
328  for (int x = 0; x < width; ++x) {
329  unsigned int count = qBound<unsigned int>(
330  1, Kwave::toUint(length / m_scale), CACHE_SIZE);
331 
332  // get the corresponding cache index
333  unsigned int index = ((count - 1) * x) / (width - 1);
334  unsigned int last_index = ((count - 1) * (x + 1)) / (width - 1);
335  Q_ASSERT(index < CACHE_SIZE);
336  if (index >= CACHE_SIZE) index = CACHE_SIZE - 1;
337  if (last_index > index) last_index--;
338  if (last_index >= CACHE_SIZE) last_index = CACHE_SIZE - 1;
339 
340  // loop over all cache indices
341  sample_t minimum = SAMPLE_MAX;
342  sample_t maximum = SAMPLE_MIN;
343  for (; index <= last_index; ++index) {
344  // loop over all tracks
345  foreach (const QUuid &uuid, m_state.keys()) {
346  sample_t *min = m_min[uuid].data();
347  sample_t *max = m_max[uuid].data();
348  const CacheState *state = m_state[uuid].constData();
349  Q_ASSERT(state);
350  if (!state) continue;
351  if (state[index] != Valid) {
352  if (minimum > 0) minimum = 0;
353  if (maximum < 0) maximum = 0;
354  continue;
355  }
356 
357  if (min[index] < minimum) minimum = min[index];
358  if (max[index] > maximum) maximum = max[index];
359  }
360  }
361 
362  minmax[x].min = minimum;
363  minmax[x].max = maximum;
364  retval++;
365  }
366 
367  return retval;
368 }
369 
370 //***************************************************************************
371 QImage Kwave::OverViewCache::getOverView(int width, int height,
372  const QColor &fg, const QColor &bg,
373  double gain)
374 {
375  QMutexLocker lock(&m_lock);
376 
377  QImage bitmap(width, height, QImage::Format_ARGB32_Premultiplied);
378  if ((width < 2) || (height < 3) || bitmap.isNull()) return bitmap;
379 
380  QPainter p;
381  p.begin(&bitmap);
382  p.fillRect(bitmap.rect(), bg);
383  p.setPen(fg);
384 
385  int count = getMinMax(width, m_minmax);
386  if (count < 1) {
387  p.end();
388  return bitmap; // empty ?
389  }
390 
391  // draw the bitmap
392  for (int x = 0; x < count; ++x) {
393  const int middle = (height >> 1);
394  const double scale = static_cast<double>(middle) /
395  static_cast<double>(SAMPLE_MAX);
396  double min = m_minmax[x].min * scale;
397  double max = m_minmax[x].max * scale;
398 
399  if (gain != 1.0) {
400  min *= gain;
401  max *= gain;
402  if (min < -middle) min = -middle;
403  if (min > +middle) min = +middle;
404  if (max < -middle) max = -middle;
405  if (max > +middle) max = +middle;
406  }
407  p.drawLine(x, middle - Kwave::toInt(max),
408  x, middle - Kwave::toInt(min));
409  }
410 
411  p.end();
412  return bitmap;
413 }
414 
415 //***************************************************************************
416 //***************************************************************************
virtual QImage getOverView(int width, int height, const QColor &fg, const QColor &bg, double gain=1.0)
sample_index_t offset() const
void slotTrackDeleted(const QUuid &track_id)
OverViewCache(Kwave::SignalManager &signal, sample_index_t src_offset, sample_index_t src_length, const QList< unsigned int > *src_tracks)
int getMinMax(int width, MinMaxArray &minmax)
QUuid uuidOfTrack(unsigned int track)
QHash< QUuid, QVector< sample_t > > m_min
#define SAMPLE_MIN
Definition: Sample.h:49
void slotTrackInserted(const QUuid &track_id)
QList< QUuid > allTracks()
quint64 sample_index_t
Definition: Sample.h:28
void invalidateCache(const QUuid *uuid, unsigned int first, unsigned int last)
bool connect(Kwave::StreamObject &source, const char *output, Kwave::StreamObject &sink, const char *input)
Definition: Connect.cpp:48
T round_up(T x, const T s)
Definition: Utils.h:96
void minMax(sample_index_t first, sample_index_t last, sample_t &min, sample_t &max)
void slotLengthChanged(sample_index_t new_length)
Kwave::SelectionTracker m_selection
QVector< MinMax > MinMaxArray
Definition: OverViewCache.h:60
int toInt(T x)
Definition: Utils.h:127
sample_index_t last() const
virtual bool isEmpty() const
void slotInvalidated(const QUuid *track_id, sample_index_t first, sample_index_t last)
const QList< unsigned int > allTracks()
QHash< QUuid, QVector< sample_t > > m_max
#define SAMPLE_MAX
Definition: Sample.h:52
unsigned int toUint(T x)
Definition: Utils.h:109
#define SAMPLE_INDEX_MAX
Definition: Sample.h:31
#define CACHE_SIZE
QHash< QUuid, QVector< CacheState > > m_state
sample_index_t length() const
Kwave::SignalManager & m_signal
qint32 sample_t
Definition: Sample.h:37