Skip to content

Commit f6f74e4

Browse files
committed
Added a sound test application in the SDK.
1 parent dc8b6a4 commit f6f74e4

File tree

14 files changed

+983
-38
lines changed

14 files changed

+983
-38
lines changed

Source/DFPSR/api/soundAPI.cpp

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
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+
}

Source/DFPSR/api/soundAPI.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
2+
#ifndef DFPSR_SOUND_API
3+
#define DFPSR_SOUND_API
4+
5+
#include "bufferAPI.h"
6+
7+
namespace dsr {
8+
// Call this function from a separate thread in a sound engine to initialize the sound system, call back with sound output requests and terminate when the callback returns false.
9+
// The sound_streamToSpeakers function returns false if the backend could not be created, and true iff the backend completed all work and terminated safely.
10+
// Channels: The number of virtual speakers to send data to.
11+
// How this is mapped to physical speakers depends on the system, because surround speakers may choose to play mono and stereo sound using only the front speakers.
12+
// sampleRate: The number of ticks per second for each channel.
13+
// soundOutput: A callback that requests length number of ticks generated by the sound engine and written in a packed format into the data array.
14+
// The soundOutput function returns true iff the audio backend should keep fetching sound samples, and false iff the engine is done and ready for the call to sound_streamToSpeakers to return.
15+
// data: The data array should be filled with sound samples in the -1.0f to 1.0f range, in indices from 0 to (length * channels) - 1.
16+
// The audio backend is responsible for converting the 32-bit float samples into a bit-depth chosen by the backend.
17+
// The backend is supposed to padd the SafePointer's range to at least be divisible by DSR_FLOAT_ALIGNMENT, which allow using both X vectors and F vectors.
18+
// The F vector can be larger than the X vector if building for a SIMD extension that only supports the widest vector length for floating-point operations.
19+
// Padding elements will not add to the time passed in the sound engine, for only played elements increment time.
20+
// length: The number of ticks per channel. The total number of elements to write into data is channels * length.
21+
// How to use:
22+
// Call sound_streamToSpeakers with desired channels and sampleRate from a separate thread.
23+
// Handle callbacks to soundOutput by feeding the next packed sound samples and letting it return false when done.
24+
// Close the thread and let the sound engine clean up resources.
25+
bool sound_streamToSpeakers(int channels, int sampleRate, std::function<bool(dsr::SafePointer<float> data, int length)> soundOutput);
26+
27+
// A sound buffer with packed channels of 32-bit floats.
28+
// The duration in seconds equals samplesPerChannel / sampleRate
29+
struct SoundBuffer {
30+
Buffer samples; // The packed samples.
31+
uint32_t samplesPerChannel = 0u; // Number of samples per channel.
32+
uint32_t channelCount = 0u; // Number of channels packed into the sound format.
33+
uint32_t sampleRate = 0u; // How many samples each channel will play per second.
34+
SoundBuffer(uint32_t samplesPerChannel, uint32_t channelCount, uint32_t sampleRate);
35+
SoundBuffer() {}
36+
};
37+
38+
enum class RiffWaveFormat {
39+
RawU8,
40+
RawI16,
41+
RawI24,
42+
RawI32
43+
};
44+
45+
// Encodes a Riff wave file into a file buffer.
46+
Buffer sound_saveToRiffWaveBuffer(const SoundBuffer &sound, RiffWaveFormat format);
47+
48+
// Decodes a RIFF wave file from memory in file buffer and returns the sound as a packed 32-bit float sound in the -1 to +1 range.
49+
SoundBuffer sound_loadFromRiffWaveBuffer(const Buffer &fileBuffer);
50+
}
51+
52+
#endif

Source/DFPSR/includeFramework.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include "api/guiAPI.h" // Handling windows, interfaces and components
2222
#include "api/mediaMachineAPI.h" // A machine for running image functions
2323
#include "api/fontAPI.h" // Printing text to images
24+
// Sound API
25+
#include "api/soundAPI.h" // Processing buffers and playing sounds
2426
// Convenient API
2527
#include "api/algorithmAPI.h" // Functions for performing operations on whole collections
2628
#include "api/timeAPI.h" // Methods for time and delays
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CompilerFlag "-std=c++14"
2+
Graphics
3+
Sound
4+
Crawl "main.cpp"
5+
Import "../../DFPSR/DFPSR.DsrHead"
6+
7+
# If compiling using CLANG instead of GCC in tools/builder/buildProject.sh, you need to include the C++ standard library explicitly.
8+
#Link "stdc++"
9+
10+
# Linking statically to standard C/C++ libraries allow running the program without installing them.
11+
# Recommended for making an installer for another application or programs that should not need an installer.
12+
# If you don't want static linking of standard C/C++ libraries, you have to comment out StaticRuntime
13+
# and bundle the C/C++ runtime of the compiler with your program's installer.
14+
StaticRuntime
15+
16+
# Uncomment to enable debug mode, which is slower
17+
#Debug

Source/SDK/SoundEngine/Water.wav

615 KB
Binary file not shown.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
3+
echo "Running build_linux.sh $@"
4+
chmod +x ../../tools/builder/buildProject.sh;
5+
../../tools/builder/buildProject.sh SoundEngine.DsrProj Linux $@;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@echo off
2+
3+
echo "Running build_windows.bat %@%
4+
..\..\tools\builder\buildProject.bat SoundEngine.DsrProj Windows %@%

0 commit comments

Comments
 (0)