From 094faf36179fb643fff360cb03ae202c6dd7cd14 Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Wed, 5 Sep 2012 15:56:07 -0700 Subject: [PATCH] Added audio docs. --- .DS_Store | Bin 0 -> 6148 bytes audio.cpp | 99 +++++++++++++++--- audio.h | 77 +++++++++++--- field.cpp | 1 + field.h | 13 +++ interface.xcodeproj/project.pbxproj | 6 ++ .../UserInterfaceState.xcuserstate | Bin 20210 -> 26817 bytes .../xcdebugger/Breakpoints.xcbkptlist | 14 +++ main.cpp | 2 +- particle.cpp | 67 ++++++++++++ particle.h | 30 ++++++ 11 files changed, 281 insertions(+), 28 deletions(-) create mode 100644 .DS_Store create mode 100644 particle.cpp create mode 100644 particle.h diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1b81a9d586ba146115f9796a1435be157470c08f GIT binary patch literal 6148 zcmeI0&1xGl6ot>Voo0qMU?`%SJITENuhWZf<73oM$(Z+Ge)|{0BBsR0nh~y!NpdLd*ODz?$Giz+W^Rp zu|R?pXP86W=^c28@VDE6gn)*)_5E^aq^eA`G)5DEElAYJprPcl%%wIGz?IB(R#yA_ zKdII3Y(3eI;y8MCdXg=aS&mJ;Q04pS%q*vwN~I`1*m5PRx*Utbw!9P7O#1S%F^d-y0na*XuZn;_h|yVt4QG?Xg&WUVr)O-ZAm> zGCMvte80VpW_jG)#yK?RO%;#u92pjavS!zB_JD5S_yz~Ev7i8klc#LRR1 zT=eV5|4%kIlZmRUq*+lQ5D095-z1}_8)yT8z~hGZhn~yrapdDS7wy(Ta29}oPV*+5 z53qiKrF_p)jw}Un$wucM$VGl`&l{2$w5y3C$l4OKFH5Z-k}gX8rC~G$j%-J z1pcP&D!P3BzrjEKe;^Qe#8784os8|Fdp~^E@_G8vH}esGGZA2#B%tN~EPU0BL{7>v KBm#lJ1K~Fyd3K8c literal 0 HcmV?d00001 diff --git a/audio.cpp b/audio.cpp index c05d98ce1d..26ccb19087 100644 --- a/audio.cpp +++ b/audio.cpp @@ -5,6 +5,14 @@ // Created by Seiji Emery on 9/2/12. // Copyright (c) 2012 __MyCompanyName__. All rights reserved. // + +/** + * @file audio.cpp + * Low level audio i/o wrapper around portaudio. + * + * @author Seiji Emery + * + */ #include #include @@ -18,7 +26,25 @@ Audio::AudioData *Audio::data; PaStream *Audio::stream; PaError Audio::err; float Audio::AudioData::inputGain; - +/** + * Audio callback used by portaudio. + * Communicates with Audio via a shared pointer to Audio::data. + * Writes input audio channels (if they exist) into Audio::data->buffer, + multiplied by Audio::data->inputGain. + * Then writes Audio::data->buffer into output audio channels, and clears + the portion of Audio::data->buffer that has been read from for reuse. + * + * @param[in] inputBuffer A pointer to an internal portaudio data buffer containing data read by portaudio. + * @param[out] outputBuffer A pointer to an internal portaudio data buffer to be read by the configured output device. + * @param[in] frames Number of frames that portaudio requests to be read/written. + (Valid size of input/output buffers = frames * number of channels (2) * sizeof data type (float)). + * @param[in] timeInfo Portaudio time info. Currently unused. + * @param[in] statusFlags Portaudio status flags. Currently unused. + * @param[in] userData Pointer to supplied user data (in this case, a pointer to Audio::data). + Used to communicate with external code (since portaudio calls this function from another thread). + * @return Should be of type PaStreamCallbackResult. Return paComplete to end the stream, or paContinue to continue (default). + Can be used to end the stream from within the callback. + */ int audioCallback (const void *inputBuffer, void *outputBuffer, unsigned long frames, @@ -71,10 +97,14 @@ int audioCallback (const void *inputBuffer, return paContinue; } -/* - ** Initializes portaudio, and creates and starts an audio stream. +/** + * Initialize portaudio and start an audio stream. + * Should be called at the beginning of program exection. + * @seealso Audio::terminate + * @return Returns true if successful or false if an error occurred. + Use Audio::getError() to retrieve the error code. */ -void Audio::init() +bool Audio::init() { initialized = true; @@ -87,7 +117,7 @@ void Audio::init() 2, // input channels 2, // output channels paFloat32, // sample format - 44100, // sample rate + 44100, // sample rate (hz) 256, // frames per buffer audioCallback, // callback function (void*)data); // user data to be passed to callback @@ -96,17 +126,22 @@ void Audio::init() err = Pa_StartStream(stream); if (err != paNoError) goto error; - return; + return paNoError; error: fprintf(stderr, "-- Failed to initialize portaudio --\n"); fprintf(stderr, "PortAudio error (%d): %s\n", err, Pa_GetErrorText(err)); - exit(err); // replace w/ return value error code? + initialized = false; + delete[] data; + return false; } -/* - ** Closes the running audio stream, and deinitializes portaudio. +/** + * Close the running audio stream, and deinitialize portaudio. + * Should be called at the end of program execution. + * @return Returns true if the initialization was successful, or false if an error occured. + The error code may be retrieved by Audio::getError(). */ -void Audio::terminate () +bool Audio::terminate () { if (!initialized) return; initialized = false; @@ -121,13 +156,21 @@ void Audio::terminate () err = Pa_Terminate(); if (err != paNoError) goto error; - return; + return true; error: fprintf(stderr, "-- portaudio termination error --\n"); fprintf(stderr, "PortAudio error (%d): %s\n", err, Pa_GetErrorText(err)); - exit(err); + return false; } +/** + * Write a stereo audio stream (float*) to the audio buffer. + * Values should be clamped between -1.0f and 1.0f. + * @param[in] offset Write offset from the start of the audio buffer. + * @param[in] length Length of audio channels to be read. + * @param[in] left Left channel of the audio stream. + * @param[in] right Right channel of the audio stream. + */ void Audio::writeAudio (unsigned int offset, unsigned int length, float *left, float *right) { if (length > data->bufferLength) { fprintf(stderr, "Audio::writeAudio length exceeded (%d). Truncating to %d.\n", length, data->bufferLength); @@ -149,6 +192,14 @@ void Audio::writeAudio (unsigned int offset, unsigned int length, float *left, f } } +/** + * Write a repeated stereo sample (float) to the audio buffer. + * Values should be clamped between -1.0f and 1.0f. + * @param[in] offset Write offset from the start of the audio buffer. + * @param[in] length Length of tone. + * @param[in] left Left component. + * @param[in] right Right component. + */ void Audio::writeTone (unsigned int offset, unsigned int length, float left, float right) { if (length > data->bufferLength) { fprintf(stderr, "Audio::writeTone length exceeded (%d). Truncating to %d.\n", length, data->bufferLength); @@ -170,6 +221,15 @@ void Audio::writeTone (unsigned int offset, unsigned int length, float left, flo } } +/** + * Write a stereo audio stream (float*) to the audio buffer. + * Audio stream is added to the existing contents of the audio buffer. + * Values should be clamped between -1.0f and 1.0f. + * @param[in] offset Write offset from the start of the audio buffer. + * @param[in] length Length of audio channels to be read. + * @param[in] left Left channel of the audio stream. + * @param[in] right Right channel of the audio stream. + */ void Audio::addAudio (unsigned int offset, unsigned int length, float *left, float *right) { if (length > data->bufferLength) { fprintf(stderr, "Audio::addAudio length exceeded (%d). Truncating to %d.\n", length, data->bufferLength); @@ -191,6 +251,15 @@ void Audio::addAudio (unsigned int offset, unsigned int length, float *left, flo } } +/** + * Write a repeated stereo sample (float) to the audio buffer. + * Sample is added to the existing contents of the audio buffer. + * Values should be clamped between -1.0f and 1.0f. + * @param[in] offset Write offset from the start of the audio buffer. + * @param[in] length Length of tone. + * @param[in] left Left component. + * @param[in] right Right component. + */ void Audio::addTone (unsigned int offset, unsigned int length, float left, float right) { if (length > data->bufferLength) { fprintf(stderr, "Audio::writeTone length exceeded (%d). Truncating to %d.\n", length, data->bufferLength); @@ -211,7 +280,11 @@ void Audio::addTone (unsigned int offset, unsigned int length, float left, float } } } - +/** + * Clear a section of the audio buffer. + * @param[in] offset Offset from the start of the audio buffer. + * @param[in] length Length of section to clear. + */ void Audio::clearAudio(unsigned int offset, unsigned int length) { if (length > data->bufferLength) { fprintf(stderr, "Audio::clearAudio length exceeded (%d). Truncating to %d.\n", length, data->bufferLength); diff --git a/audio.h b/audio.h index 2daa5abd94..27915648dc 100644 --- a/audio.h +++ b/audio.h @@ -11,43 +11,78 @@ #include "portaudio.h" -typedef short sample_t; +// Note: main documentation in audio.cpp -int audioCallback (const void *inputBuffer, - void *outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData); - -/* - ** TODO: Docs +/** + * Low level audio interface. + * + * Contains static methods that write to an internal audio buffer, which + is read from by a portaudio callback. + * Responsible for initializing and terminating portaudio. Audio::init() + and Audio::terminate() should be called at the beginning and end of + program execution. */ class Audio { public: - static void init (); - static void terminate (); + // Initializes portaudio. Should be called at the beginning of program execution. + static bool init (); + // Deinitializes portaudio. Should be called at the end of program execution. + static bool terminate (); - // Audio values clamped betewen -1.0f and 1.0f + // Write methods: write to internal audio buffer. static void writeAudio (unsigned int offset, unsigned int length, float *left, float *right); static void addAudio (unsigned int offset, unsigned int length, float *left, float *right); static void writeTone (unsigned int offset, unsigned int length, float left, float right); static void addTone (unsigned int offset, unsigned int length, float left, float right); static void clearAudio (unsigned int offset, unsigned int length); + /** + * Set the audio input gain. (multiplier applied to mic input) + */ static void setInputGain (float gain) { data->inputGain = gain; } + /** + * Get the internal portaudio error code (paNoError if none). + * Use in conjunction with Audio::init() or Audio::terminate(), as it is not + impacted by any other methods. + */ + const PaError getError () { return err; } private: + /** + * Set to true by Audio::init() and false by Audio::terminate(). + * Used to prevent Audio::terminate() from deleting uninitialized memory. + */ static bool initialized; + /** + * Internal audio data. + * Used to communicate with the audio callback code via a shared pointer. + */ static struct AudioData { + /** + * Internal (stereo) audio buffer. + * Written to by Audio I/O methods and the audio callback. + * As this is a ring buffer, it should not be written to directly – thus methods + like Audio::writeAudio are provided. + */ struct BufferFrame{ float l, r; } *buffer; + /** + * Length of the audio buffer. + */ const static unsigned int bufferLength = 1000; + /** + * Current position (start) within the ring buffer. + * Updated by the audio callback. + */ unsigned int bufferPos; + /** + * Audio input gain (multiplier applied to the incoming audio stream). + * Use Audio::setInputGain() to modify this. + */ static float inputGain;// = 1.f; AudioData () : bufferPos(0) { @@ -62,13 +97,27 @@ private: } }*data; + /** + * Internal audio stream handle. + */ static PaStream *stream; + /** + * Internal error code (used only by Audio::init() and Audio::terminate()). + */ static PaError err; Audio (); // prevent instantiation (private constructor) - friend int audioCallback (const void*, void*, unsigned long, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void*); }; +// Audio callback called by portaudio. +int audioCallback (const void *inputBuffer, + void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData); + + #endif diff --git a/field.cpp b/field.cpp index 4b0d2dc86d..29fe7af813 100644 --- a/field.cpp +++ b/field.cpp @@ -103,3 +103,4 @@ void field_render() glEnd(); } + diff --git a/field.h b/field.h index 8867123fdf..9c565861b9 100644 --- a/field.h +++ b/field.h @@ -9,6 +9,8 @@ #ifndef interface_field_h #define interface_field_h +#include + // Field is a lattice of vectors uniformly distributed FIELD_ELEMENTS^(1/3) on side const int FIELD_ELEMENTS = 1000; @@ -18,4 +20,15 @@ int field_value(float *ret, float *pos); void field_render(); void field_add(float* add, float *loc); +class Field { +public: + static void init (); + static int addTo (const glm::vec3 &pos, glm::vec3 &v); + +private: + const static unsigned int fieldSize = 1000; + const static float fieldScale; // defined in cpp – inline const float definitions not allowed in standard C++?! (allowed in C++0x) + static glm::vec3 field[fieldSize]; +}; + #endif diff --git a/interface.xcodeproj/project.pbxproj b/interface.xcodeproj/project.pbxproj index a47dfaf8e4..0a0a767709 100644 --- a/interface.xcodeproj/project.pbxproj +++ b/interface.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ B6BDADE215F44AA5002A07DF /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6BDADD815F444C1002A07DF /* CoreAudio.framework */; }; B6BDADE315F44AB0002A07DF /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6BDADDA15F444C9002A07DF /* AudioToolbox.framework */; }; B6BDADE415F44AC7002A07DF /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6BDADDC15F444D3002A07DF /* AudioUnit.framework */; }; + B6BDAE4415F6BE53002A07DF /* particle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B6BDAE4315F6BE53002A07DF /* particle.cpp */; }; D40BDFD513404BA300B0BE1F /* GLUT.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D40BDFD413404BA300B0BE1F /* GLUT.framework */; }; D40BDFD713404BB300B0BE1F /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D40BDFD613404BB300B0BE1F /* OpenGL.framework */; }; D4EE3BBC15E45FFE00EE4C89 /* SerialInterface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D4EE3BBB15E45FFE00EE4C89 /* SerialInterface.cpp */; }; @@ -46,6 +47,8 @@ B6BDADDA15F444C9002A07DF /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; B6BDADDC15F444D3002A07DF /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; B6BDADDE15F444DB002A07DF /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; + B6BDAE4115F6BE4D002A07DF /* particle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = particle.h; sourceTree = ""; }; + B6BDAE4315F6BE53002A07DF /* particle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = particle.cpp; sourceTree = ""; }; C6859E8B029090EE04C91782 /* test_c_plus.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = test_c_plus.1; sourceTree = ""; }; D40BDFD413404BA300B0BE1F /* GLUT.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GLUT.framework; path = /System/Library/Frameworks/GLUT.framework; sourceTree = ""; }; D40BDFD613404BB300B0BE1F /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = /System/Library/Frameworks/OpenGL.framework; sourceTree = ""; }; @@ -99,6 +102,8 @@ isa = PBXGroup; children = ( 08FB7796FE84155DC02AAC07 /* main.cpp */, + B6BDAE4115F6BE4D002A07DF /* particle.h */, + B6BDAE4315F6BE53002A07DF /* particle.cpp */, D4EE3BC015E746E900EE4C89 /* world.h */, D4EE3BC415EBD90C00EE4C89 /* network.h */, D4EE3BC515EBD93400EE4C89 /* network.cpp */, @@ -189,6 +194,7 @@ D4EE3BC215E761B000EE4C89 /* util.cpp in Sources */, D4EE3BC615EBD93600EE4C89 /* network.cpp in Sources */, B6BDADD415F4085B002A07DF /* audio.cpp in Sources */, + B6BDAE4415F6BE53002A07DF /* particle.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/interface.xcodeproj/project.xcworkspace/xcuserdata/Seiji.xcuserdatad/UserInterfaceState.xcuserstate b/interface.xcodeproj/project.xcworkspace/xcuserdata/Seiji.xcuserdatad/UserInterfaceState.xcuserstate index 9269fb4058fb5ed26c5306db31cadf2f263a3a28..ffea69364a3fb90c1b3bbc5db6276faaa35c8518 100644 GIT binary patch literal 26817 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 literal 20210 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 + + diff --git a/main.cpp b/main.cpp index e99002231c..e657acf0fc 100644 --- a/main.cpp +++ b/main.cpp @@ -762,7 +762,7 @@ void key(unsigned char k, int x, int y) field_add(add, pos); } if (k == 't') { - Audio::writeTone(0, 150, 0.5f, 0.5f); + Audio::writeTone(0, 400, 1.0f, 0.5f); } } diff --git a/particle.cpp b/particle.cpp new file mode 100644 index 0000000000..c5cc15162b --- /dev/null +++ b/particle.cpp @@ -0,0 +1,67 @@ +// +// particle.cpp +// interface +// +// Created by Seiji Emery on 9/4/12. +// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// + +#include "particle.h" + +void ParticleSystem::simulate (float deltaTime) { + for (unsigned int i = 0; i < particleCount; ++i) { + // Move particles + particles[i].position += particles[i].velocity * deltaTime; + + // Add gravity + particles[i].velocity.y -= gravity; + + // Drag: decay velocity + particles[i].velocity *= 0.99; + + // Add velocity from field + //Field::addTo(particles[i].velocity); + //particles[i].velocity += Field::valueAt(particles[i].position); + + if (wrapBounds) { + // wrap around bounds + if (particles[i].position.x > bounds.x) + particles[i].position.x -= bounds.x; + else if (particles[i].position.x < 0.0f) + particles[i].position.x += bounds.x; + + if (particles[i].position.y > bounds.y) + particles[i].position.y -= bounds.y; + else if (particles[i].position.y < 0.0f) + particles[i].position.y += bounds.y; + + if (particles[i].position.z > bounds.z) + particles[i].position.z -= bounds.z; + else if (particles[i].position.z < 0.0f) + particles[i].position.z += bounds.z; + } else { + // Bounce at bounds + if (particles[i].position.x > bounds.x + || particles[i].position.x < 0.f) { + particles[i].velocity.x *= -1; + } + if (particles[i].position.y > bounds.y + || particles[i].position.y < 0.f) { + particles[i].velocity.y *= -1; + } + if (particles[i].position.z > bounds.z + || particles[i].position.z < 0.f) { + particles[i].velocity.z *= -1; + } + } + } +} + + + + + + + + + diff --git a/particle.h b/particle.h new file mode 100644 index 0000000000..68982377cf --- /dev/null +++ b/particle.h @@ -0,0 +1,30 @@ +// +// particle.h +// interface +// +// Created by Seiji Emery on 9/4/12. +// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// + +#ifndef interface_particle_h +#define interface_particle_h + +#include + +class ParticleSystem { +public: + void simulate (float deltaTime); + void draw (); + +private: + struct Particle { + glm::vec3 position, velocity; + } *particles; + unsigned int particleCount; + + glm::vec3 bounds; + const static bool wrapBounds = false; + const static float gravity = 0.0001; +}; + +#endif