diff options
author | Chris Robinson <[email protected]> | 2017-03-10 04:35:32 -0800 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2017-03-10 04:35:32 -0800 |
commit | 583d431947be540a33843d68feb5f1456671c298 (patch) | |
tree | 0f85220f58dd446d9732cb3a70bf7917585619c8 /Alc | |
parent | d9b1995e95ac3d838566500f1e5496ae71d832e0 (diff) |
Implement NFC filters for Ambisonic rendering
NFC filters currently only work when rendering to ambisonic buffers, which
includes HQ rendering and ambisonic output. There are two new config options:
'decoder/nfc' (default on) enables or disables use of NFC filters globally, and
'decoder/nfc-ref-delay' (default 0) specifies the reference delay parameter for
NFC-HOA rendering with ambisonic output (a value of 0 disables NFC).
Currently, NFC filters rely on having an appropriate value set for
AL_METERS_PER_UNIT to get the correct scaling. HQ rendering uses the averaged
speaker distances as a control/reference, and currently doesn't correct for
individual speaker distances (if the speakers are all equidistant, this is
fine, otherwise per-speaker correction should be done as well).
Diffstat (limited to 'Alc')
-rw-r--r-- | Alc/ALc.c | 20 | ||||
-rw-r--r-- | Alc/ALu.c | 64 | ||||
-rw-r--r-- | Alc/mixer.c | 55 | ||||
-rw-r--r-- | Alc/panning.c | 172 |
4 files changed, 235 insertions, 76 deletions
@@ -2310,6 +2310,24 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const ALCint *attrList) } } AllocateVoices(context, context->MaxVoices, old_sends); + for(pos = 0;pos < context->VoiceCount;pos++) + { + ALvoice *voice = context->Voices[pos]; + if(!voice->Source) continue; + + if(device->AvgSpeakerDist > 0.0f) + { + /* Reinitialize the NFC filters for new parameters. */ + ALfloat w1 = SPEEDOFSOUNDMETRESPERSEC / + (device->AvgSpeakerDist * device->Frequency); + for(i = 0;i < voice->NumChannels;i++) + { + NfcFilterCreate1(&voice->Direct.Params[i].NFCtrlFilter[0], 0.0f, w1); + NfcFilterCreate2(&voice->Direct.Params[i].NFCtrlFilter[1], 0.0f, w1); + NfcFilterCreate3(&voice->Direct.Params[i].NFCtrlFilter[2], 0.0f, w1); + } + } + } UnlockUIntMapRead(&context->SourceMap); UpdateListenerProps(context); @@ -3755,6 +3773,7 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) device->FOAOut.NumChannels = 0; device->RealOut.Buffer = NULL; device->RealOut.NumChannels = 0; + device->AvgSpeakerDist = 0.0f; ATOMIC_INIT(&device->ContextList, NULL); @@ -4271,6 +4290,7 @@ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceN device->FOAOut.NumChannels = 0; device->RealOut.Buffer = NULL; device->RealOut.NumChannels = 0; + device->AvgSpeakerDist = 0.0f; ATOMIC_INIT(&device->ContextList, NULL); @@ -496,6 +496,7 @@ static void CalcNonAttnSourceParams(ALvoice *voice, const struct ALsourceProps * break; } + voice->Flags &= ~(VOICE_IS_HRTF | VOICE_HAS_NFC); if(isbformat) { ALfloat N[3], V[3], U[3]; @@ -535,6 +536,17 @@ static void CalcNonAttnSourceParams(ALvoice *voice, const struct ALsourceProps * for(c = 0;c < num_channels;c++) ComputeFirstOrderGains(Device->FOAOut, matrix.m[c], DryGain, voice->Direct.Params[c].Gains.Target); + if(Device->AvgSpeakerDist > 0.0f) + { + /* NOTE: The NFCtrlFilters were created with a w0 of 0, which is + * what we want for FOA input. So there's nothing to adjust. + */ + voice->Direct.ChannelsPerOrder[0] = 1; + voice->Direct.ChannelsPerOrder[1] = mini(voice->Direct.Channels-1, 3); + voice->Direct.ChannelsPerOrder[2] = 0; + voice->Direct.ChannelsPerOrder[3] = 0; + voice->Flags |= VOICE_HAS_NFC; + } for(i = 0;i < NumSends;i++) { @@ -553,8 +565,6 @@ static void CalcNonAttnSourceParams(ALvoice *voice, const struct ALsourceProps * voice->Send[i].Params[c].Gains.Target[j] = 0.0f; } } - - voice->IsHrtf = AL_FALSE; } else { @@ -592,8 +602,6 @@ static void CalcNonAttnSourceParams(ALvoice *voice, const struct ALsourceProps * voice->Send[i].Params[c].Gains.Target[j] = 0.0f; } } - - voice->IsHrtf = AL_FALSE; } else if(Device->Render_Mode == HrtfRender) { @@ -647,7 +655,7 @@ static void CalcNonAttnSourceParams(ALvoice *voice, const struct ALsourceProps * } } - voice->IsHrtf = AL_TRUE; + voice->Flags |= VOICE_IS_HRTF; } else { @@ -694,8 +702,6 @@ static void CalcNonAttnSourceParams(ALvoice *voice, const struct ALsourceProps * voice->Send[i].Params[c].Gains.Target[j] = 0.0f; } } - - voice->IsHrtf = AL_FALSE; } } @@ -1064,6 +1070,7 @@ static void CalcAttnSourceParams(ALvoice *voice, const struct ALsourceProps *pro voice->Step = maxi(fastf2i(Pitch*FRACTIONONE + 0.5f), 1); BsincPrepare(voice->Step, &voice->ResampleState.bsinc); + voice->Flags &= ~(VOICE_IS_HRTF | VOICE_HAS_NFC); if(Device->Render_Mode == HrtfRender) { /* Full HRTF rendering. Skip the virtual channels and render to the @@ -1115,7 +1122,7 @@ static void CalcAttnSourceParams(ALvoice *voice, const struct ALsourceProps *pro voice->Send[i].Params[0].Gains.Target[j] = 0.0f; } - voice->IsHrtf = AL_TRUE; + voice->Flags |= VOICE_IS_HRTF; } else { @@ -1128,10 +1135,49 @@ static void CalcAttnSourceParams(ALvoice *voice, const struct ALsourceProps *pro /* Get the localized direction, and compute panned gains. */ if(Distance > FLT_EPSILON) { + if(Device->AvgSpeakerDist > 0.0f && MetersPerUnit > 0.0f) + { + ALfloat w0 = SPEEDOFSOUNDMETRESPERSEC / + (Distance*MetersPerUnit * (ALfloat)Device->Frequency); + ALfloat w1 = SPEEDOFSOUNDMETRESPERSEC / + (Device->AvgSpeakerDist * (ALfloat)Device->Frequency); + /* Clamp w0 for really close distances, to prevent excessive + * bass. + */ + w0 = minf(w0, w1*4.0f); + + NfcFilterAdjust1(&voice->Direct.Params[0].NFCtrlFilter[0], w0); + NfcFilterAdjust2(&voice->Direct.Params[0].NFCtrlFilter[1], w0); + NfcFilterAdjust3(&voice->Direct.Params[0].NFCtrlFilter[2], w0); + + for(i = 0;i < MAX_AMBI_ORDER+1;i++) + voice->Direct.ChannelsPerOrder[i] = Device->Dry.NumChannelsPerOrder[i]; + voice->Flags |= VOICE_HAS_NFC; + } + dir[0] = -SourceToListener.v[0]; dir[1] = -SourceToListener.v[1]; dir[2] = -SourceToListener.v[2] * ZScale; } + else if(Device->AvgSpeakerDist > 0.0f) + { + /* If the source distance is 0, set w0 to w1 to act as a pass- + * through. We still want to pass the signal through the filters so + * they keep an appropriate history, in case the source moves away + * from the listener. + */ + ALfloat w0 = SPEEDOFSOUNDMETRESPERSEC / + (Device->AvgSpeakerDist * (ALfloat)Device->Frequency); + + NfcFilterAdjust1(&voice->Direct.Params[0].NFCtrlFilter[0], w0); + NfcFilterAdjust2(&voice->Direct.Params[0].NFCtrlFilter[1], w0); + NfcFilterAdjust3(&voice->Direct.Params[0].NFCtrlFilter[2], w0); + + for(i = 0;i < MAX_AMBI_ORDER+1;i++) + voice->Direct.ChannelsPerOrder[i] = Device->Dry.NumChannelsPerOrder[i]; + voice->Flags |= VOICE_HAS_NFC; + } + if(radius > Distance) spread = F_TAU - Distance/radius*F_PI; else if(Distance > FLT_EPSILON) @@ -1160,8 +1206,6 @@ static void CalcAttnSourceParams(ALvoice *voice, const struct ALsourceProps *pro for(j = 0;j < MAX_EFFECT_CHANNELS;j++) voice->Send[i].Params[0].Gains.Target[j] = 0.0f; } - - voice->IsHrtf = AL_FALSE; } { diff --git a/Alc/mixer.c b/Alc/mixer.c index 1c503a92..961c8f31 100644 --- a/Alc/mixer.c +++ b/Alc/mixer.c @@ -545,15 +545,60 @@ ALboolean MixSource(ALvoice *voice, ALsource *Source, ALCdevice *Device, ALsizei &parms->LowPass, &parms->HighPass, Device->FilteredData, ResampledData, DstBufferSize, parms->FilterType ); - if(!voice->IsHrtf) + if(!(voice->Flags&VOICE_IS_HRTF)) { if(!Counter) memcpy(parms->Gains.Current, parms->Gains.Target, sizeof(parms->Gains.Current)); - MixSamples(samples, voice->Direct.Channels, voice->Direct.Buffer, - parms->Gains.Current, parms->Gains.Target, Counter, OutPos, - DstBufferSize - ); + if(!(voice->Flags&VOICE_HAS_NFC)) + MixSamples(samples, voice->Direct.Channels, voice->Direct.Buffer, + parms->Gains.Current, parms->Gains.Target, Counter, OutPos, + DstBufferSize + ); + else + { + ALfloat *nfcsamples = Device->NFCtrlData; + ALsizei chanoffset = 0; + MixSamples(samples, + voice->Direct.ChannelsPerOrder[0], voice->Direct.Buffer, + parms->Gains.Current, parms->Gains.Target, Counter, OutPos, + DstBufferSize + ); + chanoffset += voice->Direct.ChannelsPerOrder[0]; + if(voice->Direct.ChannelsPerOrder[1] > 0) + { + NfcFilterUpdate1(&parms->NFCtrlFilter[0], nfcsamples, samples, + DstBufferSize); + MixSamples(nfcsamples, + voice->Direct.ChannelsPerOrder[1], voice->Direct.Buffer+chanoffset, + parms->Gains.Current+chanoffset, parms->Gains.Target+chanoffset, + Counter, OutPos, DstBufferSize + ); + chanoffset += voice->Direct.ChannelsPerOrder[1]; + } + if(voice->Direct.ChannelsPerOrder[2] > 0) + { + NfcFilterUpdate2(&parms->NFCtrlFilter[1], nfcsamples, samples, + DstBufferSize); + MixSamples(nfcsamples, + voice->Direct.ChannelsPerOrder[2], voice->Direct.Buffer+chanoffset, + parms->Gains.Current+chanoffset, parms->Gains.Target+chanoffset, + Counter, OutPos, DstBufferSize + ); + chanoffset += voice->Direct.ChannelsPerOrder[2]; + } + if(voice->Direct.ChannelsPerOrder[3] > 0) + { + NfcFilterUpdate3(&parms->NFCtrlFilter[2], nfcsamples, samples, + DstBufferSize); + MixSamples(nfcsamples, + voice->Direct.ChannelsPerOrder[3], voice->Direct.Buffer+chanoffset, + parms->Gains.Current+chanoffset, parms->Gains.Target+chanoffset, + Counter, OutPos, DstBufferSize + ); + chanoffset += voice->Direct.ChannelsPerOrder[3]; + } + } } else { diff --git a/Alc/panning.c b/Alc/panning.c index c4d3e43f..6315328a 100644 --- a/Alc/panning.c +++ b/Alc/panning.c @@ -487,6 +487,88 @@ static const ChannelMap MonoCfg[1] = { { BackRight, { 2.04124145e-1f, -1.08880247e-1f, 0.0f, -1.88586120e-1f, 1.29099444e-1f, 0.0f, 0.0f, 0.0f, 7.45355993e-2f, -3.73460789e-2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.00000000e+0f } }, }; +static void InitNearFieldCtrl(ALCdevice *device, ALfloat ctrl_dist, ALsizei order, bool periphonic) +{ + const char *devname = al_string_get_cstr(device->DeviceName); + ALsizei i; + + if(GetConfigValueBool(devname, "decoder", "nfc", 1) && ctrl_dist > 0.0f) + { + /* NFC is only used when AvgSpeakerDist is greater than 0, and + * METERS_PER_UNIT is also greater than 0. In addition, NFC can only be + * used when rendering to an ambisonic buffer. + */ + device->AvgSpeakerDist = ctrl_dist; + + device->Dry.NumChannelsPerOrder[0] = 1; + if(periphonic) + for(i = 1;i < order+1;i++) + device->Dry.NumChannelsPerOrder[i] = (i+1)*(i+1) - i*i; + else + for(i = 1;i < order+1;i++) + device->Dry.NumChannelsPerOrder[i] = (i*2+1) - ((i-1)*2+1); + for(;i < MAX_AMBI_ORDER+1;i++) + device->Dry.NumChannelsPerOrder[i] = 0; + } +} + +static void InitDistanceComp(ALCdevice *device, const AmbDecConf *conf, const ALsizei speakermap[MAX_OUTPUT_CHANNELS]) +{ + const char *devname = al_string_get_cstr(device->DeviceName); + ALfloat maxdist = 0.0f; + ALsizei total = 0; + ALsizei i; + + for(i = 0;i < conf->NumSpeakers;i++) + maxdist = maxf(maxdist, conf->Speakers[i].Distance); + + if(GetConfigValueBool(devname, "decoder", "distance-comp", 1) && maxdist > 0.0f) + { + ALfloat srate = (ALfloat)device->Frequency; + for(i = 0;i < conf->NumSpeakers;i++) + { + ALsizei chan = speakermap[i]; + ALfloat delay; + + /* Distance compensation only delays in steps of the sample rate. + * This is a bit less accurate since the delay time falls to the + * nearest sample time, but it's far simpler as it doesn't have to + * deal with phase offsets. This means at 48khz, for instance, the + * distance delay will be in steps of about 7 millimeters. + */ + delay = floorf((maxdist-conf->Speakers[i].Distance) / SPEEDOFSOUNDMETRESPERSEC * + srate + 0.5f); + if(delay >= (ALfloat)MAX_DELAY_LENGTH) + ERR("Delay for speaker \"%s\" exceeds buffer length (%f >= %u)\n", + al_string_get_cstr(conf->Speakers[i].Name), delay, MAX_DELAY_LENGTH); + + device->ChannelDelay[chan].Length = (ALsizei)clampf( + delay, 0.0f, (ALfloat)(MAX_DELAY_LENGTH-1) + ); + device->ChannelDelay[chan].Gain = conf->Speakers[i].Distance / maxdist; + TRACE("Channel %u \"%s\" distance compensation: %d samples, %f gain\n", chan, + al_string_get_cstr(conf->Speakers[i].Name), device->ChannelDelay[chan].Length, + device->ChannelDelay[chan].Gain + ); + + /* Round up to the next 4th sample, so each channel buffer starts + * 16-byte aligned. + */ + total += RoundUp(device->ChannelDelay[chan].Length, 4); + } + } + + if(total > 0) + { + device->ChannelDelay[0].Buffer = al_calloc(16, total * sizeof(ALfloat)); + for(i = 1;i < MAX_OUTPUT_CHANNELS;i++) + { + size_t len = RoundUp(device->ChannelDelay[i-1].Length, 4); + device->ChannelDelay[i].Buffer = device->ChannelDelay[i-1].Buffer + len; + } + } +} + static void InitPanning(ALCdevice *device) { const ChannelMap *chanmap = NULL; @@ -546,10 +628,12 @@ static void InitPanning(ALCdevice *device) if(device->FmtChans >= DevFmtAmbi1 && device->FmtChans <= DevFmtAmbi3) { + const char *devname = al_string_get_cstr(device->DeviceName); const ALsizei *acnmap = (device->AmbiLayout == AmbiLayout_FuMa) ? FuMa2ACN : ACN2ACN; const ALfloat *n3dscale = (device->AmbiScale == AmbiNorm_FuMa) ? FuMa2N3DScale : (device->AmbiScale == AmbiNorm_SN3D) ? SN3D2N3DScale : /*(device->AmbiScale == AmbiNorm_N3D) ?*/ UnitScale; + ALfloat nfc_delay = 0.0f; count = (device->FmtChans == DevFmtAmbi3) ? 16 : (device->FmtChans == DevFmtAmbi2) ? 9 : @@ -585,6 +669,16 @@ static void InitPanning(ALCdevice *device) ambiup_reset(device->AmbiUp, device); } + + if(ConfigValueFloat(devname, "decoder", "nfc-ref-delay", &nfc_delay)) + { + nfc_delay = clampf(nfc_delay, 0.001f, 1000.0f); + InitNearFieldCtrl(device, nfc_delay * SPEEDOFSOUNDMETRESPERSEC, + (device->FmtChans == DevFmtAmbi3) ? 3 : + (device->FmtChans == DevFmtAmbi2) ? 2 : 1, + true + ); + } } else { @@ -612,63 +706,6 @@ static void InitPanning(ALCdevice *device) device->RealOut.NumChannels = 0; } -static void InitDistanceComp(ALCdevice *device, const AmbDecConf *conf, const ALsizei speakermap[MAX_OUTPUT_CHANNELS]) -{ - const char *devname = al_string_get_cstr(device->DeviceName); - ALfloat maxdist = 0.0f; - ALsizei total = 0; - ALsizei i; - - for(i = 0;i < conf->NumSpeakers;i++) - maxdist = maxf(maxdist, conf->Speakers[i].Distance); - - if(GetConfigValueBool(devname, "decoder", "distance-comp", 1) && maxdist > 0.0f) - { - ALfloat srate = (ALfloat)device->Frequency; - for(i = 0;i < conf->NumSpeakers;i++) - { - ALsizei chan = speakermap[i]; - ALfloat delay; - - /* Distance compensation only delays in steps of the sample rate. - * This is a bit less accurate since the delay time falls to the - * nearest sample time, but it's far simpler as it doesn't have to - * deal with phase offsets. This means at 48khz, for instance, the - * distance delay will be in steps of about 7 millimeters. - */ - delay = floorf((maxdist-conf->Speakers[i].Distance) / SPEEDOFSOUNDMETRESPERSEC * - srate + 0.5f); - if(delay >= (ALfloat)MAX_DELAY_LENGTH) - ERR("Delay for speaker \"%s\" exceeds buffer length (%f >= %u)\n", - al_string_get_cstr(conf->Speakers[i].Name), delay, MAX_DELAY_LENGTH); - - device->ChannelDelay[chan].Length = (ALsizei)clampf( - delay, 0.0f, (ALfloat)(MAX_DELAY_LENGTH-1) - ); - device->ChannelDelay[chan].Gain = conf->Speakers[i].Distance / maxdist; - TRACE("Channel %u \"%s\" distance compensation: %d samples, %f gain\n", chan, - al_string_get_cstr(conf->Speakers[i].Name), device->ChannelDelay[chan].Length, - device->ChannelDelay[chan].Gain - ); - - /* Round up to the next 4th sample, so each channel buffer starts - * 16-byte aligned. - */ - total += RoundUp(device->ChannelDelay[chan].Length, 4); - } - } - - if(total > 0) - { - device->ChannelDelay[0].Buffer = al_calloc(16, total * sizeof(ALfloat)); - for(i = 1;i < MAX_OUTPUT_CHANNELS;i++) - { - size_t len = RoundUp(device->ChannelDelay[i-1].Length, 4); - device->ChannelDelay[i].Buffer = device->ChannelDelay[i-1].Buffer + len; - } - } -} - static void InitCustomPanning(ALCdevice *device, const AmbDecConf *conf, const ALsizei speakermap[MAX_OUTPUT_CHANNELS]) { ChannelMap chanmap[MAX_OUTPUT_CHANNELS]; @@ -756,8 +793,9 @@ static void InitCustomPanning(ALCdevice *device, const AmbDecConf *conf, const A static void InitHQPanning(ALCdevice *device, const AmbDecConf *conf, const ALsizei speakermap[MAX_OUTPUT_CHANNELS]) { - size_t count; - size_t i; + ALfloat avg_dist; + ALsizei count; + ALsizei i; if((conf->ChanMask&AMBI_PERIPHONIC_MASK)) { @@ -825,6 +863,15 @@ static void InitHQPanning(ALCdevice *device, const AmbDecConf *conf, const ALsiz device->RealOut.NumChannels = ChannelsFromDevFmt(device->FmtChans); + avg_dist = 0.0f; + for(i = 0;i < conf->NumSpeakers;i++) + avg_dist += conf->Speakers[i].Distance; + avg_dist /= (ALfloat)conf->NumSpeakers; + InitNearFieldCtrl(device, avg_dist, + (conf->ChanMask > 0x1ff) ? 3 : (conf->ChanMask > 0xf) ? 2 : 1, + !!(conf->ChanMask&AMBI_PERIPHONIC_MASK) + ); + InitDistanceComp(device, conf, speakermap); } @@ -879,8 +926,8 @@ static void InitHrtfPanning(ALCdevice *device, bool hoa_mode) { { 1.43315266e-001f, 0.00000000e+000f, -1.90399923e-001f, 0.00000000e+000f, 0.00000000e+000f, 0.00000000e+000f, 1.18020996e-001f, 0.00000000e+000f, 0.00000000e+000f }, { 7.26741039e-002f, 0.00000000e+000f, -1.24646009e-001f, 0.00000000e+000f, 0.00000000e+000f, 0.00000000e+000f, 1.49618920e-001f, 0.00000000e+000f, 0.00000000e+000f } }, }; const ALfloat (*AmbiMatrix)[2][MAX_AMBI_COEFFS] = hoa_mode ? AmbiMatrixHOA : AmbiMatrixFOA; - size_t count = hoa_mode ? 9 : 4; - size_t i; + ALsizei count = hoa_mode ? 9 : 4; + ALsizei i; static_assert(9 <= COUNTOF(device->Hrtf.Coeffs), "ALCdevice::Hrtf.Values/Coeffs size is too small"); static_assert(COUNTOF(AmbiPoints) <= HRTF_AMBI_MAX_CHANNELS, "HRTF_AMBI_MAX_CHANNELS is too small"); @@ -960,7 +1007,10 @@ void aluInitRenderer(ALCdevice *device, ALint hrtf_id, enum HrtfRequestMode hrtf memset(&device->Dry.Ambi, 0, sizeof(device->Dry.Ambi)); device->Dry.CoeffCount = 0; device->Dry.NumChannels = 0; + for(i = 0;i < MAX_AMBI_ORDER+1;i++) + device->Dry.NumChannelsPerOrder[i] = 0; + device->AvgSpeakerDist = 0.0f; memset(device->ChannelDelay, 0, sizeof(device->ChannelDelay)); for(i = 0;i < MAX_OUTPUT_CHANNELS;i++) { |