#include #include "mungal4.h" #pragma hdrstop #include "l4audlvl.h" #include "l4audres.h" #include "..\munga\audrend.h" #include "..\munga\objstrm.h" #include "..\munga\namelist.h" #include "openal/al.h" //~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Release fades ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // (task #50, AUDIO_FIDELITY F13) ~20 looping presets author a releaseVolEnv of // 1.1-3.9 s the AWE32 applied as a note-off fade while the loop continued; // StopNote registers a dB-linear ramp here instead of cutting. Serviced once // per frame from AudioHead::Execute; a fade finishing (or being reclaimed by a // restart) stops + rewinds the source so the next SetupPatch sees AL_INITIAL. // namespace { struct ReleaseFade { ALuint source; float gain0; float duration; float elapsed; bool active; }; const int MAX_RELEASE_FADES = 64; ReleaseFade s_releaseFades[MAX_RELEASE_FADES]; void FinishFade(ReleaseFade &fade) { alSourceStop(fade.source); alSourceRewind(fade.source); alSourcef(fade.source, AL_GAIN, fade.gain0); fade.active = false; } void StartReleaseFade(ALuint source, float duration) { for (int i = 0; i < MAX_RELEASE_FADES; i++) { if (s_releaseFades[i].active && s_releaseFades[i].source == source) { return; // already releasing } } for (int i = 0; i < MAX_RELEASE_FADES; i++) { if (!s_releaseFades[i].active) { ReleaseFade &fade = s_releaseFades[i]; fade.source = source; alGetSourcef(source, AL_GAIN, &fade.gain0); fade.duration = duration; fade.elapsed = 0.0f; fade.active = true; return; } } // table full -- fall back to the instant cut alSourceStop(source); alSourceRewind(source); } // A restart wants this source back NOW: finalize any fade so the caller // sees AL_INITIAL with the pre-fade gain restored. void ReclaimFadingSource(ALuint source) { for (int i = 0; i < MAX_RELEASE_FADES; i++) { if (s_releaseFades[i].active && s_releaseFades[i].source == source) { FinishFade(s_releaseFades[i]); return; } } } } void PRESET_serviceReleaseFades(float elapsed_seconds) { for (int i = 0; i < MAX_RELEASE_FADES; i++) { ReleaseFade &fade = s_releaseFades[i]; if (!fade.active) { continue; } fade.elapsed += elapsed_seconds; if (fade.elapsed >= fade.duration) { FinishFade(fade); } else { // dB-linear ramp to -96 dB over the authored release time float attenuation = powf(10.0f, -4.8f * (fade.elapsed / fade.duration)); alSourcef(fade.source, AL_GAIN, fade.gain0 * attenuation); } } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PatchLevelOfDetail ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #if DEBUG_LEVEL>0 TableOf PatchLevelOfDetail::patchTableSocket(NULL, True); #endif // //############################################################################# //############################################################################# // PatchLevelOfDetail::PatchLevelOfDetail(PlugStream *stream): AudioLevelOfDetail(stream) { MemoryStream_Read(stream, &bankID); MemoryStream_Read(stream, &patchID); MemoryStream_Read(stream, &maxMIDIFilterCutoff); // (task #50) the AWE 4-voice comment applied to layers SHARING a key range; // full-zone presets legitimately carry up to MAX_PRESET_SAMPLES zones // (key-splits select at most a few per note). Warn(GetVoiceCount() > MAX_PRESET_SAMPLES); #ifdef LAB_ONLY setupCount = 0; #endif // // Keep table of created patchs to verify that duplicates // are not created, could move this to tool time // #if DEBUG_LEVEL>0 unsigned int index_value = (bankID * 1000) + patchID; if (patchTableSocket.Find(index_value) != NULL) { Dump((int)bankID); Dump((int)patchID); } Verify(patchTableSocket.Find(index_value) == NULL); patchTableSocket.AddValue(this, index_value); #endif } // //############################################################################# //############################################################################# // void PatchLevelOfDetail::BuildFromPage( PlugStream *stream, NameList *name_list, ClassID class_ID, ObjectID object_ID ) { AudioLevelOfDetail::BuildFromPage(stream, name_list, class_ID, object_ID); // // Store fields // MEM_STRM_WRITE_ENTRY(*stream, name_list, SBKBankID, bank_ID); MEM_STRM_WRITE_ENTRY(*stream, name_list, SBKPatchID, patch_ID); MEM_STRM_WRITE_ENTRY(*stream, name_list, MIDINRPNValue, max_filter_cutoff); } // //############################################################################# //############################################################################# // PatchLevelOfDetail::~PatchLevelOfDetail() { #ifdef LAB_ONLY cout << "PatchLevelOfDetail::~PatchLevelOfDetail\t"; cout << (int)bankID << ":" << (int)patchID << "\t"; cout << "setupCount=\t" << setupCount << "\n"; #endif /*if (m_numSources > 0 && m_sources != NULL) { for (int i=0; i < m_numSources; i++) { //Detach buffer alSourcei(m_sources[i],AL_BUFFER,0); //Stop source ALenum state; alGetSourcei(m_sources[i],AL_SOURCE_STATE,&state); if (state == AL_PLAYING) { alSourceStop(m_sources[i]); } } //Destroy all sources alDeleteSources(m_numSources,m_sources); }*/ } // //############################################################################# //############################################################################# // Logical PatchLevelOfDetail::TestInstance() const { AudioLevelOfDetail::TestInstance(); return True; } // //############################################################################# //############################################################################# // void PatchLevelOfDetail::SetupPatch(SourceSet sourceSet, int note) { // Check(this); //// Check(channel); // // #ifdef LAB_ONLY // setupCount++; // #endif SAMPLEINFO info; // (task #50, AUDIO_FIDELITY F1) the authored MIDI note SELECTS zones: // attach only samples whose [keyLo,keyHi] contains the note (key-splits // pick one band, layers attach together); detach the rest so a rewound // source can't replay a stale buffer from a previous note. for (int i=0; i < sourceSet.count; i++) { info = PRESET_getSampleInfo(bankID,patchID,i); if (info.bufferIndex >= 0) { ReclaimFadingSource(sourceSet.sources[i]); ALenum sourceState; alGetSourcei(sourceSet.sources[i], AL_SOURCE_STATE, &sourceState); bool zone_matches = (note >= info.keyLo && note <= info.keyHi); if (getenv("BT_AUDIO_SPATIAL")) { static int s_se=0; if (s_se++<200) DEBUG_STREAM << "[audio] SetupPatch ENTRY bank=" << (int)bankID << " patch=" << (int)patchID << " src=" << sourceSet.sources[i] << " file=" << (info.file?info.file:"?") << " note=" << note << (zone_matches?"":" ZONESKIP") << " state=" << sourceState << " (INITIAL=" << AL_INITIAL << ")" << "\n" << std::flush; } if (sourceState == AL_INITIAL) { alSourcei(sourceSet.sources[i],AL_BUFFER, zone_matches ? AL_getBuffer(info.bufferIndex) : 0); { static int s_a=0; if (getenv("BT_AUDIO_LOG") && s_a++<24) DEBUG_STREAM << "[audio] SetupPatch src=" << sourceSet.sources[i] << " file=" << (info.file?info.file:"?") << " loop=" << (info.loop==ForceStatic?0:1) << "\n" << std::flush; } alSourcei(sourceSet.sources[i],AL_SOURCE_RELATIVE,AL_TRUE); AudioRenderer *render = application->GetAudioRenderer(); AudioHead *head = render->GetAudioHead(); // (F1) authored stereo pairs: pan hard-left/right zones via a // listener-relative offset (the distance model is AL_NONE, so // this affects direction only, never gain). float pan_x = (info.chan == CHANNEL_LEFT) ? -0.5f : (info.chan == CHANNEL_RIGHT) ? 0.5f : 0.0f; alSource3f(sourceSet.sources[i],AL_POSITION,pan_x,0,0); alSource3f(sourceSet.sources[i],AL_VELOCITY,0,0,0); if (info.loop == ForceStatic) { alSourcei(sourceSet.sources[i],AL_LOOPING,0); } else { alSourcei(sourceSet.sources[i],AL_LOOPING,1); } } } } // // Set the channel to the bank and program, reset patch controls // /* channel->SelectBank(bankID); channel->SendProgramChange(patchID); channel->SendController(MIDI_CONTROL_RESET, MIDI_MAX_CONTROL_VALUE);*/ //TODO: Sync up OpenAL source info appropriately /*#if 0 Tell((int)bankID << ":" << (int)patchID << "\n"); #endif*/ } void PatchLevelOfDetail::PlayNote(SourceSet sourceSet, int note) { if (sourceSet.count > 0 && sourceSet.sources != NULL) { alGetError(); //DEBUG_STREAM << "Playing bank " << (int)bankID << ", patch " << (int)patchID << std::endl; for (int i = 0; i < sourceSet.count; i++) { // (F1) play only the zones SetupPatch attached for this note ALint attached = 0; alGetSourcei(sourceSet.sources[i], AL_BUFFER, &attached); if (attached == 0) { continue; } ALenum state; alGetSourcei(sourceSet.sources[i], AL_SOURCE_STATE, &state); if (state != AL_PLAYING) { alSourcePlay(sourceSet.sources[i]); { static int s_p=0; if (getenv("BT_AUDIO_LOG") && s_p++<30) DEBUG_STREAM << "[audio] PlayNote alSourcePlay src=" << sourceSet.sources[i] << " note=" << note << "\n" << std::flush; } } ALenum error = alGetError(); if (error != AL_NO_ERROR) { DEBUG_STREAM << "Playing hit an error: " << error << std::endl; return; } } } } void PatchLevelOfDetail::StopNote(SourceSet sourceSet) { if (getenv("BT_AUDIO_LOG")) { static int s_s=0; if (s_s++<400) { DEBUG_STREAM << "[audio] StopNote"; for (int _i=0;_i0 && sourceSet.sources != NULL) { // (task #50, AUDIO_FIDELITY F13) honor the authored releaseVolEnv: // a playing zone with a release time fades out dB-linearly instead of // cutting; everything else stops instantly (faithful for one-shots). for (int i = 0; i < sourceSet.count; i++) { SAMPLEINFO info = PRESET_getSampleInfo(bankID,patchID,i); ALenum state; alGetSourcei(sourceSet.sources[i], AL_SOURCE_STATE, &state); if (state == AL_PLAYING && info.releaseSec > 0.05f) { StartReleaseFade(sourceSet.sources[i], info.releaseSec); } else { alSourceStop(sourceSet.sources[i]); alSourceRewind(sourceSet.sources[i]); } } } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PatchResource ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // //############################################################################# //############################################################################# // PatchResource::PatchResource(PlugStream *stream): AudioResource(stream) { } // //############################################################################# //############################################################################# // void PatchResource::BuildFromPage( PlugStream *stream, NameList *name_list, ClassID class_ID, ObjectID object_ID ) { AudioResource::BuildFromPage(stream, name_list, class_ID, object_ID); } // //############################################################################# //############################################################################# // PatchResource::~PatchResource() { } // //############################################################################# //############################################################################# // Logical PatchResource::TestInstance() const { AudioResource::TestInstance(); return True; } // //############################################################################# //############################################################################# // void PatchResource::SetupPatch(SourceSet sourceSet, int note) { Check(this); // Check(channel); PatchLevelOfDetail *patch_level_of_detail = Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail()); Check(patch_level_of_detail); patch_level_of_detail->SetupPatch(sourceSet, note); } void PatchResource::PlayNote(SourceSet sourceSet, int note) { Check(this); // Check(channel); PatchLevelOfDetail *patch_level_of_detail = Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail()); Check(patch_level_of_detail); patch_level_of_detail->PlayNote(sourceSet, note); } void PatchResource::StopNote(SourceSet sourceSet) { Check(this); // Check(channel); PatchLevelOfDetail *patch_level_of_detail = Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail()); Check(patch_level_of_detail); patch_level_of_detail->StopNote(sourceSet); } // //############################################################################# //############################################################################# // int PatchResource::GetBankID() { PatchLevelOfDetail *patch_level_of_detail = Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail()); return (patch_level_of_detail != 0) ? patch_level_of_detail->GetBankID() : 0; } int PatchResource::GetPatchID() { PatchLevelOfDetail *patch_level_of_detail = Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail()); return (patch_level_of_detail != 0) ? patch_level_of_detail->GetPatchID() : 0; } // //############################################################################# //############################################################################# // MIDINRPNValue PatchResource::GetMaxMIDIFilterCutoff() { Check(this); PatchLevelOfDetail *patch_level_of_detail = Cast_Object(PatchLevelOfDetail*, GetAudioLevelOfDetail()); Check(patch_level_of_detail); return patch_level_of_detail->GetMaxMIDIFilterCutoff(); }