1 module polyplex.core.audio.effect; 2 public import polyplex.core.audio.effects; 3 public import polyplex.core.audio.filters; 4 public import polyplex.core.audio; 5 import std.algorithm.searching; 6 import std.array; 7 import std.algorithm.mutation : remove; 8 import openal; 9 10 private enum ErrorNotOpenAlSoftRouting = "Chaining effects together is not supported on the current OpenAL implementation 11 12 - Try using OpenAL-Soft which is open source."; 13 14 enum EffectType : ALenum { 15 Nothing = AL_EFFECT_NULL, 16 RoomDynamics = AL_EFFECT_EAXREVERB, 17 Reverb = AL_EFFECT_REVERB, 18 Chorus = AL_EFFECT_CHORUS, 19 Distortion = AL_EFFECT_DISTORTION, 20 Echo = AL_EFFECT_ECHO, 21 Flanger = AL_EFFECT_FLANGER, 22 FreqencyShifter = AL_EFFECT_FREQUENCY_SHIFTER, 23 VocalMorpher = AL_EFFECT_VOCAL_MORPHER, 24 PitchShifter = AL_EFFECT_PITCH_SHIFTER, 25 RingModulation = AL_EFFECT_RING_MODULATOR, 26 AutoWah = AL_EFFECT_AUTOWAH, 27 Compressor = AL_EFFECT_COMPRESSOR, 28 Equalizer = AL_EFFECT_EQUALIZER 29 } 30 31 enum FilterType : ALenum { 32 Nothing = AL_FILTER_NULL, 33 Lowpass = AL_FILTER_LOWPASS, 34 Highpass = AL_FILTER_HIGHPASS, 35 Bandpass = AL_FILTER_BANDPASS 36 } 37 38 public class AudioEffect { 39 protected: 40 ALuint id; 41 ALuint sendId; 42 EffectType effectType; 43 44 AudioEffect target = null; 45 AudioEffect[] attachedEffects; 46 47 void setupDone() { 48 alAuxiliaryEffectSloti(sendId, AL_EFFECTSLOT_EFFECT, id); 49 } 50 51 void deattachAllChildren() { 52 foreach (effect; attachedEffects) { 53 effect.unbind(); 54 } 55 attachedEffects.length = 0; 56 } 57 58 void attach(AudioEffect effect) { 59 if (!effect.attachedEffects.canFind(this)) { 60 effect.attachedEffects ~= this; 61 } 62 target = effect; 63 } 64 65 void deattachChild(AudioEffect effect) { 66 if (attachedEffects.canFind(effect)) { 67 attachedEffects.remove(attachedEffects.countUntil(effect)); 68 } 69 effect.unbind(); 70 } 71 72 void bind(ALuint id) { 73 alAuxiliaryEffectSloti(Id, AL_EFFECTSLOT_TARGET_SOFT, id); 74 } 75 76 void unbind() { 77 bind(0); 78 } 79 80 public: 81 82 this(EffectType eType) { 83 if (!(AudioDevice.SupportedExtensions & ALExtensionSupport.EFX)) { 84 return; 85 } 86 87 // clear errors 88 alGetError(); 89 90 alGenEffects(1, &id); 91 alGenAuxiliaryEffectSlots(1, &sendId); 92 this.effectType = eType; 93 94 alEffecti(id, AL_EFFECT_TYPE, cast(ALenum)eType); 95 96 import std.conv; 97 ErrCodes err = cast(ErrCodes)alGetError(); 98 if (cast(ALint)err != AL_NO_ERROR) throw new Exception("Failed to create object "~err.to!string); 99 } 100 101 ~this() { 102 if (!(AudioDevice.SupportedExtensions & ALExtensionSupport.EFX)) { 103 return; 104 } 105 if (AudioDevice.SupportedExtensions & ALExtensionSupport.EffectChaining) { 106 // Be SURE to unmap this effect from something else. 107 // Prevents OpenAL crashing in some instances. 108 deattachAllChildren(); 109 unbind(); 110 } 111 alDeleteAuxiliaryEffectSlots(1, &sendId); 112 alDeleteEffects(1, &id); 113 } 114 115 ALuint Id() { 116 return sendId; 117 } 118 119 ALuint RawId() { 120 return id; 121 } 122 123 EffectType Type() { 124 return effectType; 125 } 126 127 /** 128 Attach sound effect to another. 129 130 A sound effect can only attach to one other sound effect. 131 132 A sound effect can be attached to by many others. 133 */ 134 void AttachTo(AudioEffect effect) { 135 if (!(AudioDevice.SupportedExtensions & ALExtensionSupport.EffectChaining)) { 136 throw new Exception(ErrorNotOpenAlSoftRouting); 137 } 138 139 // Unaffiliate self with previous parent when change is requested. 140 if (target !is null) { 141 target.deattachChild(this); 142 } 143 144 // Bind to new effect. 145 bind(effect.Id); 146 effect.attach(this); 147 } 148 149 /** 150 Deattaches the sound effect from ALL other sound effects. 151 */ 152 void Deattach() { 153 if (!(AudioDevice.SupportedExtensions & ALExtensionSupport.EffectChaining)) { 154 throw new Exception(ErrorNotOpenAlSoftRouting); 155 } 156 // Skip all the unbinding as it's not neccesary, small optimization. 157 if (target is null) { 158 return; 159 } 160 unbind(); 161 target.deattachChild(this); 162 target = null; 163 } 164 } 165 166 public class AudioFilter { 167 protected: 168 ALuint id; 169 FilterType filterType; 170 171 public: 172 this(FilterType fType) { 173 if (!(AudioDevice.SupportedExtensions & ALExtensionSupport.EFX)) { 174 return; 175 } 176 alGenFilters(1, &id); 177 this.filterType = fType; 178 179 alFilteri(id, AL_FILTER_TYPE, cast(ALenum)fType); 180 181 import std.conv; 182 ErrCodes err = cast(ErrCodes)alGetError(); 183 if (cast(ALint)err != AL_NO_ERROR) throw new Exception("Failed to create object "~err.to!string); 184 } 185 186 ~this() { 187 if (!(AudioDevice.SupportedExtensions & ALExtensionSupport.EFX)) { 188 return; 189 } 190 alDeleteFilters(1, &id); 191 } 192 193 ALuint Id() { 194 return id; 195 } 196 197 FilterType Type() { 198 return filterType; 199 } 200 201 }