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

kunitoki / VelociLoops / 25340587988

04 May 2026 08:02PM UTC coverage: 74.725% (-19.2%) from 93.961%
25340587988

Pull #2

github

kunitoki
More coverage fixes
Pull Request #2: Onset detection and save

292 of 337 new or added lines in 1 file covered. (86.65%)

19 existing lines in 1 file now uncovered.

4482 of 5998 relevant lines covered (74.72%)

2566822.17 hits per line

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

92.65
/src/velociloops.cpp
1
#include "velociloops.h"
2

3
#include <algorithm>
4
#include <cassert>
5
#include <cmath>
6
#include <cstdint>
7
#include <cstring>
8
#include <fstream>
9
#include <limits>
10
#include <memory>
11
#include <new>
12
#include <string>
13
#include <vector>
14

15

16
/* -----------------------------------------------------------------------
17
   DWOP decompressor
18
   ----------------------------------------------------------------------- */
19

20
namespace
21
{
22

23
#include "fft8g.h"
24

25
inline int32_t clampInt32(int64_t v)
43,070,488✔
26
{
27
    if (v > std::numeric_limits<int32_t>::max())
43,070,488✔
28
        return std::numeric_limits<int32_t>::max();
×
29

30
    if (v < std::numeric_limits<int32_t>::min())
43,070,488✔
31
        return std::numeric_limits<int32_t>::min();
×
32

33
    return static_cast<int32_t>(v);
43,070,488✔
34
}
35

36
inline int32_t addInt32(int32_t a, int32_t b)
18,171,940✔
37
{
38
    return clampInt32(static_cast<int64_t>(a) + static_cast<int64_t>(b));
18,171,940✔
39
}
40

41
inline int32_t subInt32(int32_t a, int32_t b)
24,898,548✔
42
{
43
    return clampInt32(static_cast<int64_t>(a) - static_cast<int64_t>(b));
24,898,548✔
44
}
45

46
struct VLChannelState
47
{
48
    int32_t deltas[5] = {0, 0, 0, 0, 0};
49
    uint32_t averages[5] = {2560, 2560, 2560, 2560, 2560};
50
};
51

52
class VLDWOPDecompressor
53
{
54
public:
55
    VLChannelState ch[2];
56
    uint32_t currentWord = 0;
57
    int32_t bitsLeft = 0;
58
    const uint32_t* inputPtr = nullptr;
59
    const uint32_t* endPtr = nullptr;
60
    std::vector<uint32_t> buf;
61

62
    void init(const uint8_t* data, size_t size)
90✔
63
    {
64
        const size_t words = size / 4;
90✔
65
        buf.resize(words);
90✔
66

67
        for (size_t i = 0; i < words; ++i)
2,678,472✔
68
        {
69
            const size_t b = i * 4;
2,678,382✔
70
            buf[i] = ((uint32_t)data[b] << 24) | ((uint32_t)data[b + 1] << 16) | ((uint32_t)data[b + 2] << 8) | (uint32_t)data[b + 3];
2,678,382✔
71
        }
72

73
        inputPtr = buf.data();
90✔
74
        endPtr = buf.data() + buf.size();
90✔
75
        currentWord = 0;
90✔
76
        bitsLeft = 0;
90✔
77
    }
90✔
78

79
    bool decompressMono(uint32_t frameCount, int32_t* out, int32_t bitDepth)
77✔
80
    {
81
        if (!out || frameCount == 0)
77✔
82
            return false;
×
83

84
        int32_t d0 = ch[0].deltas[0] * 2, d1 = ch[0].deltas[1] * 2, d2 = ch[0].deltas[2] * 2, d3 = ch[0].deltas[3] * 2, d4 = ch[0].deltas[4] * 2;
77✔
85

86
        uint32_t a0 = ch[0].averages[0], a1 = ch[0].averages[1], a2 = ch[0].averages[2], a3 = ch[0].averages[3], a4 = ch[0].averages[4];
77✔
87

88
        uint32_t j = 2;
77✔
89
        int32_t rbits = 0;
77✔
90

91
        uint32_t cw = currentWord;
77✔
92
        int32_t bl = bitsLeft;
77✔
93
        const uint32_t* inp = inputPtr;
77✔
94
        bool eof = false;
77✔
95

96
        for (uint32_t f = 0; f < frameCount && !eof; ++f)
9,042,993✔
97
        {
98
            uint32_t minAvg = a0;
9,042,916✔
99
            int minIdx = 0;
9,042,916✔
100

101
            if (a1 < minAvg)
9,042,916✔
102
            {
103
                minAvg = a1;
8,435,265✔
104
                minIdx = 1;
8,435,265✔
105
            }
106

107
            if (a2 < minAvg)
9,042,916✔
108
            {
109
                minAvg = a2;
3,873,454✔
110
                minIdx = 2;
3,873,454✔
111
            }
112

113
            if (a3 < minAvg)
9,042,916✔
114
            {
115
                minAvg = a3;
2,367,280✔
116
                minIdx = 3;
2,367,280✔
117
            }
118

119
            if (a4 < minAvg)
9,042,916✔
120
            {
121
                minAvg = a4;
1,505,454✔
122
                minIdx = 4;
1,505,454✔
123
            }
124

125
            uint32_t step = ((minAvg * 3u) + 36u) >> 7;
9,042,916✔
126
            uint32_t prefixSum = 0;
9,042,916✔
127
            int zerosWin = 7;
9,042,916✔
128

129
            while (true)
130
            {
131
                bool bit = readBit(cw, bl, inp, eof);
16,827,779✔
132

133
                if (eof)
16,827,779✔
134
                    break;
×
135

136
                if (bit)
16,827,779✔
137
                    break;
9,042,916✔
138

139
                if (step > 0 && prefixSum > 0xFFFFFFFFu - step)
7,784,863✔
140
                {
141
                    eof = true;
×
142
                    break;
×
143
                }
144

145
                prefixSum += step;
7,784,863✔
146
                if (--zerosWin == 0)
7,784,863✔
147
                {
148
                    step <<= 2;
49,191✔
149
                    zerosWin = 7;
49,191✔
150
                }
151
            }
7,784,863✔
152

153
            if (eof)
9,042,916✔
154
                break;
×
155

156
            adjustJRbits(step, j, rbits);
9,042,916✔
157

158
            uint32_t rem = (rbits > 0) ? readBits(rbits, cw, bl, inp, eof) : 0;
9,042,916✔
159
            if (eof)
9,042,916✔
160
                break;
×
161

162
            const int64_t thresh = static_cast<int64_t>(j) - static_cast<int64_t>(step);
9,042,916✔
163
            if (static_cast<int64_t>(rem) - thresh >= 0)
9,042,916✔
164
            {
165
                const uint32_t extra = readBits(1, cw, bl, inp, eof);
4,408,233✔
166

167
                if (eof)
4,408,233✔
168
                    break;
×
169

170
                rem = rem * 2u - static_cast<uint32_t>(thresh) + extra;
4,408,233✔
171
            }
172

173
            const uint32_t codeVal = rem + prefixSum;
9,042,916✔
174
            const int32_t signed2x = -(int32_t)(codeVal & 1u) ^ (int32_t)codeVal;
9,042,916✔
175
            const int32_t s2x = applyPredictor(minIdx, signed2x, d0, d1, d2, d3, d4);
9,042,916✔
176
            out[f] = clampSample(s2x >> 1, bitDepth);
9,042,916✔
177
            updateAverages(a0, a1, a2, a3, a4, d0, d1, d2, d3, d4);
9,042,916✔
178
        }
179

180
        ch[0].deltas[0] = d0 >> 1;
77✔
181
        ch[0].deltas[1] = d1 >> 1;
77✔
182
        ch[0].deltas[2] = d2 >> 1;
77✔
183
        ch[0].deltas[3] = d3 >> 1;
77✔
184
        ch[0].deltas[4] = d4 >> 1;
77✔
185
        ch[0].averages[0] = a0;
77✔
186
        ch[0].averages[1] = a1;
77✔
187
        ch[0].averages[2] = a2;
77✔
188
        ch[0].averages[3] = a3;
77✔
189
        ch[0].averages[4] = a4;
77✔
190
        currentWord = cw;
77✔
191
        bitsLeft = bl;
77✔
192
        inputPtr = inp;
77✔
193
        return !eof;
77✔
194
    }
195

196
    bool decompressStereo(uint32_t frameCount, int32_t* out, int32_t bitDepth)
13✔
197
    {
198
        if (!out || frameCount == 0)
13✔
199
            return false;
×
200

201
        int32_t d[2][5];
202
        uint32_t a[2][5];
203
        for (int c = 0; c < 2; ++c)
39✔
204
        {
205
            for (int i = 0; i < 5; ++i)
156✔
206
            {
207
                d[c][i] = ch[c].deltas[i] * 2;
130✔
208
                a[c][i] = ch[c].averages[i];
130✔
209
            }
210
        }
211

212
        uint32_t j[2] = {2, 2};
13✔
213
        int32_t rbits[2] = {0, 0};
13✔
214

215
        uint32_t cw = currentWord;
13✔
216
        int32_t bl = bitsLeft;
13✔
217
        const uint32_t* inp = inputPtr;
13✔
218
        bool eof = false;
13✔
219

220
        for (uint32_t f = 0; f < frameCount && !eof; ++f)
862,366✔
221
        {
222
            int32_t ch2x[2] = {0, 0};
862,353✔
223
            for (int c = 0; c < 2 && !eof; ++c)
2,587,059✔
224
            {
225
                uint32_t minAvg = a[c][0];
1,724,706✔
226
                int minIdx = 0;
1,724,706✔
227
                if (a[c][1] < minAvg)
1,724,706✔
228
                {
229
                    minAvg = a[c][1];
1,277,134✔
230
                    minIdx = 1;
1,277,134✔
231
                }
232

233
                if (a[c][2] < minAvg)
1,724,706✔
234
                {
235
                    minAvg = a[c][2];
452,099✔
236
                    minIdx = 2;
452,099✔
237
                }
238

239
                if (a[c][3] < minAvg)
1,724,706✔
240
                {
241
                    minAvg = a[c][3];
134,524✔
242
                    minIdx = 3;
134,524✔
243
                }
244

245
                if (a[c][4] < minAvg)
1,724,706✔
246
                {
247
                    minAvg = a[c][4];
126,522✔
248
                    minIdx = 4;
126,522✔
249
                }
250

251
                uint32_t step = ((minAvg * 3u) + 36u) >> 7;
1,724,706✔
252
                uint32_t prefixSum = 0;
1,724,706✔
253
                int zerosWin = 7;
1,724,706✔
254

255
                while (true)
256
                {
257
                    bool bit = readBit(cw, bl, inp, eof);
2,937,765✔
258

259
                    if (eof)
2,937,765✔
260
                        break;
×
261

262
                    if (bit)
2,937,765✔
263
                        break;
1,724,706✔
264

265
                    if (step > 0 && prefixSum > 0xFFFFFFFFu - step)
1,213,059✔
266
                    {
267
                        eof = true;
×
268
                        break;
×
269
                    }
270

271
                    prefixSum += step;
1,213,059✔
272
                    if (--zerosWin == 0)
1,213,059✔
273
                    {
274
                        step <<= 2;
1,125✔
275
                        zerosWin = 7;
1,125✔
276
                    }
277
                }
1,213,059✔
278

279
                if (eof)
1,724,706✔
280
                    break;
×
281

282
                adjustJRbits(step, j[c], rbits[c]);
1,724,706✔
283

284
                uint32_t rem = (rbits[c] > 0) ? readBits(rbits[c], cw, bl, inp, eof) : 0;
1,724,706✔
285
                if (eof)
1,724,706✔
286
                    break;
×
287

288
                const int64_t thresh = static_cast<int64_t>(j[c]) - static_cast<int64_t>(step);
1,724,706✔
289
                if (static_cast<int64_t>(rem) - thresh >= 0)
1,724,706✔
290
                {
291
                    const uint32_t extra = readBits(1, cw, bl, inp, eof);
700,489✔
292

293
                    if (eof)
700,489✔
294
                        break;
×
295

296
                    rem = rem * 2u - static_cast<uint32_t>(thresh) + extra;
700,489✔
297
                }
298

299
                const uint32_t codeVal = rem + prefixSum;
1,724,706✔
300
                const int32_t signed2x = -(int32_t)(codeVal & 1u) ^ (int32_t)codeVal;
1,724,706✔
301
                ch2x[c] = applyPredictor(minIdx, signed2x, d[c][0], d[c][1], d[c][2], d[c][3], d[c][4]);
1,724,706✔
302
                updateAverages(a[c][0], a[c][1], a[c][2], a[c][3], a[c][4], d[c][0], d[c][1], d[c][2], d[c][3], d[c][4]);
1,724,706✔
303
            }
304

305
            if (eof)
862,353✔
306
                break;
×
307

308
            out[f * 2 + 0] = clampSample(ch2x[0] >> 1, bitDepth);
862,353✔
309
            out[f * 2 + 1] = clampSample(((int64_t)ch2x[0] + (int64_t)ch2x[1]) >> 1, bitDepth);
862,353✔
310
        }
311

312
        for (int c = 0; c < 2; ++c)
39✔
313
        {
314
            for (int i = 0; i < 5; ++i)
156✔
315
            {
316
                ch[c].deltas[i] = d[c][i] >> 1;
130✔
317
                ch[c].averages[i] = a[c][i];
130✔
318
            }
319
        }
320
        currentWord = cw;
13✔
321
        bitsLeft = bl;
13✔
322
        inputPtr = inp;
13✔
323
        return !eof;
13✔
324
    }
325

326
private:
327
    static int32_t clampSample(int64_t v, int32_t bitDepth)
10,767,622✔
328
    {
329
        const int32_t maxSample = bitDepth == 24 ? 8388607 : 32767;
10,767,622✔
330
        const int32_t minSample = bitDepth == 24 ? -8388608 : -32768;
10,767,622✔
331

332
        if (v > maxSample)
10,767,622✔
333
            return maxSample;
×
334

335
        if (v < minSample)
10,767,622✔
336
            return minSample;
×
337

338
        return (int32_t)v;
10,767,622✔
339
    }
340

341
    bool readBit(uint32_t& cw, int32_t& bl, const uint32_t*& inp, bool& eof)
19,765,544✔
342
    {
343
        const int32_t blBefore = bl--;
19,765,544✔
344
        if (blBefore - 1 < 0)
19,765,544✔
345
        {
346
            if (inp >= endPtr)
621,351✔
347
            {
348
                eof = true;
×
349
                return false;
×
350
            }
351

352
            cw = *inp++;
621,351✔
353
            bl = 0x1f;
621,351✔
354
        }
355

356
        const bool bit = (int32_t)cw < 0;
19,765,544✔
357
        cw <<= 1;
19,765,544✔
358
        return bit;
19,765,544✔
359
    }
360

361
    uint32_t readBits(int n, uint32_t& cw, int32_t& bl, const uint32_t*& inp, bool& eof)
15,039,191✔
362
    {
363
        if (n <= 0 || n > 31)
15,039,191✔
364
        {
365
            eof = true;
×
366
            return 0;
×
367
        }
368

369
        uint32_t result = cw >> (32 - n);
15,039,191✔
370
        cw <<= n;
15,039,191✔
371

372
        const int32_t blBefore = bl;
15,039,191✔
373
        bl -= n;
15,039,191✔
374

375
        if (blBefore - n < 0)
15,039,191✔
376
        {
377
            if (inp >= endPtr)
2,056,982✔
378
            {
379
                eof = true;
×
380
                return result;
×
381
            }
382

383
            const uint32_t next = *inp++;
2,056,982✔
384
            bl += 32;
2,056,982✔
385
            result |= next >> bl;
2,056,982✔
386
            cw = next << (32 - bl);
2,056,982✔
387
        }
388

389
        return result;
15,039,191✔
390
    }
391

392
    static void adjustJRbits(uint32_t step, uint32_t& j, int32_t& rbits)
10,767,622✔
393
    {
394
        if (step < j)
10,767,622✔
395
        {
396
            for (uint32_t jt = j >> 1; step < jt; jt >>= 1)
10,801,553✔
397
            {
398
                j = jt;
191,635✔
399
                --rbits;
191,635✔
400
            }
401
        }
402
        else
403
        {
404
            while (step >= j)
349,842✔
405
            {
406
                const uint32_t prev = j;
192,138✔
407

408
                j <<= 1;
192,138✔
409
                ++rbits;
192,138✔
410

411
                if (j <= prev)
192,138✔
412
                {
413
                    j = prev;
×
414
                    break;
×
415
                }
416
            }
417
        }
418
    }
10,767,622✔
419

420
    static int32_t applyPredictor(int idx, int32_t s2x, int32_t& d0, int32_t& d1, int32_t& d2, int32_t& d3, int32_t& d4)
10,767,622✔
421
    {
422
        switch (idx)
10,767,622✔
423
        {
424
            case 0:
1,055,222✔
425
            {
426
                const int32_t t0 = subInt32(s2x, d0), t1 = subInt32(t0, d1), t2 = subInt32(t1, d2);
1,055,222✔
427
                d4 = subInt32(t2, d3);
1,055,222✔
428
                d3 = t2;
1,055,222✔
429
                d2 = t1;
1,055,222✔
430
                d1 = t0;
1,055,222✔
431
                d0 = s2x;
1,055,222✔
432
                return s2x;
1,055,222✔
433
            }
434

435
            case 1:
5,386,705✔
436
            {
437
                const int32_t t1 = subInt32(s2x, d1), t2 = subInt32(t1, d2), nd0 = addInt32(d0, s2x);
5,386,705✔
438
                d4 = subInt32(t2, d3);
5,386,705✔
439
                d3 = t2;
5,386,705✔
440
                d2 = t1;
5,386,705✔
441
                d1 = s2x;
5,386,705✔
442
                d0 = nd0;
5,386,705✔
443
                return nd0;
5,386,705✔
444
            }
445

446
            case 2:
1,823,826✔
447
            {
448
                const int32_t nd1 = addInt32(d1, s2x), nd0 = addInt32(d0, nd1), t = subInt32(s2x, d2);
1,823,826✔
449
                d4 = subInt32(t, d3);
1,823,826✔
450
                d3 = t;
1,823,826✔
451
                d2 = s2x;
1,823,826✔
452
                d1 = nd1;
1,823,826✔
453
                d0 = nd0;
1,823,826✔
454
                return nd0;
1,823,826✔
455
            }
456

457
            case 3:
869,893✔
458
            {
459
                const int32_t nd2 = addInt32(d2, s2x), nd1 = addInt32(d1, nd2), nd0 = addInt32(d0, nd1);
869,893✔
460
                d4 = subInt32(s2x, d3);
869,893✔
461
                d3 = s2x;
869,893✔
462
                d2 = nd2;
869,893✔
463
                d1 = nd1;
869,893✔
464
                d0 = nd0;
869,893✔
465
                return nd0;
869,893✔
466
            }
467

468
            case 4:
1,631,976✔
469
            {
470
                const int32_t nd3 = addInt32(d3, s2x), nd2 = addInt32(d2, nd3), nd1 = addInt32(d1, nd2), nd0 = addInt32(d0, nd1);
1,631,976✔
471
                d4 = s2x;
1,631,976✔
472
                d3 = nd3;
1,631,976✔
473
                d2 = nd2;
1,631,976✔
474
                d1 = nd1;
1,631,976✔
475
                d0 = nd0;
1,631,976✔
476
                return nd0;
1,631,976✔
477
            }
478

479
            default: return d0; // LCOV_EXCL_LINE idx comes from a 0..4 minimum search.
480
        }
481
    }
482

483
    static void updateAverages(uint32_t& a0, uint32_t& a1, uint32_t& a2, uint32_t& a3, uint32_t& a4, int32_t d0, int32_t d1, int32_t d2, int32_t d3, int32_t d4)
10,767,622✔
484
    {
485
        auto mag = [](int32_t v) -> uint32_t
53,838,110✔
486
        {
487
            return (uint32_t)(v ^ (v >> 31));
53,838,110✔
488
        };
489

490
        a0 = a0 + mag(d0) - (a0 >> 5);
10,767,622✔
491
        a1 = a1 + mag(d1) - (a1 >> 5);
10,767,622✔
492
        a2 = a2 + mag(d2) - (a2 >> 5);
10,767,622✔
493
        a3 = a3 + mag(d3) - (a3 >> 5);
10,767,622✔
494
        a4 = a4 + mag(d4) - (a4 >> 5);
10,767,622✔
495
    }
10,767,622✔
496
};
497

498
class VLBitWriter
499
{
500
public:
501
    void writeBit(bool bit)
96,812,128✔
502
    {
503
        if (bit)
96,812,128✔
504
            currentWord |= (uint32_t)1u << (31 - bitCount);
50,124,810✔
505
        if (++bitCount == 32)
96,812,128✔
506
            flushWord();
3,025,316✔
507
    }
96,812,128✔
508

509
    void writeBits(uint32_t value, int count)
11,193,023✔
510
    {
511
        for (int i = count - 1; i >= 0; --i)
79,438,526✔
512
            writeBit(((value >> i) & 1u) != 0);
68,245,503✔
513
    }
11,193,023✔
514

515
    std::vector<uint8_t> finish()
116✔
516
    {
517
        if (bitCount > 0)
116✔
518
            flushWord();
110✔
519

520
        return bytes;
116✔
521
    }
522

523
private:
524
    std::vector<uint8_t> bytes;
525
    uint32_t currentWord = 0;
526
    int bitCount = 0;
527

528
    void flushWord()
3,025,426✔
529
    {
530
        bytes.push_back((uint8_t)(currentWord >> 24));
3,025,426✔
531
        bytes.push_back((uint8_t)(currentWord >> 16));
3,025,426✔
532
        bytes.push_back((uint8_t)(currentWord >> 8));
3,025,426✔
533
        bytes.push_back((uint8_t)currentWord);
3,025,426✔
534
        currentWord = 0;
3,025,426✔
535
        bitCount = 0;
3,025,426✔
536
    }
3,025,426✔
537
};
538

539
class VLDWOPCompressor
540
{
541
public:
542
    VLChannelState ch[2];
543

544
    std::vector<uint8_t> compressMono(const int32_t* in, uint32_t frameCount)
103✔
545
    {
546
        reset();
103✔
547

548
        VLBitWriter bw;
103✔
549
        int32_t d[5] = {};
103✔
550
        uint32_t a[5] = {2560, 2560, 2560, 2560, 2560};
103✔
551
        uint32_t j = 2;
103✔
552
        int32_t rbits = 0;
103✔
553

554
        for (uint32_t f = 0; f < frameCount; ++f)
11,102,791✔
555
            encodeChannel(in[f] * 2, d, a, j, rbits, bw);
11,102,688✔
556

557
        return bw.finish();
206✔
558
    }
103✔
559

560
    std::vector<uint8_t> compressStereo(const int32_t* in, uint32_t frameCount)
13✔
561
    {
562
        reset();
13✔
563

564
        VLBitWriter bw;
13✔
565
        int32_t d[2][5] = {};
13✔
566
        uint32_t a[2][5] = {{2560, 2560, 2560, 2560, 2560}, {2560, 2560, 2560, 2560, 2560}};
13✔
567
        uint32_t j[2] = {2, 2};
13✔
568
        int32_t rbits[2] = {0, 0};
13✔
569

570
        for (uint32_t f = 0; f < frameCount; ++f)
838,011✔
571
        {
572
            const int32_t left2x = in[(size_t)f * 2] * 2;
837,998✔
573
            const int32_t right2x = in[(size_t)f * 2 + 1] * 2;
837,998✔
574
            encodeChannel(left2x, d[0], a[0], j[0], rbits[0], bw);
837,998✔
575
            encodeChannel(right2x - left2x, d[1], a[1], j[1], rbits[1], bw);
837,998✔
576
        }
577

578
        return bw.finish();
26✔
579
    }
13✔
580

581
private:
582
    void reset()
116✔
583
    {
584
        for (auto& c : ch)
348✔
585
        {
586
            std::fill(c.deltas, c.deltas + 5, 0);
232✔
587
            std::fill(c.averages, c.averages + 5, 2560);
232✔
588
        }
589
    }
116✔
590

591
    static uint32_t toCodeValue(int32_t signed2x)
12,778,684✔
592
    {
593
        if (signed2x >= 0)
12,778,684✔
594
            return (uint32_t)signed2x;
7,247,474✔
595

596
        return (uint32_t)(-signed2x - 1);
5,531,210✔
597
    }
598

599
    static int minAverageIndex(const uint32_t a[5])
12,778,684✔
600
    {
601
        int idx = 0;
12,778,684✔
602

603
        for (int i = 1; i < 5; ++i)
63,893,420✔
604
        {
605
            if (a[i] < a[idx])
51,114,736✔
606
                idx = i;
20,550,229✔
607
        }
608

609
        return idx;
12,778,684✔
610
    }
611

612
    static int32_t predictorResidual(int idx, int32_t sample2x, const int32_t d[5])
12,778,684✔
613
    {
614
        switch (idx)
12,778,684✔
615
        {
616
            case 0: return sample2x;
1,816,958✔
617

618
            case 1: return sample2x - d[0];
6,045,332✔
619

620
            case 2: return sample2x - d[0] - d[1];
2,053,928✔
621

622
            case 3: return sample2x - d[0] - d[1] - d[2];
1,052,599✔
623

624
            case 4: return sample2x - d[0] - d[1] - d[2] - d[3];
1,809,867✔
625

626
            default: return sample2x; // LCOV_EXCL_LINE idx comes from a 0..4 minimum search.
627
        }
628
    }
629

630
    static int32_t applyPredictor(int idx, int32_t s2x, int32_t d[5])
12,778,684✔
631
    {
632
        switch (idx)
12,778,684✔
633
        {
634
            case 0:
1,816,958✔
635
            {
636
                const int32_t t0 = s2x - d[0], t1 = t0 - d[1], t2 = t1 - d[2];
1,816,958✔
637
                d[4] = t2 - d[3];
1,816,958✔
638
                d[3] = t2;
1,816,958✔
639
                d[2] = t1;
1,816,958✔
640
                d[1] = t0;
1,816,958✔
641
                d[0] = s2x;
1,816,958✔
642
                return s2x;
1,816,958✔
643
            }
644

645
            case 1:
6,045,332✔
646
            {
647
                const int32_t t1 = s2x - d[1], t2 = t1 - d[2], nd0 = d[0] + s2x;
6,045,332✔
648
                d[4] = t2 - d[3];
6,045,332✔
649
                d[3] = t2;
6,045,332✔
650
                d[2] = t1;
6,045,332✔
651
                d[1] = s2x;
6,045,332✔
652
                d[0] = nd0;
6,045,332✔
653
                return nd0;
6,045,332✔
654
            }
655

656
            case 2:
2,053,928✔
657
            {
658
                const int32_t nd1 = d[1] + s2x, nd0 = d[0] + nd1, t = s2x - d[2];
2,053,928✔
659
                d[4] = t - d[3];
2,053,928✔
660
                d[3] = t;
2,053,928✔
661
                d[2] = s2x;
2,053,928✔
662
                d[1] = nd1;
2,053,928✔
663
                d[0] = nd0;
2,053,928✔
664
                return nd0;
2,053,928✔
665
            }
666

667
            case 3:
1,052,599✔
668
            {
669
                const int32_t nd2 = d[2] + s2x, nd1 = d[1] + nd2, nd0 = d[0] + nd1;
1,052,599✔
670
                d[4] = s2x - d[3];
1,052,599✔
671
                d[3] = s2x;
1,052,599✔
672
                d[2] = nd2;
1,052,599✔
673
                d[1] = nd1;
1,052,599✔
674
                d[0] = nd0;
1,052,599✔
675
                return nd0;
1,052,599✔
676
            }
677

678
            case 4:
1,809,867✔
679
            {
680
                const int32_t nd3 = d[3] + s2x, nd2 = d[2] + nd3, nd1 = d[1] + nd2, nd0 = d[0] + nd1;
1,809,867✔
681
                d[4] = s2x;
1,809,867✔
682
                d[3] = nd3;
1,809,867✔
683
                d[2] = nd2;
1,809,867✔
684
                d[1] = nd1;
1,809,867✔
685
                d[0] = nd0;
1,809,867✔
686
                return nd0;
1,809,867✔
687
            }
688

689
            default: return d[0]; // LCOV_EXCL_LINE idx comes from a 0..4 minimum search.
690
        }
691
    }
692

693
    static void updateAverages(uint32_t a[5], const int32_t d[5])
12,778,684✔
694
    {
695
        auto mag = [](int32_t v) -> uint32_t
63,893,420✔
696
        {
697
            return (uint32_t)(v ^ (v >> 31));
63,893,420✔
698
        };
699

700
        for (int i = 0; i < 5; ++i)
76,672,104✔
701
        {
702
            a[i] = a[i] + mag(d[i]) - (a[i] >> 5);
63,893,420✔
703
        }
704
    }
12,778,684✔
705

706
    static void adjustJRbits(uint32_t step, uint32_t& j, int32_t& rbits)
22,935,373✔
707
    {
708
        if (step < j)
22,935,373✔
709
        {
710
            for (uint32_t jt = j >> 1; step < jt; jt >>= 1)
23,371,772✔
711
            {
712
                j = jt;
819,278✔
713
                --rbits;
819,278✔
714
            }
715
        }
716
        else
717
        {
718
            while (step >= j)
878,476✔
719
            {
720
                const uint32_t prev = j;
495,597✔
721

722
                j <<= 1;
495,597✔
723
                ++rbits;
495,597✔
724

725
                if (j <= prev)
495,597✔
726
                {
727
                    j = prev;
×
728
                    break;
×
729
                }
730
            }
731
        }
732
    }
22,935,373✔
733

734
    static bool encodeRemainder(uint32_t raw, uint32_t step, uint32_t j, int32_t rbits, uint32_t& remBits, bool& hasExtra, bool& extraBit)
22,935,373✔
735
    {
736
        if (rbits < 0 || rbits > 31)
22,935,373✔
737
            return false;
×
738

739
        const uint32_t limit = rbits == 31 ? 0x80000000u : (1u << rbits);
22,935,373✔
740
        const int32_t threshSigned = (int32_t)j - (int32_t)step;
22,935,373✔
741
        if (threshSigned < 0)
22,935,373✔
742
            return false;
×
743

744
        const uint32_t thresh = (uint32_t)threshSigned;
22,935,373✔
745

746
        if (raw < thresh)
22,935,373✔
747
        {
748
            if (raw >= limit)
7,147,432✔
749
                return false;
×
750

751
            remBits = raw;
7,147,432✔
752
            hasExtra = false;
7,147,432✔
753
            extraBit = false;
7,147,432✔
754
            return true;
7,147,432✔
755
        }
756

757
        const uint32_t folded = raw + thresh;
15,787,941✔
758
        remBits = folded >> 1;
15,787,941✔
759
        hasExtra = true;
15,787,941✔
760
        extraBit = (folded & 1u) != 0;
15,787,941✔
761
        return remBits >= thresh && remBits < limit;
15,787,941✔
762
    }
763

764
    static void writeCodeValue(uint32_t codeVal, uint32_t baseStep, uint32_t& j, int32_t& rbits, VLBitWriter& bw)
12,778,684✔
765
    {
766
        uint32_t prefixSum = 0;
12,778,684✔
767
        uint32_t step = baseStep;
12,778,684✔
768
        int zerosWin = 7;
12,778,684✔
769

770
        for (uint32_t zeros = 0; zeros < 0x100000; ++zeros)
22,935,373✔
771
        {
772
            if (codeVal >= prefixSum)
22,935,373✔
773
            {
774
                uint32_t trialJ = j;
22,935,373✔
775
                int32_t trialRbits = rbits;
22,935,373✔
776
                adjustJRbits(step, trialJ, trialRbits);
22,935,373✔
777

778
                uint32_t remBits = 0;
22,935,373✔
779
                bool hasExtra = false;
22,935,373✔
780
                bool extraBit = false;
22,935,373✔
781
                if (encodeRemainder(codeVal - prefixSum, step, trialJ, trialRbits, remBits, hasExtra, extraBit))
22,935,373✔
782
                {
783
                    for (uint32_t i = 0; i < zeros; ++i)
22,935,373✔
784
                        bw.writeBit(false);
10,156,689✔
785

786
                    bw.writeBit(true);
12,778,684✔
787

788
                    if (trialRbits > 0)
12,778,684✔
789
                        bw.writeBits(remBits, trialRbits);
11,193,023✔
790

791
                    if (hasExtra)
12,778,684✔
792
                        bw.writeBit(extraBit);
5,631,252✔
793

794
                    j = trialJ;
12,778,684✔
795
                    rbits = trialRbits;
12,778,684✔
796
                    return;
12,778,684✔
797
                }
798
            }
799

800
            if (baseStep == 0)
10,156,689✔
801
                break;
×
802

803
            if (UINT32_MAX - prefixSum < step)
10,156,689✔
804
                break;
×
805

806
            prefixSum += step;
10,156,689✔
807
            if (prefixSum > codeVal && step != 0)
10,156,689✔
808
                break;
×
809

810
            if (--zerosWin == 0)
10,156,689✔
811
            {
812
                step <<= 2;
60,852✔
813
                zerosWin = 7;
60,852✔
814
            }
815
        }
816

817
        bw.writeBit(true); // LCOV_EXCL_LINE emergency fallback; valid sample residuals encode above.
818
    }
819

820
    static void encodeChannel(int32_t sample2x, int32_t d[5], uint32_t a[5], uint32_t& j, int32_t& rbits, VLBitWriter& bw)
12,778,684✔
821
    {
822
        const int idx = minAverageIndex(a);
12,778,684✔
823
        const uint32_t baseStep = ((a[idx] * 3u) + 36u) >> 7;
12,778,684✔
824
        const int32_t residual = predictorResidual(idx, sample2x, d);
12,778,684✔
825
        writeCodeValue(toCodeValue(residual), baseStep, j, rbits, bw);
12,778,684✔
826
        applyPredictor(idx, residual, d);
12,778,684✔
827
        updateAverages(a, d);
12,778,684✔
828
    }
12,778,684✔
829
};
830

831
class VLIFFWriter
832
{
833
public:
834
    std::vector<uint8_t> data;
835

836
    size_t beginChunk(const char id[4])
5,057✔
837
    {
838
        const size_t start = data.size();
5,057✔
839
        data.insert(data.end(), id, id + 4);
5,057✔
840
        put32(0);
5,057✔
841
        return start;
5,057✔
842
    }
843

844
    size_t beginCat(const char type[4])
348✔
845
    {
846
        const size_t start = beginChunk("CAT ");
348✔
847
        data.insert(data.end(), type, type + 4);
348✔
848
        return start;
348✔
849
    }
850

851
    void endChunk(size_t start)
5,057✔
852
    {
853
        const size_t payloadStart = start + 8;
5,057✔
854
        const uint32_t size = (uint32_t)(data.size() - payloadStart);
5,057✔
855

856
        data[start + 4] = (uint8_t)(size >> 24);
5,057✔
857
        data[start + 5] = (uint8_t)(size >> 16);
5,057✔
858
        data[start + 6] = (uint8_t)(size >> 8);
5,057✔
859
        data[start + 7] = (uint8_t)size;
5,057✔
860

861
        if (data.size() & 1u)
5,057✔
862
            data.push_back(0);
4,350✔
863
    }
5,057✔
864

865
    void put8(uint8_t v)
5,626✔
866
    {
867
        data.push_back(v);
5,626✔
868
    }
5,626✔
869

870
    void put16(uint16_t v)
4,582✔
871
    {
872
        data.push_back((uint8_t)(v >> 8));
4,582✔
873
        data.push_back((uint8_t)v);
4,582✔
874
    }
4,582✔
875

876
    void put32(uint32_t v)
13,580✔
877
    {
878
        data.push_back((uint8_t)(v >> 24));
13,580✔
879
        data.push_back((uint8_t)(v >> 16));
13,580✔
880
        data.push_back((uint8_t)(v >> 8));
13,580✔
881
        data.push_back((uint8_t)v);
13,580✔
882
    }
13,580✔
883

884
    void putBytes(const uint8_t* p, size_t n)
510✔
885
    {
886
        data.insert(data.end(), p, p + n);
510✔
887
    }
510✔
888
};
889

890
/* -----------------------------------------------------------------------
891
   REX2 file parser + DWOP decompressor wrapper
892
   ----------------------------------------------------------------------- */
893

894
enum VLSliceState
895
{
896
    kSliceNormal = 1,
897
    kSliceMuted = 2,
898
    kSliceLocked = 3
899
};
900

901
struct VLSliceEntry
902
{
903
    uint32_t ppq_pos = 0;
904
    uint32_t sample_length = 0;
905
    uint32_t rendered_length = 0;
906
    uint32_t sample_start = 0;
907
    uint32_t render_loop_start = 0;
908
    uint32_t render_loop_end = 0;
909
    float render_loop_volume_compensation = 1.0f;
910
    uint16_t points = 0x7fff;
911
    uint8_t selected_flag = 0;
912
    VLSliceState state = kSliceNormal;
913
    bool synthetic_leading = false;
914
    bool marker = false;
915
};
916

917
int32_t sliceFlags(const VLSliceEntry& s)
2,177✔
918
{
919
    int32_t flags = 0;
2,177✔
920
    if (s.state == kSliceMuted)
2,177✔
921
        flags |= VL_SLICE_FLAG_MUTED;
2✔
922
    else if (s.state == kSliceLocked)
2,175✔
923
        flags |= VL_SLICE_FLAG_LOCKED;
10✔
924
    if (s.selected_flag)
2,177✔
925
        flags |= VL_SLICE_FLAG_SELECTED;
67✔
926
    if (s.marker)
2,177✔
927
        flags |= VL_SLICE_FLAG_MARKER;
×
928
    if (s.synthetic_leading)
2,177✔
929
        flags |= VL_SLICE_FLAG_SYNTHETIC;
7✔
930
    return flags;
2,177✔
931
}
932

933
VLError applySliceFlags(VLSliceEntry& s, int32_t flags, int32_t analysisPoints)
6✔
934
{
935
    static constexpr int32_t kKnownFlags = VL_SLICE_FLAG_MUTED | VL_SLICE_FLAG_LOCKED | VL_SLICE_FLAG_SELECTED | VL_SLICE_FLAG_MARKER | VL_SLICE_FLAG_SYNTHETIC;
936

937
    if (flags & ~kKnownFlags)
6✔
938
        return VL_ERROR_INVALID_ARG;
1✔
939

940
    if ((flags & VL_SLICE_FLAG_MUTED) && (flags & VL_SLICE_FLAG_LOCKED))
5✔
941
        return VL_ERROR_INVALID_ARG;
1✔
942

943
    if (flags & VL_SLICE_FLAG_LOCKED)
4✔
944
        s.state = kSliceLocked;
1✔
945
    else if (flags & VL_SLICE_FLAG_MUTED)
3✔
946
        s.state = kSliceMuted;
1✔
947
    else
948
        s.state = kSliceNormal;
2✔
949

950
    s.selected_flag = (flags & VL_SLICE_FLAG_SELECTED) ? 1 : 0;
4✔
951

952
    if (analysisPoints >= 0)
4✔
953
    {
954
        if (analysisPoints > 0x7fff)
3✔
955
            return VL_ERROR_INVALID_ARG;
1✔
956
        s.points = (uint16_t)analysisPoints;
2✔
957
    }
958

959
    return VL_OK;
3✔
960
}
961

962
template <typename T> class VLHeapArray
963
{
964
public:
965
    VLHeapArray() = default;
244✔
966

967
    VLHeapArray(const VLHeapArray&) = delete;
968
    VLHeapArray& operator=(const VLHeapArray&) = delete;
969

970
    VLHeapArray(VLHeapArray&& o) noexcept : ptr_(std::move(o.ptr_)), size_(o.size_)
971
    {
972
        o.size_ = 0;
973
    }
974

975
    VLHeapArray& operator=(VLHeapArray&& o) noexcept
976
    {
977
        ptr_ = std::move(o.ptr_);
978
        size_ = o.size_;
979
        o.size_ = 0;
980
        return *this;
981
    }
982

983
    [[nodiscard]] bool assign(std::size_t n, T val) noexcept
100✔
984
    {
985
        if (n == 0)
100✔
986
        {
987
            ptr_.reset();
×
988
            size_ = 0;
×
989
            return true;
×
990
        }
991

992
        T* raw = new (std::nothrow) T[n];
100✔
993
        if (!raw)
100✔
994
            return false;
×
995

996
        std::fill_n(raw, n, val);
100✔
997
        ptr_.reset(raw);
100✔
998
        size_ = n;
100✔
999

1000
        return true;
100✔
1001
    }
1002

1003
    [[nodiscard]] bool resize(std::size_t n, T val) noexcept
55✔
1004
    {
1005
        if (n == size_)
55✔
1006
            return true;
×
1007

1008
        if (n == 0)
55✔
1009
        {
1010
            ptr_.reset();
×
1011
            size_ = 0;
×
1012
            return true;
×
1013
        }
1014

1015
        T* raw = new (std::nothrow) T[n];
55✔
1016
        if (!raw)
55✔
1017
            return false;
×
1018

1019
        const std::size_t keep = std::min(size_, n);
55✔
1020
        if (keep)
55✔
1021
            std::memcpy(raw, ptr_.get(), keep * sizeof(T));
5✔
1022

1023
        if (n > size_)
55✔
1024
            std::fill_n(raw + size_, n - size_, val);
55✔
1025

1026
        ptr_.reset(raw);
55✔
1027
        size_ = n;
55✔
1028
        return true;
55✔
1029
    }
1030

1031
    T& operator[](std::size_t i) noexcept
7,372,441✔
1032
    {
1033
        assert(i < size_);
7,372,441✔
1034
        return ptr_[i];
7,372,441✔
1035
    }
1036

1037
    const T& operator[](std::size_t i) const noexcept
9,661,894✔
1038
    {
1039
        assert(i < size_);
9,661,894✔
1040
        return ptr_[i];
9,661,894✔
1041
    }
1042

1043
    T* data() noexcept
116✔
1044
    {
1045
        return ptr_.get();
116✔
1046
    }
1047

1048
    const T* data() const noexcept
1049
    {
1050
        return ptr_.get();
1051
    }
1052

1053
    std::size_t size() const noexcept
9,664,234✔
1054
    {
1055
        return size_;
9,664,234✔
1056
    }
1057

1058
    bool empty() const noexcept
1,674✔
1059
    {
1060
        return size_ == 0;
1,674✔
1061
    }
1062

1063
    static constexpr std::size_t max_size() noexcept
1,112✔
1064
    {
1065
        return std::numeric_limits<std::size_t>::max() / sizeof(T);
1,112✔
1066
    }
1067

1068
private:
1069
    std::unique_ptr<T[]> ptr_;
1070
    std::size_t size_ = 0;
1071
};
1072

1073
class VLFileImpl
1074
{
1075
public:
1076
    VLFileInfo info = {};
1077
    VLCreatorInfo creator = {};
1078
    std::vector<VLSliceEntry> slices;
1079
    std::vector<uint8_t> fileData;
1080
    VLHeapArray<int32_t> pcm;
1081
    uint32_t totalFrames = 0;
1082
    uint32_t loopStart = 0;
1083
    uint32_t loopEnd = 0;
1084
    bool transientEnabled = true;
1085
    uint16_t transientAttack = 0x15;
1086
    uint16_t transientDecay = 0x3ff;
1087
    uint16_t transientStretch = 0x28;
1088
    uint16_t processingGain = 1000;
1089
    uint8_t analysisSensitivity = 0;
1090
    uint16_t gateSensitivity = 0;
1091
    bool silenceSelected = false;
1092
    bool headerValid = true;
1093
    VLError loadError = VL_OK;
1094
    bool loadedFromFile = false;
1095
    uint16_t globBars = 1;
1096
    uint8_t globBeats = 0;
1097

1098
    static constexpr int32_t kREXPPQ = 15360;
1099

1100
    bool loadFromBuffer(const char* buf, size_t size)
193✔
1101
    {
1102
        fileData.assign((const uint8_t*)buf, (const uint8_t*)buf + size);
193✔
1103

1104
        info.channels = 1;
193✔
1105
        info.sample_rate = 44100;
193✔
1106
        info.slice_count = 0;
193✔
1107
        info.tempo = 120000;
193✔
1108
        info.original_tempo = 120000;
193✔
1109
        info.ppq_length = 61440;
193✔
1110
        info.time_sig_num = 4;
193✔
1111
        info.time_sig_den = 4;
193✔
1112
        info.bit_depth = 16;
193✔
1113
        info.total_frames = 0;
193✔
1114
        info.loop_start = 0;
193✔
1115
        info.loop_end = 0;
193✔
1116
        info.processing_gain = processingGain;
193✔
1117
        info.transient_enabled = transientEnabled ? 1 : 0;
193✔
1118
        info.transient_attack = transientAttack;
193✔
1119
        info.transient_decay = transientDecay;
193✔
1120
        info.transient_stretch = transientStretch;
193✔
1121
        info.silence_selected = silenceSelected ? 1 : 0;
193✔
1122
        analysisSensitivity = 0;
193✔
1123
        gateSensitivity = 0;
193✔
1124
        headerValid = true;
193✔
1125
        loadError = VL_OK;
193✔
1126
        loadedFromFile = false;
193✔
1127
        globBars = 1;
193✔
1128
        globBeats = 0;
193✔
1129

1130
        std::memset(&creator, 0, sizeof(creator));
193✔
1131

1132
        if (fileData.size() < 12)
193✔
1133
            return fail(VL_ERROR_INVALID_SIZE);
1✔
1134

1135
        if (fileData[0] == 'F' && fileData[1] == 'O' && fileData[2] == 'R' && fileData[3] == 'M' && fileData[8] == 'A' && fileData[9] == 'I' &&
240✔
1136
            fileData[10] == 'F' && fileData[11] == 'F')
240✔
1137
        {
1138
            const bool ok = loadLegacyAIFF();
48✔
1139
            if (ok)
48✔
1140
                loadedFromFile = true;
10✔
1141
            return ok;
48✔
1142
        }
1143

1144
        if (fileData[0] != 'C' || fileData[1] != 'A' || fileData[2] != 'T' || fileData[3] != ' ')
144✔
1145
            return fail(VL_ERROR_FILE_CORRUPT);
16✔
1146

1147
        size_t dwopOffset = 0, dwopSize = 0;
128✔
1148
        bool hasDWOP = false;
128✔
1149

1150
        parseIFF(8 + 4, fileData.size(), dwopOffset, dwopSize, hasDWOP);
128✔
1151
        if (loadError != VL_OK)
128✔
1152
            return false;
26✔
1153

1154
        finalizeSlices();
102✔
1155

1156
        if (!headerValid)
102✔
1157
            return false;
×
1158

1159
        if (!hasDWOP || dwopSize == 0)
102✔
1160
            return fail(VL_ERROR_FILE_CORRUPT);
4✔
1161

1162
        if (dwopOffset + dwopSize > fileData.size())
98✔
1163
            return fail(VL_ERROR_INVALID_SIZE);
×
1164

1165
        if (totalFrames == 0)
98✔
1166
            return fail(VL_ERROR_INVALID_SIZE);
8✔
1167

1168
        // 3600 * 192000 = 691,200,000 — exactly 1 hour at 192 kHz max sample rate.
1169
        static constexpr uint32_t kMaxTotalFrames = 3600u * 192000u;
1170
        if (totalFrames > kMaxTotalFrames)
90✔
1171
            return fail(VL_ERROR_INVALID_SIZE);
×
1172

1173
        const size_t pcmElements = (size_t)totalFrames * (size_t)info.channels;
90✔
1174

1175
        if (!pcm.assign(pcmElements, 0))
90✔
1176
            return fail(VL_ERROR_OUT_OF_MEMORY);
×
1177

1178
        VLDWOPDecompressor dec;
90✔
1179
        dec.init(&fileData[dwopOffset], dwopSize);
90✔
1180

1181
        uint32_t done = 0;
90✔
1182
        bool ok = true;
90✔
1183
        while (done < totalFrames && ok)
180✔
1184
        {
1185
            const uint32_t chunk = std::min<uint32_t>(0x100000, totalFrames - done);
90✔
1186

1187
            if (info.channels == 1)
90✔
1188
                ok = dec.decompressMono(chunk, &pcm[done], info.bit_depth);
77✔
1189
            else
1190
                ok = dec.decompressStereo(chunk, &pcm[(size_t)done * 2], info.bit_depth);
13✔
1191

1192
            done += chunk;
90✔
1193
        }
1194

1195
        if (!ok)
90✔
1196
            return fail(VL_ERROR_FILE_CORRUPT);
×
1197

1198
        finalizeRenderedLengths();
90✔
1199
        loadedFromFile = true;
90✔
1200
        return true;
90✔
1201
    }
90✔
1202

1203
private:
1204
    bool fail(VLError error)
95✔
1205
    {
1206
        if (loadError == VL_OK)
95✔
1207
            loadError = error;
93✔
1208
        return false;
95✔
1209
    }
1210

1211
    static uint32_t be32(const uint8_t* p)
60,047✔
1212
    {
1213
        return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3];
60,047✔
1214
    }
1215

1216
    static uint16_t be16(const uint8_t* p)
771,518✔
1217
    {
1218
        return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
771,518✔
1219
    }
1220

1221
    static int32_t readAIFFExtendedRate(const uint8_t* p)
48✔
1222
    {
1223
        const uint16_t expon = be16(p);
48✔
1224
        uint64_t mant = 0;
48✔
1225
        for (int i = 0; i < 8; ++i)
432✔
1226
            mant = (mant << 8) | p[2 + i];
384✔
1227

1228
        if (expon == 0 || mant == 0)
48✔
1229
            return 0;
4✔
1230

1231
        const int sign = (expon & 0x8000u) ? -1 : 1;
44✔
1232
        const int exp = (int)(expon & 0x7fffu) - 16383;
44✔
1233
        const long double value = (long double)sign * (long double)mant * std::ldexp((long double)1.0, exp - 63);
44✔
1234
        if (value <= 0.0L || value > (long double)std::numeric_limits<int32_t>::max())
44✔
1235
            return 0;
×
1236

1237
        return (int32_t)std::lround((double)value);
44✔
1238
    }
1239

1240
    static int32_t readSignedSampleBE(const uint8_t* p, uint16_t bits)
964,919✔
1241
    {
1242
        switch (bits)
964,919✔
1243
        {
1244
            case 8: return (int32_t)(int8_t)p[0] * 256;
91,528✔
1245
            case 16: return (int16_t)be16(p);
766,609✔
1246
            case 24:
61,018✔
1247
            {
1248
                int32_t v = ((int32_t)p[0] << 16) | ((int32_t)p[1] << 8) | (int32_t)p[2];
61,018✔
1249
                if (v & 0x800000)
61,018✔
1250
                    v |= ~0xffffff;
32,554✔
1251
                return v;
61,018✔
1252
            }
1253
            case 32:
45,764✔
1254
            {
1255
                int32_t v = (int32_t)be32(p);
45,764✔
1256
                return v >> 16;
45,764✔
1257
            }
1258
            default: return 0;
×
1259
        }
1260
    }
1261

1262
    bool parseLegacyTempo(const uint8_t* d, uint32_t sz, bool appIsReCy)
44✔
1263
    {
1264
        const uint32_t tempoOffset = appIsReCy ? 14u : 16u;
44✔
1265
        if (sz < tempoOffset + 4u)
44✔
1266
            return false;
4✔
1267

1268
        const uint32_t v = be32(d + tempoOffset);
40✔
1269
        if (v < 20000u || v > 450000u)
40✔
1270
            return false;
4✔
1271

1272
        info.tempo = (int32_t)v;
36✔
1273
        info.original_tempo = (int32_t)v;
36✔
1274
        return true;
36✔
1275
    }
1276

1277
    static uint16_t legacyReCycleFilterPoints(uint16_t sensitivity)
9✔
1278
    {
1279
        const uint32_t sens = std::min<uint32_t>(sensitivity, 1000u);
9✔
1280
        const uint32_t visibleRange = (sens * 0x7fffu + 999u) / 1000u;
9✔
1281
        return (uint16_t)(0x7fffu - visibleRange);
9✔
1282
    }
1283

1284
    bool parseLegacyReCycleSlices(const uint8_t* d, uint32_t sz, std::vector<VLSliceEntry>& out)
41✔
1285
    {
1286
        if (sz < 4u + 0xa0u)
41✔
1287
            return false;
32✔
1288

1289
        const uint8_t* binary = d + 4;
9✔
1290
        const uint32_t binarySize = sz - 4u;
9✔
1291
        if (be32(binary) != 0xd1daded0u)
9✔
1292
            return false;
×
1293

1294
        const uint16_t sensitivity = be16(binary + 0x14);
9✔
1295
        const uint16_t filterPoints = legacyReCycleFilterPoints(sensitivity);
9✔
1296
        const uint16_t storedCount = be16(binary + 0x9e);
9✔
1297
        if (storedCount == 0 || storedCount > 1000)
9✔
1298
            return false;
×
1299

1300
        if (binarySize < 0xa0u + (uint32_t)storedCount * 8u)
9✔
1301
            return false;
×
1302

1303
        std::vector<VLSliceEntry> parsed;
9✔
1304
        parsed.reserve(storedCount);
9✔
1305
        for (uint16_t i = 0; i < storedCount; ++i)
186✔
1306
        {
1307
            const uint8_t* rec = binary + 0xa0u + (uint32_t)i * 8u;
177✔
1308
            const uint8_t state = rec[0] & 0x7f;
177✔
1309
            const bool selected = (rec[0] & 0x80) != 0;
177✔
1310
            const uint32_t start = ((uint32_t)rec[1] << 24) | ((uint32_t)rec[2] << 16) | ((uint32_t)rec[3] << 8) | rec[4];
177✔
1311
            const uint16_t points = be16(rec + 6);
177✔
1312

1313
            if (!selected && (state != 0 || points <= filterPoints))
177✔
1314
                continue;
107✔
1315

1316
            VLSliceEntry s;
70✔
1317
            s.sample_start = start;
70✔
1318
            s.sample_length = 1;
70✔
1319
            s.points = points;
70✔
1320
            s.selected_flag = selected ? 1 : 0;
70✔
1321
            if (state == 1)
70✔
1322
                s.state = kSliceLocked;
7✔
1323
            else if (state == 2)
63✔
1324
                s.state = kSliceMuted;
×
1325
            else
1326
                s.state = kSliceNormal;
63✔
1327
            parsed.push_back(s);
70✔
1328
        }
1329

1330
        if (parsed.empty())
9✔
1331
            return false;
2✔
1332

1333
        out = std::move(parsed);
7✔
1334
        return true;
7✔
1335
    }
9✔
1336

1337
    bool parseLegacyREXSlices(const uint8_t* d, uint32_t sz, std::vector<VLSliceEntry>& out, uint32_t& ppqLength,
3✔
1338
                              uint32_t& exportedFrameCount)
1339
    {
1340
        if (sz < 4u + 0x3f8u)
3✔
1341
            return false;
×
1342

1343
        const uint8_t* binary = d + 4;
3✔
1344
        const uint32_t binarySize = sz - 4u;
3✔
1345
        if (be32(binary) != 0xd1d1d1dau)
3✔
1346
            return false;
×
1347

1348
        const uint32_t storedPpqLength = be32(binary + 6);
3✔
1349
        if (storedPpqLength == 0 || storedPpqLength > std::numeric_limits<uint32_t>::max() / 16u)
3✔
1350
            return false;
×
1351

1352
        const uint16_t storedCount = be16(binary + 0x0a);
3✔
1353
        if (storedCount == 0 || storedCount > 1000)
3✔
1354
            return false;
×
1355

1356
        if (binarySize < 0x3f8u + (uint32_t)storedCount * 12u)
3✔
1357
            return false;
×
1358

1359
        std::vector<VLSliceEntry> parsed;
3✔
1360
        std::vector<uint32_t> ppqPositions;
3✔
1361
        std::vector<uint32_t> sourceLengths;
3✔
1362
        parsed.reserve(storedCount);
3✔
1363
        ppqPositions.reserve(storedCount);
3✔
1364
        sourceLengths.reserve(storedCount);
3✔
1365
        for (uint16_t i = 0; i < storedCount; ++i)
33✔
1366
        {
1367
            const uint8_t* rec = binary + 0x3f8u + (uint32_t)i * 12u;
30✔
1368
            const uint32_t start = be32(rec);
30✔
1369
            const uint32_t length = be32(rec + 4);
30✔
1370
            const uint32_t ppq16 = be32(rec + 8);
30✔
1371
            if (ppq16 > storedPpqLength || ppq16 > std::numeric_limits<uint32_t>::max() / 16u)
30✔
1372
                return false;
×
1373
            if (length == 0)
30✔
1374
                continue;
×
1375

1376
            VLSliceEntry s;
30✔
1377
            s.ppq_pos = ppq16 * 16u;
30✔
1378
            s.sample_start = start;
30✔
1379
            s.sample_length = length;
30✔
1380
            s.points = 0x7fff;
30✔
1381
            s.selected_flag = i == 0 ? 1 : 0;
30✔
1382
            s.state = kSliceNormal;
30✔
1383
            parsed.push_back(s);
30✔
1384
            ppqPositions.push_back(ppq16);
30✔
1385
            sourceLengths.push_back(length);
30✔
1386
        }
1387

1388
        if (parsed.empty())
3✔
1389
            return false;
×
1390

1391
        double samplesPerPpq = 0.0;
3✔
1392
        for (size_t i = 0; i < ppqPositions.size(); ++i)
33✔
1393
        {
1394
            uint32_t nextPpq = storedPpqLength;
30✔
1395
            for (size_t j = i + 1; j < ppqPositions.size(); ++j)
30✔
1396
            {
1397
                if (ppqPositions[j] != ppqPositions[i])
27✔
1398
                {
1399
                    nextPpq = ppqPositions[j];
27✔
1400
                    break;
27✔
1401
                }
1402
            }
1403

1404
            if (nextPpq <= ppqPositions[i])
30✔
1405
                continue;
×
1406

1407
            const uint32_t delta = nextPpq - ppqPositions[i];
30✔
1408
            samplesPerPpq = std::max(samplesPerPpq, (double)sourceLengths[i] / (double)delta);
30✔
1409
        }
1410

1411
        if (samplesPerPpq <= 0.0)
3✔
1412
            return false;
×
1413

1414
        ppqLength = storedPpqLength * 16u;
3✔
1415
        exportedFrameCount = (uint32_t)(samplesPerPpq * (double)storedPpqLength);
3✔
1416
        out = std::move(parsed);
3✔
1417
        return true;
3✔
1418
    }
3✔
1419

1420
    bool loadLegacyAIFF()
48✔
1421
    {
1422
        uint16_t channels = 0;
48✔
1423
        uint32_t frameCount = 0;
48✔
1424
        uint16_t bits = 0;
48✔
1425
        int32_t sampleRate = 0;
48✔
1426
        bool haveCOMM = false;
48✔
1427
        bool haveSSND = false;
48✔
1428
        size_t ssndOffset = 0;
48✔
1429
        size_t ssndSize = 0;
48✔
1430
        uint32_t markerLoopStart = 0;
48✔
1431
        uint32_t markerLoopEnd = 0;
48✔
1432
        bool haveLoopStart = false;
48✔
1433
        bool haveLoopEnd = false;
48✔
1434
        bool sawLegacyApp = false;
48✔
1435
        bool sawLegacyTempo = false;
48✔
1436
        bool legacyLoopLengthSet = true;
48✔
1437
        std::vector<VLSliceEntry> legacySlices;
48✔
1438
        bool legacySlicesHaveExplicitPpq = false;
48✔
1439
        uint32_t legacyRexPpqLength = 0;
48✔
1440
        uint32_t legacyRexExportedFrameCount = 0;
48✔
1441

1442
        const size_t formEnd = std::min(fileData.size(), (size_t)be32(&fileData[4]) + 8u);
48✔
1443
        size_t off = 12;
48✔
1444
        while (off + 8 <= formEnd && off + 8 <= fileData.size())
212✔
1445
        {
1446
            char id[5] = {};
164✔
1447
            std::memcpy(id, &fileData[off], 4);
164✔
1448
            const uint32_t sz = be32(&fileData[off + 4]);
164✔
1449
            const size_t payload = off + 8;
164✔
1450
            if (payload + sz > fileData.size())
164✔
1451
                break;
×
1452

1453
            if (std::strcmp(id, "COMM") == 0 && sz >= 18)
164✔
1454
            {
1455
                channels = be16(&fileData[payload]);
48✔
1456
                frameCount = be32(&fileData[payload + 2]);
48✔
1457
                bits = be16(&fileData[payload + 6]);
48✔
1458
                sampleRate = readAIFFExtendedRate(&fileData[payload + 8]);
48✔
1459
                haveCOMM = true;
48✔
1460
            }
1461
            else if (std::strcmp(id, "SSND") == 0 && sz >= 8)
116✔
1462
            {
1463
                const uint32_t dataOffset = be32(&fileData[payload]);
48✔
1464
                const size_t dataStart = payload + 8u + (size_t)dataOffset;
48✔
1465
                if (dataStart <= payload + sz && dataStart <= fileData.size())
48✔
1466
                {
1467
                    ssndOffset = dataStart;
48✔
1468
                    ssndSize = (payload + sz) - dataStart;
48✔
1469
                    haveSSND = true;
48✔
1470
                }
1471
            }
48✔
1472
            else if (std::strcmp(id, "MARK") == 0 && sz >= 2)
68✔
1473
            {
1474
                uint32_t pos = 2;
12✔
1475
                const uint16_t count = be16(&fileData[payload]);
12✔
1476
                for (uint16_t i = 0; i < count && pos + 7 <= sz; ++i)
36✔
1477
                {
1478
                    const uint32_t markerPos = be32(&fileData[payload + pos + 2]);
24✔
1479
                    const uint8_t nameLen = fileData[payload + pos + 6];
24✔
1480
                    pos += 7;
24✔
1481
                    if (pos + nameLen > sz)
24✔
1482
                        break;
×
1483

1484
                    const char* name = (const char*)&fileData[payload + pos];
24✔
1485
                    const bool isLoopStart = nameLen == 10 && std::memcmp(name, "Loop start", 10) == 0;
24✔
1486
                    const bool isLoopEnd = nameLen == 8 && std::memcmp(name, "Loop end", 8) == 0;
24✔
1487
                    if (isLoopStart)
24✔
1488
                    {
1489
                        markerLoopStart = markerPos;
12✔
1490
                        haveLoopStart = true;
12✔
1491
                    }
1492
                    else if (isLoopEnd)
12✔
1493
                    {
1494
                        markerLoopEnd = markerPos;
12✔
1495
                        haveLoopEnd = true;
12✔
1496
                    }
1497

1498
                    pos += nameLen + (((nameLen + 1u) & 1u) ? 1u : 0u);
24✔
1499
                }
1500
            }
12✔
1501
            else if (std::strcmp(id, "APPL") == 0 && sz >= 8)
56✔
1502
            {
1503
                const bool appIsREX = std::memcmp(&fileData[payload], "REX ", 4) == 0;
44✔
1504
                const bool appIsReCy = std::memcmp(&fileData[payload], "ReCy", 4) == 0;
44✔
1505
                if (appIsREX || appIsReCy)
44✔
1506
                {
1507
                    sawLegacyApp = true;
44✔
1508
                    if (appIsReCy)
44✔
1509
                    {
1510
                        legacyLoopLengthSet = sz > 12 && fileData[payload + 12] != 0;
41✔
1511
                        parseLegacyReCycleSlices(&fileData[payload], sz, legacySlices);
41✔
1512
                    }
1513
                    else if (appIsREX)
3✔
1514
                    {
1515
                        legacySlicesHaveExplicitPpq =
1516
                            parseLegacyREXSlices(&fileData[payload], sz, legacySlices, legacyRexPpqLength, legacyRexExportedFrameCount);
3✔
1517
                    }
1518
                    sawLegacyTempo = parseLegacyTempo(&fileData[payload], sz, appIsReCy) || sawLegacyTempo;
44✔
1519
                }
1520
            }
1521

1522
            off = payload + sz;
164✔
1523
            if (off & 1)
164✔
1524
                ++off;
×
1525
        }
1526

1527
        if (!sawLegacyApp)
48✔
1528
            return fail(VL_ERROR_FILE_CORRUPT);
4✔
1529

1530
        if (!sawLegacyTempo)
44✔
1531
            return fail(VL_ERROR_INVALID_TEMPO);
8✔
1532

1533
        if (!legacyLoopLengthSet)
36✔
1534
            return fail(VL_ERROR_ZERO_LOOP_LENGTH);
6✔
1535

1536
        if (!haveCOMM || !haveSSND || channels < 1 || channels > 2 || frameCount == 0 || sampleRate <= 0)
30✔
1537
        {
1538
            if (frameCount == 0)
12✔
1539
                return fail(VL_ERROR_INVALID_SIZE);
4✔
1540
            if (sampleRate <= 0)
8✔
1541
                return fail(VL_ERROR_INVALID_SAMPLE_RATE);
4✔
1542
            return fail(VL_ERROR_FILE_CORRUPT);
4✔
1543
        }
1544

1545
        if (bits != 8 && bits != 16 && bits != 24 && bits != 32)
18✔
1546
            return fail(VL_ERROR_FILE_CORRUPT);
4✔
1547

1548
        const size_t bytesPerSample = (size_t)((bits + 7u) / 8u);
14✔
1549
        const size_t frameBytes = bytesPerSample * (size_t)channels;
14✔
1550
        if (frameBytes == 0)
14✔
1551
            return fail(VL_ERROR_INVALID_SIZE);
×
1552

1553
        const uint32_t availableFrames = (uint32_t)std::min<size_t>(frameCount, ssndSize / frameBytes);
14✔
1554
        if (availableFrames == 0)
14✔
1555
            return fail(VL_ERROR_INVALID_SIZE);
4✔
1556

1557
        if (!pcm.assign((size_t)availableFrames * channels, 0))
10✔
1558
            return fail(VL_ERROR_OUT_OF_MEMORY);
×
1559

1560
        const uint8_t* src = &fileData[ssndOffset];
10✔
1561
        for (uint32_t frame = 0; frame < availableFrames; ++frame)
964,929✔
1562
        {
1563
            for (uint16_t ch = 0; ch < channels; ++ch)
1,929,838✔
1564
            {
1565
                const size_t sample = ((size_t)frame * channels + ch) * bytesPerSample;
964,919✔
1566
                pcm[(size_t)frame * channels + ch] = readSignedSampleBE(src + sample, bits);
964,919✔
1567
            }
1568
        }
1569

1570
        totalFrames = availableFrames;
10✔
1571
        loopStart = haveLoopStart && markerLoopStart < availableFrames ? markerLoopStart : 0;
10✔
1572
        loopEnd = haveLoopEnd && markerLoopEnd > loopStart && markerLoopEnd <= availableFrames ? markerLoopEnd : availableFrames;
10✔
1573

1574
        info.channels = channels;
10✔
1575
        info.sample_rate = sampleRate;
10✔
1576
        info.slice_count = 1;
10✔
1577
        info.ppq_length = legacyRexPpqLength > 0 ? (int32_t)legacyRexPpqLength : kREXPPQ * 4;
10✔
1578
        info.time_sig_num = 4;
10✔
1579
        info.time_sig_den = 4;
10✔
1580
        info.bit_depth = bits;
10✔
1581
        info.total_frames = (int32_t)totalFrames;
10✔
1582
        info.loop_start = (int32_t)loopStart;
10✔
1583
        info.loop_end = (int32_t)loopEnd;
10✔
1584
        info.processing_gain = processingGain;
10✔
1585
        info.transient_enabled = 0;
10✔
1586
        info.transient_attack = 0;
10✔
1587
        info.transient_decay = 1023;
10✔
1588
        info.transient_stretch = 0;
10✔
1589
        info.silence_selected = 0;
10✔
1590
        transientEnabled = false;
10✔
1591
        transientAttack = 0;
10✔
1592
        transientDecay = 1023;
10✔
1593
        transientStretch = 0;
10✔
1594

1595
        if (legacyRexExportedFrameCount > 0 && info.ppq_length > 0)
10✔
1596
        {
1597
            const double beats = (double)info.ppq_length / (double)kREXPPQ;
3✔
1598
            const double bpm = beats * 60.0 * (double)info.sample_rate / (double)legacyRexExportedFrameCount;
3✔
1599
            const int32_t derivedOriginalTempo = (int32_t)std::lround(bpm * 1000.0);
3✔
1600
            if (derivedOriginalTempo > 0)
3✔
1601
                info.original_tempo = derivedOriginalTempo;
3✔
1602
        }
1603

1604
        if (!legacySlices.empty())
10✔
1605
        {
1606
            std::sort(legacySlices.begin(), legacySlices.end(),
10✔
1607
                      [](const VLSliceEntry& a, const VLSliceEntry& b)
180✔
1608
                      {
1609
                          return a.sample_start < b.sample_start;
180✔
1610
                      });
1611
            legacySlices.erase(std::unique(legacySlices.begin(), legacySlices.end(),
10✔
1612
                                           [](const VLSliceEntry& a, const VLSliceEntry& b)
90✔
1613
                                           {
1614
                                               return a.sample_start == b.sample_start;
90✔
1615
                                           }),
1616
                               legacySlices.end());
10✔
1617

1618
            const uint32_t sliceEnd = loopEnd > loopStart ? loopEnd : totalFrames;
10✔
1619
            for (size_t i = 0; i < legacySlices.size(); ++i)
110✔
1620
            {
1621
                const uint32_t next = i + 1 < legacySlices.size() ? legacySlices[i + 1].sample_start : sliceEnd;
100✔
1622
                if (!legacySlicesHaveExplicitPpq)
100✔
1623
                    legacySlices[i].sample_length = next > legacySlices[i].sample_start ? next - legacySlices[i].sample_start : 1u;
70✔
1624
            }
1625

1626
            slices = std::move(legacySlices);
10✔
1627
            if (!legacySlicesHaveExplicitPpq)
10✔
1628
                finalizeSlices();
7✔
1629
            else
1630
                info.slice_count = (int32_t)slices.size();
3✔
1631
            finalizeRenderedLengths();
10✔
1632
        }
1633
        else
1634
        {
1635
            VLSliceEntry s;
×
1636
            s.ppq_pos = 0;
×
1637
            s.sample_start = loopStart;
×
1638
            s.sample_length = std::max<uint32_t>(1u, loopEnd > loopStart ? loopEnd - loopStart : totalFrames - loopStart);
×
1639
            s.rendered_length = s.sample_length;
×
1640
            s.points = 0x7fff;
×
1641
            s.selected_flag = 1;
×
1642
            s.state = kSliceNormal;
×
1643
            slices.push_back(s);
×
1644
            info.slice_count = (int32_t)slices.size();
×
1645
            finalizeRenderedLengths();
×
1646
        }
1647
        return true;
10✔
1648
    }
48✔
1649

1650
    void parseIFF(size_t start, size_t end, size_t& dwopOffset, size_t& dwopSize, bool& hasDWOP)
316✔
1651
    {
1652
        size_t off = start;
316✔
1653
        while (off + 8 < end && off + 8 < fileData.size())
5,305✔
1654
        {
1655
            char id[5] = {};
4,997✔
1656
            std::memcpy(id, &fileData[off], 4);
4,997✔
1657
            off += 4;
4,997✔
1658

1659
            const uint32_t sz = be32(&fileData[off]);
4,997✔
1660
            off += 4;
4,997✔
1661

1662
            if (off + sz > fileData.size())
4,997✔
1663
            {
1664
                fail(VL_ERROR_INVALID_SIZE);
8✔
1665
                break;
8✔
1666
            }
1667

1668
            if (std::strcmp(id, "HEAD") == 0)
4,989✔
1669
                parseHEAD(&fileData[off], sz);
128✔
1670

1671
            else if (std::strcmp(id, "CREI") == 0)
4,861✔
1672
                parseCREI(&fileData[off], sz);
9✔
1673

1674
            else if (std::strcmp(id, "SINF") == 0)
4,852✔
1675
                parseSINF(&fileData[off], sz);
126✔
1676

1677
            else if (std::strcmp(id, "GLOB") == 0)
4,726✔
1678
                parseGLOB(&fileData[off], sz);
128✔
1679

1680
            else if (std::strcmp(id, "TRSH") == 0)
4,598✔
1681
                parseTRSH(&fileData[off], sz);
94✔
1682

1683
            else if (std::strcmp(id, "RECY") == 0)
4,504✔
1684
                parseRECY(&fileData[off], sz);
94✔
1685

1686
            else if (std::strcmp(id, "SLCE") == 0)
4,410✔
1687
                parseSLCE(&fileData[off], sz);
3,907✔
1688

1689
            else if ((std::strcmp(id, "SDAT") == 0 || std::strcmp(id, "DWOP") == 0) && !hasDWOP)
503✔
1690
            {
1691
                dwopOffset = off;
118✔
1692
                dwopSize = sz;
118✔
1693
                hasDWOP = true;
118✔
1694
            }
1695

1696
            else if (std::strcmp(id, "CAT ") == 0 && sz >= 4)
385✔
1697
            {
1698
                parseIFF(off + 4, off + sz, dwopOffset, dwopSize, hasDWOP);
188✔
1699
            }
1700

1701
            off += sz;
4,989✔
1702
            if (off & 1)
4,989✔
1703
                ++off;
4,357✔
1704
        }
1705
    }
316✔
1706

1707
    void parseSINF(const uint8_t* d, uint32_t sz)
126✔
1708
    {
1709
        if (sz < 18)
126✔
1710
            return;
4✔
1711

1712
        info.channels = d[0];
122✔
1713
        const uint8_t bd = d[1];
122✔
1714
        info.sample_rate = (int32_t)be32(d + 2);
122✔
1715
        totalFrames = be32(d + 6);
122✔
1716
        loopStart = be32(d + 10);
122✔
1717
        loopEnd = be32(d + 14);
122✔
1718
        info.total_frames = (int32_t)totalFrames;
122✔
1719
        info.loop_start = (int32_t)loopStart;
122✔
1720
        info.loop_end = (int32_t)loopEnd;
122✔
1721

1722
        switch (bd)
122✔
1723
        {
1724
            case 1: info.bit_depth = 8; break;
1✔
1725

1726
            case 3: info.bit_depth = 16; break;
112✔
1727

1728
            case 5: info.bit_depth = 24; break;
7✔
1729

1730
            case 7: info.bit_depth = 32; break;
1✔
1731

1732
            default: info.bit_depth = 16; break;
1✔
1733
        }
1734

1735
        const uint32_t frames = (loopEnd > loopStart) ? (loopEnd - loopStart) : totalFrames;
122✔
1736
        if (frames > 0 && info.sample_rate > 0 && info.ppq_length > 0)
122✔
1737
        {
1738
            const double beats = (double)info.ppq_length / (double)kREXPPQ;
118✔
1739
            const double bpm = beats * 60.0 * (double)info.sample_rate / (double)frames;
118✔
1740

1741
            const int32_t t = (int32_t)std::lround(bpm * 1000.0);
118✔
1742
            if (t > 0)
118✔
1743
                info.original_tempo = t;
118✔
1744
        }
1745

1746
        if (info.original_tempo == 0)
122✔
1747
            info.original_tempo = info.tempo;
×
1748

1749
        if (info.channels != 1 && info.channels != 2)
122✔
1750
            info.channels = 1;
×
1751
    }
1752

1753
    void parseHEAD(const uint8_t* d, uint32_t sz)
128✔
1754
    {
1755
        if (sz < 6 || be32(d) != 0x490cf18du)
128✔
1756
        {
1757
            headerValid = false;
4✔
1758
            fail(VL_ERROR_FILE_CORRUPT);
4✔
1759
            return;
4✔
1760
        }
1761

1762
        if (d[4] != 0xbc)
124✔
1763
        {
1764
            headerValid = false;
×
1765
            fail(VL_ERROR_FILE_CORRUPT);
×
1766
            return;
×
1767
        }
1768

1769
        if (d[5] > 0x03)
124✔
1770
        {
1771
            headerValid = false;
6✔
1772
            fail(VL_ERROR_FILE_TOO_NEW);
6✔
1773
        }
1774
    }
1775

1776
    void parseGLOB(const uint8_t* d, uint32_t sz)
128✔
1777
    {
1778
        if (sz < 22)
128✔
1779
        {
1780
            fail(VL_ERROR_INVALID_SIZE);
6✔
1781
            return;
6✔
1782
        }
1783

1784
        info.slice_count = (int32_t)be32(d);
122✔
1785
        globBars = be16(d + 4);
122✔
1786
        globBeats = d[6];
122✔
1787
        info.time_sig_num = d[7];
122✔
1788
        info.time_sig_den = d[8];
122✔
1789
        analysisSensitivity = d[9];
122✔
1790
        gateSensitivity = be16(d + 10);
122✔
1791
        const uint32_t tempo = be32(d + 16);
122✔
1792
        if (tempo < 20000u || tempo > 450000u)
122✔
1793
            fail(VL_ERROR_INVALID_TEMPO);
4✔
1794
        info.tempo = (int32_t)tempo;
122✔
1795
        {
1796
            const int32_t timeSigNum = info.time_sig_num > 0 ? info.time_sig_num : 4;
122✔
1797
            const int32_t totalBeats = (int32_t)globBars * timeSigNum + (int32_t)globBeats;
122✔
1798
            info.ppq_length = totalBeats > 0 ? totalBeats * kREXPPQ : 4 * kREXPPQ;
122✔
1799
        }
1800
        processingGain = be16(d + 12);
122✔
1801
        silenceSelected = d[21] != 0;
122✔
1802
        info.processing_gain = processingGain;
122✔
1803
        info.silence_selected = silenceSelected ? 1 : 0;
122✔
1804
        loadedFromFile = true;
122✔
1805
    }
1806

1807
    void parseCREI(const uint8_t* d, uint32_t sz)
9✔
1808
    {
1809
        uint32_t off = 0;
9✔
1810
        auto readString = [&](char* dst, size_t dstSize)
45✔
1811
        {
1812
            if (!dst || dstSize == 0)
45✔
UNCOV
1813
                return;
×
1814

1815
            dst[0] = '\0';
45✔
1816
            if (off + 4 > sz)
45✔
1817
                return;
4✔
1818

1819
            const uint32_t n = be32(d + off);
41✔
1820
            off += 4;
41✔
1821
            if (off + n > sz)
41✔
1822
            {
1823
                off = sz;
1✔
1824
                return;
1✔
1825
            }
1826

1827
            const size_t copy = std::min<size_t>(n, dstSize - 1);
40✔
1828
            if (copy)
40✔
1829
                std::memcpy(dst, d + off, copy);
33✔
1830

1831
            dst[copy] = '\0';
40✔
1832
            off += n;
40✔
1833
        };
9✔
1834

1835
        readString(creator.name, sizeof(creator.name));
9✔
1836
        readString(creator.copyright, sizeof(creator.copyright));
9✔
1837
        readString(creator.url, sizeof(creator.url));
9✔
1838
        readString(creator.email, sizeof(creator.email));
9✔
1839
        readString(creator.free_text, sizeof(creator.free_text));
9✔
1840
    }
9✔
1841

1842
    void parseTRSH(const uint8_t* d, uint32_t sz)
94✔
1843
    {
1844
        if (sz < 7)
94✔
UNCOV
1845
            return;
×
1846

1847
        transientEnabled = d[0] != 0;
94✔
1848
        transientAttack = be16(d + 1);
94✔
1849
        transientDecay = be16(d + 3);
94✔
1850
        transientStretch = be16(d + 5);
94✔
1851
        info.transient_enabled = transientEnabled ? 1 : 0;
94✔
1852
        info.transient_attack = transientAttack;
94✔
1853
        info.transient_decay = transientDecay;
94✔
1854
        info.transient_stretch = transientStretch;
94✔
1855
    }
1856

1857
    void parseRECY(const uint8_t* d, uint32_t sz)
94✔
1858
    {
1859
        if (sz < 12)
94✔
UNCOV
1860
            return;
×
1861

1862
        const int32_t t = (int32_t)be32(d + 8);
94✔
1863
        if (t > 0)
94✔
1864
            info.original_tempo = t;
91✔
1865
    }
1866

1867
    void parseSLCE(const uint8_t* d, uint32_t sz)
3,907✔
1868
    {
1869
        static constexpr size_t kMaxSlices = 1024;
1870

1871
        if (slices.size() >= kMaxSlices)
3,907✔
UNCOV
1872
            return;
×
1873

1874
        if (sz < 10)
3,907✔
UNCOV
1875
            return;
×
1876

1877
        VLSliceEntry s;
3,907✔
1878
        s.sample_start = be32(d);
3,907✔
1879
        s.sample_length = be32(d + 4);
3,907✔
1880
        s.points = be16(d + 8);
3,907✔
1881
        s.marker = s.sample_length <= 1;
3,907✔
1882

1883
        const uint8_t flags = sz > 10 ? d[10] : 0;
3,907✔
1884
        s.selected_flag = (flags & 0x04) ? 1 : 0;
3,907✔
1885

1886
        if (flags & 0x02)
3,907✔
1887
            s.state = kSliceLocked;
9✔
1888
        else if (flags & 0x01)
3,898✔
1889
            s.state = kSliceMuted;
1✔
1890
        else
1891
            s.state = kSliceNormal;
3,897✔
1892

1893
        slices.push_back(s);
3,907✔
1894
        info.slice_count = (int32_t)slices.size();
3,907✔
1895
    }
1896

1897
    static uint16_t rex2FilterPoints(uint8_t sensitivity)
671✔
1898
    {
1899
        const uint32_t sens = std::min<uint32_t>(sensitivity, 99u);
671✔
1900
        const uint32_t visibleRange = (sens * 0x7fffu + 98u) / 99u;
671✔
1901
        return (uint16_t)(0x7fffu - visibleRange);
671✔
1902
    }
1903

1904
    bool isVisibleSliceBoundary(const VLSliceEntry& s) const
3,851✔
1905
    {
1906
        if (s.sample_length > 1)
3,851✔
1907
            return true;
3,180✔
1908

1909
        if (s.selected_flag || s.state != kSliceNormal)
671✔
UNCOV
1910
            return true;
×
1911

1912
        return s.points > rex2FilterPoints(analysisSensitivity);
671✔
1913
    }
1914

1915
    uint32_t defaultSliceEnd(uint32_t start) const
3,199✔
1916
    {
1917
        if (loopEnd > loopStart && start < loopEnd)
3,199✔
1918
            return loopEnd;
3,195✔
1919

1920
        return totalFrames;
4✔
1921
    }
1922

1923
    uint32_t gateLengthFrames() const
109✔
1924
    {
1925
        if (gateSensitivity == 0)
109✔
1926
            return 0;
60✔
1927

1928
        const uint32_t sr = info.sample_rate > 0 ? (uint32_t)info.sample_rate : 44100u;
49✔
1929
        uint32_t frames = (uint32_t)(((uint64_t)gateSensitivity * sr + 4500u) / 9000u);
49✔
1930
        frames = std::max(1u, frames);
49✔
1931

1932
        return std::max(1u, ((frames + 64u) / 128u) * 128u);
49✔
1933
    }
1934

1935
    void finalizeSlices()
109✔
1936
    {
1937
        const uint32_t denom = (loopEnd > loopStart) ? (loopEnd - loopStart) : (totalFrames ? totalFrames : 1u);
109✔
1938
        const uint32_t gatedFrames = gateLengthFrames();
109✔
1939

1940
        std::sort(slices.begin(), slices.end(),
109✔
1941
                  [](const VLSliceEntry& a, const VLSliceEntry& b)
26,920✔
1942
                  {
1943
                      return a.sample_start < b.sample_start;
26,920✔
1944
                  });
1945

1946
        std::vector<VLSliceEntry> out;
109✔
1947
        for (auto s : slices)
4,062✔
1948
        {
1949
            if (loopEnd > loopStart && s.sample_start < totalFrames && s.sample_start >= loopEnd)
3,953✔
1950
                continue;
102✔
1951

1952
            if (!isVisibleSliceBoundary(s))
3,851✔
1953
                continue;
652✔
1954

1955
            const uint32_t rel = (s.sample_start > loopStart) ? (s.sample_start - loopStart) : 0;
3,199✔
1956

1957
            s.ppq_pos = (uint32_t)(((uint64_t)rel * info.ppq_length + denom / 2) / denom);
3,199✔
1958
            s.marker = false;
3,199✔
1959

1960
            out.push_back(s);
3,199✔
1961
        }
1962

1963
        for (size_t i = 0; i < out.size(); ++i)
3,308✔
1964
        {
1965
            const uint32_t start = out[i].sample_start;
3,199✔
1966
            uint32_t next = defaultSliceEnd(start);
3,199✔
1967
            for (size_t j = i + 1; j < out.size(); ++j)
3,200✔
1968
            {
1969
                if (out[j].sample_start > start)
3,111✔
1970
                {
1971
                    next = out[j].sample_start;
3,110✔
1972
                    break;
3,110✔
1973
                }
1974
            }
1975

1976
            const uint32_t derived = next > start ? next - start : 1u;
3,199✔
1977
            if (gateSensitivity == 0 || out[i].sample_length <= 1)
3,199✔
1978
            {
1979
                out[i].sample_length = derived;
1,837✔
1980
                if (gateSensitivity != 0 && gatedFrames > 0)
1,837✔
UNCOV
1981
                    out[i].sample_length = std::min(out[i].sample_length, gatedFrames);
×
1982
            }
1983
            else if (derived > 1)
1,362✔
1984
            {
1985
                out[i].sample_length = std::min(out[i].sample_length, derived);
1,361✔
1986
            }
1987

1988
            out[i].sample_length = std::max<uint32_t>(1u, out[i].sample_length);
3,199✔
1989
        }
1990

1991
        if (!out.empty() && loopEnd > loopStart && out.front().sample_start > loopStart && out.front().sample_start <= totalFrames)
109✔
1992
        {
1993
            VLSliceEntry leading;
9✔
1994
            leading.ppq_pos = 0;
9✔
1995
            leading.sample_start = loopStart;
9✔
1996
            leading.sample_length = out.front().sample_start - loopStart;
9✔
1997
            leading.points = 0x7fff;
9✔
1998
            leading.selected_flag = 1;
9✔
1999
            leading.state = kSliceNormal;
9✔
2000
            leading.synthetic_leading = true;
9✔
2001

2002
            out.insert(out.begin(), leading);
9✔
2003
        }
2004

2005
        slices.swap(out);
109✔
2006
        info.slice_count = (int32_t)slices.size();
109✔
2007
    }
109✔
2008

2009
    uint32_t sourceEndForSlice(const VLSliceEntry& s) const
15,128✔
2010
    {
2011
        const uint32_t start = s.sample_start;
15,128✔
2012
        if (start >= totalFrames)
15,128✔
2013
            return start;
8✔
2014

2015
        uint32_t frames = std::min(s.sample_length, totalFrames - start);
15,120✔
2016
        if (loopEnd > loopStart && start < loopEnd)
15,120✔
2017
            frames = std::min(frames, loopEnd - start);
14,008✔
2018

2019
        return start + frames;
15,120✔
2020
    }
2021

2022
public:
2023
    struct SegmentLoop
2024
    {
2025
        uint32_t start = 0;
2026
        uint32_t end = 0;
2027
        float volumeCompensation = 1.0f;
2028
    };
2029

2030
    SegmentLoop findSegmentLoop(uint32_t start, uint32_t end) const
8,120✔
2031
    {
2032
        SegmentLoop r{start, end};
8,120✔
2033

2034
        const uint32_t sr = info.sample_rate ? (uint32_t)info.sample_rate : 44100u;
8,120✔
2035
        const uint32_t srch = std::max(1u, (400u * sr) / 44100u);
8,120✔
2036
        const uint32_t mhl = std::max(1u, (20000u * sr) / 44100u);
8,120✔
2037

2038
        if (end <= start || end - start < srch * 3u)
8,120✔
2039
            return r;
4,075✔
2040

2041
        const uint32_t ch = std::max(1, info.channels);
4,045✔
2042
        auto leftAbs = [&](uint32_t f) -> int
3,002,638✔
2043
        {
2044
            const size_t i = (size_t)f * ch;
3,002,638✔
2045
            return i < pcm.size() ? std::abs((int)pcm[i]) : 0;
3,002,638✔
2046
        };
4,045✔
2047

2048
        uint32_t loopEnd = end - srch;
4,045✔
2049
        int peak = -1;
4,045✔
2050
        for (uint32_t i = 0, f = end - srch; i < srch && f > start; ++i, --f)
1,614,245✔
2051
        {
2052
            const int p = leftAbs(f);
1,610,200✔
2053
            if (p > peak)
1,610,200✔
2054
            {
2055
                peak = p;
79,198✔
2056
                loopEnd = f;
79,198✔
2057
            }
2058
        }
2059

2060
        uint32_t hl = std::min((loopEnd - start) / 2u, mhl);
4,045✔
2061
        uint32_t ls = loopEnd - hl;
4,045✔
2062
        uint32_t loopStart = ls;
4,045✔
2063
        int lspeak = -1;
4,045✔
2064
        for (uint32_t i = 0, f = ls; i < srch && f >= start; ++i)
1,396,483✔
2065
        {
2066
            const int p = leftAbs(f);
1,392,438✔
2067
            if (p > lspeak)
1,392,438✔
2068
            {
2069
                lspeak = p;
59,870✔
2070
                loopStart = f;
59,870✔
2071
            }
2072

2073
            if (f == 0)
1,392,438✔
UNCOV
2074
                break;
×
2075

2076
            --f;
1,392,438✔
2077
        }
2078

2079
        r.start = std::clamp(loopStart, start, end - 1u);
4,045✔
2080
        r.end = std::clamp(loopEnd, r.start + 1u, end);
4,045✔
2081

2082
        if (lspeak > 0 && peak > 0)
4,045✔
2083
            r.volumeCompensation = std::min(10.0f, (float)peak / (float)lspeak);
3,904✔
2084

2085
        return r;
4,045✔
2086
    }
2087

2088
private:
2089
    void cacheRenderLoop(VLSliceEntry& s) const
8,120✔
2090
    {
2091
        const uint32_t start = s.sample_start;
8,120✔
2092
        const uint32_t end = sourceEndForSlice(s);
8,120✔
2093
        const SegmentLoop loop = findSegmentLoop(start, end);
8,120✔
2094
        s.render_loop_start = loop.start;
8,120✔
2095
        s.render_loop_end = loop.end;
8,120✔
2096
        s.render_loop_volume_compensation = loop.volumeCompensation;
8,120✔
2097
    }
8,120✔
2098

2099
    uint32_t calcRenderedLength(const VLSliceEntry& s) const
7,008✔
2100
    {
2101
        const uint32_t start = s.sample_start;
7,008✔
2102
        const uint32_t end = sourceEndForSlice(s);
7,008✔
2103

2104
        if (end <= start)
7,008✔
2105
            return 1u;
4✔
2106

2107
        const uint32_t segLen = end - start;
7,004✔
2108
        if (!transientEnabled || transientStretch == 0)
7,004✔
2109
            return segLen;
3,328✔
2110

2111
        const uint32_t loopE = (s.render_loop_end > start && s.render_loop_end <= end) ? s.render_loop_end : end;
3,676✔
2112
        const uint32_t stretchN = (uint32_t)transientStretch + 1u;
3,676✔
2113
        const uint32_t stretchT = (uint32_t)(((uint64_t)(loopE - start) * stretchN) / 100u);
3,676✔
2114

2115
        return std::max(1u, segLen + stretchT);
3,676✔
2116
    }
2117

2118
public:
2119
    void cacheSliceRender(VLSliceEntry& s) const
7,008✔
2120
    {
2121
        cacheRenderLoop(s);
7,008✔
2122
        s.rendered_length = calcRenderedLength(s);
7,008✔
2123
    }
7,008✔
2124

2125
    void cacheSliceLoop(VLSliceEntry& s) const
1,112✔
2126
    {
2127
        cacheRenderLoop(s);
1,112✔
2128
    }
1,112✔
2129

2130
    void finalizeRenderedLengths()
216✔
2131
    {
2132
        for (auto& s : slices)
7,224✔
2133
            cacheSliceRender(s);
7,008✔
2134
    }
216✔
2135
};
2136

2137
int32_t storageBitDepth(int32_t bitDepth)
13,066,853✔
2138
{
2139
    return bitDepth == 24 ? 24 : 16;
13,066,853✔
2140
}
2141

2142
int32_t floatToPCM(float s, int32_t bitDepth)
6,407,432✔
2143
{
2144
    if (s > 1.0f)
6,407,432✔
2145
        s = 1.0f;
64✔
2146

2147
    if (s < -1.0f)
6,407,432✔
2148
        s = -1.0f;
64✔
2149

2150
    if (storageBitDepth(bitDepth) == 24)
6,407,432✔
2151
    {
2152
        const float scaled = s >= 0.0f ? s * 8388607.0f : s * 8388608.0f;
113,479✔
2153
        return (int32_t)std::lround(scaled);
113,479✔
2154
    }
2155

2156
    const float scaled = s >= 0.0f ? s * 32767.0f : s * 32768.0f;
6,293,953✔
2157
    return (int32_t)std::lround(scaled);
6,293,953✔
2158
}
2159

2160
float pcmToFloat(int32_t sample, int32_t bitDepth)
6,659,256✔
2161
{
2162
    return storageBitDepth(bitDepth) == 24 ? (float)sample / 8388608.0f : (float)sample / 32768.0f;
6,659,256✔
2163
}
2164

2165
uint8_t bitDepthCode(int32_t bitDepth)
116✔
2166
{
2167
    switch (bitDepth)
116✔
2168
    {
2169
        case 8: return 1; // LCOV_EXCL_LINE unsupported authored depth is normalized before writing.
2170

2171
        case 16: return 3;
110✔
2172

2173
        case 24: return 5;
6✔
2174

2175
        case 32: return 7; // LCOV_EXCL_LINE unsupported authored depth is normalized before writing.
2176

2177
        default: return 3; // LCOV_EXCL_LINE unsupported authored depth is normalized before writing.
2178
    }
2179
}
2180

2181
bool hasCreatorInfo(const VLCreatorInfo& c)
116✔
2182
{
2183
    return c.name[0] || c.copyright[0] || c.url[0] || c.email[0] || c.free_text[0];
116✔
2184
}
2185

2186
void writeCStringChunkString(VLIFFWriter& w, const char* s)
55✔
2187
{
2188
    const size_t n = s ? std::min<size_t>(std::strlen(s), 255) : 0;
55✔
2189

2190
    w.put32((uint32_t)n);
55✔
2191

2192
    if (n)
55✔
2193
        w.putBytes((const uint8_t*)s, n);
46✔
2194
}
55✔
2195

2196
void writeSimpleChunk(VLIFFWriter& w, const char id[4], const std::vector<uint8_t>& payload)
116✔
2197
{
2198
    const size_t c = w.beginChunk(id);
116✔
2199

2200
    if (!payload.empty())
116✔
2201
        w.putBytes(payload.data(), payload.size());
116✔
2202

2203
    w.endChunk(c);
116✔
2204
}
116✔
2205

2206
uint32_t calcSampleStartFromPPQ(const VLFileImpl& impl, uint32_t ppq)
634✔
2207
{
2208
    const VLFileInfo& info = impl.info;
634✔
2209
    if (info.ppq_length > 0 && info.loop_end > info.loop_start)
634✔
2210
    {
2211
        const uint32_t denom = (uint32_t)(info.loop_end - info.loop_start);
621✔
2212
        return (uint32_t)info.loop_start + (uint32_t)(((uint64_t)ppq * denom + (uint32_t)info.ppq_length / 2u) / (uint32_t)info.ppq_length);
621✔
2213
    }
2214

2215
    const uint32_t tempo = info.tempo > 0 ? (uint32_t)info.tempo : 120000u;
13✔
2216
    const uint32_t sr = info.sample_rate > 0 ? (uint32_t)info.sample_rate : 44100u;
13✔
2217
    return (uint32_t)(((uint64_t)ppq * sr * 60000u + (uint64_t)tempo * VLFileImpl::kREXPPQ / 2u) / ((uint64_t)tempo * VLFileImpl::kREXPPQ));
13✔
2218
}
2219

2220
void normaliseInfoForSave(VLFileImpl& impl)
116✔
2221
{
2222
    impl.info.channels = std::clamp(impl.info.channels, 1, 2);
116✔
2223

2224
    if (impl.info.sample_rate <= 0)
116✔
UNCOV
2225
        impl.info.sample_rate = 44100;
×
2226

2227
    if (impl.info.tempo <= 0)
116✔
UNCOV
2228
        impl.info.tempo = 120000;
×
2229

2230
    if (impl.info.original_tempo <= 0)
116✔
2231
        impl.info.original_tempo = impl.info.tempo;
2✔
2232

2233
    if (impl.info.time_sig_num <= 0)
116✔
2234
        impl.info.time_sig_num = 4;
2✔
2235

2236
    if (impl.info.time_sig_den <= 0)
116✔
2237
        impl.info.time_sig_den = 4;
2✔
2238

2239
    impl.info.bit_depth = storageBitDepth(impl.info.bit_depth);
116✔
2240
    impl.info.slice_count = (int32_t)impl.slices.size();
116✔
2241

2242
    impl.totalFrames = (uint32_t)(impl.pcm.size() / (size_t)impl.info.channels);
116✔
2243
    impl.info.total_frames = (int32_t)impl.totalFrames;
116✔
2244

2245
    if (impl.info.loop_start < 0 || (uint32_t)impl.info.loop_start >= impl.totalFrames)
116✔
2246
        impl.info.loop_start = 0;
1✔
2247

2248
    if (impl.info.loop_end <= impl.info.loop_start || (uint32_t)impl.info.loop_end > impl.totalFrames)
116✔
2249
        impl.info.loop_end = (int32_t)impl.totalFrames;
1✔
2250

2251
    impl.loopStart = (uint32_t)std::max(0, impl.info.loop_start);
116✔
2252
    impl.loopEnd = (uint32_t)std::max(impl.info.loop_start, impl.info.loop_end);
116✔
2253

2254
    if (!impl.loadedFromFile && impl.analysisSensitivity == 0)
116✔
2255
        impl.analysisSensitivity = 99;
25✔
2256

2257
    if (impl.info.ppq_length <= 0)
116✔
2258
    {
2259
        const uint32_t frames = impl.loopEnd > impl.loopStart ? impl.loopEnd - impl.loopStart : impl.totalFrames;
2✔
2260

2261
        const double beats = frames > 0 ? ((double)frames * (double)impl.info.tempo) / (60000.0 * (double)impl.info.sample_rate) : 4.0;
2✔
2262

2263
        impl.info.ppq_length = std::max(1, (int32_t)std::lround(beats * VLFileImpl::kREXPPQ));
2✔
2264
    }
2265

2266
    {
2267
        const int32_t timeSigNum = impl.info.time_sig_num > 0 ? impl.info.time_sig_num : 4;
116✔
2268
        const int32_t totalBeats = std::max(1, (impl.info.ppq_length + VLFileImpl::kREXPPQ / 2) / VLFileImpl::kREXPPQ);
116✔
2269
        impl.globBars = (uint16_t)(totalBeats / timeSigNum);
116✔
2270
        impl.globBeats = (uint8_t)(totalBeats % timeSigNum);
116✔
2271
    }
2272

2273
    impl.processingGain = (uint16_t)std::clamp(impl.info.processing_gain > 0 ? impl.info.processing_gain : 1000, 0, 1000);
116✔
2274
    impl.transientEnabled = impl.info.transient_enabled != 0;
116✔
2275
    impl.transientAttack = (uint16_t)std::clamp(impl.info.transient_attack, 0, 1023);
116✔
2276
    impl.transientDecay = (uint16_t)std::clamp(impl.info.transient_decay > 0 ? impl.info.transient_decay : 1023, 0, 1023);
116✔
2277
    impl.transientStretch = (uint16_t)std::clamp(impl.info.transient_stretch, 0, 100);
116✔
2278
    impl.silenceSelected = impl.info.silence_selected != 0;
116✔
2279
    impl.finalizeRenderedLengths();
116✔
2280
}
116✔
2281

2282
std::vector<uint8_t> buildREX2File(VLFileImpl& impl)
116✔
2283
{
2284
    normaliseInfoForSave(impl);
116✔
2285

2286
    VLDWOPCompressor comp;
116✔
2287
    const std::vector<uint8_t> sdat =
2288
        impl.info.channels == 2 ? comp.compressStereo(impl.pcm.data(), impl.totalFrames) : comp.compressMono(impl.pcm.data(), impl.totalFrames);
116✔
2289

2290
    VLIFFWriter w;
116✔
2291
    const size_t root = w.beginCat("REX2");
116✔
2292

2293
    {
2294
        const size_t c = w.beginChunk("HEAD");
116✔
2295
        const uint8_t head[] = {0x49, 0x0c, 0xf1, 0x8d, 0xbc, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
116✔
2296
                                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
2297
        w.putBytes(head, sizeof(head));
116✔
2298
        w.endChunk(c);
116✔
2299
    }
2300

2301
    if (hasCreatorInfo(impl.creator))
116✔
2302
    {
2303
        const size_t c = w.beginChunk("CREI");
11✔
2304
        writeCStringChunkString(w, impl.creator.name);
11✔
2305
        writeCStringChunkString(w, impl.creator.copyright);
11✔
2306
        writeCStringChunkString(w, impl.creator.url);
11✔
2307
        writeCStringChunkString(w, impl.creator.email);
11✔
2308
        writeCStringChunkString(w, impl.creator.free_text);
11✔
2309
        w.endChunk(c);
11✔
2310
    }
2311

2312
    {
2313
        const size_t c = w.beginChunk("GLOB");
116✔
2314
        w.put32((uint32_t)impl.slices.size());
116✔
2315
        w.put16(impl.globBars);
116✔
2316
        w.put8(impl.globBeats);
116✔
2317
        w.put8((uint8_t)impl.info.time_sig_num);
116✔
2318
        w.put8((uint8_t)impl.info.time_sig_den);
116✔
2319
        w.put8(impl.analysisSensitivity);
116✔
2320
        const uint16_t gate = impl.slices.empty() ? impl.gateSensitivity : std::max<uint16_t>(1, impl.gateSensitivity);
116✔
2321
        w.put16(gate);
116✔
2322
        w.put16(impl.processingGain);
116✔
2323
        w.put16(1);
116✔
2324
        w.put32((uint32_t)impl.info.tempo);
116✔
2325
        w.put8(1);
116✔
2326
        w.put8(impl.silenceSelected ? 1 : 0);
116✔
2327
        w.endChunk(c);
116✔
2328
    }
2329

2330
    {
2331
        const size_t c = w.beginChunk("RECY");
116✔
2332
        w.put8(0xbc);
116✔
2333
        w.put8(0x02);
116✔
2334
        w.put8(0);
116✔
2335
        w.put8(0);
116✔
2336
        w.put8(0);
116✔
2337
        w.put8(1);
116✔
2338
        w.put8(0);
116✔
2339
        w.put32((uint32_t)(impl.totalFrames * impl.info.channels * (impl.info.bit_depth / 8)));
116✔
2340
        w.put32((uint32_t)impl.slices.size());
116✔
2341
        w.endChunk(c);
116✔
2342
    }
2343

2344
    {
2345
        const size_t devl = w.beginCat("DEVL");
116✔
2346
        const size_t trsh = w.beginChunk("TRSH");
116✔
2347
        w.put8(impl.transientEnabled ? 1 : 0);
116✔
2348
        w.put16(impl.transientAttack);
116✔
2349
        w.put16(impl.transientDecay);
116✔
2350
        w.put16(impl.transientStretch);
116✔
2351
        w.endChunk(trsh);
116✔
2352

2353
        const size_t eq = w.beginChunk("EQ  ");
116✔
2354
        const uint8_t eqPayload[] = {0x00, 0x00, 0x0f, 0x00, 0x64, 0x00, 0x00, 0x03, 0xe8, 0x09, 0xc4, 0x00, 0x00, 0x03, 0xe8, 0x4e, 0x20};
116✔
2355
        w.putBytes(eqPayload, sizeof(eqPayload));
116✔
2356
        w.endChunk(eq);
116✔
2357

2358
        const size_t comp = w.beginChunk("COMP");
116✔
2359
        const uint8_t compPayload[] = {0x00, 0x00, 0x4d, 0x00, 0x27, 0x00, 0x42, 0x00, 0x38};
116✔
2360
        w.putBytes(compPayload, sizeof(compPayload));
116✔
2361
        w.endChunk(comp);
116✔
2362

2363
        w.endChunk(devl);
116✔
2364
    }
2365

2366
    {
2367
        const size_t slcl = w.beginCat("SLCL");
116✔
2368
        std::vector<VLSliceEntry> sorted = impl.slices;
116✔
2369
        std::sort(sorted.begin(), sorted.end(),
116✔
2370
                  [](const VLSliceEntry& a, const VLSliceEntry& b)
28,758✔
2371
                  {
2372
                      return a.sample_start < b.sample_start;
28,758✔
2373
                  });
2374
        for (const auto& s : sorted)
3,886✔
2375
        {
2376
            const size_t c = w.beginChunk("SLCE");
3,770✔
2377
            w.put32(s.sample_start);
3,770✔
2378
            w.put32(std::max<uint32_t>(1, s.sample_length));
3,770✔
2379
            w.put16(s.points);
3,770✔
2380
            uint8_t flags = 0;
3,770✔
2381
            if (s.state == kSliceMuted)
3,770✔
2382
                flags |= 0x01;
6✔
2383
            else if (s.state == kSliceLocked)
3,764✔
2384
                flags |= 0x02;
12✔
2385
            if (s.selected_flag)
3,770✔
2386
                flags |= 0x04;
69✔
2387
            w.put8(flags);
3,770✔
2388
            w.endChunk(c);
3,770✔
2389
        }
2390
        w.endChunk(slcl);
116✔
2391
    }
116✔
2392

2393
    {
2394
        const size_t c = w.beginChunk("SINF");
116✔
2395
        w.put8((uint8_t)impl.info.channels);
116✔
2396
        w.put8(bitDepthCode(impl.info.bit_depth));
116✔
2397
        w.put32((uint32_t)impl.info.sample_rate);
116✔
2398
        w.put32(impl.totalFrames);
116✔
2399
        w.put32(impl.loopStart);
116✔
2400
        w.put32(impl.loopEnd);
116✔
2401
        w.endChunk(c);
116✔
2402
    }
2403

2404
    writeSimpleChunk(w, "SDAT", sdat);
116✔
2405
    w.endChunk(root);
116✔
2406
    return w.data;
232✔
2407
}
116✔
2408

2409
} // anonymous namespace
2410

2411
/* -----------------------------------------------------------------------
2412
   VLFile_s  —  the opaque handle
2413
   ----------------------------------------------------------------------- */
2414

2415
struct VLFile_s
2416
{
2417
    VLFileImpl impl;
2418
};
2419

2420
/* -----------------------------------------------------------------------
2421
   Utilities
2422
   ----------------------------------------------------------------------- */
2423

2424
namespace
2425
{
2426

2427
constexpr double kVLTwoPi = 6.283185307179586476925286766559;
2428

2429
bool isPowerOfTwo(int32_t value)
26✔
2430
{
2431
    return value > 0 && (value & (value - 1)) == 0;
26✔
2432
}
2433

2434
VLSuperFluxOptions superfluxDefaults()
26✔
2435
{
2436
    VLSuperFluxOptions o = {};
26✔
2437
    o.frame_size = 2048;
26✔
2438
    o.fps = 200;
26✔
2439
    o.filter_bands = 24;
26✔
2440
    o.max_bins = 3;
26✔
2441
    o.diff_frames = 0;
26✔
2442
    o.min_slice_frames = 0;
26✔
2443
    o.filter_equal = 0;
26✔
2444
    o.online = 0;
26✔
2445
    o.threshold = 1.1f;
26✔
2446
    o.combine_ms = 50.0f;
26✔
2447
    o.pre_avg = 0.15f;
26✔
2448
    o.pre_max = 0.01f;
26✔
2449
    o.post_avg = 0.0f;
26✔
2450
    o.post_max = 0.05f;
26✔
2451
    o.delay_ms = 0.0f;
26✔
2452
    o.ratio = 0.5f;
26✔
2453
    o.fmin = 30.0f;
26✔
2454
    o.fmax = 17000.0f;
26✔
2455
    o.log_mul = 1.0f;
26✔
2456
    o.log_add = 1.0f;
26✔
2457
    return o;
26✔
2458
}
2459

2460
VLError validateSuperFluxOptions(const VLSuperFluxOptions& o, int32_t sampleRate)
26✔
2461
{
2462
    if (!isPowerOfTwo(o.frame_size) || o.frame_size < 64 || o.frame_size > 32768)
26✔
NEW
2463
        return VL_ERROR_INVALID_ARG;
×
2464

2465
    if (o.fps <= 0 || o.fps > sampleRate)
26✔
NEW
2466
        return VL_ERROR_INVALID_ARG;
×
2467

2468
    if (o.filter_bands < 1 || o.max_bins < 1)
26✔
NEW
2469
        return VL_ERROR_INVALID_ARG;
×
2470

2471
    if (o.diff_frames < 0)
26✔
NEW
2472
        return VL_ERROR_INVALID_ARG;
×
2473

2474
    if (o.min_slice_frames < 0)
26✔
NEW
2475
        return VL_ERROR_INVALID_ARG;
×
2476

2477
    if (!std::isfinite(o.threshold) || !std::isfinite(o.combine_ms) || !std::isfinite(o.pre_avg) || !std::isfinite(o.pre_max) ||
52✔
2478
        !std::isfinite(o.post_avg) || !std::isfinite(o.post_max) || !std::isfinite(o.delay_ms) || !std::isfinite(o.ratio) ||
26✔
2479
        !std::isfinite(o.fmin) || !std::isfinite(o.fmax) || !std::isfinite(o.log_mul) || !std::isfinite(o.log_add))
52✔
2480
    {
NEW
2481
        return VL_ERROR_INVALID_ARG;
×
2482
    }
2483

2484
    if (o.combine_ms < 0.0f || o.pre_avg < 0.0f || o.pre_max < 0.0f || o.post_avg < 0.0f || o.post_max < 0.0f)
26✔
NEW
2485
        return VL_ERROR_INVALID_ARG;
×
2486

2487
    if (o.ratio < 0.0f || o.ratio > 1.0f)
26✔
NEW
2488
        return VL_ERROR_INVALID_ARG;
×
2489

2490
    if (o.fmin <= 0.0f || o.fmax <= o.fmin || o.log_mul <= 0.0f || o.log_add <= 0.0f)
26✔
NEW
2491
        return VL_ERROR_INVALID_ARG;
×
2492

2493
    return VL_OK;
26✔
2494
}
2495

2496
std::vector<float> makeHannWindow(int32_t frameSize)
26✔
2497
{
2498
    std::vector<float> window((size_t)frameSize, 1.0f);
26✔
2499
    if (frameSize <= 1)
26✔
NEW
2500
        return window;
×
2501

2502
    for (int32_t i = 0; i < frameSize; ++i)
53,274✔
2503
        window[(size_t)i] = (float)(0.5 - 0.5 * std::cos(kVLTwoPi * (double)i / (double)(frameSize - 1)));
53,248✔
2504

2505
    return window;
26✔
2506
}
2507

2508
std::vector<float> superfluxFrequencies(int32_t bands, float fmin, float fmax)
26✔
2509
{
2510
    std::vector<float> frequencies;
26✔
2511
    const double factor = std::pow(2.0, 1.0 / (double)bands);
26✔
2512

2513
    double freq = 440.0;
26✔
2514
    frequencies.push_back((float)freq);
26✔
2515
    while (freq <= (double)fmax)
3,328✔
2516
    {
2517
        freq *= factor;
3,302✔
2518
        frequencies.push_back((float)freq);
3,302✔
2519
    }
2520

2521
    freq = 440.0;
26✔
2522
    while (freq >= (double)fmin)
2,444✔
2523
    {
2524
        freq /= factor;
2,418✔
2525
        frequencies.push_back((float)freq);
2,418✔
2526
    }
2527

2528
    std::sort(frequencies.begin(), frequencies.end());
26✔
2529
    return frequencies;
26✔
NEW
2530
}
×
2531

2532
bool buildSuperFluxFilterbank(int32_t numFftBins,
26✔
2533
                              int32_t sampleRate,
2534
                              const VLSuperFluxOptions& options,
2535
                              std::vector<float>& filterbank,
2536
                              int32_t& numBands)
2537
{
2538
    const float fmax = std::min(options.fmax, (float)sampleRate * 0.5f);
26✔
2539
    std::vector<float> frequencies = superfluxFrequencies(options.filter_bands, options.fmin, fmax);
26✔
2540
    const double factor = ((double)sampleRate * 0.5) / (double)numFftBins;
26✔
2541

2542
    std::vector<int32_t> bins;
26✔
2543
    bins.reserve(frequencies.size());
26✔
2544
    for (float frequency : frequencies)
5,772✔
2545
    {
2546
        const int32_t bin = (int32_t)std::lround((double)frequency / factor);
5,746✔
2547
        if (bin >= 0 && bin < numFftBins)
5,746✔
2548
            bins.push_back(bin);
5,746✔
2549
    }
2550

2551
    std::sort(bins.begin(), bins.end());
26✔
2552
    bins.erase(std::unique(bins.begin(), bins.end()), bins.end());
26✔
2553
    if (bins.size() < 5)
26✔
NEW
2554
        return false;
×
2555

2556
    numBands = (int32_t)bins.size() - 2;
26✔
2557
    if (numBands < 3)
26✔
NEW
2558
        return false;
×
2559

2560
    filterbank.assign((size_t)numFftBins * (size_t)numBands, 0.0f);
26✔
2561
    for (int32_t band = 0; band < numBands; ++band)
3,692✔
2562
    {
2563
        const int32_t start = bins[(size_t)band];
3,666✔
2564
        const int32_t mid = bins[(size_t)band + 1u];
3,666✔
2565
        const int32_t stop = bins[(size_t)band + 2u];
3,666✔
2566
        if (mid <= start || stop <= mid)
3,666✔
NEW
2567
            continue;
×
2568

2569
        const float height = options.filter_equal ? 2.0f / (float)(stop - start) : 1.0f;
3,666✔
2570
        for (int32_t bin = start; bin < mid; ++bin)
23,868✔
2571
        {
2572
            const float t = (float)(bin - start) / (float)(mid - start);
20,202✔
2573
            filterbank[(size_t)bin * (size_t)numBands + (size_t)band] = t * height;
20,202✔
2574
        }
2575
        for (int32_t bin = mid; bin < stop; ++bin)
24,414✔
2576
        {
2577
            const float t = (float)(bin - mid) / (float)(stop - mid);
20,748✔
2578
            filterbank[(size_t)bin * (size_t)numBands + (size_t)band] = (1.0f - t) * height;
20,748✔
2579
        }
2580
    }
2581

2582
    return true;
26✔
2583
}
26✔
2584

2585
int32_t deriveSuperFluxDiffFrames(const std::vector<float>& window, double hopSize, float ratio)
26✔
2586
{
2587
    size_t sample = 0;
26✔
2588
    while (sample < window.size() && window[sample] <= ratio)
13,338✔
2589
        ++sample;
13,312✔
2590

2591
    const double diffSamples = (double)window.size() * 0.5 - (double)sample;
26✔
2592
    return std::max(1, (int32_t)std::lround(diffSamples / hopSize));
26✔
2593
}
2594

2595
bool computeSuperFluxActivations(const float* left,
26✔
2596
                                 const float* right,
2597
                                 int32_t channels,
2598
                                 int32_t frames,
2599
                                 int32_t sampleRate,
2600
                                 const VLSuperFluxOptions& options,
2601
                                 std::vector<float>& activations)
2602
{
2603
    const int32_t frameSize = options.frame_size;
26✔
2604
    const int32_t numFftBins = frameSize / 2;
26✔
2605
    const double hopSize = (double)sampleRate / (double)options.fps;
26✔
2606
    const int32_t numFrames = std::max(1, (int32_t)std::ceil((double)frames / hopSize));
26✔
2607

2608
    std::vector<float> window = makeHannWindow(frameSize);
26✔
2609
    std::vector<float> filterbank;
26✔
2610
    int32_t numBands = 0;
26✔
2611
    if (!buildSuperFluxFilterbank(numFftBins, sampleRate, options, filterbank, numBands))
26✔
NEW
2612
        return false;
×
2613

2614
    std::vector<float> spec((size_t)numFrames * (size_t)numBands, 0.0f);
26✔
2615
    std::vector<double> fftBuffer((size_t)frameSize);
52✔
2616
    std::vector<float> magnitudes((size_t)numFftBins);
52✔
2617
    std::vector<int> fftIp(2 + (size_t)frameSize, 0);
26✔
2618
    std::vector<double> fftW((size_t)frameSize / 2);
26✔
2619

2620
    for (int32_t frame = 0; frame < numFrames; ++frame)
17,725✔
2621
    {
2622
        const int32_t seek = options.online ? (int32_t)((double)(frame + 1) * hopSize - (double)frameSize)
17,699✔
2623
                                            : (int32_t)((double)frame * hopSize - (double)frameSize * 0.5);
17,699✔
2624

2625
        for (int32_t i = 0; i < frameSize; ++i)
36,265,251✔
2626
        {
2627
            const int32_t src = seek + i;
36,247,552✔
2628
            float sample = 0.0f;
36,247,552✔
2629
            if (src >= 0 && src < frames)
36,247,552✔
2630
            {
2631
                const float l = std::isfinite(left[src]) ? left[src] : 0.0f;
36,106,416✔
2632
                if (channels == 2)
36,106,416✔
2633
                {
2634
                    const float r = std::isfinite(right[src]) ? right[src] : 0.0f;
4,237,454✔
2635
                    sample = (l + r) * 0.5f;
4,237,454✔
2636
                }
2637
                else
2638
                {
2639
                    sample = l;
31,868,962✔
2640
                }
2641
            }
2642
            fftBuffer[(size_t)i] = (double)(sample * window[(size_t)i]);
36,247,552✔
2643
        }
2644

2645
        rdft(frameSize, 1, fftBuffer.data(), fftIp.data(), fftW.data());
17,699✔
2646

2647
        magnitudes[0] = (float)std::abs(fftBuffer[0]);
17,699✔
2648
        for (int32_t bin = 1; bin < numFftBins; ++bin)
18,123,776✔
2649
        {
2650
            const double re = fftBuffer[(size_t)bin * 2];
18,106,077✔
2651
            const double im = fftBuffer[(size_t)bin * 2 + 1];
18,106,077✔
2652
            magnitudes[(size_t)bin] = (float)std::sqrt(re * re + im * im);
18,106,077✔
2653
        }
2654

2655
        for (int32_t band = 0; band < numBands; ++band)
2,513,258✔
2656
        {
2657
            double value = 0.0;
2,495,559✔
2658
            for (int32_t bin = 0; bin < numFftBins; ++bin)
2,147,483,647✔
2659
                value += (double)magnitudes[(size_t)bin] * (double)filterbank[(size_t)bin * (size_t)numBands + (size_t)band];
2,147,483,647✔
2660

2661
            const float logged = std::log10(options.log_mul * (float)value + options.log_add);
2,495,559✔
2662
            spec[(size_t)frame * (size_t)numBands + (size_t)band] = logged;
2,495,559✔
2663
        }
2664
    }
2665

2666
    const int32_t diffFrames =
2667
        options.diff_frames > 0 ? options.diff_frames : deriveSuperFluxDiffFrames(window, hopSize, options.ratio);
26✔
2668

2669
    activations.assign((size_t)numFrames, 0.0f);
26✔
2670
    for (int32_t frame = diffFrames; frame < numFrames; ++frame)
17,673✔
2671
    {
2672
        float sum = 0.0f;
17,647✔
2673
        const int32_t prevFrame = frame - diffFrames;
17,647✔
2674
        for (int32_t band = 0; band < numBands; ++band)
2,505,874✔
2675
        {
2676
            int32_t first = band - options.max_bins / 2;
2,488,227✔
2677
            int32_t last = first + options.max_bins - 1;
2,488,227✔
2678
            if (first < 0)
2,488,227✔
2679
            {
2680
                last -= first;
17,647✔
2681
                first = 0;
17,647✔
2682
            }
2683
            if (last >= numBands)
2,488,227✔
2684
            {
2685
                first = std::max(0, first - (last - numBands + 1));
17,647✔
2686
                last = numBands - 1;
17,647✔
2687
            }
2688
            float prevMax = spec[(size_t)prevFrame * (size_t)numBands + (size_t)band];
2,488,227✔
2689
            for (int32_t k = first; k <= last; ++k)
9,952,908✔
2690
                prevMax = std::max(prevMax, spec[(size_t)prevFrame * (size_t)numBands + (size_t)k]);
7,464,681✔
2691

2692
            const float diff = spec[(size_t)frame * (size_t)numBands + (size_t)band] - prevMax;
2,488,227✔
2693
            if (diff > 0.0f)
2,488,227✔
2694
                sum += diff;
415,324✔
2695
        }
2696
        activations[(size_t)frame] = sum;
17,647✔
2697
    }
2698

2699
    return true;
26✔
2700
}
26✔
2701

2702
std::vector<double> pickSuperFluxOnsets(const std::vector<float>& activations, int32_t fps, const VLSuperFluxOptions& options)
26✔
2703
{
2704
    const int32_t count = (int32_t)activations.size();
26✔
2705
    const int32_t preAvg = std::max(0, (int32_t)std::lround((double)fps * (double)options.pre_avg));
26✔
2706
    const int32_t preMax = std::max(0, (int32_t)std::lround((double)fps * (double)options.pre_max));
26✔
2707
    const int32_t postAvg = options.online ? 0 : std::max(0, (int32_t)std::lround((double)fps * (double)options.post_avg));
26✔
2708
    const int32_t postMax = options.online ? 0 : std::max(0, (int32_t)std::lround((double)fps * (double)options.post_max));
26✔
2709
    const double combineSeconds = (double)options.combine_ms / 1000.0;
26✔
2710
    const double delaySeconds = (double)options.delay_ms / 1000.0;
26✔
2711

2712
    std::vector<double> detections;
26✔
2713
    double lastDetection = -std::numeric_limits<double>::infinity();
26✔
2714
    for (int32_t frame = 0; frame < count; ++frame)
17,725✔
2715
    {
2716
        const int32_t maxStart = std::max(0, frame - preMax);
17,699✔
2717
        const int32_t maxStop = std::min(count - 1, frame + postMax);
17,699✔
2718
        float movMax = 0.0f;
17,699✔
2719
        for (int32_t i = maxStart; i <= maxStop; ++i)
243,146✔
2720
            movMax = std::max(movMax, activations[(size_t)i]);
225,447✔
2721

2722
        const int32_t avgStart = std::max(0, frame - preAvg);
17,699✔
2723
        const int32_t avgStop = std::min(count - 1, frame + postAvg);
17,699✔
2724
        double avg = 0.0;
17,699✔
2725
        for (int32_t i = avgStart; i <= avgStop; ++i)
554,278✔
2726
            avg += activations[(size_t)i];
536,579✔
2727
        avg /= (double)(avgStop - avgStart + 1);
17,699✔
2728

2729
        const float value = activations[(size_t)frame];
17,699✔
2730
        if (value <= 0.0f || value != movMax || (double)value < avg + (double)options.threshold)
17,699✔
2731
            continue;
17,101✔
2732

2733
        const double time = (double)frame / (double)fps + delaySeconds;
598✔
2734
        if (detections.empty() || time - lastDetection > combineSeconds)
598✔
2735
        {
2736
            detections.push_back(time);
476✔
2737
            lastDetection = time;
476✔
2738
        }
2739
    }
2740

2741
    return detections;
26✔
NEW
2742
}
×
2743

2744
std::vector<uint32_t> superfluxSliceBoundaries(const std::vector<double>& detections,
26✔
2745
                                               int32_t frames,
2746
                                               int32_t sampleRate,
2747
                                               const VLSuperFluxOptions& options)
2748
{
2749
    const uint32_t combineFrames =
2750
        (uint32_t)std::max(1, (int32_t)std::lround((double)options.combine_ms / 1000.0 * (double)sampleRate));
26✔
2751
    const uint32_t minSliceFrames =
2752
        (uint32_t)(options.min_slice_frames > 0 ? (uint32_t)options.min_slice_frames : std::max(combineFrames, (uint32_t)std::max(1, (int32_t)std::lround((double)sampleRate * 0.01))));
26✔
2753

2754
    std::vector<uint32_t> boundaries;
26✔
2755
    boundaries.push_back(0);
26✔
2756

2757
    for (double detection : detections)
502✔
2758
    {
2759
        const int64_t rounded = (int64_t)std::llround(detection * (double)sampleRate);
476✔
2760
        if (rounded <= 0 || rounded >= frames)
476✔
NEW
2761
            continue;
×
2762

2763
        const uint32_t sample = (uint32_t)rounded;
476✔
2764
        if (sample - boundaries.back() >= minSliceFrames)
476✔
2765
            boundaries.push_back(sample);
457✔
2766
    }
2767

2768
    if ((uint32_t)frames - boundaries.back() < minSliceFrames && boundaries.size() > 1)
26✔
2769
        boundaries.pop_back();
2✔
2770

2771
    boundaries.push_back((uint32_t)frames);
26✔
2772
    return boundaries;
52✔
NEW
2773
}
×
2774

2775
int32_t addSliceAtSample(VLFile file, uint32_t sample_start, int32_t ppq_pos, const float* left, const float* right, int32_t frames)
1,115✔
2776
{
2777
    if (!file)
1,115✔
UNCOV
2778
        return (int32_t)VL_ERROR_INVALID_HANDLE;
×
2779

2780
    if (!left || frames <= 0 || ppq_pos < 0)
1,115✔
2781
        return (int32_t)VL_ERROR_INVALID_ARG;
2✔
2782

2783
    const int32_t channels = std::clamp(file->impl.info.channels, 1, 2);
1,113✔
2784
    if (channels == 2 && !right)
1,113✔
2785
        return (int32_t)VL_ERROR_INVALID_ARG;
1✔
2786

2787
    const uint32_t end = sample_start + (uint32_t)frames;
1,112✔
2788
    const uint32_t declaredFrames = file->impl.info.total_frames > 0 ? (uint32_t)file->impl.info.total_frames : 0u;
1,112✔
2789
    const uint32_t requiredFrames = std::max(end, declaredFrames);
1,112✔
2790
    const size_t required = (size_t)requiredFrames * (size_t)channels;
1,112✔
2791

2792
    if (required > file->impl.pcm.max_size())
1,112✔
2793
        return (int32_t)VL_ERROR_OUT_OF_MEMORY; // LCOV_EXCL_LINE max_size is not reachable in tests.
2794

2795
    if (file->impl.pcm.size() < required)
1,112✔
2796
    {
2797
        if (!file->impl.pcm.resize(required, 0))
55✔
UNCOV
2798
            return (int32_t)VL_ERROR_OUT_OF_MEMORY;
×
2799
    }
2800

2801
    for (int32_t f = 0; f < frames; ++f)
5,819,336✔
2802
    {
2803
        const size_t dst = ((size_t)sample_start + (size_t)f) * (size_t)channels;
5,818,224✔
2804
        file->impl.pcm[dst] = floatToPCM(left[f], file->impl.info.bit_depth);
5,818,224✔
2805
        if (channels == 2)
5,818,224✔
2806
            file->impl.pcm[dst + 1] = floatToPCM(right[f], file->impl.info.bit_depth);
589,208✔
2807
    }
2808

2809
    VLSliceEntry s;
1,112✔
2810
    s.ppq_pos = (uint32_t)ppq_pos;
1,112✔
2811
    s.sample_start = sample_start;
1,112✔
2812
    s.sample_length = (uint32_t)frames;
1,112✔
2813
    s.rendered_length = (uint32_t)frames;
1,112✔
2814
    s.points = 0x7fff;
1,112✔
2815
    s.selected_flag = 0;
1,112✔
2816
    s.state = kSliceNormal;
1,112✔
2817

2818
    file->impl.totalFrames = (uint32_t)(file->impl.pcm.size() / (size_t)channels);
1,112✔
2819
    file->impl.info.total_frames = (int32_t)file->impl.totalFrames;
1,112✔
2820
    file->impl.cacheSliceLoop(s);
1,112✔
2821

2822
    if (file->impl.slices.size() == file->impl.slices.max_size())
1,112✔
2823
        return (int32_t)VL_ERROR_OUT_OF_MEMORY; // LCOV_EXCL_LINE max_size is not reachable in tests.
2824
    file->impl.slices.push_back(s);
1,112✔
2825

2826
    file->impl.info.slice_count = (int32_t)file->impl.slices.size();
1,112✔
2827
    if (file->impl.info.loop_end <= file->impl.info.loop_start)
1,112✔
2828
        file->impl.info.loop_end = file->impl.info.total_frames;
9✔
2829

2830
    return (int32_t)file->impl.slices.size() - 1;
1,112✔
2831
}
2832

2833
} // anonymous namespace
2834

2835
/* -----------------------------------------------------------------------
2836
   Open / close
2837
   ----------------------------------------------------------------------- */
2838

2839
VLFile vl_open(const char* path, VLError* err)
95✔
2840
{
2841
    auto set = [&](VLError e)
2✔
2842
    {
2843
        if (err)
2✔
2844
            *err = e;
2✔
2845
    };
97✔
2846

2847
    if (!path)
95✔
2848
    {
2849
        set(VL_ERROR_INVALID_ARG);
1✔
2850
        return nullptr;
1✔
2851
    }
2852

2853
    std::ifstream f(path, std::ios::binary | std::ios::ate);
94✔
2854
    if (!f)
94✔
2855
    {
2856
        set(VL_ERROR_FILE_NOT_FOUND);
1✔
2857
        return nullptr;
1✔
2858
    }
2859

2860
    const std::streamsize sz = f.tellg();
93✔
2861
    f.seekg(0);
93✔
2862

2863
    std::vector<char> buf((size_t)sz);
93✔
2864
    if (!f.read(buf.data(), sz))
93✔
2865
    {
UNCOV
2866
        set(VL_ERROR_FILE_CORRUPT);
×
2867
        return nullptr;
×
2868
    }
2869

2870
    return vl_open_from_memory(buf.data(), (size_t)sz, err);
93✔
2871
}
94✔
2872

2873
VLFile vl_open_from_memory(const void* data, size_t size, VLError* err)
194✔
2874
{
2875
    auto set = [&](VLError e)
194✔
2876
    {
2877
        if (err)
194✔
2878
            *err = e;
194✔
2879
    };
388✔
2880

2881
    if (!data || size == 0)
194✔
2882
    {
2883
        set(VL_ERROR_INVALID_ARG);
1✔
2884
        return nullptr;
1✔
2885
    }
2886

2887
    VLFile_s* h = new (std::nothrow) VLFile_s();
193✔
2888
    if (!h)
193✔
2889
    {
UNCOV
2890
        set(VL_ERROR_OUT_OF_MEMORY);
×
2891
        return nullptr;
×
2892
    }
2893

2894
    if (!h->impl.loadFromBuffer((const char*)data, size))
193✔
2895
    {
2896
        const VLError loadError = h->impl.loadError != VL_OK ? h->impl.loadError : VL_ERROR_FILE_CORRUPT;
93✔
2897
        delete h;
93✔
2898
        set(loadError);
93✔
2899
        return nullptr;
93✔
2900
    }
2901

2902
    set(VL_OK);
100✔
2903
    return h;
100✔
2904
}
2905

2906
VLFile vl_create_new(int32_t channels, int32_t sample_rate, int32_t tempo, VLError* err)
67✔
2907
{
2908
    auto set = [&](VLError e)
67✔
2909
    {
2910
        if (err)
67✔
2911
            *err = e;
51✔
2912
    };
134✔
2913

2914
    if (channels != 1 && channels != 2)
67✔
2915
    {
2916
        set(VL_ERROR_INVALID_ARG);
4✔
2917
        return nullptr;
4✔
2918
    }
2919

2920
    if (sample_rate < 8000 || sample_rate > 192000)
63✔
2921
    {
2922
        set(VL_ERROR_INVALID_SAMPLE_RATE);
8✔
2923
        return nullptr;
8✔
2924
    }
2925

2926
    if (tempo <= 0)
55✔
2927
    {
2928
        set(VL_ERROR_INVALID_TEMPO);
4✔
2929
        return nullptr;
4✔
2930
    }
2931

2932
    VLFile_s* h = new (std::nothrow) VLFile_s();
51✔
2933
    if (!h)
51✔
2934
    {
UNCOV
2935
        set(VL_ERROR_OUT_OF_MEMORY);
×
2936
        return nullptr;
×
2937
    }
2938

2939
    h->impl.info.channels = channels;
51✔
2940
    h->impl.info.sample_rate = sample_rate;
51✔
2941
    h->impl.info.slice_count = 0;
51✔
2942
    h->impl.info.tempo = tempo;
51✔
2943
    h->impl.info.original_tempo = tempo;
51✔
2944
    h->impl.info.ppq_length = VLFileImpl::kREXPPQ * 4;
51✔
2945
    h->impl.info.time_sig_num = 4;
51✔
2946
    h->impl.info.time_sig_den = 4;
51✔
2947
    h->impl.info.bit_depth = 16;
51✔
2948
    h->impl.info.total_frames = 0;
51✔
2949
    h->impl.info.loop_start = 0;
51✔
2950
    h->impl.info.loop_end = 0;
51✔
2951
    h->impl.info.processing_gain = 1000;
51✔
2952
    h->impl.info.transient_enabled = 1;
51✔
2953
    h->impl.info.transient_attack = 0;
51✔
2954
    h->impl.info.transient_decay = 1023;
51✔
2955
    h->impl.info.transient_stretch = 0;
51✔
2956
    h->impl.info.silence_selected = 0;
51✔
2957
    h->impl.processingGain = 1000;
51✔
2958
    h->impl.transientEnabled = true;
51✔
2959
    h->impl.transientAttack = 0;
51✔
2960
    h->impl.transientDecay = 1023;
51✔
2961
    h->impl.transientStretch = 0;
51✔
2962
    h->impl.silenceSelected = false;
51✔
2963

2964
    set(VL_OK);
51✔
2965
    return h;
51✔
2966
}
2967

2968
void vl_superflux_default_options(VLSuperFluxOptions* out)
9✔
2969
{
2970
    if (out)
9✔
2971
        *out = superfluxDefaults();
8✔
2972
}
9✔
2973

2974
VLFile vl_create_from_superflux(int32_t channels,
31✔
2975
                                int32_t sample_rate,
2976
                                int32_t tempo,
2977
                                const float* left,
2978
                                const float* right,
2979
                                int32_t frames,
2980
                                const VLSuperFluxOptions* options,
2981
                                VLError* err)
2982
{
2983
    auto set = [&](VLError e)
31✔
2984
    {
2985
        if (err)
31✔
2986
            *err = e;
31✔
2987
    };
62✔
2988

2989
    if (channels != 1 && channels != 2)
31✔
2990
    {
2991
        set(VL_ERROR_INVALID_ARG);
1✔
2992
        return nullptr;
1✔
2993
    }
2994

2995
    if (sample_rate < 8000 || sample_rate > 192000)
30✔
2996
    {
2997
        set(VL_ERROR_INVALID_SAMPLE_RATE);
1✔
2998
        return nullptr;
1✔
2999
    }
3000

3001
    if (tempo <= 0)
29✔
3002
    {
3003
        set(VL_ERROR_INVALID_TEMPO);
1✔
3004
        return nullptr;
1✔
3005
    }
3006

3007
    if (!left || frames <= 0 || (channels == 2 && !right))
28✔
3008
    {
3009
        set(VL_ERROR_INVALID_ARG);
2✔
3010
        return nullptr;
2✔
3011
    }
3012

3013
    VLSuperFluxOptions opts = options ? *options : superfluxDefaults();
26✔
3014
    VLError optionError = validateSuperFluxOptions(opts, sample_rate);
26✔
3015
    if (optionError != VL_OK)
26✔
3016
    {
NEW
3017
        set(optionError);
×
NEW
3018
        return nullptr;
×
3019
    }
3020

3021
    try
3022
    {
3023
        std::vector<float> activations;
26✔
3024
        if (!computeSuperFluxActivations(left, right, channels, frames, sample_rate, opts, activations))
26✔
3025
        {
NEW
3026
            set(VL_ERROR_INVALID_ARG);
×
NEW
3027
            return nullptr;
×
3028
        }
3029

3030
        const std::vector<double> detections = pickSuperFluxOnsets(activations, opts.fps, opts);
26✔
3031
        const std::vector<uint32_t> boundaries = superfluxSliceBoundaries(detections, frames, sample_rate, opts);
26✔
3032

3033
        VLError createError = VL_OK;
26✔
3034
        VLFile file = vl_create_new(channels, sample_rate, tempo, &createError);
26✔
3035
        if (!file)
26✔
3036
        {
NEW
3037
            set(createError);
×
NEW
3038
            return nullptr;
×
3039
        }
3040

3041
        VLFileInfo info = {};
26✔
3042
        if (vl_get_info(file, &info) != VL_OK)
26✔
3043
        {
NEW
3044
            vl_close(file);
×
3045
            set(VL_ERROR_INVALID_HANDLE); // LCOV_EXCL_LINE freshly-created handles are valid.
NEW
3046
            return nullptr;
×
3047
        }
3048

3049
        const double beats = ((double)frames * (double)tempo) / (60000.0 * (double)sample_rate);
26✔
3050
        info.ppq_length = std::max(1, (int32_t)std::lround(beats * (double)VLFileImpl::kREXPPQ));
26✔
3051
        info.total_frames = frames;
26✔
3052
        info.loop_start = 0;
26✔
3053
        info.loop_end = frames;
26✔
3054
        info.transient_enabled = 0;
26✔
3055
        info.transient_stretch = 0;
26✔
3056
        info.transient_attack = 0;
26✔
3057
        info.transient_decay = 1023;
26✔
3058
        VLError infoError = vl_set_info(file, &info);
26✔
3059
        if (infoError != VL_OK)
26✔
3060
        {
NEW
3061
            vl_close(file);
×
NEW
3062
            set(infoError);
×
NEW
3063
            return nullptr;
×
3064
        }
3065

3066
        int32_t previousPpq = -1;
26✔
3067
        for (size_t i = 0; i + 1 < boundaries.size(); ++i)
507✔
3068
        {
3069
            const uint32_t start = boundaries[i];
481✔
3070
            const uint32_t stop = boundaries[i + 1u];
481✔
3071
            if (stop <= start)
481✔
NEW
3072
                continue;
×
3073

3074
            int32_t ppq = (int32_t)(((uint64_t)start * (uint64_t)info.ppq_length + (uint32_t)frames / 2u) / (uint32_t)frames);
481✔
3075
            if (ppq <= previousPpq)
481✔
NEW
3076
                ppq = previousPpq + 1;
×
3077
            ppq = std::min(ppq, std::max(0, info.ppq_length - 1));
481✔
3078
            previousPpq = ppq;
481✔
3079

3080
            const int32_t sliceIndex =
3081
                addSliceAtSample(file, start, ppq, left + start, channels == 2 ? right + start : nullptr, (int32_t)(stop - start));
481✔
3082
            if (sliceIndex < 0)
481✔
3083
            {
NEW
3084
                const VLError sliceError = (VLError)sliceIndex;
×
NEW
3085
                vl_close(file);
×
NEW
3086
                set(sliceError);
×
NEW
3087
                return nullptr;
×
3088
            }
3089
        }
3090

3091
        if (file->impl.slices.empty())
26✔
3092
        {
NEW
3093
            vl_close(file);
×
3094
            set(VL_ERROR_INVALID_ARG); // LCOV_EXCL_LINE boundaries always create at least one slice for frames > 0.
NEW
3095
            return nullptr;
×
3096
        }
3097

3098
        set(VL_OK);
26✔
3099
        return file;
26✔
3100
    }
26✔
NEW
3101
    catch (const std::bad_alloc&)
×
3102
    {
NEW
3103
        set(VL_ERROR_OUT_OF_MEMORY);
×
NEW
3104
        return nullptr;
×
NEW
3105
    }
×
NEW
3106
    catch (...)
×
3107
    {
NEW
3108
        set(VL_ERROR_OUT_OF_MEMORY);
×
NEW
3109
        return nullptr;
×
NEW
3110
    }
×
3111
}
3112

3113
void vl_close(VLFile file)
209✔
3114
{
3115
    delete file;
209✔
3116
}
209✔
3117

3118
/* -----------------------------------------------------------------------
3119
   Read: metadata
3120
   ----------------------------------------------------------------------- */
3121

3122
VLError vl_get_info(VLFile file, VLFileInfo* out)
162✔
3123
{
3124
    if (!file)
162✔
3125
        return VL_ERROR_INVALID_HANDLE;
1✔
3126

3127
    if (!out)
161✔
3128
        return VL_ERROR_INVALID_ARG;
16✔
3129

3130
    *out = file->impl.info;
145✔
3131
    return VL_OK;
145✔
3132
}
3133

3134
VLError vl_get_creator_info(VLFile file, VLCreatorInfo* out)
35✔
3135
{
3136
    if (!file)
35✔
3137
        return VL_ERROR_INVALID_HANDLE;
1✔
3138

3139
    if (!out)
34✔
3140
        return VL_ERROR_INVALID_ARG;
16✔
3141

3142
    const VLCreatorInfo& src = file->impl.creator;
18✔
3143
    if (!src.name[0] && !src.copyright[0] && !src.url[0] && !src.email[0] && !src.free_text[0])
18✔
3144
        return VL_ERROR_NO_CREATOR_INFO;
16✔
3145

3146
    *out = src;
2✔
3147
    return VL_OK;
2✔
3148
}
3149

3150
/* -----------------------------------------------------------------------
3151
   Read: slice enumeration
3152
   ----------------------------------------------------------------------- */
3153

3154
VLError vl_get_slice_info(VLFile file, int32_t index, VLSliceInfo* out)
2,242✔
3155
{
3156
    if (!file)
2,242✔
3157
        return VL_ERROR_INVALID_HANDLE;
1✔
3158

3159
    if (!out)
2,241✔
3160
        return VL_ERROR_INVALID_ARG;
32✔
3161

3162
    if (index < 0 || (size_t)index >= file->impl.slices.size())
2,209✔
3163
        return VL_ERROR_INVALID_SLICE;
32✔
3164

3165
    const VLSliceEntry& s = file->impl.slices[(size_t)index];
2,177✔
3166
    out->ppq_pos = (int32_t)s.ppq_pos;
2,177✔
3167
    out->sample_length = (int32_t)s.sample_length;
2,177✔
3168
    out->sample_start = (int32_t)s.sample_start;
2,177✔
3169
    out->analysis_points = (int32_t)s.points;
2,177✔
3170
    out->flags = sliceFlags(s);
2,177✔
3171
    return VL_OK;
2,177✔
3172
}
3173

3174
VLError vl_set_slice_info(VLFile file, int32_t index, int32_t flags, int32_t analysis_points)
39✔
3175
{
3176
    if (!file)
39✔
3177
        return VL_ERROR_INVALID_HANDLE;
1✔
3178

3179
    if (index < 0 || (size_t)index >= file->impl.slices.size())
38✔
3180
        return VL_ERROR_INVALID_SLICE;
32✔
3181

3182
    VLError err = applySliceFlags(file->impl.slices[(size_t)index], flags, analysis_points);
6✔
3183
    if (err != VL_OK)
6✔
3184
        return err;
3✔
3185

3186
    return VL_OK;
3✔
3187
}
3188

3189
/* -----------------------------------------------------------------------
3190
   Read: sample extraction
3191
   ----------------------------------------------------------------------- */
3192

3193
int32_t vl_get_slice_frame_count(VLFile file, int32_t index)
1,573✔
3194
{
3195
    if (!file)
1,573✔
3196
        return (int32_t)VL_ERROR_INVALID_HANDLE;
1✔
3197

3198
    if (index < 0 || (size_t)index >= file->impl.slices.size())
1,572✔
3199
        return (int32_t)VL_ERROR_INVALID_SLICE;
34✔
3200

3201
    return (int32_t)file->impl.slices[(size_t)index].rendered_length;
1,538✔
3202
}
3203

3204
VLError vl_decode_slice(VLFile file, int32_t index, float* left, float* right, int32_t frame_offset, int32_t capacity, int32_t* frames_out)
1,588✔
3205
{
3206
    if (!file)
1,588✔
3207
        return VL_ERROR_INVALID_HANDLE;
1✔
3208

3209
    if (!left)
1,587✔
3210
        return VL_ERROR_INVALID_ARG;
14✔
3211

3212
    if (frame_offset < 0)
1,573✔
3213
        return VL_ERROR_INVALID_ARG;
14✔
3214

3215
    if (index < 0 || (size_t)index >= file->impl.slices.size())
1,559✔
3216
        return VL_ERROR_INVALID_SLICE;
28✔
3217

3218
    const VLSliceEntry& s = file->impl.slices[(size_t)index];
1,531✔
3219
    const int32_t renderedFrames = (int32_t)s.rendered_length;
1,531✔
3220
    if (frame_offset > renderedFrames)
1,531✔
3221
        return VL_ERROR_INVALID_ARG;
14✔
3222

3223
    const int32_t needed = renderedFrames - frame_offset;
1,517✔
3224
    if (capacity < needed)
1,517✔
3225
        return VL_ERROR_BUFFER_TOO_SMALL;
15✔
3226

3227
    const auto& pcm = file->impl.pcm;
1,502✔
3228
    if (pcm.empty())
1,502✔
UNCOV
3229
        return VL_ERROR_FILE_CORRUPT;
×
3230

3231
    const uint32_t requestedFrames = (uint32_t)renderedFrames;
1,502✔
3232
    uint32_t sourceStart = s.sample_start;
1,502✔
3233
    if (sourceStart >= file->impl.totalFrames)
1,502✔
3234
    {
3235
        std::fill(left, left + needed, 0.f);
1✔
3236

3237
        if (right)
1✔
3238
            std::fill(right, right + needed, 0.f);
1✔
3239

3240
        if (frames_out)
1✔
3241
            *frames_out = needed;
1✔
3242

3243
        return VL_OK;
1✔
3244
    }
3245

3246
    uint32_t sourceFrames = s.sample_length;
1,501✔
3247
    if (file->impl.loopEnd > file->impl.loopStart && sourceStart < file->impl.loopEnd)
1,501✔
3248
        sourceFrames = std::min<uint32_t>(sourceFrames, file->impl.loopEnd - sourceStart);
1,265✔
3249
    sourceFrames = std::min<uint32_t>(sourceFrames, file->impl.totalFrames - sourceStart);
1,501✔
3250

3251
    const uint32_t sourceEnd = sourceStart + sourceFrames;
1,501✔
3252
    const VLFileImpl::SegmentLoop segmentLoop{
3253
        s.render_loop_start,
1,501✔
3254
        s.render_loop_end,
1,501✔
3255
        s.render_loop_volume_compensation,
1,501✔
3256
    };
1,501✔
3257
    const int32_t ch = std::max(1, file->impl.info.channels);
1,501✔
3258
    const float samplerGain = (float)file->impl.processingGain * 0.000833333354f;
1,501✔
3259

3260
    uint32_t samplePos = std::min(sourceStart + 2u, sourceEnd);
1,501✔
3261
    int loopPhase = 0; // 0: forward source, 1: forward loop, 2: backward loop
1,501✔
3262
    bool stretchPhase = false;
1,501✔
3263
    float stretchEnv = 1.0f;
1,501✔
3264
    const uint32_t stretchFrameCount =
3265
        std::max<uint32_t>(1u, sourceEnd - segmentLoop.end + (requestedFrames > sourceFrames ? requestedFrames - sourceFrames : 0u));
1,501✔
3266
    const float stretchEnvDec = 1.0f / (float)stretchFrameCount;
1,501✔
3267
    float loopLevelComp = 1.0f;
1,501✔
3268
    const float loopLevelCompInc =
1,501✔
3269
        (segmentLoop.end > segmentLoop.start) ? (1.0f - segmentLoop.volumeCompensation) / (float)(segmentLoop.end - segmentLoop.start) : 0.0f;
1,501✔
3270

3271
    for (uint32_t f = 0; f < requestedFrames; ++f)
5,986,316✔
3272
    {
3273
        const size_t src = (size_t)samplePos * (size_t)ch;
5,984,815✔
3274
        float l = 0.f;
5,984,815✔
3275
        float r = 0.f;
5,984,815✔
3276

3277
        if (src < pcm.size())
5,984,815✔
3278
        {
3279
            const float level = samplerGain * stretchEnv * loopLevelComp;
5,984,815✔
3280
            l = pcmToFloat(pcm[src], file->impl.info.bit_depth) * level;
5,984,815✔
3281
            r = (ch >= 2 && src + 1 < pcm.size()) ? pcmToFloat(pcm[src + 1], file->impl.info.bit_depth) * level : l;
5,984,815✔
3282
        }
3283

3284
        if ((int32_t)f >= frame_offset)
5,984,815✔
3285
        {
3286
            const size_t dst = (size_t)((int32_t)f - frame_offset);
5,970,771✔
3287
            left[dst] = l;
5,970,771✔
3288
            if (right)
5,970,771✔
3289
                right[dst] = r;
674,441✔
3290
        }
3291

3292
        if (stretchPhase)
5,984,815✔
3293
        {
3294
            stretchEnv = std::max(0.0f, stretchEnv - stretchEnvDec);
709,283✔
3295
            if (loopPhase == 1)
709,283✔
3296
                loopLevelComp += loopLevelCompInc;
66,954✔
3297
            else if (loopPhase == 2)
642,329✔
3298
                loopLevelComp -= loopLevelCompInc;
642,329✔
3299
        }
3300

3301
        if (loopPhase <= 1)
5,984,815✔
3302
        {
3303
            ++samplePos;
5,342,486✔
3304
            if (samplePos >= segmentLoop.end)
5,342,486✔
3305
            {
3306
                stretchPhase = true;
1,503✔
3307
                loopPhase = 2;
1,503✔
3308
                if (samplePos > 0)
1,503✔
3309
                    --samplePos;
1,503✔
3310
                if (samplePos <= segmentLoop.start)
1,503✔
UNCOV
3311
                    loopPhase = 1;
×
3312
            }
3313
        }
3314
        else
3315
        {
3316
            if (samplePos > 0)
642,329✔
3317
                --samplePos;
642,329✔
3318
            if (samplePos <= segmentLoop.start)
642,329✔
3319
                loopPhase = 1;
33✔
3320
        }
3321
    }
3322

3323
    if (frames_out)
1,501✔
3324
        *frames_out = needed;
1,501✔
3325

3326
    return VL_OK;
1,501✔
3327
}
3328

3329
/* -----------------------------------------------------------------------
3330
   Write: assembly from audio slices
3331
   ----------------------------------------------------------------------- */
3332

3333
VLError vl_set_info(VLFile file, const VLFileInfo* info)
54✔
3334
{
3335
    if (!file)
54✔
3336
        return VL_ERROR_INVALID_HANDLE;
1✔
3337

3338
    if (!info)
53✔
3339
        return VL_ERROR_INVALID_ARG;
1✔
3340

3341
    if (!file->impl.slices.empty() || !file->impl.pcm.empty())
52✔
3342
        return VL_ERROR_ALREADY_HAS_DATA;
1✔
3343

3344
    if (info->channels != 1 && info->channels != 2)
51✔
UNCOV
3345
        return VL_ERROR_INVALID_ARG;
×
3346

3347
    if (info->sample_rate < 8000 || info->sample_rate > 192000)
51✔
3348
        return VL_ERROR_INVALID_SAMPLE_RATE;
1✔
3349

3350
    if (info->tempo <= 0)
50✔
3351
        return VL_ERROR_INVALID_TEMPO;
1✔
3352

3353
    file->impl.info = *info;
49✔
3354
    file->impl.info.channels = info->channels;
49✔
3355
    file->impl.info.sample_rate = info->sample_rate;
49✔
3356
    file->impl.info.bit_depth = storageBitDepth(info->bit_depth);
49✔
3357
    file->impl.processingGain = (uint16_t)std::clamp(info->processing_gain > 0 ? info->processing_gain : 1000, 0, 1000);
49✔
3358
    file->impl.transientEnabled = info->transient_enabled != 0;
49✔
3359
    file->impl.transientAttack = (uint16_t)std::clamp(info->transient_attack, 0, 1023);
49✔
3360
    file->impl.transientDecay = (uint16_t)std::clamp(info->transient_decay > 0 ? info->transient_decay : 1023, 0, 1023);
49✔
3361
    file->impl.transientStretch = (uint16_t)std::clamp(info->transient_stretch, 0, 100);
49✔
3362
    file->impl.silenceSelected = info->silence_selected != 0;
49✔
3363

3364
    return VL_OK;
49✔
3365
}
3366

3367
VLError vl_set_creator_info(VLFile file, const VLCreatorInfo* info)
6✔
3368
{
3369
    if (!file)
6✔
3370
        return VL_ERROR_INVALID_HANDLE;
1✔
3371

3372
    if (!info)
5✔
3373
        return VL_ERROR_INVALID_ARG;
1✔
3374

3375
    if (!file->impl.slices.empty() || !file->impl.pcm.empty())
4✔
3376
        return VL_ERROR_ALREADY_HAS_DATA;
1✔
3377

3378
    file->impl.creator = *info;
3✔
3379
    file->impl.creator.name[sizeof(file->impl.creator.name) - 1] = '\0';
3✔
3380
    file->impl.creator.copyright[sizeof(file->impl.creator.copyright) - 1] = '\0';
3✔
3381
    file->impl.creator.url[sizeof(file->impl.creator.url) - 1] = '\0';
3✔
3382
    file->impl.creator.email[sizeof(file->impl.creator.email) - 1] = '\0';
3✔
3383
    file->impl.creator.free_text[sizeof(file->impl.creator.free_text) - 1] = '\0';
3✔
3384

3385
    return VL_OK;
3✔
3386
}
3387

3388
int32_t vl_add_slice(VLFile file, int32_t ppq_pos, const float* left, const float* right, int32_t frames)
636✔
3389
{
3390
    if (!file)
636✔
3391
        return (int32_t)VL_ERROR_INVALID_HANDLE;
1✔
3392

3393
    if (ppq_pos < 0)
635✔
3394
        return (int32_t)VL_ERROR_INVALID_ARG;
1✔
3395

3396
    const uint32_t start = calcSampleStartFromPPQ(file->impl, (uint32_t)ppq_pos);
634✔
3397
    return addSliceAtSample(file, start, ppq_pos, left, right, frames);
634✔
3398
}
3399

3400
VLError vl_remove_slice(VLFile file, int32_t index)
4✔
3401
{
3402
    if (!file)
4✔
3403
        return VL_ERROR_INVALID_HANDLE;
1✔
3404

3405
    if (index < 0 || (size_t)index >= file->impl.slices.size())
3✔
3406
        return VL_ERROR_INVALID_SLICE;
2✔
3407

3408
    file->impl.slices.erase(file->impl.slices.begin() + index);
1✔
3409
    file->impl.info.slice_count = (int32_t)file->impl.slices.size();
1✔
3410

3411
    return VL_OK;
1✔
3412
}
3413

3414
VLError vl_save(VLFile file, const char* path)
5✔
3415
{
3416
    if (!file)
5✔
3417
        return VL_ERROR_INVALID_HANDLE;
1✔
3418

3419
    if (!path)
4✔
3420
        return VL_ERROR_INVALID_ARG;
1✔
3421

3422
    size_t size = 0;
3✔
3423
    VLError e = vl_save_to_memory(file, nullptr, &size);
3✔
3424
    if (e != VL_OK)
3✔
3425
        return e;
1✔
3426

3427
    std::vector<uint8_t> buf(size);
2✔
3428
    e = vl_save_to_memory(file, buf.data(), &size);
2✔
3429
    if (e != VL_OK)
2✔
UNCOV
3430
        return e;
×
3431

3432
    std::ofstream out(path, std::ios::binary);
2✔
3433
    if (!out)
2✔
3434
        return VL_ERROR_INVALID_ARG;
1✔
3435

3436
    out.write((const char*)buf.data(), (std::streamsize)size);
1✔
3437
    return out ? VL_OK : VL_ERROR_FILE_CORRUPT;
1✔
3438
}
2✔
3439

3440
VLError vl_save_to_memory(VLFile file, void* buf, size_t* size_out)
135✔
3441
{
3442
    if (!file)
135✔
3443
        return VL_ERROR_INVALID_HANDLE;
1✔
3444

3445
    if (!size_out)
134✔
3446
        return VL_ERROR_INVALID_ARG;
16✔
3447

3448
    if (file->impl.pcm.empty())
118✔
3449
        return VL_ERROR_INVALID_ARG;
2✔
3450

3451
    std::vector<uint8_t> encoded = buildREX2File(file->impl);
116✔
3452

3453
    if (!buf)
116✔
3454
    {
3455
        *size_out = encoded.size();
43✔
3456
        return VL_OK;
43✔
3457
    }
3458

3459
    if (*size_out < encoded.size())
73✔
3460
    {
3461
        *size_out = encoded.size();
30✔
3462
        return VL_ERROR_BUFFER_TOO_SMALL;
30✔
3463
    }
3464

3465
    std::memcpy(buf, encoded.data(), encoded.size());
43✔
3466
    *size_out = encoded.size();
43✔
3467
    return VL_OK;
43✔
3468
}
116✔
3469

3470
/* -----------------------------------------------------------------------
3471
   Utility
3472
   ----------------------------------------------------------------------- */
3473

3474
const char* vl_error_string(VLError err)
17✔
3475
{
3476
    switch (static_cast<int32_t>(err))
17✔
3477
    {
3478
        case static_cast<int32_t>(VL_OK): return "OK";
1✔
3479
        case static_cast<int32_t>(VL_ERROR_INVALID_HANDLE): return "invalid handle";
1✔
3480
        case static_cast<int32_t>(VL_ERROR_INVALID_ARG): return "invalid argument";
1✔
3481
        case static_cast<int32_t>(VL_ERROR_FILE_NOT_FOUND): return "file not found";
1✔
3482
        case static_cast<int32_t>(VL_ERROR_FILE_CORRUPT): return "file corrupt or unsupported format";
1✔
3483
        case static_cast<int32_t>(VL_ERROR_OUT_OF_MEMORY): return "out of memory";
1✔
3484
        case static_cast<int32_t>(VL_ERROR_INVALID_SLICE): return "invalid slice index";
1✔
3485
        case static_cast<int32_t>(VL_ERROR_INVALID_SAMPLE_RATE): return "invalid sample rate";
1✔
3486
        case static_cast<int32_t>(VL_ERROR_BUFFER_TOO_SMALL): return "buffer too small";
1✔
3487
        case static_cast<int32_t>(VL_ERROR_NO_CREATOR_INFO): return "no creator info available";
1✔
3488
        case static_cast<int32_t>(VL_ERROR_NOT_IMPLEMENTED): return "not implemented";
1✔
3489
        case static_cast<int32_t>(VL_ERROR_ALREADY_HAS_DATA): return "already has data";
1✔
3490
        case static_cast<int32_t>(VL_ERROR_FILE_TOO_NEW): return "file too new";
1✔
3491
        case static_cast<int32_t>(VL_ERROR_ZERO_LOOP_LENGTH): return "zero loop length";
1✔
3492
        case static_cast<int32_t>(VL_ERROR_INVALID_SIZE): return "invalid size";
1✔
3493
        case static_cast<int32_t>(VL_ERROR_INVALID_TEMPO): return "invalid tempo";
1✔
3494
        default: return "unknown error";
1✔
3495
    }
3496
}
3497

3498
const char* vl_version_string(void)
1✔
3499
{
3500
    return "0.2.0";
1✔
3501
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc