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 }