44 |
45 |
46 | Installation
47 | ------------
48 |
49 | * use SPM: add `https://github.com/dmrschmidt/DSWaveformImage` and set "Up to Next Major" with "14.0.0"
50 |
51 | ```swift
52 | import DSWaveformImage // for core classes to generate `UIImage` / `NSImage` directly
53 | import DSWaveformImageViews // if you want to use the native UIKit / SwiftUI views
54 | ```
55 |
56 | Usage
57 | -----
58 |
59 | `DSWaveformImage` provides 3 kinds of tools to use
60 | * native SwiftUI views - [SwiftUI example usage code](Example/DSWaveformImageExample-iOS/SwiftUIExample/SwiftUIExampleView.swift)
61 | * native UIKit views - [UIKit example usage code](Example/DSWaveformImageExample-iOS/ViewController.swift)
62 | * access to the raw renderes and processors
63 |
64 | The core renderes and processors as well as SwiftUI views natively support iOS & macOS, using `UIImage` & `NSImage` respectively.
65 |
66 | ### SwiftUI
67 |
68 | #### `WaveformView` - renders a one-off waveform from an audio file:
69 |
70 | ```swift
71 | @State var audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
72 | WaveformView(audioURL: audioURL)
73 | ```
74 |
75 | Default styling may be overridden if you have more complex requirements:
76 |
77 | ```swift
78 | @State var audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
79 | WaveformView(audioURL: audioURL) { waveformShape in
80 | waveformShape
81 | .stroke(LinearGradient(colors: [.red, [.green, red, orange], startPoint: .zero, endPoint: .topTrailing), lineWidth: 3)
82 | }
83 | ```
84 |
85 | Similar to [AsyncImage](https://developer.apple.com/documentation/swiftui/asyncimage/init(url:scale:content:placeholder:)), a placeholder can be
86 | set to show until the load and render operation completes successfully. Thanks to [@alfogrillo](https://github.com/alfogrillo)!
87 |
88 | ```swift
89 | WaveformView(audioURL: audioURL) { waveformShape in
90 | waveformShape
91 | .stroke(LinearGradient(colors: [.red, [.green, red, orange], startPoint: .zero, endPoint: .topTrailing), lineWidth: 3)
92 | } placeholder: {
93 | ProgressView()
94 | }
95 | ```
96 |
97 | #### `WaveformLiveCanvas` - renders a live waveform from `(0...1)` normalized samples:
98 |
99 | ```swift
100 | @StateObject private var audioRecorder: AudioRecorder = AudioRecorder() // just an example
101 | WaveformLiveCanvas(samples: audioRecorder.samples)
102 | ```
103 |
104 | ### UIKit
105 |
106 | #### `WaveformImageView` - renders a one-off waveform from an audio file:
107 |
108 | ```swift
109 | let audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
110 | waveformImageView = WaveformImageView(frame: CGRect(x: 0, y: 0, width: 500, height: 300)
111 | waveformImageView.waveformAudioURL = audioURL
112 | ```
113 |
114 | #### `WaveformLiveView` - renders a live waveform from `(0...1)` normalized samples:
115 |
116 | Find a full example in the [sample project's RecordingViewController](Example/DSWaveformImageExample-iOS/RecordingViewController.swift).
117 |
118 | ```swift
119 | let waveformView = WaveformLiveView()
120 |
121 | // configure and start AVAudioRecorder
122 | let recorder = AVAudioRecorder()
123 | recorder.isMeteringEnabled = true // required to get current power levels
124 |
125 | // after all the other recording (omitted for focus) setup, periodically (every 20ms or so):
126 | recorder.updateMeters() // gets the current value
127 | let currentAmplitude = 1 - pow(10, recorder.averagePower(forChannel: 0) / 20)
128 | waveformView.add(sample: currentAmplitude)
129 | ```
130 |
131 | ### Raw API
132 |
133 | #### Configuration
134 |
135 | *Note:* Calculations are always performed and returned on a background thread, so make sure to return to the main thread before doing any UI work.
136 |
137 | Check `Waveform.Configuration` in [WaveformImageTypes](./Sources/DSWaveformImage/WaveformImageTypes.swift) for various configuration options.
138 |
139 | #### `WaveformImageDrawer` - creates a `UIImage` waveform from an audio file:
140 |
141 | ```swift
142 | let waveformImageDrawer = WaveformImageDrawer()
143 | let audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
144 | let image = try await waveformImageDrawer.waveformImage(
145 | fromAudioAt: audioURL,
146 | with: .init(size: topWaveformView.bounds.size, style: .filled(UIColor.black)),
147 | renderer: LinearWaveformRenderer()
148 | )
149 |
150 | // need to jump back to main queue
151 | DispatchQueue.main.async {
152 | self.topWaveformView.image = image
153 | }
154 | ```
155 |
156 | #### `WaveformAnalyzer` - calculates an audio file's waveform sample:
157 |
158 | ```swift
159 | let audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
160 | waveformAnalyzer = WaveformAnalyzer()
161 | let samples = try await waveformAnalyzer.samples(fromAudioAt: audioURL, count: 200)
162 | print("samples: \(samples)")
163 | ```
164 |
165 | ### Playback Progress Indication
166 |
167 | If you're playing back audio files and would like to indicate the playback progress to your users, you can [find inspiration in the example app](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Example/DSWaveformImageExample-iOS/ProgressViewController.swift). UIKit and [SwiftUI](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Example/DSWaveformImageExample-iOS/SwiftUIExample/ProgressWaveformView.swift) examples are provided.
168 |
169 | Both approaches will result in something like the image below.
170 |
171 |
172 |
173 |
174 |
175 |
176 | There is currently no plan to integrate this as a 1st class citizen to the library itself, as every app will have different design requirements, and `WaveformImageDrawer` as well as `WaveformAnalyzer` are as simple to use as the views themselves as you can see in the examples.
177 |
178 | ### Loading remote audio files from URL
179 |
180 | For one example way to display waveforms for audio files on remote URLs see https://github.com/dmrschmidt/DSWaveformImage/issues/22.
181 |
182 | What it looks like
183 | ------------------
184 |
185 | Waveforms can be rendered in 2 different ways and 5 different styles each.
186 |
187 | By default [`LinearWaveformRenderer`](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Sources/DSWaveformImage/Renderers/LinearWaveformRenderer.swift) is used, which draws a linear 2D amplitude envelope.
188 |
189 | [`CircularWaveformRenderer`](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Sources/DSWaveformImage/Renderers/CircularWaveformRenderer.swift) is available as an alternative, which can be passed in to the `WaveformView` or `WaveformLiveView` respectively. It draws a circular
190 | 2D amplitude envelope.
191 |
192 | You can implement your own renderer by implementing [`WaveformRenderer`](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Sources/DSWaveformImage/Renderers/WaveformRenderer.swift).
193 |
194 | The following styles can be applied to either renderer:
195 | - **filled**: Use solid color for the waveform.
196 | - **outlined**: Draws the envelope as an outline with the provided thickness.
197 | - **gradient**: Use gradient based on color for the waveform.
198 | - **gradientOutlined**: Use gradient based on color for the waveform. Draws the envelope as an outline with the provided thickness.
199 | - **striped**: Use striped filling based on color for the waveform.
200 |
201 |
202 |
203 |
204 |
205 |
206 | ### Live waveform rendering
207 | https://user-images.githubusercontent.com/69365/127739821-061a4345-0adc-4cc1-bfd6-f7cfbe1268c9.mov
208 |
209 |
210 | Migration
211 | ---------
212 | ### In 14.0.0
213 | * Minimum iOS Deployment target is 15.0, macOS is 12.0 to remove internal usage of deprecated APIs
214 | * `WaveformAnalyzer` and `WaveformImageDrawer` now return `Result<[Float] | DSImage, Error>` when used with completionHandler for better error handling
215 | * `WaveformAnalyzer` is now stateless and requires the URL in `.samples(fromAudioAt:count:qos:)` instead of its constructor
216 | * SwiftUI's `WaveformView` has a new constructor that provides optional access to the underlying `WaveformShape`, which is now used for rendering, see [#78](https://github.com/dmrschmidt/DSWaveformImage/issues/78)
217 |
218 | ### In 13.0.0
219 | * Any mentions of `dampening` & similar were corrected to `damping` etc in [11460b8b](https://github.com/dmrschmidt/DSWaveformImage/commit/11460b8b8203f163868ba774d1533116d2fe68a1). Most notably in `Waveform.Configuration`. See [#64](https://github.com/dmrschmidt/DSWaveformImage/issues/64).
220 | * styles `.outlined` & `.gradientOutlined` were added to `Waveform.Style`, see https://github.com/dmrschmidt/DSWaveformImage#what-it-looks-like
221 | * `Waveform.Position` was removed. If you were using it to place the view somewhere, move this responsibility up to its parent for positioning, like with any other view as well.
222 |
223 | ### In 12.0.0
224 | * The rendering pipeline was split out from the analysis. You can now create your own renderes by subclassing [`WaveformRenderer`](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Sources/DSWaveformImage/Renderers/WaveformRenderer.swift).
225 | * A new [`CircularWaveformRenderer`](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Sources/DSWaveformImage/Renderers/CircularWaveformRenderer.swift) has been added.
226 | * `position` was removed from `Waveform.Configuration`, see [0447737](https://github.com/dmrschmidt/DSWaveformImage/commit/044773782092becec0424527f6feef061988db7a).
227 | * new `Waveform.Style` option have been added and need to be accounted for in `switch` statements etc.
228 |
229 | ### In 11.0.0
230 | the library was split into two: `DSWaveformImage` and `DSWaveformImageViews`. If you've used any of the native views bevore, just add the additional `import DSWaveformImageViews`.
231 | The SwiftUI views have changed from taking a Binding to the respective plain values instead.
232 |
233 | ### In 9.0.0
234 | a few public API's have been slightly changed to be more concise. All types have also been grouped under the `Waveform` enum-namespace. Meaning `WaveformConfiguration` for instance has become `Waveform.Configuration` and so on.
235 |
236 | ### In 7.0.0
237 | colors have moved into associated values on the respective `style` enum.
238 |
239 | `Waveform` and the `UIImage` category have been removed in 6.0.0 to simplify the API.
240 | See `Usage` for current usage.
241 |
242 | ## See it live in action
243 |
244 | [SoundCard - postcards with sound](https://www.soundcard.io) lets you send real, physical postcards with audio messages. Right from your iOS device.
245 |
246 | DSWaveformImage is used to draw the waveforms of the audio messages that get printed on the postcards sent by [SoundCard - postcards with audio](https://www.soundcard.io).
247 |
248 |
249 |
250 |