|
| 1 | + |
| 2 | +#include "soundAPI.h" |
| 3 | +#include "stringAPI.h" |
| 4 | +#include "../settings.h" |
| 5 | +#include <cstdint> |
| 6 | +#include <limits> |
| 7 | +#include <cmath> |
| 8 | + |
| 9 | +namespace dsr { |
| 10 | + |
| 11 | +// See the Source/soundManagers folder for implementations of sound_streamToSpeakers for different operating systems. |
| 12 | + |
| 13 | +SoundBuffer::SoundBuffer(uint32_t samplesPerChannel, uint32_t channelCount, uint32_t sampleRate) |
| 14 | +: samples(buffer_create(samplesPerChannel * channelCount * sizeof(float))), |
| 15 | + samplesPerChannel(samplesPerChannel), |
| 16 | + channelCount(channelCount), |
| 17 | + sampleRate(sampleRate) {} |
| 18 | + |
| 19 | +// TODO: Move endian specified memory access to a reusable header, so that it can be reused for other file formats. |
| 20 | + |
| 21 | +static uint16_t readU16LE(const SafePointer<uint8_t> source, int firstByteIndex) { |
| 22 | + return ((uint16_t)source[firstByteIndex ] ) |
| 23 | + | ((uint16_t)source[firstByteIndex + 1] << 8); |
| 24 | +} |
| 25 | + |
| 26 | +static int16_t readI16LE(const SafePointer<uint8_t> source, int firstByteIndex) { |
| 27 | + return ((uint16_t)source[firstByteIndex ] ) |
| 28 | + | ((uint16_t)source[firstByteIndex + 1] << 8); |
| 29 | +} |
| 30 | + |
| 31 | +static int16_t readI24LE(const SafePointer<uint8_t> source, int firstByteIndex) { |
| 32 | + // Read three bytes of data. |
| 33 | + int32_t result = ((int32_t)source[firstByteIndex ] ) |
| 34 | + | ((int32_t)source[firstByteIndex + 1] << 8 ) |
| 35 | + | ((int32_t)source[firstByteIndex + 2] << 16); |
| 36 | + // Sign extend with a fourth byte if negative. |
| 37 | + if (result & 0b00000000100000000000000000000000) result |= 0b11111111000000000000000000000000; |
| 38 | + return result; |
| 39 | +} |
| 40 | + |
| 41 | +static uint32_t readU32LE(const SafePointer<uint8_t> source, int firstByteIndex) { |
| 42 | + return ((uint32_t)source[firstByteIndex ] ) |
| 43 | + | ((uint32_t)source[firstByteIndex + 1] << 8 ) |
| 44 | + | ((uint32_t)source[firstByteIndex + 2] << 16) |
| 45 | + | ((uint32_t)source[firstByteIndex + 3] << 24); |
| 46 | +} |
| 47 | + |
| 48 | +static int32_t readI32LE(const SafePointer<uint8_t> source, int firstByteIndex) { |
| 49 | + return ((uint32_t)source[firstByteIndex ] ) |
| 50 | + | ((uint32_t)source[firstByteIndex + 1] << 8 ) |
| 51 | + | ((uint32_t)source[firstByteIndex + 2] << 16) |
| 52 | + | ((uint32_t)source[firstByteIndex + 3] << 24); |
| 53 | +} |
| 54 | + |
| 55 | +static void writeU16LE(SafePointer<uint8_t> source, int firstByteIndex, uint16_t value) { |
| 56 | + source[firstByteIndex ] = uint8_t(value & 0xFF); |
| 57 | + source[firstByteIndex + 1] = uint8_t(value >> 8 ); |
| 58 | +} |
| 59 | + |
| 60 | +static void writeI16LE(SafePointer<uint8_t> source, int firstByteIndex, int16_t value) { |
| 61 | + source[firstByteIndex ] = uint8_t(value & 0xFF); |
| 62 | + source[firstByteIndex + 1] = uint8_t(value >> 8 ); |
| 63 | +} |
| 64 | + |
| 65 | +static void writeU32LE(SafePointer<uint8_t> source, int firstByteIndex, uint32_t value) { |
| 66 | + source[firstByteIndex ] = uint8_t( value & 0xFF); |
| 67 | + source[firstByteIndex + 1] = uint8_t((value >> 8 ) & 0xFF); |
| 68 | + source[firstByteIndex + 2] = uint8_t((value >> 16) & 0xFF); |
| 69 | + source[firstByteIndex + 3] = uint8_t( value >> 24 ); |
| 70 | +} |
| 71 | + |
| 72 | +static void writeI32LE(SafePointer<uint8_t> source, int firstByteIndex, int32_t value) { |
| 73 | + source[firstByteIndex ] = uint8_t( value & 0xFF); |
| 74 | + source[firstByteIndex + 1] = uint8_t((value >> 8 ) & 0xFF); |
| 75 | + source[firstByteIndex + 2] = uint8_t((value >> 16) & 0xFF); |
| 76 | + source[firstByteIndex + 3] = uint8_t( value >> 24 ); |
| 77 | +} |
| 78 | + |
| 79 | +// TODO: Create a folder for implementations of sound formats. |
| 80 | + |
| 81 | +/* RIFF wave file header |
| 82 | + char chunkId[4]; // @0 RIFF |
| 83 | + uint32_t chunkSize; //@ 4 |
| 84 | + char format[4]; // @ 8 WAVE |
| 85 | + char subChunkId[4]; // @ 12 fmt |
| 86 | + uint32_t subChunkSize; // @ 16 |
| 87 | + uint16_t audioFormat; // @ 20 |
| 88 | + uint16_t channelCount; // @ 22 |
| 89 | + uint32_t sampleRate; // @ 24 |
| 90 | + uint32_t bytesPerSecond; // @ 28 |
| 91 | + uint16_t blockAlign; // @ 32 |
| 92 | + uint16_t bitsPerSample; // @ 34 |
| 93 | + char dataChunkId[4]; // @ 36 |
| 94 | + uint32_t dataSize; // @ 40 |
| 95 | +*/ |
| 96 | +static const int waveFileHeaderOffset_chunkId = 0; |
| 97 | +static const int waveFileHeaderOffset_chunkSize = 4; |
| 98 | +static const int waveFileHeaderOffset_format = 8; |
| 99 | +static const int waveFileHeaderOffset_subChunkId = 12; |
| 100 | +static const int waveFileHeaderOffset_subChunkSize = 16; |
| 101 | +static const int waveFileHeaderOffset_audioFormat = 20; |
| 102 | +static const int waveFileHeaderOffset_channelCount = 22; |
| 103 | +static const int waveFileHeaderOffset_sampleRate = 24; |
| 104 | +static const int waveFileHeaderOffset_bytesPerSecond = 28; |
| 105 | +static const int waveFileHeaderOffset_blockAlign = 32; |
| 106 | +static const int waveFileHeaderOffset_bitsPerSample = 34; |
| 107 | +static const int waveFileHeaderOffset_dataChunkId = 36; |
| 108 | +static const int waveFileHeaderOffset_dataSize = 40; |
| 109 | +static const int waveFileDataOffset = 44; |
| 110 | + |
| 111 | +static uint32_t getSampleBits(RiffWaveFormat format) { |
| 112 | + if (format == RiffWaveFormat::RawU8) { |
| 113 | + return 8; |
| 114 | + } else if (format == RiffWaveFormat::RawI16) { |
| 115 | + return 16; |
| 116 | + } else if (format == RiffWaveFormat::RawI24) { |
| 117 | + return 24; |
| 118 | + } else { |
| 119 | + return 32; |
| 120 | + } |
| 121 | +} |
| 122 | + |
| 123 | +static uint32_t getPaddedSampleSize(RiffWaveFormat format) { |
| 124 | + if (format == RiffWaveFormat::RawU8) { |
| 125 | + return 1; |
| 126 | + } else if (format == RiffWaveFormat::RawI16) { |
| 127 | + return 2; |
| 128 | + } else { |
| 129 | + return 4; |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +static uint8_t floatToNearestU8(float value) { |
| 134 | + int64_t closest = ((value * 128.0f) + 128.5f); |
| 135 | + if (closest < 0) closest = 0; |
| 136 | + if (closest > 255) closest = 255; |
| 137 | + return (uint8_t)closest; |
| 138 | +} |
| 139 | + |
| 140 | +static int16_t floatToNearestI16(float value) { |
| 141 | + int64_t closest = ((value * 32768.0f) + 0.5f); |
| 142 | + if (closest < -32768) closest = -32768; |
| 143 | + if (closest > 32767) closest = 32767; |
| 144 | + return (int16_t)closest; |
| 145 | +} |
| 146 | + |
| 147 | +static int32_t floatToNearestI24(float value) { |
| 148 | + int64_t closest = ((value * 8388608.0f) + 0.5f); |
| 149 | + if (closest < -8388608) closest = -8388608; |
| 150 | + if (closest > 8388607) closest = 8388607; |
| 151 | + return (int32_t)closest; |
| 152 | +} |
| 153 | + |
| 154 | +static int32_t floatToNearestI32(float value) { |
| 155 | + int64_t closest = ((value * 2147483648.0f) + 0.5f); |
| 156 | + if (closest < -2147483648) closest = -2147483648; |
| 157 | + if (closest > 2147483647) closest = 2147483647; |
| 158 | + return (int32_t)closest; |
| 159 | +} |
| 160 | + |
| 161 | +Buffer sound_saveToRiffWaveBuffer(const SoundBuffer &sound, RiffWaveFormat format) { |
| 162 | + uint32_t paddedBytesPerSample = getPaddedSampleSize(format); |
| 163 | + uintptr_t sampleCount = sound.channelCount * sound.samplesPerChannel; |
| 164 | + uintptr_t dataSize = sampleCount * paddedBytesPerSample; |
| 165 | + uintptr_t totalBytes = waveFileDataOffset + dataSize; |
| 166 | + Buffer result = buffer_create(totalBytes); |
| 167 | + SafePointer<uint8_t> fileContent = buffer_getSafeData<uint8_t>(result, "Wave saving file buffer"); |
| 168 | + uintptr_t subChunkSize = 16u; // PCM |
| 169 | + fileContent[waveFileHeaderOffset_chunkId + 0] = 'R'; |
| 170 | + fileContent[waveFileHeaderOffset_chunkId + 1] = 'I'; |
| 171 | + fileContent[waveFileHeaderOffset_chunkId + 2] = 'F'; |
| 172 | + fileContent[waveFileHeaderOffset_chunkId + 3] = 'F'; |
| 173 | + writeU32LE(fileContent, waveFileHeaderOffset_chunkSize, 36 + subChunkSize); |
| 174 | + fileContent[waveFileHeaderOffset_format + 0] = 'W'; |
| 175 | + fileContent[waveFileHeaderOffset_format + 1] = 'A'; |
| 176 | + fileContent[waveFileHeaderOffset_format + 2] = 'V'; |
| 177 | + fileContent[waveFileHeaderOffset_format + 3] = 'E'; |
| 178 | + fileContent[waveFileHeaderOffset_subChunkId + 0] = 'f'; |
| 179 | + fileContent[waveFileHeaderOffset_subChunkId + 1] = 'm'; |
| 180 | + fileContent[waveFileHeaderOffset_subChunkId + 2] = 't'; |
| 181 | + fileContent[waveFileHeaderOffset_subChunkId + 3] = ' '; |
| 182 | + writeU32LE(fileContent, waveFileHeaderOffset_subChunkSize , subChunkSize); |
| 183 | + writeU16LE(fileContent, waveFileHeaderOffset_audioFormat , 1u); // PCM |
| 184 | + writeU16LE(fileContent, waveFileHeaderOffset_channelCount , sound.channelCount); |
| 185 | + writeU32LE(fileContent, waveFileHeaderOffset_sampleRate , sound.sampleRate); |
| 186 | + writeU32LE(fileContent, waveFileHeaderOffset_bytesPerSecond, sound.sampleRate * sound.channelCount * paddedBytesPerSample); |
| 187 | + writeU16LE(fileContent, waveFileHeaderOffset_blockAlign , paddedBytesPerSample); |
| 188 | + writeU16LE(fileContent, waveFileHeaderOffset_bitsPerSample , getSampleBits(format)); |
| 189 | + fileContent[waveFileHeaderOffset_dataChunkId + 0] = 'd'; |
| 190 | + fileContent[waveFileHeaderOffset_dataChunkId + 1] = 'a'; |
| 191 | + fileContent[waveFileHeaderOffset_dataChunkId + 2] = 't'; |
| 192 | + fileContent[waveFileHeaderOffset_dataChunkId + 3] = 'a'; |
| 193 | + writeU32LE(fileContent, waveFileHeaderOffset_dataSize, dataSize); |
| 194 | + int totalSamples = dataSize / paddedBytesPerSample; |
| 195 | + SafePointer<float> source = buffer_getSafeData<float>(sound.samples, "Float source sound buffer"); |
| 196 | + SafePointer<uint8_t> waveContent = buffer_getSafeData<uint8_t>(result, "Wave saving file buffer"); |
| 197 | + waveContent.increaseBytes(waveFileDataOffset); |
| 198 | + if (format == RiffWaveFormat::RawU8) { |
| 199 | + for (int s = 0; s < totalSamples; s++) { |
| 200 | + waveContent[s] = floatToNearestU8(source[s]); |
| 201 | + } |
| 202 | + } else if (format == RiffWaveFormat::RawI16) { |
| 203 | + for (int s = 0; s < totalSamples; s++) { |
| 204 | + writeI16LE(waveContent, s * paddedBytesPerSample, floatToNearestI16(source[s])); |
| 205 | + } |
| 206 | + } else if (format == RiffWaveFormat::RawI24) { |
| 207 | + for (int s = 0; s < totalSamples; s++) { |
| 208 | + writeI32LE(waveContent, s * paddedBytesPerSample, floatToNearestI24(source[s])); |
| 209 | + } |
| 210 | + } else if (format == RiffWaveFormat::RawI32) { |
| 211 | + for (int s = 0; s < totalSamples; s++) { |
| 212 | + writeI32LE(waveContent, s * paddedBytesPerSample, floatToNearestI32(source[s])); |
| 213 | + } |
| 214 | + } |
| 215 | + return result; |
| 216 | +} |
| 217 | + |
| 218 | +SoundBuffer sound_loadFromRiffWaveBuffer(const Buffer &fileBuffer) { |
| 219 | + SafePointer<uint8_t> fileContent = buffer_getSafeData<uint8_t>(fileBuffer, "Wave loading file buffer"); |
| 220 | + if (fileContent[waveFileHeaderOffset_chunkId + 0] != 'R' |
| 221 | + || fileContent[waveFileHeaderOffset_chunkId + 1] != 'I' |
| 222 | + || fileContent[waveFileHeaderOffset_chunkId + 2] != 'F' |
| 223 | + || fileContent[waveFileHeaderOffset_chunkId + 3] != 'F') { |
| 224 | + sendWarning(U"Expected \"RIFF\" in RIFF wave file's ChunkID field!\n"); |
| 225 | + } |
| 226 | + if (fileContent[waveFileHeaderOffset_subChunkId + 0] != 'f' |
| 227 | + || fileContent[waveFileHeaderOffset_subChunkId + 1] != 'm' |
| 228 | + || fileContent[waveFileHeaderOffset_subChunkId + 2] != 't' |
| 229 | + || fileContent[waveFileHeaderOffset_subChunkId + 3] != ' ') { |
| 230 | + sendWarning(U"Expected \"fmt \" in RIFF wave file's SubChunk1ID field!\n"); |
| 231 | + } |
| 232 | + // TODO: Validate all fields and warn about bad file formating. |
| 233 | + //uint32_t chunkSize = readU32LE(fileContent, waveFileHeaderOffset_chunkSize); |
| 234 | + uint32_t subChunkSize = readU32LE(fileContent, waveFileHeaderOffset_subChunkSize); |
| 235 | + uint16_t audioFormat = readU16LE(fileContent, waveFileHeaderOffset_audioFormat); |
| 236 | + uint16_t channelCount = readU16LE(fileContent, waveFileHeaderOffset_channelCount); |
| 237 | + uint32_t sampleRate = readU32LE(fileContent, waveFileHeaderOffset_sampleRate); |
| 238 | + //uint32_t bytesPerSecond = readU32LE(fileContent, waveFileHeaderOffset_bytesPerSecond); |
| 239 | + uint16_t paddedBytesPerSample = readU16LE(fileContent, waveFileHeaderOffset_blockAlign); |
| 240 | + //uint16_t bitsPerSample = readU16LE(fileContent, waveFileHeaderOffset_bitsPerSample); |
| 241 | + if (fileContent[waveFileHeaderOffset_dataChunkId + 0] != 'd' |
| 242 | + || fileContent[waveFileHeaderOffset_dataChunkId + 1] != 'a' |
| 243 | + || fileContent[waveFileHeaderOffset_dataChunkId + 2] != 't' |
| 244 | + || fileContent[waveFileHeaderOffset_dataChunkId + 3] != 'a') { |
| 245 | + sendWarning(U"Expected \"data\" in RIFF wave file's Subchunk2ID field!\n"); |
| 246 | + } |
| 247 | + uint32_t dataSize = readU32LE(fileContent, waveFileHeaderOffset_dataSize); |
| 248 | + if (dataSize > (buffer_getSize(fileBuffer) - waveFileDataOffset)) { |
| 249 | + sendWarning(U"Data size out of bound in wave file!\n"); |
| 250 | + // Returning an empty buffer because of the failure. |
| 251 | + return SoundBuffer(); |
| 252 | + } |
| 253 | + int totalSamples = dataSize / paddedBytesPerSample; |
| 254 | + SoundBuffer result = SoundBuffer(totalSamples / channelCount, channelCount, sampleRate); |
| 255 | + SafePointer<float> target = buffer_getSafeData<float>(result.samples, "Float target sound buffer"); |
| 256 | + SafePointer<uint8_t> waveContent = buffer_getSafeData<uint8_t>(fileBuffer, "Wave loading file buffer"); |
| 257 | + waveContent.increaseBytes(waveFileDataOffset); |
| 258 | + if (audioFormat == 1 && subChunkSize == 8) { |
| 259 | + // 8-bit sounds have a very poor sampling quality, but this library supports all raw integer formats. |
| 260 | + for (int s = 0; s < totalSamples; s++) { |
| 261 | + target[s] = (float(waveContent[s]) - 128.0f) / 128.0f; |
| 262 | + } |
| 263 | + return result; |
| 264 | + } else if (audioFormat == 1 && subChunkSize == 16) { |
| 265 | + // Recommended sound format to use. |
| 266 | + for (int s = 0; s < totalSamples; s++) { |
| 267 | + target[s] = float(readI16LE(waveContent, s * paddedBytesPerSample)) * 0.000030517578125f; |
| 268 | + } |
| 269 | + return result; |
| 270 | + } else if (audioFormat == 1 && subChunkSize == 24) { |
| 271 | + // A wasteful format to use, because it only uses 24 of 32 bits. |
| 272 | + // 24-bit format stored as signed 32-bit integers but only using the range of signed 24-bit integers. |
| 273 | + for (int s = 0; s < totalSamples; s++) { |
| 274 | + target[s] = float(readI24LE(waveContent, s * paddedBytesPerSample)) * 0.000000119209289551f; |
| 275 | + } |
| 276 | + return result; |
| 277 | + } else if (audioFormat == 1 && subChunkSize == 32) { |
| 278 | + // 32-bit integer format for high-quality raw sound. |
| 279 | + // Useful for short loops of instrument sounds that do not take much space anyway. |
| 280 | + for (int s = 0; s < totalSamples; s++) { |
| 281 | + target[s] = float(readI32LE(waveContent, s * paddedBytesPerSample)) * 0.000000119209289551f; |
| 282 | + } |
| 283 | + return result; |
| 284 | + } else { |
| 285 | + sendWarning(U"Unsupported sound formet of ", subChunkSize, " bits in wave file.\n"); |
| 286 | + // Returning an empty buffer because of the failure. |
| 287 | + return SoundBuffer(); |
| 288 | + } |
| 289 | + return SoundBuffer(); |
| 290 | +} |
| 291 | + |
| 292 | +} |
0 commit comments