@@ -3,68 +3,61 @@ import DSWaveformImageViews
33import SwiftUI
44
55struct SwiftUIExampleView : View {
6- private enum ActiveTab : Hashable {
7- case recorder, shape, overview
6+ private enum Tab : Hashable {
7+ case gallery
8+ case recorder
89 }
910
10- private static let colors = [ UIColor . systemPink, UIColor . systemBlue, UIColor . systemGreen]
11- private static var randomColor : UIColor { colors. randomElement ( ) ! }
12-
13- private static var audioURLs : [ URL ? ] = [
14- Bundle . main. url ( forResource: " example_sound " , withExtension: " m4a " ) ,
15- Bundle . main. url ( forResource: " example_sound_2 " , withExtension: " m4a " )
16- ]
17- private static func randomURL( _ current: URL ? ) -> URL ? { audioURLs. filter { $0 != current } . randomElement ( ) ! }
18-
19- @StateObject private var audioRecorder : AudioRecorder = AudioRecorder ( )
20-
21- @State private var configuration : Waveform . Configuration = Waveform . Configuration (
22- style: . striped( Waveform . Style. StripeConfig ( color: Self . randomColor, width: 3 , lineCap: . round) ) ,
23- verticalScalingFactor: 0.9
24- )
25-
26- @State private var liveConfiguration : Waveform . Configuration = Waveform . Configuration (
27- style: . striped( . init( color: randomColor, width: 3 , spacing: 3 ) )
28- )
29-
30- @State private var audioURL : URL ? = audioURLs. first!
31- @State private var samples : [ Float ] = [ ]
32- @State private var silence : Bool = true
33- @State private var selection : ActiveTab = . overview
11+ @State private var tab : Tab = . gallery
3412
3513 var body : some View {
36- VStack {
37- Text ( " SwiftUI examples " )
38- . font ( . largeTitle. bold ( ) )
39-
40- Picker ( " Hey " , selection: $selection) {
41- Text ( " Recorder " ) . tag ( ActiveTab . recorder)
42- Text ( " Shape " ) . tag ( ActiveTab . shape)
43- Text ( " Overview " ) . tag ( ActiveTab . overview)
14+ VStack ( spacing: 0 ) {
15+ Picker ( " Section " , selection: $tab) {
16+ Label ( " Gallery " , systemImage: " rectangle.grid.2x2 " ) . tag ( Tab . gallery)
17+ Label ( " Live " , systemImage: " mic.circle.fill " ) . tag ( Tab . recorder)
4418 }
4519 . pickerStyle ( . segmented)
20+ . labelsHidden ( )
4621 . padding ( . horizontal)
22+ . padding ( . top, 12 )
4723
48- switch selection {
49- case . recorder: recordingExample
50- case . shape: shape
51- case . overview: overview
24+ switch tab {
25+ case . gallery: WaveformGalleryView ( )
26+ case . recorder: LiveRecordingTab ( )
5227 }
5328 }
54- . padding ( . vertical, 20 )
5529 }
30+ }
31+
32+ private struct LiveRecordingTab : View {
33+ @StateObject private var audioRecorder = AudioRecorder ( )
34+ @State private var silence : Bool = true
35+ @State private var configuration : Waveform . Configuration = Waveform . Configuration (
36+ style: . striped( . init( color: . systemIndigo, width: 3 , spacing: 3 ) )
37+ )
38+
39+ var body : some View {
40+ VStack ( spacing: 16 ) {
41+ VStack ( alignment: . leading, spacing: 4 ) {
42+ Text ( " Live recording " )
43+ . font ( . title2. weight ( . semibold) )
44+ Text ( " WaveformLiveCanvas streams microphone amplitude into a circular renderer. " )
45+ . font ( . callout)
46+ . foregroundStyle ( . secondary)
47+ }
48+ . frame ( maxWidth: . infinity, alignment: . leading)
49+ . padding ( . horizontal)
50+ . padding ( . top, 16 )
5651
57- @ViewBuilder
58- private var recordingExample : some View {
59- VStack {
6052 WaveformLiveCanvas (
6153 samples: audioRecorder. samples,
62- configuration: liveConfiguration ,
54+ configuration: configuration ,
6355 renderer: CircularWaveformRenderer ( kind: . circle) ,
6456 shouldDrawSilencePadding: silence
6557 )
58+ . padding ( . horizontal)
6659
67- Toggle ( " draw silence" , isOn: $silence)
60+ Toggle ( " Pad silence" , isOn: $silence)
6861 . controlSize ( . mini)
6962 . padding ( . horizontal)
7063
@@ -75,136 +68,8 @@ struct SwiftUIExampleView: View {
7568 isRecording: $audioRecorder. isRecording
7669 )
7770 . padding ( . horizontal)
78- }
79- }
80-
81- @ViewBuilder
82- private var shape : some View {
83- VStack {
84- Text ( " WaveformView " ) . font ( . monospaced( . title. bold ( ) ) ( ) )
8571
86- HStack {
87- Button {
88- configuration = configuration. with ( style: . striped( Waveform . Style. StripeConfig ( color: Self . randomColor, width: 3 , lineCap: . round) ) )
89- liveConfiguration = liveConfiguration. with ( style: . striped( . init( color: Self . randomColor, width: 3 , spacing: 3 ) ) )
90- } label: {
91- Label ( " color " , systemImage: " dice " )
92- . frame ( maxWidth: . infinity)
93- }
94- . font ( . body. bold ( ) )
95- . padding ( 8 )
96- . background ( Color ( . systemGray6) )
97- . cornerRadius ( 10 )
98-
99- Button {
100- audioURL = Self . randomURL ( audioURL)
101- print ( " will draw \( audioURL!) " )
102- } label: {
103- Label ( " waveform " , systemImage: " dice " )
104- . frame ( maxWidth: . infinity)
105- }
106- . font ( . body. bold ( ) )
107- . padding ( 8 )
108- . background ( Color ( . systemGray6) )
109- . cornerRadius ( 10 )
110- }
111- . padding ( . horizontal)
112-
113- // the if let is left here intentionally to illustrate how to deal with optional URLs
114- // as this was asked in an older GitHub issue
115- if let audioURL {
116- WaveformView ( audioURL: audioURL, configuration: configuration)
117-
118- WaveformView (
119- audioURL: audioURL,
120- configuration: configuration,
121- renderer: CircularWaveformRenderer ( kind: . ring( 0.7 ) )
122- ) { shape in
123- // you may completely override the shape styling this way
124- shape
125- . stroke (
126- LinearGradient ( colors: [ . red, Color ( Self . randomColor) ] , startPoint: . zero, endPoint: . topTrailing) ,
127- style: StrokeStyle ( lineWidth: 3 , lineCap: . round) )
128- }
129-
130- Divider ( )
131- Text ( " WaveformShape " ) . font ( . monospaced( . title. bold ( ) ) ( ) )
132-
133- /// **Note:** It's possible, but discouraged to use WaveformShape directly.
134- /// As Shapes should not do any expensive computations, the analyzing should happen outside,
135- /// hence making the API a tiny bit clumsy if used directly, since we do require to know the size,
136- /// even though the Shape of course intrinsically knows its size already.
137- GeometryReader { geometry in
138- WaveformShape ( samples: samples)
139- . fill ( Color . orange)
140- . task {
141- do {
142- let samplesNeeded = Int ( geometry. size. width * configuration. scale)
143- let samples = try await WaveformAnalyzer ( ) . samples ( fromAudioAt: audioURL, count: samplesNeeded)
144- await MainActor . run { self . samples = samples }
145- } catch {
146- assertionFailure ( error. localizedDescription)
147- }
148- }
149- }
150- }
151- }
152- }
153-
154- @ViewBuilder
155- private var overview : some View {
156- if let audioURL {
157- HStack {
158- VStack {
159- WaveformView ( audioURL: audioURL, configuration: . init( style: . filled( . red) ) )
160- WaveformView ( audioURL: audioURL, configuration: . init( style: . outlined( . blue, 0.5 ) ) )
161- WaveformView ( audioURL: audioURL, configuration: . init( style: . gradient( [ . yellow, . orange] ) ) )
162- WaveformView ( audioURL: audioURL, configuration: . init( style: . gradientOutlined( [ . yellow, . orange] , 1 ) ) )
163- WaveformView ( audioURL: audioURL, configuration: . init( style: . striped( . init( color: . red, width: 2 , spacing: 1 ) ) ) )
164-
165- // Multi-channel examples
166- WaveformView ( audioURL: audioURL, configuration: . init( style: . filled( . blue) ) , renderer: LinearWaveformRenderer ( channelSelection: . specific( 0 ) ) )
167- WaveformView ( audioURL: audioURL, configuration: . init( style: . filled( . red) ) , renderer: LinearWaveformRenderer ( channelSelection: . specific( 1 ) ) )
168- WaveformView ( audioURL: audioURL, configuration: . init( style: . gradient( [ . blue, . cyan] ) ) , renderer: LinearWaveformRenderer . stereo)
169-
170- WaveformView ( audioURL: audioURL, configuration: . init( style: . striped( . init( color: . black) ) ) ) { shape in
171- shape // override the shape styling
172- . stroke ( LinearGradient ( colors: [ . blue, . pink] , startPoint: . bottom, endPoint: . top) , lineWidth: 3 )
173- } placeholder: {
174- ProgressView ( )
175- }
176- }
177-
178- VStack {
179- WaveformView ( audioURL: audioURL, configuration: . init( style: . filled( . red) ) , renderer: CircularWaveformRenderer ( ) )
180- WaveformView ( audioURL: audioURL, configuration: . init( style: . outlined( . blue, 0.5 ) ) , renderer: CircularWaveformRenderer ( ) )
181- WaveformView ( audioURL: audioURL, configuration: . init( style: . gradient( [ . yellow, . orange] ) ) , renderer: CircularWaveformRenderer ( ) )
182- WaveformView ( audioURL: audioURL, configuration: . init( style: . gradientOutlined( [ . yellow, . orange] , 1 ) ) , renderer: CircularWaveformRenderer ( ) )
183- WaveformView ( audioURL: audioURL, configuration: . init( style: . striped( . init( color: . red, width: 2 , spacing: 2 ) ) ) , renderer: CircularWaveformRenderer ( ) )
184-
185- WaveformView ( audioURL: audioURL, configuration: . init( style: . striped( . init( color: . black) ) ) , renderer: CircularWaveformRenderer ( ) ) { shape in
186- shape // override the shape styling
187- . stroke ( LinearGradient ( colors: [ . blue, . pink] , startPoint: . bottom, endPoint: . top) , lineWidth: 3 )
188- } placeholder: {
189- ProgressView ( )
190- }
191- }
192-
193- VStack {
194- WaveformView ( audioURL: audioURL, configuration: . init( style: . filled( . red) ) , renderer: CircularWaveformRenderer ( kind: . ring( 0.5 ) ) )
195- WaveformView ( audioURL: audioURL, configuration: . init( style: . outlined( . blue, 0.5 ) ) , renderer: CircularWaveformRenderer ( kind: . ring( 0.5 ) ) )
196- WaveformView ( audioURL: audioURL, configuration: . init( style: . gradient( [ . yellow, . orange] ) ) , renderer: CircularWaveformRenderer ( kind: . ring( 0.5 ) ) )
197- WaveformView ( audioURL: audioURL, configuration: . init( style: . gradientOutlined( [ . yellow, . orange] , 1 ) ) , renderer: CircularWaveformRenderer ( kind: . ring( 0.5 ) ) )
198- WaveformView ( audioURL: audioURL, configuration: . init( style: . striped( . init( color: . red, width: 2 , spacing: 2 ) ) ) , renderer: CircularWaveformRenderer ( kind: . ring( 0.5 ) ) )
199-
200- WaveformView ( audioURL: audioURL, configuration: . init( style: . striped( . init( color: . black) ) ) , renderer: CircularWaveformRenderer ( kind: . ring( 0.5 ) ) ) { shape in
201- shape // override the shape styling
202- . stroke ( LinearGradient ( colors: [ . blue, . pink] , startPoint: . bottom, endPoint: . top) , lineWidth: 3 )
203- } placeholder: {
204- ProgressView ( )
205- }
206- }
207- }
72+ Spacer ( minLength: 0 )
20873 }
20974 }
21075}
@@ -229,9 +94,7 @@ private class AudioRecorder: NSObject, ObservableObject, RecordingDelegate {
22994
23095 override init ( ) {
23196 audioManager = SCAudioManager ( )
232-
23397 super. init ( )
234-
23598 audioManager. prepareAudioRecording ( )
23699 audioManager. recordingDelegate = self
237100 }
@@ -250,16 +113,13 @@ private class AudioRecorder: NSObject, ObservableObject, RecordingDelegate {
250113 // MARK: - RecordingDelegate
251114
252115 func audioManager( _ manager: SCAudioManager ! , didAllowRecording flag: Bool ) { }
253-
254116 func audioManager( _ manager: SCAudioManager ! , didFinishRecordingSuccessfully flag: Bool ) { }
255117
256118 func audioManager( _ manager: SCAudioManager ! , didUpdateRecordProgress progress: CGFloat ) {
257119 let linear = 1 - pow( 10 , manager. lastAveragePower ( ) / 20 )
258-
259120 // Here we add the same sample 3 times to speed up the animation.
260121 // Usually you'd just add the sample once.
261122 recordingTime = audioManager. currentRecordingTime
262123 samples += [ linear, linear, linear]
263124 }
264125}
265-
0 commit comments