-
-
Notifications
You must be signed in to change notification settings - Fork 127
Expand file tree
/
Copy pathWaveformShape.swift
More file actions
59 lines (51 loc) · 2.85 KB
/
WaveformShape.swift
File metadata and controls
59 lines (51 loc) · 2.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import Foundation
import SwiftUI
import DSWaveformImage
/// A waveform SwiftUI `Shape` object for generating a shape path from component(s) of the waveform.
/// **Note:** The Shape does *not* style itself. Use `WaveformView` for that purpose and only use the Shape directly if needed.
///
/// If `renderer` is a `ChannelAwareWaveformRenderer` with a non-`.merged` selection, the caller is
/// responsible for providing `samples` in the matching layout (e.g. `[allLeft..., allRight...]` for
/// `.stereo`). The Shape only renders; it cannot re-sample the audio. `WaveformView` handles this
/// automatically — prefer it when working from an `audioURL`.
@available(iOS 15.0, macOS 12.0, *)
public struct WaveformShape: Shape {
private let samples: [Float]
private let configuration: Waveform.Configuration
private let renderer: WaveformRenderer
public init(
samples: [Float],
configuration: Waveform.Configuration = Waveform.Configuration(),
renderer: WaveformRenderer = LinearWaveformRenderer()
) {
self.samples = samples
self.configuration = configuration
self.renderer = renderer
}
public func path(in rect: CGRect) -> Path {
// Debug-only sanity check: stereo expects an even-length sample array (one half per channel).
// We can't verify content correctness here — that's on the caller — but an odd count is
// unambiguously wrong and easy to catch.
if let channelAware = renderer as? ChannelAwareWaveformRenderer, channelAware.channelSelection == .stereo {
assert(samples.count % 2 == 0, "WaveformShape: a `.stereo` renderer expects samples laid out as [allLeft..., allRight...] (even length). Got \(samples.count).")
}
let size = CGSize(width: rect.maxX, height: rect.maxY)
let isStereo = (renderer as? ChannelAwareWaveformRenderer)?.channelSelection == .stereo
let drawer = WaveformImageDrawer()
let scaledSamples = drawer.applyAmplitudeScaling(samples, scaling: configuration.amplitudeScaling)
let dampedSamples = configuration.shouldDamp ? drawer.damp(scaledSamples, with: configuration, isStereo: isStereo) : scaledSamples
let path = renderer.path(samples: dampedSamples, with: configuration.with(size: size), lastOffset: 0)
return Path(path)
}
/// Whether the shape has no underlying samples to display.
var isEmpty: Bool {
samples.isEmpty
}
/// SwiftUI fill style this shape's path expects. Uses even-odd when the renderer's path is
/// built from multiple subpaths that need region subtraction (e.g. `CircularWaveformRenderer`
/// in `.ring` kind). Defaults to non-zero for everyone else.
public var fillStyle: FillStyle {
let eoFill = (renderer as? CircularWaveformRenderer)?.prefersEvenOddFillRule ?? false
return FillStyle(eoFill: eoFill)
}
}