aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Alc/ALc.c1
-rw-r--r--Alc/ALu.c2
-rw-r--r--Alc/alcEcho.c194
-rw-r--r--CMakeLists.txt1
-rw-r--r--OpenAL32/Include/alAuxEffectSlot.h2
-rw-r--r--OpenAL32/Include/alEcho.h24
-rw-r--r--OpenAL32/Include/alEffect.h34
-rw-r--r--OpenAL32/alAuxEffectSlot.c20
-rw-r--r--OpenAL32/alEffect.c153
-rw-r--r--alsoftrc.sample2
10 files changed, 431 insertions, 2 deletions
diff --git a/Alc/ALc.c b/Alc/ALc.c
index 943201f6..3c0b1e62 100644
--- a/Alc/ALc.c
+++ b/Alc/ALc.c
@@ -322,6 +322,7 @@ static void InitAL(void)
int type;
} EffectList[] = {
{ "reverb", REVERB },
+ { "echo", ECHO },
{ NULL, 0 }
};
int n;
diff --git a/Alc/ALu.c b/Alc/ALu.c
index ea2f6526..babccb8e 100644
--- a/Alc/ALu.c
+++ b/Alc/ALu.c
@@ -1383,6 +1383,8 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma
{
if(ALEffectSlot->effect.type == AL_EFFECT_REVERB)
VerbProcess(ALEffectSlot->ReverbState, SamplesToDo, ALEffectSlot->WetBuffer, DryBuffer);
+ else if(ALEffectSlot->effect.type == AL_EFFECT_ECHO)
+ EchoProcess(ALEffectSlot->EchoState, SamplesToDo, ALEffectSlot->WetBuffer, DryBuffer);
for(i = 0;i < SamplesToDo;i++)
ALEffectSlot->WetBuffer[i] = 0.0f;
diff --git a/Alc/alcEcho.c b/Alc/alcEcho.c
new file mode 100644
index 00000000..6640d9af
--- /dev/null
+++ b/Alc/alcEcho.c
@@ -0,0 +1,194 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2009 by Chris Robinson.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+#include "AL/al.h"
+#include "alFilter.h"
+#include "alAuxEffectSlot.h"
+#include "alEcho.h"
+
+#ifdef HAVE_SQRTF
+#define aluSqrt(x) ((ALfloat)sqrtf((float)(x)))
+#else
+#define aluSqrt(x) ((ALfloat)sqrt((double)(x)))
+#endif
+
+struct ALechoState {
+ ALfloat *SampleBuffer;
+ ALuint BufferLength;
+
+ // The echo is two tap. The third tap is the offset to sample from for
+ // feedback
+ struct {
+ ALuint offset;
+ } Tap[3];
+ // The LR gains for the first tap. The second tap uses the reverse
+ ALfloat GainL;
+ ALfloat GainR;
+
+ ALfloat FeedGain;
+ FILTER iirFilter;
+};
+
+// Find the next power of 2. Actually, this will return the input value if
+// it is already a power of 2.
+static ALuint NextPowerOf2(ALuint value)
+{
+ ALuint powerOf2 = 1;
+
+ if(value)
+ {
+ value--;
+ while(value)
+ {
+ value >>= 1;
+ powerOf2 <<= 1;
+ }
+ }
+ return powerOf2;
+}
+
+ALechoState *EchoCreate(ALCcontext *Context)
+{
+ ALechoState *state;
+ ALuint i, maxlen;
+
+ state = malloc(sizeof(*state));
+ if(!state)
+ return NULL;
+
+ maxlen = (ALuint)(AL_ECHO_MAX_DELAY * Context->Frequency);
+ maxlen += (ALuint)(AL_ECHO_MAX_LRDELAY * Context->Frequency);
+
+ // Use the next power of 2 for the buffer length, so the tap offsets can be
+ // wrapped using a mask instead of a modulo
+ state->BufferLength = NextPowerOf2(maxlen+1);
+ state->SampleBuffer = malloc(state->BufferLength * sizeof(ALfloat));
+ if(!state->SampleBuffer)
+ {
+ free(state);
+ return NULL;
+ }
+
+ for(i = 0;i < state->BufferLength;i++)
+ state->SampleBuffer[i] = 0.0f;
+
+ state->Tap[0].offset = 0;
+ state->Tap[1].offset = 0;
+ state->Tap[2].offset = 0;
+ state->GainL = 0.0f;
+ state->GainR = 0.0f;
+
+ for(i = 0;i < sizeof(state->iirFilter.history)/sizeof(state->iirFilter.history[0]);i++)
+ state->iirFilter.history[i] = 0.0f;
+ state->iirFilter.coeff = 0.0f;
+
+ return state;
+}
+
+ALvoid EchoDestroy(ALechoState *state)
+{
+ if(state)
+ {
+ free(state->SampleBuffer);
+ state->SampleBuffer = NULL;
+ free(state);
+ }
+}
+
+ALvoid EchoUpdate(ALCcontext *Context, struct ALeffectslot *Slot, ALeffect *Effect)
+{
+ ALechoState *state = Slot->EchoState;
+ ALuint newdelay1, newdelay2;
+ ALfloat lrpan, cw, a, g;
+
+ newdelay1 = (ALuint)(Effect->Echo.Delay * Context->Frequency);
+ newdelay2 = (ALuint)(Effect->Echo.LRDelay * Context->Frequency);
+
+ state->Tap[0].offset = (state->BufferLength - newdelay1 - 1 +
+ state->Tap[2].offset)%state->BufferLength;
+ state->Tap[1].offset = (state->BufferLength - newdelay1 - newdelay2 - 1 +
+ state->Tap[2].offset)%state->BufferLength;
+
+ lrpan = Effect->Echo.Spread*0.5f + 0.5f;
+ state->GainL = aluSqrt( lrpan);
+ state->GainR = aluSqrt(1.0f-lrpan);
+
+ state->FeedGain = Effect->Echo.Feedback;
+
+ cw = cos(2.0*M_PI * LOWPASSFREQCUTOFF / Context->Frequency);
+ g = 1.0f - Effect->Echo.Damping;
+ a = 0.0f;
+ if(g < 0.9999f) // 1-epsilon
+ a = (1 - g*cw - aluSqrt(2*g*(1-cw) - g*g*(1 - cw*cw))) / (1 - g);
+ state->iirFilter.coeff = a;
+}
+
+ALvoid EchoProcess(ALechoState *state, ALuint SamplesToDo, const ALfloat *SamplesIn, ALfloat (*SamplesOut)[OUTPUTCHANNELS])
+{
+ ALfloat *history = state->iirFilter.history;
+ const ALfloat a = state->iirFilter.coeff;
+ const ALuint delay = state->BufferLength-1;
+ ALuint tap1off = state->Tap[0].offset;
+ ALuint tap2off = state->Tap[1].offset;
+ ALuint fboff = state->Tap[2].offset;
+ ALfloat samp[2];
+ ALuint i;
+
+ for(i = 0;i < SamplesToDo;i++)
+ {
+ // Apply damping
+ samp[0] = state->SampleBuffer[tap2off] + SamplesIn[i];
+
+ samp[0] += (history[0]-samp[0]) * a;
+ history[0] = samp[0];
+ samp[0] += (history[1]-samp[0]) * a;
+ history[1] = samp[0];
+
+ // Apply feedback gain and mix in the new sample
+ state->SampleBuffer[fboff] = samp[0] * state->FeedGain;
+
+ tap1off = (tap1off+1) & delay;
+ tap2off = (tap2off+1) & delay;
+ fboff = (fboff+1) & delay;
+
+ // Sample first tap
+ samp[0] = state->SampleBuffer[tap1off]*state->GainL;
+ samp[1] = state->SampleBuffer[tap1off]*state->GainR;
+ // Sample second tap. Reverse LR panning
+ samp[0] += state->SampleBuffer[tap2off]*state->GainR;
+ samp[1] += state->SampleBuffer[tap2off]*state->GainL;
+
+ SamplesOut[i][FRONT_LEFT] += samp[0];
+ SamplesOut[i][FRONT_RIGHT] += samp[1];
+ SamplesOut[i][SIDE_LEFT] += samp[0];
+ SamplesOut[i][SIDE_RIGHT] += samp[1];
+ SamplesOut[i][BACK_LEFT] += samp[0];
+ SamplesOut[i][BACK_RIGHT] += samp[1];
+ }
+
+ state->Tap[0].offset = tap1off;
+ state->Tap[1].offset = tap2off;
+ state->Tap[2].offset = fboff;
+}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bf2cd4dd..59c8c78d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -238,6 +238,7 @@ SET(OPENAL_OBJS OpenAL32/alAuxEffectSlot.c
SET(ALC_OBJS Alc/ALc.c
Alc/ALu.c
Alc/alcConfig.c
+ Alc/alcEcho.c
Alc/alcReverb.c
Alc/alcRing.c
Alc/alcThread.c
diff --git a/OpenAL32/Include/alAuxEffectSlot.h b/OpenAL32/Include/alAuxEffectSlot.h
index a818bb47..8dfa9418 100644
--- a/OpenAL32/Include/alAuxEffectSlot.h
+++ b/OpenAL32/Include/alAuxEffectSlot.h
@@ -5,6 +5,7 @@
#include "alEffect.h"
#include "alFilter.h"
#include "alReverb.h"
+#include "alEcho.h"
#ifdef __cplusplus
extern "C" {
@@ -24,6 +25,7 @@ typedef struct ALeffectslot
ALboolean AuxSendAuto;
ALverbState *ReverbState;
+ ALechoState *EchoState;
ALfloat WetBuffer[BUFFERSIZE];
diff --git a/OpenAL32/Include/alEcho.h b/OpenAL32/Include/alEcho.h
new file mode 100644
index 00000000..34896ea7
--- /dev/null
+++ b/OpenAL32/Include/alEcho.h
@@ -0,0 +1,24 @@
+#ifndef AL_ECHO_H
+#define AL_ECHO_H
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "alMain.h"
+#include "alEffect.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct ALechoState ALechoState;
+
+ALechoState *EchoCreate(ALCcontext *Context);
+ALvoid EchoDestroy(ALechoState *State);
+ALvoid EchoUpdate(ALCcontext *Context, struct ALeffectslot *Slot, ALeffect *Effect);
+ALvoid EchoProcess(ALechoState *State, ALuint SamplesToDo, const ALfloat *SamplesIn, ALfloat (*SamplesOut)[OUTPUTCHANNELS]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/OpenAL32/Include/alEffect.h b/OpenAL32/Include/alEffect.h
index 5f6723bc..6dd1b766 100644
--- a/OpenAL32/Include/alEffect.h
+++ b/OpenAL32/Include/alEffect.h
@@ -37,9 +37,33 @@ extern "C" {
#define AL_REVERB_ROOM_ROLLOFF_FACTOR 0x000C
#define AL_REVERB_DECAY_HFLIMIT 0x000D
+#define AL_ECHO_DELAY 0x0001
+#define AL_ECHO_LRDELAY 0x0002
+#define AL_ECHO_DAMPING 0x0003
+#define AL_ECHO_FEEDBACK 0x0004
+#define AL_ECHO_SPREAD 0x0005
+
+#define AL_ECHO_MIN_DELAY (0.0f)
+#define AL_ECHO_MAX_DELAY (0.207f)
+#define AL_ECHO_DEFAULT_DELAY (0.1f)
+#define AL_ECHO_MIN_LRDELAY (0.0f)
+#define AL_ECHO_MAX_LRDELAY (0.404f)
+#define AL_ECHO_DEFAULT_LRDELAY (0.1f)
+#define AL_ECHO_MIN_DAMPING (0.0f)
+#define AL_ECHO_MAX_DAMPING (0.99f)
+#define AL_ECHO_DEFAULT_DAMPING (0.5f)
+#define AL_ECHO_MIN_FEEDBACK (0.0f)
+#define AL_ECHO_MAX_FEEDBACK (1.0f)
+#define AL_ECHO_DEFAULT_FEEDBACK (0.5f)
+#define AL_ECHO_MIN_SPREAD (-1.0f)
+#define AL_ECHO_MAX_SPREAD (1.0f)
+#define AL_ECHO_DEFAULT_SPREAD (-1.0f)
+
enum {
REVERB = 0,
+ ECHO,
+
MAX_EFFECTS
};
extern ALboolean DisabledEffects[MAX_EFFECTS];
@@ -67,6 +91,16 @@ typedef struct ALeffect_struct
ALboolean DecayHFLimit;
} Reverb;
+ struct {
+ ALfloat Delay;
+ ALfloat LRDelay;
+
+ ALfloat Damping;
+ ALfloat Feedback;
+
+ ALfloat Spread;
+ } Echo;
+
// Index to itself
ALuint effect;
diff --git a/OpenAL32/alAuxEffectSlot.c b/OpenAL32/alAuxEffectSlot.c
index 85a7c6ee..810b380e 100644
--- a/OpenAL32/alAuxEffectSlot.c
+++ b/OpenAL32/alAuxEffectSlot.c
@@ -151,6 +151,7 @@ ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots)
ALTHUNK_REMOVEENTRY(ALAuxiliaryEffectSlot->effectslot);
VerbDestroy(ALAuxiliaryEffectSlot->ReverbState);
+ EchoDestroy(ALAuxiliaryEffectSlot->EchoState);
memset(ALAuxiliaryEffectSlot, 0, sizeof(ALeffectslot));
free(ALAuxiliaryEffectSlot);
@@ -474,14 +475,32 @@ static ALvoid InitializeEffect(ALCcontext *Context, ALeffectslot *ALEffectSlot,
memset(&ALEffectSlot->effect, 0, sizeof(ALEffectSlot->effect));
VerbDestroy(ALEffectSlot->ReverbState);
ALEffectSlot->ReverbState = NULL;
+ EchoDestroy(ALEffectSlot->EchoState);
+ ALEffectSlot->EchoState = NULL;
return;
}
if(effect->type == AL_EFFECT_REVERB)
{
+ if(ALEffectSlot->EchoState)
+ {
+ EchoDestroy(ALEffectSlot->EchoState);
+ ALEffectSlot->EchoState = NULL;
+ }
if(!ALEffectSlot->ReverbState)
ALEffectSlot->ReverbState = VerbCreate(Context);
VerbUpdate(Context, ALEffectSlot, effect);
}
+ else if(effect->type == AL_EFFECT_ECHO)
+ {
+ if(ALEffectSlot->ReverbState)
+ {
+ VerbDestroy(ALEffectSlot->ReverbState);
+ ALEffectSlot->ReverbState = NULL;
+ }
+ if(!ALEffectSlot->EchoState)
+ ALEffectSlot->EchoState = EchoCreate(Context);
+ EchoUpdate(Context, ALEffectSlot, effect);
+ }
memcpy(&ALEffectSlot->effect, effect, sizeof(*effect));
}
@@ -500,6 +519,7 @@ ALvoid ReleaseALAuxiliaryEffectSlots(ALCcontext *Context)
// Release effectslot structure
VerbDestroy(temp->ReverbState);
+ EchoDestroy(temp->EchoState);
ALTHUNK_REMOVEENTRY(temp->effectslot);
memset(temp, 0, sizeof(ALeffectslot));
diff --git a/OpenAL32/alEffect.c b/OpenAL32/alEffect.c
index 939b663f..4455a230 100644
--- a/OpenAL32/alEffect.c
+++ b/OpenAL32/alEffect.c
@@ -170,7 +170,8 @@ ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue)
if(param == AL_EFFECT_TYPE)
{
ALboolean isOk = (iValue == AL_EFFECT_NULL ||
- (iValue == AL_EFFECT_REVERB && !DisabledEffects[REVERB]));
+ (iValue == AL_EFFECT_REVERB && !DisabledEffects[REVERB]) ||
+ (iValue == AL_EFFECT_ECHO && !DisabledEffects[ECHO]));
if(isOk)
InitEffectParams(ALEffect, iValue);
@@ -193,6 +194,15 @@ ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue)
break;
}
}
+ else if(ALEffect->type == AL_EFFECT_ECHO)
+ {
+ switch(param)
+ {
+ default:
+ alSetError(AL_INVALID_ENUM);
+ break;
+ }
+ }
else
alSetError(AL_INVALID_ENUM);
}
@@ -230,6 +240,15 @@ ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, ALint *piValues)
break;
}
}
+ else if(ALEffect->type == AL_EFFECT_ECHO)
+ {
+ switch(param)
+ {
+ default:
+ alSetError(AL_INVALID_ENUM);
+ break;
+ }
+ }
else
alSetError(AL_INVALID_ENUM);
}
@@ -343,6 +362,50 @@ ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue)
break;
}
}
+ else if(ALEffect->type == AL_EFFECT_ECHO)
+ {
+ switch(param)
+ {
+ case AL_ECHO_DELAY:
+ if(flValue >= AL_ECHO_MIN_DELAY && flValue <= AL_ECHO_MAX_DELAY)
+ ALEffect->Echo.Delay = flValue;
+ else
+ alSetError(AL_INVALID_VALUE);
+ break;
+
+ case AL_ECHO_LRDELAY:
+ if(flValue >= AL_ECHO_MIN_LRDELAY && flValue <= AL_ECHO_MAX_LRDELAY)
+ ALEffect->Echo.LRDelay = flValue;
+ else
+ alSetError(AL_INVALID_VALUE);
+ break;
+
+ case AL_ECHO_DAMPING:
+ if(flValue >= AL_ECHO_MIN_DAMPING && flValue <= AL_ECHO_MAX_DAMPING)
+ ALEffect->Echo.Damping = flValue;
+ else
+ alSetError(AL_INVALID_VALUE);
+ break;
+
+ case AL_ECHO_FEEDBACK:
+ if(flValue >= AL_ECHO_MIN_FEEDBACK && flValue <= AL_ECHO_MAX_FEEDBACK)
+ ALEffect->Echo.Feedback = flValue;
+ else
+ alSetError(AL_INVALID_VALUE);
+ break;
+
+ case AL_ECHO_SPREAD:
+ if(flValue >= AL_ECHO_MIN_SPREAD && flValue <= AL_ECHO_MAX_SPREAD)
+ ALEffect->Echo.Spread = flValue;
+ else
+ alSetError(AL_INVALID_VALUE);
+ break;
+
+ default:
+ alSetError(AL_INVALID_ENUM);
+ break;
+ }
+ }
else
alSetError(AL_INVALID_ENUM);
}
@@ -387,6 +450,23 @@ ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, ALfloat *pflValues)
break;
}
}
+ else if(ALEffect->type == AL_EFFECT_ECHO)
+ {
+ switch(param)
+ {
+ case AL_ECHO_DELAY:
+ case AL_ECHO_LRDELAY:
+ case AL_ECHO_DAMPING:
+ case AL_ECHO_FEEDBACK:
+ case AL_ECHO_SPREAD:
+ alEffectf(effect, param, pflValues[0]);
+ break;
+
+ default:
+ alSetError(AL_INVALID_ENUM);
+ break;
+ }
+ }
else
alSetError(AL_INVALID_ENUM);
}
@@ -424,6 +504,15 @@ ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue)
break;
}
}
+ else if(ALEffect->type == AL_EFFECT_ECHO)
+ {
+ switch(param)
+ {
+ default:
+ alSetError(AL_INVALID_ENUM);
+ break;
+ }
+ }
else
alSetError(AL_INVALID_ENUM);
}
@@ -461,6 +550,15 @@ ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues)
break;
}
}
+ else if(ALEffect->type == AL_EFFECT_ECHO)
+ {
+ switch(param)
+ {
+ default:
+ alSetError(AL_INVALID_ENUM);
+ break;
+ }
+ }
else
alSetError(AL_INVALID_ENUM);
}
@@ -538,6 +636,35 @@ ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue)
break;
}
}
+ else if(ALEffect->type == AL_EFFECT_ECHO)
+ {
+ switch(param)
+ {
+ case AL_ECHO_DELAY:
+ *pflValue = ALEffect->Echo.Delay;
+ break;
+
+ case AL_ECHO_LRDELAY:
+ *pflValue = ALEffect->Echo.LRDelay;
+ break;
+
+ case AL_ECHO_DAMPING:
+ *pflValue = ALEffect->Echo.Damping;
+ break;
+
+ case AL_ECHO_FEEDBACK:
+ *pflValue = ALEffect->Echo.Feedback;
+ break;
+
+ case AL_ECHO_SPREAD:
+ *pflValue = ALEffect->Echo.Spread;
+ break;
+
+ default:
+ alSetError(AL_INVALID_ENUM);
+ break;
+ }
+ }
else
alSetError(AL_INVALID_ENUM);
}
@@ -582,6 +709,23 @@ ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues
break;
}
}
+ else if(ALEffect->type == AL_EFFECT_ECHO)
+ {
+ switch(param)
+ {
+ case AL_ECHO_DELAY:
+ case AL_ECHO_LRDELAY:
+ case AL_ECHO_DAMPING:
+ case AL_ECHO_FEEDBACK:
+ case AL_ECHO_SPREAD:
+ alGetEffectf(effect, param, pflValues);
+ break;
+
+ default:
+ alSetError(AL_INVALID_ENUM);
+ break;
+ }
+ }
else
alSetError(AL_INVALID_ENUM);
}
@@ -632,5 +776,12 @@ static void InitEffectParams(ALeffect *effect, ALenum type)
effect->Reverb.RoomRolloffFactor = 0.0f;
effect->Reverb.DecayHFLimit = AL_TRUE;
break;
+ case AL_EFFECT_ECHO:
+ effect->Echo.Delay = AL_ECHO_DEFAULT_DELAY;
+ effect->Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY;
+ effect->Echo.Damping = AL_ECHO_DEFAULT_DAMPING;
+ effect->Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK;
+ effect->Echo.Spread = AL_ECHO_DEFAULT_SPREAD;
+ break;
}
}
diff --git a/alsoftrc.sample b/alsoftrc.sample
index 8408ab78..855b862e 100644
--- a/alsoftrc.sample
+++ b/alsoftrc.sample
@@ -64,7 +64,7 @@ drivers = # Sets the backend driver list order, comma-seperated. Unknown
excludefx = # Sets which effects to exclude, preventing apps from using them.
# This can help for apps that try to use effects which are too CPU
# intensive for the system to handle. Available effects are:
- # reverb
+ # reverb,echo
# Default is empty (all available effects enabled)
slots = 4 # Sets the maximum number of Auxiliary Effect Slots an app can