pl-nk v0.4.5
Plonk|Plink|Plank are a set of cross-platform C/C++ frameworks for audio software development
plonk_IOSAudioHostInline.h
00001 /*
00002  -------------------------------------------------------------------------------
00003  This file is part of the Plink, Plonk, Plank libraries
00004  by Martin Robinson
00005  
00006  http://code.google.com/p/pl-nk/
00007  
00008  Copyright University of the West of England, Bristol 2011-14
00009  All rights reserved.
00010  
00011  Redistribution and use in source and binary forms, with or without
00012  modification, are permitted provided that the following conditions are met:
00013  
00014  * Redistributions of source code must retain the above copyright
00015    notice, this list of conditions and the following disclaimer.
00016  * Redistributions in binary form must reproduce the above copyright
00017    notice, this list of conditions and the following disclaimer in the
00018    documentation and/or other materials provided with the distribution.
00019  * Neither the name of University of the West of England, Bristol nor 
00020    the names of its contributors may be used to endorse or promote products
00021    derived from this software without specific prior written permission.
00022  
00023  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
00024  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00025  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
00026  DISCLAIMED. IN NO EVENT SHALL UNIVERSITY OF THE WEST OF ENGLAND, BRISTOL BE 
00027  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
00028  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
00029  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
00030  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
00031  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
00032  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
00033  
00034  This software makes use of third party libraries. For more information see:
00035  doc/license.txt included in the distribution.
00036  -------------------------------------------------------------------------------
00037  */
00038 
00039 #if PLANK_INLINING_FUNCTIONS
00040 
00041 BEGIN_PLONK_NAMESPACE
00042 
00045 //  #pragma clang diagnostic push
00046 //  #pragma clang diagnostic ignored "-Wno-deprecated"
00047 //
00048   #pragma GCC diagnostic push
00049   #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
00050 //
00053 
00054 template<class SampleType>
00055 static OSStatus Render (void                        *inRefCon,
00056                         AudioUnitRenderActionFlags      *ioActionFlags,
00057                         const AudioTimeStamp        *inTimeStamp,
00058                         UInt32                                          inBusNumber,
00059                         UInt32                                          inNumberFrames,
00060                         AudioBufferList                         *ioData)
00061 {       
00062         IOSAudioHostBase<SampleType> *host = (IOSAudioHostBase<SampleType> *)inRefCon;
00063         const OSStatus result =  host->renderCallback (inNumberFrames, 
00064                                                    ioActionFlags, 
00065                                                    inTimeStamp, 
00066                                                    ioData);
00067         return result;
00068 }
00069 
00070 template<class SampleType>
00071 static void PropertyListener (void *                  inClientData,
00072                               AudioSessionPropertyID  inID,
00073                               UInt32                  inDataSize,
00074                               const void *            inPropertyValue)
00075 {       
00076         IOSAudioHostBase<SampleType> *host = (IOSAudioHostBase<SampleType> *)inClientData;
00077     host->propertyCallback (inID, inDataSize, inPropertyValue);
00078 }
00079 
00080 template<class SampleType>
00081 static void InterruptionListener (void *inClientData,
00082                                   UInt32 inInterruption)
00083 {       
00084         IOSAudioHostBase<SampleType> *host = (IOSAudioHostBase<SampleType> *)inClientData;
00085     host->interruptionCallback (inInterruption);
00086 }
00087 
00088 
00089 template<class SrcType>
00090 static inline void audioTypeToShort (const SrcType * const src, short* const dst, const unsigned int length) throw()
00091 {
00092     NumericalArrayConverter<short,SrcType>::convertScaled (dst, src, length);    
00093 }
00094 
00095 template<class SrcType>
00096 static inline void audioTypeToShortChannels (SrcType * const src[], AudioBufferList* const dst, const unsigned int length, const unsigned int numChannels) throw()
00097 {
00098         for (UInt32 channel = 0; channel < numChannels; ++channel)
00099         {
00100         AudioBuffer* const audioBuffer = dst->mBuffers + channel;
00101                 AudioSampleType* const audioUnitSamples = (AudioSampleType*)audioBuffer->mData;
00102                 audioTypeToShort (src[channel], audioUnitSamples, length);
00103         }
00104 }
00105 
00106 template<class DstType>
00107 static inline void audioShortToType (const short * const src, DstType* const dst, const unsigned int length) throw()
00108 {
00109     NumericalArrayConverter<DstType,short>::convertScaled (dst, src, length);
00110 }
00111 
00112 template<class DstType>
00113 static inline void audioShortToTypeChannels (AudioBufferList* src, DstType* const dst[], const unsigned int length, const unsigned int numChannels) throw()
00114 {
00115         for (UInt32 channel = 0; channel < numChannels; ++channel)
00116         {
00117                 AudioSampleType* const audioUnitSamples = (AudioSampleType*)src->mBuffers[0].mData; // need this other than 0?...
00118                 audioShortToType (audioUnitSamples, dst[channel], length);
00119         }       
00120 }
00121 
00122 //------------------------------------------------------------------------------
00123 
00124 template<class SampleType>
00125 IOSAudioHostBase<SampleType>::IOSAudioHostBase() throw()
00126 :   hwSampleRate (0.0),         // let the hardware choose
00127     rioUnit (NULL),
00128     cpuUsage (0.0),
00129     interrupted (NO),
00130     customRenderCallbackRef (NULL),
00131     customPreRender (NULL),
00132     customPostRender (NULL)
00133 {
00134     this->setPreferredGraphBlockSize (256);
00135 }
00136 
00137 template<class SampleType>
00138 IOSAudioHostBase<SampleType>::~IOSAudioHostBase()
00139 {
00140     this->stopHost();
00141     this->killRemoteIO();
00142 }
00143 
00144 template<class SampleType>
00145 Text IOSAudioHostBase<SampleType>::getHostName() const throw()
00146 {
00147     return "iOS (" + TypeUtility<SampleType>::getTypeName() + ")";
00148 }
00149 
00150 template<class SampleType>
00151 Text IOSAudioHostBase<SampleType>::getNativeHostName() const throw()
00152 {
00153     return "RemoteIO (Short)";
00154 }
00155 
00156 template<class SampleType>
00157 Text IOSAudioHostBase<SampleType>::getInputName() const throw()
00158 {
00159     Text result = "Default Input";
00160     
00161     CFDictionaryRef dictionary = 0;
00162         UInt32 propertySize = sizeof (dictionary);
00163     
00164         if (AudioSessionGetProperty (kAudioSessionProperty_AudioRouteDescription, &propertySize, &dictionary) == noErr)
00165         {
00166         CFArrayRef array = (CFArrayRef)CFDictionaryGetValue (dictionary, kAudioSession_AudioRouteKey_Inputs);
00167         
00168         if (array != nil)
00169         {
00170             for (int i = 0; i < CFArrayGetCount (array); ++i)
00171             {  
00172                 CFDictionaryRef dictionary2 = (CFDictionaryRef)CFArrayGetValueAtIndex (array, i);
00173                 NSString* name = (NSString*)CFDictionaryGetValue (dictionary2, kAudioSession_AudioRouteKey_Type);
00174                 result = [name UTF8String];
00175             }
00176         }
00177         
00178                 CFRelease (dictionary);
00179         }
00180     
00181     return result;
00182 }
00183 
00184 template<class SampleType>
00185 Text IOSAudioHostBase<SampleType>::getOutputName() const throw()
00186 {    
00187     Text result = "Default Output";
00188     
00189     CFDictionaryRef dictionary = 0;
00190         UInt32 propertySize = sizeof (dictionary);
00191     
00192         if (AudioSessionGetProperty (kAudioSessionProperty_AudioRouteDescription, &propertySize, &dictionary) == noErr)
00193         {
00194         CFArrayRef array = (CFArrayRef)CFDictionaryGetValue (dictionary, kAudioSession_AudioRouteKey_Outputs);
00195         
00196         if (array != nil)
00197         {
00198             for (int i = 0; i < CFArrayGetCount (array); ++i)
00199             {  
00200                 CFDictionaryRef dictionary2 = (CFDictionaryRef)CFArrayGetValueAtIndex (array, i);
00201                 NSString* name = (NSString*)CFDictionaryGetValue (dictionary2, kAudioSession_AudioRouteKey_Type);
00202                 result = [name UTF8String];
00203             }
00204         }
00205         
00206                 CFRelease (dictionary);
00207         }
00208     
00209     return result;
00210 }
00211 
00212 template<class SampleType>
00213 void IOSAudioHostBase<SampleType>::stopHost() throw()
00214 {
00215     if (this->getIsRunning())
00216     {
00217         AudioOutputUnitStop (rioUnit);
00218         this->setIsRunning (false);
00219         this->hostStopped();
00220     }
00221 }
00222 
00223 template<class SampleType>
00224 void IOSAudioHostBase<SampleType>::startHost() throw()
00225 {
00226     if (rioUnit != NULL)
00227     {
00228         this->setIsRunning (true);
00229         AudioSessionSetActive (true);
00230         this->hostStarting();
00231         restart();
00232     }
00233     else
00234     {
00235         UInt32 size;
00236         
00237         // render proc
00238         inputProc.inputProc = Render<SampleType>;
00239         inputProc.inputProcRefCon = this;
00240             
00241         // session
00242         AudioSessionInitialize (NULL, NULL, InterruptionListener<SampleType>, this);
00243         AudioSessionSetActive (true);
00244                 
00245         AudioSessionAddPropertyListener (kAudioSessionProperty_AudioRouteChange, PropertyListener<SampleType>, this);
00246         AudioSessionAddPropertyListener (kAudioSessionProperty_InterruptionType, PropertyListener<SampleType>, this);
00247         AudioSessionAddPropertyListener (kAudioSessionProperty_AudioCategory, PropertyListener<SampleType>, this);
00248         
00249         fixAudioRouteIfSetToReceiver();
00250         
00251         const double preferredHostSampleRate = this->getPreferredHostSampleRate();
00252         size = sizeof (preferredHostSampleRate);
00253         
00254         if (preferredHostSampleRate > 0.0)
00255             AudioSessionSetProperty (kAudioSessionProperty_PreferredHardwareSampleRate, size, &preferredHostSampleRate);
00256             
00257         resetIO();
00258         
00259         this->setPreferredHostSampleRate (hwSampleRate);
00260         this->setPreferredHostBlockSize (bufferSize);
00261         this->startHostInternal();
00262         
00263         restart();
00264         
00265         size = sizeof (format);
00266         AudioUnitGetProperty (rioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, &size);
00267     }    
00268 }
00269 
00270 template<class SampleType>
00271 void IOSAudioHostBase<SampleType>::pauseHost() throw()
00272 {
00273     if (this->getIsRunning())
00274     {
00275         AudioOutputUnitStop (rioUnit);
00276     }
00277 
00278     this->setIsPaused (true);
00279     this->hostPaused();
00280 }
00281 
00282 
00283 template<class SampleType>
00284 void IOSAudioHostBase<SampleType>::resumeHost() throw()
00285 {
00286     this->hostResuming();
00287     this->setIsPaused (false);
00288 
00289     if (this->getIsRunning())
00290     {
00291         AudioSessionSetActive (true);
00292         AudioOutputUnitStart (rioUnit);
00293     }
00294 }
00295 
00296 template<class SampleType>
00297 void IOSAudioHostBase<SampleType>::setFormat() throw()
00298 {
00299         memset (&format, 0, sizeof (AudioStreamBasicDescription));
00300         format.mSampleRate = hwSampleRate;
00301         format.mFormatID = kAudioFormatLinearPCM;
00302         const int sampleSize = sizeof (AudioSampleType);
00303         format.mFormatFlags = kAudioFormatFlagsCanonical;
00304         format.mBitsPerChannel = 8 * sampleSize;
00305         format.mChannelsPerFrame = this->getNumOutputs();
00306         format.mFramesPerPacket = 1;
00307         format.mBytesPerPacket = format.mBytesPerFrame = sampleSize;
00308         format.mFormatFlags |= kAudioFormatFlagIsNonInterleaved;                
00309 }
00310 
00311 template<class SampleType>
00312 int IOSAudioHostBase<SampleType>::setupRemoteIO() throw()
00313 {       
00314         // Open the output unit
00315         AudioComponentDescription desc;
00316         desc.componentType = kAudioUnitType_Output;
00317         desc.componentSubType = kAudioUnitSubType_RemoteIO;
00318         desc.componentManufacturer = kAudioUnitManufacturer_Apple;
00319         desc.componentFlags = 0;
00320         desc.componentFlagsMask = 0;
00321         
00322         AudioComponent comp = AudioComponentFindNext (NULL, &desc);
00323         AudioComponentInstanceNew (comp, &rioUnit);
00324 
00325         OSStatus status;
00326         const UInt32 one = 1;
00327 
00328     status = AudioUnitSetProperty (rioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &one, sizeof (one));
00329     status = AudioUnitSetProperty (rioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format));
00330 
00331     if (this->getNumInputs() > 0)
00332     {
00333         status = AudioUnitSetProperty (rioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof (one));
00334         status = AudioUnitSetProperty (rioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1, &format, sizeof (format));
00335     }
00336     
00337     status = AudioUnitSetProperty (rioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &inputProc, sizeof (inputProc));
00338     
00339         AudioUnitInitialize (rioUnit);
00340         
00341 //    NSLog(@"setupRemoteIO complete");
00342     
00343         return 0;       
00344 }
00345 
00346 template<class SampleType>
00347 void IOSAudioHostBase<SampleType>::killRemoteIO() throw()
00348 {
00349     if (rioUnit)
00350     {
00351         AudioOutputUnitStop (rioUnit);
00352         AudioComponentInstanceDispose (rioUnit);
00353         rioUnit = NULL;
00354     }
00355 }
00356 
00357 template<class SampleType>
00358 void IOSAudioHostBase<SampleType>::restart() throw()
00359 {                   
00360     resetIO();
00361     
00362     this->setPreferredHostSampleRate (hwSampleRate);
00363     this->setPreferredHostBlockSize (bufferSize);
00364         
00365     if (!rioUnit)
00366     {
00367         setFormat();
00368         setupRemoteIO();
00369         }
00370     
00371         AudioOutputUnitStart (rioUnit);
00372 }
00373 
00374 template<class SampleType>
00375 void IOSAudioHostBase<SampleType>::fixAudioRouteIfSetToReceiver() throw()
00376 {
00377         CFStringRef audioRoute = 0;
00378         UInt32 propertySize = sizeof (audioRoute);
00379     
00380         if (AudioSessionGetProperty (kAudioSessionProperty_AudioRoute, &propertySize, &audioRoute) == noErr)
00381         {
00382                 NSString* route = (PLANK_OBJC_BRIDGE NSString*)audioRoute;
00383         
00384                 if ([route hasPrefix: @"Receiver"])
00385                 {
00386                         UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
00387                         AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute, sizeof (audioRouteOverride), &audioRouteOverride);
00388                 }
00389                 
00390                 CFRelease (audioRoute);
00391         }
00392 }
00393 
00394 template<class SampleType>
00395 OSStatus IOSAudioHostBase<SampleType>::renderCallback (UInt32                     inNumberFrames,
00396                                                        AudioUnitRenderActionFlags *ioActionFlags, 
00397                                                        const AudioTimeStamp       *inTimeStamp, 
00398                                                        AudioBufferList            *ioData) throw()
00399 {
00400     OSStatus err = 0;
00401     int i;
00402     
00403         double renderTime = CFAbsoluteTimeGetCurrent();
00404         
00405         if (inNumberFrames > bufferSize)
00406         {
00407                 bufferSize = inNumberFrames;        
00408         convertBuffer.setSize (inNumberFrames * plonk::max (this->getNumInputs(), 
00409                                                             this->getNumOutputs()), 
00410                                false);
00411         }
00412         
00413     this->setPreferredHostBlockSize (inNumberFrames);
00414         
00415         SampleType *convertBufferData[2];
00416         convertBufferData[0] = convertBuffer.getArray();
00417         convertBufferData[1] = convertBufferData[0] + inNumberFrames;   
00418     
00419     ConstBufferArray& inputs = this->getInputs();
00420     BufferArray& outputs = this->getOutputs();
00421     
00422         if (audioInputIsAvailable && (inputs.length() > 0))
00423         {
00424                 err = AudioUnitRender (rioUnit, ioActionFlags, inTimeStamp, 1, inNumberFrames, ioData);
00425         
00426         if (err == noErr)
00427             audioShortToTypeChannels (ioData, convertBufferData, inNumberFrames, numInputChannels);                             
00428         }
00429         else convertBuffer.clear();  
00430             
00431     for (i = 0; i < inputs.length(); ++i)
00432         inputs.atUnchecked (i) = convertBufferData[i];
00433     
00434     for (i = 0; i < outputs.length(); ++i)
00435         outputs.atUnchecked (i) = convertBufferData[i];
00436 
00437     if (customPreRender)
00438         customPreRender (customRenderCallbackRef, inNumberFrames, ioActionFlags, inTimeStamp, ioData);
00439     
00440     this->process();
00441         
00442         audioTypeToShortChannels (convertBufferData, ioData, inNumberFrames, ioData->mNumberBuffers);
00443     
00444     if (customPostRender)
00445         customPostRender (customRenderCallbackRef, inNumberFrames, ioActionFlags, inTimeStamp, ioData);
00446     
00447         renderTime = CFAbsoluteTimeGetCurrent() - renderTime;
00448         
00449         const double timeRatio = renderTime * reciprocalBufferDuration;
00450         cpuUsage += 0.2 * (timeRatio - cpuUsage); 
00451         
00452         return err;     
00453 }
00454 
00455 template<class SampleType>
00456 void IOSAudioHostBase<SampleType>::propertyCallback (AudioSessionPropertyID inID,
00457                                                      UInt32                 inDataSize,
00458                                                      const void *           inPropertyValue) throw()
00459 {
00460         
00461     if (inID == kAudioSessionProperty_AudioRouteChange)
00462     {
00463         if (inPropertyValue)
00464         {
00465             CFDictionaryRef routeChangeDictionary = (CFDictionaryRef)inPropertyValue;
00466             CFNumberRef routeChangeReasonRef = 
00467                 (CFNumberRef)CFDictionaryGetValue (routeChangeDictionary, CFSTR (kAudioSession_AudioRouteChangeKey_Reason));
00468             
00469             SInt32 routeChangeReason;
00470             CFNumberGetValue (routeChangeReasonRef, kCFNumberSInt32Type, &routeChangeReason);
00471             
00472             static const char* reasons[] = {
00473                 "Unknown",
00474                 "NewDeviceAvailable", "OldDeviceUnavailable", "CategoryChange", "Override",
00475                 "(Invalid)",
00476                 "WakeFromSleep", "NoSuitableRouteForCategory"
00477             };
00478             
00479             CFStringRef newAudioRoute;
00480             UInt32 propertySize = sizeof (CFStringRef);
00481             AudioSessionGetProperty (kAudioSessionProperty_AudioRoute, &propertySize, &newAudioRoute);
00482             NSLog (@"route: %s reason = '%s' (%d)\n",
00483                    CFStringGetCStringPtr (newAudioRoute,CFStringGetSystemEncoding()),
00484                    reasons[routeChangeReason], (int)routeChangeReason);
00485             CFRelease(newAudioRoute);
00486             
00487             this->fixAudioRouteIfSetToReceiver();
00488         }
00489     }
00490     else if (inID == kAudioSessionProperty_AudioCategory)
00491     {
00492         resetIO();
00493     }
00494 }
00495 
00496 template<class SampleType>
00497 void IOSAudioHostBase<SampleType>::resetIO() throw()
00498 {
00499     OSStatus err;
00500     UInt32 size;
00501     
00502     size = sizeof (hwSampleRate);
00503     AudioSessionGetProperty (kAudioSessionProperty_CurrentHardwareSampleRate, &size, &hwSampleRate);
00504     
00505     if (this->getPreferredHostBlockSize() > 0)
00506     {
00507         bufferDuration = (double (this->getPreferredHostBlockSize()) + 0.5) / hwSampleRate;
00508         AudioSessionSetProperty (kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof (bufferDuration), &bufferDuration);
00509     }
00510 
00511     size = sizeof(UInt32);
00512     err = AudioSessionGetProperty (kAudioSessionProperty_CurrentHardwareInputNumberChannels, &size, &numInputChannels);
00513     
00514     if (err != noErr)
00515         numInputChannels = 0;
00516     
00517     err = AudioSessionGetProperty (kAudioSessionProperty_CurrentHardwareOutputNumberChannels, &size, &numOutputChannels);
00518     err = AudioSessionGetProperty (kAudioSessionProperty_AudioInputAvailable, &size, &audioInputIsAvailable);
00519     
00520     if (audioInputIsAvailable && this->getNumInputs() > 0)
00521         this->setNumInputs (numInputChannels);
00522         
00523     this->setNumOutputs (numOutputChannels);
00524     
00525     size = sizeof (bufferDuration);
00526     AudioSessionGetProperty (kAudioSessionProperty_CurrentHardwareIOBufferDuration, &size, &bufferDuration);
00527     
00528     reciprocalBufferDuration = 1.0 / double (bufferDuration);
00529     
00530     bufferSize = (int)(hwSampleRate * bufferDuration + 0.5);
00531     convertBuffer.setSize (bufferSize * plonk::max (this->getNumOutputs(),
00532                                                     this->getNumInputs()),
00533                            false);        
00534 }
00535 
00536 template<class SampleType>
00537 void IOSAudioHostBase<SampleType>::interruptionCallback (UInt32 inInterruption) throw()
00538 {
00539     NSLog (@"...Interruption...");
00540     
00541     if (inInterruption == kAudioSessionBeginInterruption)
00542     {
00543         interrupted = true;
00544         this->hostInterruption (true);
00545         this->stopHost();
00546         
00547         NSLog (@"Interruption %s", inInterruption ? "begin" : "end");
00548     }
00549     else if (inInterruption == kAudioSessionEndInterruption)
00550     {
00551         this->hostInterruption (false);
00552         this->startHost();
00553         interrupted = false;
00554 
00555         NSLog (@"Interruption %s", inInterruption ? "begin" : "end");
00556         }
00557 }
00558 
00559 template<class SampleType>
00560 void IOSAudioHostBase<SampleType>::setCustomRenderCallbacks (void* refCon,
00561                                                              RenderCallbackFunction preRender,
00562                                                              RenderCallbackFunction postRender) throw()
00563 {
00564     customRenderCallbackRef = refCon;
00565     customPreRender = preRender;
00566     customPostRender = postRender;
00567 }
00568 
00569 //#ifdef __llvm__
00570 // #ifdef __clang__
00571 //  #pragma clang diagnostic pop
00572 // #endif
00573 //#endif
00574 
00575 #pragma GCC diagnostic pop
00576 
00577 
00578 END_PLONK_NAMESPACE
00579 
00580 #endif
00581 
00582 #undef kOutputBus
00583 #undef kInputBus
 All Classes Functions Typedefs Enumerations Enumerator Properties