diff options
author | Arturs Artamonovs <arturs.artamonovs@protonmail.com> | 2025-01-17 09:27:56 +0000 |
---|---|---|
committer | Arturs Artamonovs <arturs.artamonovs@protonmail.com> | 2025-01-17 09:27:56 +0000 |
commit | 06a90001363fdcb542b531a74ed2c18f714f58c4 (patch) | |
tree | e0ae3092494e6136ec7479b0e9bda20ef930039b /Waterfall_UI | |
parent | a0d12ecbac8fe327d1dcd4580fee594e24d4191b (diff) | |
download | PrySDR-06a90001363fdcb542b531a74ed2c18f714f58c4.tar.gz PrySDR-06a90001363fdcb542b531a74ed2c18f714f58c4.zip |
waterfallui: data get processed and drawn, ui controlls are dummy
Diffstat (limited to 'Waterfall_UI')
-rw-r--r-- | Waterfall_UI/Assets.xcassets/AccentColor.colorset/Contents.json | 11 | ||||
-rw-r--r-- | Waterfall_UI/Assets.xcassets/AppIcon.appiconset/Contents.json | 58 | ||||
-rw-r--r-- | Waterfall_UI/Assets.xcassets/Contents.json | 6 | ||||
-rw-r--r-- | Waterfall_UI/ContentView.swift | 194 | ||||
-rw-r--r-- | Waterfall_UI/Item.swift | 18 | ||||
-rw-r--r-- | Waterfall_UI/Preview Content/Preview Assets.xcassets/Contents.json | 6 | ||||
-rw-r--r-- | Waterfall_UI/SDRSpectrum.swift | 137 | ||||
-rw-r--r-- | Waterfall_UI/Waterfall_UI.entitlements | 12 | ||||
-rw-r--r-- | Waterfall_UI/Waterfall_UIApp.swift | 46 |
9 files changed, 488 insertions, 0 deletions
diff --git a/Waterfall_UI/Assets.xcassets/AccentColor.colorset/Contents.json b/Waterfall_UI/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Waterfall_UI/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Waterfall_UI/Assets.xcassets/AppIcon.appiconset/Contents.json b/Waterfall_UI/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..3f00db4 --- /dev/null +++ b/Waterfall_UI/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Waterfall_UI/Assets.xcassets/Contents.json b/Waterfall_UI/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Waterfall_UI/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Waterfall_UI/ContentView.swift b/Waterfall_UI/ContentView.swift new file mode 100644 index 0000000..6c09fec --- /dev/null +++ b/Waterfall_UI/ContentView.swift @@ -0,0 +1,194 @@ +// +// ContentView.swift +// Waterfall_UI +// +// Created by Jacky Jack on 06/01/2025. +// + +import SwiftUI +import SwiftData + +private struct DeviceChoose: Identifiable { + let deviceName: String + var id: String { deviceName } +} + +private let deviceList: [DeviceChoose] = [ + DeviceChoose(deviceName:"RtlSDR SN:00000000"), + DeviceChoose(deviceName:"BladeRF SN:00000000"), + DeviceChoose(deviceName:"AirSpy SN:00000000"), + DeviceChoose(deviceName:"AirSpyHF SN:00000000") +] + +private struct SampleRateChoose: Identifiable { + let sampleRate: Int + var id: String { String(sampleRate) } +} + +private let sampleRateList: [SampleRateChoose] = [ + SampleRateChoose(sampleRate:1000), + SampleRateChoose(sampleRate:100000) +] + +private struct FFTOptions: Identifiable { + let binsize: Int + var id: String { String(binsize) } +} + +private let fftBinsList: [FFTOptions] = [ + FFTOptions(binsize: 512), + FFTOptions(binsize: 1024), + FFTOptions(binsize: 2048), + FFTOptions(binsize: 4096), +] + +struct ContentView: View { + @Environment(\.modelContext) private var modelContext + + @EnvironmentObject var sdrSpectrum: SDRSpectrum + + @Query private var items: [Item] + + @State private var selectedDevice: String = deviceList[0].deviceName + @State private var selectedSampleRate: String = String(sampleRateList[0].sampleRate) + @State private var setFrequency: Int = 100000000 + @State private var toggleAGC: Bool = false + @State private var selectorFFTBin: String = String(fftBinsList[0].binsize) + @State private var statusMessage: String = "" + + + var body: some View { + /* + NavigationSplitView { + List { + ForEach(items) { item in + NavigationLink { + Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") + } label: { + Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) + } + } + .onDelete(perform: deleteItems) + } + .navigationSplitViewColumnWidth(min: 180, ideal: 200) + .toolbar { + ToolbarItem { + Button(action: addItem) { + Label("Add Item", systemImage: "plus") + } + } + } + } detail: { + Text("Select an item") + } + */ + HStack { + VStack { + Text("Hardware")//.border(.red) + Picker("Device", selection:$selectedDevice) { + ForEach(deviceList) { device in + Text("\(device.deviceName)") + } + } + .onChange(of: selectedDevice) { + statusMessage = "selected \(selectedDevice)" + } + Picker("SampleRate", selection:$selectedSampleRate) { + ForEach(sampleRateList) { samplerate in + Text("\(samplerate.sampleRate)") + } + }.onChange(of:selectedSampleRate) { + statusMessage = "selected \(selectedSampleRate)" + } + Divider() + HStack { + Text("Control") + Button("Start") { + print("Start") + statusMessage = "Running" + } + Button("Stop") { + print("Stop") + statusMessage = "Idle" + } + } + Divider() + Text("Config") + HStack { + Text("Frequency: ") + TextField("Frquency: ", value: $setFrequency, format: .number) + .onChange(of: setFrequency) { + statusMessage = "frequency \(setFrequency)" + } + .onSubmit { + statusMessage = "frequency submit" + } + Text("Hz") + } + Toggle("AGC",isOn: $toggleAGC) + .onChange(of: toggleAGC) { + if (self.toggleAGC) { + statusMessage = "AGC on" + } else { + statusMessage = "AGC off" + } + } + Divider() + Text("FFT View") + Picker("FFT bins", selection:$selectorFFTBin) { + ForEach(fftBinsList) { binssize in + Text("\(binssize.binsize)") + } + } + .onChange(of: selectorFFTBin) { + statusMessage = "set fft bins \(selectorFFTBin)" + } + Spacer() + Divider()//.border(.red) + + HStack { + Text("Version: \(software_version)")//.border(.red) + Spacer() + } + } + Divider() + VStack { + //Text("Right")//.border(.red) + Divider() + Image(decorative: sdrSpectrum.outputImage, + scale: 1, + orientation: .left) + .resizable() + .scaledToFit() + .rotationEffect(Angle(degrees: 90)) + Spacer() + Divider()//.border(.red) + + HStack { + Spacer() + Text("Status message: \(statusMessage)")//.border(.red) + } + } + } + } + + private func addItem() { + withAnimation { + let newItem = Item(timestamp: Date()) + modelContext.insert(newItem) + } + } + + private func deleteItems(offsets: IndexSet) { + withAnimation { + for index in offsets { + modelContext.delete(items[index]) + } + } + } +} + +#Preview { + ContentView() + .modelContainer(for: Item.self, inMemory: true) +} diff --git a/Waterfall_UI/Item.swift b/Waterfall_UI/Item.swift new file mode 100644 index 0000000..0f2a8ae --- /dev/null +++ b/Waterfall_UI/Item.swift @@ -0,0 +1,18 @@ +// +// Item.swift +// Waterfall_UI +// +// Created by Jacky Jack on 06/01/2025. +// + +import Foundation +import SwiftData + +@Model +final class Item { + var timestamp: Date + + init(timestamp: Date) { + self.timestamp = timestamp + } +} diff --git a/Waterfall_UI/Preview Content/Preview Assets.xcassets/Contents.json b/Waterfall_UI/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Waterfall_UI/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Waterfall_UI/SDRSpectrum.swift b/Waterfall_UI/SDRSpectrum.swift new file mode 100644 index 0000000..c83e802 --- /dev/null +++ b/Waterfall_UI/SDRSpectrum.swift @@ -0,0 +1,137 @@ +// +// SDRSpectrum.swift +// PrySDR +// +// Created by Jacky Jack on 10/01/2025. +// + +// +// FileSpectrum.swift +// PrySDR +// +// Created by Jacky Jack on 01/01/2025. +// + +import Accelerate +import AppKit +import Combine +import CoreImage + +import libr820 + +class SDRSpectrum: NSObject, ObservableObject { + + static let default_width = 512 + static let default_height = 512 + static let defaultSleepTime:UInt32 = 1 + var offset:Int=0 + var allData:[Int8]? = nil + var line_counter:Int = 0 + + //SDR default configs + let device_idx: UInt32 = 0 + let samplerate: Int = 2048000 + let gain: Int = 0 + let frequency: Int = 99500000 + let nsamples: Int = default_width + var device: R820Tuner? + + //fft drawing related + let fft512 = NaiveFFT512() + + let sessionQueue = DispatchQueue(label: "sessionQueue", + attributes: [], + autoreleaseFrequency: .workItem) + + @Published var outputImage = emptyCGImage + + let simpleImage = SimpleImage(width: default_width, height: default_height) + + /// A 1x1 Core Graphics image. + static var emptyCGImage: CGImage = { + let buffer = vImage.PixelBuffer( + pixelValues: [0], + size: .init(width: 1, height: 1), + pixelFormat: vImage.Planar8.self) + + let fmt = vImage_CGImageFormat( + bitsPerComponent: 8, + bitsPerPixel: 8 , + colorSpace: CGColorSpaceCreateDeviceGray(), + bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue), + renderingIntent: .defaultIntent) + + return buffer.makeCGImage(cgImageFormat: fmt!)! + }() + + func makeSpectrogramImage() -> NSImage { + + return NSImage(cgImage: SDRSpectrum.emptyCGImage, size: .zero) + } + + //just get data time to time from databuffer and pretend to be realtime data + func dataFileProcessor() { + + + } + + func startRunning() { + let buf_ptr = UnsafeMutableRawPointer.allocate(byteCount: nsamples, alignment: 1) + var nbytes:Int32 = 0 + //var total_bytes:Int32 = 0 + + + //prepare sdr + print("Prepare SDR") + let count = getDeviceCount() + print("Found \(count) devices") + self.device = R820Tuner() + sleep(1) + if (self.device != nil) { + self.device!.open(index:0) + + //prepare dongle to receive some data + let _ = self.device!.setSampleRate(samplerate: UInt32(samplerate)) + let _ = self.device!.setCenterFreq(freq: UInt32(frequency)) + let _ = self.device!.setAgcMode(on: 1) + let _ = self.device!.resetBuffer() + } + //run the task + sessionQueue.async { + print("lets start the task for spectrum analysis") + for i in 0..<512 { + + if let dev = self.device { + let ret = dev.readSync(buf: buf_ptr, len: Int32(self.nsamples), n_read: &nbytes) + if ret<0 { + print("data read sync returned <0 = \(ret)") + sleep(1); + continue; + } + } + print("Got \(nbytes) bytes") + + /* + let dataU8 = buf_ptr.bindMemory(to: UInt8.self, capacity: self.nsamples) + let bufferU8 = UnsafeBufferPointer(start: dataU8, count: self.nsamples) + let convertedData = Data(bufferU8) + */ + let dataI8 = buf_ptr.bindMemory(to: Int8.self, capacity: self.nsamples) + let bufferI8 = UnsafeBufferPointer(start: dataI8, count: self.nsamples) + let safeI8 = Array(bufferI8) + + let transform_result = self.fft512.computeLine(safeI8) + print(transform_result) + print("FFT result \(transform_result.count) points") + //let drawSlice = transform_result[0...SDRSpectrum.default_width-1] + //print("FFT result \(drawSlice.count) points") + self.simpleImage.drawPalletLine(line: i, pixelLine: transform_result) + self.outputImage = self.simpleImage.toCGImage()! + + print("Process the task line \(i)") + sleep(1) + } + print("SDR loop done run") + } + } +} diff --git a/Waterfall_UI/Waterfall_UI.entitlements b/Waterfall_UI/Waterfall_UI.entitlements new file mode 100644 index 0000000..9c8ecca --- /dev/null +++ b/Waterfall_UI/Waterfall_UI.entitlements @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.security.app-sandbox</key> + <true/> + <key>com.apple.security.device.usb</key> + <true/> + <key>com.apple.security.files.user-selected.read-only</key> + <true/> +</dict> +</plist> diff --git a/Waterfall_UI/Waterfall_UIApp.swift b/Waterfall_UI/Waterfall_UIApp.swift new file mode 100644 index 0000000..6d9a13b --- /dev/null +++ b/Waterfall_UI/Waterfall_UIApp.swift @@ -0,0 +1,46 @@ +// +// Waterfall_UIApp.swift +// Waterfall_UI +// +// Created by Jacky Jack on 06/01/2025. +// + +import SwiftUI +import SwiftData + + +@main +struct Waterfall_UIApp: App { + + let sdrSpectrum = SDRSpectrum() + + @Environment(\.scenePhase) private var scenePhase + + var sharedModelContainer: ModelContainer = { + let schema = Schema([ + Item.self, + ]) + let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) + + do { + return try ModelContainer(for: schema, configurations: [modelConfiguration]) + } catch { + fatalError("Could not create ModelContainer: \(error)") + } + }() + + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(sdrSpectrum) + .onChange(of: scenePhase) { phase in + if phase == .active { + Task(priority: .userInitiated) { + sdrSpectrum.startRunning() + } + } + } + } + .modelContainer(sharedModelContainer) + } +} |