1e153f2f85
Complete Phase 1 implementation of the Yaesu FT-991A remote control application with CAT protocol support over USB serial (CP210x). Features implemented: - SerialPortManager with auto-detection of CP210x ports - Full CAT protocol parser and command builder - RadioState model with all transceiver parameters - Modern SwiftUI interface with frequency/mode/level controls - Skeuomorphic front panel view (switchable) - Debug panel with CAT command console - QSO log panel with CSV export/import - Audio routing panel with BlackHole integration - Settings with connection, UI, keyboard configuration - Menu bar extra for background operation - German/English localization - Logging system for debugging Supports: Frequency control, VFO A/B, all modes (LSB/USB/CW/FM/AM/ DATA/RTTY/C4FM), level controls, NB/NR/DNF/ATU/Split functions, S-meter/Power/SWR metering, PTT control via Shift key. Target: macOS 15.0+ (Sequoia/Tahoe)
149 lines
5.5 KiB
Swift
149 lines
5.5 KiB
Swift
//
|
|
// AudioPanel.swift
|
|
// FT991A-Remote
|
|
//
|
|
// BlackHole audio routing panel
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
// MARK: - Audio Panel
|
|
|
|
struct AudioPanel: View {
|
|
@StateObject private var audioRouter = AudioRouter()
|
|
@EnvironmentObject var settingsController: SettingsController
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
// Header
|
|
HStack {
|
|
Text("Audio Routing")
|
|
.font(.headline)
|
|
|
|
Spacer()
|
|
|
|
Button {
|
|
audioRouter.refreshDevices()
|
|
} label: {
|
|
Image(systemName: "arrow.clockwise")
|
|
}
|
|
.help("Geräte aktualisieren")
|
|
}
|
|
.padding(.horizontal)
|
|
.padding(.vertical, 8)
|
|
.background(Color.secondary.opacity(0.1))
|
|
|
|
Divider()
|
|
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 16) {
|
|
// BlackHole Status
|
|
GroupBox("BlackHole Status") {
|
|
HStack {
|
|
Circle()
|
|
.fill(audioRouter.isBlackHoleInstalled ? Color.green : Color.red)
|
|
.frame(width: 12, height: 12)
|
|
|
|
Text(audioRouter.isBlackHoleInstalled ? "Installiert" : "Nicht gefunden")
|
|
|
|
Spacer()
|
|
|
|
if !audioRouter.isBlackHoleInstalled {
|
|
Link("Installieren", destination: URL(string: "https://existential.audio/blackhole/")!)
|
|
.font(.caption)
|
|
}
|
|
}
|
|
.padding(.vertical, 4)
|
|
|
|
if let device = audioRouter.blackHoleDevice {
|
|
Text("Gerät: \(device.name)")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
|
|
// Input Device
|
|
GroupBox("Eingang (RX Audio)") {
|
|
Picker("Eingabegerät", selection: $audioRouter.selectedInputDevice) {
|
|
Text("Keines").tag(nil as AudioDeviceID?)
|
|
ForEach(audioRouter.inputDevices) { device in
|
|
Text(device.displayName).tag(device.id as AudioDeviceID?)
|
|
}
|
|
}
|
|
.pickerStyle(.menu)
|
|
|
|
if let ft991a = audioRouter.ft991aDevice {
|
|
HStack {
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.foregroundColor(.green)
|
|
Text("FT-991A erkannt: \(ft991a.name)")
|
|
.font(.caption)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Output Device
|
|
GroupBox("Ausgang (TX Audio)") {
|
|
Picker("Ausgabegerät", selection: $audioRouter.selectedOutputDevice) {
|
|
Text("Keines").tag(nil as AudioDeviceID?)
|
|
ForEach(audioRouter.outputDevices) { device in
|
|
Text(device.displayName).tag(device.id as AudioDeviceID?)
|
|
}
|
|
}
|
|
.pickerStyle(.menu)
|
|
}
|
|
|
|
// Digital Mode Configuration
|
|
GroupBox("Digitale Betriebsarten") {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
Text("Für FT8, WSPR, RTTY und andere digitale Modi:")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
|
|
Button("Für Digimodes konfigurieren") {
|
|
_ = audioRouter.configureForDigitalModes()
|
|
}
|
|
.disabled(!audioRouter.isBlackHoleInstalled)
|
|
|
|
Toggle("BlackHole verwenden", isOn: $settingsController.useBlackHole)
|
|
.disabled(!audioRouter.isBlackHoleInstalled)
|
|
}
|
|
.padding(.vertical, 4)
|
|
}
|
|
|
|
// Routing Diagram
|
|
GroupBox("Routing-Schema") {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text("FT-991A USB Audio → BlackHole → WSJT-X/fldigi")
|
|
.font(.caption.monospaced())
|
|
Text("WSJT-X/fldigi → BlackHole → FT-991A USB Audio")
|
|
.font(.caption.monospaced())
|
|
}
|
|
.foregroundColor(.secondary)
|
|
.padding(.vertical, 4)
|
|
}
|
|
|
|
// Error display
|
|
if let error = audioRouter.lastError {
|
|
HStack {
|
|
Image(systemName: "exclamationmark.triangle")
|
|
.foregroundColor(.orange)
|
|
Text(error)
|
|
.font(.caption)
|
|
}
|
|
}
|
|
}
|
|
.padding()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
|
|
#Preview {
|
|
AudioPanel()
|
|
.environmentObject(SettingsController())
|
|
.frame(width: 350, height: 500)
|
|
}
|