Skip to content

Conversation

@fulldecent
Copy link
Owner

No description provided.

Copilot AI review requested due to automatic review settings December 5, 2025 04:11
Copilot finished reviewing on behalf of fulldecent December 5, 2025 04:15
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors FDWaveformView to separate data source and rendering concerns through protocol-based abstractions. It introduces FDWaveformDataSource for providing audio samples and FDWaveformRenderer for visualization, enabling support for custom data sources beyond file-based audio (e.g., procedural audio, streaming).

Key Changes

  • Introduces FDWaveformDataSource protocol with readSamples(in:) method for flexible sample provision
  • Adds FDWaveformRenderer protocol (though not yet integrated into the main view)
  • Refactors FDAudioContext to implement FDWaveformDataSource and adds readSamples implementation
  • Extracts FDWaveformType enum to separate file with improved documentation
  • Adds example SineWaveSource demonstrating procedural audio generation

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
Sources/FDWaveformView/FDWaveformView.swift Adds dataSource property, refactors to use _dataSource internally, improves code formatting
Sources/FDWaveformView/FDWaveformDataSource.swift New protocol defining data source contract for audio samples
Sources/FDWaveformView/FDWaveformRenderer.swift New protocol for pluggable rendering strategies
Sources/FDWaveformView/FDWaveformType.swift Extracted enum with improved documentation for linear/logarithmic scaling
Sources/FDWaveformView/FDCoreGraphicsRenderer.swift New concrete renderer implementations (line and bar styles)
Sources/FDWaveformView/FDWaveformRenderOperation.swift Updated to accept FDWaveformDataSource, adds sliceDataSource method
Sources/FDWaveformView/FDAudioContext.swift Implements FDWaveformDataSource, adds readSamples method with AVAssetReader
Example/Sources/SineWaveSource.swift New example data source generating procedural sine waves
Example/Sources/ContentView.swift Updated UI to demonstrate data source switching and new features
Example/Example.xcodeproj/project.pbxproj Project configuration updates for new files

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

var maxInRange: Float = 0
let rangeCount = vDSP_Length(endIndex - startIndex)
vDSP_maxv(
Array(processedSamples[startIndex..<endIndex]), 1, &maxInRange, rangeCount)
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a new array with Array(processedSamples[startIndex..<endIndex]) on every iteration is inefficient. Consider using vDSP_maxv with a stride and pointer arithmetic to avoid the allocation, or use vDSP_maxmgv for finding max magnitude.

Suggested change
Array(processedSamples[startIndex..<endIndex]), 1, &maxInRange, rangeCount)
&processedSamples[startIndex], 1, &maxInRange, rangeCount)

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +63
waveformType.process(normalizedSamples: &processedSamples)

// Take absolute values for amplitude
vDSP_vabs(processedSamples, 1, &processedSamples, 1, vDSP_Length(processedSamples.count))
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect processing order: waveformType.process() expects samples in range 0...1 (absolute amplitude) according to its documentation, but it's being called on the raw signed samples (-1.0 to 1.0) before vDSP_vabs(). The correct order should be: 1) vDSP_vabs() to get absolute values, 2) then waveformType.process() to apply log scaling. This will cause incorrect dB calculations for logarithmic rendering.

Suggested change
waveformType.process(normalizedSamples: &processedSamples)
// Take absolute values for amplitude
vDSP_vabs(processedSamples, 1, &processedSamples, 1, vDSP_Length(processedSamples.count))
// Take absolute values for amplitude
vDSP_vabs(processedSamples, 1, &processedSamples, 1, vDSP_Length(processedSamples.count))
// Process samples through the waveform type (linear/logarithmic)
waveformType.process(normalizedSamples: &processedSamples)

Copilot uses AI. Check for mistakes.
Comment on lines +1 to 7
import AVFoundation
import Accelerate
import MediaPlayer
//
// Copyright 2013 - 2017, William Entriken and the FDWaveformView contributors.
// Copyright William Entriken and the FDWaveformView contributors.
//
import UIKit
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imports are placed before the copyright comment. By convention, copyright headers should appear before imports. Consider moving the copyright comment to the top of the file.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to 6
import AVFoundation
import Accelerate
//
// Copyright 2013 - 2017, William Entriken and the FDWaveformView contributors.
// Copyright William Entriken and the FDWaveformView contributors.
//
import UIKit
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imports are placed before the copyright comment. By convention, copyright headers should appear before imports. Consider moving the copyright comment to the top of the file.

Copilot uses AI. Check for mistakes.
Comment on lines +188 to +190

// Notify delegate
delegate?.waveformViewDidLoad?(self)
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling waveformViewDidLoad? in resetForNewDataSource() is inconsistent with the audioURL setter behavior. When setting audioURL, the delegate methods waveformViewWillLoad? and waveformViewDidLoad? are called around the async loading operation. For dataSource, there's no loading operation, so calling waveformViewDidLoad? without a corresponding waveformViewWillLoad? call creates an asymmetric API behavior. Consider either removing this call or creating separate delegate methods for data source changes.

Suggested change
// Notify delegate
delegate?.waveformViewDidLoad?(self)

Copilot uses AI. Check for mistakes.
Comment on lines +86 to +96
let int16Count = sampleBuffer.count / MemoryLayout<Int16>.size
var floatSamples = [Float](repeating: 0.0, count: int16Count)

sampleBuffer.withUnsafeBytes { bytes in
guard let samples = bytes.bindMemory(to: Int16.self).baseAddress else { return }
vDSP_vflt16(samples, 1, &floatSamples, 1, vDSP_Length(int16Count))
}

// Normalize to [-1, 1] range
var scalar: Float = 1.0 / Float(Int16.max)
vDSP_vsmul(floatSamples, 1, &scalar, &floatSamples, 1, vDSP_Length(int16Count))
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Int16 to Float conversion and normalization could overflow or produce unexpected results if sampleBuffer.count is not a multiple of MemoryLayout<Int16>.size. Consider adding a check to ensure proper alignment and handling of any remainder bytes.

Copilot uses AI. Check for mistakes.
Comment on lines +57 to +61
let visibleSamples = Int(Double(totalSamples) / zoomFactor)
let center = totalSamples / 2
let start = max(0, center - visibleSamples / 2)
let end = min(totalSamples, start + visibleSamples)
uiView.zoomSamples = start..<end
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential for creating an empty range: if zoomFactor is very large, visibleSamples could be 0 or very small, potentially creating start >= end. Consider adding a minimum visible sample count (e.g., max(10, Int(Double(totalSamples) / zoomFactor))) to prevent empty ranges.

Copilot uses AI. Check for mistakes.
Comment on lines +178 to +191
private func resetForNewDataSource() {
waveformImage = nil
zoomSamples = 0..<self.totalSamples
highlightedSamples = nil
inProgressWaveformRenderOperation = nil
cachedWaveformRenderOperation = nil
renderForCurrentAssetFailed = false

setNeedsDisplay()
setNeedsLayout()

// Notify delegate
delegate?.waveformViewDidLoad?(self)
}
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resetForNewDataSource() method duplicates most of the logic from the audioContext didSet observer (lines 165-174). Consider refactoring to use a shared reset method to avoid code duplication and potential inconsistencies.

Copilot uses AI. Check for mistakes.
}
}

/// Legacy initializer for backward compatibility
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "Legacy initializer for backward compatibility" but this initializer is not marked as deprecated. Consider adding @available(*, deprecated, message: "Use init(dataSource:imageSize:sampleRange:format:completionHandler:) instead") to guide users toward the new API.

Suggested change
/// Legacy initializer for backward compatibility
/// Legacy initializer for backward compatibility
@available(*, deprecated, message: "Use init(dataSource:imageSize:sampleRange:format:completionHandler:) instead")

Copilot uses AI. Check for mistakes.



// Mark - Private vars
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in MARK comment: should be // MARK: (all caps) to be recognized by Xcode's navigation system.

Suggested change
// Mark - Private vars
// MARK: - Private vars

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants