variable size array

Hello,


How can I access variable size arrays in Swift 1.2?

For example


var layout:AudioChannelLayout = ...


for var i:UInt32 = 0; i < layout.mNumberChannelDescriptions; i++

{

if layout.mChannelDescriptions[i].mChannelLabel == kAudioChannelLabel_Left { /*some action*/ } // 'AudioChannelDescription' does not have a member named 'subscript'

}

Type of layout.mChannelDescriptions is (AudioChannelDescription)


TIA,


Jan E.


BTW it would be nice if the Analyzer's remarks could be copied

Replies

Arrays are collections just loop like this...


“let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
    print("Hello, \(name)!")
}”

Excerpt From: Apple Inc. “The Swift Programming Language (Swift 2 Prerelease).” iBooks. https://itun.es/us/k5SW7.l


If you need an index create a counter above the loop:


var index = 0


and within the loop do whatever you need to and increment the index like this...

++index
//do whatever you need with the index

Swift doesn't have fixed-length arrays.

Then what does this definition mean?


Declaration

SWIFT

struct AudioChannelLayout { var mChannelLayoutTag: AudioChannelLayoutTag var mChannelBitmap: UInt32 var mNumberChannelDescriptions: UInt32 var mChannelDescriptions: (AudioChannelDescription) }


How can I read the channel descriptions?


Jan E.

The parentheses around AudioChannelDescription denote that it is a tuple. That's how Swift handles C arrays.

It is odd that they defined it as a tuple, since the C version is an array. Anyway the tuple syntax would be layout.mChannelDescriptions.0 and you can't easily iterate tuples at runtime. The way it's defined the number of elements can't change at runtime anyway I think.


Edit: Perhaps someone who actually knows something about Swift, unlike me 😝, could comment on whether you're expected to use unsafe casts such as in this blog post at www.sitepoint.com/using-legacy-c-apis-swift/ when dealing with such C APIs. If so, wow that's ugly.

The struct is defined in C as:

struct AudioChannelLayout
{
    AudioChannelLayoutTag       mChannelLayoutTag;
    UInt32                      mChannelBitmap;
    UInt32                      mNumberChannelDescriptions;
    AudioChannelDescription     mChannelDescriptions[1];
};
typedef struct AudioChannelLayout AudioChannelLayout;

So, if you allocate a variable of this type statically in your code, you can use only one element for mChannelDescriptions.

        AudioChannelLayout layout;
        for( int i = 0; i < (int)layout.mNumberChannelDescriptions; ++i ) {
            //This code may crash your app when i being 1 or more.
            if( layout.mChannelDescriptions[i].mChannelLabel == kAudioChannelLabel_Left ) {
                /*some action*/
            }
        }

If you want to use more than one channels, you need to allocate it dynamically:

        int numChannels = 2;
        AudioChannelLayout *layoutPtr = malloc(sizeof(AudioChannelLayout) + (numChannels - 1) * sizeof(AudioChannelDescription));
        layoutPtr->mNumberChannelDescriptions = numChannels;
        for( int i = 0; i < numChannels; ++i ) {
            if( layoutPtr->mChannelDescriptions[i].mChannelLabel == kAudioChannelLabel_Left ) {
                /some action*/
            }
        }


The declaration of the Swift version of AudioChannelLayout is generated by the Swift's C code importer which maps fixed-sized array in a stuct to Swift tuple.

And you need to remember, Swift does not have a single-value tuple, so the declaration is exactly the same as:

struct AudioChannelLayout {
    var mChannelLayoutTag: AudioChannelLayoutTag
    var mChannelBitmap: UInt32
    var mNumberChannelDescriptions: UInt32
    var mChannelDescriptions: AudioChannelDescription
}

As you see, mChannelDescriptions is a single AudioChannelDescription, not array, not tuple.


So, you need to translate the `dynamically` C-code into Swift:

let numChannes = 2
let layoutPtr  = UnsafeMutablePointer<AudioChannelLayout>(UnsafeMutablePointer<CChar>.alloc(strideof(AudioChannelLayout) + (numChannes - 1) * strideof(AudioChannelDescription)))
layoutPtr.memory.mNumberChannelDescriptions = UInt32(numChannes)
for i in 0..<numChannes {
    //Calculate the address of the first mChannelDescriptions
    //Caution: this code depends on boundaries and alignments of types contained in AudioChannelLayout
    let descPtr = UnsafeMutablePointer<AudioChannelDescription>(UnsafeMutablePointer<CChar>(layoutPtr).advancedBy(sizeof(AudioChannelLayoutTag) + sizeof(UInt32) * 2))
    if descPtr[i].mChannelFlags == kAudioChannelLabel_Left {
        /*some action*/
    }
}

...

AudioBufferList has a similar definition, you can find some discussions about it in the dev forums including archived old forums.

I see.

As far as I can tell this isn't mentioned in the "Using Swift with Cocoa and Objective-C" document.

I searched "tuple" on the C page; indeed nothing came up. A documentation radar is in order. I can't see this implementation detail lasting into next year, however. I think it's reasknable to assume that Swift will work with C++ next year, and that compatibility will come with C compatibility improvements also.

Thanks.

This is beginning to look ugly!

So I can just write layoutPtr[0] for layoutPtr.memory ? Nice.

Did you mean strideof(UInt32) instead of sizeof(UInt32) in the Swift code?

This is beginning to look ugly!

As noted in another reply, C-interoperability of Swift has many rooms to be impoved...


So I can just write layoutPtr[0] for layoutPtr.memory ? Nice.

That should work. I tend to use .memory when the pointer points to a single object, and [0] for the pointers which may point to (possibly) more than 1 objects.

(When converting existing C code, *ptr to ptr.memory, ptr[0] to ptr[0], is my preferred way.)


Did you mean strideof(UInt32) instead of sizeof(UInt32) in the Swift code?

Sorry for my inconsistent use of sizeof and strideof. It is what I told in the old dev forums, that strideof returns the same values to C-sizeof, when applied to structs which may have trailing padding. But they both returns the same value when applied to a single scalar value with its size power to two.

So, I tend to use sizeof for scalars, and strideof for structs. But always using strideof might be safer when importing C codes.

Swift 4 is here and this stuff has changed a lot! I'm trying to understand your sample code at the bottom there, but it's almost unreadable with today's Swift as a reference. Any chance you could update it?

Try this on for size:

let channelCount = 2

// Calculate the size of the memory buffer required for the
// `AudioChannelLayout` value, including an `AudioChannelDescription` entry
// for each channel.

let size = MemoryLayout<AudioChannelLayout>.stride + (channelCount - 1) * MemoryLayout<AudioChannelDescription>.stride

// Allocate that buffer using `calloc`, then construct an
// `UnsafeMutablePointer<AudioChannelLayout>` value from that raw pointer.
// This is one of many techniques you can use for this.  I prefer this
// option because it more closely mirrors what you'd do in C.

let layoutPtr = calloc(1, size)!.assumingMemoryBound(to: AudioChannelLayout.self)

// Fill out properties in the fixed `AudioChannelLayout` header.

layoutPtr.pointee.mNumberChannelDescriptions = UInt32(channelCount)
// … and so on …

// Now create an `UnsafeMutableBufferPointer<AudioChannelDescription>` value
// that represents the array of channel descriptions at the end of the
// buffer.

withUnsafeMutablePointer(to: &layoutPtr.pointee.mChannelDescriptions) { start in
    let channelDescriptions = UnsafeMutableBufferPointer(start: start, count: channelCount)

    // Now work with that buffer of channel descriptions.

    for channelIndex in 0..<channelCount {
        channelDescriptions[channelIndex].mChannelLabel = kAudioChannelLabel_Left
        // … and so on …
    }
}

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Oh! Thanks for that! I'm actually trying to figure out how to get at the values of the variable length array (presented as a tuple in Swift) of AudioChannelDescription structs from an AudioChannelLayout. This is what I have so far:


var channelLayoutDescriptionString = ""
var outLayoutSize: Int = 0
if let channelLayout = CMAudioFormatDescriptionGetChannelLayout(audioFormatDescription, &outLayoutSize)?.pointee {
    if channelLayout.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions {
        var channelDescriptionsTuple = channelLayout.mChannelDescriptions
        let channelDescriptions = UnsafeBufferPointer<AudioChannelDescription>(start: &channelDescriptionsTuple, count: Int(channelLayout.mNumberChannelDescriptions))
        
        var count = 0
        for channelDescription in channelDescriptions {
            if count != 0 {
                channelLayoutDescriptionString += ", "
            }
            channelLayoutDescriptionString += "\(channelDescription.mChannelLabel.audioChannelLabelString)"
            count += 1
        }
    }
    else{
        channelLayoutDescriptionString = channelLayout.mChannelLayoutTag.audioChannelLayoutTagString
    }
}

I check to see if the channel layout should be specified by the channel descriptions (kAudioChannelLayoutTag_UseChannelDescriptions tag), and if so, get the tuple and create an UnsafeBufferPointer<AudioChannelDescription> with a start at the tuple and the count of channel descriptions specified in the channel layout struct. From there I try to iterate through the channel descriptions, but only get values for the first channel description...

Oof nevermind I just took another look at your example and just took the bottom part:

var channelLayoutDescriptionString = ""
var outLayoutSize: Int = 0
if var channelLayout = CMAudioFormatDescriptionGetChannelLayout(audioFormatDescription, &outLayoutSize)?.pointee {
    if channelLayout.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions {
        withUnsafeMutablePointer(to: &channelLayout.mChannelDescriptions) { start in
            let channelDescriptions = UnsafeMutableBufferPointer(start: start, count: Int(channelLayout.mNumberChannelDescriptions))
            
            for channelDescription in channelDescriptions {
                if count != 0 {
                    channelLayoutDescriptionString += ", "
                }
                channelLayoutDescriptionString += "\(channelDescription.mChannelLabel.audioChannelLabelString)"
                count += 1
            }
        }
    }
    else{
        channelLayoutDescriptionString = channelLayout.mChannelLayoutTag.audioChannelLayoutTagString
    }
}

That worked!