* Adjustment: Update Bullet version to 3.24.

This commit is contained in:
Robert MacGregor 2022-06-27 10:01:08 -04:00
parent 35de012ee7
commit 4a3f31df2a
6148 changed files with 2112532 additions and 56873 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,203 @@
#include "TinyAudioExample.h"
#include "../CommonInterfaces/CommonExampleInterface.h"
#include "../CommonInterfaces/CommonGUIHelperInterface.h"
#include "Bullet3Common/b3AlignedObjectArray.h"
#include "Bullet3Common/b3HashMap.h"
#include "b3SoundEngine.h"
#include "b3SoundSource.h"
#include <string>
///very basic hashable string implementation, compatible with b3HashMap
struct MyHashString
{
std::string m_string;
unsigned int m_hash;
B3_FORCE_INLINE unsigned int getHash() const
{
return m_hash;
}
MyHashString(const char* name)
: m_string(name)
{
/* magic numbers from http://www.isthe.com/chongo/tech/comp/fnv/ */
static const unsigned int InitialFNV = 2166136261u;
static const unsigned int FNVMultiple = 16777619u;
/* Fowler / Noll / Vo (FNV) Hash */
unsigned int hash = InitialFNV;
for (int i = 0; m_string[i]; i++)
{
hash = hash ^ (m_string[i]); /* xor the low 8 bits */
hash = hash * FNVMultiple; /* multiply by the magic number */
}
m_hash = hash;
}
bool equals(const MyHashString& other) const
{
return (m_string == other.m_string);
}
};
double base_frequency = 440.0;
double base_pitch = 69.0;
double MidiPitch2Frequency(double incoming_note)
{
return base_frequency * pow(2.0, (incoming_note - base_pitch) / 12.0);
}
double FrequencytoMidiPitch(double incoming_frequency)
{
return base_pitch + (12.0 * log(incoming_frequency / base_frequency) / log(2));
}
class TinyAudioExample : public CommonExampleInterface
{
GUIHelperInterface* m_guiHelper;
b3SoundEngine m_soundEngine;
int m_wavId;
b3HashMap<MyHashString, int> m_keyToSoundSource;
public:
TinyAudioExample(struct GUIHelperInterface* helper)
: m_guiHelper(helper)
{
}
virtual ~TinyAudioExample()
{
}
virtual void initPhysics()
{
int numSoundSources = 32;
bool useRealTimeDac = true;
m_soundEngine.init(numSoundSources, useRealTimeDac);
m_wavId = m_soundEngine.loadWavFile("wav/xylophone.rosewood.ff.C5B5_1.wav");
int sampleRate = m_soundEngine.getSampleRate();
}
virtual void exitPhysics()
{
m_soundEngine.exit();
}
virtual void renderScene()
{
}
virtual void stepSimulation(float deltaTime)
{
}
virtual void physicsDebugDraw(int debugFlags)
{
}
virtual bool mouseMoveCallback(float x, float y)
{
return false;
}
virtual bool mouseButtonCallback(int button, int state, float x, float y)
{
return false;
}
virtual bool keyboardCallback(int key, int state)
{
if (key >= 'a' && key <= 'z')
{
char keyStr[2];
keyStr[0] = (char)key;
keyStr[1] = 0;
MyHashString hs(keyStr);
if (state)
{
int soundSourceIndex = m_soundEngine.getAvailableSoundSource();
if (soundSourceIndex >= 0)
{
int note = key - (97 - 58);
double freq = MidiPitch2Frequency(note);
b3SoundMessage msg;
msg.m_type = B3_SOUND_SOURCE_SINE_OSCILLATOR;
msg.m_frequency = freq;
msg.m_amplitude = 1;
msg.m_type = B3_SOUND_SOURCE_WAV_FILE;
msg.m_wavId = m_wavId;
msg.m_attackRate = 1;
msg.m_sustainLevel = 1;
msg.m_releaseRate = 0.001;
m_soundEngine.startSound(soundSourceIndex, msg);
m_keyToSoundSource.insert(hs, soundSourceIndex);
//printf("soundSourceIndex:%d\n", soundSourceIndex);
#if 0
b3SoundSource* soundSource = this->m_soundSourcesPool[soundSourceIndex];
soundSource->setOscillatorFrequency(0, freq );
soundSource->setOscillatorFrequency(1, freq );
soundSource->startSound();
{
int* soundSourceIndexPtr = m_keyToSoundSource[hs];
if (soundSourceIndexPtr)
{
int newIndex = *soundSourceIndexPtr;
printf("just inserted: %d\n", newIndex);
}
}
#endif
}
}
else
{
int* soundSourceIndexPtr = m_keyToSoundSource[hs];
if (soundSourceIndexPtr)
{
int soundSourceIndex = *soundSourceIndexPtr;
//printf("releaseSound: %d\n", soundSourceIndex);
m_soundEngine.releaseSound(soundSourceIndex);
}
#if 0
if (soundSourceIndex>=0)
{
printf("releasing %d\n", soundSourceIndex);
b3SoundSource* soundSource = this->m_soundSourcesPool[soundSourceIndex];
soundSource->stopSound();
}
}
#endif
}
}
return false;
}
void resetCamera()
{
float dist = 4;
float pitch = 52;
float yaw = 35;
float targetPos[3] = {0, 0, 0};
m_guiHelper->resetCamera(dist, pitch, yaw, targetPos[0], targetPos[1], targetPos[2]);
}
};
CommonExampleInterface* TinyAudioExampleCreateFunc(CommonExampleOptions& options)
{
return new TinyAudioExample(options.m_guiHelper);
}
B3_STANDALONE_EXAMPLE(TinyAudioExampleCreateFunc)

View file

@ -0,0 +1,7 @@
#ifndef TINY_AUDIO_EXAMPLE_H
#define TINY_AUDIO_EXAMPLE_H
class CommonExampleInterface* TinyAudioExampleCreateFunc(struct CommonExampleOptions& options);
#endif //TINY_AUDIO_EXAMPLE_H

View file

@ -0,0 +1,109 @@
#include "b3ADSR.h"
//ADSR mostly copied/reimplemented from Stk, see
//http://github.com/thestk/stk
//! ADSR envelope states.
enum
{
ADSR_ATTACK, /*!< Attack */
ADSR_DECAY, /*!< Decay */
ADSR_SUSTAIN, /*!< Sustain */
ADSR_RELEASE, /*!< Release */
ADSR_IDLE /*!< Before attack / after release */
};
b3ADSR::b3ADSR()
{
m_target = 0.0;
m_value = 0.0;
m_attackRate = 0.001;
m_decayRate = 0.00001;
m_releaseRate = 0.0005;
m_sustainLevel = 0.5;
m_state = ADSR_IDLE;
m_autoKeyOff = false;
}
b3ADSR::~b3ADSR()
{
}
double b3ADSR::tick()
{
switch (m_state)
{
case ADSR_ATTACK:
m_value += m_attackRate;
if (m_value >= m_target)
{
m_value = m_target;
m_target = m_sustainLevel;
m_state = ADSR_DECAY;
}
break;
case ADSR_DECAY:
if (m_value > m_sustainLevel)
{
m_value -= m_decayRate;
if (m_value <= m_sustainLevel)
{
m_value = m_sustainLevel;
m_state = ADSR_SUSTAIN;
if (m_autoKeyOff)
{
keyOff();
}
}
}
else
{
m_value += m_decayRate; // attack target < sustain level
if (m_value >= m_sustainLevel)
{
m_value = m_sustainLevel;
m_state = ADSR_SUSTAIN;
if (m_autoKeyOff)
{
keyOff();
}
}
}
break;
case ADSR_RELEASE:
m_value -= m_releaseRate;
if (m_value <= 0.0)
{
m_value = 0.0;
m_state = ADSR_IDLE;
}
}
return m_value;
}
bool b3ADSR::isIdle() const
{
return m_state == ADSR_IDLE;
}
void b3ADSR::keyOn(bool autoKeyOff)
{
m_autoKeyOff = autoKeyOff;
if (m_target <= 0.0)
m_target = 1.0;
if (m_attackRate == 1)
{
m_value = 1.0;
}
m_state = ADSR_ATTACK;
}
void b3ADSR::keyOff()
{
m_autoKeyOff = false;
m_target = 0.0;
m_state = ADSR_RELEASE;
}

View file

@ -0,0 +1,34 @@
#ifndef B3_ADSR_H
#define B3_ADSR_H
class b3ADSR
{
int m_state;
double m_value;
double m_target;
double m_attackRate;
double m_decayRate;
double m_releaseRate;
double m_releaseTime;
double m_sustainLevel;
bool m_autoKeyOff;
public:
b3ADSR();
virtual ~b3ADSR();
double tick();
bool isIdle() const;
void keyOn(bool autoKeyOff);
void keyOff();
void setValues(double attack, double decay, double sustain, double release)
{
m_attackRate = attack;
m_decayRate = decay;
m_sustainLevel = sustain;
m_releaseRate = release;
}
};
#endif //B3_ADSR_H

View file

@ -0,0 +1,166 @@
#include "b3AudioListener.h"
#include "b3SoundSource.h"
#include "Bullet3Common/b3Logging.h"
#include "b3WriteWavFile.h"
#include <math.h>
template <class T>
inline const T& MyMin(const T& a, const T& b)
{
return a < b ? a : b;
}
#define MAX_SOUND_SOURCES 128
#define B3_SAMPLE_RATE 48000
struct b3AudioListenerInternalData
{
int m_numControlTicks;
double m_sampleRate;
b3SoundSource* m_soundSources[MAX_SOUND_SOURCES];
b3WriteWavFile m_wavOut2;
bool m_writeWavOut;
b3AudioListenerInternalData()
: m_numControlTicks(64),
m_sampleRate(B3_SAMPLE_RATE),
m_writeWavOut(false)
{
for (int i = 0; i < MAX_SOUND_SOURCES; i++)
{
m_soundSources[i] = 0;
}
}
};
b3AudioListener::b3AudioListener()
{
m_data = new b3AudioListenerInternalData();
if (m_data->m_writeWavOut)
{
m_data->m_wavOut2.setWavFile("bulletAudio2.wav", B3_SAMPLE_RATE, 2, false);
}
}
b3AudioListener::~b3AudioListener()
{
if (m_data->m_writeWavOut)
{
m_data->m_wavOut2.closeWavFile();
}
delete m_data;
}
int b3AudioListener::addSoundSource(b3SoundSource* source)
{
int soundIndex = -1;
for (int i = 0; i < MAX_SOUND_SOURCES; i++)
{
if (m_data->m_soundSources[i] == 0)
{
m_data->m_soundSources[i] = source;
soundIndex = i;
break;
}
}
return soundIndex;
}
void b3AudioListener::removeSoundSource(b3SoundSource* source)
{
for (int i = 0; i < MAX_SOUND_SOURCES; i++)
{
if (m_data->m_soundSources[i] == source)
{
m_data->m_soundSources[i] = 0;
}
}
}
b3AudioListenerInternalData* b3AudioListener::getTickData()
{
return m_data;
}
const b3AudioListenerInternalData* b3AudioListener::getTickData() const
{
return m_data;
}
double b3AudioListener::getSampleRate() const
{
return m_data->m_sampleRate;
}
void b3AudioListener::setSampleRate(double sampleRate)
{
m_data->m_sampleRate = sampleRate;
}
int b3AudioListener::tick(void* outputBuffer, void* inputBuffer1, unsigned int nBufferFrames,
double streamTime, unsigned int status, void* dataPointer)
{
B3_PROFILE("b3AudioListener::tick");
b3AudioListenerInternalData* data = (b3AudioListenerInternalData*)dataPointer;
register double outs[2], *samples = (double*)outputBuffer;
register double tempOuts[2];
int counter, nTicks = (int)nBufferFrames;
bool done = false;
int numSamples = 0;
while (nTicks > 0 && !done)
{
counter = MyMin(nTicks, data->m_numControlTicks);
bool newsynth = true;
if (newsynth)
{
for (int i = 0; i < counter; i++)
{
outs[0] = 0.;
outs[1] = 0.;
//make_sound_double(outs,1);
float numActiveSources = 0;
for (int i = 0; i < MAX_SOUND_SOURCES; i++)
{
if (data->m_soundSources[i])
{
tempOuts[0] = 0;
tempOuts[1] = 0;
if (data->m_soundSources[i]->computeSamples(tempOuts, 1, data->m_sampleRate))
{
numActiveSources++;
//simple mixer
outs[0] += tempOuts[0];
outs[1] += tempOuts[1];
}
}
}
//soft-clipping of sounds
outs[0] = tanh(outs[0]);
outs[1] = tanh(outs[1]);
*samples++ = outs[0];
*samples++ = outs[1];
numSamples++;
}
nTicks -= counter;
}
if (nTicks == 0)
break;
}
//logging to wav file
if (data->m_writeWavOut && numSamples)
{
data->m_wavOut2.tick((double*)outputBuffer, numSamples);
}
return 0;
}

View file

@ -0,0 +1,27 @@
#ifndef B3_AUDIO_LISTENER_H
#define B3_AUDIO_LISTENER_H
class b3SoundSource;
class b3AudioListener
{
struct b3AudioListenerInternalData* m_data;
public:
b3AudioListener();
virtual ~b3AudioListener();
static int tick(void* outputBuffer, void* inputBuffer1, unsigned int nBufferFrames,
double streamTime, unsigned int status, void* dataPointer);
int addSoundSource(b3SoundSource* source);
void removeSoundSource(b3SoundSource* source);
b3AudioListenerInternalData* getTickData();
const b3AudioListenerInternalData* getTickData() const;
double getSampleRate() const;
void setSampleRate(double sampleRate);
};
#endif //B3_AUDIO_LISTENER_H

View file

@ -0,0 +1,492 @@
//b3ReadWavFile is implemented based on code from the STK toolkit
//See https://github.com/thestk/stk
//Some improvement: the ticking data (b3WavTicker) is separate from wav file,
//This makes it possoble to play a single wav multiple times at the same time
#include "b3ReadWavFile.h"
#include "b3SwapUtils.h"
const unsigned long B3_SINT8 = 0x1;
const unsigned long B3_SINT16 = 0x2;
const unsigned long B3_SINT24 = 0x4;
const unsigned long B3_SINT32 = 0x8;
const unsigned long B3_FLOAT32 = 0x10;
const unsigned long B3_FLOAT64 = 0x20;
b3ReadWavFile::b3ReadWavFile()
{
fd_ = 0;
m_machineIsLittleEndian = b3MachineIsLittleEndian();
}
b3ReadWavFile::~b3ReadWavFile()
{
if (fd_)
fclose(fd_);
}
void b3ReadWavFile::normalize(double peak)
{
int i;
double max = 0.0;
for (i = 0; i < m_frames.size(); i++)
{
if (fabs(m_frames[i]) > max)
max = (double)fabs((double)m_frames[i]);
}
if (max > 0.0)
{
max = 1.0 / max;
max *= peak;
for (i = 0; i < m_frames.size(); i++)
m_frames[i] *= max;
}
}
double b3ReadWavFile::interpolate(double frame, unsigned int channel) const
{
int iIndex = (int)frame; // integer part of index
double output, alpha = frame - (double)iIndex; // fractional part of index
iIndex = iIndex * channels_ + channel;
output = m_frames[iIndex];
if (alpha > 0.0)
output += (alpha * (m_frames[iIndex + channels_] - output));
return output;
}
double b3ReadWavFile::tick(unsigned int channel, b3WavTicker *ticker)
{
if (ticker->finished_) return 0.0;
if (ticker->time_ < 0.0 || ticker->time_ > (double)(this->m_numFrames - 1.0))
{
for (int i = 0; i < ticker->lastFrame_.size(); i++) ticker->lastFrame_[i] = 0.0;
ticker->finished_ = true;
return 0.0;
}
double tyme = ticker->time_;
bool interpolate_ = true; //for now
if (interpolate_)
{
for (int i = 0; i < ticker->lastFrame_.size(); i++)
ticker->lastFrame_[i] = interpolate(tyme, i);
}
// Increment time, which can be negative.
ticker->time_ += ticker->rate_;
return ticker->lastFrame_[channel];
}
void b3ReadWavFile::resize()
{
m_frames.resize(channels_ * m_numFrames);
}
b3WavTicker b3ReadWavFile::createWavTicker(double sampleRate)
{
b3WavTicker ticker;
ticker.lastFrame_.resize(this->channels_);
ticker.time_ = 0;
ticker.finished_ = false;
ticker.rate_ = fileDataRate_ / sampleRate;
return ticker;
}
bool b3ReadWavFile::getWavInfo(const char *fileName)
{
fd_ = fopen(fileName, "rb");
if (fd_ == 0)
return false;
char header[12];
if (fread(&header, 4, 3, fd_) != 3)
return false;
bool res = false;
if (!strncmp(header, "RIFF", 4) &&
!strncmp(&header[8], "WAVE", 4))
res = true;
//getWavInfo( fileName );
// Find "format" chunk ... it must come before the "data" chunk.
char id[4];
int chunkSize;
if (fread(&id, 4, 1, fd_) != 1)
return false;
while (strncmp(id, "fmt ", 4))
{
if (fread(&chunkSize, 4, 1, fd_) != 1)
return false;
if (!m_machineIsLittleEndian)
{
b3Swap32((unsigned char *)&chunkSize);
}
if (fseek(fd_, chunkSize, SEEK_CUR) == -1)
return false;
if (fread(&id, 4, 1, fd_) != 1)
return false;
}
// Check that the data is not compressed.
unsigned short format_tag;
if (fread(&chunkSize, 4, 1, fd_) != 1)
return false; // Read fmt chunk size.
if (fread(&format_tag, 2, 1, fd_) != 1)
return false;
if (!m_machineIsLittleEndian)
{
b3Swap16((unsigned char *)&format_tag);
b3Swap32((unsigned char *)&chunkSize);
}
if (format_tag == 0xFFFE)
{ // WAVE_FORMAT_EXTENSIBLE
dataOffset_ = ftell(fd_);
if (fseek(fd_, 14, SEEK_CUR) == -1)
return false;
unsigned short extSize;
if (fread(&extSize, 2, 1, fd_) != 1)
return false;
if (!m_machineIsLittleEndian)
{
b3Swap16((unsigned char *)&extSize);
}
if (extSize == 0)
return false;
if (fseek(fd_, 6, SEEK_CUR) == -1)
return false;
if (fread(&format_tag, 2, 1, fd_) != 1)
return false;
if (!m_machineIsLittleEndian)
{
b3Swap16((unsigned char *)&format_tag);
}
if (fseek(fd_, dataOffset_, SEEK_SET) == -1)
return false;
}
if (format_tag != 1 && format_tag != 3)
{ // PCM = 1, FLOAT = 3
// oStream_ << "FileRead: "<< fileName << " contains an unsupported data format type (" << format_tag << ").";
return false;
}
// Get number of channels from the header.
short int temp;
if (fread(&temp, 2, 1, fd_) != 1)
return false;
if (!m_machineIsLittleEndian)
{
b3Swap16((unsigned char *)&temp);
}
channels_ = (unsigned int)temp;
// Get file sample rate from the header.
int srate;
if (fread(&srate, 4, 1, fd_) != 1)
return false;
if (!m_machineIsLittleEndian)
{
b3Swap32((unsigned char *)&srate);
}
fileDataRate_ = (double)srate;
// Determine the data type.
dataType_ = 0;
if (fseek(fd_, 6, SEEK_CUR) == -1)
return false; // Locate bits_per_sample info.
if (fread(&temp, 2, 1, fd_) != 1)
return false;
if (!m_machineIsLittleEndian)
{
b3Swap16((unsigned char *)&temp);
}
if (format_tag == 1)
{
if (temp == 8)
dataType_ = B3_SINT8;
else if (temp == 16)
dataType_ = B3_SINT16;
else if (temp == 24)
dataType_ = B3_SINT24;
else if (temp == 32)
dataType_ = B3_SINT32;
}
else if (format_tag == 3)
{
if (temp == 32)
dataType_ = B3_FLOAT32;
else if (temp == 64)
dataType_ = B3_FLOAT64;
}
if (dataType_ == 0)
{
// oStream_ << "FileRead: " << temp << " bits per sample with data format " << format_tag << " are not supported (" << fileName << ").";
return false;
}
// Jump over any remaining part of the "fmt" chunk.
if (fseek(fd_, chunkSize - 16, SEEK_CUR) == -1)
return false;
// Find "data" chunk ... it must come after the "fmt" chunk.
if (fread(&id, 4, 1, fd_) != 1)
return false;
while (strncmp(id, "data", 4))
{
if (fread(&chunkSize, 4, 1, fd_) != 1)
return false;
if (!m_machineIsLittleEndian)
{
b3Swap32((unsigned char *)&chunkSize);
}
chunkSize += chunkSize % 2; // chunk sizes must be even
if (fseek(fd_, chunkSize, SEEK_CUR) == -1)
return false;
if (fread(&id, 4, 1, fd_) != 1)
return false;
}
// Get length of data from the header.
int bytes;
if (fread(&bytes, 4, 1, fd_) != 1)
return false;
if (!m_machineIsLittleEndian)
{
b3Swap32((unsigned char *)&bytes);
}
m_numFrames = bytes / temp / channels_; // sample frames
m_numFrames *= 8; // sample frames
dataOffset_ = ftell(fd_);
byteswap_ = false;
if (!m_machineIsLittleEndian)
{
byteswap_ = true;
}
wavFile_ = true;
return true;
}
bool b3ReadWavFile::read(unsigned long startFrame, bool doNormalize)
{
// Make sure we have an open file.
if (fd_ == 0)
{
// oStream_ << "FileRead::read: a file is not open!";
// Stk::handleError( StkError::WARNING ); return;
return false;
}
// Check the m_frames size.
unsigned long nFrames = this->m_numFrames; //m_frames.frames();
if (nFrames == 0)
{
// oStream_ << "FileRead::read: StkFrames m_frames size is zero ... no data read!";
// Stk::handleError( StkError::WARNING );
return false;
}
if (startFrame >= m_numFrames)
{
return false;
//oStream_ << "FileRead::read: startFrame argument is greater than or equal to the file size!";
//Stk::handleError( StkError::FUNCTION_ARGUMENT );
}
// Check for file end.
if (startFrame + nFrames > m_numFrames)
nFrames = m_numFrames - startFrame;
long i, nSamples = (long)(nFrames * channels_);
unsigned long offset = startFrame * channels_;
// Read samples into StkFrames data m_frames.
if (dataType_ == B3_SINT16)
{
signed short int *buf = (signed short int *)&m_frames[0];
if (fseek(fd_, dataOffset_ + (offset * 2), SEEK_SET) == -1)
return false;
if (fread(buf, nSamples * 2, 1, fd_) != 1)
return false;
if (byteswap_)
{
signed short int *ptr = buf;
for (i = nSamples - 1; i >= 0; i--)
b3Swap16((unsigned char *)ptr++);
}
if (doNormalize)
{
double gain = 1.0 / 32768.0;
for (i = nSamples - 1; i >= 0; i--)
m_frames[i] = buf[i] * gain;
}
else
{
for (i = nSamples - 1; i >= 0; i--)
m_frames[i] = buf[i];
}
}
else if (dataType_ == B3_SINT32)
{
int *buf = (int *)&m_frames[0];
if (fseek(fd_, dataOffset_ + (offset * 4), SEEK_SET) == -1)
return false;
if (fread(buf, nSamples * 4, 1, fd_) != 1)
return false;
if (byteswap_)
{
int *ptr = buf;
for (i = nSamples - 1; i >= 0; i--)
b3Swap32((unsigned char *)ptr++);
}
if (doNormalize)
{
double gain = 1.0 / 2147483648.0;
for (i = nSamples - 1; i >= 0; i--)
m_frames[i] = buf[i] * gain;
}
else
{
for (i = nSamples - 1; i >= 0; i--)
m_frames[i] = buf[i];
}
}
else if (dataType_ == B3_FLOAT32)
{
float *buf = (float *)&m_frames[0];
if (fseek(fd_, dataOffset_ + (offset * 4), SEEK_SET) == -1)
return false;
if (fread(buf, nSamples * 4, 1, fd_) != 1)
return false;
if (byteswap_)
{
float *ptr = buf;
for (i = nSamples - 1; i >= 0; i--)
b3Swap32((unsigned char *)ptr++);
}
for (i = nSamples - 1; i >= 0; i--)
m_frames[i] = buf[i];
}
else if (dataType_ == B3_FLOAT64)
{
double *buf = (double *)&m_frames[0];
if (fseek(fd_, dataOffset_ + (offset * 8), SEEK_SET) == -1)
return false;
if (fread(buf, nSamples * 8, 1, fd_) != 1)
return false;
if (byteswap_)
{
double *ptr = buf;
for (i = nSamples - 1; i >= 0; i--)
b3Swap64((unsigned char *)ptr++);
}
for (i = nSamples - 1; i >= 0; i--)
m_frames[i] = buf[i];
}
else if (dataType_ == B3_SINT8 && wavFile_)
{ // 8-bit WAV data is unsigned!
unsigned char *buf = (unsigned char *)&m_frames[0];
if (fseek(fd_, dataOffset_ + offset, SEEK_SET) == -1)
return false;
if (fread(buf, nSamples, 1, fd_) != 1)
return false;
if (doNormalize)
{
double gain = 1.0 / 128.0;
for (i = nSamples - 1; i >= 0; i--)
m_frames[i] = (buf[i] - 128) * gain;
}
else
{
for (i = nSamples - 1; i >= 0; i--)
m_frames[i] = buf[i] - 128.0;
}
}
else if (dataType_ == B3_SINT8)
{ // signed 8-bit data
char *buf = (char *)&m_frames[0];
if (fseek(fd_, dataOffset_ + offset, SEEK_SET) == -1)
return false;
if (fread(buf, nSamples, 1, fd_) != 1)
return false;
if (doNormalize)
{
double gain = 1.0 / 128.0;
for (i = nSamples - 1; i >= 0; i--)
m_frames[i] = buf[i] * gain;
}
else
{
for (i = nSamples - 1; i >= 0; i--)
m_frames[i] = buf[i];
}
}
else if (dataType_ == B3_SINT24)
{
// 24-bit values are harder to import efficiently since there is
// no native 24-bit type. The following routine works but is much
// less efficient than that used for the other data types.
int temp;
unsigned char *ptr = (unsigned char *)&temp;
double gain = 1.0 / 2147483648.0;
if (fseek(fd_, dataOffset_ + (offset * 3), SEEK_SET) == -1)
return false;
for (i = 0; i < nSamples; i++)
{
if (m_machineIsLittleEndian)
{
if (byteswap_)
{
if (fread(ptr, 3, 1, fd_) != 1)
return false;
temp &= 0x00ffffff;
b3Swap32((unsigned char *)ptr);
}
else
{
if (fread(ptr + 1, 3, 1, fd_) != 1)
return false;
temp &= 0xffffff00;
}
}
else
{
if (byteswap_)
{
if (fread(ptr + 1, 3, 1, fd_) != 1)
return false;
temp &= 0xffffff00;
b3Swap32((unsigned char *)ptr);
}
else
{
if (fread(ptr, 3, 1, fd_) != 1)
return false;
temp &= 0x00ffffff;
}
}
if (doNormalize)
{
m_frames[i] = (double)temp * gain; // "gain" also includes 1 / 256 factor.
}
else
m_frames[i] = (double)temp / 256; // right shift without affecting the sign bit
}
}
// m_frames.setDataRate( fileDataRate_ );
return true;
// error:
// oStream_ << "FileRead: Error reading file data.";
// handleError( StkError::FILE_ERROR);
}

View file

@ -0,0 +1,53 @@
#ifndef B3_READ_WAV_FILE_H
#define B3_READ_WAV_FILE_H
#include "Bullet3Common/b3AlignedObjectArray.h"
#include <stdio.h>
#include <string.h>
struct b3WavTicker
{
b3AlignedObjectArray<double> lastFrame_;
bool finished_;
double time_;
double rate_;
};
class b3ReadWavFile
{
bool byteswap_;
bool wavFile_;
unsigned long m_numFrames;
unsigned long dataType_;
double fileDataRate_;
FILE *fd_;
unsigned long dataOffset_;
unsigned int channels_;
bool m_machineIsLittleEndian;
public:
b3ReadWavFile();
virtual ~b3ReadWavFile();
b3AlignedObjectArray<double> m_frames;
bool getWavInfo(const char *fileName);
void normalize(double peak);
double interpolate(double frame, unsigned int channel) const;
double tick(unsigned int channel, b3WavTicker *ticker);
void resize();
b3WavTicker createWavTicker(double sampleRate);
bool read(unsigned long startFrame, bool doNormalize);
int getNumFrames() const
{
return m_numFrames;
}
};
#endif //B3_READ_WAV_FILE_H

View file

@ -0,0 +1,185 @@
#include "b3SoundEngine.h"
#include "RtAudio.h"
#include "b3AudioListener.h"
#include "b3SoundSource.h"
#include "Bullet3Common/b3AlignedObjectArray.h"
#include "b3ReadWavFile.h"
#include "../Utils/b3ResourcePath.h"
#include "Bullet3Common/b3HashMap.h"
// The default real-time audio input and output buffer size. If
// clicks are occuring in the input and/or output sound stream, a
// larger buffer size may help. Larger buffer sizes, however, produce
// more latency.
//const unsigned int RT_BUFFER_SIZE = 1024;
const unsigned int RT_BUFFER_SIZE = 256;
struct b3SoundEngineInternalData
{
b3AudioListener m_listener;
RtAudio m_dac;
bool m_useRealTimeDac;
b3AlignedObjectArray<b3SoundSource*> m_soundSources;
b3HashMap<b3HashInt, b3ReadWavFile*> m_wavFiles;
b3HashMap<b3HashString, int> m_name2wav;
int m_wavFileUidGenerator;
b3SoundEngineInternalData()
: m_useRealTimeDac(false),
m_wavFileUidGenerator(123)
{
}
};
b3SoundEngine::b3SoundEngine()
{
m_data = new b3SoundEngineInternalData();
}
b3SoundEngine::~b3SoundEngine()
{
exit();
delete m_data;
}
void b3SoundEngine::init(int maxNumSoundSources, bool useRealTimeDac)
{
for (int i = 0; i < maxNumSoundSources; i++)
{
b3SoundSource* source = new b3SoundSource();
m_data->m_soundSources.push_back(source);
m_data->m_listener.addSoundSource(source);
}
this->m_data->m_useRealTimeDac = useRealTimeDac;
if (useRealTimeDac)
{
RtAudioFormat format = (sizeof(double) == 8) ? RTAUDIO_FLOAT64 : RTAUDIO_FLOAT32;
RtAudio::StreamParameters parameters;
parameters.deviceId = m_data->m_dac.getDefaultOutputDevice();
parameters.nChannels = 2;
unsigned int bufferFrames = RT_BUFFER_SIZE;
double sampleRate = m_data->m_listener.getSampleRate();
m_data->m_dac.openStream(&parameters, NULL, format, (unsigned int)sampleRate, &bufferFrames, &b3AudioListener::tick,
(void*)m_data->m_listener.getTickData());
m_data->m_dac.startStream();
}
}
void b3SoundEngine::exit()
{
m_data->m_dac.closeStream();
m_data->m_useRealTimeDac = false;
for (int i = 0; i < m_data->m_soundSources.size(); i++)
{
m_data->m_listener.removeSoundSource(m_data->m_soundSources[i]);
m_data->m_soundSources[i]->stopSound();
delete m_data->m_soundSources[i];
}
m_data->m_soundSources.clear();
for (int i = 0; i < m_data->m_wavFiles.size(); i++)
{
b3ReadWavFile** wavPtr = m_data->m_wavFiles.getAtIndex(i);
if (wavPtr && *wavPtr)
{
b3ReadWavFile* wav = *wavPtr;
delete wav;
}
}
m_data->m_wavFiles.clear();
m_data->m_name2wav.clear();
}
int b3SoundEngine::getAvailableSoundSource()
{
for (int i = 0; i < m_data->m_soundSources.size(); i++)
{
if (m_data->m_soundSources[i]->isAvailable())
{
return i;
}
}
return -1;
}
void b3SoundEngine::startSound(int soundSourceIndex, b3SoundMessage msg)
{
b3SoundSource* soundSource = m_data->m_soundSources[soundSourceIndex];
soundSource->setOscillatorAmplitude(0, msg.m_amplitude);
soundSource->setOscillatorAmplitude(1, msg.m_amplitude);
soundSource->setADSR(msg.m_attackRate, msg.m_decayRate, msg.m_sustainLevel, msg.m_releaseRate);
switch (msg.m_type)
{
case B3_SOUND_SOURCE_SINE_OSCILLATOR:
{
soundSource->setOscillatorFrequency(0, msg.m_frequency);
soundSource->setOscillatorFrequency(1, msg.m_frequency);
soundSource->startSound(msg.m_autoKeyOff);
break;
}
case B3_SOUND_SOURCE_WAV_FILE:
{
b3ReadWavFile** wavFilePtr = m_data->m_wavFiles[msg.m_wavId];
if (wavFilePtr)
{
b3ReadWavFile* wavFile = *wavFilePtr;
soundSource->setWavFile(0, wavFile, getSampleRate());
soundSource->setWavFile(1, wavFile, getSampleRate());
soundSource->startSound(msg.m_autoKeyOff);
}
break;
}
default:
{
}
}
}
void b3SoundEngine::releaseSound(int soundSourceIndex)
{
b3SoundSource* soundSource = m_data->m_soundSources[soundSourceIndex];
soundSource->stopSound();
}
int b3SoundEngine::loadWavFile(const char* fileName)
{
int* wavUidPtr = m_data->m_name2wav[fileName];
if (wavUidPtr)
{
return *wavUidPtr;
}
char resourcePath[1024];
if (b3ResourcePath::findResourcePath(fileName, resourcePath, 1024, 0))
{
b3ReadWavFile* wavFile = new b3ReadWavFile();
wavFile->getWavInfo(resourcePath);
wavFile->resize();
wavFile->read(0, true);
wavFile->normalize(1);
int wavUID = m_data->m_wavFileUidGenerator++;
m_data->m_wavFiles.insert(wavUID, wavFile);
m_data->m_name2wav.insert(fileName, wavUID);
return wavUID;
}
return 0;
}
double b3SoundEngine::getSampleRate() const
{
return m_data->m_listener.getSampleRate();
}

View file

@ -0,0 +1,55 @@
#ifndef B3_SOUND_ENGINE_H
#define B3_SOUND_ENGINE_H
#include "Bullet3Common/b3Scalar.h"
#include "b3Sound_C_Api.h"
struct b3SoundMessage
{
int m_type; //B3_SOUND_SOURCE_TYPE
double m_amplitude;
double m_frequency;
int m_wavId;
double m_attackRate;
double m_decayRate;
double m_sustainLevel;
double m_releaseRate;
bool m_autoKeyOff;
b3SoundMessage()
: m_type(B3_SOUND_SOURCE_SINE_OSCILLATOR),
m_amplitude(0.5),
m_frequency(440),
m_wavId(-1),
m_attackRate(0.001),
m_decayRate(0.00001),
m_sustainLevel(0.5),
m_releaseRate(0.0005),
m_autoKeyOff(false)
{
}
};
class b3SoundEngine
{
struct b3SoundEngineInternalData* m_data;
public:
b3SoundEngine();
virtual ~b3SoundEngine();
void init(int maxNumSoundSources, bool useRealTimeDac);
void exit();
int getAvailableSoundSource();
void startSound(int soundSourceIndex, b3SoundMessage msg);
void releaseSound(int soundSourceIndex);
int loadWavFile(const char* fileName);
double getSampleRate() const;
};
#endif //B3_SOUND_ENGINE_H

View file

@ -0,0 +1,233 @@
#include "b3SoundSource.h"
#define MY2PI (2. * 3.14159265)
#include <math.h>
#include "Bullet3Common/b3FileUtils.h"
#include "b3ReadWavFile.h"
#include "b3ADSR.h"
#include "b3Sound_C_Api.h"
struct b3SoundOscillator
{
int m_type;
double m_frequency;
double m_amplitude;
double m_phase;
b3WavTicker m_wavTicker;
double sampleSineWaveForm(double sampleRate)
{
while (m_phase >= MY2PI)
m_phase -= MY2PI;
double z = sinf(m_phase);
double sample = m_amplitude * z;
m_phase += MY2PI * (1. / sampleRate) * m_frequency;
return sample;
}
double sampleSawWaveForm(double sampleRate)
{
while (m_phase >= MY2PI)
m_phase -= MY2PI;
double z = 2. * (m_phase) / MY2PI - 1.;
double sample = m_amplitude * z;
m_phase += MY2PI * (1. / sampleRate) * m_frequency;
return sample;
}
void reset()
{
m_phase = 0;
}
b3SoundOscillator()
: m_type(0),
m_frequency(442.),
m_amplitude(1),
m_phase(0)
{
}
};
#define MAX_OSCILLATORS 2
struct b3SoundSourceInternalData
{
b3SoundOscillator m_oscillators[MAX_OSCILLATORS];
b3ADSR m_envelope;
b3ReadWavFile* m_wavFilePtr;
b3SoundSourceInternalData()
: m_wavFilePtr(0)
{
}
};
b3SoundSource::b3SoundSource()
{
m_data = new b3SoundSourceInternalData();
}
b3SoundSource::~b3SoundSource()
{
delete m_data;
}
void b3SoundSource::setADSR(double attack, double decay, double sustain, double release)
{
m_data->m_envelope.setValues(attack, decay, sustain, release);
}
bool b3SoundSource::computeSamples(double* sampleBuffer, int numSamples, double sampleRate)
{
double* outputSamples = sampleBuffer;
int numActive = 0;
for (int i = 0; i < numSamples; i++)
{
double samples[MAX_OSCILLATORS] = {0};
double env = m_data->m_envelope.tick();
if (env)
{
for (int osc = 0; osc < MAX_OSCILLATORS; osc++)
{
if (m_data->m_oscillators[osc].m_type == 0)
{
samples[osc] += env * m_data->m_oscillators[osc].sampleSineWaveForm(sampleRate);
numActive++;
}
if (m_data->m_oscillators[osc].m_type == 1)
{
samples[osc] += env * m_data->m_oscillators[osc].sampleSawWaveForm(sampleRate);
numActive++;
}
if (m_data->m_oscillators[osc].m_type == 128)
{
int frame = 0;
double data = env * m_data->m_oscillators[osc].m_amplitude * m_data->m_wavFilePtr->tick(frame, &m_data->m_oscillators[osc].m_wavTicker);
samples[osc] += data;
numActive++;
}
}
}
else
{
for (int osc = 0; osc < MAX_OSCILLATORS; osc++)
{
if (m_data->m_oscillators[osc].m_type == 128)
{
m_data->m_oscillators[osc].m_wavTicker.finished_ = true;
}
}
}
//sample *= 1./double(MAX_OSCILLATORS);
double sampleLeft = samples[0];
double sampleRight = samples[1];
if (sampleLeft != sampleRight)
{
}
*outputSamples++ = sampleRight;
*outputSamples++ = sampleLeft;
}
/* if (m_data->m_flags & looping)
{
for (int osc=0;osc<MAX_OSCILLATORS;osc++)
{
if (m_data->m_oscillators[osc].m_waveIn.isFinished())
m_data->m_oscillators[osc].m_waveIn.reset();
}
}
*/
return numActive > 0;
// return false;
}
int b3SoundSource::getNumOscillators() const
{
return MAX_OSCILLATORS;
}
void b3SoundSource::setOscillatorType(int oscillatorIndex, int type)
{
m_data->m_oscillators[oscillatorIndex].m_type = type;
}
void b3SoundSource::setOscillatorFrequency(int oscillatorIndex, double frequency)
{
m_data->m_oscillators[oscillatorIndex].m_frequency = frequency;
}
void b3SoundSource::setOscillatorAmplitude(int oscillatorIndex, double amplitude)
{
m_data->m_oscillators[oscillatorIndex].m_amplitude = amplitude;
}
void b3SoundSource::setOscillatorPhase(int oscillatorIndex, double phase)
{
m_data->m_oscillators[oscillatorIndex].m_phase = phase;
}
bool b3SoundSource::isAvailable() const
{
//available if ADSR is idle and wavticker is finished
return m_data->m_envelope.isIdle();
}
void b3SoundSource::startSound(bool autoKeyOff)
{
if (m_data->m_envelope.isIdle())
{
for (int osc = 0; osc < MAX_OSCILLATORS; osc++)
{
m_data->m_oscillators[osc].reset();
if (m_data->m_oscillators[osc].m_type == B3_SOUND_SOURCE_WAV_FILE) // .m_wavTicker.finished_)
{
//test reverse playback of wav
//m_data->m_oscillators[osc].m_wavTicker.rate_ *= -1;
if (m_data->m_oscillators[osc].m_wavTicker.rate_ < 0)
{
m_data->m_oscillators[osc].m_wavTicker.time_ = m_data->m_wavFilePtr->getNumFrames() - 1.;
}
else
{
m_data->m_oscillators[osc].m_wavTicker.time_ = 0.f;
}
m_data->m_oscillators[osc].m_wavTicker.finished_ = false;
}
}
}
m_data->m_envelope.keyOn(autoKeyOff);
}
void b3SoundSource::stopSound()
{
m_data->m_envelope.keyOff();
}
bool b3SoundSource::setWavFile(int oscillatorIndex, b3ReadWavFile* wavFilePtr, int sampleRate)
{
{
m_data->m_wavFilePtr = wavFilePtr;
m_data->m_oscillators[oscillatorIndex].m_wavTicker = m_data->m_wavFilePtr->createWavTicker(sampleRate);
// waveIn.openFile(resourcePath);
double rate = 1.0;
// rate = waveIn.getFileRate() / stkSampleRate;
// waveIn.setRate( rate );
// waveIn.ignoreSampleRateChange();
// Find out how many channels we have.
// int channels = waveIn.channelsOut();
// m_data->m_oscillators[oscillatorIndex].m_frames.resize( 1, channels );
m_data->m_oscillators[oscillatorIndex].m_type = 128;
return true;
}
return false;
}

View file

@ -0,0 +1,31 @@
#ifndef B3_SOUND_SOURCE_H
#define B3_SOUND_SOURCE_H
#include "b3Sound_C_Api.h"
class b3SoundSource
{
struct b3SoundSourceInternalData* m_data;
public:
b3SoundSource();
virtual ~b3SoundSource();
virtual bool computeSamples(double* sampleBuffer, int numSamples, double sampleRate);
int getNumOscillators() const;
void setOscillatorType(int oscillatorIndex, int type);
void setOscillatorFrequency(int oscillatorIndex, double frequency);
void setOscillatorAmplitude(int oscillatorIndex, double amplitude);
void setOscillatorPhase(int oscillatorIndex, double phase);
void setADSR(double attackRate, double decayRate, double sustainLevel, double releaseRate);
bool setWavFile(int oscillatorIndex, class b3ReadWavFile* wavFilePtr, int sampleRate);
void startSound(bool autoKeyOff);
void stopSound();
bool isAvailable() const;
};
#endif //B3_SOUND_SOURCE_H

View file

@ -0,0 +1,29 @@
#ifndef B3_SOUND_C_API_H
#define B3_SOUND_C_API_H
//todo: create a sound C-API
//create sound engine
//destroy sound engine
//getSoundSource
//startSound
//releaseSound
//etc
enum B3_SOUND_SOURCE_TYPE
{
B3_SOUND_SOURCE_SINE_OSCILLATOR = 1,
B3_SOUND_SOURCE_SAW_OSCILLATOR,
B3_SOUND_SOURCE_WAV_FILE,
};
#ifdef __cplusplus
extern "C"
{
#endif
#ifdef __cplusplus
}
#endif
#endif ///B3_SOUND_C_API_H

View file

@ -0,0 +1,58 @@
#ifndef B3_SWAP_UTILS_H
#define B3_SWAP_UTILS_H
inline void b3Swap16(unsigned char *ptr)
{
unsigned char val;
// Swap 1st and 2nd bytes
val = *(ptr);
*(ptr) = *(ptr + 1);
*(ptr + 1) = val;
}
inline void b3Swap32(unsigned char *ptr)
{
unsigned char val;
// Swap 1st and 4th bytes
val = *(ptr);
*(ptr) = *(ptr + 3);
*(ptr + 3) = val;
//Swap 2nd and 3rd bytes
ptr += 1;
val = *(ptr);
*(ptr) = *(ptr + 1);
*(ptr + 1) = val;
}
inline void b3Swap64(unsigned char *ptr)
{
unsigned char val;
// Swap 1st and 8th bytes
val = *(ptr);
*(ptr) = *(ptr + 7);
*(ptr + 7) = val;
// Swap 2nd and 7th bytes
ptr += 1;
val = *(ptr);
*(ptr) = *(ptr + 5);
*(ptr + 5) = val;
// Swap 3rd and 6th bytes
ptr += 1;
val = *(ptr);
*(ptr) = *(ptr + 3);
*(ptr + 3) = val;
// Swap 4th and 5th bytes
ptr += 1;
val = *(ptr);
*(ptr) = *(ptr + 1);
*(ptr + 1) = val;
}
#endif //B3_SWAP_UTILS_H

View file

@ -0,0 +1,276 @@
// b3WriteWavFile is copied from Stk::FileWvOut/FileWrite
// See also https://github.com/thestk/stk
// by Perry R. Cook and Gary P. Scavone, 1995--2014.
#include "b3WriteWavFile.h"
#include "Bullet3Common/b3AlignedObjectArray.h"
#include "b3SwapUtils.h"
#define B3_FLOAT32 32
#define B3_FLOAT64 64
// WAV header structure. See
// http://www-mmsp.ece.mcgill.ca/documents/audioformats/WAVE/Docs/rfc2361.txt
// for information regarding format codes.
struct b3WaveHeader
{
char riff[4]; // "RIFF"
int fileSize; // in bytes
char wave[4]; // "WAVE"
char fmt[4]; // "fmt "
int chunkSize; // in bytes (16 for PCM)
union {
signed short formatCode; // 1=PCM, 2=ADPCM, 3=IEEE float, 6=A-Law, 7=Mu-Law
unsigned short uformatCode;
};
signed short nChannels; // 1=mono, 2=stereo
int sampleRate;
int bytesPerSecond;
signed short bytesPerSample; // 2=16-bit mono, 4=16-bit stereo
signed short bitsPerSample;
signed short cbSize; // size of extension
signed short validBits; // valid bits per sample
int channelMask; // speaker position mask
char subformat[16]; // format code and GUID
char fact[4]; // "fact"
int factSize; // fact chunk size
int frames; // sample frames
};
struct b3WriteWavFileInternalData
{
FILE *m_file;
int m_numChannels;
int m_sampleRate;
int m_dataType; // single precision 32bit float, 64bit double
bool m_byteswap;
int m_frameCounter;
int m_bufferIndex;
int m_bufferSize;
bool m_clipped;
bool m_isMachineLittleEndian;
b3AlignedObjectArray<float> m_floatBuffer;
b3AlignedObjectArray<double> m_doubleBuffer;
b3WriteWavFileInternalData()
: m_file(0),
m_numChannels(0),
m_dataType(B3_FLOAT32),
m_byteswap(false),
m_frameCounter(0),
m_bufferIndex(0),
m_bufferSize(1024),
m_clipped(false)
{
m_floatBuffer.reserve(m_bufferSize);
m_doubleBuffer.reserve(m_bufferSize);
m_isMachineLittleEndian = b3MachineIsLittleEndian();
}
};
b3WriteWavFile::b3WriteWavFile()
{
m_data = new b3WriteWavFileInternalData();
}
b3WriteWavFile::~b3WriteWavFile()
{
closeWavFile();
delete m_data;
}
bool b3WriteWavFile::setWavFile(std::string fileName, int sampleRate, int numChannels, bool useDoublePrecision)
{
m_data->m_numChannels = numChannels;
m_data->m_sampleRate = sampleRate;
if (useDoublePrecision)
{
m_data->m_dataType = B3_FLOAT64;
}
else
{
m_data->m_dataType = B3_FLOAT32;
}
if (fileName.find(".wav") == std::string::npos)
fileName += ".wav";
m_data->m_file = fopen(fileName.c_str(), "wb");
if (!m_data->m_file)
{
return false;
}
struct b3WaveHeader hdr = {{'R', 'I', 'F', 'F'}, 44, {'W', 'A', 'V', 'E'}, {'f', 'm', 't', ' '}, 16, 1, 1, sampleRate, 0, 2, 16, 0, 0, 0, {'\x01', '\x00', '\x00', '\x00', '\x00', '\x00', '\x10', '\x00', '\x80', '\x00', '\x00', '\xAA', '\x00', '\x38', '\x9B', '\x71'}, {'f', 'a', 'c', 't'}, 4, 0};
hdr.nChannels = (signed short)m_data->m_numChannels;
if (m_data->m_dataType == B3_FLOAT32)
{
hdr.formatCode = 3;
hdr.bitsPerSample = 32;
}
else if (m_data->m_dataType == B3_FLOAT64)
{
hdr.formatCode = 3;
hdr.bitsPerSample = 64;
}
hdr.bytesPerSample = (signed short)(m_data->m_numChannels * hdr.bitsPerSample / 8);
hdr.bytesPerSecond = (int)(hdr.sampleRate * hdr.bytesPerSample);
unsigned int bytesToWrite = 36;
if (m_data->m_numChannels > 2 || hdr.bitsPerSample > 16)
{ // use extensible format
bytesToWrite = 72;
hdr.chunkSize += 24;
hdr.uformatCode = 0xFFFE;
hdr.cbSize = 22;
hdr.validBits = hdr.bitsPerSample;
signed short *subFormat = (signed short *)&hdr.subformat[0];
if (m_data->m_dataType == B3_FLOAT32 || m_data->m_dataType == B3_FLOAT64)
*subFormat = 3;
else
*subFormat = 1;
}
m_data->m_byteswap = false;
if (!m_data->m_isMachineLittleEndian)
{
m_data->m_byteswap = true;
b3Swap32((unsigned char *)&hdr.chunkSize);
b3Swap16((unsigned char *)&hdr.formatCode);
b3Swap16((unsigned char *)&hdr.nChannels);
b3Swap32((unsigned char *)&hdr.sampleRate);
b3Swap32((unsigned char *)&hdr.bytesPerSecond);
b3Swap16((unsigned char *)&hdr.bytesPerSample);
b3Swap16((unsigned char *)&hdr.bitsPerSample);
b3Swap16((unsigned char *)&hdr.cbSize);
b3Swap16((unsigned char *)&hdr.validBits);
b3Swap16((unsigned char *)&hdr.subformat[0]);
b3Swap32((unsigned char *)&hdr.factSize);
}
char data[4] = {'d', 'a', 't', 'a'};
int dataSize = 0;
if (fwrite(&hdr, 1, bytesToWrite, m_data->m_file) != bytesToWrite)
return false;
if (fwrite(&data, 4, 1, m_data->m_file) != 1)
return false;
if (fwrite(&dataSize, 4, 1, m_data->m_file) != 1)
return false;
return true;
}
void b3WriteWavFile::closeWavFile()
{
if (m_data->m_file == 0)
return;
flushData(1);
int bytesPerSample = 1;
if (m_data->m_dataType == B3_FLOAT32)
bytesPerSample = 4;
else if (m_data->m_dataType == B3_FLOAT64)
bytesPerSample = 8;
bool useExtensible = false;
int dataLocation = 40;
if (bytesPerSample > 2 || m_data->m_numChannels > 2)
{
useExtensible = true;
dataLocation = 76;
}
int bytes = (int)(m_data->m_frameCounter * m_data->m_numChannels * bytesPerSample);
if (bytes % 2)
{ // pad extra byte if odd
signed char sample = 0;
fwrite(&sample, 1, 1, m_data->m_file);
}
#ifndef __LITTLE_ENDIAN__
b3Swap32((unsigned char *)&bytes);
#endif
fseek(m_data->m_file, dataLocation, SEEK_SET); // jump to data length
fwrite(&bytes, 4, 1, m_data->m_file);
bytes = (int)(m_data->m_frameCounter * m_data->m_numChannels * bytesPerSample + 44);
if (useExtensible) bytes += 36;
#ifndef __LITTLE_ENDIAN__
b3Swap32((unsigned char *)&bytes);
#endif
fseek(m_data->m_file, 4, SEEK_SET); // jump to file size
fwrite(&bytes, 4, 1, m_data->m_file);
if (useExtensible)
{ // fill in the "fact" chunk frames value
bytes = (int)m_data->m_frameCounter;
#ifndef __LITTLE_ENDIAN__
b3Swap32((unsigned char *)&bytes);
#endif
fseek(m_data->m_file, 68, SEEK_SET);
fwrite(&bytes, 4, 1, m_data->m_file);
}
fclose(m_data->m_file);
m_data->m_file = 0;
}
void b3WriteWavFile::tick(double *frames, int numFrames)
{
int iFrames = 0;
int j, nChannels = m_data->m_numChannels;
for (int i = 0; i < numFrames; i++)
{
for (j = 0; j < nChannels; j++)
{
double sample = frames[iFrames++];
if (sample < -1.)
{
sample = -1.;
m_data->m_clipped = true;
}
if (sample > 1)
{
sample = 1.;
m_data->m_clipped = true;
}
if (m_data->m_dataType == B3_FLOAT32)
{
m_data->m_floatBuffer.push_back((float)sample);
}
else
{
m_data->m_doubleBuffer.push_back(sample);
}
flushData(m_data->m_bufferSize);
}
m_data->m_frameCounter++;
}
}
void b3WriteWavFile::flushData(int bufferSize)
{
if (m_data->m_dataType == B3_FLOAT32)
{
if (m_data->m_floatBuffer.size() >= bufferSize)
{
fwrite(&m_data->m_floatBuffer[0], sizeof(float), m_data->m_floatBuffer.size(), m_data->m_file);
m_data->m_floatBuffer.resize(0);
}
}
else
{
if (m_data->m_doubleBuffer.size() >= bufferSize)
{
fwrite(&m_data->m_doubleBuffer[0], sizeof(double), m_data->m_doubleBuffer.size(), m_data->m_file);
m_data->m_doubleBuffer.resize(0);
}
}
}

View file

@ -0,0 +1,29 @@
#ifndef B3_WRITE_WAV_FILE_H
#define B3_WRITE_WAV_FILE_H
// b3WriteWavFile is copied from Stk::FileWvOut/FileWrite
// See also https://github.com/thestk/stk
// by Perry R. Cook and Gary P. Scavone, 1995--2014.
#include <string>
class b3WriteWavFile
{
void incrementFrame(void);
void flush();
struct b3WriteWavFileInternalData* m_data;
void flushData(int bufferSize);
public:
b3WriteWavFile();
virtual ~b3WriteWavFile();
bool setWavFile(std::string fileName, int sampleRate, int numChannels, bool useDoublePrecision = true);
void closeWavFile();
void tick(double* values, int numValues);
};
#endif //B3_WRITE_WAV_FILE_H

View file

@ -0,0 +1,41 @@
project "App_TinyAudioExample"
language "C++"
kind "ConsoleApp"
includedirs {
".",
"../../src",
}
defines {"B3_USE_STANDALONE_EXAMPLE", "__STK_REALTIME__"}
files {
"**.cpp",
"**.h",
"../StandaloneMain/main_console_single_example.cpp",
"../Utils/b3ResourcePath.cpp"
}
links {"Bullet3Common"}
if os.is("Windows") then
links {"winmm","Wsock32","dsound"}
defines {"WIN32","__WINDOWS_MM__","__WINDOWS_DS__"}
end
if os.is("Linux") then initX11()
defines {"__OS_LINUX__","__LINUX_ALSA__"}
links {"asound","pthread"}
end
if os.is("MacOSX") then
links{"Cocoa.framework"}
links{"CoreAudio.framework", "coreMIDI.framework", "Cocoa.framework"}
defines {"__OS_MACOSX__", "__MACOSX_CORE__"}
end