summaryrefslogtreecommitdiff
path: root/Waterfall_UI
diff options
context:
space:
mode:
authorArturs Artamonovs <arturs.artamonovs@protonmail.com>2025-01-17 09:27:56 +0000
committerArturs Artamonovs <arturs.artamonovs@protonmail.com>2025-01-17 09:27:56 +0000
commit06a90001363fdcb542b531a74ed2c18f714f58c4 (patch)
treee0ae3092494e6136ec7479b0e9bda20ef930039b /Waterfall_UI
parenta0d12ecbac8fe327d1dcd4580fee594e24d4191b (diff)
downloadPrySDR-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.json11
-rw-r--r--Waterfall_UI/Assets.xcassets/AppIcon.appiconset/Contents.json58
-rw-r--r--Waterfall_UI/Assets.xcassets/Contents.json6
-rw-r--r--Waterfall_UI/ContentView.swift194
-rw-r--r--Waterfall_UI/Item.swift18
-rw-r--r--Waterfall_UI/Preview Content/Preview Assets.xcassets/Contents.json6
-rw-r--r--Waterfall_UI/SDRSpectrum.swift137
-rw-r--r--Waterfall_UI/Waterfall_UI.entitlements12
-rw-r--r--Waterfall_UI/Waterfall_UIApp.swift46
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)
+ }
+}