タコさんブログ

プログラミングメモと小言

<CoreAudioTypes/CoreAudioBaseTypes.h>, <AudioToolbox/AudioFile.h> メモ

<CoreAudioTypes/CoreAudioBaseTypes.h>, <AudioToolbox/AudioFile.h> 内に記載されている説明のコピぺー。

AudioStreamBasicDescription

This structure encapsulates all the information for describing the basic format properties of a stream of audio data.

This structure is sufficient to describe any constant bit rate format that has channels that are the same size. Extensions are required for variable bit rate data and for constant bit rate data where the channels have unequal sizes. However, where applicable, the appropriate fields will be filled out correctly for these kinds of formats (the extra data is provided via separate properties). In all fields, a value of 0 indicates that the field is either unknown, not applicable or otherwise is inapproprate for the format and should be ignored. Note that 0 is still a valid value for most formats in the mFormatFlags field.

In audio data a frame is one sample across all channels. In non-interleaved audio, the per frame fields identify one channel. In interleaved audio, the per frame fields identify the set of n channels. In uncompressed audio, a Packet is one frame, (mFramesPerPacket == 1). In compressed audio, a Packet is an indivisible chunk of compressed data, for example an AAC packet will contain 1024 sample frames.

struct AudioStreamBasicDescription
{
    Float64             mSampleRate;
    AudioFormatID       mFormatID;
    AudioFormatFlags    mFormatFlags;
    UInt32              mBytesPerPacket;
    UInt32              mFramesPerPacket;
    UInt32              mBytesPerFrame;
    UInt32              mChannelsPerFrame;
    UInt32              mBitsPerChannel;
    UInt32              mReserved;
}
var Description
mSampleRate The number of sample frames per second of the data in the stream.
mFormatID The AudioFormatID indicating the general kind of data in the stream.
mFormatFlags The AudioFormatFlags for the format indicated by mFormatID.
mBytesPerPacket The number of bytes in a packet of data.
mFramesPerPacket The number of sample frames in each packet of data.
mBytesPerFrame The number of bytes in a single sample frame of data.
mChannelsPerFrame The number of channels in each frame of data.
mBitsPerChannel The number of bits of sample data for each channel in a frame of data.
mReserved Pads the structure out to force an even 8 byte alignment.

Audio File Properties

Constants for AudioFile get/set property calls.

AudioFilePropertyID Description
kAudioFilePropertyFileFormat An AudioFileTypeID that identifies the format of the file
kAudioFilePropertyDataFormat An AudioStreamBasicDescription describing the format of the audio data
kAudioFilePropertyFormatList In order to support formats such as AAC SBR where an encoded data stream can be decoded to multiple destination formats, this property returns an array of AudioFormatListItems (see AudioFormat.h) of those formats.
The default behavior is to return an AudioFormatListItem that has the same AudioStreamBasicDescription that kAudioFilePropertyDataFormat returns.
kAudioFilePropertyIsOptimized A UInt32 indicating whether an Audio File has been optimized.
Optimized means it is ready to start having sound data written to it.
A value of 0 indicates the file needs to be optimized.
A value of 1 indicates the file is currently optimized.
kAudioFilePropertyMagicCookieData A void * pointing to memory set up by the caller.
Some file types require that a magic cookie be provided before packets can be written to the file, so this property should be set before calling AudioFileWriteBytes()/AudioFileWritePackets() if a magic cookie exists.
kAudioFilePropertyAudioDataByteCount a UInt64 that indicates the number of bytes of audio data contained in the file
kAudioFilePropertyAudioDataPacketCount a UInt64 that indicates the number of packets of audio data contained in the file
kAudioFilePropertyMaximumPacketSize a UInt32 that indicates the maximum size of a packet for the data contained in the file
kAudioFilePropertyDataOffset a SInt64 that indicates the byte offset in the file of the audio data.
kAudioFilePropertyChannelLayout An AudioChannelLayout struct.
kAudioFilePropertyDeferSizeUpdates A UInt32.
If 1, then updating the files sizes in the header is not done for every write, but deferred until the file is read, optimized or closed. This is more efficient, but less safe since, if the application crashes before the size is updated, the file may not be readable. The default value is one, it doesn't update the header.
kAudioFilePropertyDataFormatName This is deprecated
Use kAudioFormatProperty_FormatName in AudioFormat.h instead.
kAudioFilePropertyMarkerList access the list of markers defined in the file.
returns an AudioFileMarkerList.
The CFStringRefs in the returned structs must be released by the client.
available in 10.2.4 and later
kAudioFilePropertyRegionList access the list of regions defined in the file.
returns an Array of AudioFileRegions.
The CFStringRefs in the returned structs must be released by the client.
available in 10.2.4 and later
kAudioFilePropertyPacketToFrame pass a AudioFramePacketTranslation with mPacket filled out and get mFrame back. mFrameOffsetInPacket is ignored.
kAudioFilePropertyFrameToPacket pass a AudioFramePacketTranslation with mFrame filled out and get mPacket and mFrameOffsetInPacket back.
kAudioFilePropertyRestrictsRandomAccess A UInt32 indicating whether an Audio File contains packets that cannot be used as random access points.
A value of 0 indicates that any packet can be used as a random access point, i.e. that a decoder can start decoding with any packet.
A value of 1 indicates that some packets cannot be used as random access points, i.e. that kAudioFilePropertyPacketToRollDistance must be employed in order to identify an appropriate initial packet for decoding.
kAudioFilePropertyPacketToRollDistance Pass an AudioPacketRollDistanceTranslation with mPacket filled out and get mRollDistance back.
The roll distance indicates the count of packets that must be decoded prior to the packet with the specified number in order to achieve the best practice for the decoding of that packet.
For file types for which a minimal roll distance is prohibitively expensive to determine per packet, the value returned may be derived from an upper bound for all packet roll distances.
If the value of kAudioFilePropertyRestrictsRandomAccess is 1, either kAudioFilePropertyPacketToRollDistance or kAudioFilePropertyPacketToDependencyInfo must be used in order to identify an appropriate random access point. If the value of kAudioFilePropertyRestrictsRandomAccess is 0, kAudioFilePropertyPacketToRollDistance can be used in order to identify the best available random access point, which may be prior to the specified packet even if the specified packet can be used as a random access point.
kAudioFilePropertyPreviousIndependentPacket
kAudioFilePropertyNextIndependentPacket
Pass an AudioIndependentPacketTranslation with mPacket filled out and get mIndependentlyDecodablePacket back.
A value of -1 means that no independent packet is present in the stream in the direction of interest. Otherwise, for kAudioFilePropertyPreviousIndependentPacket, mIndependentlyDecodablePacket will be less than mPacket, and for kAudioFilePropertyNextIndependentPacket, mIndependentlyDecodablePacket will be greater than mPacket.
kAudioFilePropertyPacketToDependencyInfo Pass an AudioPacketDependencyInfoTranslation with mPacket filled out and get mIsIndependentlyDecodable and mPrerollPacketCount back.
A value of 0 for mIsIndependentlyDecodable indicates that the specified packet is not independently decodable.
A value of 1 for mIsIndependentlyDecodable indicates that the specified packet is independently decodable.
For independently decodable packets, mPrerollPacketCount indicates the count of packets that must be decoded after the packet with the specified number in order to refresh the decoder.
If the value of kAudioFilePropertyRestrictsRandomAccess is 1, either kAudioFilePropertyPacketToRollDistance or kAudioFilePropertyPacketToDependencyInfo must be used in order to identify an appropriate random access point.
kAudioFilePropertyPacketToByte pass an AudioBytePacketTranslation struct with mPacket filled out and get mByte back.
mByteOffsetInPacket is ignored. If the mByte value is an estimate then kBytePacketTranslationFlag_IsEstimate will be set in the mFlags field.
kAudioFilePropertyByteToPacket pass an AudioBytePacketTranslation struct with mByte filled out and get mPacket and mByteOffsetInPacket back. If the mPacket value is an estimate then kBytePacketTranslationFlag_IsEstimate will be set in the mFlags field.
kAudioFilePropertyChunkIDs returns an array of OSType four char codes for each kind of chunk in the file.
kAudioFilePropertyInfoDictionary returns a CFDictionary filled with information about the data contained in the file.
See dictionary key constants already defined for info string types.
AudioFileComponents are free to add keys to the dictionaries that they return for this property... caller is responsible for releasing the CFObject
kAudioFilePropertyPacketTableInfo Gets or sets an AudioFilePacketTableInfo struct for the file types that support it.
When setting, the sum of mNumberValidFrames, mPrimingFrames and mRemainderFrames must be the same as the total number of frames in all packets. If not you will get a kAudio_ParamError. The best way to ensure this is to get the value of the property and make sure the sum of the three values you set has the same sum as the three values you got.
kAudioFilePropertyPacketSizeUpperBound a UInt32 for the theoretical maximum packet size in the file (without actually scanning the whole file to find the largest packet, as may happen with kAudioFilePropertyMaximumPacketSize).
kAudioFilePropertyPacketRangeByteCountUpperBound Pass an AudioPacketRangeByteCountTranslation with mPacket and mPacketCount filled out and get mByteCountUpperBound back. The value of mByteCountUpperBound can be used to allocate a buffer for use with AudioFileReadPacketData in order to accommodate the entire packet range.
May require scanning in order to obtain the requested information, but even if so, no scanning will occur beyond the last packet in the specified range.
For file formats in which packets are directly accessible and stored both contiguously and byte-aligned, the returned upper bound will be equal to the total size of the packets in the range. Otherwise the upper bound may reflect per-packet storage overhead.
kAudioFilePropertyReserveDuration The value is a Float64 of the duration in seconds of data that is expected to be written.
Setting this property before any data has been written reserves space in the file header for a packet table and/or other information so that it can appear before the audio data. Otherwise the packet table may get written at the end of the file, preventing the file from being streamable.
kAudioFilePropertyEstimatedDuration The value is a Float64 representing an estimated duration in seconds. If duration can be calculated without scanning the entire file, or all the audio data packets have been scanned, the value will accurately reflect the duration of the audio data.
kAudioFilePropertyBitRate Returns the bit rate for the audio data as a UInt32. For some formats this will be approximate.
kAudioFilePropertyID3Tag A void * pointing to memory set up by the caller to contain a fully formatted ID3 tag (get/set v2.2, v2.3, or v2.4, v1 get only).
The ID3 tag is not manipulated in anyway either for read or write. When setting, this property must be called before calling AudioFileWritePackets.
kAudioFilePropertySourceBitDepth For encoded data this property returns the bit depth of the source as an SInt32, if known.
The bit depth is expressed as a negative number if the source was floating point, e.g. -32 for float, -64 for double.
kAudioFilePropertyAlbumArtwork returns a CFDataRef filled with the Album Art or NULL.
The caller is responsible for releasing a non-NULL CFDataRef.
In order to parse the contents of the data, CGImageSourceCreateWithData may be used.
kAudioFilePropertyAudioTrackCount a UInt32 that indicates the number of audio tracks contained in the file. (get property only)
kAudioFilePropertyUseAudioTrack a UInt32 that indicates the number of audio tracks contained in the file. (set property only)

AudioFile error codes

These are the error codes returned from the AudioFile API.

Error Code Description Four char code OSStatus Code Remarks
kAudioFileUnspecifiedError An unspecified error has occurred. wht? 2003334207
kAudioFileUnsupportedFileTypeError The file type is not supported. typ? 1954115647
kAudioFileUnsupportedDataFormatError The data format is not supported by this file type. fmt? 1718449215
kAudioFileUnsupportedPropertyError The property is not supported. pty? 1886681407
kAudioFileBadPropertySizeError The size of the property data was not correct. !siz 561211770
kAudioFilePermissionsError The operation violated the file permissions. For example, trying to write to a file opened with kAudioFileReadPermission. prm? 1886547263
kAudioFileNotOptimizedError There are chunks following the audio data chunk that prevent extending the audio data chunk.
The file must be optimized in order to write more audio data.
optm 1869640813
kAudioFileInvalidChunkError The chunk does not exist in the file or is not supported by the file. chk? 1667787583
kAudioFileDoesNotAllow64BitDataSizeError The a file offset was too large for the file type. AIFF and WAVE have a 32 bit file size limit. off? 1868981823
kAudioFileInvalidPacketOffsetError A packet offset was past the end of the file, or not at the end of the file when writing a VBR format, or a corrupt packet size was read when building the packet table. pck? 1885563711
kAudioFileInvalidPacketDependencyError Either the packet dependency info that's necessary for the audio format has not been provided, or the provided packet dependency info indicates dependency on a packet that's unavailable. dep? 1684369471
kAudioFileInvalidFileError The file is malformed, or otherwise not a valid instance of an audio file of its type. dta? 1685348671
kAudioFileOperationNotSupportedError The operation cannot be performed. For example, setting kAudioFilePropertyAudioDataByteCount to increase the size of the audio data in a file is not a supported operation. Write the data instead. 1869627199 'op??', integer used because of trigraph
kAudioFileNotOpenError The file is closed. -38
kAudioFileEndOfFileError End of file. -39
kAudioFilePositionError Invalid file position. -40
kAudioFileFileNotFoundError File not found. -43

Appleのドキュメント

Xcode9 に XVim2 入れたどぉ〜!!

環境

インストール

  1. Signing Xcode (これを忘れるとXVimはロードされない)
    1. Xcode を閉じる
    2. 証明書の作成 (Apple Developer Programまたは他の証明書を使用する場合は 3~5 をスキップ)
    3. Keychain Access を起動
    4. 左ペインのログインを選択し、メニューのキーチェーンアクセス -> 証明書アシスタント -> 証明書を作成 を選択
    5. 名前欄に 適当な名前を入力(ex. XcodeSigner), 固有名のタイプ欄 自己署名ルート, 証明書のタイプ コード署名 を選択し、作成
    6. codesign
    7. $ sudo codesign -f -s XcodeSigner /Applications/Xcode.app
      実行に少し時間がかかる.
      Apple Developer Programの証明書を使用する場合は, XcodeSigner を "iPhone Developer: ... " 等にすれば良いはず.
  2. ソースをダウンロード
  3. $ git clone https://github.com/XVimProject/XVim2.git
  4. xcode-select で Xcodeパスの確認
  5. $ xcode-select -p
    /Applications/Xcode.app/Contents/Developer // が出力されるはず
  6. make
  7. XVim2をクローンしたディレクトリに cd して
    $ make
  8. お好みで .xvimrc を作成
  9. Xcodeを起動
  10. XVimをロードするか聞かれるので Load Bundle を選択. f:id:tiny_wing:20171016194150p:plain 誤って Skip Bundle を選択してしまった場合は、ターミナルで下のコマンドを実行.
    $ defaults delete com.apple.dt.Xcode DVTPlugInManagerNonApplePlugIns-Xcode-X.X
    X.XにはXcodeのバージョンを指定(i.e. 9.0).
  11. Xcodeを再起動

参照URL

Swiftでちょっとだけモナド

Monads for functional programming - Philip Wadler セクション2のメモ。セクション2では、シンプルな評価器を構成することにより、モナドを導入し、修正に強い評価器を構成するのにモナドが使用できることが説明されている。

環境

準備

  • forget inpure world

バリエーション0:基本的な評価器

項を定数 con(Int) か 項の商 div(Term, Term) で定義する。

enum Term {
    case con(Int)
    indirect case div(Term, Term)
}

ログのために CustomStringConvertible を準拠させる。

extension Term: CustomStringConvertible {
    var description: String {
        switch self {
        case let .con(a):
            return "con(\(a))"
        case let .div(t, u):
            return "div(\(t), \(u))"
        }
    }
}

評価器(eval関数)は項(Term)に作用する。
基本的な評価機(eval関数)は至って簡単。
(話を分かりやすくするために、簡単にしてある。)

func eval(_ term: Term) -> Int {
    switch term {
    case let .con(a):
        return a
    case let .div(t, u):
        return eval(t) / eval(u)
    }
}

以下の項 answererror は他の例でも用いる。

// (1972 / 2) / 23
let answer: Term = .div(.div(.con(1972), .con(2)), .con(23))
// 1 / 0
let error: Term = .div(.con(1), .con(0))

eval(answer) の評価は 42 になる。この評価器には例外処理は組み込まれていないので、eval(error)の評価はexecution errorになる。

eval(answer)  // 出力:42
eval(error)   // error

バリエーション1:例外

上記の例 eval(error) でエラーメッセージを返すようにエラー判定を加える。 例外処理は例外を投げる可能性がある計算を表す型を導入する。

enum M<A> {
    typealias `exception` = String
    case raise(`exception`)
    case ret(A)  // Haskellのreturn
}

型M<A> は、 .raise(e) の形式(eはexception)か、.ret(a) の形式(aは型Aの値)をした値をとる。

評価器をこの型に適合させるのは簡単だが、退屈である。

func eval(_ term: Term) -> M<Int> {
    switch term {
    case let .con(a):
        return .ret(a)
    case let .div(t, u):
        switch eval(t) {
        case let .raise(e):
            return .raise(e)
        case let .ret(a):
            switch eval(u) {
            case let .raise(e):
                return .raise(e)
            case let .ret(b):
                if b == 0 {
                    return .raise("divided by zero")
                } else {
                    return .ret(a / b)
                }
            }
        }
    }
}

各評価器の呼び出し時に、結果の形式を確認する必要がある。
例外が投げられた場合は、再度例外が投げられ、値の場合は、処理される。

eval(answer) // 出力:ret(42)
eval(error)  // 出力:raise("divided by zero")

バリエーション2:状態(State)

評価中の除法(割り算)の回数をカウントすることを考える。

状態は状態に作用する計算を表す型を導入する。

typealias State = Int
typealias M<A> = (State) -> (A, State)

型M<A> の値は初期状態を取り、最終状態とペアになった計算の値を戻り値にする関数である。

再び、評価器をこの型に適合させるのは簡単だが、退屈である。

func eval(_ term: Term) -> M<Int> {
    switch term {
    case let .con(a):
        return { x in (a, x) }
    case let .div(t, u):
        return { x in
            let (a, y) = eval(t)(x)
            let (b, z) = eval(u)(y)
            return (a / b, z + 1)
        }
    }
}

eval(answer)(0) の評価は (42, 2) になる。よって、初期状態 0 の時、答えは 42 で、最終状態は、除法が2回行われたことを示す 2 である。

eval(answer)(0) // 出力:42 2

バリエーション3:出力

実行のトレースを出力することを考える。

出力は出力を生成する計算を表現する型を導入する。

typealias Output = String
typealias M<A> = (Output, A)

型M<A> の値は出力を生成したペアと計算結果の値で構成される。

又々、評価器をこの型に適合させるのは簡単だが、退屈である。

func eval(_ term: Term) -> M<Int> {
    switch term {
    case let .con(a):
        return (line(term)(a), a)
    case let .div(t, u):
        let (x, a) = eval(t)
        let (y, b) = eval(u)
        return (x + y + line(term)(a / b), a / b)
    }
}

func line(_ term: Term) -> (Int) -> Output {
    return { "eval \(term) <= \($0)\n" }
}

eval(answer) のoutputは計算のトレースを表し、出力は以下のようになる。

eval con(1972) <= 1972
eval con(2) <= 2
eval div(con(1972), con(2)) <= 986
eval con(23) <= 23
eval div(div(con(1972), con(2)), con(23)) <= 42

Monadic評価器

各バリエーションで、計算の種類を見た。それぞれMは、例外を発生させ、状態に作用し、出力を生成する計算を表した。最初の評価器はTerm -> Intの型を持っていたが、各バリエーションでは、Term -> M<Int>の型になった。一般に、A -> Bの関数の型からA -> M<B>の関数の型に替わった。これは、型Aを引数に取り、型Bの結果とMによって得られた付加効果を返す関数と解釈できる。

それぞれの例から分かるように、型Mに必要なオペレーションは以下の2つ:

func unit(_ a: A) -> M<A>
func *<A, B>(x: M<A>, f:(A) -> M<B>) -> M<B>

型構成子Mと2つのオペレーションで構成する3つの組み(M, unit, *)モナドと言う。これらのオペレーションはセクション3で説明されている3つの法則を満たさなければならない。

これらの抽象化の観点から、評価器は以下のように書き換えられる:

func eval(_ term: Term) -> M<Int> {
    switch term {
    case let .con(a):
        return unit(a)
    case let .div(t, u):
        return eval(t) * { a in eval(u) * { b in
                unit(a / b)
            }
        }
    }
}

この評価器は最初のものよりは複雑だが、柔軟である。各バリエーションで見た変更は、M, unit, * の定義と局所的な変更をするだけで良い。評価器全体を書き換える必要がない。

バリエーション0再考:基本的な評価器

最も単純なモナドでは、計算は値と何ら変わりはない。

typealias M<A> = A

func unit<A>(_ a: A) -> M<A> {
    return a
}

func *<A, B>(a: A, f: ((A) -> M<B>)) -> M<B> {
    return f(a)
}

これを恒等モナド(Identity Monad)という。

バリエーション1再考:例外

例外モナドでは、計算は例外を発生させるか、値を返す。

enum M<A> {
    typealias `exception` = String
    case raise(`exception`)
    case ret(A)
}

func unit<A>(_ a: A) -> M<A> {
    return .ret(a)
}

func *<A, B>(a: M<A>, k:(A) -> M<B>) -> M<B> {
    switch a {
    case let .ret(a):
        return k(a)
    case let .raise(e):
        return .raise(e)
    }
}

func raise<A>(_ e: M<A>.exception) -> M<A> {
    return .raise(e)
}

unit(a) は単純に値aを返す。
m * k はmが例外の場合、再度例外を発生させ、その他の場合、関数kを戻された値に適応する。
最後に関数raiseは例外を発生させる。

例外処理をmonadic評価器に加えるには、上記のモナドと、 unit(a/b) を以下のように変更すれば良い。

if b == 0 { return raise("divide by zero") } 
else { return unit(a / b) }

評価器は以下のようになる:

func eval(_ term: Term) -> M<Int> {
    switch term {
    case let .con(a):
        return unit(a)
    case let .div(t, u):
        return eval(t) * { a in eval(u) * { b in
                if b == 0 { return raise("divided by zero")}
                else { return unit(a / b) }
            }
        }
    }
}

バリエーション2再考:状態

状態モナドでは、計算は初期状態を取り、値とペアになる最終状態を返す。

typealias State = Int
typealias M<A> = (State) -> (A, State)

func unit<A>(_ a: A) -> M<A> {
    return { x in (a, x) }
}

func *<A, B>(m: @escaping M<A>, k: @escaping ((A) -> M<B>)) -> M<B> {
    return { x in
        let (a, y) = m(x)
        let (b, z) = k(a)(y)
        return (b, z)
    }
}

let tick: M<()> = { x in ((), x + 1) }

unit(a) は初期状態xと値aと最終状態xをとる計算を返す。つまり、unit(a)はaをとり状態を変更させない。
m * k は初期状態xの計算mを実行し、値aと中間状態yを引き起こし、状態yの計算k(a)を実行し、値bと最終状態zを返す。
tick は状態を増加させ、空の値()を返す。

monadic評価器に実行回数をカウントする機能を追加するには、上記のモナドとunit(a/b) を以下に変更すれば良い。

 tick * { unit(a / b) }

評価器は以下のようになる:

func eval(_ term: Term) -> M<Int> {
    switch term {
    case let .con(a):
        return unit(a)
    case let .div(t, u):
        return eval(t) * { a in eval(u) * { b in
                tick * { unit(a / b) }
            }
        }
    }
}

バリエーション3再考:出力

出力モナドでは、計算は生成された出力とペアになる値を返す。

typealias Output = String
typealias M<A> = (Output, A)

func unit<A>(_ a: A) -> M<A> {
    return ("", a)
}

func *<A, B>(m: M<A>, k:(A) -> M<B>) -> M<B> {
    let (x, a) = m
    let (y, b) = k(a)
    return (x + y, b)
}

func out(_ x: Output) -> M<()> {
    return (x, ())
}

unit(a) は出力なしとペアになるaを返す。 m * k は出力xと計算mの値aを抽出し、出力yと計算k(a)の値bを抽出し、xとyの結合からなる出力とペアになる値bを返す。 out(x) は出力xと空の値()の計算を返す。

実行のトレースをmonadic評価器に適応すると以下のようになる。

func eval(_ term: Term) -> M<Int> {
    switch term {
    case let .con(a):
        let output = out(line(term)(a))
        return output * { unit(a) }
    case let .div(t, u):
        return eval(t) * { a in eval(u) * { b in
                let output = out(line(.div(t, u))(a / b))
                return output * { unit(a / b) }
            }
        }
    }
}

参考URL

Swift AVAudioEngine でマイク入力を録音

AVAudioEngine を使用して、マイクからの入力を録音する。
AVAudioEngineの基本的な使い方は Swift AVAudioEngine の基本 - タコさんブログ を参照。
簡単のため、マイクアクセスの許可、オーディオセッションの管理などのハンドリングは考慮していない。

環境

準備

AVFoundation をインポートする。

import AVFoundation

録音

オーディオフォーマットは以下:

  • オーディオファイルフォーマット: PCM
  • サンプリング周波数: 44.1 kHz
  • ビットレート: 32 bit
  • チャンネル数: 1
  • Interleaved: true

※ Non-interleaved を指定すると、Audio files cannot be non-interleaved とエラーがコンソールに表示され、自動的に Interleaved に変更されているので注意

AVAudioEngineは 暗黙の入力ノード(inputNode) を持ち、この入力ノードはマイク(入力ハードウェア)からオーディオデータを受け取る。inputNodeの出力を別のノードに接続するか、タップをインストールすれば、inputNodeから入力を受け取ることができる。

録音するには AVAudioNode.installTap(onBus:bufferSize:format:block:) を使用してノードからの出力を監視し、バッファを逐次保存する。

// オーディオエンジンをインスタンス変数として宣言
let engine = AVAudioEngine()

func record() {
  do {
    // 保存する場所: 今回はDocumentディレクトリにファイル名"sample.caf"で保存
    let documentDir = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first!
    let filePath = NSURL(fileURLWithPath: documentDir + "/sample.caf")
    // オーディオフォーマット
    let format = AVAudioFormat(commonFormat: .PCMFormatFloat32  , sampleRate: 44100, channels: 1 , interleaved: true)
    // オーディオファイル
    let audioFile = try AVAudioFile(forWriting: filePath, settings: format.settings)
    // inputNodeの出力バス(インデックス0)にタップをインストール
    // installTapOnBusの引数formatにnilを指定するとタップをインストールしたノードの出力バスのフォーマットを使用する
    // (この例だとフォーマットに inputNode.outputFormatForBus(0) を指定するのと同じ)
    // tapBlockはメインスレッドで実行されるとは限らないので注意
    let inputNode = engine.inputNode!  // 端末にマイクがあると仮定する
    inputNode.installTapOnBus(0, bufferSize: 4096, format: nil) { (buffer, when) in
      do {
        // audioFileにバッファを書き込む
        try audioFile.writeFromBuffer(buffer)
      } catch let error {
        print("audioFile.writeFromBuffer error:", error)
      }
    }

    do {
      // エンジンを開始
      try engine.start()
    } catch let error {
      print("engine.start() error:", error)
    }
  } catch let error {
    print("AVAudioFile error:", error)
  }
}

エフェクトをかけて録音

ディレイ効果をかけて、エフェクトがかかった音を録音する。

func record() {
  do {
    // 保存するファイルパス
    let documentDir = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first!
    let filePath = NSURL(fileURLWithPath: documentDir + "/sample.caf")
    // オーディオフォーマット
    let format = AVAudioFormat(commonFormat: .PCMFormatFloat32  , sampleRate: 44100, channels: 1 , interleaved: true)
    // オーディオファイル
    let audioFile = try AVAudioFile(forWriting: filePath, settings: format.settings)
    // ディレイ
    let delay = AVAudioUnitDelay()
    delay.delayTime = 0.25
    // オーディオエンジンにディレイをアタッチ
    engine.attachNode(delay)
    // ノード同士を接続 (inputNode -> delay)
    let inputNode = engine.inputNode!
    engine.connect(inputNode, to: delay, format: format)
    // ディレイの出力バスにタップをインストール
    delay.installTapOnBus(0, bufferSize: 4096, format: format) { (buffer, when) in
      do {
        try audioFile.writeFromBuffer(buffer)
      } catch let error {
        print("audioFile.writeFromBuffer error:", error)
      }
    }

    do {
      // エンジンを開始
      try engine.start()
    } catch let error {
      print("engine.start error:", error)
    }
  } catch let error {
    print("AVAudioFile error:", error)
  }
}

上記の Audio Graph は以下のようになっている:

// --sound--> inputNode(mic) --audio source--> delay --audio source-->
//                                                   ^
//                                                   |
//                                        installTapOnBus(...) { 録音 }

タップの削除

AVAudioNode.removeTapOnBus() でインストールしたオーディオタップを削除できる。

例)

engine.inputNode!.removeTapOnBus(0)

Note

例えば、引数にフォーマットを指定するとき、間違ったフォーマットを指定しまっていると訳の分からない例外が投げられたりする。エフェクトをかけて録音の delay.installTapOnBus(0, bufferSize: 4096, format: format) { ... }delay.installTapOnBus(0, bufferSize: 4096, format: nil) { ... } とすると、以下のようなエラーがでて、クラッシュする。

ERROR: [0x1a0a06000] AVAudioEngineGraph.mm:2510: PerformCommand: error -10868
*** Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'error -10868'

Swift vDSP で 実FFT

C言語ではじめる音のプログラミング―サウンドエフェクトの信号処理 ページ33 で取り上げられているサイン波を vDSP(vectorized digital signal processing) ライブラリを使用して、FFTにより振幅スペクトルを求める(ここでは簡単のために窓関数を用いずにFFTを実行する)。

サイン波は次式のように表せられる。

x(n) = Amp * sin(2 * PI * f0 * n / fs), (0<=n<N) 

ここで、Ampは振幅、f0は基本周波数、fsは標本化周波数、nは標本化数

FFTを実行する音データとして、以下の値をとるサイン波を考える。

  • 振幅(Amp) 0.25
  • 基本周波数(f0) 250 Hz
  • 標本化周波数(fs) 8k Hz
  • 標本化数(n) 64

このとき、サイン波は次のようになる。

 x(n) = 0.25 * sin(PI * n / 16.0), (0 <= n < 64) (式1)

環境

準備

Accelerateフレームワークをインポート。

import Accelerate

サンプルデータの用意

振幅(Amp)を 0.25、 基本周波数(f0)を 250 Hz、 標本化周波数(fs)を 8k Hz、 標本化数を 64 としたときのサイン波(式1)のデータの配列 samples を用意する。

// 標本化数
let numSamples = 64
// サンプルデータ
var samples = [Float](count: numSamples, repeatedValue: 0)

for n in 0..<numSamples {
    samples[n] = 0.25 * sinf(Float(M_PI) * Float(n) / 16.0)
}

サンプルデータをDSPSplitComplex型に変換する

まず、vDSPで実FFTを実行する前に、vDSP_ctoz関数を使用してDSPSplitComplex型にパック(変換)する必要がある。 vDS_ctozを 実数データの配列 A = {A[0], A[1], … A[n]} に適用すると even-odd 配列 AEvenOdd = {A[0], A[2], …, A[n-1], A[1], A[3], … A[n]} のように変換される。

var reals = [Float](count: numSamples/2, repeatedValue: 0)
var imgs  = [Float](count: numSamples/2, repeatedValue: 0)
var splitComplex = DSPSplitComplex(realp: &reals, imagp: &imgs)
let samplesPtr = UnsafePointer<DSPComplex>(samples)
vDSP_ctoz(samplesPtr, 2, &splitComplex, 1, vDSP_Length(numSamples/2))

FFT ウェイト配列(FFT Weights Arrays)

FFTのウェイト配列は vDSP_create_fftsetup 関数を使用することによって生成する。 vDSP_create_fftsetupの第1引数 __Log2n には、底を2とする標本化数の対数(i.e. log2(number_of_samples))以上を指定する。第2引数 __Radix には、基数オプションを指定する。基数オプションには、ビット演算ORでFFT_RADIX2、FFT_RADIX3、FFT_RADIX5の任意の組み合わせが使用できる。

//  Create FFT setup
// __Log2nは log2(64) = 6 より、6 を指定
let setup = vDSP_create_fftsetup(6, FFTRadix(FFT_RADIX2))

FFTの実行・出力

vDSP_fft_zrip 関数を使用してFFTを実行する。

// Perform FFT
vDSP_fft_zrip(setup, &splitComplex, 1, 6, FFTDirection(FFT_FORWARD))

vDSP Programming Guide に記載されている通り、C_imp = 2 * C_math となっている。C_math を求めるには 1/2倍する必要がある。vDSP_vsmul 関数を使用して各配列要素の値を1/2倍する。また、splitComplex.realp[0], splitComplex.imagp[0] には DC成分ナイキスト成分 がそれぞれ入っている。音データの場合は無視できるので、インデックスは1から出力する。

// splitComplex.realp, splitComplex.imagpの各要素を1/2倍する
var scale:Float = 1 / 2
vDSP_vsmul(splitComplex.realp, 1, &scale, splitComplex.realp, 1, vDSP_Length(numSamples/2))
vDSP_vsmul(splitComplex.imagp, 1, &scale, splitComplex.imagp, 1, vDSP_Length(numSamples/2))
// 複素数の実部と虚部を取得する
let r = Array(UnsafeBufferPointer(start: splitComplex.realp, count: numSamples/2))
let i = Array(UnsafeBufferPointer(start: splitComplex.imagp, count: numSamples/2))
for n in 1..<numSamples/2 {
  let rel = r[n]
  let img = i[n]
  let mag = sqrtf(rel * rel + img * img)
  let log = "[%02d]: Mag: %5.2f, Rel: %5.2f, Img: %5.2f"
  print(String(format: log, n, mag, rel, img))
}

// setupを解放
vDSP_destroy_fftsetup(setup)

出力結果は以下のようになる。

[01]: Mag:  0.00, Rel: -0.00, Img: -0.00
[02]: Mag:  8.00, Rel:  0.00, Img: -8.00
[03]: Mag:  0.00, Rel:  0.00, Img:  0.00
以下0が続く
 .
 .
 .

C言語ではじめる音のプログラミング―サウンドエフェクトの信号処理 に記載されている値が出力されていることがわかる。

参考URL

Swift AVAudioEngine でサイン波を鳴らす

AVAudioEngine を使用して、440Hzのサイン波(ラ音)をPCMバッファーに設定し、鳴らす。 AVAudioEngineの基本的な使い方は Swift AVAudioEngine の基本 - タコさんブログ を参照。

環境

準備

AVFoundation をインポートする

import AVFoundation

サイン波

周波数fのサイン波は以下の式で与えられる。

 x(n) = sin(2 * PI * f * n/fs)

ここで、
 n = 0, 1, 2, ...
 fs はサンプリング周波数

この式を使用すると、サンプリング周波数 44.1k Hz, 440 Hzのサイン波は以下のようになる。

for n in 0..<some_buffer_length {
  samples[n] = sinf(Float(2.0 * M_PI) * 440.0 * Float(n) / sampleRate)
}

PCMバッファーの生成と設定

2 ch, 44.1k Hz, 非インターリーブドのオーディオフォーマット(audioFormat)に対し、PCMバッファーを以下のように生成する。

let buffer = AVAudioPCMBuffer(PCMFormat: audioFormat, frameCapacity:UInt32(44100))
buffer.frameLength = UInt32(44100)

上記のバッファー変数(buffer)に対して、サイン波の値を設定する。

// オーディオフォーマットからオーディオのチャンネル数を取得
let channels = Int(audioFormat.channelCount)
for ch in (0..<channels) {
  let samples = buffer.floatChannelData[ch]
  for n in 0..<Int(buffer.frameLength) {
    samples[n] = sinf(Float(2.0 * M_PI) * 440.0 * Float(n) / sampleRate)
  }
}

このバッファーをAVAudioPlayerNode.scheduleBuffer()でスケジュールし、再生すればサイン波が鳴る。

AVAudioEngine, AVAudioPlayerNode をインスタンス変数として宣言する。

// エンジンの生成
let audioEngine = AVAudioEngine()
// ソースノードの生成
let player = AVAudioPlayerNode()

playSineWave関数でオーディオエンジン・バッファーを設定し、サイン波を再生する。

func playSineWave() {
  // プレイヤーノードからオーディオフォーマットを取得
  let audioFormat = player.outputFormatForBus(0)
  // サンプリング周波数: 44.1K Hz
  let sampleRate = Float(audioFormat.sampleRate)
  // 3秒間鳴らすフレームの長さ
  let length = 3.0 * sampleRate
  // PCMバッファーを生成
  let buffer = AVAudioPCMBuffer(PCMFormat: audioFormat, frameCapacity:UInt32(length))
  // frameLength を設定することで mDataByteSize が更新される
  buffer.frameLength = UInt32(length)
  // オーディオのチャンネル数
  let channels = Int(audioFormat.channelCount)
  for ch in (0..<channels) {
    let samples = buffer.floatChannelData[ch]
    for n in 0..<Int(buffer.frameLength) {
      samples[n] = sinf(Float(2.0 * M_PI) * 440.0 * Float(n) / sampleRate)
    }
  }
      
  // オーディオエンジンにプレイヤーをアタッチ
  audioEngine.attachNode(player)
  let mixer = audioEngine.mainMixerNode
  // プレイヤーノードとミキサーノードを接続
  audioEngine.connect(player, to: mixer, format: audioFormat)
  // 再生の開始を設定
  player.scheduleBuffer(buffer) {
      print("Play completed")
  }
    
  do {
    // エンジンを開始
    try audioEngine.start()
    // 再生
    player.play()
  } catch let error {
    print(error)
  }
}

参考URL

Swift AVAudioEngine の基本

環境

準備

AVFoundation をインポートする。

import AVFoundation

AVAudioEngine

AVAudioEngine は 接続された audio node(AVAudioNode) のグループを定義する。 Audio node を使用してオーディオシグナルを生成、それらの処理、オーディオ入力・出力を実行する。

Audioエンジンを使用する方法は、 Audio node をそれぞれ生成し、audioエンジンへアタッチする。

エンジンの利用手順

  1. エンジンの生成
  2. ノードの生成
  3. エンジンにノードをアタッチ
  4. ノード同士を接続
  5. エンジンをスタート

AVAudioNode

AVAudioNodeはオーディオの生成・処理・I/Oブロックの抽象クラス。 ノードは入力・出力バス(接続ポイントのようなもの)を持っている。ミキサーは複数の入力バスがあり、1つの出力バスがある。 全てのバスにはバスと関連してフォーマットがあり、AVAudioMixerNode と AVAudioOutputNode 以外はノードどうしを接続するときフォーマットが同じである必要がある。

ノードの種類

  • Source (ex. Player, microphone)
  • Process (ex. Mixer, effect)
  • Destination (ex. Speaker)

Source ノードと Destination ノードが接続されている状態をActive Chainと言う。Active Chainでない状態をInactive Chainと言う。

Active Chain 例)

 Source Node (Player) - connect -> Destination Node
 Source Node (Player) - connect -> Processing Node - connect -> Destination Node

出力ノード (Output Node)

エンジンには暗黙に出力ノード(Output node)と呼ばれる Desitination Node があり、出力ノードはオーディオデータを出力ハードウェアに提供する。

ミキサーノード (Mixer Node)

ミキサーノードはN個の入力をミックスし、1つの出力へ出力する。 エンジンには暗黙的にミキサーノードがあり、追加のミキサーノードを生成し、エンジンにアタッチすることができる。ミキサーのインプットには異なるオーディオフォーマットを使用することができる。

 Input 1 -> | ---------------- |
 Input 2 -> |                  |
 Input 3 -> | AVAudioMixerNode |->
   .     -> |                  |
 Input N -> | ---------------- |

以下のようにSub-Mixingをすることも可能。

 Input 1 -> | ------------------ |
   .     -> |  AVAudioMixerNode  | -> | ---------------------------- |
 Input N -> | ------------------ |
                                      | AVAudioMixerNode (Main Mixer)|->
 Input 1 -> | ------------------ |
   .     -> |  AVAudioMixerNode  | -> | ---------------------------- |
 Input M -> | ------------------ |

プレイヤーノード(Player Node)

プレイヤーノード(AVPlayerNode)はファイル(AVAudioFile)またはバッファ(AVAudioBuffer)からデータをスケジュール(特定の時間に)し再生する。

エンジンとノードの生成・ノードのアタッチ

以下のようにして、エンジンとソースノード(Playerノード)を生成し、エンジンにノードをアタッチする。

// エンジンの生成
let engine = AVAudioEngine()
// ソースノードの生成
let player = AVAudioPlayerNode()
// エンジンにノードをアタッチ
engine.attachNode(player)

プレイヤーの設定(ファイル)

ファイルからプレイヤーをスケジュールする例。

do {
  // オーディオファイルの取得
  let audioFile = try AVAudioFile(forReading: url)
  // エンジンからメインミキサを取得
  let mainMixer = engine.mainMixerNode
  // プレイヤーノードとメインミキサーを接続
  engine.connect(player, to: mainMixer, format: audioFile.processingFormat)
  // プレイヤーをすぐに開始するようにスケジュール(atTimeをnil)
  player.scheduleFile(audioFile, atTime:nil, completionHandler:nil)
} catch let error {
  print(error)
}

プレイヤーの設定(バッファ)

バッファからプレイヤーをスケジュールする例。

// バッファを生成または取得
let buffer: AVAudioPCMBuffer = ...
// メインミキサの取得
let mainMixer = engine.mainMixerNode
// プレイヤーノードとメインミキサーを接続
engine.connect(player, to: mainMixer, format: buffer.format)
// プレイヤのスケジュール
player.scheduleBuffer(buffer, atTime:nil, completionHandler:nil)

エンジンの開始とプレイヤーの再生

do {
  // エンジンの開始
  try engine.start()
  // プレイヤーの再生
  player.start
} catch let error {
  print(error)
}

プレイヤーのスケジュールオプション

AVPlayer.scheduleFile(audioFile,atTime,options,completionHandler) のoptionsに設定する。

設定できる options

  • Loops (ループ再生)
  • Interrupts (再生中のオーディオをすぐに停止して、再生)
  • InterruptsAtLoop (ループ再生が終了してから再生)

ループ再生オプションの例。

player.scheduleFile(audioFile,
                    atTime:nil,
                    Loops,
                    completionHandler:nil)

再生の開始時間を設定する

10秒後に再生する例。

let tenSecInTheFuture = AVAudioTime.timeWithSampleTime(10 * buffer.format.sampleRate), atRate: buffer.format.sampleRate)
player.scheduleFile(audioFile, atTime:tenSecInTheFuture, completionHandler:nil)
player.play()

Node Tap

Node Tapを使用すればデータをプルすることができる。

Outputバス(インデックス0)にタップをインストールする例。

let format = mixier.outputFormatForBus(0)
mixier.installTapOnBuss(0,
                        bufferSize:4096,
                        format:format) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
  // Process buffer
}

ノードを非接続にする

入力ノードを非接続にする例。

engine.disconnectNodeOutput(input)

オーディオの再生例

// インスタンス変数
// エンジンの生成
let audioEngine = AVAudioEngine()
// Playerノードの生成
let player = AVAudioPlayerNode()

func play() {
  if let url = NSBundle.mainBundle().URLForResource("sound-file", withExtension: "wav") {
    do {
      // オーディオファイルの取得
      let audioFile = try AVAudioFile(forReading: url)
      // エンジンにノードをアタッチ
      audioEngine.attachNode(player)
      // メインミキサの取得
      let mixer = audioEngine.mainMixerNode
      // Playerノードとメインミキサーを接続
      audioEngine.connect(player,
                          to: mixer,
                          format: audioFile.processingFormat)
      // プレイヤのスケジュール
      player.scheduleFile(audioFile, atTime: nil) {
          print("complete")
      }
      // エンジンの開始
      try audioEngine.start()
      // オーディオの再生
      player.play()
    } catch let error {
      print(error)
    }
  } else {
    print("File not found")
  }
}

参考URL