#include "sound.hpp" #include #include #include #include #include #include #include #include "util.hpp" // for dumping sound #include namespace sound { typedef struct { PaStream *stream; unsigned int framesPerBuffer; unsigned int sampleRate; unsigned int sound_uid_counter; // constant unsigned int maxNumSounds; // num active sonuds unsigned int highestSoundIndex; // pointer to array of sound handles that has this number of active sounds SoundHandle **sounds; bool dumping; const char *dump_filename; SNDFILE *outfile; SF_INFO sfinfo; bool init; } SoundContext; // singleton so no arguments must be supplied for play() functions etc. static SoundContext context; static double startup_time = -1.0; static double global_time = -1.0;; static double timestamp_double(void) { struct timeval now; gettimeofday(&now, NULL); //printf("%d %d\n", (int) now.tv_sec, (int) now.tv_usec); double t = now.tv_sec; t += now.tv_usec / 1000000.0f; //printf("%f\n", t); return t; } const char *stringFromEnvelope(enum EnvelopeState envelope) { switch(envelope) { case New: return "New"; case Wait: return "Wait"; case Rise: return "Rise"; case BeginOvershoot: return "BeginOvershoot"; case EndOvershoot: return "EndOvershoot"; case Decay: return "Decay"; case Hold: return "Hold"; case Done: return "Done"; default: assert(false); return ""; } } static void checkEnvelopeState(SoundHandle *handle, enum EnvelopeState next) { if (handle == NULL) { return; } if (handle->envelope != next) { #if 1 printf("[sound] global time %4.3f #%d (%s) t=%f %5s -> %5s\n", global_time, handle->uid, handle->name, handle->time, stringFromEnvelope(handle->envelope), stringFromEnvelope(next)); #endif handle->envelope = next; } } void stopDumpingWav(void) { if (!context.init || !context.dumping) { return; } int err = sf_close(context.outfile); context.outfile = NULL; context.dumping = false; if (err < 0) { printf("warning: stop dumping wav returned error: %d\n", err); } else { printf("sucessfully stopped dumping to file %s\n", context.dump_filename); } } static bool dumpSamples(float *samples, size_t numSamples) { // TODO: dumping in mono? if (!context.init || !context.dumping) { printf("warning: can't dump samples: context not initialized or not currently dumping!\n"); return false; } sf_writef_float(context.outfile, samples, numSamples); sf_write_sync(context.outfile); // force writing return true; } bool startDumpingWav(const char *filename) { if (!context.init) { return false; } if (context.dumping) { printf("warning: already dumping to %s!\n", context.dump_filename); return false; } context.sfinfo.channels = 2; context.sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; context.sfinfo.samplerate = context.sampleRate; context.outfile = sf_open(filename, SFM_WRITE, &context.sfinfo); if (context.outfile == NULL) { printf("Unable to dump to output file %s.\n", filename); sf_perror(NULL); return false; }; //printf("start dumping wav to file %s\n", filename); context.dump_filename = filename; context.dumping = true; return true; } static float amplitudeForTime(SoundHandle *handle) { //float amplitude = handle->amplitude; float amplitude = 1.0; float t = handle->time; if (t < handle->_riseTime) { // linear increase float a = t / handle->_riseTime; if (handle->_relativeOvershootTime == 0.0) { checkEnvelopeState(handle, Rise); } else { // // |--overshoot-up-| |-overshoot-down-| // |-------------overshoot------------| // |-----------------rise-time -----------------| // make it raise to full level earlier const float overshoot = 2.0*handle->_relativeOvershootTime; a *= 1.0+overshoot; // compensate the overshoot during last r/2 time to normal level // (1.0) by inversing slope if (a < 1.0) { checkEnvelopeState(handle, Rise); } else if (a < 1.0 + (overshoot/2.0)) { checkEnvelopeState(handle, BeginOvershoot); } else { a = 2.0 + overshoot - a; checkEnvelopeState(handle, EndOvershoot); } } amplitude *= a; //printf("amplitude rising: %f\n", a); } else { t -= handle->_riseTime; if (t < handle->_holdTime) { // stays constant during this time checkEnvelopeState(handle, Hold); } else { t -= handle->_holdTime; if (t < handle->_decayTime) { // linear decrease float a = 1.0 - (t / handle->_decayTime); //printf("amplitude decaying : %f\n", a); amplitude *= a; checkEnvelopeState(handle, Decay); } else { bool switchToDone = false; t -= handle->_decayTime; if (t < handle->_waitTime) { // still waiting checkEnvelopeState(handle, Wait); } else if (handle->looping && (handle->_loopCount == -1 || handle->_loopCount > 0)) { // waited long enough to restart // TODO: dont waste time! does stuff stay synchronized // over a long period of time? //handle->time = 0.0; handle->time -= handle->_riseTime + handle->_waitTime + handle->_holdTime + handle->_decayTime; if (handle->_loopCount != -1) { handle->_loopCount--; //printf("decrement loopCount to %d\n", handle->_loopCount); if (handle->_loopCount == 0) { switchToDone = true; } } } else { switchToDone = true; } if (switchToDone) { // zero checkEnvelopeState(handle, Done); handle->_done = true; return 0.0; } amplitude = 0.0; } } } //int phase = handle->envelope; //printf("%f, %d, %f\n", handle->time, phase, amplitude); if (handle->minAmplitude != 0.0) { amplitude = handle->minAmplitude + amplitude/(1.0-handle->minAmplitude); } amplitude *= handle->amplitude; return amplitude; } // TODO: support stereo fully // args: // n: n-th last history value static float historyValue(SoundHandle *handle, size_t n) { if (handle == NULL) { return 0.0; } if (handle->history == NULL) { return 0.0; } if (n >= handle->numHistorySamples) { // TODO clear history when looping? // or add zero samples? printf("warning: try to access more history values than saved.\n"); return 0.0; } return handle->history[2 * ((handle->lastHistorySample+n) % handle->numHistorySamples)]; } void saveHistoryValue(SoundHandle *handle, float v) { if (handle == NULL || handle->history == NULL) { return; } handle->history[2*handle->lastHistorySample] = v; handle->lastHistorySample = (handle->lastHistorySample+1) % handle->numHistorySamples; } float sumHistory(SoundHandle *handle, float initialWeight, float v) { float result = initialWeight * v; float totalWeight = initialWeight; size_t i; for (i=0; inumHistorySamples; i++) { float weight = 1.0f / (float) (1+i); //float weight = 1.0f; result += weight * historyValue(handle, i); totalWeight += weight; } result /= totalWeight; return result; } // return false if playing done. static bool advanceSound(SoundHandle *handle, float *out, unsigned int framesPerBuffer) { assert(handle != NULL); assert(framesPerBuffer > 0); assert(out != NULL); if (handle == NULL || framesPerBuffer == 0 || out == NULL) { return false; } // TODO: add length to sounds // TODO: find out the period, write just one period and then copy the // values. //float dt = handle->freq / context.sampleRate; // TODO: divide into three parts for envelops // because they are linear in the phases so interpolating between that // should be made easier // advance parameters for testing switch(handle->_type) { //float div = 10.0; //float t = (((int)(100*handle->time/div)) % 101) / 100.0; //handle->leakage = 0.5+0.5*sin(2.0*M_PI*t); default: break; } const float dt = 1.0 / context.sampleRate; const float ds = 2.0 * M_PI * handle->freq * dt; // XXX too long range when becomes done unsigned int i; for (i=0; itime += dt; // TODO: save this state too? if (handle->time < 0.0) { checkEnvelopeState(handle, Wait); // this sound can not yet be heard continue; } // TODO: add phase as argument. float v; switch (handle->_type) { case SoundFrequency: handle->state += ds; v = sin(handle->state); break; case SoundWhiteNoise: v = util::randf_m1_1(); break; case SoundPinkNoise: //v = (historyValue(handle, 0) + Randf_m1_1()) / 2.0f; //saveHistoryValue(handle, v); v = (handle->state + util::randf_m1_1()) / 2.0f; handle->state = v; break; case SoundBrownianNoise: // this is created by integrating white noise. { float white = util::randf_m1_1(); float p = handle->leakage * handle->state; float s = handle->scaling * white; float z = p + s; if (handle->skipDirectionIfClamping && fabsf(z) >= 1.0f) { z = p - s; } handle->state = z; v = z; } //v = sumHistory(handle, handle->freq, randf_m1_1()); //v = sumHistory(handle, 0.1, randf_m1_1()); //v = 0.2*historyValue(handle, 1) + 0.3*historyValue(handle, 0) + 0.5*randf_m1_1(); //v = (historyValue(handle, 1) + historyValue(handle, 0) + randf_m1_1()) / 3.0; //saveHistoryValue(handle, v); #if 0 { float a = 0.8; v = a*handle->state + (1.0-a)*randf_m1_1(); handle->state = v; } #endif break; default: printf("warning: unimplemented sound type for #%d: %d\n", handle->uid, handle->_type); continue; } if (handle->_useEnvelope) { v *= amplitudeForTime(handle); if (handle->_done) { return false; } } else { v *= handle->amplitude; } // add values from this sound te to current sound level *out++ += v; *out++ += v; } return true; } /* This routine will be called by the PortAudio engine when audio is needed. It may called at interrupt level on some machines so don't do anything that could mess up the system like calling malloc() or free(). */ static int sound_callback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { (void) inputBuffer; (void) timeInfo; (void) statusFlags; (void) userData; // update time double now = timestamp_double(); // should not happen. just in case. global_time = now - startup_time; //printf("global time raw: %f\n", global_time); if (global_time < 0.0) { global_time = 0.0; } // we have 32 bit float format so cast here float *out = (float *) outputBuffer; // set all to zero unsigned int i; for (i=0; i_done) { #if 0 printf("play sound 0x%x with loop=%d, sample=%d, freq=%f amplitude=%f\n", (void*) handle, handle->looping, (unsigned int) handle->_currentSample, handle->freq, handle->amplitude); #endif if (!advanceSound(handle, out, framesPerBuffer)) { handle->_done = true; } } // TODO: when we should not free in the loop, where else? if (handle->_done) { //printf("[sound] %f freeing sound #%d handle 0x%p\n", global_time, handle->uid, handle); context.sounds[i] = NULL; if (!handle->keep_when_done) { deleteSound(handle); } } } // make sure all -1.0 >= x <= 1 #if 0 out = outputBuffer; for (i=0; i<2*framesPerBuffer; i++) { if (*out < -1.0) { *out = -1.0; } else if (*out > 1.0) { *out = 1.0; } } #endif if (context.dumping) { dumpSamples((float *) outputBuffer, framesPerBuffer); } // TODO: check! #if 0 // apply post-processing out = outputBuffer; // advance all sounds for (i=0; iname); } #endif } /* Open an audio I/O stream. */ err = Pa_OpenDefaultStream( &context.stream, 0, /* no input channels */ 2, /* stereo output */ paFloat32, /* 32 bit floating point output */ context.sampleRate, /* sample rate */ context.framesPerBuffer, /* frames per buffer */ sound_callback, /* this is your callback function */ NULL); /* callback function pointer */ if (err != paNoError) { printf("error: opening default stream: %s\n", Pa_GetErrorText(err)); return false; } err = Pa_StartStream(context.stream); if (err != paNoError) { printf("error: starting default stream failed: %s\n", Pa_GetErrorText(err)); return false; } // clear that //sound_context.num_active_handles = 0; //sound_context.num_cached = 0; context.init = true; return true; } static void allocateHistory(SoundHandle *handle, size_t numHistorySamples) { handle->lastHistorySample = 0; if (numHistorySamples == 0) { handle->numHistorySamples = 0; handle->history = NULL; return; } handle->numHistorySamples = numHistorySamples; handle->history = (float *) calloc(sizeof(float)*2, handle->numHistorySamples); // clear all size_t i; for (i=0; i<2*handle->numHistorySamples; i++) { handle->history[i] = 0.0f; } } SoundHandle *playFrequency(float freq, float amplitude) { SoundHandle *handle = playSound(SoundFrequency, amplitude); if (handle != NULL) { handle->freq = freq; //handle->looping = true; } return handle; } SoundHandle *playSound(enum Sound type, float amplitude) { if (!context.init) { //printf("warning: called playSound() with no initialized sound system!\n"); return NULL; } size_t nextFreeIndex = 0; while(nextFreeIndex < context.highestSoundIndex) { if (context.sounds[nextFreeIndex] == NULL) { // got an empyt sound to use break; } nextFreeIndex++; } if (nextFreeIndex >= context.maxNumSounds) { printf("error: can't create more sounds, got maximum of: %d\n", context.maxNumSounds); return NULL; } SoundHandle *handle = (SoundHandle *) calloc(1, sizeof(SoundHandle)); handle->envelope = New; handle->amplitude = amplitude; handle->minAmplitude = 0.0; handle->freq = 1000; handle->looping = false; handle->_loopCount = 0; handle->_type = type; handle->time = 0.0; handle->keep_when_done = false; handle->state = 0.0; handle->leakage = 0.0; handle->scaling = 0.0; handle->_done = false; handle->skipDirectionIfClamping = false; handle->_useEnvelope = false; handle->_riseTime = 0.0; handle->_relativeOvershootTime = 0.0; handle->_decayTime = 0.0; handle->_holdTime = 0.0; handle->_waitTime = 0.0; handle->decayType = SoundDecayLinear; handle->riseType = SoundDecayLinear; handle->name = ""; // filters may to access values from their history int numHistoryValues = 0; switch(type) { // pink/white/brown noise need to save only one sample, state is enough // for that. case SoundBrownianNoise: // something like lowpass filter constant. // lower value == lower freq. noise // values that audacity uses //handle->leakage = 0.997; //handle->scaling = 0.05; handle->leakage = (context.sampleRate - 144.0) / context.sampleRate; handle->scaling = 9.0 / sqrt(context.sampleRate); //printf("computed leakage and scaling: %f %F\n", handle->leakage, handle->scaling); //handle->leakage = 0.7; //handle->scaling = 0.2; handle->skipDirectionIfClamping = true; break; case SoundPinkNoise: break; case SoundWhiteNoise: break; default: break; } allocateHistory(handle, numHistoryValues); handle->uid = context.sound_uid_counter++; context.sounds[nextFreeIndex] = handle; if (nextFreeIndex == context.highestSoundIndex) { context.highestSoundIndex++; } return handle; } bool stopSound(SoundHandle *handle) { if (!context.init) { printf("warning: called stopSound() with no initialized sound system!\n"); return false; } if (handle == NULL) { printf("warning: stopSound() handle is NULL\n"); return false; } if (handle->_done) { printf("warning: stopSound() sound is already stopped\n"); return false; } // TODO: kill with variable handle->_done = true; return true; } void stopAllSounds(void) { if (!context.init) { printf("warning: called stopAllSounds() with no initialized sound system!\n"); } unsigned int i; for (i=0; i_done) { stopSound(context.sounds[i]); } } } void computeEnvelopeUse(SoundHandle *handle) { if (handle == NULL) { return; } // wait time has no influence (yet) handle->_useEnvelope = (handle->_riseTime != 0.0 || handle->_holdTime != 0.0 || handle->_decayTime != 0.0); } bool configureEnvelope(SoundHandle *handle, float rise, float hold, float decay) { if (handle == NULL) { return false; } // TODO: check inf/nan too if (rise < 0.0) { return false; } if (hold < 0.0) { return false; } if (decay < 0.0) { return false; } handle->_riseTime = rise; handle->_holdTime = hold; handle->_decayTime = decay; // set depending on flags computeEnvelopeUse(handle); return true; } bool configureEnvelopeLooping(SoundHandle *handle, float rise, float hold, float decay, float wait) { if (wait < 0.0) { return false; } if (!configureEnvelope(handle, rise, hold, decay)) { return false; } handle->looping = true; handle->_loopCount = -1; handle->_waitTime = wait; return true; } float getRiseDuration(SoundHandle *handle) { if (handle == NULL) { return -1.0; } return handle->_riseTime; } float getHoldDuration(SoundHandle *handle) { if (handle == NULL) { return -1.0; } return handle->_holdTime; } float getDecayDuration(SoundHandle *handle) { if (handle == NULL) { return -1.0; } return handle->_decayTime; } float getwaitTime(SoundHandle *handle) { if (handle == NULL) { return -1.0; } return handle->_waitTime; } void teardownSound(void) { if (!context.init) { printf("[sonud] can't teardown sound when not initialized!\n"); return; } PaError err = Pa_CloseStream(context.stream); if (err != paNoError) { printf("[sound] portaudio failed to close stream: %d\n", err); } err = Pa_Terminate(); if (err != paNoError) { printf("[sound] portaudio failed to terminate: %d\n", err); } if (context.dumping) { stopDumpingWav(); } context.init = false; } bool configureOvershoot(SoundHandle *handle, float relativeOvershootTime) { if (handle == NULL) { return false; } if (relativeOvershootTime < 0.0) { return false; } handle->_relativeOvershootTime = relativeOvershootTime; return true; } void setLoopCount(SoundHandle *handle, int numRepetitions) { if (numRepetitions < -1) { numRepetitions = -1; } handle->_loopCount = numRepetitions; if (numRepetitions == 0) { handle->looping = false; } else if (numRepetitions == -1) { handle->looping = true; } else { handle->looping = true; } } int numActiveSounds(void) { int num = 0; size_t i; for (i=0; i_done = true; // clear from that size_t i; for (i=0; ihistory != NULL) { free(handle->history); } free(handle); } bool configureEnvelopeWait(SoundHandle *handle, float rise, float hold, float decay, float wait) { if (configureEnvelopeLooping(handle, rise, hold, decay, wait)) { handle->looping = false; handle->_loopCount = -1; return true; } else { return false; } } }