-
Notifications
You must be signed in to change notification settings - Fork 0
/
BRRBlock.cs
173 lines (151 loc) · 4.88 KB
/
BRRBlock.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// BRR Suite is licensed under the MIT license.
using System.Runtime.CompilerServices;
namespace BRRSuite;
/// <summary>
/// Provides a by-reference wrapper for one BRR block with granular data access.
/// </summary>
public readonly ref struct BRRBlock {
// managed pointer to header
private readonly ref byte _header;
// DUMB (EPIC) HACK
// long is 8 bytes in size, or 16 nibbles
// this can give us fast manipulation of the 4-bit samples without any unsafe pointer or indexing nonsense
// using long because it facilitates sign extension for reading
private readonly ref long _samples;
/// <summary>
/// <u><b>Do not use this constructor.</b></u> Always throws <see cref="InvalidOperationException"/>.
/// </summary>
[Obsolete("The default constructor BRRBlock() should not be used. Use the instance method BRRSample.GetBlock(int).", error: true)]
public BRRBlock() {
throw new InvalidOperationException();
}
/// <remarks>
/// Fast and direct access for <see cref="BRRSample.GetBlock(int)"/>.
/// Skips error checking because it assumes the caller already did it.
/// </remarks>
internal BRRBlock(ref byte headerByte) {
_header = ref headerByte; // reference to header byte
_samples = ref Unsafe.AddByteOffset(ref Unsafe.As<byte, long>(ref _header), 1); // recast as long, +1 byte to skip over header
}
/// <summary>
/// Necessary correction for endianness of the machine.
/// </summary>
/// <remarks>
/// <para>
/// For little-endian machines, each byte is still big-endian,
/// with the high nibble holding the earlier sample.
/// To account for that, we just need to flip the parity of the index with <c>i^0x1</c>.
/// </para>
/// <para>
/// For big-endian machines, the whole thing is big-endian.
/// We need <c>i</c> to become <c>15-i</c>, which is equivalent to <c>i^0xF</c> for our use case.
/// </para>
/// </remarks>
private static readonly int IndexCorrection = BitConverter.IsLittleEndian ? 0b0001 : 0b1111;
/// <summary>
/// Necessary correction for endianness of the machine.
/// </summary>
/// <remarks>
/// <para>
/// For little-endian machines, each byte is already big-endian,
/// so no need to flip parity.
/// </para>
/// <para>
/// For big-endian machines, no correction is needed.
/// </para>
/// </remarks>
private static readonly int IndexCorrectionRead = BitConverter.IsLittleEndian ? 0b1110 : 0b0000;
/// <summary>
/// Provides signed 4-bit access to any of the 16 samples encoded in this block.
/// </summary>
/// <remarks>
/// <para>
/// When getting samples, the return value will be sign extended from bit 3.
/// </para>
/// <para>
/// When setting samples, the input value is masked to the lowest 4 bits.
/// </para>
/// </remarks>
/// <param name="sample">The sample ([0,15]) to access.</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public readonly int this[int sample] {
get {
// if any bit besides the bottom 4 is set, we're not in bounds
// so just get rid of those bits and check for 0
if ((sample >> 4) != 0) { // very fast bounds checking
throw new ArgumentOutOfRangeException();
}
sample ^= IndexCorrectionRead;
sample *= 4; // make it nibbles
// shift left to put value in the highest nibble for free sign extension
long ret = _samples << sample;
// shift to lowest nibble
return (int) (ret >> 60);
}
set {
if ((sample >> 4) != 0) { // very fast bounds checking
throw new ArgumentOutOfRangeException();
}
sample ^= IndexCorrection;
sample *= 4; // make it nibbles
_samples &= ~(0xFL << sample); // create a mask to remove the nibble
_samples |= (value & 0xFL) << sample; // mask the value to 4 bits and shift into place
}
}
/// <summary>
/// Gets a reference to the header byte of this block.
/// </summary>
public readonly ref byte Header {
get => ref _header;
}
/// <summary>
/// Gets or sets the range field in the header byte.
/// </summary>
public readonly int Range {
get => (_header & RangeMask) >> RangeShift;
set {
value <<= RangeShift;
value &= RangeMask;
_header &= RangeMaskOff;
_header |= (byte) value;
}
}
/// <summary>
/// Gets or sets the filter field in the header byte.
/// </summary>
public readonly int Filter {
get => (_header & FilterMask) >> FilterShift;
set {
value <<= FilterShift;
value &= FilterMask;
_header &= FilterMaskOff;
_header |= (byte) value;
}
}
/// <summary>
/// Gets or sets the loop flag field in the header byte.
/// </summary>
public readonly bool Loop {
get => (_header & LoopFlag) != 0;
set {
if (value) {
_header |= LoopFlag;
} else {
_header &= LoopFlagOff;
}
}
}
/// <summary>
/// Gets or sets the end flag field in the header byte.
/// </summary>
public readonly bool End {
get => (_header & EndFlag) != 0;
set {
if (value) {
_header |= EndFlag;
} else {
_header &= EndFlagOff;
}
}
}
}