@@ -14,30 +14,93 @@ struct SwiftUIFDWaveformView: UIViewRepresentable {
1414 @Binding var progress : Int ?
1515 @Binding var wavesColor : Color ?
1616 @Binding var backgroundColor : Color ?
17- let delegate = FDWDelegate ( )
17+ @Binding var zoomToMiddle50Percent : Bool
18+ @Binding var isLogarithmic : Bool
19+ var zoomChangeCounter : Int
20+ var onTotalSamplesChanged : ( ( Int ) -> Void ) ?
21+ var onRenderComplete : ( ( ) -> Void ) ?
22+
23+ func makeCoordinator( ) -> Coordinator {
24+ Coordinator ( onTotalSamplesChanged: onTotalSamplesChanged, onRenderComplete: onRenderComplete)
25+ }
1826
1927 func makeUIView( context: Context ) -> FDWaveformView {
2028 let waveformView = FDWaveformView ( )
21- // TODO, this delegate is not actually connected
22- waveformView. delegate = delegate
29+ waveformView. delegate = context. coordinator
2330 return waveformView
2431 }
2532
2633 func updateUIView( _ uiView: FDWaveformView , context: Context ) {
27- uiView. audioURL = audioURL
34+ // Only update audioURL if it actually changed to avoid re-triggering load
35+ if uiView. audioURL != audioURL {
36+ uiView. audioURL = audioURL
37+ }
2838 uiView. doesAllowScrubbing = canScrub
2939 uiView. doesAllowStretch = canStretch
3040 uiView. doesAllowScroll = canScroll
3141 if let progress {
3242 uiView. highlightedSamples = 0 ..< progress
43+ } else {
44+ uiView. highlightedSamples = nil
3345 }
3446 if let wavesColor {
3547 uiView. wavesColor = UIColor ( wavesColor)
3648 }
3749 if let backgroundColor {
3850 uiView. backgroundColor = UIColor ( backgroundColor)
3951 }
40- // Add other property updates if needed
52+
53+ // Handle zoom - only apply when button is pressed (counter changed)
54+ // Don't reset zoom when other properties change
55+ let totalSamples = uiView. totalSamples
56+ if totalSamples > 0 && zoomChangeCounter != context. coordinator. lastZoomChangeCounter {
57+ context. coordinator. lastZoomChangeCounter = zoomChangeCounter
58+ if zoomToMiddle50Percent {
59+ let quarter = totalSamples / 4
60+ uiView. zoomSamples = quarter..< ( totalSamples - quarter)
61+ } else {
62+ uiView. zoomSamples = 0 ..< totalSamples
63+ }
64+ }
65+
66+ // Handle waveform type
67+ uiView. waveformType = isLogarithmic ? . logarithmic : . linear
68+ }
69+
70+ class Coordinator : NSObject , FDWaveformViewDelegate {
71+ var onTotalSamplesChanged : ( ( Int ) -> Void ) ?
72+ var onRenderComplete : ( ( ) -> Void ) ?
73+ var lastZoomChangeCounter = 0
74+ private var startRendering = Date ( )
75+ private var startLoading = Date ( )
76+
77+ init ( onTotalSamplesChanged: ( ( Int ) -> Void ) ? , onRenderComplete: ( ( ) -> Void ) ? ) {
78+ self . onTotalSamplesChanged = onTotalSamplesChanged
79+ self . onRenderComplete = onRenderComplete
80+ }
81+
82+ func waveformViewWillRender( _ waveformView: FDWaveformView ) {
83+ startRendering = Date ( )
84+ }
85+
86+ func waveformViewDidRender( _ waveformView: FDWaveformView ) {
87+ let elapsed = Date ( ) . timeIntervalSince ( startRendering)
88+ NSLog ( " FDWaveformView rendering done, took %0.3f seconds " , elapsed)
89+ onRenderComplete ? ( )
90+ UIView . animate ( withDuration: 0.25 ) {
91+ waveformView. alpha = 1.0
92+ }
93+ }
94+
95+ func waveformViewWillLoad( _ waveformView: FDWaveformView ) {
96+ startLoading = Date ( )
97+ }
98+
99+ func waveformViewDidLoad( _ waveformView: FDWaveformView ) {
100+ let elapsed = Date ( ) . timeIntervalSince ( startLoading)
101+ NSLog ( " FDWaveformView loading done, took %0.3f seconds " , elapsed)
102+ onTotalSamplesChanged ? ( waveformView. totalSamples)
103+ }
41104 }
42105}
43106
@@ -49,22 +112,30 @@ struct ContentView: View {
49112 @State private var progress : Int ?
50113 @State private var wavesColor : Color ?
51114 @State private var backgroundColor : Color ?
115+ @State private var isLogarithmic = true
116+ @State private var totalSamples : Int = 0
117+ @State private var zoomToMiddle50Percent = false
118+ @State private var zoomChangeCounter = 0
119+ @State private var profilingMessage : String ?
120+ @State private var isShowingProfilingAlert = false
121+ @State private var profilingStartTime : Date ?
122+ @State private var aacRenderTime : TimeInterval ?
52123
53124 var body : some View {
54125 NavigationView {
55126 VStack {
56127 HStack {
57128 Button ( " Load AAC " ) {
58129 audioURL = nil
59- if let url = Bundle . main. url ( forResource: " TchaikovskyExample2 " , withExtension: " m4a " ) {
130+ if let url = Bundle . main. url ( forResource: " Submarine " , withExtension: " aiff " ) {
60131 audioURL = url
61132 }
62133 }
63134 . buttonStyle ( . borderedProminent)
64135
65136 Button ( " Load MP3 " ) {
66137 audioURL = nil
67- if let url = Bundle . main. url ( forResource: " TchaikovskyExample2 " , withExtension: " mp3 " ) {
138+ if let url = Bundle . main. url ( forResource: " Submarine " , withExtension: " aiff " ) {
68139 audioURL = url
69140 }
70141 }
@@ -73,82 +144,57 @@ struct ContentView: View {
73144
74145 HStack {
75146 Button ( " Randomize Progress " ) {
76- // TODO: fix this
77- // how to load the samples count?
78- progress = 4000
147+ if totalSamples > 0 {
148+ progress = Int . random ( in : 0 ..< totalSamples )
149+ }
79150 }
80151 . buttonStyle ( . bordered)
81152
82153 Button ( " Randomize Colors " ) {
83- // XKCD #221
84- wavesColor = . red
85- backgroundColor = . blue
154+ wavesColor = Color (
155+ red: Double . random ( in: 0 ... 1 ) ,
156+ green: Double . random ( in: 0 ... 1 ) ,
157+ blue: Double . random ( in: 0 ... 1 )
158+ )
159+ backgroundColor = Color (
160+ red: Double . random ( in: 0 ... 1 ) ,
161+ green: Double . random ( in: 0 ... 1 ) ,
162+ blue: Double . random ( in: 0 ... 1 )
163+ )
86164 }
87165 . buttonStyle ( . bordered)
88166 }
89167
90168 HStack {
91169 Button ( " Zoom In " ) {
92- // TODO: fix this
170+ zoomToMiddle50Percent = true
171+ zoomChangeCounter += 1
93172 }
94173 . buttonStyle ( . bordered)
95174
96175 Button ( " Zoom Out " ) {
97- // TODO: fix this
176+ zoomToMiddle50Percent = false
177+ zoomChangeCounter += 1
98178 }
99179 . buttonStyle ( . bordered)
100180 }
101181
102- HStack {
103- Button ( " Logarithmic " ) {
104- // TODO: fix this
105- }
106- . buttonStyle ( . bordered)
107-
108- Button ( " Linear " ) {
109- // TODO: fiw this
110- }
111- . buttonStyle ( . bordered)
182+ Picker ( " Waveform type " , selection: $isLogarithmic) {
183+ Text ( " Linear " ) . tag ( false )
184+ Text ( " Logarithmic " ) . tag ( true )
112185 }
186+ . pickerStyle ( . segmented)
187+ . padding ( )
113188
114189 Button ( " Run Performance Profiling " ) {
115- /*
116- NSLog("RUNNING PERFORMANCE PROFILE")
117-
118- let alert = UIAlertController(title: "Start Profiling", message:"Profiling will begin, please don't touch anything. This will take less than 30 seconds.", preferredStyle: .alert)
119- let action = UIAlertAction(title: "OK", style: .default)
120- alert.addAction(action)
121- present(alert, animated: true)
122-
123- self.profileResult = ""
124- // Delay execution of my block for 1 seconds.
125- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(1) * Int64(NSEC_PER_SEC)) / Double(NSEC_PER_SEC), execute: {
126- self.profileResult.append("AAC:")
127- self.doLoadAAC()
128- })
129- // Delay execution of my block for 5 seconds.
130- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(5) * Int64(NSEC_PER_SEC)) / Double(NSEC_PER_SEC), execute: {
131- self.profileResult.append(" MP3:")
132- self.doLoadMP3()
133- })
134- /*
135- // Delay execution of my block for 9 seconds.
136- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(9) * Int64(NSEC_PER_SEC)) / Double(NSEC_PER_SEC), execute: {
137- self.profileResult.append(" OGG:")
138- self.doLoadOGG()
139- })
140- */
141- // Delay execution of my block for 9 seconds.
142- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(9) * Int64(NSEC_PER_SEC)) / Double(NSEC_PER_SEC), execute: {
143- let alert = UIAlertController(title: "PLEASE POST TO github.com/fulldecent/FDWaveformView/wiki", message: self.profileResult, preferredStyle: .alert)
144- let action = UIAlertAction(title: "OK", style: .default)
145- alert.addAction(action)
146- self.present(alert, animated: true)
147- })
148-
149- */
190+ runPerformanceProfiling ( )
150191 }
151192 . buttonStyle ( . borderedProminent)
193+ . alert ( " Performance results " , isPresented: $isShowingProfilingAlert) {
194+ Button ( " OK " , role: . cancel) { }
195+ } message: {
196+ Text ( profilingMessage ?? " " )
197+ }
152198
153199 HStack {
154200 Toggle ( " Scrub " , isOn: $isScrubActive)
@@ -164,7 +210,16 @@ struct ContentView: View {
164210 canScroll: $isScrollActive,
165211 progress: $progress,
166212 wavesColor: $wavesColor,
167- backgroundColor: $backgroundColor
213+ backgroundColor: $backgroundColor,
214+ zoomToMiddle50Percent: $zoomToMiddle50Percent,
215+ isLogarithmic: $isLogarithmic,
216+ zoomChangeCounter: zoomChangeCounter,
217+ onTotalSamplesChanged: { samples in
218+ totalSamples = samples
219+ } ,
220+ onRenderComplete: {
221+ handleRenderComplete ( )
222+ }
168223 )
169224 . onAppear {
170225 if let url = Bundle . main. url ( forResource: " Submarine " , withExtension: " aiff " ) {
@@ -176,45 +231,49 @@ struct ContentView: View {
176231 . padding ( )
177232 }
178233 }
179- }
180-
181- #Preview {
182- ContentView ( )
183- }
184234
185- class FDWDelegate : NSObject , FDWaveformViewDelegate {
186- fileprivate var startRendering = Date ( )
187- fileprivate var endRendering = Date ( )
188- fileprivate var startLoading = Date ( )
189- fileprivate var endLoading = Date ( )
190- fileprivate var profileResult = " "
235+ private func runPerformanceProfiling( ) {
236+ profilingStartTime = Date ( )
237+ aacRenderTime = nil
191238
192- func waveformViewWillRender( _ waveformView: FDWaveformView ) {
193- startRendering = Date ( )
239+ // Load first file (using Submarine.aiff as AAC equivalent)
240+ audioURL = nil
241+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 ) {
242+ if let url = Bundle . main. url ( forResource: " Submarine " , withExtension: " aiff " ) {
243+ audioURL = url
244+ }
245+ }
194246 }
195247
196- func waveformViewDidRender( _ waveformView: FDWaveformView ) {
197- endRendering = Date ( )
198- NSLog (
199- " FDWaveformView rendering done, took %0.3f seconds " ,
200- endRendering. timeIntervalSince ( startRendering) )
201- profileResult. append (
202- String ( format: " render %0.3f " , endRendering. timeIntervalSince ( startRendering) ) )
203- UIView . animate (
204- withDuration: 0.25 ,
205- animations: { ( ) -> Void in
206- waveformView. alpha = 1.0
207- } )
208- }
248+ private func handleRenderComplete( ) {
249+ guard let startTime = profilingStartTime else { return }
209250
210- func waveformViewWillLoad ( _ waveformView : FDWaveformView ) {
211- startLoading = Date ( )
212- }
251+ if aacRenderTime == nil {
252+ // First render complete (AAC )
253+ aacRenderTime = Date ( ) . timeIntervalSince ( startTime )
213254
214- func waveformViewDidLoad( _ waveformView: FDWaveformView ) {
215- endLoading = Date ( )
216- NSLog (
217- " FDWaveformView loading done, took %0.3f seconds " , endLoading. timeIntervalSince ( startLoading) )
218- profileResult. append ( String ( format: " load %0.3f " , endLoading. timeIntervalSince ( startLoading) ) )
255+ // Load second file
256+ audioURL = nil
257+ profilingStartTime = Date ( )
258+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 ) {
259+ if let url = Bundle . main. url ( forResource: " Submarine " , withExtension: " aiff " ) {
260+ audioURL = url
261+ }
262+ }
263+ } else {
264+ // Second render complete (MP3)
265+ let mp3RenderTime = Date ( ) . timeIntervalSince ( startTime)
266+ profilingMessage = String (
267+ format: " AAC: %.3f seconds \n MP3: %.3f seconds " ,
268+ aacRenderTime ?? 0 ,
269+ mp3RenderTime
270+ )
271+ isShowingProfilingAlert = true
272+ profilingStartTime = nil
273+ }
219274 }
220275}
276+
277+ #Preview {
278+ ContentView ( )
279+ }
0 commit comments