Skip to content

Commit 2062983

Browse files
committed
Refactored types under AudioFileConverter namespace
1 parent 411cadc commit 2062983

18 files changed

+334
-280
lines changed

Sources/SwiftAudioFileConverter/AudioFileConverter/AudioFileConverter+ExtAudioFile.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ extension AudioFileConverter {
1414
@concurrent nonisolated static func performExtAudioFileConversion(
1515
from inputURL: URL,
1616
to outputURL: URL,
17-
settings: AudioFileSettings
17+
settings: Settings
1818
) async throws {
1919
// 1. Open the input file
2020
var inputFile: ExtAudioFileRef?
2121
var result = ExtAudioFileOpenURL(inputURL as CFURL, &inputFile)
2222
try checkError(result)
2323
guard let inputFile = inputFile else {
24-
throw SwiftAudioFileConverterError.unableToOpenFile(inputURL)
24+
throw ConverterError.unableToOpenFile(inputURL)
2525
}
2626

2727
// 2. Prepare an AudioStreamBasicDescription for the *destination* format
@@ -42,15 +42,15 @@ extension AudioFileConverter {
4242

4343
try checkError(result)
4444
guard let outputFile = outputFile else {
45-
throw SwiftAudioFileConverterError.unableToOpenFile(outputURL)
45+
throw ConverterError.unableToOpenFile(outputURL)
4646
}
4747

4848
// 4. We’ll set a “client format” to read and write in a consistent format (e.g., float32 PCM).
4949
// Even for compressed output like AAC, we often use a PCM client format for reading/writing.
5050
let channels = (settings.channelFormat == .mono) ? UInt32(1) : UInt32(2)
5151

5252
var clientFormat = AudioStreamBasicDescription(
53-
mSampleRate: settings.sampleRate.rawValue,
53+
mSampleRate: settings.sampleRate.hzDouble,
5454
mFormatID: kAudioFormatLinearPCM,
5555
mFormatFlags: kLinearPCMFormatFlagIsFloat | kLinearPCMFormatFlagIsPacked,
5656
mBytesPerPacket: 4 * channels,
@@ -123,13 +123,13 @@ extension AudioFileConverter {
123123
/// Returns a tuple of (destination AudioStreamBasicDescription, destination AudioFileTypeID)
124124
/// based on the user’s desired FileFormat in `AudioFileSettings`.
125125
nonisolated private static func audioFormat(
126-
for settings: AudioFileSettings
126+
for settings: Settings
127127
) throws -> (AudioStreamBasicDescription, AudioFileTypeID) {
128128
let channels = (settings.channelFormat == .mono) ? UInt32(1) : UInt32(2)
129129

130130
// Base description; we’ll adjust for each format
131131
var asbd = AudioStreamBasicDescription()
132-
asbd.mSampleRate = settings.sampleRate.rawValue
132+
asbd.mSampleRate = settings.sampleRate.hzDouble
133133
asbd.mChannelsPerFrame = channels
134134

135135
switch settings.fileFormat {
@@ -169,7 +169,7 @@ extension AudioFileConverter {
169169

170170
case .mp3, .flac:
171171
// We should never get here, because we throw for unsupported
172-
throw SwiftAudioFileConverterError.unsupportedConversion(settings.fileFormat)
172+
throw ConverterError.unsupportedConversion(settings.fileFormat)
173173
}
174174
}
175175

@@ -202,7 +202,7 @@ extension AudioFileConverter {
202202
/// Throws a Swift error if the OSStatus indicates failure.
203203
nonisolated static func checkError(_ status: OSStatus) throws {
204204
guard status == noErr else {
205-
throw SwiftAudioFileConverterError.coreAudioError(CoreAudioError(status: status))
205+
throw ConverterError.coreAudioError(CoreAudioError(status: status))
206206
}
207207
}
208208
}

Sources/SwiftAudioFileConverter/AudioFileConverter/AudioFileConverter+FLAC.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ extension AudioFileConverter {
1313
@concurrent nonisolated static func performFlacConversion(
1414
from inputURL: URL,
1515
to outputURL: URL,
16-
settings: AudioFileSettings
16+
settings: Settings
1717
) async throws {
1818
// 1) Open the input file with ExtAudioFile
1919
var inputFile: ExtAudioFileRef?
2020
var result = ExtAudioFileOpenURL(inputURL as CFURL, &inputFile)
2121
try checkError(result)
2222
guard let inputFile = inputFile else {
23-
throw SwiftAudioFileConverterError.unableToOpenFile(inputURL)
23+
throw ConverterError.unableToOpenFile(inputURL)
2424
}
2525
defer { ExtAudioFileDispose(inputFile) }
2626

@@ -30,7 +30,7 @@ extension AudioFileConverter {
3030
// 3) We want 16-bit interleaved PCM from ExtAudioFile
3131
// because FLAC expects integer samples
3232
var clientFormat = AudioStreamBasicDescription(
33-
mSampleRate: settings.sampleRate.rawValue,
33+
mSampleRate: settings.sampleRate.hzDouble,
3434
mFormatID: kAudioFormatLinearPCM,
3535
mFormatFlags: kAudioFormatFlagsNativeEndian
3636
| kLinearPCMFormatFlagIsPacked
@@ -54,14 +54,14 @@ extension AudioFileConverter {
5454

5555
// 4) Create and configure the FLAC encoder
5656
guard let flacEncoder = FLAC__stream_encoder_new() else {
57-
throw SwiftAudioFileConverterError.unsupportedConversion(.flac)
57+
throw ConverterError.unsupportedConversion(.flac)
5858
}
5959
defer { FLAC__stream_encoder_delete(flacEncoder) }
6060

6161
// Set basic FLAC encoder properties
6262
// Sample rate, channels, bits per sample
6363
FLAC__stream_encoder_set_channels(flacEncoder, channels)
64-
FLAC__stream_encoder_set_sample_rate(flacEncoder, UInt32(settings.sampleRate.rawValue))
64+
FLAC__stream_encoder_set_sample_rate(flacEncoder, UInt32(settings.sampleRate.hzInt))
6565
FLAC__stream_encoder_set_bits_per_sample(flacEncoder, 16)
6666

6767
// Optionally set a compression level (0 = fastest, 8 = slowest/best)
@@ -72,7 +72,7 @@ extension AudioFileConverter {
7272
FLAC__stream_encoder_init_file(flacEncoder, pathCstr, nil, nil)
7373
}
7474
if initStatus != FLAC__STREAM_ENCODER_INIT_STATUS_OK {
75-
throw SwiftAudioFileConverterError.unsupportedConversion(.flac)
75+
throw ConverterError.unsupportedConversion(.flac)
7676
}
7777

7878
// 6) Read PCM in chunks, pass to FLAC
@@ -139,14 +139,14 @@ extension AudioFileConverter {
139139
}
140140

141141
if ok == 0 {
142-
throw SwiftAudioFileConverterError.flacConversionUnknownError
142+
throw ConverterError.flacConversionUnknownError
143143
}
144144
}
145145

146146
// 7) Finish/flush the encoder
147147
let finishOK = FLAC__stream_encoder_finish(flacEncoder)
148148
if finishOK == 0 {
149-
throw SwiftAudioFileConverterError.flacConversionUnknownError
149+
throw ConverterError.flacConversionUnknownError
150150
}
151151

152152
// 8) The output FLAC file now exists at outputURL

Sources/SwiftAudioFileConverter/AudioFileConverter/AudioFileConverter+LAME.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ extension AudioFileConverter {
1313
@concurrent nonisolated static func performLameConversion(
1414
from inputURL: URL,
1515
to outputURL: URL,
16-
settings: AudioFileSettings
16+
settings: Settings
1717
) async throws {
1818
// 1) Open the input file using ExtAudioFile
1919
var inputFile: ExtAudioFileRef?
2020
var result = ExtAudioFileOpenURL(inputURL as CFURL, &inputFile)
2121
try checkError(result)
2222
guard let inputFile = inputFile else {
23-
throw SwiftAudioFileConverterError.unableToOpenFile(inputURL)
23+
throw ConverterError.unableToOpenFile(inputURL)
2424
}
2525
defer {
2626
ExtAudioFileDispose(inputFile)
@@ -32,7 +32,7 @@ extension AudioFileConverter {
3232
// 3) We want 32-bit float interleaved PCM for reading from ExtAudioFile
3333
// Build a client format describing float PCM.
3434
var clientFormat = AudioStreamBasicDescription(
35-
mSampleRate: settings.sampleRate.rawValue,
35+
mSampleRate: settings.sampleRate.hzDouble,
3636
mFormatID: kAudioFormatLinearPCM,
3737
mFormatFlags: kLinearPCMFormatFlagIsFloat
3838
| kAudioFormatFlagsNativeEndian
@@ -56,18 +56,18 @@ extension AudioFileConverter {
5656

5757
// 4) Prepare LAME for float input
5858
guard let lame = lame_init() else {
59-
throw SwiftAudioFileConverterError.unsupportedConversion(.mp3)
59+
throw ConverterError.unsupportedConversion(.mp3)
6060
}
6161
lame_set_num_channels(lame, Int32(channels))
62-
lame_set_in_samplerate(lame, Int32(settings.sampleRate.rawValue))
62+
lame_set_in_samplerate(lame, Int32(settings.sampleRate.hzInt))
6363

6464
// Example settings:
6565
lame_set_brate(lame, 128) // 128 kbps
6666
lame_set_quality(lame, 2) // quality scale: 0=best, 9=worst
6767

6868
if lame_init_params(lame) < 0 {
6969
lame_close(lame)
70-
throw SwiftAudioFileConverterError.unsupportedConversion(.mp3)
70+
throw ConverterError.unsupportedConversion(.mp3)
7171
}
7272
defer { lame_close(lame) }
7373

@@ -77,10 +77,10 @@ extension AudioFileConverter {
7777
try fileManager.removeItem(at: outputURL)
7878
}
7979
guard fileManager.createFile(atPath: outputURL.path, contents: nil) else {
80-
throw SwiftAudioFileConverterError.fileIsNotWritable(outputURL)
80+
throw ConverterError.fileIsNotWritable(outputURL)
8181
}
8282
guard let fileHandle = FileHandle(forWritingAtPath: outputURL.path) else {
83-
throw SwiftAudioFileConverterError.fileIsNotWritable(outputURL)
83+
throw ConverterError.fileIsNotWritable(outputURL)
8484
}
8585
defer { try? fileHandle.close() }
8686

@@ -156,7 +156,7 @@ extension AudioFileConverter {
156156
}
157157

158158
if encodedBytes < 0 {
159-
throw SwiftAudioFileConverterError.unsupportedConversion(.mp3)
159+
throw ConverterError.unsupportedConversion(.mp3)
160160
}
161161

162162
// 6d) Write the MP3 bytes to the output file

Sources/SwiftAudioFileConverter/AudioFileConverter/AudioFileConverter.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,29 @@ public enum AudioFileConverter {
1313
@concurrent nonisolated static public func convert(
1414
from inputURL: URL,
1515
to outputURL: URL,
16-
with settings: AudioFileSettings
16+
with settings: Settings
1717
) async throws {
1818
// Check if the input file extension is a known audio format
1919
guard FileFormat.allCases.map(\.rawValue).contains(inputURL.pathExtension.lowercased()) else {
20-
throw SwiftAudioFileConverterError.unsupportedAudioFileExtension(inputURL)
20+
throw ConverterError.unsupportedAudioFileExtension(inputURL)
2121
}
2222

2323
// Check read permissions on the input file
2424
guard FileManager.default.isReadableFile(atPath: inputURL.path) else {
25-
throw SwiftAudioFileConverterError.fileIsNotReadable(inputURL)
25+
throw ConverterError.fileIsNotReadable(inputURL)
2626
}
2727

2828
// Check write permissions on the output (directory must be writable)
2929
// NOTE: For a brand new file, `isWritableFile(atPath:)` can be misleading.
3030
// Often, you'll want to check if the directory is writable instead.
3131
let outputDirectory = outputURL.deletingLastPathComponent()
3232
guard FileManager.default.isWritableFile(atPath: outputDirectory.path) else {
33-
throw SwiftAudioFileConverterError.fileIsNotWritable(outputURL)
33+
throw ConverterError.fileIsNotWritable(outputURL)
3434
}
3535

3636
// Check if output extension matches `settings.fileFormat`
3737
guard outputURL.pathExtension.lowercased() == settings.fileFormat.rawValue else {
38-
throw SwiftAudioFileConverterError.audioFileExtensionSettingsMismatch(outputURL, settings)
38+
throw ConverterError.audioFileExtensionSettingsMismatch(outputURL, settings)
3939
}
4040

4141
// Dispatch the conversion based on the requested format

Sources/SwiftAudioFileConverter/Errors/SwiftAudioFileConverterError.swift renamed to Sources/SwiftAudioFileConverter/AudioFileConverter/Errors/ConverterError.swift

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
11
//
2-
// SwiftAudioFileConverterError.swift
2+
// ConverterError.swift
33
// SwiftAudioFileConverter
44
//
55
// Created by Devin Roth on 2025-04-03.
66
//
77

88
import Foundation
99

10-
public enum SwiftAudioFileConverterError: LocalizedError {
11-
case invalidAudioFileSettings(AudioFileSettings)
12-
case unsupportedAudioFileExtension(URL)
13-
case audioFileExtensionSettingsMismatch(URL, AudioFileSettings)
14-
case fileDoesNotExist(URL)
15-
case fileIsNotReadable(URL)
16-
case unableToOpenFile(URL)
17-
case fileIsNotWritable(URL)
18-
case unsupportedConversion(FileFormat)
19-
case coreAudioError(CoreAudioError)
20-
case flacConversionUnknownError
10+
extension AudioFileConverter {
11+
public enum ConverterError: Error {
12+
case invalidAudioFileSettings(Settings)
13+
case unsupportedAudioFileExtension(URL)
14+
case audioFileExtensionSettingsMismatch(URL, Settings)
15+
case fileDoesNotExist(URL)
16+
case fileIsNotReadable(URL)
17+
case unableToOpenFile(URL)
18+
case fileIsNotWritable(URL)
19+
case unsupportedConversion(FileFormat)
20+
case coreAudioError(CoreAudioError)
21+
case flacConversionUnknownError
22+
}
2123
}
2224

23-
extension SwiftAudioFileConverterError: Equatable { }
25+
extension AudioFileConverter.ConverterError: Equatable { }
2426

25-
extension SwiftAudioFileConverterError: Hashable { }
27+
extension AudioFileConverter.ConverterError: Hashable { }
2628

27-
extension SwiftAudioFileConverterError: Sendable { }
29+
extension AudioFileConverter.ConverterError: Sendable { }
2830

29-
extension SwiftAudioFileConverterError {
31+
extension AudioFileConverter.ConverterError: LocalizedError {
3032
public var errorDescription: String? {
3133
switch self {
3234
case let .invalidAudioFileSettings(audioFileSettings):
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// CoreAudioError.swift
3+
// SwiftAudioFileConverter
4+
//
5+
// Created by Devin Roth on 2025-04-03.
6+
//
7+
8+
import Foundation
9+
import AudioToolbox
10+
11+
extension AudioFileConverter {
12+
public enum CoreAudioError: Error {
13+
case formatNotSupported
14+
case unspecified
15+
case unsupportedProperty
16+
case badPropertySize
17+
case badSpecifierSize
18+
case unknownFormat
19+
case unknownError(OSStatus)
20+
21+
public init(status: OSStatus) {
22+
switch status {
23+
case kAudioFormatUnsupportedDataFormatError:
24+
self = .formatNotSupported
25+
case kAudioFormatUnspecifiedError:
26+
self = .unspecified
27+
case kAudioFormatUnsupportedPropertyError:
28+
self = .unsupportedProperty
29+
case kAudioFormatBadPropertySizeError:
30+
self = .badPropertySize
31+
case kAudioFormatBadSpecifierSizeError:
32+
self = .badSpecifierSize
33+
case kAudioFormatUnknownFormatError:
34+
self = .unknownFormat
35+
default:
36+
self = .unknownError(status)
37+
}
38+
}
39+
}
40+
}
41+
42+
extension AudioFileConverter.CoreAudioError: Equatable { }
43+
44+
extension AudioFileConverter.CoreAudioError: Hashable { }
45+
46+
extension AudioFileConverter.CoreAudioError: Sendable { }
47+
48+
extension AudioFileConverter.CoreAudioError: LocalizedError {
49+
public var errorDescription: String? {
50+
switch self {
51+
case .formatNotSupported:
52+
"Format not supported."
53+
case .unspecified:
54+
"Unspecified error."
55+
case .unsupportedProperty:
56+
"Unsupported property."
57+
case .badPropertySize:
58+
"Bad property size."
59+
case .badSpecifierSize:
60+
"Bad specifier size."
61+
case .unknownFormat:
62+
"Unknown format."
63+
case let .unknownError(osStatus):
64+
"Unknown error (OSStatus \(osStatus))"
65+
}
66+
}
67+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// BitDepth.swift
3+
// SwiftAudioFileConverter
4+
//
5+
// Created by Devin Roth on 2025-04-03.
6+
//
7+
8+
import Foundation
9+
10+
extension AudioFileConverter {
11+
public enum BitDepth {
12+
case int16
13+
case int24
14+
case float32
15+
}
16+
}
17+
18+
extension AudioFileConverter.BitDepth: Equatable { }
19+
20+
extension AudioFileConverter.BitDepth: Hashable { }
21+
22+
extension AudioFileConverter.BitDepth: Sendable { }
23+
24+
extension AudioFileConverter.BitDepth: CaseIterable { }
25+
26+
extension AudioFileConverter.BitDepth: CustomStringConvertible {
27+
public var description: String {
28+
switch self {
29+
case .int16:
30+
"16-bit Int"
31+
case .int24:
32+
"24-bit Int"
33+
case .float32:
34+
"32-bit Float"
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)