diff --git a/Makefile b/Makefile index 6586f3c09a..d40b7b4845 100644 --- a/Makefile +++ b/Makefile @@ -638,6 +638,8 @@ $(BUILD_DIR)/src/libultra/%.o: ASOPTFLAGS := $(EGCS_ASOPTFLAGS) $(BUILD_DIR)/src/libultra/reg/_%.o: OPTFLAGS := -O0 $(BUILD_DIR)/src/libultra/reg/_%.o: MIPS_VERSION := -mgp64 -mfp64 -mips3 +$(BUILD_DIR)/src/libultra/audio/%.o: OPTFLAGS := -O2 + $(BUILD_DIR)/src/libultra/libc/ll.o: OPTFLAGS := -O0 $(BUILD_DIR)/src/libultra/libc/llcvt.o: OPTFLAGS := -O0 diff --git a/include/ultra64.h b/include/ultra64.h index e8a4bd0ca7..30e43563e9 100644 --- a/include/ultra64.h +++ b/include/ultra64.h @@ -70,7 +70,6 @@ void* osViGetNextFramebuffer(void); void osCreatePiManager(OSPri pri, OSMesgQueue* cmdQueue, OSMesg* cmdBuf, s32 cmdMsgCnt); void __osDevMgrMain(void* arg); s32 __osPiRawStartDma(s32 dir, u32 cartAddr, void* dramAddr, size_t size); -u32 osVirtualToPhysical(void* vaddr); void osViBlack(u8 active); s32 __osSiRawReadIo(void* devAddr, u32* dst); OSId osGetThreadId(OSThread* thread); diff --git a/include/ultra64/convert.h b/include/ultra64/convert.h index 13679fc5b0..771fad19cd 100644 --- a/include/ultra64/convert.h +++ b/include/ultra64/convert.h @@ -15,4 +15,6 @@ #define OS_PHYSICAL_TO_K0(x) (void*)(((u32)(x)+0x80000000)) #define OS_PHYSICAL_TO_K1(x) (void*)(((u32)(x)+0xA0000000)) +u32 osVirtualToPhysical(void* vaddr); + #endif diff --git a/src/boot/z_std_dma.c b/src/boot/z_std_dma.c index 2872d127bd..b708a54dd0 100644 --- a/src/boot/z_std_dma.c +++ b/src/boot/z_std_dma.c @@ -28,7 +28,7 @@ #endif #pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.2:68 pal-1.0:66 pal-1.1:66" + "ntsc-1.2:66 pal-1.0:64 pal-1.1:64" StackEntry sDmaMgrStackInfo; OSMesgQueue sDmaMgrMsgQueue; diff --git a/src/code/fault_gc.c b/src/code/fault_gc.c index 059162c162..59f95d17ff 100644 --- a/src/code/fault_gc.c +++ b/src/code/fault_gc.c @@ -40,7 +40,7 @@ * DPad-Up may be pressed to enable sending fault pages over osSyncPrintf as well as displaying them on-screen. * DPad-Down disables sending fault pages over osSyncPrintf. */ -#pragma increment_block_number "gc-eu:160 gc-eu-mq:160 gc-eu-mq-dbg:160 gc-jp:160 gc-jp-ce:160 gc-jp-mq:160 gc-us:160" \ +#pragma increment_block_number "gc-eu:160 gc-eu-mq:160 gc-eu-mq-dbg:144 gc-jp:160 gc-jp-ce:160 gc-jp-mq:160 gc-us:160" \ "gc-us-mq:160" #include "global.h" diff --git a/src/code/sys_math3d.c b/src/code/sys_math3d.c index 203b9f2e2a..0a773bfb69 100644 --- a/src/code/sys_math3d.c +++ b/src/code/sys_math3d.c @@ -5,7 +5,7 @@ #include "macros.h" #include "sys_math3d.h" -#pragma increment_block_number "gc-eu:99 gc-eu-mq:99 gc-jp:99 gc-jp-ce:99 gc-jp-mq:99 gc-us:99 gc-us-mq:99" \ +#pragma increment_block_number "gc-eu:98 gc-eu-mq:98 gc-jp:98 gc-jp-ce:98 gc-jp-mq:98 gc-us:98 gc-us-mq:98" \ "ntsc-1.0:80 ntsc-1.1:80 ntsc-1.2:79 pal-1.0:80 pal-1.1:80" s32 Math3D_LineVsLineClosestTwoPoints(Vec3f* lineAPointA, Vec3f* lineAPointB, Vec3f* lineBPointA, Vec3f* lineBPointB, diff --git a/src/code/z_camera.c b/src/code/z_camera.c index 715d3f42c2..f36aba0d7d 100644 --- a/src/code/z_camera.c +++ b/src/code/z_camera.c @@ -3639,7 +3639,7 @@ s32 Camera_KeepOn3(Camera* camera) { } #pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.0:130 ntsc-1.1:130 ntsc-1.2:130 pal-1.0:128 pal-1.1:128" + "ntsc-1.0:129 ntsc-1.1:129 ntsc-1.2:129 pal-1.0:127 pal-1.1:127" s32 Camera_KeepOn4(Camera* camera) { static Vec3f D_8015BD50; diff --git a/src/code/z_collision_check.c b/src/code/z_collision_check.c index aa83c5e532..03e97bcc16 100644 --- a/src/code/z_collision_check.c +++ b/src/code/z_collision_check.c @@ -16,7 +16,7 @@ #include "z_lib.h" #pragma increment_block_number "gc-eu:128 gc-eu-mq:128 gc-jp:128 gc-jp-ce:128 gc-jp-mq:128 gc-us:128 gc-us-mq:128" \ - "ntsc-1.0:96 ntsc-1.1:96 ntsc-1.2:96 pal-1.0:96 pal-1.1:96" + "ntsc-1.0:88 ntsc-1.1:88 ntsc-1.2:88 pal-1.0:88 pal-1.1:88" typedef s32 (*ColChkResetFunc)(PlayState*, Collider*); typedef void (*ColChkApplyFunc)(PlayState*, CollisionCheckContext*, Collider*); diff --git a/src/code/z_kankyo.c b/src/code/z_kankyo.c index eff23ff2a0..1fc96fcb35 100644 --- a/src/code/z_kankyo.c +++ b/src/code/z_kankyo.c @@ -1,5 +1,5 @@ -#pragma increment_block_number "gc-eu:216 gc-eu-mq:216 gc-jp:212 gc-jp-ce:212 gc-jp-mq:212 gc-us:212 gc-us-mq:212" \ - "ntsc-1.0:208 ntsc-1.1:208 ntsc-1.2:208 pal-1.0:228 pal-1.1:228" +#pragma increment_block_number "gc-eu:216 gc-eu-mq:216 gc-jp:192 gc-jp-ce:192 gc-jp-mq:192 gc-us:192 gc-us-mq:192" \ + "ntsc-1.0:208 ntsc-1.1:208 ntsc-1.2:208 pal-1.0:224 pal-1.1:224" #include "global.h" #include "ultra64.h" diff --git a/src/libultra/audio/auxbus.c b/src/libultra/audio/auxbus.c index 8b13789179..d6e827d217 100644 --- a/src/libultra/audio/auxbus.c +++ b/src/libultra/audio/auxbus.c @@ -1 +1,33 @@ +#include "libaudio.h" +#include "synthInternals.h" +Acmd* alAuxBusPull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p) { + Acmd* ptr = p; + ALAuxBus* m = (ALAuxBus*)filter; + ALFilter** sources = m->sources; + s32 i; + + // clear the output buffers here + aClearBuffer(ptr++, AL_AUX_L_OUT, outCount << 1); + aClearBuffer(ptr++, AL_AUX_R_OUT, outCount << 1); + + for (i = 0; i < m->sourceCount; i++) { + ptr = sources[i]->handler(sources[i], outp, outCount, sampleOffset, ptr); + } + return ptr; +} + +s32 alAuxBusParam(void* filter, s32 paramID, void* param) { + ALAuxBus* m = (ALAuxBus*)filter; + ALFilter** sources = m->sources; + + switch (paramID) { + case AL_FILTER_ADD_SOURCE: + sources[m->sourceCount++] = (ALFilter*)param; + break; + + default: + break; + } + return 0; +} diff --git a/src/libultra/audio/copy.c b/src/libultra/audio/copy.c index 8b13789179..1da435cc9e 100644 --- a/src/libultra/audio/copy.c +++ b/src/libultra/audio/copy.c @@ -1 +1,11 @@ +#include "libaudio.h" +void alCopy(void* src, void* dest, s32 len) { + s32 i; + u8* s = (u8*)src; + u8* d = (u8*)dest; + + for (i = 0; i < len; i++) { + *d++ = *s++; + } +} diff --git a/src/libultra/audio/drvrnew.c b/src/libultra/audio/drvrnew.c index 8b13789179..6b10e66b72 100644 --- a/src/libultra/audio/drvrnew.c +++ b/src/libultra/audio/drvrnew.c @@ -1 +1,264 @@ +#include "libaudio.h" +#include "synthInternals.h" +// WARNING: THE FOLLOWING CONSTANT MUST BE KEPT IN SYNC WITH SCALING IN MICROCODE!!! +#define SCALE 16384 + +// the following arrays contain default parameters for a few hopefully useful effects. +#define ms *(((s32)(44.1f)) & ~7) + +// clang-format off +static s32 SMALLROOM_PARAMS[2 + 3 * 8] = { + /* sections */ 3, + /* length */ 100 ms, + /* input output fbcoef ffcoef gain rate depth coef */ + 0, 54 ms, 9830, -9830, 0, 0, 0, 0, + 19 ms, 38 ms, 3276, -3276, 0x3FFF, 0, 0, 0, + 0, 60 ms, 5000, 0, 0, 0, 0, 0x5000 +}; + +static s32 BIGROOM_PARAMS[2 + 4 * 8] = { + /* sections */ 4, + /* length */ 100 ms, + /* input output fbcoef ffcoef gain rate depth coef */ + 0, 66 ms, 9830, -9830, 0, 0, 0, 0, + 22 ms, 54 ms, 3276, -3276, 0x3FFF, 0, 0, 0, + 66 ms, 91 ms, 3276, -3276, 0x3FFF, 0, 0, 0, + 0, 94 ms, 8000, 0, 0, 0, 0, 0x5000, +}; + +static s32 ECHO_PARAMS[2 + 1 * 8] = { + /* sections */ 1, + /* length */ 200 ms, + /* input output fbcoef ffcoef gain rate depth coef */ + 0, 179 ms, 12000, 0, 0x7FFF, 0, 0, 0, +}; + +static s32 CHORUS_PARAMS[2 + 1 * 8] = { + /* sections */ 1, + /* length */ 20 ms, + /* input output fbcoef ffcoef gain rate depth coef */ + 0, 5 ms, 0x4000, 0, 0x7FFF, 7600, 700, 0, +}; + +static s32 FLANGE_PARAMS[2 + 1 * 8] = { + /* sections */ 1, + /* length */ 20 ms, + /* input output fbcoef ffcoef gain rate depth coef */ + 0, 5 ms, 0, 0x5FFF, 0x7FFF, 380, 500, 0, +}; + +static s32 NULL_PARAMS[2 + 1 * 8] = { + /* sections */ 0, + /* length */ 0, + /* input output fbcoef ffcoef gain rate depth coef */ + 0, 0, 0, 0, 0, 0, 0, 0, +}; +// clang-format on + +void _init_lpfilter(ALLowPass* lp) { + s32 i; + s32 temp; + s16 fc; + f64 ffc; + f64 fcoef; + + temp = lp->fc * SCALE; + fc = temp >> 15; + lp->fgain = SCALE - fc; + + lp->first = true; + for (i = 0; i < 8; i++) { + lp->fcvec.fccoef[i] = 0; + } + + lp->fcvec.fccoef[i++] = fc; + fcoef = ffc = (f64)fc / SCALE; + + for (; i < 16; i++) { + fcoef *= ffc; + lp->fcvec.fccoef[i] = (s16)(s32)(fcoef * SCALE); + } +} + +void alFxNew(ALFx* r, ALSynConfig* c, ALHeap* hp) { + u16 i; + u16 j; + u16 k; + s32* param = NULL; + ALFilter* f = &r->filter; + ALDelay* d; + + alFilterNew(f, NULL, alFxParam, AL_FX); + f->handler = alFxPull; + r->paramHdl = (ALSetFXParam)alFxParamHdl; + + switch (c->fxType) { + case AL_FX_SMALLROOM: + param = SMALLROOM_PARAMS; + break; + + case AL_FX_BIGROOM: + param = BIGROOM_PARAMS; + break; + + case AL_FX_ECHO: + param = ECHO_PARAMS; + break; + + case AL_FX_CHORUS: + param = CHORUS_PARAMS; + break; + + case AL_FX_FLANGE: + param = FLANGE_PARAMS; + break; + + case AL_FX_CUSTOM: + param = c->params; + break; + + default: + param = NULL_PARAMS; + break; + } + + j = 0; + + r->section_count = param[j++]; + r->length = param[j++]; + + r->delay = alHeapAlloc(hp, r->section_count, sizeof(ALDelay)); + r->base = alHeapAlloc(hp, r->length, sizeof(s16)); + r->input = r->base; + + for (k = 0; k < r->length; k++) { + r->base[k] = 0; + } + + for (i = 0; i < r->section_count; i++) { + d = &r->delay[i]; + d->input = param[j++]; + d->output = param[j++]; + d->fbcoef = param[j++]; + d->ffcoef = param[j++]; + d->gain = param[j++]; + + if (param[j] != 0) { +#define RANGE 2.0 + d->rsinc = ((((f32)param[j++]) / 1000.0f) * RANGE) / c->outputRate; + + // the following constant is derived from: + // + // ratio = 2^(cents/1200) + // + // and therefore for hundredths of a cent + // x + // ln(ratio) = --------------- + // (120,000)/ln(2) + // where + // 120,000/ln(2) = 173123.40... +#define CONVERT 173123.404906676 + d->rsgain = (((f32)param[j++]) / CONVERT) * (d->output - d->input); + d->rsval = 1.0f; + d->rsdelta = 0.0f; + d->rs = alHeapAlloc(hp, 1, sizeof(ALResampler)); + d->rs->state = alHeapAlloc(hp, 1, sizeof(RESAMPLE_STATE)); + d->rs->delta = 0.0f; + d->rs->first = true; + } else { + d->rs = NULL; + j++; + j++; + } + + if (param[j] != 0) { + d->lp = alHeapAlloc(hp, 1, sizeof(ALLowPass)); + d->lp->fstate = alHeapAlloc(hp, 1, sizeof(POLEF_STATE)); + d->lp->fc = param[j++]; + _init_lpfilter(d->lp); + } else { + d->lp = NULL; + j++; + } + } +} + +void alEnvmixerNew(ALEnvMixer* e, ALHeap* hp) { + alFilterNew(&e->filter, alEnvmixerPull, alEnvmixerParam, AL_ENVMIX); + e->state = alHeapAlloc(hp, 1, sizeof(ENVMIX_STATE)); + e->first = true; + e->motion = AL_STOPPED; + e->volume = 1; + e->ltgt = 1; + e->rtgt = 1; + e->cvolL = 1; + e->cvolR = 1; + e->dryamt = 0; + e->wetamt = 0; + e->lratm = 1; + e->lratl = 0; + e->lratm = 1; + e->lratl = 0; + e->delta = 0; + e->segEnd = 0; + e->pan = 0; + e->ctrlList = NULL; + e->ctrlTail = NULL; + e->sources = NULL; +} + +void alLoadNew(ALLoadFilter* f, ALDMANew dmaNew, ALHeap* hp) { + s32 i; + + // init filter superclass + alFilterNew(&f->filter, alAdpcmPull, alLoadParam, AL_ADPCM); + + f->state = alHeapAlloc(hp, 1, sizeof(ADPCM_STATE)); + f->lstate = alHeapAlloc(hp, 1, sizeof(ADPCM_STATE)); + + f->dma = dmaNew(&f->dmaState); + + // init the adpcm state + f->lastsam = 0; + f->first = true; + f->memin = 0; +} + +void alResampleNew(ALResampler* r, ALHeap* hp) { + alFilterNew(&r->filter, alResamplePull, alResampleParam, AL_RESAMPLE); + + // Init resampler state + r->state = alHeapAlloc(hp, 1, sizeof(RESAMPLE_STATE)); + r->delta = 0.0f; + r->first = true; + r->motion = AL_STOPPED; + r->ratio = 1.0f; + r->upitch = 0; + r->ctrlList = NULL; + r->ctrlTail = NULL; + // state in the ucode is initialized by the A_INIT flag +} + +void alAuxBusNew(ALAuxBus* m, void* sources, s32 maxSources) { + alFilterNew(&m->filter, alAuxBusPull, alAuxBusParam, AL_AUXBUS); + m->sourceCount = 0; + m->maxSources = maxSources; + m->sources = (ALFilter**)sources; +} + +void alMainBusNew(ALMainBus* m, void* sources, s32 maxSources) { + alFilterNew(&m->filter, alMainBusPull, alMainBusParam, AL_MAINBUS); + m->sourceCount = 0; + m->maxSources = maxSources; + m->sources = (ALFilter**)sources; +} + +void alSaveNew(ALSave* f) { + // init filter superclass + alFilterNew(&f->filter, alSavePull, alSaveParam, AL_SAVE); + + // init the save state, which is a virtual dram address + f->dramout = 0; + f->first = true; +} diff --git a/src/libultra/audio/env.c b/src/libultra/audio/env.c index 8b13789179..2d0027ba7d 100644 --- a/src/libultra/audio/env.c +++ b/src/libultra/audio/env.c @@ -1 +1,420 @@ +#include "libaudio.h" +#include "synthInternals.h" +#include "libc/math.h" +#include "ultra64/convert.h" +#define EQPOWER_LENGTH 128 +static s16 eqpower[EQPOWER_LENGTH] = { + 32767, 32764, 32757, 32744, 32727, 32704, 32677, 32644, 32607, 32564, 32517, 32464, 32407, 32344, 32277, 32205, + 32127, 32045, 31958, 31866, 31770, 31668, 31561, 31450, 31334, 31213, 31087, 30957, 30822, 30682, 30537, 30388, + 30234, 30075, 29912, 29744, 29572, 29395, 29214, 29028, 28838, 28643, 28444, 28241, 28033, 27821, 27605, 27385, + 27160, 26931, 26698, 26461, 26220, 25975, 25726, 25473, 25216, 24956, 24691, 24423, 24151, 23875, 23596, 23313, + 23026, 22736, 22442, 22145, 21845, 21541, 21234, 20924, 20610, 20294, 19974, 19651, 19325, 18997, 18665, 18331, + 17993, 17653, 17310, 16965, 16617, 16266, 15913, 15558, 15200, 14840, 14477, 14113, 13746, 13377, 13006, 12633, + 12258, 11881, 11503, 11122, 10740, 10357, 9971, 9584, 9196, 8806, 8415, 8023, 7630, 7235, 6839, 6442, + 6044, 5646, 5246, 4845, 4444, 4042, 3640, 3237, 2833, 2429, 2025, 1620, 1216, 810, 405, 0 +}; + +static Acmd* _pullSubFrame(void* filter, s16* inp, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p); +static s16 _getRate(f64 vol, f64 tgt, s32 count, u16* ratel); +static f32 _getVol(f32 ivol, s32 samples, s16 ratem, u16 ratel); + +Acmd* alEnvmixerPull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p) { + Acmd* ptr = p; + ALEnvMixer* e = (ALEnvMixer*)filter; + s16 inp; + s32 lastOffset; + s32 thisOffset = sampleOffset; + s32 samples; + s16 loutp = 0; + s32 fVol; + ALParam* thisParam; + + // Force the input to be the resampler output + inp = AL_RESAMPLER_OUT; + + while (e->ctrlList != NULL) { + lastOffset = thisOffset; + thisOffset = e->ctrlList->delta; + samples = thisOffset - lastOffset; + if (samples > outCount) { + break; + } + + switch (e->ctrlList->type) { + case AL_FILTER_START_VOICE_ALT: { + ALStartParamAlt* param = (ALStartParamAlt*)e->ctrlList; + ALFilter* f = &e->filter; + s32 tmp; + + if (param->unity != 0) { + (*e->filter.setParam)(&e->filter, AL_FILTER_SET_UNITY_PITCH, 0); + } + + (*e->filter.setParam)(&e->filter, AL_FILTER_SET_WAVETABLE, param->wave); + (*e->filter.setParam)(&e->filter, AL_FILTER_START, 0); + + e->first = true; + + e->delta = 0; + e->segEnd = param->samples; + + tmp = ((s32)param->volume * (s32)param->volume) >> 15; + e->volume = (s16)tmp; + e->pan = param->pan; + e->dryamt = eqpower[param->fxMix]; + e->wetamt = eqpower[EQPOWER_LENGTH - param->fxMix - 1]; + + if (param->samples != 0) { + e->cvolL = 1; + e->cvolR = 1; + } else { + // Attack time is zero. Simply set the volume. We don't want an attack segment. + e->cvolL = (e->volume * eqpower[e->pan]) >> 15; + e->cvolR = (e->volume * eqpower[EQPOWER_LENGTH - e->pan - 1]) >> 15; + } + + if (f->source != NULL) { + union { + f32 f; + s32 i; + } data; + data.f = param->pitch; + (*f->source->setParam)(f->source, AL_FILTER_SET_PITCH, (void*)data.i); + } + } break; + + case AL_FILTER_SET_FXAMT: + case AL_FILTER_SET_PAN: + case AL_FILTER_SET_VOLUME: + ptr = _pullSubFrame(e, &inp, &loutp, samples, sampleOffset, ptr); + + if (e->delta >= e->segEnd) { + // We should have reached our target, calculate target in case e->segEnd was 0 + e->ltgt = (e->volume * eqpower[e->pan]) >> 15; + e->rtgt = (e->volume * eqpower[EQPOWER_LENGTH - e->pan - 1]) >> 15; + e->delta = e->segEnd; // To prevent overflow + e->cvolL = e->ltgt; + e->cvolR = e->rtgt; + } else { + // Estimate the current volume + e->cvolL = _getVol(e->cvolL, e->delta, e->lratm, e->lratl); + e->cvolR = _getVol(e->cvolR, e->delta, e->rratm, e->rratl); + } + + // We can't have volume of zero, because the envelope would never go anywhere from there + if (e->cvolL == 0) { + e->cvolL = 1; + } + if (e->cvolR == 0) { + e->cvolR = 1; + } + + if (e->ctrlList->type == AL_FILTER_SET_PAN) { + // This should result in a change to the current segment rate and target + e->pan = (s16)e->ctrlList->data.i; + } + + if (e->ctrlList->type == AL_FILTER_SET_VOLUME) { + // Switching to a new segment + e->delta = 0; + + // Map volume non-linearly to give something close to loudness + fVol = (e->ctrlList->data.i); + fVol = (fVol * fVol) >> 15; + e->volume = (s16)fVol; + e->segEnd = e->ctrlList->moredata.i; + } + + if (e->ctrlList->type == AL_FILTER_SET_FXAMT) { + e->dryamt = eqpower[e->ctrlList->data.i]; + e->wetamt = eqpower[EQPOWER_LENGTH - e->ctrlList->data.i - 1]; + } + + // Force a volume update + e->first = true; + break; + + case AL_FILTER_START_VOICE: { + ALStartParam* p = (ALStartParam*)e->ctrlList; + + // Changing to PLAYING (since the previous state was persumable STOPPED, we'll just bump the output + // pointer rather than pull a subframe of zeros). + if (p->unity != 0) { + (*e->filter.setParam)(&e->filter, AL_FILTER_SET_UNITY_PITCH, 0); + } + + (*e->filter.setParam)(&e->filter, AL_FILTER_SET_WAVETABLE, p->wave); + (*e->filter.setParam)(&e->filter, AL_FILTER_START, 0); + } break; + + case AL_FILTER_STOP_VOICE: { + // Changing to STOPPED and reset the filter + ptr = _pullSubFrame(e, &inp, &loutp, samples, sampleOffset, ptr); + (*e->filter.setParam)(&e->filter, AL_FILTER_RESET, 0); + } break; + + case AL_FILTER_FREE_VOICE: { + ALSynth* drvr = &alGlobals->drvr; + ALFreeParam* param = (ALFreeParam*)e->ctrlList; + param->pvoice->offset = 0; + _freePVoice(drvr, param->pvoice); + } break; + + default: + // Pull the reuired number of samples and then pass the message on down the chain + ptr = _pullSubFrame(e, &inp, &loutp, samples, sampleOffset, ptr); + (*e->filter.setParam)(&e->filter, e->ctrlList->type, (void*)e->ctrlList->data.i); + break; + } + loutp += samples << 1; + outCount -= samples; + + // put the param record back on the free list + thisParam = e->ctrlList; + e->ctrlList = e->ctrlList->next; + if (e->ctrlList == NULL) { + e->ctrlTail = NULL; + } + + __freeParam(thisParam); + } + + ptr = _pullSubFrame(e, &inp, &loutp, outCount, sampleOffset, ptr); + + // Prevent overflow in e->delta + if (e->delta > e->segEnd) { + e->delta = e->segEnd; + } + + return ptr; +} + +s32 alEnvmixerParam(void* filter, s32 paramID, void* param) { + ALFilter* f = (ALFilter*)filter; + ALEnvMixer* e = (ALEnvMixer*)filter; + + switch (paramID) { + case AL_FILTER_ADD_UPDATE: + if (e->ctrlTail != NULL) { + e->ctrlTail->next = (ALParam*)param; + } else { + e->ctrlList = (ALParam*)param; + } + e->ctrlTail = (ALParam*)param; + break; + + case AL_FILTER_RESET: + e->first = true; + e->motion = AL_STOPPED; + e->volume = 1; + if (f->source != NULL) { + (*f->source->setParam)(f->source, AL_FILTER_RESET, param); + } + break; + + case AL_FILTER_START: + e->motion = AL_PLAYING; + if (f->source != NULL) { + (*f->source->setParam)(f->source, AL_FILTER_START, param); + } + break; + + case AL_FILTER_SET_SOURCE: + f->source = (ALFilter*)param; + break; + + default: + if (f->source != NULL) { + (*f->source->setParam)(f->source, paramID, param); + } + break; + } + return 0; +} + +static Acmd* _pullSubFrame(void* filter, s16* inp, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p) { + Acmd* ptr = p; + ALEnvMixer* e = (ALEnvMixer*)filter; + ALFilter* source = e->filter.source; + + // filter must be playing and request non-zero output samples to pull. + if (e->motion != AL_PLAYING || !outCount) { + return ptr; + } + + // ask all filters upstream from us to build their command lists. + ptr = (*source->handler)(source, inp, outCount, sampleOffset, p); + + // construct our portion of the command list + aSetBuffer(ptr++, A_MAIN, *inp, AL_MAIN_L_OUT + *outp, outCount << 1); + aSetBuffer(ptr++, A_AUX, AL_MAIN_R_OUT + *outp, AL_AUX_L_OUT + *outp, AL_AUX_R_OUT + *outp); + + if (e->first) { + e->first = false; + + // Calculate derived parameters + e->ltgt = (e->volume * eqpower[e->pan]) >> 15; + e->lratm = _getRate((f64)e->cvolL, (f64)e->ltgt, e->segEnd, &e->lratl); + e->rtgt = (e->volume * eqpower[EQPOWER_LENGTH - e->pan - 1]) >> 15; + e->rratm = _getRate((f64)e->cvolR, (f64)e->rtgt, e->segEnd, &e->rratl); + + aSetVolume(ptr++, A_LEFT | A_VOL, e->cvolL, 0, 0); + aSetVolume(ptr++, A_RIGHT | A_VOL, e->cvolR, 0, 0); + aSetVolume(ptr++, A_LEFT | A_RATE, e->ltgt, e->lratm, e->lratl); + aSetVolume(ptr++, A_RIGHT | A_RATE, e->rtgt, e->rratm, e->rratl); + aSetVolume(ptr++, A_AUX, e->dryamt, 0, e->wetamt); + aEnvMixer(ptr++, A_INIT | A_AUX, osVirtualToPhysical(e->state)); + } else + aEnvMixer(ptr++, A_CONTINUE | A_AUX, osVirtualToPhysical(e->state)); + + // bump the input buffer pointer + *inp += outCount << 1; + e->delta += outCount; + + return ptr; +} + +f64 _frexpf(f64 value, s32* eptr) { + f64 absvalue; + + *eptr = 0; + if (value == 0.0) { + // nothing to do for zero + return value; + } + absvalue = (value > 0.0) ? value : -value; + for (; absvalue >= 1.0; absvalue *= 0.5) { + (*eptr)++; + } + for (; absvalue < 0.5; absvalue += absvalue) { + (*eptr)--; + } + return (value > 0.0 ? absvalue : -absvalue); +} + +f64 _ldexpf(f64 in, s32 ex) { + s32 exp; + + if (ex != 0) { + exp = 1 << ex; + in *= (f64)exp; + } + return in; +} + +/** + * This function determines how to go from the current volume level (vol) to the target volume level (tgt) in some + * number of steps (count). Two values are returned that are used as multipliers to incrementally scale the volume. + * Some tricky math is used and is explained below. + * RWW 28jun95 + */ +static s16 _getRate(f64 vol, f64 tgt, s32 count, u16* ratel) { + s16 s; + f64 invn = 1.0 / count; + f64 eps; + f64 a; + f64 fs; + f64 mant; + s32 i_invn; + s32 ex; + s32 indx; + + if (count == 0) { + if (tgt >= vol) { + *ratel = 0xFFFF; + return 0x7FFF; + } else { + *ratel = 0; + return 0; + } + } + + if (tgt < 1.0) { + tgt = 1.0; + } + if (vol <= 0.0) { + vol = 1.0; // zero and neg values not allowed + } + +#define NBITS 3 +#define NPOS (1 << NBITS) +#define NFRACBITS 30 +#define M_LN2 0.69314718055994530942 + // rww's parametric pow() + // Goal: compute a = (tgt/vol)^(1/count) + // + // Approach: + // (tgt/vol)^(1/count) = ((tgt/vol)^(1/2^30))^(2^30*1/count) + // + // (tgt/vol)^(1/2^30) ~= 1 + eps + // + // where + // + // eps ~= ln(tgt/vol)/2^30 + // + // ln(tgt/vol) = ln2(tgt/vol) * ln(2) + // + // ln2(tgt/vol) = fp_exponent( tgt/vol ) + ln2( fp_mantissa( tgt/vol ) ) + // + // fp_mantissa() and fp_exponent() are calculated via tricky bit manipulations of + // the floating point number. ln2() is approximated by a look up table. + // + // Note that this final (1+eps) value needs to be raised to the 2^30/count power. This + // is done by operating on the binary representaion of this number in the final while loop. + // + // Enjoy! + { + f64 logtab[] = { -0.912537, -0.752072, -0.607683, -0.476438, -0.356144, -0.245112, -0.142019, -0.045804 }; + + i_invn = (s32)_ldexpf(invn, NFRACBITS); + mant = _frexpf(tgt / vol, &ex); + indx = (s32)_ldexpf(mant, NBITS + 1); // NPOS <= indx < 2*NPOS + eps = (logtab[indx - NPOS] + ex) * M_LN2; + eps /= _ldexpf(1, NFRACBITS); // eps / 2^NFRACBITS + fs = 1.0 + eps; + a = 1.0; + while (i_invn != 0) { + if (i_invn & 1) { + a = a * fs; + } + fs *= fs; + i_invn >>= 1; + } + } + + a *= a; + a *= a; + a *= a; + s = (s16)a; + *ratel = (s16)(0xFFFF * (a - (f32)s)); + + return (s16)a; +} + +static f32 _getVol(f32 ivol, s32 samples, s16 ratem, u16 ratel) { + f32 r; + f32 a; + s32 i; + + // Rate values are actually rate^8 + samples >>= 3; + if (samples == 0) { + return ivol; + } + r = ((f32)(ratem << 16) + (f32)ratel) / 65536; + + a = 1.0f; + for (i = 0; i < 32; i++) { + if (samples & 1) { + a *= r; + } + samples >>= 1; + if (samples == 0) { + break; + } + r *= r; + } + ivol *= a; + return ivol; +} diff --git a/src/libultra/audio/filter.c b/src/libultra/audio/filter.c index 8b13789179..937ffd04c5 100644 --- a/src/libultra/audio/filter.c +++ b/src/libultra/audio/filter.c @@ -1 +1,11 @@ +#include "libaudio.h" +#include "synthInternals.h" +void alFilterNew(ALFilter* f, ALCmdHandler h, ALSetParam s, s32 type) { + f->source = NULL; + f->handler = h; + f->setParam = s; + f->inp = 0; + f->outp = 0; + f->type = type; +} diff --git a/src/libultra/audio/heapalloc.c b/src/libultra/audio/heapalloc.c index 8b13789179..52c3605c51 100644 --- a/src/libultra/audio/heapalloc.c +++ b/src/libultra/audio/heapalloc.c @@ -1 +1,15 @@ +#include "libaudio.h" +#include "synthInternals.h" +void* alHeapDBAlloc(u8* file, s32 line, ALHeap* hp, s32 num, s32 size) { + s32 bytes; + u8* ptr = NULL; + + bytes = (num * size + AL_CACHE_ALIGN) & ~AL_CACHE_ALIGN; + + if (hp->cur + bytes <= hp->base + hp->len) { + ptr = hp->cur; + hp->cur += bytes; + } + return ptr; +} diff --git a/src/libultra/audio/libaudio.h b/src/libultra/audio/libaudio.h new file mode 100644 index 0000000000..67f04567bf --- /dev/null +++ b/src/libultra/audio/libaudio.h @@ -0,0 +1,842 @@ +#ifndef __LIB_AUDIO__ +#define __LIB_AUDIO__ + +#include "libc/stdbool.h" +#include "libc/stddef.h" +#include "libc/stdint.h" +#include "ultra64/ultratypes.h" + +#include "libaudio_abi.h" + +/*********************************************************************** + * misc defines + ***********************************************************************/ + +#define AL_FX_BUFFER_SIZE 8192 +#define AL_FRAME_INIT -1 +#define AL_USEC_PER_FRAME 16000 +#define AL_MAX_PRIORITY 127 +#define AL_GAIN_CHANGE_TIME 1000 + +typedef s32 ALMicroTime; +typedef u8 ALPan; + +#define AL_PAN_CENTER 64 +#define AL_PAN_LEFT 0 +#define AL_PAN_RIGHT 127 +#define AL_VOL_FULL 127 +#define AL_KEY_MIN 0 +#define AL_KEY_MAX 127 +#define AL_DEFAULT_FXMIX 0 +#define AL_SUSTAIN 63 + +/*********************************************************************** + * Audio Library global routines + ***********************************************************************/ +typedef struct ALLink_s { + struct ALLink_s* next; + struct ALLink_s* prev; +} ALLink; + +void alUnlink(ALLink* ln); +void alLink(ALLink* ln, ALLink* to); + +typedef s32 (*ALDMAproc)(s32 addr, s32 len, void* state); +typedef ALDMAproc (*ALDMANew)(void* state); + +void alCopy(void* src, void* dest, s32 len); + +typedef struct { + u8* base; + u8* cur; + s32 len; + s32 count; +} ALHeap; + +#define AL_HEAP_DEBUG 1 +#define AL_HEAP_MAGIC 0x20736A73 // ' sjs' +#define AL_HEAP_INIT 0 + +void alHeapInit(ALHeap* hp, u8* base, s32 len); +void* alHeapDBAlloc(u8* file, s32 line, ALHeap* hp, s32 num, s32 size); +s32 alHeapCheck(ALHeap* hp); + +#define alHeapAlloc(hp, elem ,size) alHeapDBAlloc(0, 0,(hp),(elem),(size)) + +/*********************************************************************** + * FX Stuff + ***********************************************************************/ +#define AL_FX_NONE 0 +#define AL_FX_SMALLROOM 1 +#define AL_FX_BIGROOM 2 +#define AL_FX_CHORUS 3 +#define AL_FX_FLANGE 4 +#define AL_FX_ECHO 5 +#define AL_FX_CUSTOM 6 + +typedef u8 ALFxId; +typedef void* ALFxRef; + +/*********************************************************************** + * data structures for sound banks + ***********************************************************************/ + +#define AL_BANK_VERSION 0x4231 // 'B1' + +// Possible wavetable types +enum { + AL_ADPCM_WAVE = 0, + AL_RAW16_WAVE +}; + +typedef struct { + s32 order; + s32 npredictors; + s16 book[1]; // Actually variable size. Must be 8-byte aligned +} ALADPCMBook; + +typedef struct { + u32 start; + u32 end; + u32 count; + ADPCM_STATE state; +} ALADPCMloop; + +typedef struct { + u32 start; + u32 end; + u32 count; +} ALRawLoop; + +typedef struct { + ALMicroTime attackTime; + ALMicroTime decayTime; + ALMicroTime releaseTime; + u8 attackVolume; + u8 decayVolume; +} ALEnvelope; + +typedef struct { + u8 velocityMin; + u8 velocityMax; + u8 keyMin; + u8 keyMax; + u8 keyBase; + s8 detune; +} ALKeyMap; + +typedef struct { + ALADPCMloop* loop; + ALADPCMBook* book; +} ALADPCMWaveInfo; + +typedef struct { + ALRawLoop* loop; +} ALRAWWaveInfo; + +typedef struct ALWaveTable_s { + u8* base; // ptr to start of wave data + s32 len; // length of data in bytes + u8 type; // compression type + u8 flags; // offset/address flags + union { + ALADPCMWaveInfo adpcmWave; + ALRAWWaveInfo rawWave; + } waveInfo; +} ALWaveTable; + +typedef struct ALSound_s { + ALEnvelope* envelope; + ALKeyMap* keyMap; + ALWaveTable* wavetable; // offset to wavetable struct + ALPan samplePan; + u8 sampleVolume; + u8 flags; +} ALSound; + +typedef struct { + u8 volume; // overall volume for this instrument + ALPan pan; // 0 = hard left, 127 = hard right + u8 priority; // voice priority for this instrument + u8 flags; + u8 tremType; // the type of tremelo osc. to use + u8 tremRate; // the rate of the tremelo osc. + u8 tremDepth; // the depth of the tremelo osc + u8 tremDelay; // the delay for the tremelo osc + u8 vibType; // the type of tremelo osc. to use + u8 vibRate; // the rate of the tremelo osc. + u8 vibDepth; // the depth of the tremelo osc + u8 vibDelay; // the delay for the tremelo osc + s16 bendRange; // pitch bend range in cents + s16 soundCount; // number of sounds in this array + ALSound* soundArray[1]; +} ALInstrument; + +typedef struct ALBank_s { + s16 instCount; // number of programs in this bank + u8 flags; + u8 pad; + s32 sampleRate; // e.g. 44100, 22050, etc... + ALInstrument* percussion; // default percussion for GM + ALInstrument* instArray[1]; // ARRAY of instruments +} ALBank; + +typedef struct { // Note: sizeof won't be correct + s16 revision; // format revision of this file + s16 bankCount; // number of banks + ALBank* bankArray[1]; // ARRAY of bank offsets +} ALBankFile; + +void alBnkfNew(ALBankFile* f, u8* table); + +/*********************************************************************** + * Sequence Files + ***********************************************************************/ +#define AL_SEQBANK_VERSION 'S1' + +typedef struct { + u8* offset; + s32 len; +} ALSeqData; + +typedef struct { // Note: sizeof won't be correct + s16 revision; // format revision of this file + s16 seqCount; // number of sequences + ALSeqData seqArray[1]; // ARRAY of sequence info +} ALSeqFile; + +void alSeqFileNew(ALSeqFile* f, u8* base); + +/*********************************************************************** + * Synthesis driver stuff + ***********************************************************************/ +typedef ALMicroTime (*ALVoiceHandler)(void*); + +typedef struct { + s32 maxVVoices; // obsolete + s32 maxPVoices; + s32 maxUpdates; + s32 maxFXbusses; + void* dmaproc; + ALHeap* heap; + s32 outputRate; // output sample rate + ALFxId fxType; + s32* params; +} ALSynConfig; + +typedef struct ALPlayer_s { + struct ALPlayer_s* next; + void* clientData; // storage for client callback + ALVoiceHandler handler; // voice handler for player + ALMicroTime callTime; // usec requested callback + s32 samplesLeft; // usec remaining to callback +} ALPlayer; + +typedef struct ALVoice_s { + ALLink node; + struct PVoice_s* pvoice; + ALWaveTable* table; + void* clientPrivate; + s16 state; + s16 priority; + s16 fxBus; + s16 unityPitch; +} ALVoice; + +typedef struct ALVoiceConfig_s { + s16 priority; // voice priority + s16 fxBus; // bus assignment + u8 unityPitch; // unity pitch flag +} ALVoiceConfig; + +typedef struct { + ALPlayer* head; // client list head + ALLink pFreeList; // list of free physical voices + ALLink pAllocList; // list of allocated physical voices + ALLink pLameList; // list of voices ready to be freed + s32 paramSamples; + s32 curSamples; // samples from start of game + ALDMANew dma; + ALHeap* heap; + + struct ALParam_s* paramList; + + struct ALMainBus_s* mainBus; + struct ALAuxBus_s* auxBus; // ptr to array of aux bus structs + struct ALFilter_s* outputFilter; // last filter in the filter chain + + s32 numPVoices; + s32 maxAuxBusses; + s32 outputRate; // output sample rate + s32 maxOutSamples; // Maximum samples rsp can generate at one time at output rate +} ALSynth; + +void alSynNew(ALSynth* drvr, ALSynConfig* c); +void alSynDelete(ALSynth* drvr); + +void alSynAddPlayer(ALSynth* s, ALPlayer* client); +void alSynRemovePlayer(ALSynth* s, ALPlayer* client); + +s32 alSynAllocVoice(ALSynth* s, ALVoice* v, ALVoiceConfig* vc); +void alSynFreeVoice(ALSynth* s, ALVoice* voice); + +void alSynStartVoice(ALSynth* s, ALVoice* voice, ALWaveTable* w); +void alSynStartVoiceParams(ALSynth* s, ALVoice* voice, ALWaveTable* w, f32 pitch, s16 vol, ALPan pan, u8 fxmix, ALMicroTime t); +void alSynStopVoice(ALSynth* s, ALVoice* voice); + +void alSynSetVol(ALSynth* s, ALVoice* v, s16 vol, ALMicroTime delta); +void alSynSetPitch(ALSynth* s, ALVoice* voice, f32 ratio); +void alSynSetPan(ALSynth* s, ALVoice* voice, ALPan pan); +void alSynSetFXMix(ALSynth* s, ALVoice* voice, u8 fxmix); +void alSynSetPriority(ALSynth* s, ALVoice* voice, s16 priority); +s16 alSynGetPriority(ALSynth* s, ALVoice* voice); + +ALFxRef* alSynAllocFX(ALSynth* s, s16 bus, ALSynConfig* c, ALHeap* hp); +ALFxRef alSynGetFXRef(ALSynth* s, s16 bus, s16 index); +void alSynFreeFX(ALSynth* s, ALFxRef* fx); +void alSynSetFXParam(ALSynth* s, ALFxRef fx, s16 paramID, void* param); + +/*********************************************************************** + * Audio Library (AL) stuff + ***********************************************************************/ +typedef struct { + ALSynth drvr; +} ALGlobals; + +extern ALGlobals* alGlobals; + +void alInit(ALGlobals* g, ALSynConfig* c); +void alClose(ALGlobals* glob); + +Acmd* alAudioFrame(Acmd* cmdList, s32* cmdLen, s16* outBuf, s32 outLen); + +/*********************************************************************** + * Sequence Player stuff + ***********************************************************************/ + +/** + * Play states + */ +#define AL_STOPPED 0 +#define AL_PLAYING 1 +#define AL_STOPPING 2 + +#define AL_DEFAULT_PRIORITY 5 +#define AL_DEFAULT_VOICE 0 +#define AL_MAX_CHANNELS 16 + +/** + * Audio Library event type definitions + */ +enum ALMsg { + AL_SEQ_REF_EVT, // Reference to a pending event in the sequence. + AL_SEQ_MIDI_EVT, + AL_SEQP_MIDI_EVT, + AL_TEMPO_EVT, + AL_SEQ_END_EVT, + AL_NOTE_END_EVT, + AL_SEQP_ENV_EVT, + AL_SEQP_META_EVT, + AL_SEQP_PROG_EVT, + AL_SEQP_API_EVT, + AL_SEQP_VOL_EVT, + AL_SEQP_LOOP_EVT, + AL_SEQP_PRIORITY_EVT, + AL_SEQP_SEQ_EVT, + AL_SEQP_BANK_EVT, + AL_SEQP_PLAY_EVT, + AL_SEQP_STOP_EVT, + AL_SEQP_STOPPING_EVT, + AL_TRACK_END, + AL_CSP_LOOPSTART, + AL_CSP_LOOPEND, + AL_CSP_NOTEOFF_EVT, + AL_TREM_OSC_EVT, + AL_VIB_OSC_EVT +}; + +/** + * Midi event definitions + */ +#define AL_EVTQ_END 0x7FFFFFFF + +enum AL_MIDIstatus { + // For distinguishing channel number from status + AL_MIDI_ChannelMask = 0x0F, + AL_MIDI_StatusMask = 0xF0, + + // Channel voice messages + AL_MIDI_ChannelVoice = 0x80, + AL_MIDI_NoteOff = 0x80, + AL_MIDI_NoteOn = 0x90, + AL_MIDI_PolyKeyPressure = 0xA0, + AL_MIDI_ControlChange = 0xB0, + AL_MIDI_ChannelModeSelect = 0xB0, + AL_MIDI_ProgramChange = 0xC0, + AL_MIDI_ChannelPressure = 0xD0, + AL_MIDI_PitchBendChange = 0xE0, + + // System messages + AL_MIDI_SysEx = 0xF0, // System Exclusive + // System common + AL_MIDI_SystemCommon = 0xF1, + AL_MIDI_TimeCodeQuarterFrame = 0xF1, + AL_MIDI_SongPositionPointer = 0xF2, + AL_MIDI_SongSelect = 0xF3, + AL_MIDI_Undefined1 = 0xF4, + AL_MIDI_Undefined2 = 0xF5, + AL_MIDI_TuneRequest = 0xF6, + AL_MIDI_EOX = 0xF7, // End of System Exclusive + + // System real time + AL_MIDI_SystemRealTime = 0xF8, + AL_MIDI_TimingClock = 0xF8, + AL_MIDI_Undefined3 = 0xF9, + AL_MIDI_Start = 0xFA, + AL_MIDI_Continue = 0xFB, + AL_MIDI_Stop = 0xFC, + AL_MIDI_Undefined4 = 0xFD, + AL_MIDI_ActiveSensing = 0xFE, + AL_MIDI_SystemReset = 0xFF, + AL_MIDI_Meta = 0xFF // MIDI Files only +}; + +enum AL_MIDIctrl { + AL_MIDI_VOLUME_CTRL = 0x07, + AL_MIDI_PAN_CTRL = 0x0A, + AL_MIDI_PRIORITY_CTRL = 0x10, // use general purpose controller for priority + AL_MIDI_FX_CTRL_0 = 0x14, + AL_MIDI_FX_CTRL_1 = 0x15, + AL_MIDI_FX_CTRL_2 = 0x16, + AL_MIDI_FX_CTRL_3 = 0x17, + AL_MIDI_FX_CTRL_4 = 0x18, + AL_MIDI_FX_CTRL_5 = 0x19, + AL_MIDI_FX_CTRL_6 = 0x1A, + AL_MIDI_FX_CTRL_7 = 0x1B, + AL_MIDI_FX_CTRL_8 = 0x1C, + AL_MIDI_FX_CTRL_9 = 0x1D, + AL_MIDI_SUSTAIN_CTRL = 0x40, + AL_MIDI_FX1_CTRL = 0x5B, + AL_MIDI_FX3_CTRL = 0x5D +}; + +enum AL_MIDImeta { + AL_MIDI_META_TEMPO = 0x51, + AL_MIDI_META_EOT = 0x2F +}; + + +#define AL_CMIDI_BLOCK_CODE 0xFE +#define AL_CMIDI_LOOPSTART_CODE 0x2E +#define AL_CMIDI_LOOPEND_CODE 0x2D + +#define AL_CMIDI_CNTRL_LOOPSTART 102 +#define AL_CMIDI_CNTRL_LOOPEND 103 +#define AL_CMIDI_CNTRL_LOOPCOUNT_SM 104 +#define AL_CMIDI_CNTRL_LOOPCOUNT_BIG 105 + +typedef struct { + u8* curPtr; // ptr to the next event + s32 lastTicks; // sequence clock ticks (used by alSeqSetLoc) + s32 curTicks; // sequence clock ticks of next event (used by loop end test) + s16 lastStatus; // the last status msg +} ALSeqMarker; + +typedef struct { + s32 ticks; // MIDI, Tempo and End events must start with ticks + u8 status; + u8 byte1; + u8 byte2; + u32 duration; +} ALMIDIEvent; + +typedef struct { + s32 ticks; + u8 status; + u8 type; + u8 len; + u8 byte1; + u8 byte2; + u8 byte3; +} ALTempoEvent; + +typedef struct { + s32 ticks; + u8 status; + u8 type; + u8 len; +} ALEndEvent; + +typedef struct { + struct ALVoice_s* voice; +} ALNoteEvent; + +typedef struct { + struct ALVoice_s* voice; + ALMicroTime delta; + u8 vol; +} ALVolumeEvent; + +typedef struct { + s16 vol; +} ALSeqpVolEvent; + +typedef struct { + ALSeqMarker* start; + ALSeqMarker* end; + s32 count; +} ALSeqpLoopEvent; + +typedef struct { + u8 chan; + u8 priority; +} ALSeqpPriorityEvent; + +typedef struct { + void* seq; // pointer to a seq (could be an ALSeq or an ALCSeq). +} ALSeqpSeqEvent; + +typedef struct { + ALBank* bank; +} ALSeqpBankEvent; + +typedef struct { + struct ALVoiceState_s* vs; + void* oscState; + u8 chan; +} ALOscEvent; + +typedef struct { + s16 type; + union { + ALMIDIEvent midi; + ALTempoEvent tempo; + ALEndEvent end; + ALNoteEvent note; + ALVolumeEvent vol; + ALSeqpLoopEvent loop; + ALSeqpVolEvent spvol; + ALSeqpPriorityEvent sppriority; + ALSeqpSeqEvent spseq; + ALSeqpBankEvent spbank; + ALOscEvent osc; + } msg; +} ALEvent; + +typedef struct { + ALLink node; + ALMicroTime delta; + ALEvent evt; +} ALEventListItem; + +typedef struct { + ALLink freeList; + ALLink allocList; + s32 eventCount; +} ALEventQueue; + +void alEvtqNew(ALEventQueue* evtq, ALEventListItem* items, s32 itemCount); +ALMicroTime alEvtqNextEvent(ALEventQueue* evtq, ALEvent* evt); +void alEvtqPostEvent(ALEventQueue* evtq, ALEvent* evt, ALMicroTime delta); +void alEvtqFlush(ALEventQueue* evtq); +void alEvtqFlushType(ALEventQueue* evtq, s16 type); + +#define AL_PHASE_ATTACK 0 +#define AL_PHASE_NOTEON 0 +#define AL_PHASE_DECAY 1 +#define AL_PHASE_SUSTAIN 2 +#define AL_PHASE_RELEASE 3 +#define AL_PHASE_SUSTREL 4 + +typedef struct ALVoiceState_s { + struct ALVoiceState_s* next; // MUST be first + ALVoice voice; + ALSound* sound; + ALMicroTime envEndTime; // time of envelope segment end + f32 pitch; // currect pitch ratio + f32 vibrato; // current value of the vibrato + u8 envGain; // current envelope gain + u8 channel; // channel assignment + u8 key; // note on key number + u8 velocity; // note on velocity + u8 envPhase; // what envelope phase + u8 phase; + u8 tremelo; // current value of the tremelo + u8 flags; // bit 0 tremelo flag, bit 1 vibrato flag +} ALVoiceState; + +typedef struct { + ALInstrument* instrument; // instrument assigned to this chan + s16 bendRange; // pitch bend range in cents + ALFxId fxId; // type of fx assigned to this chan + ALPan pan; // overall pan for this chan + u8 priority; // priority for this chan + u8 vol; // current volume for this chan + u8 fxmix; // current fx mix for this chan + u8 sustain; // current sustain pedal state + f32 pitchBend; // current pitch bend val in cents +} ALChanState; + +typedef struct ALSeq_s { + u8* base; // ptr to start of sequence file + u8* trackStart; // ptr to first MIDI event + u8* curPtr; // ptr to next event to read + s32 lastTicks; // MIDI ticks for last event + s32 len; // length of sequence in bytes + f32 qnpt; // qrter notes / tick (1/division) + s16 division; // ticks per quarter note + s16 lastStatus; // for running status +} ALSeq; + +typedef struct { + u32 trackOffset[16]; + u32 division; +} ALCMidiHdr; + +typedef struct ALCSeq_s { + ALCMidiHdr* base; // ptr to start of sequence file + u32 validTracks; // set of flags, showing valid tracks + f32 qnpt; // qrter notes / tick (1/division) + u32 lastTicks; // keep track of ticks incase app wants + u32 lastDeltaTicks; // number of delta ticks of last event + u32 deltaFlag; // flag: set if delta's not subtracted + u8* curLoc[16]; // ptr to current track location, may point to next event, or may point to a backup code + u8* curBUPtr[16]; // ptr to next event if in backup mode + u8 curBULen[16]; // if > 0, then in backup mode + u8 lastStatus[16]; // for running status + u32 evtDeltaTicks[16]; // delta time to next event +} ALCSeq; + +typedef struct { + u32 validTracks; + s32 lastTicks; + u32 lastDeltaTicks; + u8* curLoc[16]; + u8* curBUPtr[16]; + u8 curBULen[16]; + u8 lastStatus[16]; + u32 evtDeltaTicks[16]; +} ALCSeqMarker; + +#define NO_SOUND_ERR_MASK (1 << 0) +#define NOTE_OFF_ERR_MASK (1 << 1) +#define NO_VOICE_ERR_MASK (1 << 2) + +typedef struct { + s32 maxVoices; // max number of voices to alloc + s32 maxEvents; // max internal events to support + u8 maxChannels; // max MIDI channels to support (16) + u8 debugFlags; // control which error get reported + ALHeap* heap; // ptr to initialized heap + void* initOsc; + void* updateOsc; + void* stopOsc; +} ALSeqpConfig; + +typedef ALMicroTime (*ALOscInit)(void** oscState, f32* initVal, u8 oscType, u8 oscRate, u8 oscDepth, u8 oscDelay); +typedef ALMicroTime (*ALOscUpdate)(void* oscState, f32* updateVal); +typedef void (*ALOscStop)(void* oscState); + +typedef struct { + ALPlayer node; // note: must be first in structure + ALSynth* drvr; // reference to the client driver + ALSeq* target; // current sequence + ALMicroTime curTime; + ALBank* bank; // current ALBank + s32 uspt; // microseconds per tick + s32 nextDelta; // microseconds to next callback + s32 state; + u16 chanMask; // active channels + s16 vol; // overall sequence volume + u8 maxChannels; // number of MIDI channels + u8 debugFlags; // control which error get reported + ALEvent nextEvent; + ALEventQueue evtq; + ALMicroTime frameTime; + ALChanState* chanState; // 16 channels for MIDI + ALVoiceState* vAllocHead; // list head for allocated voices + ALVoiceState* vAllocTail; // list tail for allocated voices + ALVoiceState* vFreeList; // list of free voice state structs + ALOscInit initOsc; + ALOscUpdate updateOsc; + ALOscStop stopOsc; + ALSeqMarker* loopStart; + ALSeqMarker* loopEnd; + s32 loopCount; // -1 = loop forever, 0 = no loop +} ALSeqPlayer; + +typedef struct { + ALPlayer node; // note: must be first in structure + ALSynth* drvr; // reference to the client driver + ALCSeq* target; // current sequence + ALMicroTime curTime; + ALBank* bank; // current ALBank + s32 uspt; // microseconds per tick + s32 nextDelta; // microseconds to next callback + s32 state; + u16 chanMask; // active channels + s16 vol; // overall sequence volume + u8 maxChannels; // number of MIDI channels + u8 debugFlags; // control which error get reported + ALEvent nextEvent; + ALEventQueue evtq; + ALMicroTime frameTime; + ALChanState* chanState; // 16 channels for MIDI + ALVoiceState* vAllocHead; // list head for allocated voices + ALVoiceState* vAllocTail; // list tail for allocated voices + ALVoiceState* vFreeList; // list of free voice state structs + ALOscInit initOsc; + ALOscUpdate updateOsc; + ALOscStop stopOsc; +} ALCSPlayer; + +// Sequence data representation routines + +void alSeqNew(ALSeq* seq, u8* ptr, s32 len); +void alSeqNextEvent(ALSeq* seq, ALEvent* event); +s32 alSeqGetTicks(ALSeq* seq); +f32 alSeqTicksToSec(ALSeq* seq, s32 ticks, u32 tempo); +u32 alSeqSecToTicks(ALSeq* seq, f32 sec, u32 tempo); +void alSeqNewMarker(ALSeq* seq, ALSeqMarker* m, u32 ticks); +void alSeqSetLoc(ALSeq* seq, ALSeqMarker* marker); +void alSeqGetLoc(ALSeq* seq, ALSeqMarker* marker); + +// Compact Sequence data representation routines + +void alCSeqNew(ALCSeq* seq, u8* ptr); +void alCSeqNextEvent(ALCSeq* seq,ALEvent* evt); +s32 alCSeqGetTicks(ALCSeq* seq); +f32 alCSeqTicksToSec(ALCSeq* seq, s32 ticks, u32 tempo); +u32 alCSeqSecToTicks(ALCSeq* seq, f32 sec, u32 tempo); +void alCSeqNewMarker(ALCSeq* seq, ALCSeqMarker* m, u32 ticks); +void alCSeqSetLoc(ALCSeq* seq, ALCSeqMarker* marker); +void alCSeqGetLoc(ALCSeq* seq, ALCSeqMarker* marker); + +// Sequence Player routines + +f32 alCents2Ratio(s32 cents); + +void alSeqpNew(ALSeqPlayer* seqp, ALSeqpConfig* config); +void alSeqpDelete(ALSeqPlayer* seqp); +void alSeqpSetSeq(ALSeqPlayer* seqp, ALSeq* seq); +ALSeq* alSeqpGetSeq(ALSeqPlayer* seqp); +void alSeqpPlay(ALSeqPlayer* seqp); +void alSeqpStop(ALSeqPlayer* seqp); +s32 alSeqpGetState(ALSeqPlayer* seqp); +void alSeqpSetBank(ALSeqPlayer* seqp, ALBank* b); +void alSeqpSetTempo(ALSeqPlayer* seqp, s32 tempo); +s32 alSeqpGetTempo(ALSeqPlayer* seqp); +s16 alSeqpGetVol(ALSeqPlayer* seqp); // Master volume control +void alSeqpSetVol(ALSeqPlayer* seqp, s16 vol); +void alSeqpLoop(ALSeqPlayer* seqp, ALSeqMarker* start, ALSeqMarker* end, s32 count); + +void alSeqpSetChlProgram(ALSeqPlayer* seqp, u8 chan, u8 prog); +s32 alSeqpGetChlProgram(ALSeqPlayer* seqp, u8 chan); +void alSeqpSetChlFXMix(ALSeqPlayer* seqp, u8 chan, u8 fxmix); +u8 alSeqpGetChlFXMix(ALSeqPlayer* seqp, u8 chan); +void alSeqpSetChlVol(ALSeqPlayer* seqp, u8 chan, u8 vol); +u8 alSeqpGetChlVol(ALSeqPlayer* seqp, u8 chan); +void alSeqpSetChlPan(ALSeqPlayer* seqp, u8 chan, ALPan pan); +ALPan alSeqpGetChlPan(ALSeqPlayer* seqp, u8 chan); +void alSeqpSetChlPriority(ALSeqPlayer* seqp, u8 chan, u8 priority); +u8 alSeqpGetChlPriority(ALSeqPlayer* seqp, u8 chan); +void alSeqpSendMidi(ALSeqPlayer* seqp, s32 ticks, u8 status, u8 byte1, u8 byte2); + +// Maintain backwards compatibility with old routine names. + +#define alSeqpSetProgram alSeqpSetChlProgram +#define alSeqpGetProgram alSeqpGetChlProgram +#define alSeqpSetFXMix alSeqpSetChlFXMix +#define alSeqpGetFXMix alSeqpGetChlFXMix +#define alSeqpSetPan alSeqpSetChlPan +#define alSeqpGetPan alSeqpGetChlPan +#define alSeqpSetChannelPriority alSeqpSetChlPriority +#define alSeqpGetChannelPriority alSeqpGetChlPriority + +// Compressed Sequence Player routines + +void alCSPNew(ALCSPlayer* seqp, ALSeqpConfig* config); +void alCSPDelete(ALCSPlayer* seqp); +void alCSPSetSeq(ALCSPlayer* seqp, ALCSeq* seq); +ALCSeq* alCSPGetSeq(ALCSPlayer* seqp); +void alCSPPlay(ALCSPlayer* seqp); +void alCSPStop(ALCSPlayer* seqp); +s32 alCSPGetState(ALCSPlayer* seqp); +void alCSPSetBank(ALCSPlayer* seqp, ALBank* b); +void alCSPSetTempo(ALCSPlayer* seqp, s32 tempo); +s32 alCSPGetTempo(ALCSPlayer* seqp); +s16 alCSPGetVol(ALCSPlayer* seqp); +void alCSPSetVol(ALCSPlayer* seqp, s16 vol); + +void alCSPSetChlProgram(ALCSPlayer* seqp, u8 chan, u8 prog); +s32 alCSPGetChlProgram(ALCSPlayer* seqp, u8 chan); +void alCSPSetChlFXMix(ALCSPlayer* seqp, u8 chan, u8 fxmix); +u8 alCSPGetChlFXMix(ALCSPlayer* seqp, u8 chan); +void alCSPSetChlPan(ALCSPlayer* seqp, u8 chan, ALPan pan); +ALPan alCSPGetChlPan(ALCSPlayer* seqp, u8 chan); +void alCSPSetChlVol(ALCSPlayer* seqp, u8 chan, u8 vol); +u8 alCSPGetChlVol(ALCSPlayer* seqp, u8 chan); +void alCSPSetChlPriority(ALCSPlayer* seqp, u8 chan, u8 priority); +u8 alCSPGetChlPriority(ALCSPlayer* seqp, u8 chan); +void alCSPSendMidi(ALCSPlayer* seqp, s32 ticks, u8 status, u8 byte1, u8 byte2); + +// Maintain backwards compatibility with old routine names. + +#define alCSPSetProgram alCSPSetChlProgram +#define alCSPGetProgram alCSPGetChlProgram +#define alCSPSetFXMix alCSPSetChlFXMix +#define alCSPGetFXMix alCSPGetChlFXMix +#define alCSPSetPan alCSPSetChlPan +#define alCSPGetPan alCSPGetChlPan +#define alCSPSetChannelPriority alCSPSetChlPriority +#define alCSPGetChannelPriority alCSPGetChlPriority + + + +/*********************************************************************** + * Sound Player stuff + ***********************************************************************/ + +typedef struct { + s32 maxSounds; + s32 maxEvents; + ALHeap* heap; +} ALSndpConfig; + +typedef struct { + ALPlayer node; // note: must be first in structure + ALEventQueue evtq; + ALEvent nextEvent; + ALSynth* drvr; // reference to the client driver + s32 target; + void* sndState; + s32 maxSounds; + ALMicroTime frameTime; + ALMicroTime nextDelta; // microseconds to next callback + ALMicroTime curTime; +} ALSndPlayer; + +typedef s16 ALSndId; + +void alSndpNew(ALSndPlayer* sndp, ALSndpConfig* c); +void alSndpDelete(ALSndPlayer* sndp); + +ALSndId alSndpAllocate(ALSndPlayer* sndp, ALSound* sound); +void alSndpDeallocate(ALSndPlayer* sndp, ALSndId id); + +void alSndpSetSound(ALSndPlayer* sndp, ALSndId id); +ALSndId alSndpGetSound(ALSndPlayer* sndp); + +void alSndpPlay(ALSndPlayer* sndp); +void alSndpPlayAt(ALSndPlayer* sndp, ALMicroTime delta); +void alSndpStop(ALSndPlayer* sndp); + +void alSndpSetVol(ALSndPlayer* sndp, s16 vol); +void alSndpSetPitch(ALSndPlayer* sndp, f32 pitch); +void alSndpSetPan(ALSndPlayer* sndp, ALPan pan); +void alSndpSetPriority(ALSndPlayer* sndp, ALSndId id, u8 priority); + +void alSndpSetFXMix(ALSndPlayer* sndp, u8 mix); +s32 alSndpGetState(ALSndPlayer* sndp); + +#endif diff --git a/src/libultra/audio/libaudio_abi.h b/src/libultra/audio/libaudio_abi.h new file mode 100644 index 0000000000..725101796c --- /dev/null +++ b/src/libultra/audio/libaudio_abi.h @@ -0,0 +1,383 @@ +#ifndef _ABI_H_ +#define _ABI_H_ + +#define _SHIFTL(v, s, w) \ + ((u32) (((u32)(v) & ((1 << (w)) - 1)) << (s))) + +#define _SHIFTR(v, s, w) \ + ((u32)(((u32)(v) >> (s)) & ((1 << (w)) - 1))) + +/* Audio commands: */ +#define A_SPNOOP 0 +#define A_ADPCM 1 +#define A_CLEARBUFF 2 +#define A_ENVMIXER 3 +#define A_LOADBUFF 4 +#define A_RESAMPLE 5 +#define A_SAVEBUFF 6 +#define A_SEGMENT 7 +#define A_SETBUFF 8 +#define A_SETVOL 9 +#define A_DMEMMOVE 10 +#define A_LOADADPCM 11 +#define A_MIXER 12 +#define A_INTERLEAVE 13 +#define A_POLEF 14 +#define A_SETLOOP 15 + +#define ACMD_SIZE 32 + +/** + * Audio flags + */ + +#define A_INIT 0x01 +#define A_CONTINUE 0x00 +#define A_LOOP 0x02 +#define A_OUT 0x02 +#define A_LEFT 0x02 +#define A_RIGHT 0x00 +#define A_VOL 0x04 +#define A_RATE 0x00 +#define A_AUX 0x08 +#define A_NOAUX 0x00 +#define A_MAIN 0x00 +#define A_MIX 0x10 + +#if defined(_LANGUAGE_C) || defined(_LANGUAGE_C_PLUS_PLUS) + +/** + * Data Structures. + */ + +typedef struct { + unsigned int cmd : 8; + unsigned int flags : 8; + unsigned int gain : 16; + unsigned int addr; +} Aadpcm; + +typedef struct { + unsigned int cmd : 8; + unsigned int flags : 8; + unsigned int gain : 16; + unsigned int addr; +} Apolef; + +typedef struct { + unsigned int cmd : 8; + unsigned int flags : 8; + unsigned int pad1 : 16; + unsigned int addr; +} Aenvelope; + +typedef struct { + unsigned int cmd : 8; + unsigned int pad1 : 8; + unsigned int dmem : 16; + unsigned int pad2 : 16; + unsigned int count : 16; +} Aclearbuff; + +typedef struct { + unsigned int cmd : 8; + unsigned int pad1 : 8; + unsigned int pad2 : 16; + unsigned int inL : 16; + unsigned int inR : 16; +} Ainterleave; + +typedef struct { + unsigned int cmd : 8; + unsigned int pad1 : 24; + unsigned int addr; +} Aloadbuff; + +typedef struct { + unsigned int cmd : 8; + unsigned int flags : 8; + unsigned int pad1 : 16; + unsigned int addr; +} Aenvmixer; + +typedef struct { + unsigned int cmd : 8; + unsigned int flags : 8; + unsigned int gain : 16; + unsigned int dmemi : 16; + unsigned int dmemo : 16; +} Amixer; + +typedef struct { + unsigned int cmd : 8; + unsigned int flags : 8; + unsigned int dmem2 : 16; + unsigned int addr; +} Apan; + +typedef struct { + unsigned int cmd : 8; + unsigned int flags : 8; + unsigned int pitch : 16; + unsigned int addr; +} Aresample; + +typedef struct { + unsigned int cmd : 8; + unsigned int flags : 8; + unsigned int pad1 : 16; + unsigned int addr; +} Areverb; + +typedef struct { + unsigned int cmd : 8; + unsigned int pad1 : 24; + unsigned int addr; +} Asavebuff; + +typedef struct { + unsigned int cmd : 8; + unsigned int pad1 : 24; + unsigned int pad2 : 2; + unsigned int number : 4; + unsigned int base : 24; +} Asegment; + +typedef struct { + unsigned int cmd : 8; + unsigned int flags : 8; + unsigned int dmemin : 16; + unsigned int dmemout : 16; + unsigned int count : 16; +} Asetbuff; + +typedef struct { + unsigned int cmd : 8; + unsigned int flags : 8; + unsigned int vol : 16; + unsigned int voltgt : 16; + unsigned int volrate : 16; +} Asetvol; + +typedef struct { + unsigned int cmd : 8; + unsigned int pad1 : 8; + unsigned int dmemin : 16; + unsigned int dmemout : 16; + unsigned int count : 16; +} Admemmove; + +typedef struct { + unsigned int cmd : 8; + unsigned int pad1 : 8; + unsigned int count : 16; + unsigned int addr; +} Aloadadpcm; + +typedef struct { + unsigned int cmd : 8; + unsigned int pad1 : 8; + unsigned int pad2 : 16; + unsigned int addr; +} Asetloop; + +/** + * Generic Acmd Packet + */ + +typedef struct { + unsigned int w0; + unsigned int w1; +} Awords; + +typedef union { + Awords words; + Aadpcm adpcm; + Apolef polef; + Aclearbuff clearbuff; + Aenvelope envelope; + Ainterleave interleave; + Aloadbuff loadbuff; + Aenvmixer envmixer; + Aresample resample; + Areverb reverb; + Asavebuff savebuff; + Asegment segment; + Asetbuff setbuff; + Asetvol setvol; + Admemmove dmemmove; + Aloadadpcm loadadpcm; + Amixer mixer; + Asetloop setloop; + long long int force_union_align; /* dummy, force alignment */ +} Acmd; + +/** + * ADPCM State + */ + +#define ADPCMVSIZE 8 +#define ADPCMFSIZE 16 +typedef short ADPCM_STATE[ADPCMFSIZE]; + +/** + * Pole filter state + */ +typedef short POLEF_STATE[4]; + +/** + * Resampler state + */ +typedef short RESAMPLE_STATE[16]; + +/** + * Resampler constants + */ +#define UNITY_PITCH 0x8000 +#define MAX_RATIO 1.99996 /* within .03 cents of +1 octave */ + +/** + * Enveloper/Mixer state + */ +typedef short ENVMIX_STATE[40]; + +/** + * Macros to assemble the audio command list + */ + +#define aADPCMdec(pkt, f, s) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = _SHIFTL(A_ADPCM, 24, 8) | _SHIFTL(f, 16, 8); \ + _a->words.w1 = (unsigned int)(s); \ +} + +#define aPoleFilter(pkt, f, g, s) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = (_SHIFTL(A_POLEF, 24, 8) | _SHIFTL(f, 16, 8) | \ + _SHIFTL(g, 0, 16)); \ + _a->words.w1 = (unsigned int)(s); \ +} + +#define aClearBuffer(pkt, d, c) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = _SHIFTL(A_CLEARBUFF, 24, 8) | _SHIFTL(d, 0, 24); \ + _a->words.w1 = (unsigned int)(c); \ +} + +#define aEnvMixer(pkt, f, s) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = _SHIFTL(A_ENVMIXER, 24, 8) | _SHIFTL(f, 16, 8); \ + _a->words.w1 = (unsigned int)(s); \ +} + +#define aInterleave(pkt, l, r) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = _SHIFTL(A_INTERLEAVE, 24, 8); \ + _a->words.w1 = _SHIFTL(l, 16, 16) | _SHIFTL(r, 0, 16); \ +} + +#define aLoadBuffer(pkt, s) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = _SHIFTL(A_LOADBUFF, 24, 8); \ + _a->words.w1 = (unsigned int)(s); \ +} + +#define aMix(pkt, f, g, i, o) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = (_SHIFTL(A_MIXER, 24, 8) | _SHIFTL(f, 16, 8) | \ + _SHIFTL(g, 0, 16)); \ + _a->words.w1 = _SHIFTL(i, 16, 16) | _SHIFTL(o, 0, 16); \ +} + +#define aPan(pkt, f, d, s) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = (_SHIFTL(A_PAN, 24, 8) | _SHIFTL(f, 16, 8) | \ + _SHIFTL(d, 0, 16)); \ + _a->words.w1 = (unsigned int)(s); \ +} + +#define aResample(pkt, f, p, s) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = (_SHIFTL(A_RESAMPLE, 24, 8) | _SHIFTL(f, 16, 8) | \ + _SHIFTL(p, 0, 16)); \ + _a->words.w1 = (unsigned int)(s); \ +} + +#define aSaveBuffer(pkt, s) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = _SHIFTL(A_SAVEBUFF, 24, 8); \ + _a->words.w1 = (unsigned int)(s); \ +} + +#define aSegment(pkt, s, b) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = _SHIFTL(A_SEGMENT, 24, 8); \ + _a->words.w1 = _SHIFTL(s, 24, 8) | _SHIFTL(b, 0, 24); \ +} + +#define aSetBuffer(pkt, f, i, o, c) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = (_SHIFTL(A_SETBUFF, 24, 8) | _SHIFTL(f, 16, 8) | \ + _SHIFTL(i, 0, 16)); \ + _a->words.w1 = _SHIFTL(o, 16, 16) | _SHIFTL(c, 0, 16); \ +} + +#define aSetVolume(pkt, f, v, t, r) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = (_SHIFTL(A_SETVOL, 24, 8) | _SHIFTL(f, 16, 16) | \ + _SHIFTL(v, 0, 16)); \ + _a->words.w1 = _SHIFTL(t, 16, 16) | _SHIFTL(r, 0, 16); \ +} + +#define aSetLoop(pkt, a) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + _a->words.w0 = _SHIFTL(A_SETLOOP, 24, 8); \ + _a->words.w1 = (unsigned int)(a); \ +} + +#define aDMEMMove(pkt, i, o, c) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = _SHIFTL(A_DMEMMOVE, 24, 8) | _SHIFTL(i, 0, 24); \ + _a->words.w1 = _SHIFTL(o, 16, 16) | _SHIFTL(c, 0, 16); \ +} + +#define aLoadADPCM(pkt, c, d) \ +{ \ + Acmd* _a = (Acmd*)pkt; \ + \ + _a->words.w0 = _SHIFTL(A_LOADADPCM, 24, 8) | _SHIFTL(c, 0, 24); \ + _a->words.w1 = (unsigned int)(d); \ +} + +#endif + +#endif diff --git a/src/libultra/audio/load.c b/src/libultra/audio/load.c index 8b13789179..86486ce3a7 100644 --- a/src/libultra/audio/load.c +++ b/src/libultra/audio/load.c @@ -1 +1,375 @@ +#include "libaudio.h" +#include "synthInternals.h" +#include "ultra64/R4300.h" +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#define ADPCMFBYTES 9 +#define LFSAMPLES 4 + +static Acmd* _decodeChunk(Acmd* ptr, ALLoadFilter* f, s32 tsam, s32 nbytes, s16 outp, s16 inp, u32 flags); + +Acmd* alAdpcmPull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p) { + Acmd* ptr = p; + s16 inp; + s32 tsam; + s32 nframes; + s32 nbytes; + s32 overFlow; + s32 startZero; + s32 nOver; + s32 nSam; + s32 op; + s32 nLeft; + s32 bEnd; + s32 decoded = false; + s32 looped = false; + + ALLoadFilter* f = (ALLoadFilter*)filter; + + if (outCount == 0) { + return ptr; + } + + inp = AL_DECODER_IN; + aLoadADPCM(ptr++, f->bookSize, K0_TO_PHYS(f->table->waveInfo.adpcmWave.book->book)); + + looped = ((u32)(outCount + f->sample) > f->loop.end) && (f->loop.count != 0); + if (looped) { + nSam = f->loop.end - f->sample; + } else { + nSam = outCount; + } + + if (f->lastsam != 0) { + nLeft = ADPCMFSIZE - f->lastsam; + } else { + nLeft = 0; + } + tsam = nSam - nLeft; + if (tsam < 0) { + tsam = 0; + } + + nframes = (tsam + ADPCMFSIZE - 1) >> LFSAMPLES; + nbytes = nframes * ADPCMFBYTES; + + if (looped) { + ptr = _decodeChunk(ptr, f, tsam, nbytes, *outp, inp, f->first); + + // Fix up output pointer, which will be used as the input pointer by the following module. + if (f->lastsam != 0) { + *outp += f->lastsam << 1; + } else { + *outp += ADPCMFSIZE << 1; + } + + // Now fix up state info to reflect the loop start point + f->lastsam = f->loop.start & 0xF; + f->memin = (uintptr_t)f->table->base + ADPCMFBYTES * ((s32)(f->loop.start >> LFSAMPLES) + 1); + f->sample = f->loop.start; + + bEnd = *outp; + while (outCount > nSam) { + outCount -= nSam; + + // Put next one after the end of the last lot - on the frame boundary (32 byte) after the end. + op = (bEnd + ((nframes + 1) << (LFSAMPLES + 1))) & ~0x1F; + + // The actual end of data + bEnd += nSam << 1; + + // -1 is loop forever - the loop count is not exact now for small loops! + if (f->loop.count != -1u && f->loop.count != 0) { + f->loop.count--; + } + + // What's left to compute. + nSam = MIN((u32)outCount, f->loop.end - f->loop.start); + tsam = nSam - ADPCMFSIZE + f->lastsam; + if (tsam < 0) { + tsam = 0; + } + nframes = (tsam + ADPCMFSIZE - 1) >> LFSAMPLES; + nbytes = nframes * ADPCMFBYTES; + ptr = _decodeChunk(ptr, f, tsam, nbytes, op, inp, f->first | A_LOOP); + // Merge the two sections in DMEM. + aDMEMMove(ptr++, op + (f->lastsam << 1), bEnd, nSam << 1); + } + + f->lastsam = (outCount + f->lastsam) & 0xF; + f->sample += outCount; + f->memin += ADPCMFBYTES * nframes; + return ptr; + } + + // The unlooped case, which is executed most of the time + + nSam = nframes << LFSAMPLES; + + // overFlow is the number of bytes past the end of the bitstream I try to generate + overFlow = f->memin + nbytes - ((uintptr_t)f->table->base + f->table->len); + if (overFlow < 0) { + overFlow = 0; + } + nOver = (overFlow / ADPCMFBYTES) << LFSAMPLES; + if (nOver > nSam + nLeft) { + nOver = nSam + nLeft; + } + + nbytes -= overFlow; + + if (nOver - (nOver & 0xF) < outCount) { + decoded = true; + ptr = _decodeChunk(ptr, f, nSam - nOver, nbytes, *outp, inp, f->first); + + if (f->lastsam != 0) { + *outp += f->lastsam << 1; + } else { + *outp += ADPCMFSIZE << 1; + } + + f->lastsam = (outCount + f->lastsam) & 0xF; + f->sample += outCount; + f->memin += ADPCMFBYTES * nframes; + } else { + f->lastsam = 0; + f->memin += ADPCMFBYTES * nframes; + } + + // Put zeros in if necessary + if (nOver != 0) { + f->lastsam = 0; + if (decoded) { + startZero = (nLeft + nSam - nOver) << 1; + } else { + startZero = 0; + } + aClearBuffer(ptr++, startZero + *outp, nOver << 1); + } + return ptr; +} + +Acmd* alRaw16Pull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p) { + Acmd* ptr = p; + s32 nbytes; + s32 dramLoc; + s32 dramAlign; + s32 dmemAlign; + s32 overFlow; + s32 startZero; + s32 nSam; + s32 op; + ALLoadFilter* f = (ALLoadFilter*)filter; + ALFilter* a = (ALFilter*)filter; + + if (outCount == 0) { + return ptr; + } + + if ((u32)(outCount + f->sample) > f->loop.end && f->loop.count != 0) { + nSam = f->loop.end - f->sample; + nbytes = nSam << 1; + + if (nSam > 0) { + dramLoc = (f->dma)(f->memin, nbytes, f->dmaState); + + // Make sure enough is loaded into DMEM to take care of 8 byte alignment + dramAlign = dramLoc & 7; + nbytes += dramAlign; + aSetBuffer(ptr++, 0, *outp, 0, nbytes + 8 - (nbytes & 7)); + aLoadBuffer(ptr++, dramLoc - dramAlign); + } else { + dramAlign = 0; + } + + // Fix up output pointer to allow for dram alignment + *outp += dramAlign; + + f->memin = (uintptr_t)f->table->base + (f->loop.start << 1); + f->sample = f->loop.start; + op = *outp; + + while (outCount > nSam) { + op += (nSam << 1); + outCount -= nSam; + + // -1 is loop forever + if (f->loop.count != -1u && f->loop.count != 0) { + f->loop.count--; + } + + // What to compute. + nSam = MIN((u32)outCount, f->loop.end - f->loop.start); + nbytes = nSam << 1; + + // Do the next section, same as last. + dramLoc = (f->dma)(f->memin, nbytes, f->dmaState); + + // Make sure enough is loaded into DMEM to take care of 8 byte alignment + dramAlign = dramLoc & 7; + nbytes += dramAlign; + if (op & 7) { + dmemAlign = 8 - (op & 7); + } else { + dmemAlign = 0; + } + + aSetBuffer(ptr++, 0, op + dmemAlign, 0, nbytes + 8 - (nbytes & 7)); + aLoadBuffer(ptr++, dramLoc - dramAlign); + + // Merge the two sections in DMEM. + if (dramAlign || dmemAlign) + aDMEMMove(ptr++, op + dramAlign + dmemAlign, op, nSam << 1); + } + + f->sample += outCount; + f->memin += outCount << 1; + + return ptr; + } + + // The unlooped case, which is executed most of the time + // + // overFlow is the number of bytes past the end + // of the bitstream I try to generate + + nbytes = outCount << 1; + overFlow = f->memin + nbytes - ((uintptr_t)f->table->base + f->table->len); + if (overFlow < 0) { + overFlow = 0; + } + if (overFlow > nbytes) { + overFlow = nbytes; + } + + if (overFlow < nbytes) { + if (outCount > 0) { + nbytes -= overFlow; + dramLoc = f->dma(f->memin, nbytes, f->dmaState); + + // Make sure enough is loaded into DMEM to take care of 8 byte alignment + dramAlign = dramLoc & 7; + nbytes += dramAlign; + aSetBuffer(ptr++, 0, *outp, 0x000, nbytes + 8 - (nbytes & 7)); + aLoadBuffer(ptr++, dramLoc - dramAlign); + } else { + dramAlign = 0; + } + *outp += dramAlign; + + f->sample += outCount; + f->memin += outCount << 1; + } else { + f->memin += outCount << 1; + } + + // Put zeros in if necessary + if (overFlow != 0) { + startZero = (outCount << 1) - overFlow; + if (startZero < 0) { + startZero = 0; + } + aClearBuffer(ptr++, startZero + *outp, overFlow); + } + return ptr; +} + +s32 alLoadParam(void* filter, s32 paramID, void* param) { + ALLoadFilter* a = (ALLoadFilter*)filter; + ALFilter* f = (ALFilter*)filter; + + switch (paramID) { + case AL_FILTER_SET_WAVETABLE: + a->table = (ALWaveTable*)param; + a->memin = (uintptr_t)a->table->base; + a->sample = 0; + + switch (a->table->type) { + case AL_ADPCM_WAVE: + // Set up the correct handler + f->handler = alAdpcmPull; + + // Make sure the table length is an integer number of frames + a->table->len = ADPCMFBYTES * (s32)(a->table->len / ADPCMFBYTES); + + a->bookSize = 2 * a->table->waveInfo.adpcmWave.book->order * + a->table->waveInfo.adpcmWave.book->npredictors * ADPCMVSIZE; + if (a->table->waveInfo.adpcmWave.loop != NULL) { + a->loop.start = a->table->waveInfo.adpcmWave.loop->start; + a->loop.end = a->table->waveInfo.adpcmWave.loop->end; + a->loop.count = a->table->waveInfo.adpcmWave.loop->count; + alCopy(a->table->waveInfo.adpcmWave.loop->state, a->lstate, sizeof(ADPCM_STATE)); + } else { + a->loop.start = a->loop.end = a->loop.count = 0; + } + break; + + case AL_RAW16_WAVE: + f->handler = alRaw16Pull; + if (a->table->waveInfo.rawWave.loop != NULL) { + a->loop.start = a->table->waveInfo.rawWave.loop->start; + a->loop.end = a->table->waveInfo.rawWave.loop->end; + a->loop.count = a->table->waveInfo.rawWave.loop->count; + } else { + a->loop.start = a->loop.end = a->loop.count = 0; + } + break; + + default: + break; + } + break; + + case AL_FILTER_RESET: + a->lastsam = 0; + a->first = true; + a->sample = 0; + + // sct 2/14/96 - Check table since it is initialized to null and get loop info according to table type. + if (a->table != NULL) { + a->memin = (uintptr_t)a->table->base; + if (a->table->type == AL_ADPCM_WAVE) { + if (a->table->waveInfo.adpcmWave.loop != NULL) { + a->loop.count = a->table->waveInfo.adpcmWave.loop->count; + } + } else if (a->table->type == AL_RAW16_WAVE) { + if (a->table->waveInfo.rawWave.loop != NULL) { + a->loop.count = a->table->waveInfo.rawWave.loop->count; + } + } + } + break; + + default: + break; + } +} + +static Acmd* _decodeChunk(Acmd* ptr, ALLoadFilter* f, s32 tsam, s32 nbytes, s16 outp, s16 inp, u32 flags) { + s32 dramAlign; + s32 dramLoc; + + if (nbytes > 0) { + dramLoc = f->dma(f->memin, nbytes, f->dmaState); + // Make sure enough is loaded into DMEM to take care of 8 byte alignment + dramAlign = dramLoc & 7; + nbytes += dramAlign; + aSetBuffer(ptr++, 0, inp, 0, nbytes + 8 - (nbytes & 7)); + aLoadBuffer(ptr++, dramLoc - dramAlign); + } else { + dramAlign = 0; + } + + if (flags & A_LOOP) { + aSetLoop(ptr++, K0_TO_PHYS(f->lstate)); + } + + aSetBuffer(ptr++, 0, inp + dramAlign, outp, tsam << 1); + aADPCMdec(ptr++, flags, K0_TO_PHYS(f->state)); + f->first = false; + + return ptr; +} diff --git a/src/libultra/audio/mainbus.c b/src/libultra/audio/mainbus.c index 8b13789179..57a86d0d54 100644 --- a/src/libultra/audio/mainbus.c +++ b/src/libultra/audio/mainbus.c @@ -1 +1,38 @@ +#include "libaudio.h" +#include "synthInternals.h" +Acmd* alMainBusPull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p) { + Acmd* ptr = p; + ALMainBus* m = (ALMainBus*)filter; + ALFilter** sources = m->sources; + s32 i; + + // clear the output buffers here + aClearBuffer(ptr++, AL_MAIN_L_OUT, outCount << 1); + aClearBuffer(ptr++, AL_MAIN_R_OUT, outCount << 1); + + for (i = 0; i < m->sourceCount; i++) { + ptr = sources[i]->handler(sources[i], outp, outCount, sampleOffset, ptr); + aSetBuffer(ptr++, 0, 0x000, 0x000, outCount << 1); + aMix(ptr++, 0, 0x7FFF, AL_AUX_L_OUT, AL_MAIN_L_OUT); + aMix(ptr++, 0, 0x7FFF, AL_AUX_R_OUT, AL_MAIN_R_OUT); + } + + return ptr; +} + +s32 alMainBusParam(void* filter, s32 paramID, void* param) { + ALMainBus* m = (ALMainBus*)filter; + ALFilter** sources = m->sources; + + switch (paramID) { + case AL_FILTER_ADD_SOURCE: + sources[m->sourceCount++] = (ALFilter*)param; + break; + + default: + break; + } + + return 0; +} diff --git a/src/libultra/audio/resample.c b/src/libultra/audio/resample.c index 8b13789179..8301d22a1a 100644 --- a/src/libultra/audio/resample.c +++ b/src/libultra/audio/resample.c @@ -1 +1,98 @@ +#include "libaudio.h" +#include "synthInternals.h" +#include "ultra64/convert.h" +Acmd* alResamplePull(void* filter, s16* outp, s32 outCnt, s32 sampleOffset, Acmd* p) { + ALResampler* f = (ALResampler*)filter; + Acmd* ptr = p; + s16 inp; + s32 inCount; + ALFilter* source = f->filter.source; + s32 incr; + f32 finCount; + + inp = AL_DECODER_OUT; + + if (outCnt == 0) { + return ptr; + } + + // check if resampler is required + if (f->upitch != 0) { + ptr = (*source->handler)(source, &inp, outCnt, sampleOffset, p); + aDMEMMove(ptr++, inp, *outp, outCnt << 1); + } else { + // clip to maximum allowable pitch + // FIXME: should we check for some minimum as well? + if (f->ratio > MAX_RATIO) { + f->ratio = MAX_RATIO; + } + + // quantize the pitch + f->ratio = (s32)(f->ratio * UNITY_PITCH); + f->ratio = f->ratio / UNITY_PITCH; + + // determine how many samples to generate + finCount = f->delta + f->ratio * (f32)outCnt; + inCount = (s32)finCount; + f->delta = finCount - (f32)inCount; + + // ask all filters upstream from us to build their command lists. + ptr = (*source->handler)(source, &inp, inCount, sampleOffset, p); + + // construct our portion of the command list + incr = (s32)(f->ratio * UNITY_PITCH); + aSetBuffer(ptr++, 0, inp, *outp, outCnt << 1); + aResample(ptr++, f->first, incr, osVirtualToPhysical(f->state)); + f->first = false; + } + return ptr; +} + +s32 alResampleParam(void* filter, s32 paramID, void* param) { + ALFilter* f = (ALFilter*)filter; + ALResampler* r = (ALResampler*)filter; + union { + f32 f; + s32 i; + } data; + + switch (paramID) { + case AL_FILTER_SET_SOURCE: + f->source = (ALFilter*)param; + break; + + case AL_FILTER_RESET: + r->delta = 0.0f; + r->first = true; + r->motion = AL_STOPPED; + r->upitch = 0; + if (f->source != NULL) { + (*f->source->setParam)(f->source, AL_FILTER_RESET, 0); + } + break; + + case AL_FILTER_START: + r->motion = AL_PLAYING; + if (f->source != NULL) { + (*f->source->setParam)(f->source, AL_FILTER_START, 0); + } + break; + + case AL_FILTER_SET_PITCH: + data.i = (s32)param; + r->ratio = data.f; + break; + + case AL_FILTER_SET_UNITY_PITCH: + r->upitch = 1; + break; + + default: + if (f->source != NULL) { + (*f->source->setParam)(f->source, paramID, param); + } + break; + } + return 0; +} diff --git a/src/libultra/audio/reverb.c b/src/libultra/audio/reverb.c index 8b13789179..453147f125 100644 --- a/src/libultra/audio/reverb.c +++ b/src/libultra/audio/reverb.c @@ -1 +1,345 @@ +#include "libaudio.h" +#include "synthInternals.h" +#include "ultra64/convert.h" +#define RANGE 2.0 + +extern ALGlobals* alGlobals; + +Acmd* _loadOutputBuffer(ALFx* r, ALDelay* d, s32 buff, s32 incount, Acmd* p); +Acmd* _loadBuffer(ALFx* r, s16* curr_ptr, s32 buff, s32 count, Acmd* p); +Acmd* _saveBuffer(ALFx* r, s16* curr_ptr, s32 buff, s32 count, Acmd* p); +Acmd* _filterBuffer(ALLowPass* lp, s32 buff, s32 count, Acmd* p); +f32 _doModFunc(ALDelay* d, s32 count); + +Acmd* alFxPull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p) { + Acmd* ptr = p; + ALFx* r = (ALFx*)filter; + ALFilter* source = r->filter.source; + s16 i; + s16 buff1; + s16 buff2; + s16 input; + s16 output; + s16* in_ptr; + s16* out_ptr; + s16 gain; + s16* prev_out_ptr = NULL; + ALDelay* d; + ALDelay* pd; + + // pull channels going into this effect first + ptr = (*source->handler)(source, outp, outCount, sampleOffset, p); + + input = AL_AUX_L_OUT; + output = AL_AUX_R_OUT; + buff1 = AL_TEMP_0; + buff2 = AL_TEMP_1; + + aSetBuffer(ptr++, 0, 0x000, 0x000, outCount << 1); // set the buffer size + aMix(ptr++, 0, 0xDA83, AL_AUX_L_OUT, input); // .707L = L - .293L + aMix(ptr++, 0, 0x5A82, AL_AUX_R_OUT, input); // mix the AuxL and AuxR into the AuxL + // and write the mixed value to the delay line at r->input + ptr = _saveBuffer(r, r->input, input, outCount, ptr); + + // clear the AL_AUX_R_OUT + aClearBuffer(ptr++, output, outCount << 1); + + for (i = 0; i < r->section_count; i++) { + d = &r->delay[i]; // get the ALDelay structure + in_ptr = &r->input[-d->input]; + out_ptr = &r->input[-d->output]; + + if (in_ptr == prev_out_ptr) { + s16 t = buff2; + buff2 = buff1; + buff1 = t; + } else { + // load data at in_ptr into buff1 + ptr = _loadBuffer(r, in_ptr, buff1, outCount, ptr); + } + ptr = _loadOutputBuffer(r, d, buff2, outCount, ptr); + + if (d->ffcoef != 0) { + aMix(ptr++, 0, (u16)d->ffcoef, buff1, buff2); + if (d->rs == NULL && d->lp == NULL) { + ptr = _saveBuffer(r, out_ptr, buff2, outCount, ptr); + } + } + + if (d->fbcoef != 0) { + aMix(ptr++, 0, (u16)d->fbcoef, buff2, buff1); + ptr = _saveBuffer(r, in_ptr, buff1, outCount, ptr); + } + + if (d->lp != NULL) { + ptr = _filterBuffer(d->lp, buff2, outCount, ptr); + } + + if (d->rs == NULL) { + ptr = _saveBuffer(r, out_ptr, buff2, outCount, ptr); + } + + if (d->gain != 0) { + aMix(ptr++, 0, (u16)d->gain, buff2, output); + } + + prev_out_ptr = &r->input[d->output]; + } + + // bump the master delay line input pointer modulo the length + r->input += outCount; + if (r->input > &r->base[r->length]) { + r->input -= r->length; + } + + // output already in AL_AUX_R_OUT, just copy to AL_AUX_L_OUT + aDMEMMove(ptr++, output, AL_AUX_L_OUT, outCount << 1); + return ptr; +} + +s32 alFxParam(void* filter, s32 paramID, void* param) { + if (paramID == AL_FILTER_SET_SOURCE) { + ALFilter* f = (ALFilter*)filter; + f->source = (ALFilter*)param; + } + return 0; +} + +/** + * This routine gets called by alSynSetFXParam. No checking takes place to + * verify the validity of the paramID or the param value. input and output + * values must be 8 byte aligned, so round down any param passed. + */ +s32 alFxParamHdl(void* filter, s32 paramID, void* param) { + ALFx* f = (ALFx*)filter; + s32 p = (paramID - 2) % 8; + s32 s = (paramID - 2) / 8; + s32 val = *(s32*)param; + +#define INPUT_PARAM 0 +#define OUTPUT_PARAM 1 +#define FBCOEF_PARAM 2 +#define FFCOEF_PARAM 3 +#define GAIN_PARAM 4 +#define CHORUSRATE_PARAM 5 +#define CHORUSDEPTH_PARAM 6 +#define LPFILT_PARAM 7 + + switch (p) { + case INPUT_PARAM: + f->delay[s].input = (u32)val & 0xFFFFFFF8; + break; + + case OUTPUT_PARAM: + f->delay[s].output = (u32)val & 0xFFFFFFF8; + break; + + case FFCOEF_PARAM: + f->delay[s].ffcoef = (s16)val; + break; + + case FBCOEF_PARAM: + f->delay[s].fbcoef = (s16)val; + break; + + case GAIN_PARAM: + f->delay[s].gain = (s16)val; + break; + + case CHORUSRATE_PARAM: + f->delay[s].rsinc = (((f32)val / 1000) * RANGE) / alGlobals->drvr.outputRate; + break; + + // the following constant is derived from: + // + // ratio = 2^(cents/1200) + // + // and therefore for hundredths of a cent + // x + // ln(ratio) = --------------- + // (120,000)/ln(2) + // where + // 120,000/ln(2) = 173123.40... +#define CONVERT 173123.404906676 + + case CHORUSDEPTH_PARAM: + f->delay[s].rsgain = ((f32)val / CONVERT) * (f->delay[s].output - f->delay[s].input); + break; + + case LPFILT_PARAM: + if (f->delay[s].lp != NULL) { + f->delay[s].lp->fc = (s16)val; + _init_lpfilter(f->delay[s].lp); + } + break; + } + return 0; +} + +Acmd* _loadOutputBuffer(ALFx* r, ALDelay* d, s32 buff, s32 incount, Acmd* p) { + Acmd* ptr = p; + s32 ratio; + s32 count; + s32 rbuff = AL_TEMP_2; + s16* out_ptr; + f32 fincount; + f32 fratio; + f32 delta; + s32 ramalign = 0; + s32 length; + + // The following section implements the chorus resampling. Modulate where you pull + // the samples from, since you need varying amounts of samples. + if (d->rs != NULL) { + length = d->output - d->input; + // get the number of samples to modulate by + delta = _doModFunc(d, incount); + // find ratio of delta to delay length and quantize to same resolution as resampler + delta /= length; // convert delta from number of samples to a pitch ratio + delta = (s32)(delta * UNITY_PITCH); // quantize to value microcode will use + delta = delta / UNITY_PITCH; + // pitch ratio needs to be centered around 1, not zero + fratio = 1.0 - delta; + + // d->rs->delta is the difference between the fractional and integer value of the samples needed. + // fratio * incount + rs->delta gives the number of samples needed for this frame. + fincount = d->rs->delta + (fratio * (f32)incount); + count = (s32)fincount; // quantize to s32 + d->rs->delta = fincount - (f32)count; // calculate the round off and store + + // d->rsdelta is amount the out_ptr has deviated from its starting position. + // You calc the out_ptr by taking d->output - d->rsdelta, and then using the + // negative of that as an index into the delay buffer. loadBuffer that uses this + // value then bumps it up if it is below the delay buffer. + out_ptr = &r->input[-(d->output - d->rsdelta)]; + // calculate the number of samples needed to align the buffer + ramalign = ((intptr_t)out_ptr & 7) >> 1; + + // load the rbuff with samples, note that there will be ramalign worth of samples at the + // begining which you don't care about. + ptr = _loadBuffer(r, out_ptr - ramalign, rbuff, count + ramalign, ptr); + + // convert fratio to 16 bit fraction for microcode use + ratio = (s32)(fratio * UNITY_PITCH); + // set the buffers, and do the resample + aSetBuffer(ptr++, 0, rbuff + (ramalign << 1), buff, incount << 1); + aResample(ptr++, d->rs->first, ratio, osVirtualToPhysical(d->rs->state)); + + d->rs->first = false; // turn off first time flag + d->rsdelta += count - incount; // add the number of samples to d->rsdelta + } else { + out_ptr = &r->input[-d->output]; + ptr = _loadBuffer(r, out_ptr, buff, incount, ptr); + } + return ptr; +} + +/** + * This routine is for loading data from the delay line buff. If the + * address of curr_ptr < r->base, it will force it to be within r->base + * space, If the load goes past the end of r->base it will wrap around. + * Cause count bytes of data at curr_ptr (within the delay line) to be + * loaded into buff. (Buff is a dmem buffer) + */ +Acmd* _loadBuffer(ALFx* r, s16* curr_ptr, s32 buff, s32 count, Acmd* p) { + Acmd* ptr = p; + s32 after_end; + s32 before_end; + s16* updated_ptr; + s16* delay_end; + + delay_end = &r->base[r->length]; + + if (curr_ptr < r->base) { + curr_ptr += r->length; + } + updated_ptr = curr_ptr + count; + + if (updated_ptr > delay_end) { + after_end = updated_ptr - delay_end; + before_end = delay_end - curr_ptr; + + aSetBuffer(ptr++, 0, buff, 0x000, before_end << 1); + aLoadBuffer(ptr++, osVirtualToPhysical(curr_ptr)); + aSetBuffer(ptr++, 0, buff + (before_end << 1), 0, after_end << 1); + aLoadBuffer(ptr++, osVirtualToPhysical(r->base)); + } else { + aSetBuffer(ptr++, 0, buff, 0x000, count << 1); + aLoadBuffer(ptr++, osVirtualToPhysical(curr_ptr)); + } + aSetBuffer(ptr++, 0, 0x000, 0x000, count << 1); + return ptr; +} + +/** + * This routine is for writing data to the delay line buff. If the + * address of curr_ptr < r->base, it will force it to be within r->base + * space. If the write goes past the end of r->base, it will wrap around + * Cause count bytes of data at buff to be written to delay line, curr_ptr. + */ +Acmd* _saveBuffer(ALFx* r, s16* curr_ptr, s32 buff, s32 count, Acmd* p) { + Acmd* ptr = p; + s32 after_end; + s32 before_end; + s16* updated_ptr; + s16* delay_end; + + delay_end = &r->base[r->length]; + if (curr_ptr < r->base) { + // probably just security, shouldn't occur + curr_ptr += r->length; + } + updated_ptr = curr_ptr + count; + + if (updated_ptr > delay_end) { // if the data wraps past end of r->base + after_end = updated_ptr - delay_end; + before_end = delay_end - curr_ptr; + + aSetBuffer(ptr++, 0, 0x000, buff, before_end << 1); + aSaveBuffer(ptr++, osVirtualToPhysical(curr_ptr)); + aSetBuffer(ptr++, 0, 0x000, buff + (before_end << 1), after_end << 1); + aSaveBuffer(ptr++, osVirtualToPhysical(r->base)); + aSetBuffer(ptr++, 0, 0x000, 0x000, count << 1); + } else { + aSetBuffer(ptr++, 0, 0x000, buff, count << 1); + aSaveBuffer(ptr++, osVirtualToPhysical(curr_ptr)); + } + return ptr; +} + +Acmd* _filterBuffer(ALLowPass* lp, s32 buff, s32 count, Acmd* p) { + Acmd* ptr = p; + + aSetBuffer(ptr++, 0, buff, buff, count << 1); + aLoadADPCM(ptr++, 32, osVirtualToPhysical(lp->fcvec.fccoef)); + aPoleFilter(ptr++, lp->first, lp->fgain, osVirtualToPhysical(lp->fstate)); + lp->first = false; + + return ptr; +} + +/** + * Generate a triangle wave from -1 to 1, and find the current position + * in the wave. (Rate of the wave is controlled by d->rsinc, which is chorus + * rate) Multiply the current triangle wave value by d->rsgain, (chorus depth) + * which is expressed in number of samples back from output pointer the chorus + * should go at it's full chorus. In otherwords, this function returns a number + * of samples the output pointer should modulate backwards. + */ +f32 _doModFunc(ALDelay* d, s32 count) { + f32 val; + + // generate bipolar sawtooth from -RANGE to +RANGE + d->rsval += d->rsinc * count; + d->rsval = (d->rsval > RANGE) ? (d->rsval - RANGE * 2) : d->rsval; + + // convert to monopolar triangle from 0 to RANGE + val = d->rsval; + val = (val < 0) ? -val : val; + + // convert to bipolar triangle from -1 to 1 + val -= RANGE / 2; + + return d->rsgain * val; +} diff --git a/src/libultra/audio/save.c b/src/libultra/audio/save.c index 8b13789179..a6d25f6dc4 100644 --- a/src/libultra/audio/save.c +++ b/src/libultra/audio/save.c @@ -1 +1,36 @@ +#include "libaudio.h" +#include "synthInternals.h" +Acmd* alSavePull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p) { + Acmd* ptr = p; + ALSave* f = (ALSave*)filter; + ALFilter* source = f->filter.source; + + ptr = (*source->handler)(source, outp, outCount, sampleOffset, ptr); + + aSetBuffer(ptr++, 0, 0x000, 0x000, outCount << 1); + aInterleave(ptr++, AL_MAIN_L_OUT, AL_MAIN_R_OUT); + aSetBuffer(ptr++, 0, 0x000, 0x000, outCount << 2); + aSaveBuffer(ptr++, f->dramout); + return ptr; +} + +s32 alSaveParam(void* filter, s32 paramID, void* param) { + ALSave* a = (ALSave*)filter; + ALFilter* f = (ALFilter*)filter; + s32 pp = (s32)param; + + switch (paramID) { + case AL_FILTER_SET_SOURCE: + f->source = (ALFilter*)param; + break; + + case AL_FILTER_SET_DRAM: + a->dramout = pp; + break; + + default: + break; + } + return 0; +} diff --git a/src/libultra/audio/sl.c b/src/libultra/audio/sl.c index 8b13789179..7f2ebe28b8 100644 --- a/src/libultra/audio/sl.c +++ b/src/libultra/audio/sl.c @@ -1 +1,35 @@ +#include "libaudio.h" +ALGlobals* alGlobals = NULL; + +void alInit(ALGlobals* g, ALSynConfig* c) { + if (alGlobals == NULL) { + alGlobals = g; + alSynNew(&alGlobals->drvr, c); + } +} + +void alClose(ALGlobals* glob) { + if (alGlobals != NULL) { + alSynDelete(&glob->drvr); + alGlobals = NULL; + } +} + +void alLink(ALLink* ln, ALLink* to) { + ln->next = to->next; + ln->prev = to; + if (to->next != NULL) { + to->next->prev = ln; + } + to->next = ln; +} + +void alUnlink(ALLink* ln) { + if (ln->next != NULL) { + ln->next->prev = ln->prev; + } + if (ln->prev != NULL) { + ln->prev->next = ln->next; + } +} diff --git a/src/libultra/audio/synallocfx.c b/src/libultra/audio/synallocfx.c index 8b13789179..8c530fc19d 100644 --- a/src/libultra/audio/synallocfx.c +++ b/src/libultra/audio/synallocfx.c @@ -1 +1,8 @@ +#include "synthInternals.h" +ALFxRef* alSynAllocFX(ALSynth* s, s16 bus, ALSynConfig* c, ALHeap* hp) { + alFxNew(&s->auxBus[bus].fx[0], c, hp); + alFxParam(&s->auxBus[bus].fx[0], AL_FILTER_SET_SOURCE, &s->auxBus[bus]); + alMainBusParam(s->mainBus, AL_FILTER_ADD_SOURCE, &s->auxBus[bus].fx[0]); + return (ALFxRef)&s->auxBus[bus].fx[0]; +} diff --git a/src/libultra/audio/syndelete.c b/src/libultra/audio/syndelete.c index 8b13789179..7af0d50090 100644 --- a/src/libultra/audio/syndelete.c +++ b/src/libultra/audio/syndelete.c @@ -1 +1,5 @@ +#include "synthInternals.h" +void alSynDelete(ALSynth* drvr) { + drvr->head = NULL; +} diff --git a/src/libultra/audio/synthInternals.h b/src/libultra/audio/synthInternals.h new file mode 100644 index 0000000000..5fa8108f57 --- /dev/null +++ b/src/libultra/audio/synthInternals.h @@ -0,0 +1,312 @@ +#ifndef __audioInternals__ +#define __audioInternals__ + +#include "libaudio.h" + +/** + * filter message ids + */ +enum { + AL_FILTER_FREE_VOICE, + AL_FILTER_SET_SOURCE, + AL_FILTER_ADD_SOURCE, + AL_FILTER_ADD_UPDATE, + AL_FILTER_RESET, + AL_FILTER_SET_WAVETABLE, + AL_FILTER_SET_DRAM, + AL_FILTER_SET_PITCH, + AL_FILTER_SET_UNITY_PITCH, + AL_FILTER_START, + AL_FILTER_SET_STATE, + AL_FILTER_SET_VOLUME, + AL_FILTER_SET_PAN, + AL_FILTER_START_VOICE_ALT, + AL_FILTER_START_VOICE, + AL_FILTER_STOP_VOICE, + AL_FILTER_SET_FXAMT +}; + +#define AL_MAX_RSP_SAMPLES 160 + +/** + * buffer locations based on AL_MAX_RSP_SAMPLES + */ +#define AL_DECODER_IN 0x000 +#define AL_RESAMPLER_OUT 0x000 +#define AL_TEMP_0 0x000 +#define AL_DECODER_OUT 0x140 +#define AL_TEMP_1 0x140 +#define AL_TEMP_2 0x280 +#define AL_MAIN_L_OUT 0x440 +#define AL_MAIN_R_OUT 0x580 +#define AL_AUX_L_OUT 0x6C0 +#define AL_AUX_R_OUT 0x800 + +/** + * filter types + */ +enum { + AL_ADPCM, + AL_RESAMPLE, + AL_BUFFER, + AL_SAVE, + AL_ENVMIX, + AL_FX, + AL_AUXBUS, + AL_MAINBUS +}; + +typedef struct ALParam_s { + struct ALParam_s* next; + s32 delta; + s16 type; + union { + f32 f; + s32 i; + } data; + union { + f32 f; + s32 i; + } moredata; + union { + f32 f; + s32 i; + } stillmoredata; + union { + f32 f; + s32 i; + } yetstillmoredata; +} ALParam; + +typedef struct { + struct ALParam_s* next; + s32 delta; + s16 type; + s16 unity; // disable resampler + f32 pitch; + s16 volume; + ALPan pan; + u8 fxMix; + s32 samples; + struct ALWaveTable_s* wave; +} ALStartParamAlt; + +typedef struct { + struct ALParam_s* next; + s32 delta; + s16 type; + s16 unity; // disable resampler + struct ALWaveTable_s* wave; +} ALStartParam; + +typedef struct { + struct ALParam_s* next; + s32 delta; + s16 type; + struct PVoice_s* pvoice; +} ALFreeParam; + +typedef Acmd* (*ALCmdHandler)(void*, s16*, s32, s32, Acmd*); +typedef s32 (*ALSetParam)(void*, s32, void*); + +typedef struct ALFilter_s { + struct ALFilter_s* source; + ALCmdHandler handler; + ALSetParam setParam; + s16 inp; + s16 outp; + s32 type; +} ALFilter; + +void alFilterNew(ALFilter* f, ALCmdHandler h, ALSetParam s, s32 type); + +// Depends on number of subframes per frame and loop length +#define AL_MAX_ADPCM_STATES 3 + +typedef struct { + ALFilter filter; + ADPCM_STATE* state; + ADPCM_STATE* lstate; + ALRawLoop loop; + struct ALWaveTable_s* table; + s32 bookSize; + ALDMAproc dma; + void* dmaState; + s32 sample; + s32 lastsam; + s32 first; + s32 memin; +} ALLoadFilter; + +void alLoadNew(ALLoadFilter* f, ALDMANew dmaNew, ALHeap* hp); +Acmd* alAdpcmPull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p); +Acmd* alRaw16Pull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p); +s32 alLoadParam(void* filter, s32 paramID, void* param); + +typedef struct ALResampler_s { + ALFilter filter; + RESAMPLE_STATE* state; + f32 ratio; + s32 upitch; + f32 delta; + s32 first; + ALParam* ctrlList; + ALParam* ctrlTail; + s32 motion; +} ALResampler; + +typedef struct { + s16 fc; + s16 fgain; + union { + s16 fccoef[16]; + s64 force_aligned; + } fcvec; + POLEF_STATE* fstate; + s32 first; +} ALLowPass; + +typedef struct { + u32 input; + u32 output; + s16 ffcoef; + s16 fbcoef; + s16 gain; + f32 rsinc; + f32 rsval; + s32 rsdelta; + f32 rsgain; + ALLowPass* lp; + ALResampler* rs; +} ALDelay; + +typedef s32 (*ALSetFXParam)(void*, s32, void*); +typedef struct { + struct ALFilter_s filter; + s16* base; + s16* input; + u32 length; + ALDelay* delay; + u8 section_count; + ALSetFXParam paramHdl; +} ALFx; + +void alFxNew(ALFx* r, ALSynConfig* c, ALHeap* hp); +Acmd* alFxPull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p); +s32 alFxParam(void* filter, s32 paramID, void* param); +s32 alFxParamHdl(void* filter, s32 paramID, void* param); + +#define AL_MAX_MAIN_BUS_SOURCES 1 +typedef struct ALMainBus_s { + ALFilter filter; + s32 sourceCount; + s32 maxSources; + ALFilter** sources; +} ALMainBus; + +void alMainBusNew(ALMainBus* m, void* sources, s32 maxSources); +Acmd* alMainBusPull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p); +s32 alMainBusParam(void* filter, s32 paramID, void* param); + +#define AL_MAX_AUX_BUS_SOURCES 8 +#define AL_MAX_AUX_BUS_FX 1 +typedef struct ALAuxBus_s { + ALFilter filter; + s32 sourceCount; + s32 maxSources; + ALFilter** sources; + ALFx fx[AL_MAX_AUX_BUS_FX]; +} ALAuxBus; + +void alAuxBusNew(ALAuxBus* m, void* sources, s32 maxSources); +Acmd* alAuxBusPull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p); +s32 alAuxBusParam(void* filter, s32 paramID, void* param); + +void alResampleNew(ALResampler* r, ALHeap* hp); +Acmd* alResamplePull(void* filter, s16* outp, s32 outCnt, s32 sampleOffset, Acmd* p); +s32 alResampleParam(void* filter, s32 paramID, void* param); + +typedef struct ALSave_s { + ALFilter filter; + s32 dramout; + s32 first; +} ALSave; + +void alSaveNew(ALSave* f); +Acmd* alSavePull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p); +s32 alSaveParam(void* filter, s32 paramID, void* param); + +typedef struct ALEnvMixer_s { + ALFilter filter; + ENVMIX_STATE* state; + s16 pan; + s16 volume; + s16 cvolL; + s16 cvolR; + s16 dryamt; + s16 wetamt; + u16 lratl; + s16 lratm; + s16 ltgt; + u16 rratl; + s16 rratm; + s16 rtgt; + s32 delta; + s32 segEnd; + s32 first; + ALParam* ctrlList; + ALParam* ctrlTail; + ALFilter** sources; + s32 motion; +} ALEnvMixer; + +void alEnvmixerNew(ALEnvMixer* e, ALHeap* hp); +Acmd* alEnvmixerPull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p); +s32 alEnvmixerParam(void* filter, s32 paramID, void* param); + + +/** + * heap stuff + */ + +typedef struct { + s32 magic; // check structure integrety + s32 size; // size of this allocated block + u8* file; // file that this alloc was called from + s32 line; // line that it was called from + s32 count; // heap call number + s32 pad0; + s32 pad1; + s32 pad2; // Make it 32 bytes +} HeapInfo; + +// DCache line size (-1) +#define AL_CACHE_ALIGN 15 + +/** + * synth stuff + */ + +typedef struct PVoice_s { + ALLink node; + struct ALVoice_s* vvoice; + ALFilter* channelKnob; + ALLoadFilter decoder; + ALResampler resampler; + ALEnvMixer envmixer; + s32 offset; +} PVoice; + +// prototypes for private driver functions + +ALParam* __allocParam(void); +void __freeParam(ALParam* param); +void _freePVoice(ALSynth* drvr, PVoice* pvoice); +void _collectPVoices(ALSynth* drvr); + +s32 _timeToSamples(ALSynth* synth, s32 micros); +ALMicroTime _samplesToTime(ALSynth* synth, s32 samples); + +void _init_lpfilter(ALLowPass* lp); + +#endif diff --git a/src/libultra/audio/synthesizer.c b/src/libultra/audio/synthesizer.c index 8b13789179..62d5677ecd 100644 --- a/src/libultra/audio/synthesizer.c +++ b/src/libultra/audio/synthesizer.c @@ -1 +1,219 @@ +#include "synthInternals.h" +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +static s32 __nextSampleTime(ALSynth* drvr, ALPlayer** client); +static s32 _timeToSamplesNoRound(ALSynth* synth, s32 micros); + +void alSynNew(ALSynth* drvr, ALSynConfig* c) { + s32 i; + ALVoice* vv; + PVoice* pv; + ALVoice* vvoices; + PVoice* pvoices; + ALHeap* hp = c->heap; + ALSave* save; + ALFilter* sources; + ALParam* params; + ALParam* paramPtr; + + drvr->head = NULL; + drvr->numPVoices = c->maxPVoices; + drvr->curSamples = 0; + drvr->paramSamples = 0; + drvr->outputRate = c->outputRate; + drvr->maxOutSamples = AL_MAX_RSP_SAMPLES; + drvr->dma = (ALDMANew)c->dmaproc; + + save = alHeapAlloc(hp, 1, sizeof(ALSave)); + alSaveNew(save); + drvr->outputFilter = &save->filter; + + // allocate and initialize the auxilliary effects bus. at present we only support 1 effects bus. + drvr->auxBus = alHeapAlloc(hp, 1, sizeof(ALAuxBus)); + drvr->maxAuxBusses = 1; + sources = alHeapAlloc(hp, c->maxPVoices, sizeof(ALFilter*)); + alAuxBusNew(drvr->auxBus, sources, c->maxPVoices); + + // allocate and initialize the main bus. + drvr->mainBus = alHeapAlloc(hp, 1, sizeof(ALMainBus)); + sources = alHeapAlloc(hp, c->maxPVoices, sizeof(ALFilter*)); + alMainBusNew(drvr->mainBus, sources, c->maxPVoices); + + if (c->fxType != AL_FX_NONE) { + // Allocate an effect and set parameters + alSynAllocFX(drvr, 0, c, hp); + } else { + // Connect the aux bus to the main bus + alMainBusParam(drvr->mainBus, AL_FILTER_ADD_SOURCE, &drvr->auxBus[0]); + } + + // Build the physical voice lists + drvr->pFreeList.next = NULL; + drvr->pFreeList.prev = NULL; + drvr->pLameList.next = NULL; + drvr->pLameList.prev = NULL; + drvr->pAllocList.next = NULL; + drvr->pAllocList.prev = NULL; + + pvoices = alHeapAlloc(hp, c->maxPVoices, sizeof(PVoice)); + for (i = 0; i < c->maxPVoices; i++) { + pv = &pvoices[i]; + alLink(&pv->node, &drvr->pFreeList); + pv->vvoice = NULL; + + alLoadNew(&pv->decoder, drvr->dma, hp); + alLoadParam(&pv->decoder, AL_FILTER_SET_SOURCE, NULL); + + alResampleNew(&pv->resampler, hp); + alResampleParam(&pv->resampler, AL_FILTER_SET_SOURCE, &pv->decoder); + + alEnvmixerNew(&pv->envmixer, hp); + alEnvmixerParam(&pv->envmixer, AL_FILTER_SET_SOURCE, &pv->resampler); + + alAuxBusParam(drvr->auxBus, AL_FILTER_ADD_SOURCE, &pv->envmixer); + + pv->channelKnob = &pv->envmixer.filter; + } + + alSaveParam(save, AL_FILTER_SET_SOURCE, drvr->mainBus); + + // build the parameter update list + params = alHeapAlloc(hp, c->maxUpdates, sizeof(ALParam)); + drvr->paramList = NULL; + for (i = 0; i < c->maxUpdates; i++) { + paramPtr = ¶ms[i]; + paramPtr->next = drvr->paramList; + drvr->paramList = paramPtr; + } + drvr->heap = hp; +} + +/** + * slAudioFrame() is called every video frame, and is based on the video + * frame interrupt. It is assumed to be an accurate time source for the + * clients. + */ +Acmd* alAudioFrame(Acmd* cmdList, s32* cmdLen, s16* outBuf, s32 outLen) { + ALPlayer* client; + ALFilter* output; + ALSynth* drvr = &alGlobals->drvr; + s16 tmp = 0; // Starting buffer in DMEM + Acmd* cmdlEnd = cmdList; + Acmd* cmdPtr; + s32 nOut; + s16* lOutBuf = outBuf; + + if (drvr->head == NULL) { + // nothing to do + *cmdLen = 0; + return cmdList; + } + + // run down list of clients and execute callback if needed this + // subframe. Here we do all the work for the frame at the + // start. Time offsets that occur before the next frame are + // executed "early". + // + // paramSamples = time of next parameter change. + // curSamples = current sample time. + // so paramSamples - curSamples is the time until the next parameter change. + // if the next parameter change occurs within this frame time (outLen), + // then call back the client that contains the parameter change. + // Note, paramSamples must be rounded down to 16 sample boundary for use + // during the client handler. + + for (drvr->paramSamples = __nextSampleTime(drvr, &client); drvr->paramSamples - drvr->curSamples < outLen; + drvr->paramSamples = __nextSampleTime(drvr, &client)) { + drvr->paramSamples &= ~0xF; + client->samplesLeft += _timeToSamplesNoRound(drvr, (*client->handler)(client)); + } + + // for safety's sake, always store paramSamples aligned to 16 sample boundary. + // this way, if an voice handler routine gets called outside the ALVoiceHandler + // routine (alSynAllocVoice) it will get timestamped with an aligned value and + // will be processed immediately next audio frame. + drvr->paramSamples &= ~0xF; + + // Now build the command list in small chunks + while (outLen > 0) { + nOut = MIN(drvr->maxOutSamples, outLen); + + // construct the command list for each physical voice by calling the head of the filter chain. + cmdPtr = cmdlEnd; + aSegment(cmdPtr++, 0, 0); + output = drvr->outputFilter; + (*output->setParam)(output, AL_FILTER_SET_DRAM, lOutBuf); + cmdlEnd = (*output->handler)(output, &tmp, nOut, drvr->curSamples, cmdPtr); + + outLen -= nOut; + lOutBuf += nOut << 1; // For Stereo + drvr->curSamples += nOut; + } + *cmdLen = (s32)(cmdlEnd - cmdList); + + _collectPVoices(drvr); // collect free physical voices + return cmdlEnd; +} + +ALParam* __allocParam(void) { + ALParam* update = NULL; + ALSynth* drvr = &alGlobals->drvr; + + if (drvr->paramList != NULL) { + update = drvr->paramList; + drvr->paramList = drvr->paramList->next; + update->next = NULL; + } + return update; +} + +void __freeParam(ALParam* param) { + ALSynth* drvr = &alGlobals->drvr; + param->next = drvr->paramList; + drvr->paramList = param; +} + +void _collectPVoices(ALSynth* drvr) { + ALLink* dl; + PVoice* pv; + + while ((dl = drvr->pLameList.next) != NULL) { + pv = (PVoice*)dl; + alUnlink(&pv->node); + alLink(&pv->node, &drvr->pFreeList); + } +} + +void _freePVoice(ALSynth* drvr, PVoice* pvoice) { + // move the voice from the allocated list to the lame list + alUnlink(&pvoice->node); + alLink(&pvoice->node, &drvr->pLameList); +} + +static s32 _timeToSamplesNoRound(ALSynth* synth, s32 micros) { + // Add 0.5 to adjust the average affect of the truncation error produced by casting a float to an int. + f32 tmp = ((f32)micros) * synth->outputRate / 1000000.0 + 0.5; + return (s32)tmp; +} + +s32 _timeToSamples(ALSynth* synth, s32 micros) { + return _timeToSamplesNoRound(synth, micros) & ~0xF; +} + +static s32 __nextSampleTime(ALSynth* drvr, ALPlayer** client) { + ALMicroTime delta = 0x7FFFFFFF; // max delta for s32 + ALPlayer* cl; + + *client = NULL; + + for (cl = drvr->head; cl != NULL; cl = cl->next) { + if (cl->samplesLeft - drvr->curSamples < delta) { + *client = cl; + delta = cl->samplesLeft - drvr->curSamples; + } + } + return (*client)->samplesLeft; +} diff --git a/src/overlays/actors/ovl_Fishing/z_fishing.c b/src/overlays/actors/ovl_Fishing/z_fishing.c index 63bf23cdc2..5f995aa721 100644 --- a/src/overlays/actors/ovl_Fishing/z_fishing.c +++ b/src/overlays/actors/ovl_Fishing/z_fishing.c @@ -35,7 +35,7 @@ #include "cic6105.h" #endif -#pragma increment_block_number "gc-eu:164 gc-eu-mq:164 gc-jp:164 gc-jp-ce:164 gc-jp-mq:164 gc-us:164 gc-us-mq:164" \ +#pragma increment_block_number "gc-eu:163 gc-eu-mq:163 gc-jp:163 gc-jp-ce:163 gc-jp-mq:163 gc-us:163 gc-us-mq:163" \ "ntsc-1.0:121 ntsc-1.1:121 ntsc-1.2:121 pal-1.0:121 pal-1.1:121" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED