diff --git a/examples/ExampleBrowser/ExampleEntries.cpp b/examples/ExampleBrowser/ExampleEntries.cpp index 1a72658c0..fb4128fcd 100644 --- a/examples/ExampleBrowser/ExampleEntries.cpp +++ b/examples/ExampleBrowser/ExampleEntries.cpp @@ -52,6 +52,10 @@ #include "../RoboticsLearning/GripperGraspExample.h" #include "../InverseKinematics/InverseKinematicsExample.h" +#ifdef B3_ENABLE_TINY_AUDIO +#include "../TinyAudio/TinyAudioExample.h" +#endif //B3_ENABLE_TINY_AUDIO + #ifdef ENABLE_LUA #include "../LuaDemo/LuaPhysicsSetup.h" #endif @@ -100,7 +104,6 @@ struct ExampleEntry static ExampleEntry gDefaultExamples[]= { - ExampleEntry(0,"API"), ExampleEntry(1,"Basic Example","Create some rigid bodies using box collision shapes. This is a good example to familiarize with the basic initialization of Bullet. The Basic Example can also be compiled without graphical user interface, as a console application. Press W for wireframe, A to show AABBs, I to suspend/restart physics simulation. Press D to toggle auto-deactivation of the simulation. ", BasicExampleCreateFunc), @@ -307,7 +310,11 @@ static ExampleEntry gDefaultExamples[]= ExampleEntry(1,"TinyRenderer", "Very small software renderer.", TinyRendererCreateFunc), ExampleEntry(1,"Dynamic Texture", "Dynamic updated textured applied to a cube.", DynamicTexturedCubeDemoCreateFunc), - +#ifdef B3_ENABLE_TINY_AUDIO + ExampleEntry(0,"Audio"), + ExampleEntry(1,"Simple Audio","Play some sound", TinyAudioExampleCreateFunc), +#endif + //Extended Tutorials Added by Mobeen ExampleEntry(0,"Extended Tutorials"), diff --git a/examples/ExampleBrowser/premake4.lua b/examples/ExampleBrowser/premake4.lua index f6a7df31f..9b794ce6d 100644 --- a/examples/ExampleBrowser/premake4.lua +++ b/examples/ExampleBrowser/premake4.lua @@ -35,6 +35,28 @@ project "App_BulletExampleBrowser" } end + if _OPTIONS["audio"] then + files {"../TinyAudio/*.cpp"} + defines {"B3_ENABLE_TINY_AUDIO"} + + 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__"} + end + end + if _OPTIONS["lua"] then includedirs{"../ThirdPartyLibs/lua-5.2.3/src"} links {"lua-5.2.3"} @@ -184,6 +206,8 @@ project "BulletExampleBrowserLib" files {"../LuaDemo/LuaPhysicsSetup.cpp"} end + + initOpenGL() initGlew() diff --git a/examples/OpenGLWindow/Win32Window.cpp b/examples/OpenGLWindow/Win32Window.cpp index 022d5c802..3b74808d4 100644 --- a/examples/OpenGLWindow/Win32Window.cpp +++ b/examples/OpenGLWindow/Win32Window.cpp @@ -258,7 +258,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) break; }; } - if (keycode>=0 && sData && sData->m_keyboardCallback)// && ((HIWORD(lParam) & KF_REPEAT) == 0)) + if (keycode>=0 && sData && sData->m_keyboardCallback && ((HIWORD(lParam) & KF_REPEAT) == 0)) { int state = 1; (*sData->m_keyboardCallback)(keycode,state); diff --git a/examples/TinyAudio/TinyAudioExample.cpp b/examples/TinyAudio/TinyAudioExample.cpp index 5df1fb641..e8061bd84 100644 --- a/examples/TinyAudio/TinyAudioExample.cpp +++ b/examples/TinyAudio/TinyAudioExample.cpp @@ -2,21 +2,27 @@ #include "../CommonInterfaces/CommonExampleInterface.h" #include "../CommonInterfaces/CommonGUIHelperInterface.h" -#include "RtAudio.h" -#include "b3AudioListener.h" + + +#include "b3SoundEngine.h" #include "b3SoundSource.h" + + + class TinyAudioExample : public CommonExampleInterface { - b3AudioListener m_listener; - b3SoundSource m_soundA; - RtAudio m_dac; + GUIHelperInterface* m_guiHelper; - int m_soundIndexA; + + b3SoundEngine m_soundEngine; + b3SoundSource* m_soundSource; + public: TinyAudioExample(struct GUIHelperInterface* helper) - :m_guiHelper(helper) + :m_guiHelper(helper), + m_soundSource(0) { } @@ -26,35 +32,21 @@ public: virtual void initPhysics() { - m_soundIndexA = m_listener.addSoundSource(&m_soundA); - RtAudioFormat format = ( sizeof(double) == 8 ) ? RTAUDIO_FLOAT64 : RTAUDIO_FLOAT32; - RtAudio::StreamParameters parameters; - parameters.deviceId = 1;// dac.getDefaultOutputDevice(); - parameters.nChannels = 2; + m_soundEngine.init(); + int sampleRate = m_soundEngine.getSampleRate(); - // 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 = 512; - - unsigned int bufferFrames = RT_BUFFER_SIZE; - int sampleRate = m_listener.getSampleRate(); - - m_dac.openStream( ¶meters, NULL, format, (unsigned int)sampleRate, &bufferFrames, &b3AudioListener::tick, - (void *)m_listener.getTickData()); - - // Install an interrupt handler function. - // (void) signal( SIGINT, finish ); - - m_dac.startStream(); + m_soundSource = new b3SoundSource(); + m_soundSource->setWavFile(1,"wav/xylophone.rosewood.ff.C5B5_1.wav", sampleRate); + m_soundSource->setWavFile(0,"wav/xylophone.rosewood.ff.C5B5_1.wav", sampleRate); + m_soundSource->setOscillatorAmplitude(0,1); + m_soundSource->setOscillatorAmplitude(1,1); + m_soundEngine.addSoundSource(m_soundSource); } virtual void exitPhysics() { - m_dac.closeStream(); - m_listener.removeSoundSource(m_soundIndexA); + m_soundEngine.exit(); } virtual void renderScene() @@ -78,6 +70,37 @@ public: } virtual bool keyboardCallback(int key, int state) { + if (key=='v' || key=='b') + { + + if (state) + { + if (key=='b') + { + m_soundSource->setOscillatorFrequency(0, 442); + m_soundSource->setOscillatorFrequency(1, 442); + } + if (key=='v') + { + m_soundSource->setOscillatorFrequency(0, 2*442); + m_soundSource->setOscillatorFrequency(1, 2*442); + } + } + + + if (state==1) + { + m_soundSource->startSound(); + + + } + else + { + m_soundSource->stopSound(); + } + } + + return false; } diff --git a/examples/TinyAudio/b3ADSR.cpp b/examples/TinyAudio/b3ADSR.cpp new file mode 100644 index 000000000..aefa5db30 --- /dev/null +++ b/examples/TinyAudio/b3ADSR.cpp @@ -0,0 +1,102 @@ +#include "b3ADSR.h" +//ADSR mostly copied/reimplemented from Stk, see +//http://github.com/thestk/stk + +//! ADSR envelope states. +#include + +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.005; + m_sustainLevel = 0.5; + m_state = ADSR_IDLE; +} + +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; + printf("ADSR_ATTACK->ADSR_DECAY\n"); + } + 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; + printf("ADSR_DECAY->ADSR_SUSTAIN\n"); + } + } + else + { + m_value += m_decayRate; // attack target < sustain level + if (m_value >= m_sustainLevel) + { + m_value = m_sustainLevel; + m_state = ADSR_SUSTAIN; + printf("ADSR_DECAY->ADSR_SUSTAIN\n"); + } + } + break; + + case ADSR_RELEASE: + m_value -= m_releaseRate; + if (m_value <= 0.0) + { + m_value = 0.0; + m_state = ADSR_IDLE; + printf("ADSR_RELEASE->ADSR_IDLE\n"); + } + } + + return m_value; +} + +bool b3ADSR::isIdle() const +{ + return true; +} + +void b3ADSR::keyOn() +{ + if (m_target <= 0.0) + m_target = 1.0; + m_state = ADSR_ATTACK; + printf("keyOn::ADSR_ATTACK\n"); +} + +void b3ADSR::keyOff() +{ + m_target = 0.0; + m_state = ADSR_RELEASE; + printf("keyOff::ADSR_RELEASE\n"); + +} diff --git a/examples/TinyAudio/b3ADSR.h b/examples/TinyAudio/b3ADSR.h new file mode 100644 index 000000000..374ec409c --- /dev/null +++ b/examples/TinyAudio/b3ADSR.h @@ -0,0 +1,26 @@ +#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; + +public: + + b3ADSR(); + virtual ~b3ADSR(); + + double tick(); + bool isIdle() const; + void keyOn(); + void keyOff(); +}; + +#endif //B3_ADSR_H \ No newline at end of file diff --git a/examples/TinyAudio/b3AudioListener.cpp b/examples/TinyAudio/b3AudioListener.cpp index cd2d4e569..e2c1377ee 100644 --- a/examples/TinyAudio/b3AudioListener.cpp +++ b/examples/TinyAudio/b3AudioListener.cpp @@ -1,5 +1,7 @@ #include "b3AudioListener.h" #include "b3SoundSource.h" +#include "Bullet3Common/b3Logging.h" +#include "b3WriteWavFile.h" template inline const T& MyMin(const T& a, const T& b) @@ -7,6 +9,7 @@ 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 { @@ -15,10 +18,14 @@ struct b3AudioListenerInternalData b3SoundSource* m_soundSources[MAX_SOUND_SOURCES]; + + b3WriteWavFile m_wavOut2; + b3AudioListenerInternalData() :m_numControlTicks(64), - m_sampleRate(48000) + m_sampleRate(B3_SAMPLE_RATE) { + for (int i=0;im_wavOut2.setWavFile("bulletAudio2.wav",B3_SAMPLE_RATE,2,false); } b3AudioListener::~b3AudioListener() { + m_data->m_wavOut2.closeWavFile(); + delete m_data; } @@ -52,11 +63,14 @@ int b3AudioListener::addSoundSource(b3SoundSource* source) return soundIndex; } -void b3AudioListener::removeSoundSource(int soundSourceIndex) +void b3AudioListener::removeSoundSource(b3SoundSource* source) { - if (soundSourceIndex >=0 && soundSourceIndexm_soundSources[soundSourceIndex] = 0; + if (m_data->m_soundSources[i]==source) + { + m_data->m_soundSources[i] = 0; + } } } @@ -75,16 +89,24 @@ 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) { @@ -103,6 +125,9 @@ int b3AudioListener::tick(void *outputBuffer,void *inputBuffer1,unsigned int nBu { if (data->m_soundSources[i]) { + tempOuts[0] = 0; + tempOuts[1] = 0; + if (data->m_soundSources[i]->computeSamples(tempOuts,1, data->m_sampleRate)) { numActiveSources++; @@ -116,17 +141,25 @@ int b3AudioListener::tick(void *outputBuffer,void *inputBuffer1,unsigned int nBu //simple mixer if (numActiveSources) { - outs[0] *= 1./numActiveSources; - outs[1] *= 1./numActiveSources; + outs[0] *= .3/numActiveSources; + outs[1] *= .3/numActiveSources; } - + *samples++ = outs[0]; *samples++ = outs[1]; + numSamples++; + } nTicks -= counter; } if(nTicks == 0) break; } + + //logging to wav file + if (numSamples) + { + data->m_wavOut2.tick( (double *)outputBuffer,numSamples); + } return 0; } diff --git a/examples/TinyAudio/b3AudioListener.h b/examples/TinyAudio/b3AudioListener.h index 99a8493c8..d3378f4c4 100644 --- a/examples/TinyAudio/b3AudioListener.h +++ b/examples/TinyAudio/b3AudioListener.h @@ -16,12 +16,13 @@ public: double streamTime, unsigned int status, void *dataPointer); int addSoundSource(b3SoundSource* source); - void removeSoundSource(int soundSourceIndex); + void removeSoundSource(b3SoundSource* source); b3AudioListenerInternalData* getTickData(); const b3AudioListenerInternalData* getTickData() const; double getSampleRate() const; + void setSampleRate(double sampleRate); }; diff --git a/examples/TinyAudio/b3ReadWavFile.cpp b/examples/TinyAudio/b3ReadWavFile.cpp new file mode 100644 index 000000000..00dbafcbf --- /dev/null +++ b/examples/TinyAudio/b3ReadWavFile.cpp @@ -0,0 +1,476 @@ + +//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() +{ + m_machineIsLittleEndian = b3MachineIsLittleEndian(); +} +b3ReadWavFile::~b3ReadWavFile() +{ +} + + + +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) goto error; + 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) goto error; + while (strncmp(id, "fmt ", 4)) + { + if (fread(&chunkSize, 4, 1, fd_) != 1) goto error; + if (!m_machineIsLittleEndian) + { + b3Swap32((unsigned char *)&chunkSize); + } + if (fseek(fd_, chunkSize, SEEK_CUR) == -1) goto error; + if (fread(&id, 4, 1, fd_) != 1) goto error; + } + + // Check that the data is not compressed. + unsigned short format_tag; + if (fread(&chunkSize, 4, 1, fd_) != 1) goto error; // Read fmt chunk size. + if (fread(&format_tag, 2, 1, fd_) != 1) goto error; + 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) goto error; + unsigned short extSize; + if (fread(&extSize, 2, 1, fd_) != 1) goto error; + if (!m_machineIsLittleEndian) + { + b3Swap16((unsigned char *)&extSize); + } + if (extSize == 0) goto error; + if (fseek(fd_, 6, SEEK_CUR) == -1) goto error; + if (fread(&format_tag, 2, 1, fd_) != 1) goto error; + if (!m_machineIsLittleEndian) + { + b3Swap16((unsigned char *)&format_tag); + } + if (fseek(fd_, dataOffset_, SEEK_SET) == -1) goto error; + } + 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) goto error; + 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) goto error; + if (!m_machineIsLittleEndian) + { + b3Swap32((unsigned char *)&srate); + } + fileDataRate_ = (double)srate; + + // Determine the data type. + dataType_ = 0; + if (fseek(fd_, 6, SEEK_CUR) == -1) goto error; // Locate bits_per_sample info. + if (fread(&temp, 2, 1, fd_) != 1) goto error; + 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) goto error; + + // Find "data" chunk ... it must come after the "fmt" chunk. + if (fread(&id, 4, 1, fd_) != 1) goto error; + + while (strncmp(id, "data", 4)) + { + if (fread(&chunkSize, 4, 1, fd_) != 1) goto error; + if (!m_machineIsLittleEndian) + { + b3Swap32((unsigned char *)&chunkSize); + } + chunkSize += chunkSize % 2; // chunk sizes must be even + if (fseek(fd_, chunkSize, SEEK_CUR) == -1) goto error; + if (fread(&id, 4, 1, fd_) != 1) goto error; + } + + // Get length of data from the header. + int bytes; + if (fread(&bytes, 4, 1, fd_) != 1) goto error; + 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; + +error: + if (fd_) + fclose(fd_); + + // oStream_ << "FileRead: error reading WAV file (" << fileName << ")."; + return false; +} + + +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); +} \ No newline at end of file diff --git a/examples/TinyAudio/b3ReadWavFile.h b/examples/TinyAudio/b3ReadWavFile.h new file mode 100644 index 000000000..a89656acf --- /dev/null +++ b/examples/TinyAudio/b3ReadWavFile.h @@ -0,0 +1,57 @@ +#ifndef B3_READ_WAV_FILE_H +#define B3_READ_WAV_FILE_H + + +#include "Bullet3Common/b3AlignedObjectArray.h" +#include +#include + +struct b3WavTicker +{ + b3AlignedObjectArray lastFrame_; + bool finished_; + double time_; + double rate_; +}; + + + +class b3ReadWavFile +{ + bool byteswap_; + bool wavFile_; + unsigned long m_numFrames; + unsigned long dataType_; + double fileDataRate_; + FILE *fd_ = 0; + unsigned long dataOffset_; + unsigned int channels_; + bool m_machineIsLittleEndian; +public: + + b3ReadWavFile(); + virtual ~b3ReadWavFile(); + + b3AlignedObjectArray 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 diff --git a/examples/TinyAudio/b3SoundEngine.cpp b/examples/TinyAudio/b3SoundEngine.cpp new file mode 100644 index 000000000..a72821bf3 --- /dev/null +++ b/examples/TinyAudio/b3SoundEngine.cpp @@ -0,0 +1,87 @@ +#include "b3SoundEngine.h" + +#include "RtAudio.h" + +#include "b3AudioListener.h" +#include "b3SoundSource.h" +#include "Bullet3Common/b3AlignedObjectArray.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; + + b3AlignedObjectArray m_soundSources; + +}; + +b3SoundEngine::b3SoundEngine() +{ + m_data = new b3SoundEngineInternalData(); +} + +b3SoundEngine::~b3SoundEngine() +{ + exit(); + delete m_data; +} + +void b3SoundEngine::init() +{ + + 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( ¶meters, 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(); + + for (int i=0;im_soundSources.size();i++) + { + m_data->m_listener.removeSoundSource(m_data->m_soundSources[i]); + delete m_data->m_soundSources[i]; + } + m_data->m_soundSources.clear(); +} + + +void b3SoundEngine::addSoundSource(b3SoundSource* source) +{ + m_data->m_soundSources.push_back(source); + m_data->m_listener.addSoundSource(source); +} + +void b3SoundEngine::removeSoundSource(b3SoundSource* source) +{ + m_data->m_soundSources.remove(source); +} + +int b3SoundEngine::loadWavFile(const char* fileName) +{ + return 0; +} + +double b3SoundEngine::getSampleRate() const +{ + return m_data->m_listener.getSampleRate(); +} + + \ No newline at end of file diff --git a/examples/TinyAudio/b3SoundEngine.h b/examples/TinyAudio/b3SoundEngine.h new file mode 100644 index 000000000..b83dadd9c --- /dev/null +++ b/examples/TinyAudio/b3SoundEngine.h @@ -0,0 +1,29 @@ +#ifndef B3_SOUND_ENGINE_H +#define B3_SOUND_ENGINE_H + +#include "Bullet3Common/b3Scalar.h" + +class b3SoundEngine +{ + struct b3SoundEngineInternalData* m_data; + + public: + + b3SoundEngine(); + virtual ~b3SoundEngine(); + + void init(); + void exit(); + + //int createListener(); + + void addSoundSource(class b3SoundSource* source); + void removeSoundSource(class b3SoundSource* source); + + int loadWavFile(const char* fileName); + + double getSampleRate() const; + +}; + +#endif //B3_SOUND_ENGINE_H diff --git a/examples/TinyAudio/b3SoundSource.cpp b/examples/TinyAudio/b3SoundSource.cpp index 8810768d3..1db3129c7 100644 --- a/examples/TinyAudio/b3SoundSource.cpp +++ b/examples/TinyAudio/b3SoundSource.cpp @@ -3,37 +3,66 @@ #define MY2PI (2.*3.14159265) #include +#include "../Utils/b3ResourcePath.h" +#include "Bullet3Common/b3FileUtils.h" +#include "b3ReadWavFile.h" +#include "b3ADSR.h" + struct b3SoundOscillator { int m_type; + double m_frequency; double m_amplitude; double m_phase; - double m_frequency; + + b3WavTicker m_wavTicker; - double sampleWaveForm(double sampleRate) + + double sampleSineWaveForm(double sampleRate) { while (m_phase >= MY2PI) m_phase -= MY2PI; - double z = sinf(m_phase); + double z = sinf(m_phase); double sample = m_amplitude*z; m_phase += MY2PI * (1./sampleRate) * m_frequency; return sample; } - b3SoundOscillator() - :m_phase(0), - m_amplitude(0.8), - m_frequency(442.) + 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_oscillator; + b3SoundOscillator m_oscillators[MAX_OSCILLATORS]; + b3ADSR m_envelope; + b3ReadWavFile m_wavFile; }; b3SoundSource::b3SoundSource() @@ -48,19 +77,138 @@ b3SoundSource::~b3SoundSource() bool b3SoundSource::computeSamples(double* sampleBuffer, int numSamples, double sampleRate) { + double* outputSamples = sampleBuffer; for (int i=0;im_oscillator.sampleWaveForm(sampleRate); - double sampleLeft = sample; - double sampleRight = sample; + double samples[MAX_OSCILLATORS] ={0}; + + double env = m_data->m_envelope.tick(); + if (env) + { + for (int osc=0;oscm_oscillators[osc].m_type == 0) + { + samples[osc] += env * m_data->m_oscillators[osc].sampleSineWaveForm(sampleRate); + } + + if (m_data->m_oscillators[osc].m_type == 1) + { + samples[osc] += env * m_data->m_oscillators[osc].sampleSawWaveForm(sampleRate); + } + + if (m_data->m_oscillators[osc].m_type == 128) + { + int frame = 0; + double data = m_data->m_oscillators[osc].m_amplitude * m_data->m_wavFile.tick(frame,&m_data->m_oscillators[osc].m_wavTicker); + samples[osc] += data; + } + + } + } + //sample *= 1./double(MAX_OSCILLATORS); + + double sampleLeft = samples[0]; + double sampleRight = samples[1]; - *outputSamples++ = sampleLeft; *outputSamples++ = sampleRight; - + *outputSamples++ = sampleLeft ; } +/* if (m_data->m_flags & looping) + { + for (int osc=0;oscm_oscillators[osc].m_waveIn.isFinished()) + m_data->m_oscillators[osc].m_waveIn.reset(); + } + } + */ return true; // 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; +} + +void b3SoundSource::startSound() +{ + if (m_data->m_envelope.isIdle()) + { + for (int osc=0;oscm_oscillators[osc].m_wavTicker.finished_) + { + m_data->m_oscillators[osc].reset(); + //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_wavFile.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(); +} + +void b3SoundSource::stopSound() +{ + m_data->m_envelope.keyOff(); +} + + +bool b3SoundSource::setWavFile(int oscillatorIndex, const char* fileName, int sampleRate) +{ + char resourcePath[1024]; + + if (b3ResourcePath::findResourcePath(fileName,resourcePath,1024)) + { + + m_data->m_wavFile.getWavInfo(resourcePath); + m_data->m_wavFile.resize(); + m_data->m_wavFile.read(0,true); + m_data->m_wavFile.normalize(1); + m_data->m_oscillators[oscillatorIndex].m_wavTicker = m_data->m_wavFile.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; } \ No newline at end of file diff --git a/examples/TinyAudio/b3SoundSource.h b/examples/TinyAudio/b3SoundSource.h index f07388089..08c90475c 100644 --- a/examples/TinyAudio/b3SoundSource.h +++ b/examples/TinyAudio/b3SoundSource.h @@ -11,6 +11,18 @@ public: 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); + + bool setWavFile(int oscillatorIndex, const char* fileName, int sampleRate); + + void startSound(); + void stopSound(); + }; #endif //B3_SOUND_SOURCE_H diff --git a/examples/TinyAudio/b3SwapUtils.h b/examples/TinyAudio/b3SwapUtils.h new file mode 100644 index 000000000..32fc1a28f --- /dev/null +++ b/examples/TinyAudio/b3SwapUtils.h @@ -0,0 +1,59 @@ +#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 diff --git a/examples/TinyAudio/b3WriteWavFile.cpp b/examples/TinyAudio/b3WriteWavFile.cpp new file mode 100644 index 000000000..2e16d56a9 --- /dev/null +++ b/examples/TinyAudio/b3WriteWavFile.cpp @@ -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 m_floatBuffer; + b3AlignedObjectArray 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) goto error; + if (fwrite(&data, 4, 1, m_data->m_file) != 1) goto error; + if (fwrite(&dataSize, 4, 1, m_data->m_file) != 1) goto error; + + return true; + +error: + return false; +} + +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); + } + } +} \ No newline at end of file diff --git a/examples/TinyAudio/b3WriteWavFile.h b/examples/TinyAudio/b3WriteWavFile.h new file mode 100644 index 000000000..71462a315 --- /dev/null +++ b/examples/TinyAudio/b3WriteWavFile.h @@ -0,0 +1,32 @@ +#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 + +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 ); + void tick( float* values, int numValues ); + +}; + +#endif //B3_WRITE_WAV_FILE_H \ No newline at end of file diff --git a/examples/TinyAudio/premake4.lua b/examples/TinyAudio/premake4.lua index 22e28e0e4..4953d01f3 100644 --- a/examples/TinyAudio/premake4.lua +++ b/examples/TinyAudio/premake4.lua @@ -15,16 +15,19 @@ "**.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__","__LITTLE_ENDIAN__","__WINDOWS_DS__"} + defines {"WIN32","__WINDOWS_MM__","__WINDOWS_DS__"} end if os.is("Linux") then initX11() - defines {"__OS_LINUX__","__LINUX_ALSA__","__LITTLE_ENDIAN__"} + defines {"__OS_LINUX__","__LINUX_ALSA__"} links {"asound","pthread"} end @@ -33,6 +36,6 @@ if os.is("MacOSX") then links{"Cocoa.framework"} links{"CoreAudio.framework", "coreMIDI.framework", "Cocoa.framework"} - defines {"__OS_MACOSX__","__LITTLE_ENDIAN__"} + defines {"__OS_MACOSX__"} end