• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ossia / score / 35899133125

23 Sep 2026 05:57PM UTC coverage: 41.488% (+1.2%) from 40.337%
35899133125

push

github

jcelerier
3rdparty: follow the merged libossia

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PWVTQ1pm3X2Uz7goFgULtC

123236 of 297041 relevant lines covered (41.49%)

113626.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.26
/src/plugins/score-plugin-media/Media/MediaFileHandle.waveform.cpp
1
#include <Media/MediaFileHandle.hpp>
2

3
#include <ossia/detail/libav.hpp>
4
#include <ossia/detail/ssize.hpp>
5

6
#include <algorithm>
7
#include <limits>
8
#include <vector>
9
namespace Media
10
{
11
namespace
12
{
13
template <typename Fun_T, typename T>
14
struct FrameComputer
15
{
16
  const int64_t start_frame;
17
  const int64_t end_frame;
18
  ossia::small_vector<T, 8>& sum;
19
  static constexpr const Fun_T fun{};
20

21
  void operator()(ossia::monostate) const noexcept { }
×
22

23
  void operator()(const AudioFile::StreamView& r) const noexcept
×
24
  {
25
    const int channels = r.handle->channels();
×
26
    assert(std::ssize(sum) == channels);
×
27
    if(end_frame - start_frame > 0)
×
28
    {
29
      bool init = false;
×
30
      r.handle->fetch(
×
31
          start_frame, end_frame - start_frame, [&](float* frame, float* end) {
×
32
            assert(frame < end);
×
33
            if(!init)
×
34
            {
35
              init = true;
×
36
              for(int c = 0; c < channels; c++)
×
37
              {
38
                assert(frame + c < end);
×
39
                sum[c] = fun.init(frame[c]);
×
40
              }
×
41
            }
×
42
            else
43
            {
44
              for(int c = 0; c < channels; c++)
×
45
              {
46
                assert(frame + c < end);
×
47
                auto& s = sum[c];
×
48
                s = fun(s, (float)frame[c]);
×
49
              }
×
50
            }
51
          });
×
52
    }
×
53
    else
54
    {
55
      r.handle->fetch(start_frame, 1, [&](float* frame, float* end) {
×
56
        assert(frame < end);
×
57
        for(int c = 0; c < channels; c++)
×
58
        {
59
          assert(frame + c < end);
×
60
          sum[c] = fun.init(frame[c]);
×
61
        }
×
62
      });
×
63
    }
64
  }
×
65
  void operator()(const AudioFile::RAMView& r) noexcept
3,274✔
66
  {
67
    const int channels = r.data.size();
3,274✔
68
    assert(std::ssize(sum) == channels);
3,274✔
69
    if(end_frame - start_frame > 0)
3,274✔
70
    {
71
      for(int c = 0; c < channels; c++)
8,625✔
72
      {
73
        const auto& vals = r.data[c];
5,351✔
74
        auto& s = sum[c];
5,351✔
75
        s = fun.init(vals[start_frame]);
5,351✔
76
        for(int64_t i = start_frame + 1; i < end_frame; i++)
1,776,767✔
77
          s = fun(s, (float)vals[i]);
1,771,416✔
78
      }
5,351✔
79
    }
3,274✔
80
    else if(end_frame == start_frame)
×
81
    {
82
      for(int c = 0; c < channels; c++)
×
83
      {
84
        const auto& vals = r.data[c];
×
85
        sum[c] = fun.init(vals[start_frame]);
×
86
      }
×
87
    }
×
88
  }
3,274✔
89

90
  void operator()(AudioFile::MmapView& r) noexcept
32,299✔
91
  {
92
    auto& wav = r.wav;
32,299✔
93
    const int channels = wav.channels();
32,299✔
94
    assert(std::ssize(sum) == channels);
32,299✔
95

96
    if(end_frame - start_frame > 0)
32,299✔
97
    {
98
      const int64_t buffer_size = end_frame - start_frame;
32,221✔
99
      thread_local std::vector<float> data_cache;
32,221✔
100

101
      if(Q_UNLIKELY(!wav.seek_to_pcm_frame(start_frame)))
32,221✔
102
        return;
×
103

104
      float* floats{};
32,221✔
105
      const int64_t num_elems = buffer_size * channels;
32,221✔
106
      if(num_elems > 10000)
32,221✔
107
      {
108
        data_cache.resize(num_elems);
35✔
109
        floats = data_cache.data();
35✔
110
      }
35✔
111
      else
112
      {
113
        floats = (float*)alloca(sizeof(float) * num_elems);
32,186✔
114
      }
115

116
      auto max = wav.read_pcm_frames_f32(buffer_size, floats);
32,221✔
117
      if(Q_UNLIKELY(max == 0))
32,221✔
118
        return;
×
119

120
      for(int c = 0; c < channels; c++)
68,234✔
121
      {
122
        sum[c] = fun.init(floats[c]);
36,013✔
123
      }
36,013✔
124

125
      for(decltype(max) i = 1; i < max; i++)
2,459,742✔
126
      {
127
        for(int c = 0; c < channels; c++)
6,301,810✔
128
        {
129
          const float f = floats[i * channels + c];
3,874,289✔
130
          sum[c] = fun(sum[c], f);
3,874,289✔
131
        }
3,874,289✔
132
      }
2,427,521✔
133
    }
32,221✔
134
    else
135
    {
136
      float* val = (float*)alloca(sizeof(float) * channels);
78✔
137
      if(Q_UNLIKELY(!wav.seek_to_pcm_frame(start_frame)))
78✔
138
        return;
×
139
      int max = wav.read_pcm_frames_f32(1, val);
78✔
140
      if(Q_UNLIKELY(max == 0))
78✔
141
        return;
×
142

143
      for(int c = 0; c < channels; c++)
156✔
144
      {
145
        sum[c] = fun.init(val[c]);
78✔
146
      }
78✔
147
    }
148
  }
32,299✔
149
};
150

151
struct MinMax
152
{
153
  static constexpr FloatPair init(float v) noexcept { return {v, v}; }
41,433✔
154
  static constexpr auto init(int64_t v) noexcept { return std::make_pair(v, v); }
155
  constexpr FloatPair operator()(FloatPair f1, float f2) const noexcept
5,644,977✔
156
  {
157
    return {f1.first < f2 ? f1.first : f2, f1.second > f2 ? f1.second : f2};
5,644,977✔
158
  }
159
  constexpr auto operator()(std::pair<int64_t, int64_t> f1, int64_t f2) const noexcept
160
  {
161
    return std::make_pair(f1.first < f2 ? f1.first : f2, f1.second > f2 ? f1.second : f2);
162
  }
163
};
164

165
//! Reads straight through rather than a bucket at a time: on the mmap path
166
//! each read is a seek plus a conversion to float.
167
struct SummaryBuilder
168
{
169
  WaveformSummary& s;
170
  bool& ok;
171

172
  void operator()(ossia::monostate) const noexcept { }
×
173

174
  //! Summarising a streamed source means decoding it whole.
175
  void operator()(const AudioFile::StreamView&) const noexcept { }
×
176

177
  void operator()(const AudioFile::RAMView& r) const noexcept
4✔
178
  {
179
    if(std::ssize(r.data) < s.channels)
4✔
180
      return;
×
181

182
    for(int c = 0; c < s.channels; c++)
9✔
183
    {
184
      const audio_sample* const v = r.data[c];
5✔
185
      FloatPair* const dst = s.data.get() + int64_t(c) * s.bucketCount;
5✔
186
      for(int64_t b = 0; b < s.bucketCount; b++)
8,325✔
187
      {
188
        const int64_t beg = b * s.bucket;
8,320✔
189
        const int64_t end = std::min(beg + s.bucket, s.frames);
8,320✔
190
        float lo = v[beg], hi = v[beg];
8,320✔
191
        for(int64_t i = beg + 1; i < end; i++)
532,300✔
192
        {
193
          const float f = v[i];
523,980✔
194
          lo = f < lo ? f : lo;
523,980✔
195
          hi = f > hi ? f : hi;
523,980✔
196
        }
523,980✔
197
        dst[b] = {lo, hi};
8,320✔
198
      }
8,320✔
199
    }
5✔
200
    ok = true;
4✔
201
  }
4✔
202

203
  void operator()(AudioFile::MmapView& r) const noexcept
45✔
204
  {
205
    auto& wav = r.wav;
45✔
206
    const int channels = s.channels;
45✔
207
    if(wav.channels() != channels)
45✔
208
      return;
×
209

210
    // In samples, not frames: budgeted in frames, a file with many channels
211
    // sizes this by its channel count.
212
    const int64_t chunk = std::clamp<int64_t>(
47✔
213
        (1 << 18) / (s.bucket * int64_t(channels)), 1, s.bucketCount);
45✔
214
    std::vector<float> buf(std::size_t(chunk * s.bucket * channels));
43✔
215

216
    if(!wav.seek_to_pcm_frame(0))
43✔
217
      return;
×
218

219
    for(int64_t b0 = 0; b0 < s.bucketCount; b0 += chunk)
196✔
220
    {
221
      const int64_t nb = std::min(chunk, s.bucketCount - b0);
153✔
222
      const int64_t got = wav.read_pcm_frames_f32(nb * s.bucket, buf.data());
153✔
223
      if(got <= 0)
153✔
224
        return;
×
225
      // Only the last chunk may be short, or the reads after it stop lining up.
226
      if(got < nb * s.bucket && b0 + nb < s.bucketCount)
153✔
227
        return;
×
228

229
      for(int64_t b = 0; b < nb; b++)
459,016✔
230
      {
231
        const int64_t beg = b * s.bucket;
458,863✔
232
        const int64_t end = std::min(beg + s.bucket, got);
458,863✔
233
        if(beg >= end)
458,862✔
234
          break;
×
235

236
        for(int c = 0; c < channels; c++)
936,477✔
237
        {
238
          float lo = buf[beg * channels + c], hi = lo;
477,614✔
239
          for(int64_t i = beg + 1; i < end; i++)
30,544,642✔
240
          {
241
            const float f = buf[i * channels + c];
30,067,028✔
242
            lo = f < lo ? f : lo;
30,067,028✔
243
            hi = f > hi ? f : hi;
30,067,028✔
244
          }
30,067,028✔
245
          s.data[int64_t(c) * s.bucketCount + b0 + b] = {lo, hi};
477,614✔
246
        }
477,615✔
247
      }
458,863✔
248
    }
153✔
249
    ok = true;
43✔
250
  }
43✔
251
};
252

253
struct SingleFrameComputer
254
{
255
  int64_t start_frame;
256
  ossia::small_vector<float, 8>& sum;
257

258
  void operator()(ossia::monostate) const noexcept { }
×
259

260
  void operator()(const AudioFile::StreamView& r) noexcept
×
261
  {
262
    const int channels = r.handle->channels();
×
263
    assert(std::ssize(sum) == channels);
×
264
    r.handle->fetch(start_frame, 1, [&](float* frame, float* end) {
×
265
      for(int c = 0; c < channels; c++)
×
266
      {
267
        assert(frame + c < end);
×
268
        sum[c] = frame[c];
×
269
      }
×
270
    });
×
271
  }
×
272
  void operator()(const AudioFile::RAMView& r) noexcept
×
273
  {
274
    const int channels = r.data.size();
×
275
    assert(std::ssize(sum) == channels);
×
276
    for(int c = 0; c < channels; c++)
×
277
    {
278
      const auto& vals = r.data[c];
×
279
      sum[c] = vals[start_frame];
×
280
    }
×
281
  }
×
282

283
  void operator()(AudioFile::MmapView& r) noexcept
×
284
  {
285
    auto& wav = r.wav;
×
286
    const int channels = wav.channels();
×
287
    assert(std::ssize(sum) == channels);
×
288

289
    float* val = (float*)alloca(sizeof(float) * channels);
×
290
    if(Q_UNLIKELY(!wav.seek_to_pcm_frame(start_frame)))
×
291
      return;
×
292

293
    int max = wav.read_pcm_frames_f32(1, val);
×
294
    if(Q_UNLIKELY(max == 0))
×
295
      return;
×
296

297
    for(int c = 0; c < channels; c++)
×
298
    {
299
      sum[c] = val[c];
×
300
    }
×
301
  }
×
302
};
303
}
304
void AudioFile::ViewHandle::frame(
×
305
    int64_t start_frame, ossia::small_vector<float, 8>& out) noexcept
306
{
307
  SingleFrameComputer _{start_frame, out};
×
308
  ossia::visit(_, *this);
×
309
}
×
310

311
void AudioFile::ViewHandle::absmax_frame(
×
312
    int64_t start_frame, int64_t end_frame, ossia::small_vector<float, 8>& out) noexcept
313
{
314
  struct AbsMax
315
  {
316
    static constexpr float init(float v) noexcept { return v; }
×
317
    constexpr float operator()(float f1, float f2) const noexcept
×
318
    {
319
      return abs_max(f1, f2);
×
320
    }
321
    static constexpr auto init(int64_t v) noexcept { return v; }
322
    constexpr int64_t operator()(int64_t f1, int64_t f2) const noexcept
323
    {
324
      return abs_max(f1, f2);
325
    }
326
  };
327
  FrameComputer<AbsMax, float> _{start_frame, end_frame, out};
×
328
  ossia::visit(_, *this);
×
329
}
×
330

331
//! Folds the samples of [start_frame, end_frame) into `out`, which already
332
//! holds the min/max of another range.
333
void AudioFile::ViewHandle::merge_range(
31,888✔
334
    int64_t start_frame, int64_t end_frame,
335
    ossia::small_vector<FloatPair, 8>& out) noexcept
336
{
337
  if(start_frame >= end_frame)
31,888✔
338
    return;
694✔
339

340
  // Neutral, so a failed read leaves `out` alone rather than pulling it to 0.
341
  ossia::small_vector<FloatPair, 8> part(
31,230✔
342
      out.size(),
31,194✔
343
      FloatPair{std::numeric_limits<float>::max(), std::numeric_limits<float>::lowest()});
31,194✔
344

345
  FrameComputer<MinMax, FloatPair> _{start_frame, end_frame, part};
31,158✔
346
  ossia::visit(_, *this);
31,158✔
347

348
  for(std::size_t c = 0; c < out.size(); c++)
65,117✔
349
  {
350
    out[c].first = std::min(out[c].first, part[c].first);
33,939✔
351
    out[c].second = std::max(out[c].second, part[c].second);
33,935✔
352
  }
33,931✔
353
}
31,872✔
354

355
//! Whole buckets out of the summary, the partial ends straight from the
356
//! source. Exact: min and max do not care in which order samples are folded.
357
bool AudioFile::ViewHandle::summary_minmax(
20,325✔
358
    int64_t start_frame, int64_t end_frame,
359
    ossia::small_vector<FloatPair, 8>& out) noexcept
360
{
361
  const WaveformSummary* const s = summary.get();
20,325✔
362
  if(!s || !s->data)
20,325✔
363
    return false;
2,300✔
364
  if(std::ssize(out) != s->channels)
18,025✔
365
    return false;
×
366
  if(start_frame < 0 || end_frame > s->frames)
18,025✔
367
    return false;
×
368

369
  const int64_t B = s->bucket;
18,025✔
370

371
  // Under a few buckets the partial ends are most of the work anyway.
372
  if(end_frame - start_frame < 4 * B)
18,025✔
373
    return false;
2,087✔
374

375
  const int64_t first = (start_frame + B - 1) / B; // first whole bucket
15,938✔
376
  const int64_t last = end_frame / B;              // one past the last whole one
15,938✔
377
  if(last <= first || last > s->bucketCount)
15,938✔
378
    return false;
×
379

380
  for(int c = 0; c < s->channels; c++)
33,364✔
381
  {
382
    const FloatPair* const v = s->channel(c);
17,426✔
383
    float lo = v[first].first, hi = v[first].second;
17,426✔
384
    for(int64_t b = first + 1; b < last; b++)
144,294✔
385
    {
386
      lo = v[b].first < lo ? v[b].first : lo;
126,868✔
387
      hi = v[b].second > hi ? v[b].second : hi;
126,868✔
388
    }
126,868✔
389
    out[c] = {lo, hi};
17,426✔
390
  }
17,426✔
391

392
  merge_range(start_frame, first * B, out);
15,938✔
393
  merge_range(last * B, end_frame, out);
15,938✔
394
  return true;
15,938✔
395
}
20,325✔
396

397
bool AudioFile::ViewHandle::supports_summary() const noexcept
47✔
398
{
399
  struct
400
  {
401
    bool operator()(ossia::monostate) const noexcept { return false; }
×
402
    bool operator()(const AudioFile::StreamView&) const noexcept { return false; }
×
403
    bool operator()(const AudioFile::RAMView& r) const noexcept
4✔
404
    {
405
      return !r.data.empty();
4✔
406
    }
407
    bool operator()(const AudioFile::MmapView& r) const noexcept
43✔
408
    {
409
      return r.wav.channels() > 0;
43✔
410
    }
411
  } _;
412
  return ossia::apply(_, static_cast<const view_impl_t&>(*this));
47✔
413
}
414

415
bool AudioFile::ViewHandle::build_summary(WaveformSummary& s) noexcept
47✔
416
{
417
  bool ok = false;
47✔
418
  SummaryBuilder _{s, ok};
47✔
419
  ossia::visit(_, *this);
47✔
420
  return ok;
47✔
421
}
422

423
void AudioFile::ViewHandle::minmax_frame(
20,322✔
424
    int64_t start_frame, int64_t end_frame,
425
    ossia::small_vector<FloatPair, 8>& out) noexcept
426
{
427
  if(summary_minmax(start_frame, end_frame, out))
20,322✔
428
    return;
15,935✔
429

430
  FrameComputer<MinMax, FloatPair> _{start_frame, end_frame, out};
4,387✔
431
  ossia::visit(_, *this);
4,387✔
432
}
20,322✔
433
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc