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

kunitoki / VelociLoops / 25327602779

04 May 2026 03:23PM UTC coverage: 92.709% (-1.3%) from 93.961%
25327602779

push

github

kunitoki
Onset detection with superflux and better saving and roundtrip

307 of 353 new or added lines in 1 file covered. (86.97%)

1971 of 2126 relevant lines covered (92.71%)

2104917.0 hits per line

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

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

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

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

20
namespace
21
{
22

23
inline int32_t clampInt32(int64_t v)
48,355,072✔
24
{
25
    if (v > std::numeric_limits<int32_t>::max())
48,355,072✔
26
        return std::numeric_limits<int32_t>::max();
×
27

28
    if (v < std::numeric_limits<int32_t>::min())
48,355,072✔
29
        return std::numeric_limits<int32_t>::min();
×
30

31
    return static_cast<int32_t>(v);
48,355,072✔
32
}
33

34
inline int32_t addInt32(int32_t a, int32_t b)
19,599,052✔
35
{
36
    return clampInt32(static_cast<int64_t>(a) + static_cast<int64_t>(b));
19,599,052✔
37
}
38

39
inline int32_t subInt32(int32_t a, int32_t b)
28,756,020✔
40
{
41
    return clampInt32(static_cast<int64_t>(a) - static_cast<int64_t>(b));
28,756,020✔
42
}
43

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

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

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

65
        for (size_t i = 0; i < words; ++i)
2,979,116✔
66
        {
67
            const size_t b = i * 4;
2,979,017✔
68
            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,979,017✔
69
        }
70

71
        inputPtr = buf.data();
99✔
72
        endPtr = buf.data() + buf.size();
99✔
73
        currentWord = 0;
99✔
74
        bitsLeft = 0;
99✔
75
    }
99✔
76

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

82
        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;
82✔
83

84
        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];
82✔
85

86
        uint32_t j = 2;
82✔
87
        int32_t rbits = 0;
82✔
88

89
        uint32_t cw = currentWord;
82✔
90
        int32_t bl = bitsLeft;
82✔
91
        const uint32_t* inp = inputPtr;
82✔
92
        bool eof = false;
82✔
93

94
        for (uint32_t f = 0; f < frameCount && !eof; ++f)
9,631,798✔
95
        {
96
            uint32_t minAvg = a0;
9,631,716✔
97
            int minIdx = 0;
9,631,716✔
98

99
            if (a1 < minAvg)
9,631,716✔
100
            {
101
                minAvg = a1;
9,001,650✔
102
                minIdx = 1;
9,001,650✔
103
            }
104

105
            if (a2 < minAvg)
9,631,716✔
106
            {
107
                minAvg = a2;
3,987,142✔
108
                minIdx = 2;
3,987,142✔
109
            }
110

111
            if (a3 < minAvg)
9,631,716✔
112
            {
113
                minAvg = a3;
2,372,154✔
114
                minIdx = 3;
2,372,154✔
115
            }
116

117
            if (a4 < minAvg)
9,631,716✔
118
            {
119
                minAvg = a4;
1,505,939✔
120
                minIdx = 4;
1,505,939✔
121
            }
122

123
            uint32_t step = ((minAvg * 3u) + 36u) >> 7;
9,631,716✔
124
            uint32_t prefixSum = 0;
9,631,716✔
125
            int zerosWin = 7;
9,631,716✔
126

127
            while (true)
128
            {
129
                bool bit = readBit(cw, bl, inp, eof);
17,949,366✔
130

131
                if (eof)
17,949,366✔
132
                    break;
×
133

134
                if (bit)
17,949,366✔
135
                    break;
9,631,716✔
136

137
                if (step > 0 && prefixSum > 0xFFFFFFFFu - step)
8,317,650✔
138
                {
139
                    eof = true;
×
140
                    break;
×
141
                }
142

143
                prefixSum += step;
8,317,650✔
144
                if (--zerosWin == 0)
8,317,650✔
145
                {
146
                    step <<= 2;
49,681✔
147
                    zerosWin = 7;
49,681✔
148
                }
149
            }
8,317,650✔
150

151
            if (eof)
9,631,716✔
152
                break;
×
153

154
            adjustJRbits(step, j, rbits);
9,631,716✔
155

156
            uint32_t rem = (rbits > 0) ? readBits(rbits, cw, bl, inp, eof) : 0;
9,631,716✔
157
            if (eof)
9,631,716✔
158
                break;
×
159

160
            const int64_t thresh = static_cast<int64_t>(j) - static_cast<int64_t>(step);
9,631,716✔
161
            if (static_cast<int64_t>(rem) - thresh >= 0)
9,631,716✔
162
            {
163
                const uint32_t extra = readBits(1, cw, bl, inp, eof);
4,724,030✔
164

165
                if (eof)
4,724,030✔
166
                    break;
×
167

168
                rem = rem * 2u - static_cast<uint32_t>(thresh) + extra;
4,724,030✔
169
            }
170

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

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

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

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

210
        uint32_t j[2] = {2, 2};
17✔
211
        int32_t rbits[2] = {0, 0};
17✔
212

213
        uint32_t cw = currentWord;
17✔
214
        int32_t bl = bitsLeft;
17✔
215
        const uint32_t* inp = inputPtr;
17✔
216
        bool eof = false;
17✔
217

218
        for (uint32_t f = 0; f < frameCount && !eof; ++f)
1,228,543✔
219
        {
220
            int32_t ch2x[2] = {0, 0};
1,228,526✔
221
            for (int c = 0; c < 2 && !eof; ++c)
3,685,578✔
222
            {
223
                uint32_t minAvg = a[c][0];
2,457,052✔
224
                int minIdx = 0;
2,457,052✔
225
                if (a[c][1] < minAvg)
2,457,052✔
226
                {
227
                    minAvg = a[c][1];
1,850,131✔
228
                    minIdx = 1;
1,850,131✔
229
                }
230

231
                if (a[c][2] < minAvg)
2,457,052✔
232
                {
233
                    minAvg = a[c][2];
615,481✔
234
                    minIdx = 2;
615,481✔
235
                }
236

237
                if (a[c][3] < minAvg)
2,457,052✔
238
                {
239
                    minAvg = a[c][3];
139,159✔
240
                    minIdx = 3;
139,159✔
241
                }
242

243
                if (a[c][4] < minAvg)
2,457,052✔
244
                {
245
                    minAvg = a[c][4];
127,175✔
246
                    minIdx = 4;
127,175✔
247
                }
248

249
                uint32_t step = ((minAvg * 3u) + 36u) >> 7;
2,457,052✔
250
                uint32_t prefixSum = 0;
2,457,052✔
251
                int zerosWin = 7;
2,457,052✔
252

253
                while (true)
254
                {
255
                    bool bit = readBit(cw, bl, inp, eof);
4,220,100✔
256

257
                    if (eof)
4,220,100✔
258
                        break;
×
259

260
                    if (bit)
4,220,100✔
261
                        break;
2,457,052✔
262

263
                    if (step > 0 && prefixSum > 0xFFFFFFFFu - step)
1,763,048✔
264
                    {
265
                        eof = true;
×
266
                        break;
×
267
                    }
268

269
                    prefixSum += step;
1,763,048✔
270
                    if (--zerosWin == 0)
1,763,048✔
271
                    {
272
                        step <<= 2;
1,671✔
273
                        zerosWin = 7;
1,671✔
274
                    }
275
                }
1,763,048✔
276

277
                if (eof)
2,457,052✔
278
                    break;
×
279

280
                adjustJRbits(step, j[c], rbits[c]);
2,457,052✔
281

282
                uint32_t rem = (rbits[c] > 0) ? readBits(rbits[c], cw, bl, inp, eof) : 0;
2,457,052✔
283
                if (eof)
2,457,052✔
284
                    break;
×
285

286
                const int64_t thresh = static_cast<int64_t>(j[c]) - static_cast<int64_t>(step);
2,457,052✔
287
                if (static_cast<int64_t>(rem) - thresh >= 0)
2,457,052✔
288
                {
289
                    const uint32_t extra = readBits(1, cw, bl, inp, eof);
1,008,111✔
290

291
                    if (eof)
1,008,111✔
292
                        break;
×
293

294
                    rem = rem * 2u - static_cast<uint32_t>(thresh) + extra;
1,008,111✔
295
                }
296

297
                const uint32_t codeVal = rem + prefixSum;
2,457,052✔
298
                const int32_t signed2x = -(int32_t)(codeVal & 1u) ^ (int32_t)codeVal;
2,457,052✔
299
                ch2x[c] = applyPredictor(minIdx, signed2x, d[c][0], d[c][1], d[c][2], d[c][3], d[c][4]);
2,457,052✔
300
                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]);
2,457,052✔
301
            }
302

303
            if (eof)
1,228,526✔
304
                break;
×
305

306
            out[f * 2 + 0] = clampSample(ch2x[0] >> 1, bitDepth);
1,228,526✔
307
            out[f * 2 + 1] = clampSample(((int64_t)ch2x[0] + (int64_t)ch2x[1]) >> 1, bitDepth);
1,228,526✔
308
        }
309

310
        for (int c = 0; c < 2; ++c)
51✔
311
        {
312
            for (int i = 0; i < 5; ++i)
204✔
313
            {
314
                ch[c].deltas[i] = d[c][i] >> 1;
170✔
315
                ch[c].averages[i] = a[c][i];
170✔
316
            }
317
        }
318
        currentWord = cw;
17✔
319
        bitsLeft = bl;
17✔
320
        inputPtr = inp;
17✔
321
        return !eof;
17✔
322
    }
323

324
private:
325
    static int32_t clampSample(int64_t v, int32_t bitDepth)
12,088,768✔
326
    {
327
        const int32_t maxSample = bitDepth == 24 ? 8388607 : 32767;
12,088,768✔
328
        const int32_t minSample = bitDepth == 24 ? -8388608 : -32768;
12,088,768✔
329

330
        if (v > maxSample)
12,088,768✔
331
            return maxSample;
×
332

333
        if (v < minSample)
12,088,768✔
334
            return minSample;
×
335

336
        return (int32_t)v;
12,088,768✔
337
    }
338

339
    bool readBit(uint32_t& cw, int32_t& bl, const uint32_t*& inp, bool& eof)
22,169,466✔
340
    {
341
        const int32_t blBefore = bl--;
22,169,466✔
342
        if (blBefore - 1 < 0)
22,169,466✔
343
        {
344
            if (inp >= endPtr)
697,257✔
345
            {
346
                eof = true;
×
347
                return false;
×
348
            }
349

350
            cw = *inp++;
697,257✔
351
            bl = 0x1f;
697,257✔
352
        }
353

354
        const bool bit = (int32_t)cw < 0;
22,169,466✔
355
        cw <<= 1;
22,169,466✔
356
        return bit;
22,169,466✔
357
    }
358

359
    uint32_t readBits(int n, uint32_t& cw, int32_t& bl, const uint32_t*& inp, bool& eof)
16,844,257✔
360
    {
361
        if (n <= 0 || n > 31)
16,844,257✔
362
        {
363
            eof = true;
×
364
            return 0;
×
365
        }
366

367
        uint32_t result = cw >> (32 - n);
16,844,257✔
368
        cw <<= n;
16,844,257✔
369

370
        const int32_t blBefore = bl;
16,844,257✔
371
        bl -= n;
16,844,257✔
372

373
        if (blBefore - n < 0)
16,844,257✔
374
        {
375
            if (inp >= endPtr)
2,281,705✔
376
            {
377
                eof = true;
×
378
                return result;
×
379
            }
380

381
            const uint32_t next = *inp++;
2,281,705✔
382
            bl += 32;
2,281,705✔
383
            result |= next >> bl;
2,281,705✔
384
            cw = next << (32 - bl);
2,281,705✔
385
        }
386

387
        return result;
16,844,257✔
388
    }
389

390
    static void adjustJRbits(uint32_t step, uint32_t& j, int32_t& rbits)
12,088,768✔
391
    {
392
        if (step < j)
12,088,768✔
393
        {
394
            for (uint32_t jt = j >> 1; step < jt; jt >>= 1)
12,123,588✔
395
            {
396
                j = jt;
206,307✔
397
                --rbits;
206,307✔
398
            }
399
        }
400
        else
401
        {
402
            while (step >= j)
378,341✔
403
            {
404
                const uint32_t prev = j;
206,854✔
405

406
                j <<= 1;
206,854✔
407
                ++rbits;
206,854✔
408

409
                if (j <= prev)
206,854✔
410
                {
411
                    j = prev;
×
412
                    break;
×
413
                }
414
            }
415
        }
416
    }
12,088,768✔
417

418
    static int32_t applyPredictor(int idx, int32_t s2x, int32_t& d0, int32_t& d1, int32_t& d2, int32_t& d3, int32_t& d4)
12,088,768✔
419
    {
420
        switch (idx)
12,088,768✔
421
        {
422
            case 0:
1,236,986✔
423
            {
424
                const int32_t t0 = subInt32(s2x, d0), t1 = subInt32(t0, d1), t2 = subInt32(t1, d2);
1,236,986✔
425
                d4 = subInt32(t2, d3);
1,236,986✔
426
                d3 = t2;
1,236,986✔
427
                d2 = t1;
1,236,986✔
428
                d1 = t0;
1,236,986✔
429
                d0 = s2x;
1,236,986✔
430
                return s2x;
1,236,986✔
431
            }
432

433
            case 1:
6,249,004✔
434
            {
435
                const int32_t t1 = subInt32(s2x, d1), t2 = subInt32(t1, d2), nd0 = addInt32(d0, s2x);
6,249,004✔
436
                d4 = subInt32(t2, d3);
6,249,004✔
437
                d3 = t2;
6,249,004✔
438
                d2 = t1;
6,249,004✔
439
                d1 = s2x;
6,249,004✔
440
                d0 = nd0;
6,249,004✔
441
                return nd0;
6,249,004✔
442
            }
443

444
            case 2:
2,091,400✔
445
            {
446
                const int32_t nd1 = addInt32(d1, s2x), nd0 = addInt32(d0, nd1), t = subInt32(s2x, d2);
2,091,400✔
447
                d4 = subInt32(t, d3);
2,091,400✔
448
                d3 = t;
2,091,400✔
449
                d2 = s2x;
2,091,400✔
450
                d1 = nd1;
2,091,400✔
451
                d0 = nd0;
2,091,400✔
452
                return nd0;
2,091,400✔
453
            }
454

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

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

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

481
    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)
12,088,768✔
482
    {
483
        auto mag = [](int32_t v) -> uint32_t
60,443,840✔
484
        {
485
            return (uint32_t)(v ^ (v >> 31));
60,443,840✔
486
        };
487

488
        a0 = a0 + mag(d0) - (a0 >> 5);
12,088,768✔
489
        a1 = a1 + mag(d1) - (a1 >> 5);
12,088,768✔
490
        a2 = a2 + mag(d2) - (a2 >> 5);
12,088,768✔
491
        a3 = a3 + mag(d3) - (a3 >> 5);
12,088,768✔
492
        a4 = a4 + mag(d4) - (a4 >> 5);
12,088,768✔
493
    }
12,088,768✔
494
};
495

496
class VLBitWriter
497
{
498
public:
499
    void writeBit(bool bit)
107,197,362✔
500
    {
501
        if (bit)
107,197,362✔
502
            currentWord |= (uint32_t)1u << (31 - bitCount);
55,519,375✔
503
        if (++bitCount == 32)
107,197,362✔
504
            flushWord();
3,349,851✔
505
    }
107,197,362✔
506

507
    void writeBits(uint32_t value, int count)
12,486,074✔
508
    {
509
        for (int i = count - 1; i >= 0; --i)
87,828,837✔
510
            writeBit(((value >> i) & 1u) != 0);
75,342,763✔
511
    }
12,486,074✔
512

513
    std::vector<uint8_t> finish()
126✔
514
    {
515
        if (bitCount > 0)
126✔
516
            flushWord();
120✔
517

518
        return bytes;
126✔
519
    }
520

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

526
    void flushWord()
3,349,971✔
527
    {
528
        bytes.push_back((uint8_t)(currentWord >> 24));
3,349,971✔
529
        bytes.push_back((uint8_t)(currentWord >> 16));
3,349,971✔
530
        bytes.push_back((uint8_t)(currentWord >> 8));
3,349,971✔
531
        bytes.push_back((uint8_t)currentWord);
3,349,971✔
532
        currentWord = 0;
3,349,971✔
533
        bitCount = 0;
3,349,971✔
534
    }
3,349,971✔
535
};
536

537
class VLDWOPCompressor
538
{
539
public:
540
    VLChannelState ch[2];
541

542
    std::vector<uint8_t> compressMono(const int32_t* in, uint32_t frameCount)
109✔
543
    {
544
        reset();
109✔
545

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

552
        for (uint32_t f = 0; f < frameCount; ++f)
11,809,357✔
553
            encodeChannel(in[f] * 2, d, a, j, rbits, bw);
11,809,248✔
554

555
        return bw.finish();
218✔
556
    }
109✔
557

558
    std::vector<uint8_t> compressStereo(const int32_t* in, uint32_t frameCount)
17✔
559
    {
560
        reset();
17✔
561

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

568
        for (uint32_t f = 0; f < frameCount; ++f)
1,206,550✔
569
        {
570
            const int32_t left2x = in[(size_t)f * 2] * 2;
1,206,533✔
571
            const int32_t right2x = in[(size_t)f * 2 + 1] * 2;
1,206,533✔
572
            encodeChannel(left2x, d[0], a[0], j[0], rbits[0], bw);
1,206,533✔
573
            encodeChannel(right2x - left2x, d[1], a[1], j[1], rbits[1], bw);
1,206,533✔
574
        }
575

576
        return bw.finish();
34✔
577
    }
17✔
578

579
private:
580
    void reset()
126✔
581
    {
582
        for (auto& c : ch)
378✔
583
        {
584
            std::fill(c.deltas, c.deltas + 5, 0);
252✔
585
            std::fill(c.averages, c.averages + 5, 2560);
252✔
586
        }
587
    }
126✔
588

589
    static uint32_t toCodeValue(int32_t signed2x)
14,222,314✔
590
    {
591
        if (signed2x >= 0)
14,222,314✔
592
            return (uint32_t)signed2x;
8,053,240✔
593

594
        return (uint32_t)(-signed2x - 1);
6,169,074✔
595
    }
596

597
    static int minAverageIndex(const uint32_t a[5])
14,222,314✔
598
    {
599
        int idx = 0;
14,222,314✔
600

601
        for (int i = 1; i < 5; ++i)
71,111,570✔
602
        {
603
            if (a[i] < a[idx])
56,889,256✔
604
                idx = i;
22,102,877✔
605
        }
606

607
        return idx;
14,222,314✔
608
    }
609

610
    static int32_t predictorResidual(int idx, int32_t sample2x, const int32_t d[5])
14,222,314✔
611
    {
612
        switch (idx)
14,222,314✔
613
        {
614
            case 0: return sample2x;
2,012,618✔
615

616
            case 1: return sample2x - d[0];
6,999,875✔
617

618
            case 2: return sample2x - d[0] - d[1];
2,337,318✔
619

620
            case 3: return sample2x - d[0] - d[1] - d[2];
1,061,409✔
621

622
            case 4: return sample2x - d[0] - d[1] - d[2] - d[3];
1,811,094✔
623

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

628
    static int32_t applyPredictor(int idx, int32_t s2x, int32_t d[5])
14,222,314✔
629
    {
630
        switch (idx)
14,222,314✔
631
        {
632
            case 0:
2,012,618✔
633
            {
634
                const int32_t t0 = s2x - d[0], t1 = t0 - d[1], t2 = t1 - d[2];
2,012,618✔
635
                d[4] = t2 - d[3];
2,012,618✔
636
                d[3] = t2;
2,012,618✔
637
                d[2] = t1;
2,012,618✔
638
                d[1] = t0;
2,012,618✔
639
                d[0] = s2x;
2,012,618✔
640
                return s2x;
2,012,618✔
641
            }
642

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

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

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

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

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

691
    static void updateAverages(uint32_t a[5], const int32_t d[5])
14,222,314✔
692
    {
693
        auto mag = [](int32_t v) -> uint32_t
71,111,570✔
694
        {
695
            return (uint32_t)(v ^ (v >> 31));
71,111,570✔
696
        };
697

698
        for (int i = 0; i < 5; ++i)
85,333,884✔
699
        {
700
            a[i] = a[i] + mag(d[i]) - (a[i] >> 5);
71,111,570✔
701
        }
702
    }
14,222,314✔
703

704
    static void adjustJRbits(uint32_t step, uint32_t& j, int32_t& rbits)
25,562,609✔
705
    {
706
        if (step < j)
25,562,609✔
707
        {
708
            for (uint32_t jt = j >> 1; step < jt; jt >>= 1)
26,006,151✔
709
            {
710
                j = jt;
856,690✔
711
                --rbits;
856,690✔
712
            }
713
        }
714
        else
715
        {
716
            while (step >= j)
942,486✔
717
            {
718
                const uint32_t prev = j;
529,338✔
719

720
                j <<= 1;
529,338✔
721
                ++rbits;
529,338✔
722

723
                if (j <= prev)
529,338✔
724
                {
725
                    j = prev;
×
726
                    break;
×
727
                }
728
            }
729
        }
730
    }
25,562,609✔
731

732
    static bool encodeRemainder(uint32_t raw, uint32_t step, uint32_t j, int32_t rbits, uint32_t& remBits, bool& hasExtra, bool& extraBit)
25,562,609✔
733
    {
734
        if (rbits < 0 || rbits > 31)
25,562,609✔
735
            return false;
×
736

737
        const uint32_t limit = rbits == 31 ? 0x80000000u : (1u << rbits);
25,562,609✔
738
        const int32_t threshSigned = (int32_t)j - (int32_t)step;
25,562,609✔
739
        if (threshSigned < 0)
25,562,609✔
740
            return false;
×
741

742
        const uint32_t thresh = (uint32_t)threshSigned;
25,562,609✔
743

744
        if (raw < thresh)
25,562,609✔
745
        {
746
            if (raw >= limit)
7,930,324✔
747
                return false;
×
748

749
            remBits = raw;
7,930,324✔
750
            hasExtra = false;
7,930,324✔
751
            extraBit = false;
7,930,324✔
752
            return true;
7,930,324✔
753
        }
754

755
        const uint32_t folded = raw + thresh;
17,632,285✔
756
        remBits = folded >> 1;
17,632,285✔
757
        hasExtra = true;
17,632,285✔
758
        extraBit = (folded & 1u) != 0;
17,632,285✔
759
        return remBits >= thresh && remBits < limit;
17,632,285✔
760
    }
761

762
    static void writeCodeValue(uint32_t codeVal, uint32_t baseStep, uint32_t& j, int32_t& rbits, VLBitWriter& bw)
14,222,314✔
763
    {
764
        uint32_t prefixSum = 0;
14,222,314✔
765
        uint32_t step = baseStep;
14,222,314✔
766
        int zerosWin = 7;
14,222,314✔
767

768
        for (uint32_t zeros = 0; zeros < 0x100000; ++zeros)
25,562,609✔
769
        {
770
            if (codeVal >= prefixSum)
25,562,609✔
771
            {
772
                uint32_t trialJ = j;
25,562,609✔
773
                int32_t trialRbits = rbits;
25,562,609✔
774
                adjustJRbits(step, trialJ, trialRbits);
25,562,609✔
775

776
                uint32_t remBits = 0;
25,562,609✔
777
                bool hasExtra = false;
25,562,609✔
778
                bool extraBit = false;
25,562,609✔
779
                if (encodeRemainder(codeVal - prefixSum, step, trialJ, trialRbits, remBits, hasExtra, extraBit))
25,562,609✔
780
                {
781
                    for (uint32_t i = 0; i < zeros; ++i)
25,562,609✔
782
                        bw.writeBit(false);
11,340,295✔
783

784
                    bw.writeBit(true);
14,222,314✔
785

786
                    if (trialRbits > 0)
14,222,314✔
787
                        bw.writeBits(remBits, trialRbits);
12,486,074✔
788

789
                    if (hasExtra)
14,222,314✔
790
                        bw.writeBit(extraBit);
6,291,990✔
791

792
                    j = trialJ;
14,222,314✔
793
                    rbits = trialRbits;
14,222,314✔
794
                    return;
14,222,314✔
795
                }
796
            }
797

798
            if (baseStep == 0)
11,340,295✔
799
                break;
×
800

801
            if (UINT32_MAX - prefixSum < step)
11,340,295✔
802
                break;
×
803

804
            prefixSum += step;
11,340,295✔
805
            if (prefixSum > codeVal && step != 0)
11,340,295✔
806
                break;
×
807

808
            if (--zerosWin == 0)
11,340,295✔
809
            {
810
                step <<= 2;
62,067✔
811
                zerosWin = 7;
62,067✔
812
            }
813
        }
814

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

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

829
class VLIFFWriter
830
{
831
public:
832
    std::vector<uint8_t> data;
833

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

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

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

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

859
        if (data.size() & 1u)
5,267✔
860
            data.push_back(0);
4,500✔
861
    }
5,267✔
862

863
    void put8(uint8_t v)
5,886✔
864
    {
865
        data.push_back(v);
5,886✔
866
    }
5,886✔
867

868
    void put16(uint16_t v)
4,752✔
869
    {
870
        data.push_back((uint8_t)(v >> 8));
4,752✔
871
        data.push_back((uint8_t)v);
4,752✔
872
    }
4,752✔
873

874
    void put32(uint32_t v)
14,070✔
875
    {
876
        data.push_back((uint8_t)(v >> 24));
14,070✔
877
        data.push_back((uint8_t)(v >> 16));
14,070✔
878
        data.push_back((uint8_t)(v >> 8));
14,070✔
879
        data.push_back((uint8_t)v);
14,070✔
880
    }
14,070✔
881

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

888
/* -----------------------------------------------------------------------
889
   REX2 file parser + DWOP decompressor wrapper
890
   ----------------------------------------------------------------------- */
891

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

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

915
int32_t sliceFlags(const VLSliceEntry& s)
1,988✔
916
{
917
    int32_t flags = 0;
1,988✔
918
    if (s.state == kSliceMuted)
1,988✔
919
        flags |= VL_SLICE_FLAG_MUTED;
2✔
920
    else if (s.state == kSliceLocked)
1,986✔
921
        flags |= VL_SLICE_FLAG_LOCKED;
10✔
922
    if (s.selected_flag)
1,988✔
923
        flags |= VL_SLICE_FLAG_SELECTED;
70✔
924
    if (s.marker)
1,988✔
925
        flags |= VL_SLICE_FLAG_MARKER;
×
926
    if (s.synthetic_leading)
1,988✔
927
        flags |= VL_SLICE_FLAG_SYNTHETIC;
9✔
928
    return flags;
1,988✔
929
}
930

931
VLError applySliceFlags(VLSliceEntry& s, int32_t flags, int32_t analysisPoints)
6✔
932
{
933
    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;
934

935
    if (flags & ~kKnownFlags)
6✔
936
        return VL_ERROR_INVALID_ARG;
1✔
937

938
    if ((flags & VL_SLICE_FLAG_MUTED) && (flags & VL_SLICE_FLAG_LOCKED))
5✔
939
        return VL_ERROR_INVALID_ARG;
1✔
940

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

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

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

957
    return VL_OK;
3✔
958
}
959

960
template <typename T> class VLHeapArray
961
{
962
public:
963
    VLHeapArray() = default;
233✔
964

965
    VLHeapArray(const VLHeapArray&) = delete;
966
    VLHeapArray& operator=(const VLHeapArray&) = delete;
967

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

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

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

990
        T* raw = new (std::nothrow) T[n];
109✔
991
        if (!raw)
109✔
992
            return false;
×
993

994
        std::fill_n(raw, n, val);
109✔
995
        ptr_.reset(raw);
109✔
996
        size_ = n;
109✔
997

998
        return true;
109✔
999
    }
1000

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

1006
        if (n == 0)
34✔
1007
        {
1008
            ptr_.reset();
×
1009
            size_ = 0;
×
1010
            return true;
×
1011
        }
1012

1013
        T* raw = new (std::nothrow) T[n];
34✔
1014
        if (!raw)
34✔
1015
            return false;
×
1016

1017
        const std::size_t keep = std::min(size_, n);
34✔
1018
        if (keep)
34✔
1019
            std::memcpy(raw, ptr_.get(), keep * sizeof(T));
6✔
1020

1021
        if (n > size_)
34✔
1022
            std::fill_n(raw + size_, n - size_, val);
34✔
1023

1024
        ptr_.reset(raw);
34✔
1025
        size_ = n;
34✔
1026
        return true;
34✔
1027
    }
1028

1029
    T& operator[](std::size_t i) noexcept
3,653,958✔
1030
    {
1031
        assert(i < size_);
3,653,958✔
1032
        return ptr_[i];
3,653,958✔
1033
    }
1034

1035
    const T& operator[](std::size_t i) const noexcept
7,537,254✔
1036
    {
1037
        assert(i < size_);
7,537,254✔
1038
        return ptr_[i];
7,537,254✔
1039
    }
1040

1041
    T* data() noexcept
126✔
1042
    {
1043
        return ptr_.get();
126✔
1044
    }
1045

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

1051
    std::size_t size() const noexcept
7,538,698✔
1052
    {
1053
        return size_;
7,538,698✔
1054
    }
1055

1056
    bool empty() const noexcept
1,458✔
1057
    {
1058
        return size_ == 0;
1,458✔
1059
    }
1060

1061
    static constexpr std::size_t max_size() noexcept
659✔
1062
    {
1063
        return std::numeric_limits<std::size_t>::max() / sizeof(T);
659✔
1064
    }
1065

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

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

1096
    static constexpr int32_t kREXPPQ = 15360;
1097

1098
    bool loadFromBuffer(const char* buf, size_t size)
204✔
1099
    {
1100
        fileData.assign((const uint8_t*)buf, (const uint8_t*)buf + size);
204✔
1101

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

1128
        std::memset(&creator, 0, sizeof(creator));
204✔
1129

1130
        if (fileData.size() < 12)
204✔
1131
            return fail(VL_ERROR_INVALID_SIZE);
1✔
1132

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

1142
        if (fileData[0] != 'C' || fileData[1] != 'A' || fileData[2] != 'T' || fileData[3] != ' ')
155✔
1143
            return fail(VL_ERROR_FILE_CORRUPT);
18✔
1144

1145
        size_t dwopOffset = 0, dwopSize = 0;
137✔
1146
        bool hasDWOP = false;
137✔
1147

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

1152
        finalizeSlices();
111✔
1153

1154
        if (!headerValid)
111✔
1155
            return false;
×
1156

1157
        if (!hasDWOP || dwopSize == 0)
111✔
1158
            return fail(VL_ERROR_FILE_CORRUPT);
4✔
1159

1160
        if (dwopOffset + dwopSize > fileData.size())
107✔
1161
            return fail(VL_ERROR_INVALID_SIZE);
×
1162

1163
        if (totalFrames == 0)
107✔
1164
            return fail(VL_ERROR_INVALID_SIZE);
8✔
1165

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

1171
        const size_t pcmElements = (size_t)totalFrames * (size_t)info.channels;
99✔
1172

1173
        if (!pcm.assign(pcmElements, 0))
99✔
1174
            return fail(VL_ERROR_OUT_OF_MEMORY);
×
1175

1176
        VLDWOPDecompressor dec;
99✔
1177
        dec.init(&fileData[dwopOffset], dwopSize);
99✔
1178

1179
        uint32_t done = 0;
99✔
1180
        bool ok = true;
99✔
1181
        while (done < totalFrames && ok)
198✔
1182
        {
1183
            const uint32_t chunk = std::min<uint32_t>(0x100000, totalFrames - done);
99✔
1184

1185
            if (info.channels == 1)
99✔
1186
                ok = dec.decompressMono(chunk, &pcm[done], info.bit_depth);
82✔
1187
            else
1188
                ok = dec.decompressStereo(chunk, &pcm[(size_t)done * 2], info.bit_depth);
17✔
1189

1190
            done += chunk;
99✔
1191
        }
1192

1193
        if (!ok)
99✔
1194
            return fail(VL_ERROR_FILE_CORRUPT);
×
1195

1196
        finalizeRenderedLengths();
99✔
1197
        loadedFromFile = true;
99✔
1198
        return true;
99✔
1199
    }
99✔
1200

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

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

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

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

1226
        if (expon == 0 || mant == 0)
48✔
1227
            return 0;
4✔
1228

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

1235
        return (int32_t)std::lround((double)value);
44✔
1236
    }
1237

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

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

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

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

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

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

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

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

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

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

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

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

1328
        if (parsed.empty())
9✔
1329
            return false;
2✔
1330

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

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

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

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

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

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

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

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

1386
        if (parsed.empty())
3✔
1387
            return false;
×
1388

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

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

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

1409
        if (samplesPerPpq <= 0.0)
3✔
1410
            return false;
×
1411

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

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

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

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

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

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

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

1525
        if (!sawLegacyApp)
48✔
1526
            return fail(VL_ERROR_FILE_CORRUPT);
4✔
1527

1528
        if (!sawLegacyTempo)
44✔
1529
            return fail(VL_ERROR_INVALID_TEMPO);
8✔
1530

1531
        if (!legacyLoopLengthSet)
36✔
1532
            return fail(VL_ERROR_ZERO_LOOP_LENGTH);
6✔
1533

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

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

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

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

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

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

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

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

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

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

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

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

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

1657
            const uint32_t sz = be32(&fileData[off]);
5,282✔
1658
            off += 4;
5,282✔
1659

1660
            if (off + sz > fileData.size())
5,282✔
1661
            {
1662
                fail(VL_ERROR_INVALID_SIZE);
8✔
1663
                break;
8✔
1664
            }
1665

1666
            if (std::strcmp(id, "HEAD") == 0)
5,274✔
1667
                parseHEAD(&fileData[off], sz);
137✔
1668

1669
            else if (std::strcmp(id, "CREI") == 0)
5,137✔
1670
                parseCREI(&fileData[off], sz);
9✔
1671

1672
            else if (std::strcmp(id, "SINF") == 0)
5,128✔
1673
                parseSINF(&fileData[off], sz);
135✔
1674

1675
            else if (std::strcmp(id, "GLOB") == 0)
4,993✔
1676
                parseGLOB(&fileData[off], sz);
137✔
1677

1678
            else if (std::strcmp(id, "TRSH") == 0)
4,856✔
1679
                parseTRSH(&fileData[off], sz);
103✔
1680

1681
            else if (std::strcmp(id, "RECY") == 0)
4,753✔
1682
                parseRECY(&fileData[off], sz);
103✔
1683

1684
            else if (std::strcmp(id, "SLCE") == 0)
4,650✔
1685
                parseSLCE(&fileData[off], sz);
4,102✔
1686

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

1694
            else if (std::strcmp(id, "CAT ") == 0 && sz >= 4)
421✔
1695
            {
1696
                parseIFF(off + 4, off + sz, dwopOffset, dwopSize, hasDWOP);
206✔
1697
            }
1698

1699
            off += sz;
5,274✔
1700
            if (off & 1)
5,274✔
1701
                ++off;
4,591✔
1702
        }
1703
    }
343✔
1704

1705
    void parseSINF(const uint8_t* d, uint32_t sz)
135✔
1706
    {
1707
        if (sz < 18)
135✔
1708
            return;
4✔
1709

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

1720
        switch (bd)
131✔
1721
        {
1722
            case 1: info.bit_depth = 8; break;
1✔
1723

1724
            case 3: info.bit_depth = 16; break;
121✔
1725

1726
            case 5: info.bit_depth = 24; break;
7✔
1727

1728
            case 7: info.bit_depth = 32; break;
1✔
1729

1730
            default: info.bit_depth = 16; break;
1✔
1731
        }
1732

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

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

1744
        if (info.original_tempo == 0)
131✔
1745
            info.original_tempo = info.tempo;
×
1746

1747
        if (info.channels != 1 && info.channels != 2)
131✔
1748
            info.channels = 1;
×
1749
    }
1750

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

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

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

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

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

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

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

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

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

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

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

1840
    void parseTRSH(const uint8_t* d, uint32_t sz)
103✔
1841
    {
1842
        if (sz < 7)
103✔
1843
            return;
×
1844

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

1855
    void parseRECY(const uint8_t* d, uint32_t sz)
103✔
1856
    {
1857
        if (sz < 12)
103✔
1858
            return;
×
1859

1860
        const int32_t t = (int32_t)be32(d + 8);
103✔
1861
        if (t > 0)
103✔
1862
            info.original_tempo = t;
100✔
1863
    }
1864

1865
    void parseSLCE(const uint8_t* d, uint32_t sz)
4,102✔
1866
    {
1867
        static constexpr size_t kMaxSlices = 1024;
1868

1869
        if (slices.size() >= kMaxSlices)
4,102✔
1870
            return;
×
1871

1872
        if (sz < 10)
4,102✔
1873
            return;
×
1874

1875
        VLSliceEntry s;
4,102✔
1876
        s.sample_start = be32(d);
4,102✔
1877
        s.sample_length = be32(d + 4);
4,102✔
1878
        s.points = be16(d + 8);
4,102✔
1879
        s.marker = s.sample_length <= 1;
4,102✔
1880

1881
        const uint8_t flags = sz > 10 ? d[10] : 0;
4,102✔
1882
        s.selected_flag = (flags & 0x04) ? 1 : 0;
4,102✔
1883

1884
        if (flags & 0x02)
4,102✔
1885
            s.state = kSliceLocked;
9✔
1886
        else if (flags & 0x01)
4,093✔
1887
            s.state = kSliceMuted;
1✔
1888
        else
1889
            s.state = kSliceNormal;
4,092✔
1890

1891
        slices.push_back(s);
4,102✔
1892
        info.slice_count = (int32_t)slices.size();
4,102✔
1893
    }
1894

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

1902
    bool isVisibleSliceBoundary(const VLSliceEntry& s) const
4,037✔
1903
    {
1904
        if (s.sample_length > 1)
4,037✔
1905
            return true;
3,267✔
1906

1907
        if (s.selected_flag || s.state != kSliceNormal)
770✔
1908
            return true;
×
1909

1910
        return s.points > rex2FilterPoints(analysisSensitivity);
770✔
1911
    }
1912

1913
    uint32_t defaultSliceEnd(uint32_t start) const
3,286✔
1914
    {
1915
        if (loopEnd > loopStart && start < loopEnd)
3,286✔
1916
            return loopEnd;
3,282✔
1917

1918
        return totalFrames;
4✔
1919
    }
1920

1921
    uint32_t gateLengthFrames() const
118✔
1922
    {
1923
        if (gateSensitivity == 0)
118✔
1924
            return 0;
66✔
1925

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

1930
        return std::max(1u, ((frames + 64u) / 128u) * 128u);
52✔
1931
    }
1932

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

1938
        std::sort(slices.begin(), slices.end(),
118✔
1939
                  [](const VLSliceEntry& a, const VLSliceEntry& b)
27,487✔
1940
                  {
1941
                      return a.sample_start < b.sample_start;
27,487✔
1942
                  });
1943

1944
        std::vector<VLSliceEntry> out;
118✔
1945
        for (auto s : slices)
4,266✔
1946
        {
1947
            if (loopEnd > loopStart && s.sample_start < totalFrames && s.sample_start >= loopEnd)
4,148✔
1948
                continue;
111✔
1949

1950
            if (!isVisibleSliceBoundary(s))
4,037✔
1951
                continue;
751✔
1952

1953
            const uint32_t rel = (s.sample_start > loopStart) ? (s.sample_start - loopStart) : 0;
3,286✔
1954

1955
            s.ppq_pos = (uint32_t)(((uint64_t)rel * info.ppq_length + denom / 2) / denom);
3,286✔
1956
            s.marker = false;
3,286✔
1957

1958
            out.push_back(s);
3,286✔
1959
        }
1960

1961
        for (size_t i = 0; i < out.size(); ++i)
3,404✔
1962
        {
1963
            const uint32_t start = out[i].sample_start;
3,286✔
1964
            uint32_t next = defaultSliceEnd(start);
3,286✔
1965
            for (size_t j = i + 1; j < out.size(); ++j)
3,287✔
1966
            {
1967
                if (out[j].sample_start > start)
3,189✔
1968
                {
1969
                    next = out[j].sample_start;
3,188✔
1970
                    break;
3,188✔
1971
                }
1972
            }
1973

1974
            const uint32_t derived = next > start ? next - start : 1u;
3,286✔
1975
            if (gateSensitivity == 0 || out[i].sample_length <= 1)
3,286✔
1976
            {
1977
                out[i].sample_length = derived;
1,894✔
1978
                if (gateSensitivity != 0 && gatedFrames > 0)
1,894✔
1979
                    out[i].sample_length = std::min(out[i].sample_length, gatedFrames);
×
1980
            }
1981
            else if (derived > 1)
1,392✔
1982
            {
1983
                out[i].sample_length = std::min(out[i].sample_length, derived);
1,391✔
1984
            }
1985

1986
            out[i].sample_length = std::max<uint32_t>(1u, out[i].sample_length);
3,286✔
1987
        }
1988

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

2000
            out.insert(out.begin(), leading);
12✔
2001
        }
2002

2003
        slices.swap(out);
118✔
2004
        info.slice_count = (int32_t)slices.size();
118✔
2005
    }
118✔
2006

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

2013
        uint32_t frames = std::min(s.sample_length, totalFrames - start);
15,047✔
2014
        if (loopEnd > loopStart && start < loopEnd)
15,047✔
2015
            frames = std::min(frames, loopEnd - start);
14,388✔
2016

2017
        return start + frames;
15,047✔
2018
    }
2019

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

2028
    SegmentLoop findSegmentLoop(uint32_t start, uint32_t end) const
7,857✔
2029
    {
2030
        SegmentLoop r{start, end};
7,857✔
2031

2032
        const uint32_t sr = info.sample_rate ? (uint32_t)info.sample_rate : 44100u;
7,857✔
2033
        const uint32_t srch = std::max(1u, (400u * sr) / 44100u);
7,857✔
2034
        const uint32_t mhl = std::max(1u, (20000u * sr) / 44100u);
7,857✔
2035

2036
        if (end <= start || end - start < srch * 3u)
7,857✔
2037
            return r;
4,064✔
2038

2039
        const uint32_t ch = std::max(1, info.channels);
3,793✔
2040
        auto leftAbs = [&](uint32_t f) -> int
2,801,038✔
2041
        {
2042
            const size_t i = (size_t)f * ch;
2,801,038✔
2043
            return i < pcm.size() ? std::abs((int)pcm[i]) : 0;
2,801,038✔
2044
        };
3,793✔
2045

2046
        uint32_t loopEnd = end - srch;
3,793✔
2047
        int peak = -1;
3,793✔
2048
        for (uint32_t i = 0, f = end - srch; i < srch && f > start; ++i, --f)
1,513,193✔
2049
        {
2050
            const int p = leftAbs(f);
1,509,400✔
2051
            if (p > peak)
1,509,400✔
2052
            {
2053
                peak = p;
72,975✔
2054
                loopEnd = f;
72,975✔
2055
            }
2056
        }
2057

2058
        uint32_t hl = std::min((loopEnd - start) / 2u, mhl);
3,793✔
2059
        uint32_t ls = loopEnd - hl;
3,793✔
2060
        uint32_t loopStart = ls;
3,793✔
2061
        int lspeak = -1;
3,793✔
2062
        for (uint32_t i = 0, f = ls; i < srch && f >= start; ++i)
1,295,431✔
2063
        {
2064
            const int p = leftAbs(f);
1,291,638✔
2065
            if (p > lspeak)
1,291,638✔
2066
            {
2067
                lspeak = p;
56,448✔
2068
                loopStart = f;
56,448✔
2069
            }
2070

2071
            if (f == 0)
1,291,638✔
2072
                break;
×
2073

2074
            --f;
1,291,638✔
2075
        }
2076

2077
        r.start = std::clamp(loopStart, start, end - 1u);
3,793✔
2078
        r.end = std::clamp(loopEnd, r.start + 1u, end);
3,793✔
2079

2080
        if (lspeak > 0 && peak > 0)
3,793✔
2081
            r.volumeCompensation = std::min(10.0f, (float)peak / (float)lspeak);
3,645✔
2082

2083
        return r;
3,793✔
2084
    }
2085

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

2097
    uint32_t calcRenderedLength(const VLSliceEntry& s) const
7,198✔
2098
    {
2099
        const uint32_t start = s.sample_start;
7,198✔
2100
        const uint32_t end = sourceEndForSlice(s);
7,198✔
2101

2102
        if (end <= start)
7,198✔
2103
            return 1u;
4✔
2104

2105
        const uint32_t segLen = end - start;
7,194✔
2106
        if (!transientEnabled || transientStretch == 0)
7,194✔
2107
            return segLen;
3,448✔
2108

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

2113
        return std::max(1u, segLen + stretchT);
3,746✔
2114
    }
2115

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

2123
    void cacheSliceLoop(VLSliceEntry& s) const
659✔
2124
    {
2125
        cacheRenderLoop(s);
659✔
2126
    }
659✔
2127

2128
    void finalizeRenderedLengths()
235✔
2129
    {
2130
        for (auto& s : slices)
7,433✔
2131
            cacheSliceRender(s);
7,198✔
2132
    }
235✔
2133
};
2134

2135
int32_t storageBitDepth(int32_t bitDepth)
7,425,309✔
2136
{
2137
    return bitDepth == 24 ? 24 : 16;
7,425,309✔
2138
}
2139

2140
int32_t floatToPCM(float s, int32_t bitDepth)
2,688,940✔
2141
{
2142
    if (s > 1.0f)
2,688,940✔
2143
        s = 1.0f;
64✔
2144

2145
    if (s < -1.0f)
2,688,940✔
2146
        s = -1.0f;
64✔
2147

2148
    if (storageBitDepth(bitDepth) == 24)
2,688,940✔
2149
    {
2150
        const float scaled = s >= 0.0f ? s * 8388607.0f : s * 8388608.0f;
113,479✔
2151
        return (int32_t)std::lround(scaled);
113,479✔
2152
    }
2153

2154
    const float scaled = s >= 0.0f ? s * 32767.0f : s * 32768.0f;
2,575,461✔
2155
    return (int32_t)std::lround(scaled);
2,575,461✔
2156
}
2157

2158
float pcmToFloat(int32_t sample, int32_t bitDepth)
4,736,216✔
2159
{
2160
    return storageBitDepth(bitDepth) == 24 ? (float)sample / 8388608.0f : (float)sample / 32768.0f;
4,736,216✔
2161
}
2162

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

2169
        case 16: return 3;
120✔
2170

2171
        case 24: return 5;
6✔
2172

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

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

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

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

2188
    w.put32((uint32_t)n);
55✔
2189

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

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

2198
    if (!payload.empty())
126✔
2199
        w.putBytes(payload.data(), payload.size());
126✔
2200

2201
    w.endChunk(c);
126✔
2202
}
126✔
2203

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

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

2218
void normaliseInfoForSave(VLFileImpl& impl)
126✔
2219
{
2220
    impl.info.channels = std::clamp(impl.info.channels, 1, 2);
126✔
2221

2222
    if (impl.info.sample_rate <= 0)
126✔
2223
        impl.info.sample_rate = 44100;
×
2224

2225
    if (impl.info.tempo <= 0)
126✔
2226
        impl.info.tempo = 120000;
×
2227

2228
    if (impl.info.original_tempo <= 0)
126✔
2229
        impl.info.original_tempo = impl.info.tempo;
2✔
2230

2231
    if (impl.info.time_sig_num <= 0)
126✔
2232
        impl.info.time_sig_num = 4;
2✔
2233

2234
    if (impl.info.time_sig_den <= 0)
126✔
2235
        impl.info.time_sig_den = 4;
2✔
2236

2237
    impl.info.bit_depth = storageBitDepth(impl.info.bit_depth);
126✔
2238
    impl.info.slice_count = (int32_t)impl.slices.size();
126✔
2239

2240
    impl.totalFrames = (uint32_t)(impl.pcm.size() / (size_t)impl.info.channels);
126✔
2241
    impl.info.total_frames = (int32_t)impl.totalFrames;
126✔
2242

2243
    if (impl.info.loop_start < 0 || (uint32_t)impl.info.loop_start >= impl.totalFrames)
126✔
2244
        impl.info.loop_start = 0;
1✔
2245

2246
    if (impl.info.loop_end <= impl.info.loop_start || (uint32_t)impl.info.loop_end > impl.totalFrames)
126✔
2247
        impl.info.loop_end = (int32_t)impl.totalFrames;
1✔
2248

2249
    impl.loopStart = (uint32_t)std::max(0, impl.info.loop_start);
126✔
2250
    impl.loopEnd = (uint32_t)std::max(impl.info.loop_start, impl.info.loop_end);
126✔
2251

2252
    if (!impl.loadedFromFile && impl.analysisSensitivity == 0)
126✔
2253
        impl.analysisSensitivity = 99;
26✔
2254

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

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

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

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

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

2280
std::vector<uint8_t> buildREX2File(VLFileImpl& impl)
126✔
2281
{
2282
    normaliseInfoForSave(impl);
126✔
2283

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

2288
    VLIFFWriter w;
126✔
2289
    const size_t root = w.beginCat("REX2");
126✔
2290

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

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

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

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

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

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

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

2361
        w.endChunk(devl);
126✔
2362
    }
2363

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

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

2402
    writeSimpleChunk(w, "SDAT", sdat);
126✔
2403
    w.endChunk(root);
126✔
2404
    return w.data;
252✔
2405
}
126✔
2406

2407
} // anonymous namespace
2408

2409
/* -----------------------------------------------------------------------
2410
   VLFile_s  —  the opaque handle
2411
   ----------------------------------------------------------------------- */
2412

2413
struct VLFile_s
2414
{
2415
    VLFileImpl impl;
2416
    int32_t outputSampleRate = 44100;
2417
};
2418

2419
/* -----------------------------------------------------------------------
2420
   Utilities
2421
   ----------------------------------------------------------------------- */
2422

2423
namespace
2424
{
2425

2426
constexpr double kVLTwoPi = 6.283185307179586476925286766559;
2427

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

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

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

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

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

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

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

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

2483
    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)
2✔
NEW
2484
        return VL_ERROR_INVALID_ARG;
×
2485

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

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

2492
    return VL_OK;
2✔
2493
}
2494

2495
void fftRadix2(std::vector<std::complex<float>>& a)
800✔
2496
{
2497
    const size_t n = a.size();
800✔
2498
    for (size_t i = 1, j = 0; i < n; ++i)
1,638,400✔
2499
    {
2500
        size_t bit = n >> 1;
1,637,600✔
2501
        for (; j & bit; bit >>= 1)
3,266,400✔
2502
            j ^= bit;
1,628,800✔
2503
        j ^= bit;
1,637,600✔
2504
        if (i < j)
1,637,600✔
2505
            std::swap(a[i], a[j]);
793,600✔
2506
    }
2507

2508
    for (size_t len = 2; len <= n; len <<= 1)
9,600✔
2509
    {
2510
        const float angle = (float)(-kVLTwoPi / (double)len);
8,800✔
2511
        const std::complex<float> wlen(std::cos(angle), std::sin(angle));
8,800✔
2512
        for (size_t i = 0; i < n; i += len)
1,646,400✔
2513
        {
2514
            std::complex<float> w(1.0f, 0.0f);
1,637,600✔
2515
            for (size_t j = 0; j < len / 2; ++j)
10,648,800✔
2516
            {
2517
                const std::complex<float> u = a[i + j];
9,011,200✔
2518
                const std::complex<float> v = a[i + j + len / 2] * w;
9,011,200✔
2519
                a[i + j] = u + v;
9,011,200✔
2520
                a[i + j + len / 2] = u - v;
9,011,200✔
2521
                w *= wlen;
9,011,200✔
2522
            }
2523
        }
2524
    }
2525
}
800✔
2526

2527
std::vector<float> makeHannWindow(int32_t frameSize)
2✔
2528
{
2529
    std::vector<float> window((size_t)frameSize, 1.0f);
2✔
2530
    if (frameSize <= 1)
2✔
NEW
2531
        return window;
×
2532

2533
    for (int32_t i = 0; i < frameSize; ++i)
4,098✔
2534
        window[(size_t)i] = (float)(0.5 - 0.5 * std::cos(kVLTwoPi * (double)i / (double)(frameSize - 1)));
4,096✔
2535

2536
    return window;
2✔
2537
}
2538

2539
std::vector<float> superfluxFrequencies(int32_t bands, float fmin, float fmax)
2✔
2540
{
2541
    std::vector<float> frequencies;
2✔
2542
    const double factor = std::pow(2.0, 1.0 / (double)bands);
2✔
2543

2544
    double freq = 440.0;
2✔
2545
    frequencies.push_back((float)freq);
2✔
2546
    while (freq <= (double)fmax)
256✔
2547
    {
2548
        freq *= factor;
254✔
2549
        frequencies.push_back((float)freq);
254✔
2550
    }
2551

2552
    freq = 440.0;
2✔
2553
    while (freq >= (double)fmin)
188✔
2554
    {
2555
        freq /= factor;
186✔
2556
        frequencies.push_back((float)freq);
186✔
2557
    }
2558

2559
    std::sort(frequencies.begin(), frequencies.end());
2✔
2560
    return frequencies;
2✔
NEW
2561
}
×
2562

2563
bool buildSuperFluxFilterbank(int32_t numFftBins,
2✔
2564
                              int32_t sampleRate,
2565
                              const VLSuperFluxOptions& options,
2566
                              std::vector<float>& filterbank,
2567
                              int32_t& numBands)
2568
{
2569
    const float fmax = std::min(options.fmax, (float)sampleRate * 0.5f);
2✔
2570
    std::vector<float> frequencies = superfluxFrequencies(options.filter_bands, options.fmin, fmax);
2✔
2571
    const double factor = ((double)sampleRate * 0.5) / (double)numFftBins;
2✔
2572

2573
    std::vector<int32_t> bins;
2✔
2574
    bins.reserve(frequencies.size());
2✔
2575
    for (float frequency : frequencies)
444✔
2576
    {
2577
        const int32_t bin = (int32_t)std::lround((double)frequency / factor);
442✔
2578
        if (bin >= 0 && bin < numFftBins)
442✔
2579
            bins.push_back(bin);
442✔
2580
    }
2581

2582
    std::sort(bins.begin(), bins.end());
2✔
2583
    bins.erase(std::unique(bins.begin(), bins.end()), bins.end());
2✔
2584
    if (bins.size() < 5)
2✔
NEW
2585
        return false;
×
2586

2587
    numBands = (int32_t)bins.size() - 2;
2✔
2588
    if (numBands < 3)
2✔
NEW
2589
        return false;
×
2590

2591
    filterbank.assign((size_t)numFftBins * (size_t)numBands, 0.0f);
2✔
2592
    for (int32_t band = 0; band < numBands; ++band)
284✔
2593
    {
2594
        const int32_t start = bins[(size_t)band];
282✔
2595
        const int32_t mid = bins[(size_t)band + 1u];
282✔
2596
        const int32_t stop = bins[(size_t)band + 2u];
282✔
2597
        if (mid <= start || stop <= mid)
282✔
NEW
2598
            continue;
×
2599

2600
        const float height = options.filter_equal ? 2.0f / (float)(stop - start) : 1.0f;
282✔
2601
        for (int32_t bin = start; bin < mid; ++bin)
1,836✔
2602
        {
2603
            const float t = (float)(bin - start) / (float)(mid - start);
1,554✔
2604
            filterbank[(size_t)bin * (size_t)numBands + (size_t)band] = t * height;
1,554✔
2605
        }
2606
        for (int32_t bin = mid; bin < stop; ++bin)
1,878✔
2607
        {
2608
            const float t = (float)(bin - mid) / (float)(stop - mid);
1,596✔
2609
            filterbank[(size_t)bin * (size_t)numBands + (size_t)band] = (1.0f - t) * height;
1,596✔
2610
        }
2611
    }
2612

2613
    return true;
2✔
2614
}
2✔
2615

2616
int32_t deriveSuperFluxDiffFrames(const std::vector<float>& window, double hopSize, float ratio)
2✔
2617
{
2618
    size_t sample = 0;
2✔
2619
    while (sample < window.size() && window[sample] <= ratio)
1,026✔
2620
        ++sample;
1,024✔
2621

2622
    const double diffSamples = (double)window.size() * 0.5 - (double)sample;
2✔
2623
    return std::max(1, (int32_t)std::lround(diffSamples / hopSize));
2✔
2624
}
2625

2626
bool computeSuperFluxActivations(const float* left,
2✔
2627
                                 const float* right,
2628
                                 int32_t channels,
2629
                                 int32_t frames,
2630
                                 int32_t sampleRate,
2631
                                 const VLSuperFluxOptions& options,
2632
                                 std::vector<float>& activations)
2633
{
2634
    const int32_t frameSize = options.frame_size;
2✔
2635
    const int32_t numFftBins = frameSize / 2;
2✔
2636
    const double hopSize = (double)sampleRate / (double)options.fps;
2✔
2637
    const int32_t numFrames = std::max(1, (int32_t)std::ceil((double)frames / hopSize));
2✔
2638

2639
    std::vector<float> window = makeHannWindow(frameSize);
2✔
2640
    std::vector<float> filterbank;
2✔
2641
    int32_t numBands = 0;
2✔
2642
    if (!buildSuperFluxFilterbank(numFftBins, sampleRate, options, filterbank, numBands))
2✔
NEW
2643
        return false;
×
2644

2645
    std::vector<float> spec((size_t)numFrames * (size_t)numBands, 0.0f);
2✔
2646
    std::vector<std::complex<float>> fftBuffer((size_t)frameSize);
4✔
2647
    std::vector<float> magnitudes((size_t)numFftBins);
2✔
2648

2649
    for (int32_t frame = 0; frame < numFrames; ++frame)
802✔
2650
    {
2651
        const int32_t seek = options.online ? (int32_t)((double)(frame + 1) * hopSize - (double)frameSize)
800✔
2652
                                            : (int32_t)((double)frame * hopSize - (double)frameSize * 0.5);
800✔
2653

2654
        for (int32_t i = 0; i < frameSize; ++i)
1,639,200✔
2655
        {
2656
            const int32_t src = seek + i;
1,638,400✔
2657
            float sample = 0.0f;
1,638,400✔
2658
            if (src >= 0 && src < frames)
1,638,400✔
2659
            {
2660
                const float l = std::isfinite(left[src]) ? left[src] : 0.0f;
1,628,792✔
2661
                if (channels == 2)
1,628,792✔
2662
                {
2663
                    const float r = std::isfinite(right[src]) ? right[src] : 0.0f;
814,396✔
2664
                    sample = (l + r) * 0.5f;
814,396✔
2665
                }
2666
                else
2667
                {
2668
                    sample = l;
814,396✔
2669
                }
2670
            }
2671
            fftBuffer[(size_t)i] = std::complex<float>(sample * window[(size_t)i], 0.0f);
1,638,400✔
2672
        }
2673

2674
        fftRadix2(fftBuffer);
800✔
2675

2676
        for (int32_t bin = 0; bin < numFftBins; ++bin)
820,000✔
2677
            magnitudes[(size_t)bin] = std::abs(fftBuffer[(size_t)bin]);
819,200✔
2678

2679
        for (int32_t band = 0; band < numBands; ++band)
113,600✔
2680
        {
2681
            double value = 0.0;
112,800✔
2682
            for (int32_t bin = 0; bin < numFftBins; ++bin)
115,620,000✔
2683
                value += (double)magnitudes[(size_t)bin] * (double)filterbank[(size_t)bin * (size_t)numBands + (size_t)band];
115,507,200✔
2684

2685
            const float logged = std::log10(options.log_mul * (float)value + options.log_add);
112,800✔
2686
            spec[(size_t)frame * (size_t)numBands + (size_t)band] = logged;
112,800✔
2687
        }
2688
    }
2689

2690
    const int32_t diffFrames =
2691
        options.diff_frames > 0 ? options.diff_frames : deriveSuperFluxDiffFrames(window, hopSize, options.ratio);
2✔
2692

2693
    activations.assign((size_t)numFrames, 0.0f);
2✔
2694
    for (int32_t frame = diffFrames; frame < numFrames; ++frame)
798✔
2695
    {
2696
        float sum = 0.0f;
796✔
2697
        const int32_t prevFrame = frame - diffFrames;
796✔
2698
        for (int32_t band = 0; band < numBands; ++band)
113,032✔
2699
        {
2700
            int32_t first = band - options.max_bins / 2;
112,236✔
2701
            int32_t last = first + options.max_bins - 1;
112,236✔
2702
            if (first < 0)
112,236✔
2703
            {
2704
                last -= first;
796✔
2705
                first = 0;
796✔
2706
            }
2707
            if (last >= numBands)
112,236✔
2708
            {
2709
                first = std::max(0, first - (last - numBands + 1));
796✔
2710
                last = numBands - 1;
796✔
2711
            }
2712
            float prevMax = spec[(size_t)prevFrame * (size_t)numBands + (size_t)band];
112,236✔
2713
            for (int32_t k = first; k <= last; ++k)
448,944✔
2714
                prevMax = std::max(prevMax, spec[(size_t)prevFrame * (size_t)numBands + (size_t)k]);
336,708✔
2715

2716
            const float diff = spec[(size_t)frame * (size_t)numBands + (size_t)band] - prevMax;
112,236✔
2717
            if (diff > 0.0f)
112,236✔
2718
                sum += diff;
4,323✔
2719
        }
2720
        activations[(size_t)frame] = sum;
796✔
2721
    }
2722

2723
    return true;
2✔
2724
}
2✔
2725

2726
std::vector<double> pickSuperFluxOnsets(const std::vector<float>& activations, int32_t fps, const VLSuperFluxOptions& options)
2✔
2727
{
2728
    const int32_t count = (int32_t)activations.size();
2✔
2729
    const int32_t preAvg = std::max(0, (int32_t)std::lround((double)fps * (double)options.pre_avg));
2✔
2730
    const int32_t preMax = std::max(0, (int32_t)std::lround((double)fps * (double)options.pre_max));
2✔
2731
    const int32_t postAvg = options.online ? 0 : std::max(0, (int32_t)std::lround((double)fps * (double)options.post_avg));
2✔
2732
    const int32_t postMax = options.online ? 0 : std::max(0, (int32_t)std::lround((double)fps * (double)options.post_max));
2✔
2733
    const double combineSeconds = (double)options.combine_ms / 1000.0;
2✔
2734
    const double delaySeconds = (double)options.delay_ms / 1000.0;
2✔
2735

2736
    std::vector<double> detections;
2✔
2737
    double lastDetection = -std::numeric_limits<double>::infinity();
2✔
2738
    for (int32_t frame = 0; frame < count; ++frame)
802✔
2739
    {
2740
        const int32_t maxStart = std::max(0, frame - preMax);
800✔
2741
        const int32_t maxStop = std::min(count - 1, frame + postMax);
800✔
2742
        float movMax = 0.0f;
800✔
2743
        for (int32_t i = maxStart; i <= maxStop; ++i)
7,952✔
2744
            movMax = std::max(movMax, activations[(size_t)i]);
7,152✔
2745

2746
        const int32_t avgStart = std::max(0, frame - preAvg);
800✔
2747
        const int32_t avgStop = std::min(count - 1, frame + postAvg);
800✔
2748
        double avg = 0.0;
800✔
2749
        for (int32_t i = avgStart; i <= avgStop; ++i)
24,670✔
2750
            avg += activations[(size_t)i];
23,870✔
2751
        avg /= (double)(avgStop - avgStart + 1);
800✔
2752

2753
        const float value = activations[(size_t)frame];
800✔
2754
        if (value <= 0.0f || value != movMax || (double)value < avg + (double)options.threshold)
800✔
2755
            continue;
794✔
2756

2757
        const double time = (double)frame / (double)fps + delaySeconds;
6✔
2758
        if (detections.empty() || time - lastDetection > combineSeconds)
6✔
2759
        {
2760
            detections.push_back(time);
6✔
2761
            lastDetection = time;
6✔
2762
        }
2763
    }
2764

2765
    return detections;
2✔
NEW
2766
}
×
2767

2768
std::vector<uint32_t> superfluxSliceBoundaries(const std::vector<double>& detections,
2✔
2769
                                               int32_t frames,
2770
                                               int32_t sampleRate,
2771
                                               const VLSuperFluxOptions& options)
2772
{
2773
    const uint32_t combineFrames =
2774
        (uint32_t)std::max(1, (int32_t)std::lround((double)options.combine_ms / 1000.0 * (double)sampleRate));
2✔
2775
    const uint32_t minSliceFrames =
2776
        (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))));
2✔
2777

2778
    std::vector<uint32_t> boundaries;
2✔
2779
    boundaries.push_back(0);
2✔
2780

2781
    for (double detection : detections)
8✔
2782
    {
2783
        const int64_t rounded = (int64_t)std::llround(detection * (double)sampleRate);
6✔
2784
        if (rounded <= 0 || rounded >= frames)
6✔
NEW
2785
            continue;
×
2786

2787
        const uint32_t sample = (uint32_t)rounded;
6✔
2788
        if (sample - boundaries.back() >= minSliceFrames)
6✔
2789
            boundaries.push_back(sample);
6✔
2790
    }
2791

2792
    if ((uint32_t)frames - boundaries.back() < minSliceFrames && boundaries.size() > 1)
2✔
NEW
2793
        boundaries.pop_back();
×
2794

2795
    boundaries.push_back((uint32_t)frames);
2✔
2796
    return boundaries;
4✔
NEW
2797
}
×
2798

2799
int32_t addSliceAtSample(VLFile file, uint32_t sample_start, int32_t ppq_pos, const float* left, const float* right, int32_t frames)
662✔
2800
{
2801
    if (!file)
662✔
2802
        return (int32_t)VL_ERROR_INVALID_HANDLE;
×
2803

2804
    if (!left || frames <= 0 || ppq_pos < 0)
662✔
2805
        return (int32_t)VL_ERROR_INVALID_ARG;
2✔
2806

2807
    const int32_t channels = std::clamp(file->impl.info.channels, 1, 2);
660✔
2808
    if (channels == 2 && !right)
660✔
2809
        return (int32_t)VL_ERROR_INVALID_ARG;
1✔
2810

2811
    const uint32_t end = sample_start + (uint32_t)frames;
659✔
2812
    const uint32_t declaredFrames = file->impl.info.total_frames > 0 ? (uint32_t)file->impl.info.total_frames : 0u;
659✔
2813
    const uint32_t requiredFrames = std::max(end, declaredFrames);
659✔
2814
    const size_t required = (size_t)requiredFrames * (size_t)channels;
659✔
2815

2816
    if (required > file->impl.pcm.max_size())
659✔
2817
        return (int32_t)VL_ERROR_OUT_OF_MEMORY; // LCOV_EXCL_LINE max_size is not reachable in tests.
2818

2819
    if (file->impl.pcm.size() < required)
659✔
2820
    {
2821
        if (!file->impl.pcm.resize(required, 0))
34✔
2822
            return (int32_t)VL_ERROR_OUT_OF_MEMORY;
×
2823
    }
2824

2825
    for (int32_t f = 0; f < frames; ++f)
2,340,203✔
2826
    {
2827
        const size_t dst = ((size_t)sample_start + (size_t)f) * (size_t)channels;
2,339,544✔
2828
        file->impl.pcm[dst] = floatToPCM(left[f], file->impl.info.bit_depth);
2,339,544✔
2829
        if (channels == 2)
2,339,544✔
2830
            file->impl.pcm[dst + 1] = floatToPCM(right[f], file->impl.info.bit_depth);
349,396✔
2831
    }
2832

2833
    VLSliceEntry s;
659✔
2834
    s.ppq_pos = (uint32_t)ppq_pos;
659✔
2835
    s.sample_start = sample_start;
659✔
2836
    s.sample_length = (uint32_t)frames;
659✔
2837
    s.rendered_length = (uint32_t)frames;
659✔
2838
    s.points = 0x7fff;
659✔
2839
    s.selected_flag = 0;
659✔
2840
    s.state = kSliceNormal;
659✔
2841

2842
    file->impl.totalFrames = (uint32_t)(file->impl.pcm.size() / (size_t)channels);
659✔
2843
    file->impl.info.total_frames = (int32_t)file->impl.totalFrames;
659✔
2844
    file->impl.cacheSliceLoop(s);
659✔
2845

2846
    if (file->impl.slices.size() == file->impl.slices.max_size())
659✔
2847
        return (int32_t)VL_ERROR_OUT_OF_MEMORY; // LCOV_EXCL_LINE max_size is not reachable in tests.
2848
    file->impl.slices.push_back(s);
659✔
2849

2850
    file->impl.info.slice_count = (int32_t)file->impl.slices.size();
659✔
2851
    if (file->impl.info.loop_end <= file->impl.info.loop_start)
659✔
2852
        file->impl.info.loop_end = file->impl.info.total_frames;
9✔
2853

2854
    return (int32_t)file->impl.slices.size() - 1;
659✔
2855
}
2856

2857
} // anonymous namespace
2858

2859
/* -----------------------------------------------------------------------
2860
   Open / close
2861
   ----------------------------------------------------------------------- */
2862

2863
VLFile vl_open(const char* path, VLError* err)
101✔
2864
{
2865
    auto set = [&](VLError e)
2✔
2866
    {
2867
        if (err)
2✔
2868
            *err = e;
2✔
2869
    };
103✔
2870

2871
    if (!path)
101✔
2872
    {
2873
        set(VL_ERROR_INVALID_ARG);
1✔
2874
        return nullptr;
1✔
2875
    }
2876

2877
    std::ifstream f(path, std::ios::binary | std::ios::ate);
100✔
2878
    if (!f)
100✔
2879
    {
2880
        set(VL_ERROR_FILE_NOT_FOUND);
1✔
2881
        return nullptr;
1✔
2882
    }
2883

2884
    const std::streamsize sz = f.tellg();
99✔
2885
    f.seekg(0);
99✔
2886

2887
    std::vector<char> buf((size_t)sz);
99✔
2888
    if (!f.read(buf.data(), sz))
99✔
2889
    {
2890
        set(VL_ERROR_FILE_CORRUPT);
×
2891
        return nullptr;
×
2892
    }
2893

2894
    return vl_open_from_memory(buf.data(), (size_t)sz, err);
99✔
2895
}
100✔
2896

2897
VLFile vl_open_from_memory(const void* data, size_t size, VLError* err)
205✔
2898
{
2899
    auto set = [&](VLError e)
205✔
2900
    {
2901
        if (err)
205✔
2902
            *err = e;
205✔
2903
    };
410✔
2904

2905
    if (!data || size == 0)
205✔
2906
    {
2907
        set(VL_ERROR_INVALID_ARG);
1✔
2908
        return nullptr;
1✔
2909
    }
2910

2911
    VLFile_s* h = new (std::nothrow) VLFile_s();
204✔
2912
    if (!h)
204✔
2913
    {
2914
        set(VL_ERROR_OUT_OF_MEMORY);
×
2915
        return nullptr;
×
2916
    }
2917

2918
    if (!h->impl.loadFromBuffer((const char*)data, size))
204✔
2919
    {
2920
        const VLError loadError = h->impl.loadError != VL_OK ? h->impl.loadError : VL_ERROR_FILE_CORRUPT;
95✔
2921
        delete h;
95✔
2922
        set(loadError);
95✔
2923
        return nullptr;
95✔
2924
    }
2925

2926
    h->outputSampleRate = h->impl.info.sample_rate ? h->impl.info.sample_rate : 44100;
109✔
2927
    set(VL_OK);
109✔
2928
    return h;
109✔
2929
}
2930

2931
VLFile vl_create_new(int32_t channels, int32_t sample_rate, int32_t tempo, VLError* err)
45✔
2932
{
2933
    auto set = [&](VLError e)
45✔
2934
    {
2935
        if (err)
45✔
2936
            *err = e;
29✔
2937
    };
90✔
2938

2939
    if (channels != 1 && channels != 2)
45✔
2940
    {
2941
        set(VL_ERROR_INVALID_ARG);
4✔
2942
        return nullptr;
4✔
2943
    }
2944

2945
    if (sample_rate < 8000 || sample_rate > 192000)
41✔
2946
    {
2947
        set(VL_ERROR_INVALID_SAMPLE_RATE);
8✔
2948
        return nullptr;
8✔
2949
    }
2950

2951
    if (tempo <= 0)
33✔
2952
    {
2953
        set(VL_ERROR_INVALID_TEMPO);
4✔
2954
        return nullptr;
4✔
2955
    }
2956

2957
    VLFile_s* h = new (std::nothrow) VLFile_s();
29✔
2958
    if (!h)
29✔
2959
    {
2960
        set(VL_ERROR_OUT_OF_MEMORY);
×
2961
        return nullptr;
×
2962
    }
2963

2964
    h->outputSampleRate = sample_rate;
29✔
2965

2966
    h->impl.info.channels = channels;
29✔
2967
    h->impl.info.sample_rate = sample_rate;
29✔
2968
    h->impl.info.slice_count = 0;
29✔
2969
    h->impl.info.tempo = tempo;
29✔
2970
    h->impl.info.original_tempo = tempo;
29✔
2971
    h->impl.info.ppq_length = VLFileImpl::kREXPPQ * 4;
29✔
2972
    h->impl.info.time_sig_num = 4;
29✔
2973
    h->impl.info.time_sig_den = 4;
29✔
2974
    h->impl.info.bit_depth = 16;
29✔
2975
    h->impl.info.total_frames = 0;
29✔
2976
    h->impl.info.loop_start = 0;
29✔
2977
    h->impl.info.loop_end = 0;
29✔
2978
    h->impl.info.processing_gain = 1000;
29✔
2979
    h->impl.info.transient_enabled = 1;
29✔
2980
    h->impl.info.transient_attack = 0;
29✔
2981
    h->impl.info.transient_decay = 1023;
29✔
2982
    h->impl.info.transient_stretch = 0;
29✔
2983
    h->impl.info.silence_selected = 0;
29✔
2984
    h->impl.processingGain = 1000;
29✔
2985
    h->impl.transientEnabled = true;
29✔
2986
    h->impl.transientAttack = 0;
29✔
2987
    h->impl.transientDecay = 1023;
29✔
2988
    h->impl.transientStretch = 0;
29✔
2989
    h->impl.silenceSelected = false;
29✔
2990

2991
    set(VL_OK);
29✔
2992
    return h;
29✔
2993
}
2994

2995
void vl_superflux_default_options(VLSuperFluxOptions* out)
3✔
2996
{
2997
    if (out)
3✔
2998
        *out = superfluxDefaults();
2✔
2999
}
3✔
3000

3001
VLFile vl_create_from_superflux(int32_t channels,
7✔
3002
                                int32_t sample_rate,
3003
                                int32_t tempo,
3004
                                const float* left,
3005
                                const float* right,
3006
                                int32_t frames,
3007
                                const VLSuperFluxOptions* options,
3008
                                VLError* err)
3009
{
3010
    auto set = [&](VLError e)
7✔
3011
    {
3012
        if (err)
7✔
3013
            *err = e;
7✔
3014
    };
14✔
3015

3016
    if (channels != 1 && channels != 2)
7✔
3017
    {
3018
        set(VL_ERROR_INVALID_ARG);
1✔
3019
        return nullptr;
1✔
3020
    }
3021

3022
    if (sample_rate < 8000 || sample_rate > 192000)
6✔
3023
    {
3024
        set(VL_ERROR_INVALID_SAMPLE_RATE);
1✔
3025
        return nullptr;
1✔
3026
    }
3027

3028
    if (tempo <= 0)
5✔
3029
    {
3030
        set(VL_ERROR_INVALID_TEMPO);
1✔
3031
        return nullptr;
1✔
3032
    }
3033

3034
    if (!left || frames <= 0 || (channels == 2 && !right))
4✔
3035
    {
3036
        set(VL_ERROR_INVALID_ARG);
2✔
3037
        return nullptr;
2✔
3038
    }
3039

3040
    VLSuperFluxOptions opts = options ? *options : superfluxDefaults();
2✔
3041
    VLError optionError = validateSuperFluxOptions(opts, sample_rate);
2✔
3042
    if (optionError != VL_OK)
2✔
3043
    {
NEW
3044
        set(optionError);
×
NEW
3045
        return nullptr;
×
3046
    }
3047

3048
    try
3049
    {
3050
        std::vector<float> activations;
2✔
3051
        if (!computeSuperFluxActivations(left, right, channels, frames, sample_rate, opts, activations))
2✔
3052
        {
NEW
3053
            set(VL_ERROR_INVALID_ARG);
×
NEW
3054
            return nullptr;
×
3055
        }
3056

3057
        const std::vector<double> detections = pickSuperFluxOnsets(activations, opts.fps, opts);
2✔
3058
        const std::vector<uint32_t> boundaries = superfluxSliceBoundaries(detections, frames, sample_rate, opts);
2✔
3059

3060
        VLError createError = VL_OK;
2✔
3061
        VLFile file = vl_create_new(channels, sample_rate, tempo, &createError);
2✔
3062
        if (!file)
2✔
3063
        {
NEW
3064
            set(createError);
×
NEW
3065
            return nullptr;
×
3066
        }
3067

3068
        VLFileInfo info = {};
2✔
3069
        if (vl_get_info(file, &info) != VL_OK)
2✔
3070
        {
NEW
3071
            vl_close(file);
×
3072
            set(VL_ERROR_INVALID_HANDLE); // LCOV_EXCL_LINE freshly-created handles are valid.
NEW
3073
            return nullptr;
×
3074
        }
3075

3076
        const double beats = ((double)frames * (double)tempo) / (60000.0 * (double)sample_rate);
2✔
3077
        info.ppq_length = std::max(1, (int32_t)std::lround(beats * (double)VLFileImpl::kREXPPQ));
2✔
3078
        const int32_t totalBeats = std::max(1, (int32_t)std::lround(beats));
2✔
3079
        const int64_t musicalFrames = ((int64_t)totalBeats * (int64_t)sample_rate * 60000LL) / (int64_t)tempo;
2✔
3080
        info.total_frames = frames;
2✔
3081
        info.loop_start = 0;
2✔
3082
        info.loop_end = (int32_t)std::min(musicalFrames, (int64_t)frames);
2✔
3083
        info.transient_enabled = 0;
2✔
3084
        info.transient_stretch = 0;
2✔
3085
        info.transient_attack = 0;
2✔
3086
        info.transient_decay = 1023;
2✔
3087
        VLError infoError = vl_set_info(file, &info);
2✔
3088
        if (infoError != VL_OK)
2✔
3089
        {
NEW
3090
            vl_close(file);
×
NEW
3091
            set(infoError);
×
NEW
3092
            return nullptr;
×
3093
        }
3094

3095
        int32_t previousPpq = -1;
2✔
3096
        for (size_t i = 0; i + 1 < boundaries.size(); ++i)
10✔
3097
        {
3098
            const uint32_t start = boundaries[i];
8✔
3099
            const uint32_t stop = boundaries[i + 1u];
8✔
3100
            if (stop <= start)
8✔
NEW
3101
                continue;
×
3102

3103
            int32_t ppq = (int32_t)(((uint64_t)start * (uint64_t)info.ppq_length + (uint32_t)frames / 2u) / (uint32_t)frames);
8✔
3104
            if (ppq <= previousPpq)
8✔
NEW
3105
                ppq = previousPpq + 1;
×
3106
            ppq = std::min(ppq, std::max(0, info.ppq_length - 1));
8✔
3107
            previousPpq = ppq;
8✔
3108

3109
            const int32_t sliceIndex =
3110
                addSliceAtSample(file, start, ppq, left + start, channels == 2 ? right + start : nullptr, (int32_t)(stop - start));
8✔
3111
            if (sliceIndex < 0)
8✔
3112
            {
NEW
3113
                const VLError sliceError = (VLError)sliceIndex;
×
NEW
3114
                vl_close(file);
×
NEW
3115
                set(sliceError);
×
NEW
3116
                return nullptr;
×
3117
            }
3118
        }
3119

3120
        if (file->impl.slices.empty())
2✔
3121
        {
NEW
3122
            vl_close(file);
×
3123
            set(VL_ERROR_INVALID_ARG); // LCOV_EXCL_LINE boundaries always create at least one slice for frames > 0.
NEW
3124
            return nullptr;
×
3125
        }
3126

3127
        set(VL_OK);
2✔
3128
        return file;
2✔
3129
    }
2✔
NEW
3130
    catch (const std::bad_alloc&)
×
3131
    {
NEW
3132
        set(VL_ERROR_OUT_OF_MEMORY);
×
NEW
3133
        return nullptr;
×
NEW
3134
    }
×
NEW
3135
    catch (...)
×
3136
    {
NEW
3137
        set(VL_ERROR_OUT_OF_MEMORY);
×
NEW
3138
        return nullptr;
×
NEW
3139
    }
×
3140
}
3141

3142
void vl_close(VLFile file)
198✔
3143
{
3144
    delete file;
198✔
3145
}
198✔
3146

3147
/* -----------------------------------------------------------------------
3148
   Read: metadata
3149
   ----------------------------------------------------------------------- */
3150

3151
VLError vl_get_info(VLFile file, VLFileInfo* out)
123✔
3152
{
3153
    if (!file)
123✔
3154
        return VL_ERROR_INVALID_HANDLE;
1✔
3155

3156
    if (!out)
122✔
3157
        return VL_ERROR_INVALID_ARG;
18✔
3158

3159
    *out = file->impl.info;
104✔
3160
    return VL_OK;
104✔
3161
}
3162

3163
VLError vl_get_creator_info(VLFile file, VLCreatorInfo* out)
39✔
3164
{
3165
    if (!file)
39✔
3166
        return VL_ERROR_INVALID_HANDLE;
1✔
3167

3168
    if (!out)
38✔
3169
        return VL_ERROR_INVALID_ARG;
18✔
3170

3171
    const VLCreatorInfo& src = file->impl.creator;
20✔
3172
    if (!src.name[0] && !src.copyright[0] && !src.url[0] && !src.email[0] && !src.free_text[0])
20✔
3173
        return VL_ERROR_NO_CREATOR_INFO;
18✔
3174

3175
    *out = src;
2✔
3176
    return VL_OK;
2✔
3177
}
3178

3179
/* -----------------------------------------------------------------------
3180
   Read: slice enumeration
3181
   ----------------------------------------------------------------------- */
3182

3183
VLError vl_get_slice_info(VLFile file, int32_t index, VLSliceInfo* out)
2,061✔
3184
{
3185
    if (!file)
2,061✔
3186
        return VL_ERROR_INVALID_HANDLE;
1✔
3187

3188
    if (!out)
2,060✔
3189
        return VL_ERROR_INVALID_ARG;
36✔
3190

3191
    if (index < 0 || (size_t)index >= file->impl.slices.size())
2,024✔
3192
        return VL_ERROR_INVALID_SLICE;
36✔
3193

3194
    const VLSliceEntry& s = file->impl.slices[(size_t)index];
1,988✔
3195
    out->ppq_pos = (int32_t)s.ppq_pos;
1,988✔
3196
    out->sample_length = (int32_t)s.sample_length;
1,988✔
3197
    out->sample_start = (int32_t)s.sample_start;
1,988✔
3198
    out->analysis_points = (int32_t)s.points;
1,988✔
3199
    out->flags = sliceFlags(s);
1,988✔
3200
    return VL_OK;
1,988✔
3201
}
3202

3203
VLError vl_set_slice_info(VLFile file, int32_t index, int32_t flags, int32_t analysis_points)
43✔
3204
{
3205
    if (!file)
43✔
3206
        return VL_ERROR_INVALID_HANDLE;
1✔
3207

3208
    if (index < 0 || (size_t)index >= file->impl.slices.size())
42✔
3209
        return VL_ERROR_INVALID_SLICE;
36✔
3210

3211
    VLError err = applySliceFlags(file->impl.slices[(size_t)index], flags, analysis_points);
6✔
3212
    if (err != VL_OK)
6✔
3213
        return err;
3✔
3214

3215
    return VL_OK;
3✔
3216
}
3217

3218
/* -----------------------------------------------------------------------
3219
   Read: sample extraction
3220
   ----------------------------------------------------------------------- */
3221

3222
VLError vl_set_output_sample_rate(VLFile file, int32_t rate)
55✔
3223
{
3224
    if (!file)
55✔
3225
        return VL_ERROR_INVALID_HANDLE;
1✔
3226

3227
    if (rate < 8000 || rate > 192000)
54✔
3228
        return VL_ERROR_INVALID_SAMPLE_RATE;
18✔
3229

3230
    if (rate != file->impl.info.sample_rate)
36✔
3231
        return VL_ERROR_NOT_IMPLEMENTED;
18✔
3232

3233
    file->outputSampleRate = rate;
18✔
3234
    return VL_OK;
18✔
3235
}
3236

3237
int32_t vl_get_slice_frame_count(VLFile file, int32_t index)
1,375✔
3238
{
3239
    if (!file)
1,375✔
3240
        return (int32_t)VL_ERROR_INVALID_HANDLE;
1✔
3241

3242
    if (index < 0 || (size_t)index >= file->impl.slices.size())
1,374✔
3243
        return (int32_t)VL_ERROR_INVALID_SLICE;
38✔
3244

3245
    return (int32_t)file->impl.slices[(size_t)index].rendered_length;
1,336✔
3246
}
3247

3248
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,396✔
3249
{
3250
    if (!file)
1,396✔
3251
        return VL_ERROR_INVALID_HANDLE;
1✔
3252

3253
    if (!left)
1,395✔
3254
        return VL_ERROR_INVALID_ARG;
16✔
3255

3256
    if (frame_offset < 0)
1,379✔
3257
        return VL_ERROR_INVALID_ARG;
16✔
3258

3259
    if (index < 0 || (size_t)index >= file->impl.slices.size())
1,363✔
3260
        return VL_ERROR_INVALID_SLICE;
32✔
3261

3262
    const VLSliceEntry& s = file->impl.slices[(size_t)index];
1,331✔
3263
    const int32_t renderedFrames = (int32_t)s.rendered_length;
1,331✔
3264
    if (frame_offset > renderedFrames)
1,331✔
3265
        return VL_ERROR_INVALID_ARG;
16✔
3266

3267
    const int32_t needed = renderedFrames - frame_offset;
1,315✔
3268
    if (capacity < needed)
1,315✔
3269
        return VL_ERROR_BUFFER_TOO_SMALL;
17✔
3270

3271
    const auto& pcm = file->impl.pcm;
1,298✔
3272
    if (pcm.empty())
1,298✔
3273
        return VL_ERROR_FILE_CORRUPT;
×
3274

3275
    const uint32_t requestedFrames = (uint32_t)renderedFrames;
1,298✔
3276
    uint32_t sourceStart = s.sample_start;
1,298✔
3277
    if (sourceStart >= file->impl.totalFrames)
1,298✔
3278
    {
3279
        std::fill(left, left + needed, 0.f);
1✔
3280

3281
        if (right)
1✔
3282
            std::fill(right, right + needed, 0.f);
1✔
3283

3284
        if (frames_out)
1✔
3285
            *frames_out = needed;
1✔
3286

3287
        return VL_OK;
1✔
3288
    }
3289

3290
    uint32_t sourceFrames = s.sample_length;
1,297✔
3291
    if (file->impl.loopEnd > file->impl.loopStart && sourceStart < file->impl.loopEnd)
1,297✔
3292
        sourceFrames = std::min<uint32_t>(sourceFrames, file->impl.loopEnd - sourceStart);
1,295✔
3293
    sourceFrames = std::min<uint32_t>(sourceFrames, file->impl.totalFrames - sourceStart);
1,297✔
3294

3295
    const uint32_t sourceEnd = sourceStart + sourceFrames;
1,297✔
3296
    const VLFileImpl::SegmentLoop segmentLoop{
3297
        s.render_loop_start,
1,297✔
3298
        s.render_loop_end,
1,297✔
3299
        s.render_loop_volume_compensation,
1,297✔
3300
    };
1,297✔
3301
    const int32_t ch = std::max(1, file->impl.info.channels);
1,297✔
3302
    const float samplerGain = (float)file->impl.processingGain * 0.000833333354f;
1,297✔
3303

3304
    uint32_t samplePos = std::min(sourceStart + 2u, sourceEnd);
1,297✔
3305
    int loopPhase = 0; // 0: forward source, 1: forward loop, 2: backward loop
1,297✔
3306
    bool stretchPhase = false;
1,297✔
3307
    float stretchEnv = 1.0f;
1,297✔
3308
    const uint32_t stretchFrameCount =
3309
        std::max<uint32_t>(1u, sourceEnd - segmentLoop.end + (requestedFrames > sourceFrames ? requestedFrames - sourceFrames : 0u));
1,297✔
3310
    const float stretchEnvDec = 1.0f / (float)stretchFrameCount;
1,297✔
3311
    float loopLevelComp = 1.0f;
1,297✔
3312
    const float loopLevelCompInc =
1,297✔
3313
        (segmentLoop.end > segmentLoop.start) ? (1.0f - segmentLoop.volumeCompensation) / (float)(segmentLoop.end - segmentLoop.start) : 0.0f;
1,297✔
3314

3315
    for (uint32_t f = 0; f < requestedFrames; ++f)
4,131,721✔
3316
    {
3317
        const size_t src = (size_t)samplePos * (size_t)ch;
4,130,424✔
3318
        float l = 0.f;
4,130,424✔
3319
        float r = 0.f;
4,130,424✔
3320

3321
        if (src < pcm.size())
4,130,424✔
3322
        {
3323
            const float level = samplerGain * stretchEnv * loopLevelComp;
4,130,424✔
3324
            l = pcmToFloat(pcm[src], file->impl.info.bit_depth) * level;
4,130,424✔
3325
            r = (ch >= 2 && src + 1 < pcm.size()) ? pcmToFloat(pcm[src + 1], file->impl.info.bit_depth) * level : l;
4,130,424✔
3326
        }
3327

3328
        if ((int32_t)f >= frame_offset)
4,130,424✔
3329
        {
3330
            const size_t dst = (size_t)((int32_t)f - frame_offset);
4,116,380✔
3331
            left[dst] = l;
4,116,380✔
3332
            if (right)
4,116,380✔
3333
                right[dst] = r;
605,792✔
3334
        }
3335

3336
        if (stretchPhase)
4,130,424✔
3337
        {
3338
            stretchEnv = std::max(0.0f, stretchEnv - stretchEnvDec);
617,343✔
3339
            if (loopPhase == 1)
617,343✔
3340
                loopLevelComp += loopLevelCompInc;
71,880✔
3341
            else if (loopPhase == 2)
545,463✔
3342
                loopLevelComp -= loopLevelCompInc;
545,463✔
3343
        }
3344

3345
        if (loopPhase <= 1)
4,130,424✔
3346
        {
3347
            ++samplePos;
3,584,961✔
3348
            if (samplePos >= segmentLoop.end)
3,584,961✔
3349
            {
3350
                stretchPhase = true;
1,299✔
3351
                loopPhase = 2;
1,299✔
3352
                if (samplePos > 0)
1,299✔
3353
                    --samplePos;
1,299✔
3354
                if (samplePos <= segmentLoop.start)
1,299✔
3355
                    loopPhase = 1;
×
3356
            }
3357
        }
3358
        else
3359
        {
3360
            if (samplePos > 0)
545,463✔
3361
                --samplePos;
545,463✔
3362
            if (samplePos <= segmentLoop.start)
545,463✔
3363
                loopPhase = 1;
43✔
3364
        }
3365
    }
3366

3367
    if (frames_out)
1,297✔
3368
        *frames_out = needed;
1,297✔
3369

3370
    return VL_OK;
1,297✔
3371
}
3372

3373
/* -----------------------------------------------------------------------
3374
   Write: assembly from audio slices
3375
   ----------------------------------------------------------------------- */
3376

3377
VLError vl_set_info(VLFile file, const VLFileInfo* info)
32✔
3378
{
3379
    if (!file)
32✔
3380
        return VL_ERROR_INVALID_HANDLE;
1✔
3381

3382
    if (!info)
31✔
3383
        return VL_ERROR_INVALID_ARG;
1✔
3384

3385
    if (!file->impl.slices.empty() || !file->impl.pcm.empty())
30✔
3386
        return VL_ERROR_ALREADY_HAS_DATA;
1✔
3387

3388
    if (info->channels != 1 && info->channels != 2)
29✔
3389
        return VL_ERROR_INVALID_ARG;
×
3390

3391
    if (info->sample_rate < 8000 || info->sample_rate > 192000)
29✔
3392
        return VL_ERROR_INVALID_SAMPLE_RATE;
1✔
3393

3394
    if (info->tempo <= 0)
28✔
3395
        return VL_ERROR_INVALID_TEMPO;
1✔
3396

3397
    file->impl.info = *info;
27✔
3398
    file->impl.info.channels = info->channels;
27✔
3399
    file->impl.info.sample_rate = info->sample_rate;
27✔
3400
    file->impl.info.bit_depth = storageBitDepth(info->bit_depth);
27✔
3401
    file->impl.processingGain = (uint16_t)std::clamp(info->processing_gain > 0 ? info->processing_gain : 1000, 0, 1000);
27✔
3402
    file->impl.transientEnabled = info->transient_enabled != 0;
27✔
3403
    file->impl.transientAttack = (uint16_t)std::clamp(info->transient_attack, 0, 1023);
27✔
3404
    file->impl.transientDecay = (uint16_t)std::clamp(info->transient_decay > 0 ? info->transient_decay : 1023, 0, 1023);
27✔
3405
    file->impl.transientStretch = (uint16_t)std::clamp(info->transient_stretch, 0, 100);
27✔
3406
    file->impl.silenceSelected = info->silence_selected != 0;
27✔
3407
    file->outputSampleRate = info->sample_rate;
27✔
3408

3409
    return VL_OK;
27✔
3410
}
3411

3412
VLError vl_set_creator_info(VLFile file, const VLCreatorInfo* info)
6✔
3413
{
3414
    if (!file)
6✔
3415
        return VL_ERROR_INVALID_HANDLE;
1✔
3416

3417
    if (!info)
5✔
3418
        return VL_ERROR_INVALID_ARG;
1✔
3419

3420
    if (!file->impl.slices.empty() || !file->impl.pcm.empty())
4✔
3421
        return VL_ERROR_ALREADY_HAS_DATA;
1✔
3422

3423
    file->impl.creator = *info;
3✔
3424
    file->impl.creator.name[sizeof(file->impl.creator.name) - 1] = '\0';
3✔
3425
    file->impl.creator.copyright[sizeof(file->impl.creator.copyright) - 1] = '\0';
3✔
3426
    file->impl.creator.url[sizeof(file->impl.creator.url) - 1] = '\0';
3✔
3427
    file->impl.creator.email[sizeof(file->impl.creator.email) - 1] = '\0';
3✔
3428
    file->impl.creator.free_text[sizeof(file->impl.creator.free_text) - 1] = '\0';
3✔
3429

3430
    return VL_OK;
3✔
3431
}
3432

3433
int32_t vl_add_slice(VLFile file, int32_t ppq_pos, const float* left, const float* right, int32_t frames)
656✔
3434
{
3435
    if (!file)
656✔
3436
        return (int32_t)VL_ERROR_INVALID_HANDLE;
1✔
3437

3438
    if (ppq_pos < 0)
655✔
3439
        return (int32_t)VL_ERROR_INVALID_ARG;
1✔
3440

3441
    const uint32_t start = calcSampleStartFromPPQ(file->impl, (uint32_t)ppq_pos);
654✔
3442
    return addSliceAtSample(file, start, ppq_pos, left, right, frames);
654✔
3443
}
3444

3445
VLError vl_remove_slice(VLFile file, int32_t index)
4✔
3446
{
3447
    if (!file)
4✔
3448
        return VL_ERROR_INVALID_HANDLE;
1✔
3449

3450
    if (index < 0 || (size_t)index >= file->impl.slices.size())
3✔
3451
        return VL_ERROR_INVALID_SLICE;
2✔
3452

3453
    file->impl.slices.erase(file->impl.slices.begin() + index);
1✔
3454
    file->impl.info.slice_count = (int32_t)file->impl.slices.size();
1✔
3455

3456
    return VL_OK;
1✔
3457
}
3458

3459
VLError vl_save(VLFile file, const char* path)
5✔
3460
{
3461
    if (!file)
5✔
3462
        return VL_ERROR_INVALID_HANDLE;
1✔
3463

3464
    if (!path)
4✔
3465
        return VL_ERROR_INVALID_ARG;
1✔
3466

3467
    size_t size = 0;
3✔
3468
    VLError e = vl_save_to_memory(file, nullptr, &size);
3✔
3469
    if (e != VL_OK)
3✔
3470
        return e;
1✔
3471

3472
    std::vector<uint8_t> buf(size);
2✔
3473
    e = vl_save_to_memory(file, buf.data(), &size);
2✔
3474
    if (e != VL_OK)
2✔
3475
        return e;
×
3476

3477
    std::ofstream out(path, std::ios::binary);
2✔
3478
    if (!out)
2✔
3479
        return VL_ERROR_INVALID_ARG;
1✔
3480

3481
    out.write((const char*)buf.data(), (std::streamsize)size);
1✔
3482
    return out ? VL_OK : VL_ERROR_FILE_CORRUPT;
1✔
3483
}
2✔
3484

3485
VLError vl_save_to_memory(VLFile file, void* buf, size_t* size_out)
147✔
3486
{
3487
    if (!file)
147✔
3488
        return VL_ERROR_INVALID_HANDLE;
1✔
3489

3490
    if (!size_out)
146✔
3491
        return VL_ERROR_INVALID_ARG;
18✔
3492

3493
    if (file->impl.pcm.empty())
128✔
3494
        return VL_ERROR_INVALID_ARG;
2✔
3495

3496
    std::vector<uint8_t> encoded = buildREX2File(file->impl);
126✔
3497

3498
    if (!buf)
126✔
3499
    {
3500
        *size_out = encoded.size();
46✔
3501
        return VL_OK;
46✔
3502
    }
3503

3504
    if (*size_out < encoded.size())
80✔
3505
    {
3506
        *size_out = encoded.size();
34✔
3507
        return VL_ERROR_BUFFER_TOO_SMALL;
34✔
3508
    }
3509

3510
    std::memcpy(buf, encoded.data(), encoded.size());
46✔
3511
    *size_out = encoded.size();
46✔
3512
    return VL_OK;
46✔
3513
}
126✔
3514

3515
/* -----------------------------------------------------------------------
3516
   Utility
3517
   ----------------------------------------------------------------------- */
3518

3519
const char* vl_error_string(VLError err)
17✔
3520
{
3521
    switch (static_cast<int32_t>(err))
17✔
3522
    {
3523
        case static_cast<int32_t>(VL_OK): return "OK";
1✔
3524
        case static_cast<int32_t>(VL_ERROR_INVALID_HANDLE): return "invalid handle";
1✔
3525
        case static_cast<int32_t>(VL_ERROR_INVALID_ARG): return "invalid argument";
1✔
3526
        case static_cast<int32_t>(VL_ERROR_FILE_NOT_FOUND): return "file not found";
1✔
3527
        case static_cast<int32_t>(VL_ERROR_FILE_CORRUPT): return "file corrupt or unsupported format";
1✔
3528
        case static_cast<int32_t>(VL_ERROR_OUT_OF_MEMORY): return "out of memory";
1✔
3529
        case static_cast<int32_t>(VL_ERROR_INVALID_SLICE): return "invalid slice index";
1✔
3530
        case static_cast<int32_t>(VL_ERROR_INVALID_SAMPLE_RATE): return "invalid sample rate";
1✔
3531
        case static_cast<int32_t>(VL_ERROR_BUFFER_TOO_SMALL): return "buffer too small";
1✔
3532
        case static_cast<int32_t>(VL_ERROR_NO_CREATOR_INFO): return "no creator info available";
1✔
3533
        case static_cast<int32_t>(VL_ERROR_NOT_IMPLEMENTED): return "not implemented";
1✔
3534
        case static_cast<int32_t>(VL_ERROR_ALREADY_HAS_DATA): return "already has data";
1✔
3535
        case static_cast<int32_t>(VL_ERROR_FILE_TOO_NEW): return "file too new";
1✔
3536
        case static_cast<int32_t>(VL_ERROR_ZERO_LOOP_LENGTH): return "zero loop length";
1✔
3537
        case static_cast<int32_t>(VL_ERROR_INVALID_SIZE): return "invalid size";
1✔
3538
        case static_cast<int32_t>(VL_ERROR_INVALID_TEMPO): return "invalid tempo";
1✔
3539
        default: return "unknown error";
1✔
3540
    }
3541
}
3542

3543
const char* vl_version_string(void)
1✔
3544
{
3545
    return "velociloops 0.1.0";
1✔
3546
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc