├── wled-mic
├── Assets.xcassets
│ ├── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── wled_micApp.swift
└── ContentView.swift
├── wled-mic.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcuserdata
│ └── altryne.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── project.pbxproj
├── wled-micTests
└── wled_micTests.swift
├── wled-micUITests
├── wled_micUITestsLaunchTests.swift
└── wled_micUITests.swift
└── README.MD
/wled-mic/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/wled-mic.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/wled-mic/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/wled-mic/wled_micApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // wled_micApp.swift
3 | // wled-mic
4 | //
5 | // Created by Alex Volkov on 8/20/25.
6 | //
7 | //
8 | //import SwiftUI
9 | //
10 | //@main
11 | //struct wled_micApp: App {
12 | // var body: some Scene {
13 | // WindowGroup {
14 | // ContentView()
15 | // }
16 | // }
17 | //}
18 |
--------------------------------------------------------------------------------
/wled-micTests/wled_micTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // wled_micTests.swift
3 | // wled-micTests
4 | //
5 | // Created by Alex Volkov on 8/20/25.
6 | //
7 |
8 | import Testing
9 | @testable import wled_mic
10 |
11 | struct wled_micTests {
12 |
13 | @Test func example() async throws {
14 | // Write your test here and use APIs like `#expect(...)` to check expected conditions.
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/wled-mic.xcodeproj/xcuserdata/altryne.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | wled-mic.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/wled-mic/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "idiom" : "universal",
16 | "platform" : "ios",
17 | "size" : "1024x1024"
18 | },
19 | {
20 | "appearances" : [
21 | {
22 | "appearance" : "luminosity",
23 | "value" : "tinted"
24 | }
25 | ],
26 | "idiom" : "universal",
27 | "platform" : "ios",
28 | "size" : "1024x1024"
29 | }
30 | ],
31 | "info" : {
32 | "author" : "xcode",
33 | "version" : 1
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/wled-micUITests/wled_micUITestsLaunchTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // wled_micUITestsLaunchTests.swift
3 | // wled-micUITests
4 | //
5 | // Created by Alex Volkov on 8/20/25.
6 | //
7 |
8 | import XCTest
9 |
10 | final class wled_micUITestsLaunchTests: XCTestCase {
11 |
12 | override class var runsForEachTargetApplicationUIConfiguration: Bool {
13 | true
14 | }
15 |
16 | override func setUpWithError() throws {
17 | continueAfterFailure = false
18 | }
19 |
20 | @MainActor
21 | func testLaunch() throws {
22 | let app = XCUIApplication()
23 | app.launch()
24 |
25 | // Insert steps here to perform after app launch but before taking a screenshot,
26 | // such as logging into a test account or navigating somewhere in the app
27 |
28 | let attachment = XCTAttachment(screenshot: app.screenshot())
29 | attachment.name = "Launch Screen"
30 | attachment.lifetime = .keepAlways
31 | add(attachment)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/wled-micUITests/wled_micUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // wled_micUITests.swift
3 | // wled-micUITests
4 | //
5 | // Created by Alex Volkov on 8/20/25.
6 | //
7 |
8 | import XCTest
9 |
10 | final class wled_micUITests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 |
15 | // In UI tests it is usually best to stop immediately when a failure occurs.
16 | continueAfterFailure = false
17 |
18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
19 | }
20 |
21 | override func tearDownWithError() throws {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | }
24 |
25 | @MainActor
26 | func testExample() throws {
27 | // UI tests must launch the application that they test.
28 | let app = XCUIApplication()
29 | app.launch()
30 |
31 | // Use XCTAssert and related functions to verify your tests produce the correct results.
32 | }
33 |
34 | @MainActor
35 | func testLaunchPerformance() throws {
36 | // This measures how long it takes to launch your application.
37 | measure(metrics: [XCTApplicationLaunchMetric()]) {
38 | XCUIApplication().launch()
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # 🎶 Feed My WLED — iOS Sound Reactive Mic App
2 |
3 | This project is an **iOS companion app** that turns your iPhone into a microphone source for **WLED SoundReactive**, sending **Audio Sync v2 UDP packets** directly to one or more WLED devices on your network.
4 |
5 | It’s especially useful for **ESP8266 devices** 🕹️ — since those don’t have official sound-reactive support in the main WLED builds. With this app, your phone does the heavy lifting (FFT, AGC, noise gate) and streams to the ESP, letting you still enjoy sound-reactive effects on hardware that can’t handle onboard DSP.
6 |
7 | ---
8 |
9 | ## 🔗 Related Projects
10 |
11 | - **WLED SoundReactive (WLED-mm fork):** [https://mm.kno.wled.ge/](https://mm.kno.wled.ge/)
12 | This is the modified WLED build that understands Audio Sync packets. You can flash it directly to your ESP8266 or ESP32 devices using the [WLED Web Flasher](https://install.wled.me/).
13 |
14 | ---
15 |
16 | ## 🚲 Example Use Case
17 |
18 | Perfect for portable setups like:
19 | - A bike with WLED strips (ESP8266/ESP32)
20 | - A hat or costume with LEDs
21 | - A travel router + USB power bank
22 |
23 | Run this app on your iPhone, and your lights pulse to the beat of your music 🎵 — no laptop required.
24 |
25 | ---
26 |
27 | ## 📱 iOS App Setup (Xcode)
28 |
29 | 1. **Clone / open project** in Xcode.
30 | 2. **Add permissions in Info.plist**:
31 | - `NSMicrophoneUsageDescription` → “Mic drives WLED sound-reactive effects.”
32 | - `NSLocalNetworkUsageDescription` → “Discover and send audio sync to WLED devices on the local network.”
33 | - `NSBonjourServices` (Array) → `_wled._tcp`, `_http._tcp`
34 | - `NSAppTransportSecurity > NSAllowsArbitraryLoadsInLocalNetworking` = YES
35 | 3. **Enable Background Audio**:
36 | - Target → Signing & Capabilities → `+ Capability` → **Background Modes**
37 | - Check **Audio, AirPlay, and Picture in Picture**
38 | 4. **Run on a real iPhone** (discovery doesn’t work in Simulator).
39 | 5. **Controls in the app**:
40 | - Modes: `Single` (direct IP), `Multicast` (239.0.0.1:11988), `Unicast` (fan-out to multiple devices)
41 | - **Discover**: uses Bonjour to find WLEDs (`_wled._tcp` / `_http._tcp`) — still experimental 🚧
42 | - **Add IP**: manually add devices if discovery doesn’t work on your network
43 | - **Noise Gate**: stops sending packets when silence is detected, so LEDs go completely dark
44 | - **Ping**: checks latency and alive status of devices
45 | - **Test Packet**: send a synthetic audio frame to verify your WLED reacts
46 |
47 | ---
48 |
49 | ## ⚙️ WLED Device Setup
50 |
51 | 1. Flash WLED-mm (SoundReactive build) using [the flasher](https://install.wled.me/).
52 | 2. In WLED:
53 | - **Settings → Usermods → Audio Reactive** → Enable
54 | - **Settings → Sync → Port = 11988, Mode = Receive**
55 | - For “relay” setups: check **Sync audio data with other WLEDs**
56 | 3. Choose a **sound-reactive effect** (e.g. Gravcenter, Gravfreq, Juggles).
57 | - Effects like GEQ always draw, but with the noise gate enabled in the app, you’ll see true silence.
58 |
59 | ---
60 |
61 | ## ⚡ Current Status
62 |
63 | - ✅ UDP v2 packet streaming (AGC, FFT, noise gate)
64 | - ✅ Multicast & Unicast modes
65 | - ✅ Bonjour discovery (under development — may not work on all routers yet)
66 | - ✅ Manual IP entry + Ping
67 | - ✅ Background audio (phone can stay locked)
68 | - 🚧 Discovery stability still being improved
69 |
70 | ---
71 |
72 | ## 👤 Author
73 |
74 | Made by [Alex Volkov](https://x.com/altryne) ([@altryne on X).
75 |
76 | ---
77 |
78 | ## 📜 License
79 |
80 | MIT
--------------------------------------------------------------------------------
/wled-mic/ContentView.swift:
--------------------------------------------------------------------------------
1 | // ContentView.swift
2 | import SwiftUI
3 | import AVFoundation
4 | #if canImport(AVFAudio)
5 | import AVFAudio
6 | #endif
7 | import Network
8 | import Accelerate
9 | import UIKit
10 |
11 | // MARK: - Simple model
12 | struct Target: Identifiable, Hashable {
13 | let id = UUID()
14 | var host: String // IP or .local hostname
15 | var port: UInt16 // default 11988
16 | }
17 |
18 | // MARK: - Audio + Sender
19 | final class WLEDSender: ObservableObject {
20 | // Targets
21 | @Published var targets: [Target] = [
22 | .init(host: "wled-mm.local", port: 11988)
23 | ]
24 |
25 | // UDP connections (one per target)
26 | private var conns: [UUID: NWConnection] = [:]
27 | private let queue = DispatchQueue(label: "wled.sender.queue")
28 |
29 | // Audio / DSP
30 | private let engine = AVAudioEngine()
31 | private let frameSize = 1024
32 | private var hann: [Float] = []
33 | private var bandCenters: [Float] = Array(repeating: 0, count: 16)
34 | private var sampleRate: Double = 44100
35 | private var binEMA: [Float] = Array(repeating: 0, count: 16)
36 | private var frameCounter: UInt8 = 0
37 | private var lastUIUpdate: CFTimeInterval = 0
38 |
39 | // AGC → 0..255
40 | private var agcGain: Float = 1.0
41 | private let agcTarget: Float = 128.0
42 | private let agcAttack: Float = 0.20
43 | private let agcDecay: Float = 0.02
44 | private let agcFloor: Float = 0.05
45 | private let agcCeil: Float = 50.0
46 |
47 | // Noise gate (hysteresis + hold)
48 | @Published var gateEnabled: Bool = true
49 | @Published var gateOpenThresh: Float = 22
50 | @Published var gateCloseThresh: Float = 12
51 | @Published var gateHoldMs: Int = 250
52 | private var gateOpen = false
53 | private var gateLastAboveMs: Double = 0
54 |
55 | // UI state
56 | @Published var isRunning = false
57 | @Published var statusText = "Idle"
58 | @Published var uiLevel: Float = 0
59 | @Published var uiBins: [Float] = Array(repeating: 0, count: 16)
60 |
61 | init() { buildHann() }
62 |
63 | // MARK: Target management
64 | func addTarget(host: String, port: UInt16 = 11988) {
65 | let t = Target(host: host.trimmingCharacters(in: .whitespacesAndNewlines), port: port)
66 | guard !t.host.isEmpty else { return }
67 | targets.append(t)
68 | openConn(for: t)
69 | }
70 |
71 | func removeTargets(at offsets: IndexSet) {
72 | for i in offsets {
73 | let id = targets[i].id
74 | conns[id]?.cancel()
75 | conns.removeValue(forKey: id)
76 | }
77 | targets.remove(atOffsets: offsets)
78 | }
79 |
80 | private func openConn(for t: Target) {
81 | guard let p = NWEndpoint.Port(rawValue: t.port) else { return }
82 | let c = NWConnection(host: NWEndpoint.Host(t.host), port: p, using: .udp)
83 | c.start(queue: queue)
84 | conns[t.id] = c
85 | }
86 |
87 | private func rebuildConns() {
88 | conns.values.forEach { $0.cancel() }
89 | conns.removeAll()
90 | targets.forEach { openConn(for: $0) }
91 | }
92 |
93 | // MARK: Control
94 | func start() {
95 | guard !isRunning else { return }
96 | #if canImport(AVFAudio)
97 | if #available(iOS 17, *) {
98 | AVAudioApplication.requestRecordPermission { [weak self] ok in
99 | DispatchQueue.main.async { ok ? self?.configureAndStart() : (self?.statusText = "Mic permission denied") }
100 | }
101 | return
102 | }
103 | #endif
104 | AVAudioSession.sharedInstance().requestRecordPermission { [weak self] ok in
105 | DispatchQueue.main.async { ok ? self?.configureAndStart() : (self?.statusText = "Mic permission denied") }
106 | }
107 | }
108 |
109 | func stop() {
110 | guard isRunning else { return }
111 | engine.inputNode.removeTap(onBus: 0)
112 | engine.stop()
113 | isRunning = false
114 | statusText = "Stopped"
115 | }
116 |
117 | func sendTestPacket() {
118 | let bins = (0..<16).map { i -> UInt8 in
119 | let t = Float(i) / 15.0
120 | return UInt8(round(255.0 * sin(t * .pi)))
121 | }
122 | frameCounter &+= 1
123 | let pkt = buildV2(sampleRaw: 128, sampleSmth: 128, samplePeak: false,
124 | frameCounter: frameCounter, fftBins: bins, zeroCrossingCount: 0,
125 | FFT_Magnitude: 1000, FFT_MajorPeak: 440)
126 | fanout(pkt)
127 | }
128 |
129 | // MARK: Setup
130 | private func configureAndStart() {
131 | do {
132 | let s = AVAudioSession.sharedInstance()
133 | try s.setCategory(.playAndRecord, options: [.mixWithOthers])
134 | try s.setMode(.measurement)
135 | try s.setActive(true)
136 | } catch {
137 | statusText = "AudioSession error: \(error.localizedDescription)"; return
138 | }
139 |
140 | rebuildConns()
141 |
142 | let input = engine.inputNode
143 | let format = input.inputFormat(forBus: 0)
144 | sampleRate = format.sampleRate
145 | buildHann(); buildBands()
146 |
147 | input.removeTap(onBus: 0)
148 | input.installTap(onBus: 0, bufferSize: AVAudioFrameCount(frameSize), format: format) { [weak self] buf, _ in
149 | self?.process(buffer: buf)
150 | }
151 |
152 | do {
153 | try engine.start()
154 | isRunning = true
155 | statusText = "Streaming to \(targets.count) device(s)…"
156 | } catch {
157 | statusText = "Engine error: \(error.localizedDescription)"
158 | }
159 | }
160 |
161 | // MARK: DSP
162 | private func buildHann() {
163 | var w = [Float](); w.reserveCapacity(frameSize)
164 | for i in 0.. Float in
175 | let t0 = Double(i)/16.0, t1 = Double(i+1)/16.0
176 | let lo = f0 * pow(f1/f0, t0), hi = f0 * pow(f1/f0, t1)
177 | return Float(sqrt(lo * hi))
178 | }
179 | }
180 |
181 | private func process(buffer: AVAudioPCMBuffer) {
182 | guard let ch = buffer.floatChannelData?.pointee else { return }
183 | let n = Int(buffer.frameLength); if n == 0 { return }
184 |
185 | var x = [Float](repeating: 0, count: frameSize)
186 | let count = min(n, frameSize)
187 | x.withUnsafeMutableBufferPointer { dst in dst.baseAddress!.update(from: ch, count: count) }
188 | vDSP.multiply(x, hann, result: &x)
189 |
190 | // RMS
191 | var sum: Float = 0; vDSP_svesq(x, 1, &sum, vDSP_Length(frameSize))
192 | let rms = sqrtf(sum / Float(frameSize))
193 |
194 | // AGC → 0..255
195 | let instRaw = min(255.0, max(0.0, rms * 255.0 * agcGain))
196 | let err = agcTarget - instRaw
197 | if err > 0 { agcGain = min(agcCeil, agcGain * (1.0 + agcAttack * err/255.0)) }
198 | else { agcGain = max(agcFloor, agcGain * (1.0 + agcDecay * err/255.0)) }
199 |
200 | // Gate
201 | var gateNow = gateOpen
202 | let nowMs = CACurrentMediaTime() * 1000.0
203 | if gateEnabled {
204 | if instRaw >= gateOpenThresh { gateNow = true; gateLastAboveMs = nowMs }
205 | else if gateNow, instRaw < gateCloseThresh, (nowMs - gateLastAboveMs) > Double(gateHoldMs) { gateNow = false }
206 | } else { gateNow = true }
207 | gateOpen = gateNow
208 |
209 | // Peak
210 | // (simple, internal use only)
211 | // Zero crossings
212 | var zc: UInt16 = 0
213 | for i in 1..= 0) || (a > 0 && b <= 0) { zc &+= 1 }
216 | }
217 |
218 | // 16-band via Goertzel
219 | let (binsActual, maxMag, maxFreq) = geq16(samples: x, fs: Float(sampleRate))
220 | let magScaled = min(4096.0, max(0.0, Double(maxMag) * 600.0))
221 |
222 | // Smooth for UI
223 | let smthOpen: Float = 0.2 * instRaw + 0.8 * uiLevel * 255.0
224 |
225 | // Choose payload
226 | let instToSend: Float
227 | let smthToSend: Float
228 | let binsToSend: [UInt8]
229 | if gateOpen {
230 | instToSend = instRaw
231 | smthToSend = smthOpen
232 | binsToSend = binsActual
233 | } else {
234 | instToSend = 0; smthToSend = 0
235 | binsToSend = [UInt8](repeating: 0, count: 16)
236 | }
237 |
238 | // Build + send
239 | frameCounter &+= 1
240 | let pkt = buildV2(sampleRaw: instToSend, sampleSmth: smthToSend, samplePeak: false,
241 | frameCounter: frameCounter, fftBins: binsToSend, zeroCrossingCount: zc,
242 | FFT_Magnitude: Float(magScaled), FFT_MajorPeak: maxFreq)
243 | fanout(pkt)
244 |
245 | // UI ~20fps
246 | let now = CACurrentMediaTime()
247 | if now - lastUIUpdate > 0.05 {
248 | lastUIUpdate = now
249 | let level = min(1, max(0, instRaw / 255.0))
250 | let normBins = (gateOpen ? binsActual : [UInt8](repeating: 0, count: 16)).map { Float($0)/255.0 }
251 | DispatchQueue.main.async { [level, normBins] in
252 | self.uiLevel = level
253 | self.uiBins = normBins
254 | }
255 | }
256 | }
257 |
258 | private func geq16(samples x: [Float], fs: Float) -> ([UInt8], Float, Float) {
259 | var mags = [Float](repeating: 0, count: 16)
260 | let N = Float(x.count)
261 | var maxMag: Float = 0; var maxIdx = 0
262 | for (i, f) in bandCenters.enumerated() {
263 | let k = roundf(N * Float(f) / fs)
264 | let omega = 2.0 * Float.pi * k / N
265 | let coeff = 2.0 * cosf(omega)
266 | var s1: Float = 0, s2: Float = 0
267 | for n in 0.. maxMag { maxMag = mag; maxIdx = i }
275 | }
276 | var bins = [UInt8](repeating: 0, count: 16)
277 | let eps: Float = 1e-6
278 | for i in 0..<16 {
279 | let norm = (maxMag > eps) ? (mags[i]/maxMag) : 0
280 | binEMA[i] = 0.7*binEMA[i] + 0.3*norm
281 | bins[i] = UInt8(max(0, min(255, Int(round(binEMA[i]*255)))))
282 | }
283 | return (bins, maxMag, Float(bandCenters[maxIdx]))
284 | }
285 |
286 | // MARK: Packet + send
287 | private func buildV2(sampleRaw: Float, sampleSmth: Float, samplePeak: Bool, frameCounter: UInt8,
288 | fftBins: [UInt8], zeroCrossingCount: UInt16, FFT_Magnitude: Float, FFT_MajorPeak: Float) -> Data {
289 | var p = [UInt8](repeating: 0, count: 44)
290 | let hdr = Array("00002".utf8)
291 | for i in 0..> 8) & 0xFF)
300 | writeFloatLE(FFT_Magnitude, &p, 36)
301 | writeFloatLE(FFT_MajorPeak, &p, 40)
302 | return Data(p)
303 | }
304 |
305 | private func writeFloatLE(_ f: Float, _ buf: inout [UInt8], _ off: Int) {
306 | let le = f.bitPattern.littleEndian
307 | buf[off+0] = UInt8(le & 0xFF)
308 | buf[off+1] = UInt8((le >> 8) & 0xFF)
309 | buf[off+2] = UInt8((le >> 16) & 0xFF)
310 | buf[off+3] = UInt8((le >> 24) & 0xFF)
311 | }
312 |
313 | private func fanout(_ pkt: Data) {
314 | if conns.count != targets.count { rebuildConns() } // lazy fix if list changed
315 | for (i, t) in targets.enumerated() {
316 | let id = targets[i].id
317 | if conns[id] == nil { openConn(for: t) }
318 | conns[id]?.send(content: pkt, completion: .idempotent)
319 | }
320 | }
321 | }
322 |
323 | // MARK: - Bars
324 | struct BarView: View {
325 | var value: CGFloat
326 | var body: some View {
327 | GeometryReader { geo in
328 | let h = max(2, value * geo.size.height)
329 | ZStack(alignment: .bottom) {
330 | RoundedRectangle(cornerRadius: 3).stroke(lineWidth: 1)
331 | RoundedRectangle(cornerRadius: 3).frame(height: h)
332 | }
333 | }
334 | }
335 | }
336 |
337 | // MARK: - UI
338 | struct ContentView: View {
339 | @StateObject private var sender = WLEDSender()
340 | @State private var keepAwake = false
341 |
342 | @State private var newHost = ""
343 | @State private var newPort = "11988"
344 |
345 | var body: some View {
346 | VStack(spacing: 14) {
347 | Text("Feed My WLED — iOS").font(.title.bold())
348 |
349 | // Add target
350 | HStack {
351 | TextField("wled-mm.local or 192.168.x.x", text: $newHost)
352 | .textFieldStyle(.roundedBorder)
353 | .keyboardType(.numbersAndPunctuation)
354 | .autocapitalization(.none)
355 | .disableAutocorrection(true)
356 | TextField("11988", text: $newPort)
357 | .frame(width: 80)
358 | .textFieldStyle(.roundedBorder)
359 | .keyboardType(.numberPad)
360 | Button("Add") {
361 | sender.addTarget(host: newHost, port: UInt16(newPort) ?? 11988)
362 | newHost = ""; newPort = "11988"
363 | }
364 | .buttonStyle(.borderedProminent)
365 | }
366 | .padding(.horizontal)
367 |
368 | // List of targets
369 | List {
370 | Section("Targets (\(sender.targets.count))") {
371 | ForEach(sender.targets) { t in
372 | HStack {
373 | Text(t.host).bold().lineLimit(1).truncationMode(.middle)
374 | Spacer()
375 | Text("\(t.port)").foregroundStyle(.secondary).monospaced()
376 | }
377 | }
378 | .onDelete(perform: sender.removeTargets)
379 | }
380 | }
381 | .frame(height: 180)
382 |
383 | // Noise gate
384 | GroupBox("Noise Gate") {
385 | VStack(spacing: 8) {
386 | Toggle("Enable", isOn: $sender.gateEnabled)
387 | HStack {
388 | Text("Open").frame(width: 60, alignment: .leading)
389 | Slider(value: $sender.gateOpenThresh, in: 0...255, step: 1)
390 | Text("\(Int(sender.gateOpenThresh))").monospaced()
391 | }
392 | HStack {
393 | Text("Close").frame(width: 60, alignment: .leading)
394 | Slider(value: $sender.gateCloseThresh, in: 0...255, step: 1)
395 | Text("\(Int(sender.gateCloseThresh))").monospaced()
396 | }
397 | HStack {
398 | Text("Hold").frame(width: 60, alignment: .leading)
399 | TextField("250", text: Binding(
400 | get: { String(sender.gateHoldMs) },
401 | set: { sender.gateHoldMs = Int($0) ?? 250 }
402 | ))
403 | .textFieldStyle(.roundedBorder)
404 | .keyboardType(.numberPad)
405 | Text("ms").foregroundStyle(.secondary)
406 | }
407 | }
408 | }
409 | .padding(.horizontal)
410 |
411 | // Transport + test
412 | HStack {
413 | Button(sender.isRunning ? "Stop" : "Start") {
414 | sender.isRunning ? sender.stop() : sender.start()
415 | }
416 | .buttonStyle(.borderedProminent)
417 |
418 | Button("Send Test Packet") { sender.sendTestPacket() }
419 | .buttonStyle(.bordered)
420 |
421 | Toggle("Keep Screen Awake", isOn: $keepAwake)
422 | .onChange(of: keepAwake) { UIApplication.shared.isIdleTimerDisabled = $0 }
423 | }
424 | .padding(.horizontal)
425 |
426 | // Visualizers
427 | VStack(spacing: 8) {
428 | BarView(value: CGFloat(sender.uiLevel))
429 | .frame(height: 70)
430 | .padding(.horizontal)
431 |
432 | HStack(alignment: .bottom, spacing: 4) {
433 | ForEach(0..<16, id: \.self) { i in
434 | BarView(value: CGFloat(min(1.0, max(0.0, sender.uiBins[i]))))
435 | }
436 | }
437 | .frame(height: 80)
438 | .padding(.horizontal)
439 | }
440 |
441 | Text(sender.statusText)
442 | .font(.footnote)
443 | .foregroundStyle(.secondary)
444 |
445 | Spacer(minLength: 8)
446 | }
447 | .padding(.top, 8)
448 | }
449 | }
450 |
451 | @main
452 | struct WLEDApp: App {
453 | var body: some Scene { WindowGroup { ContentView() } }
454 | }
455 |
--------------------------------------------------------------------------------
/wled-mic.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 77;
7 | objects = {
8 |
9 | /* Begin PBXContainerItemProxy section */
10 | 3FFF895D2E56172F0075EBE1 /* PBXContainerItemProxy */ = {
11 | isa = PBXContainerItemProxy;
12 | containerPortal = 3FFF89472E56172E0075EBE1 /* Project object */;
13 | proxyType = 1;
14 | remoteGlobalIDString = 3FFF894E2E56172E0075EBE1;
15 | remoteInfo = "wled-mic";
16 | };
17 | 3FFF89672E56172F0075EBE1 /* PBXContainerItemProxy */ = {
18 | isa = PBXContainerItemProxy;
19 | containerPortal = 3FFF89472E56172E0075EBE1 /* Project object */;
20 | proxyType = 1;
21 | remoteGlobalIDString = 3FFF894E2E56172E0075EBE1;
22 | remoteInfo = "wled-mic";
23 | };
24 | /* End PBXContainerItemProxy section */
25 |
26 | /* Begin PBXFileReference section */
27 | 3FFF894F2E56172E0075EBE1 /* wled-mic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "wled-mic.app"; sourceTree = BUILT_PRODUCTS_DIR; };
28 | 3FFF895C2E56172F0075EBE1 /* wled-micTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "wled-micTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
29 | 3FFF89662E56172F0075EBE1 /* wled-micUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "wled-micUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
30 | /* End PBXFileReference section */
31 |
32 | /* Begin PBXFileSystemSynchronizedRootGroup section */
33 | 3FFF89512E56172E0075EBE1 /* wled-mic */ = {
34 | isa = PBXFileSystemSynchronizedRootGroup;
35 | path = "wled-mic";
36 | sourceTree = "";
37 | };
38 | 3FFF895F2E56172F0075EBE1 /* wled-micTests */ = {
39 | isa = PBXFileSystemSynchronizedRootGroup;
40 | path = "wled-micTests";
41 | sourceTree = "";
42 | };
43 | 3FFF89692E56172F0075EBE1 /* wled-micUITests */ = {
44 | isa = PBXFileSystemSynchronizedRootGroup;
45 | path = "wled-micUITests";
46 | sourceTree = "";
47 | };
48 | /* End PBXFileSystemSynchronizedRootGroup section */
49 |
50 | /* Begin PBXFrameworksBuildPhase section */
51 | 3FFF894C2E56172E0075EBE1 /* Frameworks */ = {
52 | isa = PBXFrameworksBuildPhase;
53 | buildActionMask = 2147483647;
54 | files = (
55 | );
56 | runOnlyForDeploymentPostprocessing = 0;
57 | };
58 | 3FFF89592E56172F0075EBE1 /* Frameworks */ = {
59 | isa = PBXFrameworksBuildPhase;
60 | buildActionMask = 2147483647;
61 | files = (
62 | );
63 | runOnlyForDeploymentPostprocessing = 0;
64 | };
65 | 3FFF89632E56172F0075EBE1 /* Frameworks */ = {
66 | isa = PBXFrameworksBuildPhase;
67 | buildActionMask = 2147483647;
68 | files = (
69 | );
70 | runOnlyForDeploymentPostprocessing = 0;
71 | };
72 | /* End PBXFrameworksBuildPhase section */
73 |
74 | /* Begin PBXGroup section */
75 | 3FFF89462E56172E0075EBE1 = {
76 | isa = PBXGroup;
77 | children = (
78 | 3FFF89512E56172E0075EBE1 /* wled-mic */,
79 | 3FFF895F2E56172F0075EBE1 /* wled-micTests */,
80 | 3FFF89692E56172F0075EBE1 /* wled-micUITests */,
81 | 3FFF89502E56172E0075EBE1 /* Products */,
82 | );
83 | sourceTree = "";
84 | };
85 | 3FFF89502E56172E0075EBE1 /* Products */ = {
86 | isa = PBXGroup;
87 | children = (
88 | 3FFF894F2E56172E0075EBE1 /* wled-mic.app */,
89 | 3FFF895C2E56172F0075EBE1 /* wled-micTests.xctest */,
90 | 3FFF89662E56172F0075EBE1 /* wled-micUITests.xctest */,
91 | );
92 | name = Products;
93 | sourceTree = "";
94 | };
95 | /* End PBXGroup section */
96 |
97 | /* Begin PBXNativeTarget section */
98 | 3FFF894E2E56172E0075EBE1 /* wled-mic */ = {
99 | isa = PBXNativeTarget;
100 | buildConfigurationList = 3FFF89702E56172F0075EBE1 /* Build configuration list for PBXNativeTarget "wled-mic" */;
101 | buildPhases = (
102 | 3FFF894B2E56172E0075EBE1 /* Sources */,
103 | 3FFF894C2E56172E0075EBE1 /* Frameworks */,
104 | 3FFF894D2E56172E0075EBE1 /* Resources */,
105 | );
106 | buildRules = (
107 | );
108 | dependencies = (
109 | );
110 | fileSystemSynchronizedGroups = (
111 | 3FFF89512E56172E0075EBE1 /* wled-mic */,
112 | );
113 | name = "wled-mic";
114 | packageProductDependencies = (
115 | );
116 | productName = "wled-mic";
117 | productReference = 3FFF894F2E56172E0075EBE1 /* wled-mic.app */;
118 | productType = "com.apple.product-type.application";
119 | };
120 | 3FFF895B2E56172F0075EBE1 /* wled-micTests */ = {
121 | isa = PBXNativeTarget;
122 | buildConfigurationList = 3FFF89732E56172F0075EBE1 /* Build configuration list for PBXNativeTarget "wled-micTests" */;
123 | buildPhases = (
124 | 3FFF89582E56172F0075EBE1 /* Sources */,
125 | 3FFF89592E56172F0075EBE1 /* Frameworks */,
126 | 3FFF895A2E56172F0075EBE1 /* Resources */,
127 | );
128 | buildRules = (
129 | );
130 | dependencies = (
131 | 3FFF895E2E56172F0075EBE1 /* PBXTargetDependency */,
132 | );
133 | fileSystemSynchronizedGroups = (
134 | 3FFF895F2E56172F0075EBE1 /* wled-micTests */,
135 | );
136 | name = "wled-micTests";
137 | packageProductDependencies = (
138 | );
139 | productName = "wled-micTests";
140 | productReference = 3FFF895C2E56172F0075EBE1 /* wled-micTests.xctest */;
141 | productType = "com.apple.product-type.bundle.unit-test";
142 | };
143 | 3FFF89652E56172F0075EBE1 /* wled-micUITests */ = {
144 | isa = PBXNativeTarget;
145 | buildConfigurationList = 3FFF89762E56172F0075EBE1 /* Build configuration list for PBXNativeTarget "wled-micUITests" */;
146 | buildPhases = (
147 | 3FFF89622E56172F0075EBE1 /* Sources */,
148 | 3FFF89632E56172F0075EBE1 /* Frameworks */,
149 | 3FFF89642E56172F0075EBE1 /* Resources */,
150 | );
151 | buildRules = (
152 | );
153 | dependencies = (
154 | 3FFF89682E56172F0075EBE1 /* PBXTargetDependency */,
155 | );
156 | fileSystemSynchronizedGroups = (
157 | 3FFF89692E56172F0075EBE1 /* wled-micUITests */,
158 | );
159 | name = "wled-micUITests";
160 | packageProductDependencies = (
161 | );
162 | productName = "wled-micUITests";
163 | productReference = 3FFF89662E56172F0075EBE1 /* wled-micUITests.xctest */;
164 | productType = "com.apple.product-type.bundle.ui-testing";
165 | };
166 | /* End PBXNativeTarget section */
167 |
168 | /* Begin PBXProject section */
169 | 3FFF89472E56172E0075EBE1 /* Project object */ = {
170 | isa = PBXProject;
171 | attributes = {
172 | BuildIndependentTargetsInParallel = 1;
173 | LastSwiftUpdateCheck = 1640;
174 | LastUpgradeCheck = 1640;
175 | TargetAttributes = {
176 | 3FFF894E2E56172E0075EBE1 = {
177 | CreatedOnToolsVersion = 16.4;
178 | };
179 | 3FFF895B2E56172F0075EBE1 = {
180 | CreatedOnToolsVersion = 16.4;
181 | TestTargetID = 3FFF894E2E56172E0075EBE1;
182 | };
183 | 3FFF89652E56172F0075EBE1 = {
184 | CreatedOnToolsVersion = 16.4;
185 | TestTargetID = 3FFF894E2E56172E0075EBE1;
186 | };
187 | };
188 | };
189 | buildConfigurationList = 3FFF894A2E56172E0075EBE1 /* Build configuration list for PBXProject "wled-mic" */;
190 | developmentRegion = en;
191 | hasScannedForEncodings = 0;
192 | knownRegions = (
193 | en,
194 | Base,
195 | );
196 | mainGroup = 3FFF89462E56172E0075EBE1;
197 | minimizedProjectReferenceProxies = 1;
198 | preferredProjectObjectVersion = 77;
199 | productRefGroup = 3FFF89502E56172E0075EBE1 /* Products */;
200 | projectDirPath = "";
201 | projectRoot = "";
202 | targets = (
203 | 3FFF894E2E56172E0075EBE1 /* wled-mic */,
204 | 3FFF895B2E56172F0075EBE1 /* wled-micTests */,
205 | 3FFF89652E56172F0075EBE1 /* wled-micUITests */,
206 | );
207 | };
208 | /* End PBXProject section */
209 |
210 | /* Begin PBXResourcesBuildPhase section */
211 | 3FFF894D2E56172E0075EBE1 /* Resources */ = {
212 | isa = PBXResourcesBuildPhase;
213 | buildActionMask = 2147483647;
214 | files = (
215 | );
216 | runOnlyForDeploymentPostprocessing = 0;
217 | };
218 | 3FFF895A2E56172F0075EBE1 /* Resources */ = {
219 | isa = PBXResourcesBuildPhase;
220 | buildActionMask = 2147483647;
221 | files = (
222 | );
223 | runOnlyForDeploymentPostprocessing = 0;
224 | };
225 | 3FFF89642E56172F0075EBE1 /* Resources */ = {
226 | isa = PBXResourcesBuildPhase;
227 | buildActionMask = 2147483647;
228 | files = (
229 | );
230 | runOnlyForDeploymentPostprocessing = 0;
231 | };
232 | /* End PBXResourcesBuildPhase section */
233 |
234 | /* Begin PBXSourcesBuildPhase section */
235 | 3FFF894B2E56172E0075EBE1 /* Sources */ = {
236 | isa = PBXSourcesBuildPhase;
237 | buildActionMask = 2147483647;
238 | files = (
239 | );
240 | runOnlyForDeploymentPostprocessing = 0;
241 | };
242 | 3FFF89582E56172F0075EBE1 /* Sources */ = {
243 | isa = PBXSourcesBuildPhase;
244 | buildActionMask = 2147483647;
245 | files = (
246 | );
247 | runOnlyForDeploymentPostprocessing = 0;
248 | };
249 | 3FFF89622E56172F0075EBE1 /* Sources */ = {
250 | isa = PBXSourcesBuildPhase;
251 | buildActionMask = 2147483647;
252 | files = (
253 | );
254 | runOnlyForDeploymentPostprocessing = 0;
255 | };
256 | /* End PBXSourcesBuildPhase section */
257 |
258 | /* Begin PBXTargetDependency section */
259 | 3FFF895E2E56172F0075EBE1 /* PBXTargetDependency */ = {
260 | isa = PBXTargetDependency;
261 | target = 3FFF894E2E56172E0075EBE1 /* wled-mic */;
262 | targetProxy = 3FFF895D2E56172F0075EBE1 /* PBXContainerItemProxy */;
263 | };
264 | 3FFF89682E56172F0075EBE1 /* PBXTargetDependency */ = {
265 | isa = PBXTargetDependency;
266 | target = 3FFF894E2E56172E0075EBE1 /* wled-mic */;
267 | targetProxy = 3FFF89672E56172F0075EBE1 /* PBXContainerItemProxy */;
268 | };
269 | /* End PBXTargetDependency section */
270 |
271 | /* Begin XCBuildConfiguration section */
272 | 3FFF896E2E56172F0075EBE1 /* Debug */ = {
273 | isa = XCBuildConfiguration;
274 | buildSettings = {
275 | ALWAYS_SEARCH_USER_PATHS = NO;
276 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
277 | CLANG_ANALYZER_NONNULL = YES;
278 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
279 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
280 | CLANG_ENABLE_MODULES = YES;
281 | CLANG_ENABLE_OBJC_ARC = YES;
282 | CLANG_ENABLE_OBJC_WEAK = YES;
283 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
284 | CLANG_WARN_BOOL_CONVERSION = YES;
285 | CLANG_WARN_COMMA = YES;
286 | CLANG_WARN_CONSTANT_CONVERSION = YES;
287 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
288 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
289 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
290 | CLANG_WARN_EMPTY_BODY = YES;
291 | CLANG_WARN_ENUM_CONVERSION = YES;
292 | CLANG_WARN_INFINITE_RECURSION = YES;
293 | CLANG_WARN_INT_CONVERSION = YES;
294 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
295 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
296 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
297 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
298 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
299 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
300 | CLANG_WARN_STRICT_PROTOTYPES = YES;
301 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
302 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
303 | CLANG_WARN_UNREACHABLE_CODE = YES;
304 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
305 | COPY_PHASE_STRIP = NO;
306 | DEBUG_INFORMATION_FORMAT = dwarf;
307 | ENABLE_STRICT_OBJC_MSGSEND = YES;
308 | ENABLE_TESTABILITY = YES;
309 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
310 | GCC_C_LANGUAGE_STANDARD = gnu17;
311 | GCC_DYNAMIC_NO_PIC = NO;
312 | GCC_NO_COMMON_BLOCKS = YES;
313 | GCC_OPTIMIZATION_LEVEL = 0;
314 | GCC_PREPROCESSOR_DEFINITIONS = (
315 | "DEBUG=1",
316 | "$(inherited)",
317 | );
318 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
319 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
320 | GCC_WARN_UNDECLARED_SELECTOR = YES;
321 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
322 | GCC_WARN_UNUSED_FUNCTION = YES;
323 | GCC_WARN_UNUSED_VARIABLE = YES;
324 | IPHONEOS_DEPLOYMENT_TARGET = 18.5;
325 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
326 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
327 | MTL_FAST_MATH = YES;
328 | ONLY_ACTIVE_ARCH = YES;
329 | SDKROOT = iphoneos;
330 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
331 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
332 | };
333 | name = Debug;
334 | };
335 | 3FFF896F2E56172F0075EBE1 /* Release */ = {
336 | isa = XCBuildConfiguration;
337 | buildSettings = {
338 | ALWAYS_SEARCH_USER_PATHS = NO;
339 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
340 | CLANG_ANALYZER_NONNULL = YES;
341 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
342 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
343 | CLANG_ENABLE_MODULES = YES;
344 | CLANG_ENABLE_OBJC_ARC = YES;
345 | CLANG_ENABLE_OBJC_WEAK = YES;
346 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
347 | CLANG_WARN_BOOL_CONVERSION = YES;
348 | CLANG_WARN_COMMA = YES;
349 | CLANG_WARN_CONSTANT_CONVERSION = YES;
350 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
351 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
352 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
353 | CLANG_WARN_EMPTY_BODY = YES;
354 | CLANG_WARN_ENUM_CONVERSION = YES;
355 | CLANG_WARN_INFINITE_RECURSION = YES;
356 | CLANG_WARN_INT_CONVERSION = YES;
357 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
358 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
359 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
360 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
361 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
362 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
363 | CLANG_WARN_STRICT_PROTOTYPES = YES;
364 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
365 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
366 | CLANG_WARN_UNREACHABLE_CODE = YES;
367 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
368 | COPY_PHASE_STRIP = NO;
369 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
370 | ENABLE_NS_ASSERTIONS = NO;
371 | ENABLE_STRICT_OBJC_MSGSEND = YES;
372 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
373 | GCC_C_LANGUAGE_STANDARD = gnu17;
374 | GCC_NO_COMMON_BLOCKS = YES;
375 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
376 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
377 | GCC_WARN_UNDECLARED_SELECTOR = YES;
378 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
379 | GCC_WARN_UNUSED_FUNCTION = YES;
380 | GCC_WARN_UNUSED_VARIABLE = YES;
381 | IPHONEOS_DEPLOYMENT_TARGET = 18.5;
382 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
383 | MTL_ENABLE_DEBUG_INFO = NO;
384 | MTL_FAST_MATH = YES;
385 | SDKROOT = iphoneos;
386 | SWIFT_COMPILATION_MODE = wholemodule;
387 | VALIDATE_PRODUCT = YES;
388 | };
389 | name = Release;
390 | };
391 | 3FFF89712E56172F0075EBE1 /* Debug */ = {
392 | isa = XCBuildConfiguration;
393 | buildSettings = {
394 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
395 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
396 | CODE_SIGN_STYLE = Automatic;
397 | CURRENT_PROJECT_VERSION = 1;
398 | ENABLE_PREVIEWS = YES;
399 | GENERATE_INFOPLIST_FILE = YES;
400 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
401 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
402 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
403 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
404 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
405 | LD_RUNPATH_SEARCH_PATHS = (
406 | "$(inherited)",
407 | "@executable_path/Frameworks",
408 | );
409 | MARKETING_VERSION = 1.0;
410 | PRODUCT_BUNDLE_IDENTIFIER = "me.alexw.wled-mic";
411 | PRODUCT_NAME = "$(TARGET_NAME)";
412 | SWIFT_EMIT_LOC_STRINGS = YES;
413 | SWIFT_VERSION = 5.0;
414 | TARGETED_DEVICE_FAMILY = "1,2";
415 | };
416 | name = Debug;
417 | };
418 | 3FFF89722E56172F0075EBE1 /* Release */ = {
419 | isa = XCBuildConfiguration;
420 | buildSettings = {
421 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
422 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
423 | CODE_SIGN_STYLE = Automatic;
424 | CURRENT_PROJECT_VERSION = 1;
425 | ENABLE_PREVIEWS = YES;
426 | GENERATE_INFOPLIST_FILE = YES;
427 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
428 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
429 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
430 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
431 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
432 | LD_RUNPATH_SEARCH_PATHS = (
433 | "$(inherited)",
434 | "@executable_path/Frameworks",
435 | );
436 | MARKETING_VERSION = 1.0;
437 | PRODUCT_BUNDLE_IDENTIFIER = "me.alexw.wled-mic";
438 | PRODUCT_NAME = "$(TARGET_NAME)";
439 | SWIFT_EMIT_LOC_STRINGS = YES;
440 | SWIFT_VERSION = 5.0;
441 | TARGETED_DEVICE_FAMILY = "1,2";
442 | };
443 | name = Release;
444 | };
445 | 3FFF89742E56172F0075EBE1 /* Debug */ = {
446 | isa = XCBuildConfiguration;
447 | buildSettings = {
448 | BUNDLE_LOADER = "$(TEST_HOST)";
449 | CODE_SIGN_STYLE = Automatic;
450 | CURRENT_PROJECT_VERSION = 1;
451 | GENERATE_INFOPLIST_FILE = YES;
452 | IPHONEOS_DEPLOYMENT_TARGET = 18.5;
453 | MARKETING_VERSION = 1.0;
454 | PRODUCT_BUNDLE_IDENTIFIER = "me.alexw.wled-micTests";
455 | PRODUCT_NAME = "$(TARGET_NAME)";
456 | SWIFT_EMIT_LOC_STRINGS = NO;
457 | SWIFT_VERSION = 5.0;
458 | TARGETED_DEVICE_FAMILY = "1,2";
459 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/wled-mic.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/wled-mic";
460 | };
461 | name = Debug;
462 | };
463 | 3FFF89752E56172F0075EBE1 /* Release */ = {
464 | isa = XCBuildConfiguration;
465 | buildSettings = {
466 | BUNDLE_LOADER = "$(TEST_HOST)";
467 | CODE_SIGN_STYLE = Automatic;
468 | CURRENT_PROJECT_VERSION = 1;
469 | GENERATE_INFOPLIST_FILE = YES;
470 | IPHONEOS_DEPLOYMENT_TARGET = 18.5;
471 | MARKETING_VERSION = 1.0;
472 | PRODUCT_BUNDLE_IDENTIFIER = "me.alexw.wled-micTests";
473 | PRODUCT_NAME = "$(TARGET_NAME)";
474 | SWIFT_EMIT_LOC_STRINGS = NO;
475 | SWIFT_VERSION = 5.0;
476 | TARGETED_DEVICE_FAMILY = "1,2";
477 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/wled-mic.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/wled-mic";
478 | };
479 | name = Release;
480 | };
481 | 3FFF89772E56172F0075EBE1 /* Debug */ = {
482 | isa = XCBuildConfiguration;
483 | buildSettings = {
484 | CODE_SIGN_STYLE = Automatic;
485 | CURRENT_PROJECT_VERSION = 1;
486 | GENERATE_INFOPLIST_FILE = YES;
487 | MARKETING_VERSION = 1.0;
488 | PRODUCT_BUNDLE_IDENTIFIER = "me.alexw.wled-micUITests";
489 | PRODUCT_NAME = "$(TARGET_NAME)";
490 | SWIFT_EMIT_LOC_STRINGS = NO;
491 | SWIFT_VERSION = 5.0;
492 | TARGETED_DEVICE_FAMILY = "1,2";
493 | TEST_TARGET_NAME = "wled-mic";
494 | };
495 | name = Debug;
496 | };
497 | 3FFF89782E56172F0075EBE1 /* Release */ = {
498 | isa = XCBuildConfiguration;
499 | buildSettings = {
500 | CODE_SIGN_STYLE = Automatic;
501 | CURRENT_PROJECT_VERSION = 1;
502 | GENERATE_INFOPLIST_FILE = YES;
503 | MARKETING_VERSION = 1.0;
504 | PRODUCT_BUNDLE_IDENTIFIER = "me.alexw.wled-micUITests";
505 | PRODUCT_NAME = "$(TARGET_NAME)";
506 | SWIFT_EMIT_LOC_STRINGS = NO;
507 | SWIFT_VERSION = 5.0;
508 | TARGETED_DEVICE_FAMILY = "1,2";
509 | TEST_TARGET_NAME = "wled-mic";
510 | };
511 | name = Release;
512 | };
513 | /* End XCBuildConfiguration section */
514 |
515 | /* Begin XCConfigurationList section */
516 | 3FFF894A2E56172E0075EBE1 /* Build configuration list for PBXProject "wled-mic" */ = {
517 | isa = XCConfigurationList;
518 | buildConfigurations = (
519 | 3FFF896E2E56172F0075EBE1 /* Debug */,
520 | 3FFF896F2E56172F0075EBE1 /* Release */,
521 | );
522 | defaultConfigurationIsVisible = 0;
523 | defaultConfigurationName = Release;
524 | };
525 | 3FFF89702E56172F0075EBE1 /* Build configuration list for PBXNativeTarget "wled-mic" */ = {
526 | isa = XCConfigurationList;
527 | buildConfigurations = (
528 | 3FFF89712E56172F0075EBE1 /* Debug */,
529 | 3FFF89722E56172F0075EBE1 /* Release */,
530 | );
531 | defaultConfigurationIsVisible = 0;
532 | defaultConfigurationName = Release;
533 | };
534 | 3FFF89732E56172F0075EBE1 /* Build configuration list for PBXNativeTarget "wled-micTests" */ = {
535 | isa = XCConfigurationList;
536 | buildConfigurations = (
537 | 3FFF89742E56172F0075EBE1 /* Debug */,
538 | 3FFF89752E56172F0075EBE1 /* Release */,
539 | );
540 | defaultConfigurationIsVisible = 0;
541 | defaultConfigurationName = Release;
542 | };
543 | 3FFF89762E56172F0075EBE1 /* Build configuration list for PBXNativeTarget "wled-micUITests" */ = {
544 | isa = XCConfigurationList;
545 | buildConfigurations = (
546 | 3FFF89772E56172F0075EBE1 /* Debug */,
547 | 3FFF89782E56172F0075EBE1 /* Release */,
548 | );
549 | defaultConfigurationIsVisible = 0;
550 | defaultConfigurationName = Release;
551 | };
552 | /* End XCConfigurationList section */
553 | };
554 | rootObject = 3FFF89472E56172E0075EBE1 /* Project object */;
555 | }
556 |
--------------------------------------------------------------------------------