2 * OpenAL cross platform audio library
3 * Copyright (C) 1999-2007 by authors.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
32 #include "alcontext.h"
33 #include "alAuxEffectSlot.h"
35 #include "alListener.h"
38 #include "fpu_modes.h"
42 static void AddActiveEffectSlots(const ALuint
*slotids
, ALsizei count
, ALCcontext
*context
);
43 static void RemoveActiveEffectSlots(const ALuint
*slotids
, ALsizei count
, ALCcontext
*context
);
47 EffectStateFactory
* (*GetFactory
)(void);
49 { AL_EFFECT_NULL
, NullStateFactory_getFactory
},
50 { AL_EFFECT_EAXREVERB
, ReverbStateFactory_getFactory
},
51 { AL_EFFECT_REVERB
, ReverbStateFactory_getFactory
},
52 { AL_EFFECT_AUTOWAH
, AutowahStateFactory_getFactory
},
53 { AL_EFFECT_CHORUS
, ChorusStateFactory_getFactory
},
54 { AL_EFFECT_COMPRESSOR
, CompressorStateFactory_getFactory
},
55 { AL_EFFECT_DISTORTION
, DistortionStateFactory_getFactory
},
56 { AL_EFFECT_ECHO
, EchoStateFactory_getFactory
},
57 { AL_EFFECT_EQUALIZER
, EqualizerStateFactory_getFactory
},
58 { AL_EFFECT_FLANGER
, FlangerStateFactory_getFactory
},
59 { AL_EFFECT_FREQUENCY_SHIFTER
, FshifterStateFactory_getFactory
},
60 { AL_EFFECT_RING_MODULATOR
, ModulatorStateFactory_getFactory
},
61 { AL_EFFECT_PITCH_SHIFTER
, PshifterStateFactory_getFactory
},
62 { AL_EFFECT_DEDICATED_DIALOGUE
, DedicatedStateFactory_getFactory
},
63 { AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT
, DedicatedStateFactory_getFactory
}
66 static inline EffectStateFactory
*getFactoryByType(ALenum type
)
69 for(i
= 0;i
< COUNTOF(FactoryList
);i
++)
71 if(FactoryList
[i
].Type
== type
)
72 return FactoryList
[i
].GetFactory();
77 static void ALeffectState_IncRef(ALeffectState
*state
);
80 static inline ALeffectslot
*LookupEffectSlot(ALCcontext
*context
, ALuint id
)
83 if(UNLIKELY(id
>= context
->EffectSlotList
.size()))
85 return context
->EffectSlotList
[id
];
88 static inline ALeffect
*LookupEffect(ALCdevice
*device
, ALuint id
)
90 ALuint lidx
= (id
-1) >> 6;
91 ALsizei slidx
= (id
-1) & 0x3f;
93 if(UNLIKELY(lidx
>= device
->EffectList
.size()))
95 EffectSubList
&sublist
= device
->EffectList
[lidx
];
96 if(UNLIKELY(sublist
.FreeMask
& (U64(1)<<slidx
)))
98 return sublist
.Effects
+ slidx
;
102 #define DO_UPDATEPROPS() do { \
103 if(!ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire)) \
104 UpdateEffectSlotProps(slot, context); \
106 ATOMIC_STORE(&slot->PropsClean, AL_FALSE, almemory_order_release); \
110 AL_API ALvoid AL_APIENTRY
alGenAuxiliaryEffectSlots(ALsizei n
, ALuint
*effectslots
)
116 context
= GetContextRef();
120 SETERR_GOTO(context
, AL_INVALID_VALUE
, done
, "Generating %d effect slots", n
);
121 if(n
== 0) goto done
;
123 LockEffectSlotList(context
);
124 device
= context
->Device
;
125 for(cur
= 0;cur
< n
;cur
++)
127 auto iter
= std::find_if(context
->EffectSlotList
.begin(), context
->EffectSlotList
.end(),
128 [](const ALeffectslotPtr
&entry
) noexcept
-> bool
131 if(iter
== context
->EffectSlotList
.end())
133 if(device
->AuxiliaryEffectSlotMax
== context
->EffectSlotList
.size())
135 UnlockEffectSlotList(context
);
136 alDeleteAuxiliaryEffectSlots(cur
, effectslots
);
137 SETERR_GOTO(context
, AL_OUT_OF_MEMORY
, done
,
138 "Exceeding %u auxiliary effect slot limit", device
->AuxiliaryEffectSlotMax
);
140 context
->EffectSlotList
.emplace_back(nullptr);
141 iter
= context
->EffectSlotList
.end() - 1;
144 auto slot
= new ALeffectslot
{};
145 ALenum err
{InitEffectSlot(slot
)};
146 if(err
!= AL_NO_ERROR
)
149 UnlockEffectSlotList(context
);
151 alDeleteAuxiliaryEffectSlots(cur
, effectslots
);
152 SETERR_GOTO(context
, err
, done
, "Effect slot object allocation failed");
154 aluInitEffectPanning(slot
);
156 slot
->id
= std::distance(context
->EffectSlotList
.begin(), iter
) + 1;
159 effectslots
[cur
] = slot
->id
;
161 AddActiveEffectSlots(effectslots
, n
, context
);
162 UnlockEffectSlotList(context
);
165 ALCcontext_DecRef(context
);
168 AL_API ALvoid AL_APIENTRY
alDeleteAuxiliaryEffectSlots(ALsizei n
, const ALuint
*effectslots
)
174 context
= GetContextRef();
177 LockEffectSlotList(context
);
179 SETERR_GOTO(context
, AL_INVALID_VALUE
, done
, "Deleting %d effect slots", n
);
180 if(n
== 0) goto done
;
184 if((slot
=LookupEffectSlot(context
, effectslots
[i
])) == nullptr)
185 SETERR_GOTO(context
, AL_INVALID_NAME
, done
, "Invalid effect slot ID %u",
187 if(ReadRef(&slot
->ref
) != 0)
188 SETERR_GOTO(context
, AL_INVALID_NAME
, done
, "Deleting in-use effect slot %u",
192 // All effectslots are valid
193 RemoveActiveEffectSlots(effectslots
, n
, context
);
196 if((slot
=LookupEffectSlot(context
, effectslots
[i
])) == nullptr)
198 context
->EffectSlotList
[effectslots
[i
]-1] = nullptr;
204 UnlockEffectSlotList(context
);
205 ALCcontext_DecRef(context
);
208 AL_API ALboolean AL_APIENTRY
alIsAuxiliaryEffectSlot(ALuint effectslot
)
213 context
= GetContextRef();
214 if(!context
) return AL_FALSE
;
216 LockEffectSlotList(context
);
217 ret
= (LookupEffectSlot(context
, effectslot
) ? AL_TRUE
: AL_FALSE
);
218 UnlockEffectSlotList(context
);
220 ALCcontext_DecRef(context
);
225 AL_API ALvoid AL_APIENTRY
alAuxiliaryEffectSloti(ALuint effectslot
, ALenum param
, ALint value
)
230 ALeffect
*effect
= nullptr;
233 context
= GetContextRef();
236 almtx_lock(&context
->PropLock
);
237 LockEffectSlotList(context
);
238 if((slot
=LookupEffectSlot(context
, effectslot
)) == nullptr)
239 SETERR_GOTO(context
, AL_INVALID_NAME
, done
, "Invalid effect slot ID %u", effectslot
);
242 case AL_EFFECTSLOT_EFFECT
:
243 device
= context
->Device
;
245 LockEffectList(device
);
246 effect
= (value
? LookupEffect(device
, value
) : nullptr);
247 if(!(value
== 0 || effect
!= nullptr))
249 UnlockEffectList(device
);
250 SETERR_GOTO(context
, AL_INVALID_VALUE
, done
, "Invalid effect ID %u", value
);
252 err
= InitializeEffect(context
, slot
, effect
);
253 UnlockEffectList(device
);
255 if(err
!= AL_NO_ERROR
)
256 SETERR_GOTO(context
, err
, done
, "Effect initialization failed");
259 case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO
:
260 if(!(value
== AL_TRUE
|| value
== AL_FALSE
))
261 SETERR_GOTO(context
, AL_INVALID_VALUE
, done
,
262 "Effect slot auxiliary send auto out of range");
263 slot
->AuxSendAuto
= value
;
267 SETERR_GOTO(context
, AL_INVALID_ENUM
, done
, "Invalid effect slot integer property 0x%04x",
273 UnlockEffectSlotList(context
);
274 almtx_unlock(&context
->PropLock
);
275 ALCcontext_DecRef(context
);
278 AL_API ALvoid AL_APIENTRY
alAuxiliaryEffectSlotiv(ALuint effectslot
, ALenum param
, const ALint
*values
)
284 case AL_EFFECTSLOT_EFFECT
:
285 case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO
:
286 alAuxiliaryEffectSloti(effectslot
, param
, values
[0]);
290 context
= GetContextRef();
293 LockEffectSlotList(context
);
294 if(LookupEffectSlot(context
, effectslot
) == nullptr)
295 SETERR_GOTO(context
, AL_INVALID_NAME
, done
, "Invalid effect slot ID %u", effectslot
);
299 alSetError(context
, AL_INVALID_ENUM
, "Invalid effect slot integer-vector property 0x%04x",
304 UnlockEffectSlotList(context
);
305 ALCcontext_DecRef(context
);
308 AL_API ALvoid AL_APIENTRY
alAuxiliaryEffectSlotf(ALuint effectslot
, ALenum param
, ALfloat value
)
313 context
= GetContextRef();
316 almtx_lock(&context
->PropLock
);
317 LockEffectSlotList(context
);
318 if((slot
=LookupEffectSlot(context
, effectslot
)) == nullptr)
319 SETERR_GOTO(context
, AL_INVALID_NAME
, done
, "Invalid effect slot ID %u", effectslot
);
322 case AL_EFFECTSLOT_GAIN
:
323 if(!(value
>= 0.0f
&& value
<= 1.0f
))
324 SETERR_GOTO(context
, AL_INVALID_VALUE
, done
, "Effect slot gain out of range");
329 SETERR_GOTO(context
, AL_INVALID_ENUM
, done
, "Invalid effect slot float property 0x%04x",
335 UnlockEffectSlotList(context
);
336 almtx_unlock(&context
->PropLock
);
337 ALCcontext_DecRef(context
);
340 AL_API ALvoid AL_APIENTRY
alAuxiliaryEffectSlotfv(ALuint effectslot
, ALenum param
, const ALfloat
*values
)
346 case AL_EFFECTSLOT_GAIN
:
347 alAuxiliaryEffectSlotf(effectslot
, param
, values
[0]);
351 context
= GetContextRef();
354 LockEffectSlotList(context
);
355 if(LookupEffectSlot(context
, effectslot
) == nullptr)
356 SETERR_GOTO(context
, AL_INVALID_NAME
, done
, "Invalid effect slot ID %u", effectslot
);
360 alSetError(context
, AL_INVALID_ENUM
, "Invalid effect slot float-vector property 0x%04x",
365 UnlockEffectSlotList(context
);
366 ALCcontext_DecRef(context
);
369 AL_API ALvoid AL_APIENTRY
alGetAuxiliaryEffectSloti(ALuint effectslot
, ALenum param
, ALint
*value
)
374 context
= GetContextRef();
377 LockEffectSlotList(context
);
378 if((slot
=LookupEffectSlot(context
, effectslot
)) == nullptr)
379 SETERR_GOTO(context
, AL_INVALID_NAME
, done
, "Invalid effect slot ID %u", effectslot
);
382 case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO
:
383 *value
= slot
->AuxSendAuto
;
387 alSetError(context
, AL_INVALID_ENUM
, "Invalid effect slot integer property 0x%04x", param
);
391 UnlockEffectSlotList(context
);
392 ALCcontext_DecRef(context
);
395 AL_API ALvoid AL_APIENTRY
alGetAuxiliaryEffectSlotiv(ALuint effectslot
, ALenum param
, ALint
*values
)
401 case AL_EFFECTSLOT_EFFECT
:
402 case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO
:
403 alGetAuxiliaryEffectSloti(effectslot
, param
, values
);
407 context
= GetContextRef();
410 LockEffectSlotList(context
);
411 if(LookupEffectSlot(context
, effectslot
) == nullptr)
412 SETERR_GOTO(context
, AL_INVALID_NAME
, done
, "Invalid effect slot ID %u", effectslot
);
416 alSetError(context
, AL_INVALID_ENUM
, "Invalid effect slot integer-vector property 0x%04x",
421 UnlockEffectSlotList(context
);
422 ALCcontext_DecRef(context
);
425 AL_API ALvoid AL_APIENTRY
alGetAuxiliaryEffectSlotf(ALuint effectslot
, ALenum param
, ALfloat
*value
)
430 context
= GetContextRef();
433 LockEffectSlotList(context
);
434 if((slot
=LookupEffectSlot(context
, effectslot
)) == nullptr)
435 SETERR_GOTO(context
, AL_INVALID_NAME
, done
, "Invalid effect slot ID %u", effectslot
);
438 case AL_EFFECTSLOT_GAIN
:
443 alSetError(context
, AL_INVALID_ENUM
, "Invalid effect slot float property 0x%04x", param
);
447 UnlockEffectSlotList(context
);
448 ALCcontext_DecRef(context
);
451 AL_API ALvoid AL_APIENTRY
alGetAuxiliaryEffectSlotfv(ALuint effectslot
, ALenum param
, ALfloat
*values
)
457 case AL_EFFECTSLOT_GAIN
:
458 alGetAuxiliaryEffectSlotf(effectslot
, param
, values
);
462 context
= GetContextRef();
465 LockEffectSlotList(context
);
466 if(LookupEffectSlot(context
, effectslot
) == nullptr)
467 SETERR_GOTO(context
, AL_INVALID_NAME
, done
, "Invalid effect slot ID %u", effectslot
);
471 alSetError(context
, AL_INVALID_ENUM
, "Invalid effect slot float-vector property 0x%04x",
476 UnlockEffectSlotList(context
);
477 ALCcontext_DecRef(context
);
481 ALenum
InitializeEffect(ALCcontext
*Context
, ALeffectslot
*EffectSlot
, ALeffect
*effect
)
483 ALCdevice
*Device
= Context
->Device
;
484 ALenum newtype
= (effect
? effect
->type
: AL_EFFECT_NULL
);
485 struct ALeffectslotProps
*props
;
486 ALeffectState
*State
;
488 if(newtype
!= EffectSlot
->Effect
.Type
)
490 EffectStateFactory
*factory
;
492 factory
= getFactoryByType(newtype
);
495 ERR("Failed to find factory for effect type 0x%04x\n", newtype
);
496 return AL_INVALID_ENUM
;
498 State
= EffectStateFactory_create(factory
);
499 if(!State
) return AL_OUT_OF_MEMORY
;
502 almtx_lock(&Device
->BackendLock
);
503 State
->OutBuffer
= Device
->Dry
.Buffer
;
504 State
->OutChannels
= Device
->Dry
.NumChannels
;
505 if(V(State
,deviceUpdate
)(Device
) == AL_FALSE
)
507 almtx_unlock(&Device
->BackendLock
);
509 ALeffectState_DecRef(State
);
510 return AL_OUT_OF_MEMORY
;
512 almtx_unlock(&Device
->BackendLock
);
517 EffectSlot
->Effect
.Type
= AL_EFFECT_NULL
;
518 memset(&EffectSlot
->Effect
.Props
, 0, sizeof(EffectSlot
->Effect
.Props
));
522 EffectSlot
->Effect
.Type
= effect
->type
;
523 EffectSlot
->Effect
.Props
= effect
->Props
;
526 ALeffectState_DecRef(EffectSlot
->Effect
.State
);
527 EffectSlot
->Effect
.State
= State
;
530 EffectSlot
->Effect
.Props
= effect
->Props
;
532 /* Remove state references from old effect slot property updates. */
533 props
= ATOMIC_LOAD_SEQ(&Context
->FreeEffectslotProps
);
537 ALeffectState_DecRef(props
->State
);
538 props
->State
= nullptr;
539 props
= ATOMIC_LOAD(&props
->next
, almemory_order_relaxed
);
546 static void ALeffectState_IncRef(ALeffectState
*state
)
549 ref
= IncrementRef(&state
->Ref
);
550 TRACEREF("%p increasing refcount to %u\n", state
, ref
);
553 void ALeffectState_DecRef(ALeffectState
*state
)
556 ref
= DecrementRef(&state
->Ref
);
557 TRACEREF("%p decreasing refcount to %u\n", state
, ref
);
558 if(ref
== 0) DELETE_OBJ(state
);
562 void ALeffectState_Construct(ALeffectState
*state
)
564 InitRef(&state
->Ref
, 1);
566 state
->OutBuffer
= nullptr;
567 state
->OutChannels
= 0;
570 void ALeffectState_Destruct(ALeffectState
*UNUSED(state
))
575 static void AddActiveEffectSlots(const ALuint
*slotids
, ALsizei count
, ALCcontext
*context
)
577 struct ALeffectslotArray
*curarray
= ATOMIC_LOAD(&context
->ActiveAuxSlots
,
578 almemory_order_acquire
);
579 struct ALeffectslotArray
*newarray
= nullptr;
580 ALsizei newcount
= curarray
->count
+ count
;
581 ALCdevice
*device
= context
->Device
;
584 /* Insert the new effect slots into the head of the array, followed by the
587 newarray
= static_cast<struct ALeffectslotArray
*>(al_calloc(DEF_ALIGN
,
588 FAM_SIZE(struct ALeffectslotArray
, slot
, newcount
)));
589 newarray
->count
= newcount
;
590 for(i
= 0;i
< count
;i
++)
591 newarray
->slot
[i
] = LookupEffectSlot(context
, slotids
[i
]);
592 for(j
= 0;i
< newcount
;)
593 newarray
->slot
[i
++] = curarray
->slot
[j
++];
594 /* Remove any duplicates (first instance of each will be kept). */
595 for(i
= 1;i
< newcount
;i
++)
599 if(UNLIKELY(newarray
->slot
[i
] == newarray
->slot
[--j
]))
602 for(j
= i
;j
< newcount
;j
++)
603 newarray
->slot
[j
] = newarray
->slot
[j
+1];
610 /* Reallocate newarray if the new size ended up smaller from duplicate
613 if(UNLIKELY(newcount
< newarray
->count
))
615 struct ALeffectslotArray
*tmp
= static_cast<struct ALeffectslotArray
*>(al_calloc(DEF_ALIGN
,
616 FAM_SIZE(struct ALeffectslotArray
, slot
, newcount
)));
617 memcpy(tmp
, newarray
, FAM_SIZE(struct ALeffectslotArray
, slot
, newcount
));
620 newarray
->count
= newcount
;
623 curarray
= ATOMIC_EXCHANGE(&context
->ActiveAuxSlots
, newarray
, almemory_order_acq_rel
);
624 while((ATOMIC_LOAD(&device
->MixCount
, almemory_order_acquire
)&1))
629 static void RemoveActiveEffectSlots(const ALuint
*slotids
, ALsizei count
, ALCcontext
*context
)
631 struct ALeffectslotArray
*curarray
= ATOMIC_LOAD(&context
->ActiveAuxSlots
,
632 almemory_order_acquire
);
633 struct ALeffectslotArray
*newarray
= nullptr;
634 ALCdevice
*device
= context
->Device
;
637 /* Don't shrink the allocated array size since we don't know how many (if
638 * any) of the effect slots to remove are in the array.
640 newarray
= static_cast<struct ALeffectslotArray
*>(al_calloc(DEF_ALIGN
,
641 FAM_SIZE(struct ALeffectslotArray
, slot
, curarray
->count
)));
643 for(i
= 0;i
< curarray
->count
;i
++)
645 /* Insert this slot into the new array only if it's not one to remove. */
646 ALeffectslot
*slot
= curarray
->slot
[i
];
647 for(j
= count
;j
!= 0;)
649 if(slot
->id
== slotids
[--j
])
652 newarray
->slot
[newarray
->count
++] = slot
;
656 /* TODO: Could reallocate newarray now that we know it's needed size. */
658 curarray
= ATOMIC_EXCHANGE(&context
->ActiveAuxSlots
, newarray
, almemory_order_acq_rel
);
659 while((ATOMIC_LOAD(&device
->MixCount
, almemory_order_acquire
)&1))
665 ALenum
InitEffectSlot(ALeffectslot
*slot
)
667 EffectStateFactory
*factory
{getFactoryByType(slot
->Effect
.Type
)};
668 slot
->Effect
.State
= EffectStateFactory_create(factory
);
669 if(!slot
->Effect
.State
) return AL_OUT_OF_MEMORY
;
671 ALeffectState_IncRef(slot
->Effect
.State
);
672 slot
->Params
.EffectState
= slot
->Effect
.State
;
676 ALeffectslot::~ALeffectslot()
678 struct ALeffectslotProps
*props
{Update
.load()};
681 if(props
->State
) ALeffectState_DecRef(props
->State
);
682 TRACE("Freed unapplied AuxiliaryEffectSlot update %p\n", props
);
687 ALeffectState_DecRef(Effect
.State
);
688 if(Params
.EffectState
)
689 ALeffectState_DecRef(Params
.EffectState
);
692 void UpdateEffectSlotProps(ALeffectslot
*slot
, ALCcontext
*context
)
694 struct ALeffectslotProps
*props
;
695 ALeffectState
*oldstate
;
697 /* Get an unused property container, or allocate a new one as needed. */
698 props
= ATOMIC_LOAD(&context
->FreeEffectslotProps
, almemory_order_relaxed
);
700 props
= static_cast<ALeffectslotProps
*>(al_calloc(16, sizeof(*props
)));
703 struct ALeffectslotProps
*next
;
705 next
= ATOMIC_LOAD(&props
->next
, almemory_order_relaxed
);
706 } while(ATOMIC_COMPARE_EXCHANGE_WEAK(&context
->FreeEffectslotProps
, &props
, next
,
707 almemory_order_seq_cst
, almemory_order_acquire
) == 0);
710 /* Copy in current property values. */
711 props
->Gain
= slot
->Gain
;
712 props
->AuxSendAuto
= slot
->AuxSendAuto
;
714 props
->Type
= slot
->Effect
.Type
;
715 props
->Props
= slot
->Effect
.Props
;
716 /* Swap out any stale effect state object there may be in the container, to
719 ALeffectState_IncRef(slot
->Effect
.State
);
720 oldstate
= props
->State
;
721 props
->State
= slot
->Effect
.State
;
723 /* Set the new container for updating internal parameters. */
724 props
= ATOMIC_EXCHANGE(&slot
->Update
, props
, almemory_order_acq_rel
);
727 /* If there was an unused update container, put it back in the
731 ALeffectState_DecRef(props
->State
);
732 props
->State
= nullptr;
733 ATOMIC_REPLACE_HEAD(struct ALeffectslotProps
*, &context
->FreeEffectslotProps
, props
);
737 ALeffectState_DecRef(oldstate
);
740 void UpdateAllEffectSlotProps(ALCcontext
*context
)
742 struct ALeffectslotArray
*auxslots
;
745 LockEffectSlotList(context
);
746 auxslots
= ATOMIC_LOAD(&context
->ActiveAuxSlots
, almemory_order_acquire
);
747 for(i
= 0;i
< auxslots
->count
;i
++)
749 ALeffectslot
*slot
= auxslots
->slot
[i
];
750 if(!ATOMIC_EXCHANGE(&slot
->PropsClean
, AL_TRUE
, almemory_order_acq_rel
))
751 UpdateEffectSlotProps(slot
, context
);
753 UnlockEffectSlotList(context
);
756 ALvoid
ReleaseALAuxiliaryEffectSlots(ALCcontext
*context
)
759 for(auto &entry
: context
->EffectSlotList
)
768 WARN("(%p) Deleted " SZFMT
" AuxiliaryEffectSlot%s\n", context
, leftover
, (leftover
==1)?"":"s");