1071 lines
31 KiB
C++
1071 lines
31 KiB
C++
#include "sound.hpp"
|
|
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
|
|
#include <portaudio.h>
|
|
|
|
#include "util.hpp"
|
|
|
|
// for dumping sound
|
|
#include <sndfile.h>
|
|
|
|
#include <pthread.h>
|
|
|
|
namespace sound {
|
|
|
|
//struct Slot {
|
|
// bool used;
|
|
// bool usable;
|
|
// bool deleteOnCleanup;
|
|
// SoundHandle *handle;
|
|
//};
|
|
|
|
struct SoundContext {
|
|
PaStream *stream;
|
|
|
|
// this locks access to the context
|
|
pthread_mutex_t mutex;
|
|
|
|
unsigned int framesPerBuffer;
|
|
unsigned int sampleRate;
|
|
|
|
size_t sound_uid_counter;
|
|
|
|
// constant
|
|
size_t maxNumSounds;
|
|
|
|
// index of highest sound or -1, if there are no sounds.
|
|
// so 0 means no sounds, > 0 could mean different things depending on
|
|
// empty sounds.
|
|
volatile ssize_t highestSoundIndex;
|
|
|
|
// pointer to array of sound handles that has this number of active sounds
|
|
//Slot *soundSlots;
|
|
SoundHandle **sounds;
|
|
|
|
bool dumping;
|
|
const char *dump_filename;
|
|
SNDFILE *outfile;
|
|
SF_INFO sfinfo;
|
|
|
|
bool init;
|
|
};
|
|
|
|
// 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 "<error>";
|
|
}
|
|
}
|
|
|
|
static void checkEnvelopeState(SoundHandle *handle, enum EnvelopeState next)
|
|
{
|
|
if (handle == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (handle->envelope != next) {
|
|
#if 1
|
|
printf("[sound] global time %4.3f #%zd (%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) {
|
|
fprintf(stderr, "[sound] warning: stop dumping wav returned error: %d\n", err);
|
|
} else {
|
|
printf("[sound] 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) {
|
|
fprintf(stderr, "[sound] 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("[sound] 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) {
|
|
fprintf(stderr, "[sound] Unable to dump to output file %s.\n", filename);
|
|
sf_perror(NULL);
|
|
return false;
|
|
};
|
|
|
|
printf("[sound] 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("[sound] 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; i<handle->numHistorySamples; 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 on no success
|
|
bool tryLock()
|
|
{
|
|
int ret = pthread_mutex_trylock(&context.mutex);
|
|
return ret == 0;
|
|
}
|
|
|
|
// wait if does not lock
|
|
void lock()
|
|
{
|
|
pthread_mutex_lock(&context.mutex);
|
|
}
|
|
|
|
void unlock()
|
|
{
|
|
pthread_mutex_unlock(&context.mutex);
|
|
}
|
|
|
|
|
|
// 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; i<framesPerBuffer; i++) {
|
|
handle->time += 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("[sound] warning: unimplemented sound type for #%zd: %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<framesPerBuffer; i++) {
|
|
*out++ = 0.0f;
|
|
*out++ = 0.0f;
|
|
}
|
|
}
|
|
|
|
// reset pointer
|
|
out = (float *) outputBuffer;
|
|
|
|
// advance all sounds
|
|
ssize_t i;
|
|
for (i=0; i<context.highestSoundIndex; i++) {
|
|
SoundHandle *handle = context.sounds[i];
|
|
if (handle == NULL) {
|
|
continue;
|
|
}
|
|
if (handle->deleteFlag) {
|
|
continue;
|
|
}
|
|
if (handle->isDeleted) {
|
|
continue;
|
|
}
|
|
|
|
if (!handle->_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 && !handle->deleteFlag) {
|
|
//printf("[sound] %f freeing sound #%d handle 0x%p\n", global_time, handle->uid, handle);
|
|
printf("[sound] %f mark sound #%zd handle 0x%p for deletion\n", global_time, handle->uid, handle);
|
|
//context.sounds[i] = NULL;
|
|
handle->deleteFlag = true;
|
|
//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; i<context.highestSoundIndex; i++) {
|
|
float v = *out;
|
|
v = pow(v, 2.0);
|
|
*out++ = v;
|
|
|
|
v = *out;
|
|
v = pow(v, 2.0);
|
|
*out++ = v;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool initSound(void)
|
|
{
|
|
startup_time = timestamp_double();
|
|
global_time = 0.0;
|
|
|
|
// TODO: make all customizable too
|
|
context.init = false; // for now, set on succes to true
|
|
context.dumping = false;
|
|
context.dump_filename = NULL;
|
|
context.sampleRate = 44800;
|
|
context.framesPerBuffer = 256;
|
|
//context.framesPerBuffer = 1024;
|
|
//context.framesPerBuffer = 100;
|
|
//context.framesPerBuffer = 44100;
|
|
|
|
// TODO: add max number of simultanous playing sounds, that's more important.
|
|
|
|
context.sound_uid_counter = 0;
|
|
context.maxNumSounds = 10;
|
|
context.highestSoundIndex = -1;
|
|
//context.soundSlots = (Slot*) calloc(context.maxNumSounds, 1000); // XXX HACK
|
|
context.sounds = (SoundHandle **) calloc(context.maxNumSounds, 1000); // XXX HACK
|
|
//assert(context.soundSlots != NULL);
|
|
|
|
PaError err = Pa_Initialize();
|
|
if (err != paNoError) {
|
|
printf("error: sound failed to initialize. error=%s\n", Pa_GetErrorText(err));
|
|
return false;
|
|
|
|
} else {
|
|
//printf("sound initialized :)");
|
|
}
|
|
|
|
int numDevices;
|
|
numDevices = Pa_GetDeviceCount();
|
|
if (numDevices < 0 ) {
|
|
printf("ERROR: Pa_CountDevices returned %i\n", numDevices);
|
|
return false;
|
|
|
|
} else {
|
|
#if 0
|
|
printf("sound: num devices=%d\n", numDevices);
|
|
const PaDeviceInfo *deviceInfo;
|
|
int i;
|
|
for( i=0; i<numDevices; i++ ) {
|
|
deviceInfo = Pa_GetDeviceInfo( i );
|
|
printf("dev [%i]: %s\n", i, deviceInfo->name);
|
|
}
|
|
#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;
|
|
}
|
|
|
|
ssize_t nextFreeIndex = 0;
|
|
while(nextFreeIndex <= context.highestSoundIndex) {
|
|
if (context.sounds[nextFreeIndex] == NULL || context.sounds[nextFreeIndex]->isDeleted) {
|
|
// got an empty sound to use
|
|
break;
|
|
}
|
|
nextFreeIndex++;
|
|
}
|
|
|
|
if (context.sounds[nextFreeIndex] != NULL) {
|
|
printf("[sound] playSound() finally free'ing old sound: %zd\n",
|
|
context.sounds[nextFreeIndex]->uid);
|
|
|
|
free(context.sounds[nextFreeIndex]);
|
|
context.sounds[nextFreeIndex] = NULL;
|
|
}
|
|
|
|
if (context.sounds[nextFreeIndex] != NULL) {
|
|
fprintf(stderr, "[sound] warning: playSound() nextFreeIndex is "
|
|
"%zd but that sound is not NULL!\n", nextFreeIndex);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (nextFreeIndex+1 >= (ssize_t) context.maxNumSounds) {
|
|
printf("[sound] warning: can't create more sounds, got maximum of: %zd\n", context.maxNumSounds);
|
|
return NULL;
|
|
}
|
|
|
|
SoundHandle *handle = (SoundHandle *) calloc(1, sizeof(SoundHandle));
|
|
handle->deleteFlag = false;
|
|
handle->isDeleted = false;
|
|
handle->_done = false;
|
|
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->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 = "<unnamed>";
|
|
|
|
// 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+1) {
|
|
context.highestSoundIndex++;
|
|
}
|
|
//if (nextFreeIndex < context.highestSoundIndex) {
|
|
// fprintf(stderr, "[sound] warning: nextFreeIndex > context.highestSoundIndex, "
|
|
// "this should not happen: %zd %zd\n", nextFreeIndex, context.highestSoundIndex);
|
|
//}
|
|
|
|
printf("[sound] playSound() created sound #%zd name %s\n", handle->uid, handle->name);
|
|
printf("[sound] highestSoundIndex now %zd\n", 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<context.highestSoundIndex; i++) {
|
|
if (context.sounds[i] != NULL && !context.sounds[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;
|
|
ssize_t i;
|
|
for (i=0; i<=context.highestSoundIndex; i++) {
|
|
// TODO: active is when not done or not deleted!
|
|
if (context.sounds[i] != NULL) {
|
|
num++;
|
|
}
|
|
}
|
|
return num;
|
|
}
|
|
|
|
void deleteSound(SoundHandle *handle)
|
|
{
|
|
if (handle == NULL) {
|
|
return;
|
|
}
|
|
|
|
// just to be sure
|
|
handle->_done = true;
|
|
|
|
// clear from that
|
|
ssize_t i;
|
|
for (i=0; i<=context.highestSoundIndex; i++) {
|
|
if (context.sounds[i] == handle) {
|
|
context.sounds[i] = NULL;
|
|
if (i+1 == context.highestSoundIndex) {
|
|
context.highestSoundIndex--;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (handle->history != 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;
|
|
}
|
|
}
|
|
|
|
void deleteOldSounds(void)
|
|
{
|
|
ssize_t i;
|
|
for (i=0; i<=context.highestSoundIndex; i++) {
|
|
SoundHandle *handle = context.sounds[i];
|
|
if (handle != NULL && handle->deleteFlag) {
|
|
//context.sounds[i] = NULL;
|
|
printf("[sound] %f finally deleting sound #%zd handle 0x%p\n", global_time, handle->uid, handle);
|
|
if (handle->history != NULL) {
|
|
free(handle->history);
|
|
}
|
|
handle->isDeleted = true;
|
|
handle->deleteFlag = false;
|
|
//free(handle);
|
|
}
|
|
}
|
|
|
|
ssize_t nextHighestSoundIndex = -1;
|
|
for (i=0; i<=context.highestSoundIndex; i++) {
|
|
SoundHandle *handle = context.sounds[i];
|
|
if (handle != NULL) {
|
|
nextHighestSoundIndex = i;
|
|
}
|
|
}
|
|
|
|
if (context.highestSoundIndex != nextHighestSoundIndex) {
|
|
printf("[sound] deleteOldSounds() setting hightest sound index from %zd to %zd\n",
|
|
context.highestSoundIndex, nextHighestSoundIndex);
|
|
|
|
context.highestSoundIndex = nextHighestSoundIndex;
|
|
}
|
|
}
|
|
}
|