Skip to content

Commit

Permalink
Album gain scanner op
Browse files Browse the repository at this point in the history
  • Loading branch information
kartik-venugopal committed Aug 23, 2024
1 parent 383611c commit e309186
Show file tree
Hide file tree
Showing 16 changed files with 312 additions and 176 deletions.
14 changes: 9 additions & 5 deletions Aural.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,8 @@
3E0219D12C23497D00865AC2 /* shufen.regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3E0219BD2C23497D00865AC2 /* shufen.regular.ttf */; };
3E0219D22C23497D00865AC2 /* WalterTurncoat.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3E0219BE2C23497D00865AC2 /* WalterTurncoat.ttf */; };
3E0219EA2C23498700865AC2 /* appIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 3E0219D32C23498700865AC2 /* appIcon.icns */; };
3E0328252C77BF7000A389B6 /* ReplayGainScannerOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E0328242C77BF7000A389B6 /* ReplayGainScannerOperation.swift */; };
3E0328252C77BF7000A389B6 /* ReplayGainTrackScannerOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E0328242C77BF7000A389B6 /* ReplayGainTrackScannerOperation.swift */; };
3E0328272C794D1200A389B6 /* ReplayGainAlbumScannerOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E0328262C794D1200A389B6 /* ReplayGainAlbumScannerOperation.swift */; };
3E0397E82B83EB73004454DB /* AppDelegate+Init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E0397E72B83EB73004454DB /* AppDelegate+Init.swift */; };
3E0397EA2B83EC27004454DB /* AppDelegate+TearDown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E0397E92B83EC27004454DB /* AppDelegate+TearDown.swift */; };
3E045D06281322450069DEFE /* TrackInfoWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3E045D05281322450069DEFE /* TrackInfoWindow.xib */; };
Expand Down Expand Up @@ -1544,7 +1545,8 @@
3E0219E42C23498700865AC2 /* copyright.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = copyright.txt; sourceTree = "<group>"; };
3E0219E52C23498700865AC2 /* xml-copyright.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "xml-copyright.sh"; sourceTree = "<group>"; };
3E0219E72C23498700865AC2 /* unused.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = unused.rb; sourceTree = "<group>"; };
3E0328242C77BF7000A389B6 /* ReplayGainScannerOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplayGainScannerOperation.swift; sourceTree = "<group>"; };
3E0328242C77BF7000A389B6 /* ReplayGainTrackScannerOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplayGainTrackScannerOperation.swift; sourceTree = "<group>"; };
3E0328262C794D1200A389B6 /* ReplayGainAlbumScannerOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplayGainAlbumScannerOperation.swift; sourceTree = "<group>"; };
3E0397E72B83EB73004454DB /* AppDelegate+Init.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Init.swift"; sourceTree = "<group>"; };
3E0397E92B83EC27004454DB /* AppDelegate+TearDown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+TearDown.swift"; sourceTree = "<group>"; };
3E045D05281322450069DEFE /* TrackInfoWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TrackInfoWindow.xib; sourceTree = "<group>"; };
Expand Down Expand Up @@ -5376,7 +5378,8 @@
isa = PBXGroup;
children = (
3E772EC42C73F80C00DC3137 /* ReplayGainScanner.swift */,
3E0328242C77BF7000A389B6 /* ReplayGainScannerOperation.swift */,
3E0328242C77BF7000A389B6 /* ReplayGainTrackScannerOperation.swift */,
3E0328262C794D1200A389B6 /* ReplayGainAlbumScannerOperation.swift */,
3EE882792C72A0DF00E270B8 /* AVFReplayGainScanner.swift */,
3EE8827A2C72A0DF00E270B8 /* FFmpegReplayGainScanner.swift */,
3E772EBC2C73F1B900DC3137 /* FFmpegReplayGainScanner+Scan.swift */,
Expand All @@ -5387,8 +5390,8 @@
3EE8827F2C72A11300E270B8 /* EBUR128 */ = {
isa = PBXGroup;
children = (
3EE8827D2C72A11300E270B8 /* EBUR128Errors.swift */,
3EE8827E2C72A11300E270B8 /* EBUR128State.swift */,
3EE8827D2C72A11300E270B8 /* EBUR128Errors.swift */,
);
path = EBUR128;
sourceTree = "<group>";
Expand Down Expand Up @@ -5864,7 +5867,7 @@
3E6C125525CEBE0600BF0D07 /* ColorSchemePreviewView.swift in Sources */,
3E6C127725CEBE1800BF0D07 /* ColorSchemesManager.swift in Sources */,
3EFFEB8027D9CB25006A333B /* ColorSchemesManager+Observer.swift in Sources */,
3E0328252C77BF7000A389B6 /* ReplayGainScannerOperation.swift in Sources */,
3E0328252C77BF7000A389B6 /* ReplayGainTrackScannerOperation.swift in Sources */,
3E6C126A25CEBE0600BF0D07 /* ColorSchemesManagerViewController.swift in Sources */,
3E0217FE2C23490E00865AC2 /* EQUnitProtocol.swift in Sources */,
3EB3A61926A763870060487C /* ColorSchemesViewProtocol.swift in Sources */,
Expand Down Expand Up @@ -6409,6 +6412,7 @@
3E0219072C23490E00865AC2 /* LegacyGesturesControlsPreferences.swift in Sources */,
3E4A90FE27F79AFA003A6C80 /* WindowID.swift in Sources */,
3EEC08B92BDC5F9A008DBF8E /* SearchViewController+Theming.swift in Sources */,
3E0328272C794D1200A389B6 /* ReplayGainAlbumScannerOperation.swift in Sources */,
3EB3A60B26A7425C0060487C /* SingletonWindowController.swift in Sources */,
3EEBC6EE27C16CF700880DFD /* EffectsUnitStateObserverRegistry.swift in Sources */,
3E0219342C23490E00865AC2 /* Chapter.swift in Sources */,
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class AVFReplayGainScanner: EBUR128LoudnessScannerProtocol {
private static let maxConsecutiveIOErrors: Int = 3
private static let maxTotalIOErrors: Int = 10

init(file: URL) throws {
required init(file: URL) throws {

self.file = file

Expand All @@ -54,93 +54,89 @@ class AVFReplayGainScanner: EBUR128LoudnessScannerProtocol {
channelLayout: channelLayout)
}

func scan(_ completionHandler: @escaping (EBUR128AnalysisResult?) -> Void) {
func scan() throws -> EBUR128TrackAnalysisResult {

DispatchQueue.global(qos: .userInitiated).async {
var samplesRead: AVAudioFramePosition = 0

guard let readBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: Self.chunkSize),
let analyzeBuffer = AVAudioPCMBuffer(pcmFormat: analysisFormat, frameCapacity: Self.chunkSize) else {

do {
completionHandler(try self.doScan())

} catch let err as EBUR128Error {
print("Error: \(err.description)")

} catch {
print("Error: \(error)")
}
throw AVFoundationError("Unable to create AVAudioPCMBuffer with format: \(audioFormat) and capacity: \(Self.chunkSize)")
}
}

private func doScan() throws -> EBUR128AnalysisResult? {

do {

var samplesRead: AVAudioFramePosition = 0

guard let readBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: Self.chunkSize),
let analyzeBuffer = AVAudioPCMBuffer(pcmFormat: analysisFormat, frameCapacity: Self.chunkSize) else {return nil}
guard let converter: AVAudioConverter = .init(from: audioFormat,
to: analysisFormat) else {

guard let converter: AVAudioConverter = .init(from: audioFormat,
to: analysisFormat) else {return nil}

var eof: Bool = false
var sampleCountFromLastRead: AVAudioFramePosition = 0
var consecutiveIOErrors: Int = 0
var totalIOErrors: Int = 0
throw AVFoundationError("Unable to create AVAudioConverter with source format: \(audioFormat) and target format: \(analysisFormat)")
}

var eof: Bool = false
var sampleCountFromLastRead: AVAudioFramePosition = 0
var consecutiveIOErrors: Int = 0
var totalIOErrors: Int = 0
var mostRecentError: Error? = nil

while (!isCancelled) && (!eof) {

while (!isCancelled) && (!eof) {
do {

do {

try audioFile.read(into: readBuffer)
sampleCountFromLastRead = AVAudioFramePosition(readBuffer.frameLength)
samplesRead += sampleCountFromLastRead

try converter.convert(to: analyzeBuffer, from: readBuffer)

guard let floatBuffer = analyzeBuffer.floatChannelData else {return nil}

try ebur128.addFramesAsFloat(framesPointer: floatBuffer[0], frameCount: Int(analyzeBuffer.frameLength))

// Reset the error counter if the read after a failed iteration succeeds.
if consecutiveIOErrors > 0 {
consecutiveIOErrors = 0
}

} catch {
try audioFile.read(into: readBuffer)
sampleCountFromLastRead = AVAudioFramePosition(readBuffer.frameLength)
samplesRead += sampleCountFromLastRead

try converter.convert(to: analyzeBuffer, from: readBuffer)

guard let floatBuffer = analyzeBuffer.floatChannelData else {
throw AVFoundationError("Unable to get floatChannelData property of AVAudioPCMBuffer")
}

try ebur128.addFramesAsFloat(framesPointer: floatBuffer[0], frameCount: Int(analyzeBuffer.frameLength))

// Reset the error counter if the read after a failed iteration succeeds.
if consecutiveIOErrors > 0 {
consecutiveIOErrors = 0
}

} catch {

let description = (error as? EBUR128Error)?.description ?? error.localizedDescription
NSLog("Waveform Decoder IO Error: \(description)")

mostRecentError = error
consecutiveIOErrors.increment()
totalIOErrors.increment()

if consecutiveIOErrors >= Self.maxConsecutiveIOErrors {

let description = (error as? EBUR128Error)?.description ?? error.localizedDescription
NSLog("Waveform Decoder IO Error: \(description)")
NSLog("Encountered too many consecutive IO errors. Terminating scan loop.")
break

consecutiveIOErrors.increment()
totalIOErrors.increment()
} else if totalIOErrors > Self.maxTotalIOErrors {

if consecutiveIOErrors >= Self.maxConsecutiveIOErrors {

NSLog("Encountered too many consecutive IO errors. Terminating scan loop.")
break

} else if totalIOErrors > Self.maxTotalIOErrors {

NSLog("Encountered too many total IO errors. Terminating scan loop.")
break
}
NSLog("Encountered too many total IO errors. Terminating scan loop.")
break
}

eof = (audioFile.framePosition >= totalSamples) ||
(samplesRead >= totalSamples) ||
(sampleCountFromLastRead == 0)
}

return isCancelled || (!eof) ? nil : try ebur128.analyze()

} catch {

print("Error: \(error.localizedDescription)")
return nil
eof = (audioFile.framePosition >= totalSamples) ||
(samplesRead >= totalSamples) ||
(sampleCountFromLastRead == 0)
}

if isCancelled {
throw EBURAnalysisInterruptedError(rootCause: mostRecentError, message: "Operation was cancelled.")
} else if consecutiveIOErrors >= 3 {
throw EBURAnalysisInterruptedError(rootCause: mostRecentError, message: "Too many consecutive errors encountered.")
} else if !eof {
throw EBURAnalysisInterruptedError(rootCause: mostRecentError, message: "Did not reach EOF.")
}

return try ebur128.analyze()
}

func cancel() {
isCancelled = true
}
}

class AVFoundationError: DisplayableError {}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fileprivate typealias EBUR128FramesAddFunction = (UnsafeMutablePointer<UInt8>?,

extension FFmpegReplayGainScanner {

func scanAsInt16() throws -> EBUR128AnalysisResult? {
func scanAsInt16() throws -> EBUR128TrackAnalysisResult {

try doScan {pointer, frame in

Expand All @@ -34,7 +34,7 @@ extension FFmpegReplayGainScanner {
}
}

func scanAsInt32() throws -> EBUR128AnalysisResult? {
func scanAsInt32() throws -> EBUR128TrackAnalysisResult {

try doScan {pointer, frame in

Expand All @@ -54,7 +54,7 @@ extension FFmpegReplayGainScanner {
}
}

func scanAsFloat() throws -> EBUR128AnalysisResult? {
func scanAsFloat() throws -> EBUR128TrackAnalysisResult {

try doScan {pointer, frame in

Expand All @@ -74,7 +74,7 @@ extension FFmpegReplayGainScanner {
}
}

func scanAsDouble() throws -> EBUR128AnalysisResult? {
func scanAsDouble() throws -> EBUR128TrackAnalysisResult {

try doScan {pointer, frame in

Expand All @@ -94,60 +94,62 @@ extension FFmpegReplayGainScanner {
}
}

fileprivate func doScan(addFramesFunction: EBUR128FramesAddFunction) throws -> EBUR128AnalysisResult? {
fileprivate func doScan(addFramesFunction: EBUR128FramesAddFunction) throws -> EBUR128TrackAnalysisResult {

defer {
self.cleanUpAfterScan()
}

do {

var curSize: Int = 0
let sizeOfAFrame = codec.sampleFormat.size * channelCount
var mostRecentError: Error? = nil
var curSize: Int = 0
let sizeOfAFrame = codec.sampleFormat.size * channelCount

while !isCancelled, !eof, consecutiveErrors < 3 {

while !isCancelled, !eof, consecutiveErrors < 3 {
do {

do {
guard let pkt = try ctx.readPacket(from: stream) else {

guard let pkt = try ctx.readPacket(from: stream) else {

consecutiveErrors.increment()
continue
}
consecutiveErrors.increment()
continue
}

let frames = try codec.decode(packet: pkt)

for frame in frames.frames {

let frames = try codec.decode(packet: pkt)
// Only 1 buffer since interleaved. Capacity = sampleCount * number of bytes in Int16 * channelCount
let newSize = frame.intSampleCount * sizeOfAFrame

for frame in frames.frames {
if newSize > curSize {

// Only 1 buffer since interleaved. Capacity = sampleCount * number of bytes in Int16 * channelCount
let newSize = frame.intSampleCount * sizeOfAFrame

if newSize > curSize {

outputData?[0] = .allocate(capacity: newSize)
curSize = newSize
}

swr?.convertFrame(frame, andStoreIn: outputData)
addFramesFunction(outputData?[0] ?? frame.dataPointers[0], frame)
outputData?[0] = .allocate(capacity: newSize)
curSize = newSize
}

} catch let err as CodedError {

eof = err.isEOF

if !err.isEOF {

consecutiveErrors.increment()
print("Error: \(err.code.errorDescription)")
}
swr?.convertFrame(frame, andStoreIn: outputData)
addFramesFunction(outputData?[0] ?? frame.dataPointers[0], frame)
}

} catch let err as CodedError {

eof = err.isEOF
mostRecentError = err

if !err.isEOF {
consecutiveErrors.increment()
}
}

} catch {
print("Error: \(error.localizedDescription)")
}

return isCancelled || (consecutiveErrors >= 3) || (!eof) ? nil : try ebur128.analyze()
if isCancelled {
throw EBURAnalysisInterruptedError(rootCause: mostRecentError, message: "Operation was cancelled.")
} else if consecutiveErrors >= 3 {
throw EBURAnalysisInterruptedError(rootCause: mostRecentError, message: "Too many consecutive errors encountered.")
} else if !eof {
throw EBURAnalysisInterruptedError(rootCause: mostRecentError, message: "Did not reach EOF.")
}

return try ebur128.analyze()
}
}
Loading

0 comments on commit e309186

Please sign in to comment.