diff options
author | Arturs Artamonovs <arturs.artamonovs@protonmail.com> | 2025-01-06 12:30:49 +0000 |
---|---|---|
committer | Arturs Artamonovs <arturs.artamonovs@protonmail.com> | 2025-01-06 12:30:49 +0000 |
commit | a0d12ecbac8fe327d1dcd4580fee594e24d4191b (patch) | |
tree | 9a7cef9ed0de080bb8c19912f678497a4fca6a86 | |
parent | f923a3824561c6cf200c638e3d44d1cbf4adf7d8 (diff) | |
download | PrySDR-a0d12ecbac8fe327d1dcd4580fee594e24d4191b.tar.gz PrySDR-a0d12ecbac8fe327d1dcd4580fee594e24d4191b.zip |
Waterfall: UI drawing from filemain
-rw-r--r-- | PrySDR.xcodeproj/project.pbxproj | 16 | ||||
-rw-r--r-- | PrySDR.xcodeproj/xcuserdata/jackyjack.xcuserdatad/xcschemes/xcschememanagement.plist | 14 | ||||
-rw-r--r-- | Radio/Utils/WaterfallFile/NaiveFFT.swift | 7 | ||||
-rw-r--r-- | Radio/Utils/WaterfallFile/NaiveFFT512.swift | 49 | ||||
-rw-r--r-- | Radio/Utils/WaterfallFile/SimpleImage.swift | 2 | ||||
-rw-r--r-- | Radio/Utils/WaterfallFile/main.swift | 84 | ||||
-rw-r--r-- | Utils/FileReader.swift | 150 | ||||
-rw-r--r-- | Utils/PathUtils.swift | 1 | ||||
-rw-r--r-- | WaterfallFile_UI/FileSpectrum.swift | 55 | ||||
-rw-r--r-- | WaterfallFile_UI/WaterfallFile_UIApp.swift | 2 |
10 files changed, 291 insertions, 89 deletions
diff --git a/PrySDR.xcodeproj/project.pbxproj b/PrySDR.xcodeproj/project.pbxproj index 5d627d2..72c61bf 100644 --- a/PrySDR.xcodeproj/project.pbxproj +++ b/PrySDR.xcodeproj/project.pbxproj @@ -601,7 +601,7 @@ Utils/TestBladeRF/main.swift, Utils/TestRtlSdr/main.swift, Utils/WaterfallFile/main.swift, - Utils/WaterfallFile/NaiveFFT.swift, + Utils/WaterfallFile/NaiveFFT512.swift, Utils/WaterfallFile/SimpleImage.swift, ); target = 8DD98C402CC592540062D678 /* PrySDR */; @@ -804,7 +804,7 @@ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( Utils/WaterfallFile/main.swift, - Utils/WaterfallFile/NaiveFFT.swift, + Utils/WaterfallFile/NaiveFFT512.swift, Utils/WaterfallFile/SimpleImage.swift, ); target = 8D9A38762D196510009A4186 /* WaterfallFile */; @@ -812,6 +812,7 @@ 8D9A38832D19D277009A4186 /* Exceptions for "Utils" folder in "WaterfallFile" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( + FileReader.swift, PathUtils.swift, ); target = 8D9A38762D196510009A4186 /* WaterfallFile */; @@ -819,11 +820,19 @@ 8DBA9F602D2928DE008ECB92 /* Exceptions for "Radio" folder in "WaterfallFile_UI" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - Utils/WaterfallFile/NaiveFFT.swift, + Utils/WaterfallFile/NaiveFFT512.swift, Utils/WaterfallFile/SimpleImage.swift, ); target = 8D9A38452D194A3D009A4186 /* WaterfallFile_UI */; }; + 8DBA9F652D2B5158008ECB92 /* Exceptions for "Utils" folder in "WaterfallFile_UI" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + FileReader.swift, + PathUtils.swift, + ); + target = 8D9A38452D194A3D009A4186 /* WaterfallFile_UI */; + }; 8DD98C7C2CC6320C0062D678 /* Exceptions for "LA" folder in "PrySDR" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( @@ -958,6 +967,7 @@ 8D40686D2CFDE4C10064C96D /* Exceptions for "Utils" folder in "AirSpyHFIQ" target */, 8D406AB52CFF0D1F0064C96D /* Exceptions for "Utils" folder in "AirSpyIQ" target */, 8D9A37F12D180D4C009A4186 /* Exceptions for "Utils" folder in "BladeRFIQ" target */, + 8DBA9F652D2B5158008ECB92 /* Exceptions for "Utils" folder in "WaterfallFile_UI" target */, 8D9A38832D19D277009A4186 /* Exceptions for "Utils" folder in "WaterfallFile" target */, ); path = Utils; diff --git a/PrySDR.xcodeproj/xcuserdata/jackyjack.xcuserdatad/xcschemes/xcschememanagement.plist b/PrySDR.xcodeproj/xcuserdata/jackyjack.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 617cef2..0000000 --- a/PrySDR.xcodeproj/xcuserdata/jackyjack.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,14 +0,0 @@ -<?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>SchemeUserState</key> - <dict> - <key>PrySDR.xcscheme_^#shared#^_</key> - <dict> - <key>orderHint</key> - <integer>0</integer> - </dict> - </dict> -</dict> -</plist> diff --git a/Radio/Utils/WaterfallFile/NaiveFFT.swift b/Radio/Utils/WaterfallFile/NaiveFFT.swift deleted file mode 100644 index 79393b7..0000000 --- a/Radio/Utils/WaterfallFile/NaiveFFT.swift +++ /dev/null @@ -1,7 +0,0 @@ -// -// NaiveFFT.swift -// PrySDR -// -// Created by Jacky Jack on 05/01/2025. -// - diff --git a/Radio/Utils/WaterfallFile/NaiveFFT512.swift b/Radio/Utils/WaterfallFile/NaiveFFT512.swift new file mode 100644 index 0000000..b4dca7e --- /dev/null +++ b/Radio/Utils/WaterfallFile/NaiveFFT512.swift @@ -0,0 +1,49 @@ +// +// NaiveFFT.swift +// PrySDR +// +// Created by Jacky Jack on 05/01/2025. +// + +import Foundation +import Accelerate + +//SUpports now only fixed sizes and not flexible implementation just a PoC +class NaiveFFT512 { + + static let sampleCount = 512 + let forwardDCT = vDSP.DCT(count: sampleCount, transformType: .II)! + + init() { + + } + + func getSampleCount() -> Int { + return NaiveFFT512.sampleCount + } + + func computeLine(_ processingArray: [Int8]) -> [Float] { + + var dataFloat = [Float](repeating: 0.0, count: NaiveFFT512.sampleCount) + + if processingArray.count != NaiveFFT512.sampleCount { + print("Not supporting arrays not equail to \(NaiveFFT512.sampleCount)") + return [] + } + + vDSP.convertElements(of: processingArray, to: &dataFloat) + //dataFloat = vDSP.add(127.0, dataFloat) + //print(dataFloat) + + //move from -127.0 to 128.0 range -1.0...1.0 + //var adjusted = vDSP.divide(dataFloat, Float(sampleCount)) + var adjusted = dataFloat + //print(adjusted) + + var transform_result = forwardDCT.transform(adjusted) + transform_result = vDSP.absolute(transform_result) + + return transform_result + } + +} diff --git a/Radio/Utils/WaterfallFile/SimpleImage.swift b/Radio/Utils/WaterfallFile/SimpleImage.swift index c05709f..4f1bed8 100644 --- a/Radio/Utils/WaterfallFile/SimpleImage.swift +++ b/Radio/Utils/WaterfallFile/SimpleImage.swift @@ -32,7 +32,7 @@ public class SimpleImage { func drawPalletLine(line: Int, pixelLine:[Float]) { for i in 0..<self.width { var pixel = pixelLine[i] - if pixel>256.0 { + if pixel>255.0 { //print("values to high") pixel = 255.0 } diff --git a/Radio/Utils/WaterfallFile/main.swift b/Radio/Utils/WaterfallFile/main.swift index cf72d56..039be72 100644 --- a/Radio/Utils/WaterfallFile/main.swift +++ b/Radio/Utils/WaterfallFile/main.swift @@ -7,84 +7,58 @@ import Foundation import Accelerate +import ArgumentParser + +//set the command line arguments +struct CommandLineArgs: ParsableCommand { + @Argument var inputFile:String = "" + @Argument var outputFile:String = "" +} + +let args = CommandLineArgs.parseOrExit() print("Read binary file") -let input_filename="rtlsdr_100M_m.cu8" +let input_filename=args.inputFile //get data from u8 file -let dir = getCurrentExecutableDir() -let filemgr = FileManager.default - -if !filemgr.fileExists(atPath: input_filename) { - print("Cant find file \(input_filename)") +let fileReader = FileReader() +do { + try fileReader.open(filename: input_filename) +} catch { + print("Cant open file \(input_filename)") exit(0) } - -let fileHandle: FileHandle? = FileHandle(forReadingAtPath: dir+"/"+input_filename) var i8_arr:[Int8]? = nil -if let file = fileHandle { - file.seek(toFileOffset: 0) - do { - if let databuf = try file.readToEnd() { - - print("read \(databuf.count) \(databuf) bytes") - //let temp_arr = [Int8](databuf) - i8_arr = [Int8](repeating: 0, count: databuf.count) - - for i in 0..<i8_arr!.count { - print(String(format:"%02X", databuf[i]),terminator: "") - //convert from 0..255 to -127..128 - let val = databuf[i] - if val <= 127 { - i8_arr![i] = Int8(val)-127 - } else { - i8_arr![i] = Int8(val-128) - } - //print(String(format:"%02X", i8_arr![i]),terminator: "") - } - } - } catch { - print(error) - exit(0) - } - +do { + try i8_arr = fileReader.readAll() +} catch { + print("Got error \(error)") } -fileHandle?.closeFile() + +fileReader.close() //all data in buffer lets process data //convert all u8 data to float's //is there other ways to do that? -/*for i in 0..<dataFloat.count { - dataFloat[i] = Float(u8_arr![i]) -}*/ //will generate 512x512 image let sampleCount = 512 let img = SimpleImage(width: sampleCount, height: sampleCount) let number_of_lines = (i8_arr!.count/sampleCount) -let forwardDCT = vDSP.DCT(count: sampleCount, transformType: .II)! var frequencyDomain:[Float] = [] var transform_result:[Float] = .init(repeating: 0.0, count: sampleCount) -var dataFloat = [Float](repeating: 0.0, count: sampleCount) +let fft512 = NaiveFFT512() + for i in 0..<sampleCount { let sample_idx = i*sampleCount if i < number_of_lines { let processingSlice = i8_arr![sample_idx...sample_idx+512-1] let processingArray = Array(processingSlice) - vDSP.convertElements(of: processingArray, to: &dataFloat) - //dataFloat = vDSP.add(127.0, dataFloat) - //print(dataFloat) - - //move from -127.0 to 128.0 range -1.0...1.0 - //var adjusted = vDSP.divide(dataFloat, Float(sampleCount)) - var adjusted = dataFloat - //print(adjusted) + transform_result = fft512.computeLine(processingArray) - transform_result = forwardDCT.transform(adjusted) - transform_result = vDSP.absolute(transform_result) - let max = vDSP.maximum(transform_result) + //let max = vDSP.maximum(transform_result) //print("max=\(max)") //frequencyDomain.append(contentsOf: transform_result) img.drawPalletLine(line: i, pixelLine: transform_result) @@ -99,15 +73,9 @@ img.drawPixel(0, 0, PixelData(a: 255, r: 255, g: 0, b: 0)) img.drawPixel(sampleCount-1, 0, PixelData(a: 255, r: 0, g: 255, b: 0)) img.drawPixel(0, sampleCount-1, PixelData(a: 255, r: 0, g: 255, b: 0)) img.drawPixel(sampleCount-1, sampleCount-1, PixelData(a: 255, r: 0, g: 0, b: 255)) - - //print(dataFloat) - - //output data to image file -img.saveAsJPEG("fft_100m_512.jpeg") - - +img.saveAsJPEG(args.outputFile) print("All computations are done") diff --git a/Utils/FileReader.swift b/Utils/FileReader.swift new file mode 100644 index 0000000..d4be92d --- /dev/null +++ b/Utils/FileReader.swift @@ -0,0 +1,150 @@ +// +// FileReader.swift +// PrySDR +// +// Created by Jacky Jack on 06/01/2025. +// + +import Foundation + +enum FileReaderError: Error { + case noFile + case readError + case noData + case seekError +} + +class FileReader { + + var filepath: String? + let filemgr = FileManager.default + var fileHandle: FileHandle? + + init() { + + } + + func open(filename: String) throws { + let dir = getCurrentExecutableDir() + + print(dir+"/"+filename) + if !filemgr.fileExists(atPath: filename) { + print("Cant find file \(filename)") + throw FileReaderError.noFile + } + + fileHandle = FileHandle(forReadingAtPath: dir+"/"+filename) + if (fileHandle == nil) { + print("FileHandler failed") + } + } + + func readBuffer(_ count: Int) -> [UInt8] { + print("FileReader not implemented") + return [] + } + + func readBuffer(_ count: Int) -> [Int8] { + print("FileReader not implemented") + return [] + } + + func readBuffer(_ count: Int) -> [UInt16] { + print("FileReader not implemented") + return [] + } + + func readBuffer(_ count: Int) -> [Int16] { + print("FileReader not implemented") + return [] + } + + func readBuffer(_ count: Int) -> [Float] { + print("FileReader not implemented") + return [] + } + + func readAll() throws -> [UInt8] { + var ret_arr:[UInt8]? = nil + if let file = fileHandle { + do { + if let databuf = try file.readToEnd() { + //print("read \(databuf.count) \(databuf) bytes") + //let temp_arr = [Int8](databuf) + ret_arr = [UInt8](repeating: 0, count: databuf.count) + for i in 0..<ret_arr!.count { + //print(String(format:"%02X", databuf[i]),terminator: "") + //convert from 0..255 to -127..128, from UInt8->Int8 + ret_arr![i] = databuf[i] + } + } + } catch { + print("ReadAll u8 error:\(error)") + throw FileReaderError.readError + } + } + if ret_arr == nil { + return [] + } else { + return ret_arr! + } + } + + func readAll() throws -> [Int8] { + var ret_arr:[Int8]? = nil + if let file = fileHandle { + do { + if let databuf = try file.readToEnd() { + //print("read \(databuf.count) \(databuf) bytes") + //let temp_arr = [Int8](databuf) + ret_arr = [Int8](repeating: 0, count: databuf.count) + for i in 0..<ret_arr!.count { + //print(String(format:"%02X", databuf[i]),terminator: "") + //convert from 0..255 to -127..128, from UInt8->Int8 + let val = databuf[i] + if val <= 127 { + ret_arr![i] = Int8(val)-127 + } else { + ret_arr![i] = Int8(val-128) + } + //print(String(format:"%02X", i8_arr![i]),terminator: "") + } + } + } catch { + print("ReadAll i8 error:\(error)") + throw FileReaderError.readError + } + } + if ret_arr == nil { + return [] + } else { + return ret_arr! + } + } + + func readAll() throws -> [UInt16] { + print("FileReader not implemented") + return [] + } + + func readAll() throws -> [Int16] { + print("FileReader not implemented") + return [] + } + + func readAll() throws -> [Float] { + print("FileReader not implemented") + return [] + } + + func seek(toFileOffset: UInt64) throws { + if let file = self.fileHandle { + file.seek(toFileOffset: toFileOffset) + } + } + + func close() { + fileHandle?.closeFile() + } + +} diff --git a/Utils/PathUtils.swift b/Utils/PathUtils.swift index a82261a..164baa1 100644 --- a/Utils/PathUtils.swift +++ b/Utils/PathUtils.swift @@ -10,4 +10,5 @@ import Foundation //get current run directory func getCurrentExecutableDir() -> String { return Process().currentDirectoryPath + //return FileManager.default.currentDirectoryPath } diff --git a/WaterfallFile_UI/FileSpectrum.swift b/WaterfallFile_UI/FileSpectrum.swift index 6415dfa..ce64836 100644 --- a/WaterfallFile_UI/FileSpectrum.swift +++ b/WaterfallFile_UI/FileSpectrum.swift @@ -13,8 +13,15 @@ import CoreImage class FileSpectrum: NSObject, ObservableObject { - let default_width = 512 - let default_height = 512 + static let default_width = 512 + static let default_height = 512 + static let defaultSleepTime:UInt32 = 1 + var offset:Int=0 + let fileReader = FileReader() + var dataBuffer:[Int8] = .init(repeating: 0, count: FileSpectrum.default_width) + var allData:[Int8]? = nil + let fft512 = NaiveFFT512() + var line_counter:Int = 0 let sessionQueue = DispatchQueue(label: "sessionQueue", attributes: [], @@ -22,7 +29,7 @@ class FileSpectrum: NSObject, ObservableObject { @Published var outputImage = emptyCGImage - let simpleImage = SimpleImage(width: 256, height: 256) + let simpleImage = SimpleImage(width: 512, height: 512) /// A 1x1 Core Graphics image. static var emptyCGImage: CGImage = { @@ -40,24 +47,62 @@ class FileSpectrum: NSObject, ObservableObject { return buffer.makeCGImage(cgImageFormat: fmt!)! }() + + init(_ input_filename: String) { + //get data from u8 file + + do { + try fileReader.open(filename: input_filename) + } catch { + print("Cant open file \(input_filename)") + exit(0) + } + var i8_arr:[Int8]? = nil + do { + try i8_arr = fileReader.readAll() + allData = i8_arr + } catch { + print("Got error \(error)") + } + + fileReader.close() + } func makeSpectrogramImage() -> NSImage { return NSImage(cgImage: FileSpectrum.emptyCGImage, size: .zero) } + + //just get data time to time from databuffer and pretend to be realtime data + func dataFileProcessor() { + //all now is about 512pixels + let processingSlice = allData![offset...offset+FileSpectrum.default_width-1] + let dataBuffer = Array(processingSlice) + var transform_result:[Float] = .init(repeating: 0.0, count: FileSpectrum.default_width) + //DispatchQueue.main.async { + transform_result = self.fft512.computeLine(dataBuffer) + self.simpleImage.drawPalletLine(line: self.line_counter, pixelLine: transform_result) + if self.offset+FileSpectrum.default_width < allData!.count { + self.offset += FileSpectrum.default_width + self.line_counter += 1 + } + //} + + } func startRunning() { sessionQueue.async { print("lets start the task for spectrum analysis") - for i in 0..<100 { + for i in 0..<512 { print("Process the task \(i)") self.simpleImage.drawPixel(i, i, PixelData(a: 255, r: 255, g: 0, b: 0)) DispatchQueue.main.async { + self.dataFileProcessor() self.outputImage = self.simpleImage.toCGImage()! } - sleep(1) + sleep(FileSpectrum.defaultSleepTime) } } } diff --git a/WaterfallFile_UI/WaterfallFile_UIApp.swift b/WaterfallFile_UI/WaterfallFile_UIApp.swift index 959fe55..be9f25a 100644 --- a/WaterfallFile_UI/WaterfallFile_UIApp.swift +++ b/WaterfallFile_UI/WaterfallFile_UIApp.swift @@ -11,7 +11,7 @@ import SwiftData @main struct WaterfallFile_UIApp: App { - let fileSpectrum = FileSpectrum() + let fileSpectrum = FileSpectrum("rtlsdr_99.5M_m.cu8") @Environment(\.scenePhase) private var scenePhase |