The Brown Hats Software Depot: F' Around & Find Out!

Login | Register (Invite Only)

pagetelegram's Profile

Title: Page Telegram of Amfile.org

Description: Brown Hats Collective F' Around and Find Out user of the Interwebs and Project Developer for a Cause Toward Insuring a Future of Liberty without Illusion.

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@=:---#@@@#%@@@*=+#@%====@@@@..:--@#====@-==#@@@+===#@%=-+@... -@@@@*@@@=+@%-*@
@@@.*@-.@@@.:@@==@:=@+ -..*@@@:# @:@=.=. #%.#@@@@ ::..@.%#.@@ %+ %@@% #@@-.@+ #@
@@@.*@+.@@@:.@@.#@%*@*.@.-*@@%@#.@.@=.%..#% *@@@@ %:+:+:@@=@@ #+ %@@+..@@= %: @@
@@@..-.=@@:* %@.%#=+@+..:@@@@@@*.@@@=..:@@@ %@%%% :.@@+-@:=#@ ...@@@:+ @@+ :- @@
@@@.+@@@@@.- =@.#@.-@*.@:=@@@@@+ @@@+:@-:@% @@*=@ @=%#+-@-.#@ @*.%@@.- #@=. + @@
@@@.+@@@@+.@..@:*%.*@*.@%.@@@@%- +@@=:@%.@@.%@:=@ @@=*#:@::@@ *# =@--% -@:- * @@
@@-::#@@%::+::*@-:=%@::.::@@@@+-::@*:.:::@::...%:::::#@*:==@#==@+:*.:=::%:-=::=@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#-.  .-#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*.        .+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-            :@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:              .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:      .++:      .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=     :@@@@@@-     -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@#     =@@@@@@@@+     #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@:    =@@@@@@@@@@+    .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@#    .@@@@@@@@@@@@.    *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@.    %@@@@@@@@@@@@%.    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@%    -@@@@@@@@@@@@@@-    #@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@=    #@@@@@@@@@@@@@@%    -@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@.   .@@@@@@@@@@@@@@@@.   .@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@    -@@@@%##***#%@@@@+    %@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@*    %@#:..       .:+@%    *@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@=    =.               :    -@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@:                          .@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@.                          .@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@.       .=%@@@@@@@+:       .@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@#      .%@@@@@@@@@@@@@-      -@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@#     .*@@@@@@@@@@@@@@@@%:     -@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@%.    .@@@@@@*%@@@@%#@@@@@@=     *@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@.    :@@@@@@@@:-@@*.%@@@@@@@+     %@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@+    :@@@@@@@@@@---.@@@@@@@@@@=    :@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@.    @@@@@@@@@@@.   @@@@@@@@@@@:    #@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@=    #@@@+--#@@@@    #@@@@%#@@@@@.   .@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@.   :@%-     .%@@-  .@@@*.   .+@@=    #@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@#    *+.       .@@@#*@@@=       .#@    -@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@-   .#          =@@-.@@%.         +=   .@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@.   -@.          @@. %@:          #%    %@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@.   #@-          +%  =%          .@@.   *@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@%    %@%          :+  .+          +@@.   =@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@#    @@@:         .:  .-         .@@@:   -@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@*   .@@@%         #   .#         =@@@:   :@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@*   .@@@@=       -@    @-       .@@@@:   :@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@#    @@@@@.     .@@    @@.      %@@@@:   -@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@%    %@@@@@:   :@@@    @@@.   .*@@@@@.   =@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@.   #@@@@@@#+#@@@@    @@@@=.:%@@@@@@.   *@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@.   :@@@@@@-..:*@@    @@#-..:%@@@@@#    %@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@-   .@@@@@:     .@    @.     .@@@@@-   .@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@#    *@@@%       @.  .@       *@@@@    -@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@.   .@@@+       @-  .@.      -@@@=    #@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@+    +@@%.     .@*  :@-     .*@@%.   .@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@.    %@@@-    *@%  #@%.   :%@@@.    %@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@*    .@@@@%=-+@@@. @@@*--#@@@@-    -@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@:    .@@@@@@@@@@*=@@@@@@@@@@:     @@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@.    .%@@@@@@@@@@@@@@@@@@@-    .#@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@%.     +@@@@@@@@@@@@@@@@#.     +@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@%.     .+@@@@@@@@@@@@%:      +@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@%.       -*%@@@@@#=.      .*@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@:                      .%@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@#.                   +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+.              .=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=.        .-%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%###%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Repositories

Viewing: LivePlotterLivePlot.bas.txt

' Live EMF Plotter 3.0 by AmFile.org August 2025
' Designed for exotic spectrum identification and abuse assessments using sensory and RF equipment data logging.
' This script enables interactive visualization of EMF data from CSV logs, allowing real-time adjustments for
' detailed analysis of magnetic field anomalies, potential interference patterns, and abuse indicators in RF environments.
' Adapt software as needed to live plot from any dataset.

DEFINT A-Z
OPTION BASE 1

DIM SHARED DataCount AS LONG
DIM SHARED MaxData AS LONG
DIM SHARED TimeStamp AS STRING * 20
DIM SHARED XVal AS SINGLE
DIM SHARED YVal AS SINGLE
DIM SHARED ZVal AS SINGLE
DIM SHARED MagVal AS SINGLE
DIM SHARED TempMag AS SINGLE
DIM SHARED FileName AS STRING
DIM SHARED HomeActive AS INTEGER
DIM SHARED Multiplier AS INTEGER
DIM SHARED GrayscaleMode AS INTEGER
DIM SHARED AxesColor AS INTEGER
DIM SHARED EMFPalette(0 TO 15) AS _UNSIGNED LONG
Multiplier = 1 ' Default multiplier
GrayscaleMode = 0 ' Default normal mode

' Colors for contrast: Blue (1) for X, Green (10) for Y, Cyan (11) for Z, Red (12) for Mag
CONST COLORX = 1
CONST COLORY = 10
CONST COLORZ = 11
CONST COLORMAG = 12

' Screen dimensions for SCREEN 12: 640x480
CONST SCREENWIDTH = 640
CONST SCREENHEIGHT = 480
CONST PLOTHEIGHT = 360 ' Plot area height, adjusted to avoid overlap
CONST PLOTBOTTOM = SCREENHEIGHT - 80 ' Bottom margin for labels
CONST PLOTLEFT = 50 ' Left margin for Y axis
CONST PLOTRIGHT = SCREENWIDTH - 50 ' Right margin
CONST MAX_PLOT_POINTS = 640 ' Max points to plot for performance on large scopes

' Initialize arrays with large initial size to reduce resizing
REDIM SHARED TimeStamp(1 TO 100000) AS STRING * 20
REDIM SHARED XVal(1 TO 100000) AS SINGLE
REDIM SHARED YVal(1 TO 100000) AS SINGLE
REDIM SHARED ZVal(1 TO 100000) AS SINGLE
REDIM SHARED MagVal(1 TO 100000) AS SINGLE

SCREEN 12
CALL SetPalette(GrayscaleMode) ' Initialize to normal mode

' Cross-platform CSV file listing
DIM SHARED CSVFiles(100) AS STRING
DIM SHARED FileCount AS INTEGER
FileCount = 0
IF _OS$ = "WINDOWS" THEN
    SHELL "dir *.csv /b > csvlist.txt"
ELSE
    SHELL "dir *.csv /b > csvlist.txt"
END IF
OPEN "csvlist.txt" FOR INPUT AS #1
DO WHILE NOT EOF(1)
    FileCount = FileCount + 1
    LINE INPUT #1, CSVFiles(FileCount)
LOOP
CLOSE #1
KILL "csvlist.txt"

IF FileCount = 0 THEN
    PRINT "No CSV files found in current directory."
    END
END IF

' Prompt user to select file
PRINT "Select CSV file for EMF data analysis:"
FOR i = 1 TO FileCount
    PRINT i; ": "; CSVFiles(i)
NEXT i
DO
    INPUT "Enter number: ", sel
LOOP UNTIL sel >= 1 AND sel <= FileCount
FileName = CSVFiles(sel)

GOSUB LoadData
MaxData = DataCount

' Initial scope: full dataset
StartIdx& = 1
EndIdx& = DataCount
HomeActive = 0


DO
    ' Handle auto-reload if Home active
    IF HomeActive THEN
        TIMER ON
        StartTime! = TIMER
        DO
            IF TIMER - StartTime! >= 2 THEN
                GOSUB LoadData
                MaxData = DataCount
                EndIdx& = MaxData
                StartIdx& = MaxData - 999
                IF StartIdx& < 1 THEN StartIdx& = 1
                EXIT DO
            END IF
        LOOP
    END IF

    CALL SetPalette(GrayscaleMode) ' Set palette based on mode

    ' Plot the data
    CLS
    ' Draw top and bottom lines for scope boundaries
    LINE (PLOTLEFT, PLOTBOTTOM)-(PLOTRIGHT, PLOTBOTTOM), AxesColor ' Bottom line
    LINE (PLOTLEFT, PLOTBOTTOM - PLOTHEIGHT)-(PLOTRIGHT, PLOTBOTTOM - PLOTHEIGHT), AxesColor ' Top line
    LOCATE 28, 1: PRINT "Time/Index"
    LOCATE 14, 1: PRINT "uT"

    ' Validate scope indices
    IF StartIdx& < 1 THEN StartIdx& = 1
    IF EndIdx& > MaxData THEN EndIdx& = MaxData
    IF StartIdx& > EndIdx& THEN SWAP StartIdx&, EndIdx&
    IF EndIdx& < StartIdx& + 10 THEN EndIdx& = StartIdx& + 10

    ' Find min/max for Y scaling and peaks
    MinY! = 1E+38
    MaxY! = -1E+38
    peakX! = -1E+38
    peakY! = -1E+38
    peakZ! = -1E+38
    peakM! = -1E+38
    FOR i& = StartIdx& TO EndIdx&
        IF XVal(i&) < MinY! THEN MinY! = XVal(i&)
        IF YVal(i&) < MinY! THEN MinY! = YVal(i&)
        IF ZVal(i&) < MinY! THEN MinY! = ZVal(i&)
        IF MagVal(i&) < MinY! THEN MinY! = MagVal(i&)
        IF XVal(i&) > MaxY! THEN MaxY! = XVal(i&)
        IF YVal(i&) > MaxY! THEN MaxY! = YVal(i&)
        IF ZVal(i&) > MaxY! THEN MaxY! = ZVal(i&)
        IF MagVal(i&) > MaxY! THEN MaxY! = MagVal(i&)
        IF XVal(i&) > peakX! THEN peakX! = XVal(i&)
        IF YVal(i&) > peakY! THEN peakY! = YVal(i&)
        IF ZVal(i&) > peakZ! THEN peakZ! = ZVal(i&)
        IF MagVal(i&) > peakM! THEN peakM! = MagVal(i&)
    NEXT i&
    IF MaxY! = MinY! THEN MaxY! = MinY! + 1 ' Avoid divide by zero

    ' Plot width based on downsampled points
    size& = EndIdx& - StartIdx& + 1
    CONST MAX_PLOT_POINTS = 640 ' Max points to plot for performance on large scopes
    IF size& > MAX_PLOT_POINTS THEN
        stepSize! = size& / MAX_PLOT_POINTS
        PlotWidth! = (PLOTRIGHT - PLOTLEFT) / MAX_PLOT_POINTS
    ELSE
        stepSize! = 1
        PlotWidth! = (PLOTRIGHT - PLOTLEFT) / size&
    END IF

    ' Plot each series with downsampling if needed
    LastX! = PLOTLEFT
    LastY_X! = PLOTBOTTOM - ((XVal(StartIdx&) - MinY!) / (MaxY! - MinY!)) * PLOTHEIGHT
    LastY_Y! = PLOTBOTTOM - ((YVal(StartIdx&) - MinY!) / (MaxY! - MinY!)) * PLOTHEIGHT
    LastY_Z! = PLOTBOTTOM - ((ZVal(StartIdx&) - MinY!) / (MaxY! - MinY!)) * PLOTHEIGHT
    LastY_Mag! = PLOTBOTTOM - ((MagVal(StartIdx&) - MinY!) / (MaxY! - MinY!)) * PLOTHEIGHT

    FOR p = 1 TO MAX_PLOT_POINTS - 1
        CurrX! = PLOTLEFT + p * PlotWidth!
        binStart& = StartIdx& + (p - 1) * stepSize!
        binEnd& = binStart& + stepSize! - 1
        IF binEnd& > EndIdx& THEN binEnd& = EndIdx&
        sumX! = 0: sumY! = 0: sumZ! = 0: sumMag! = 0
        count& = 0
        FOR j& = binStart& TO binEnd&
            sumX! = sumX! + XVal(j&)
            sumY! = sumY! + YVal(j&)
            sumZ! = sumZ! + ZVal(j&)
            sumMag! = sumMag! + MagVal(j&)
            count& = count& + 1
        NEXT j&
        IF count& > 0 THEN
            avgX! = sumX! / count&
            avgY! = sumY! / count&
            avgZ! = sumZ! / count&
            avgMag! = sumMag! / count&
        ELSE
            avgX! = 0
            avgY! = 0
            avgZ! = 0
            avgMag! = 0
        END IF
        CurrY_X! = PLOTBOTTOM - ((avgX! - MinY!) / (MaxY! - MinY!)) * PLOTHEIGHT
        CurrY_Y! = PLOTBOTTOM - ((avgY! - MinY!) / (MaxY! - MinY!)) * PLOTHEIGHT
        CurrY_Z! = PLOTBOTTOM - ((avgZ! - MinY!) / (MaxY! - MinY!)) * PLOTHEIGHT
        CurrY_Mag! = PLOTBOTTOM - ((avgMag! - MinY!) / (MaxY! - MinY!)) * PLOTHEIGHT

        LINE (LastX!, LastY_X!)-(CurrX!, CurrY_X!), COLORX
        LINE (LastX!, LastY_Y!)-(CurrX!, CurrY_Y!), COLORY
        LINE (LastX!, LastY_Z!)-(CurrX!, CurrY_Z!), COLORZ
        LINE (LastX!, LastY_Mag!)-(CurrX!, CurrY_Mag!), COLORMAG

        LastX! = CurrX!
        LastY_X! = CurrY_X!
        LastY_Y! = CurrY_Y!
        LastY_Z! = CurrY_Z!
        LastY_Mag! = CurrY_Mag!
    NEXT p

    ' Title at top
    LOCATE 1, 1: PRINT "Live EMF Plotter 3.0 by AmFile.org August 2025"
    ' Date/time scope range at top
    tempStart$ = ""
    tempEnd$ = ""
    IF StartIdx& >= 1 AND StartIdx& <= MaxData THEN tempStart$ = TimeStamp(StartIdx&)
    IF EndIdx& >= 1 AND EndIdx& <= MaxData THEN tempEnd$ = TimeStamp(EndIdx&)
    LOCATE 2, 1: PRINT "Scope: "; StartIdx&; " to "; EndIdx&; " ("; tempStart$; " to "; tempEnd$; ")"

    ' All other labels at bottom below the chart scope
    LOCATE 26, 1: PRINT "Peak X: "; peakX!; " Y: "; peakY!; " Z: "; peakZ!; " M: "; peakM!; " uT"
    IF GrayscaleMode THEN
        LOCATE 27, 1: PRINT "X: Dark Gray, Y: Medium Gray, Z: Light Gray, Mag: Lighter Gray"
    ELSE
        LOCATE 27, 1: PRINT "X: Blue, Y: Green, Z: Cyan, Mag: Red"
    END IF
    LOCATE 28, 1: PRINT "[H]elp [P]rint Mode"
    IF HomeActive THEN LOCATE 29, 1: PRINT "(Home Active - Last 1000, Auto-Reload)"

    ' Handle input
    DO
        k$ = INKEY$
    LOOP WHILE k$ = ""

    Inc& = Multiplier * 10 ' Base increment, adjust as needed
    CtrlDown = _KEYDOWN(100305) OR _KEYDOWN(100306) ' Ctrl key (left or right)

    SELECT CASE UCASE$(k$)
        CASE CHR$(27): END ' ESC
        CASE "S"
            ' Save screenshot as 24-bit BMP
            DateTime$ = MID$(DATE$, 7, 4) + MID$(DATE$, 1, 2) + MID$(DATE$, 4, 2) + "_" + MID$(TIME$, 1, 2) + MID$(TIME$, 4, 2) + MID$(TIME$, 7, 2)
            GOSUB SaveBMP
        CASE "H"
            GOSUB ShowHelp
        CASE "P"
            GrayscaleMode = NOT GrayscaleMode
        CASE CHR$(0) + CHR$(72) ' Up - Widen (zoom out)
            center& = (StartIdx& + EndIdx&) / 2
            width& = EndIdx& - StartIdx& + 1
            newWidth& = width& + Inc& * 2 ' Add to both sides
            StartIdx& = center& - newWidth& / 2
            EndIdx& = center& + newWidth& / 2 - 1
            IF StartIdx& < 1 THEN StartIdx& = 1
            IF EndIdx& > MaxData THEN EndIdx& = MaxData
        CASE CHR$(0) + CHR$(80) ' Down - Thin (zoom in)
            center& = (StartIdx& + EndIdx&) / 2
            width& = EndIdx& - StartIdx& + 1
            newWidth& = width& - Inc& * 2 ' Subtract from both sides
            IF newWidth& < 10 THEN newWidth& = 10
            StartIdx& = center& - newWidth& / 2
            EndIdx& = center& + newWidth& / 2 - 1
            IF StartIdx& < 1 THEN StartIdx& = 1
            IF EndIdx& > MaxData THEN EndIdx& = MaxData
        CASE CHR$(0) + CHR$(75) ' Left - Shift left
            IF CtrlDown THEN Shift& = 1000 ELSE Shift& = Inc&
            StartIdx& = StartIdx& - Shift&
            EndIdx& = EndIdx& - Shift&
            IF StartIdx& < 1 THEN StartIdx& = 1
            IF EndIdx& < StartIdx& + 10 THEN EndIdx& = StartIdx& + 10
            IF EndIdx& > MaxData THEN EndIdx& = MaxData
        CASE CHR$(0) + CHR$(77) ' Right - Shift right
            IF CtrlDown THEN Shift& = 1000 ELSE Shift& = Inc&
            StartIdx& = StartIdx& + Shift&
            EndIdx& = EndIdx& + Shift&
            IF EndIdx& > MaxData THEN EndIdx& = MaxData
            IF StartIdx& > EndIdx& - 10 THEN StartIdx& = EndIdx& - 10
            IF StartIdx& < 1 THEN StartIdx& = 1
        CASE CHR$(0) + CHR$(79) ' End - Full dataset
            StartIdx& = 1
            EndIdx& = MaxData
            HomeActive = 0
        CASE CHR$(0) + CHR$(71) ' Home - Last 1000
            EndIdx& = MaxData
            StartIdx& = MaxData - 999
            IF StartIdx& < 1 THEN StartIdx& = 1
            HomeActive = -1
        CASE "1" TO "9" ' Numpad for multiplier
            Multiplier = VAL(k$)
    END SELECT

LOOP

LoadData:
DataCount = 0
OPEN FileName FOR INPUT AS #2
DO WHILE NOT EOF(2)
    LINE INPUT #2, line$
    IF line$ <> "" THEN
        comma1 = INSTR(line$, ",")
        comma2 = INSTR(comma1 + 1, line$, ",")
        comma3 = INSTR(comma2 + 1, line$, ",")
        comma4 = INSTR(comma3 + 1, line$, ",")
        comma5 = INSTR(comma4 + 1, line$, ",")
        IF comma1 > 0 AND comma2 > comma1 AND comma3 > comma2 AND comma4 > comma3 AND comma5 > comma4 THEN
            DataCount = DataCount + 1
            IF DataCount > UBOUND(TimeStamp) THEN
                newSize& = DataCount * 1.5 + 10000 ' Grow dynamically
                REDIM _PRESERVE SHARED TimeStamp(1 TO newSize&) AS STRING * 20
                REDIM _PRESERVE SHARED XVal(1 TO newSize&) AS SINGLE
                REDIM _PRESERVE SHARED YVal(1 TO newSize&) AS SINGLE
                REDIM _PRESERVE SHARED ZVal(1 TO newSize&) AS SINGLE
                REDIM _PRESERVE SHARED MagVal(1 TO newSize&) AS SINGLE
            END IF
            TimeStamp(DataCount) = LEFT$(line$, comma1 - 1)
            XVal(DataCount) = VAL(MID$(line$, comma1 + 1, comma2 - comma1 - 1))
            YVal(DataCount) = VAL(MID$(line$, comma2 + 1, comma3 - comma2 - 1))
            ZVal(DataCount) = VAL(MID$(line$, comma3 + 1, comma4 - comma3 - 1))
            MagVal(DataCount) = VAL(MID$(line$, comma4 + 1, comma5 - comma4 - 1))
        END IF
    END IF
LOOP
CLOSE #2
IF DataCount = 0 THEN
    PRINT "No valid data loaded from CSV."
    END
END IF
RETURN

SaveBMP:
DIM FileSize AS LONG
DIM y AS INTEGER, x AS INTEGER, c AS INTEGER, r AS INTEGER, g AS INTEGER, b AS INTEGER
DIM rgb AS _UNSIGNED LONG
DIM Signature AS STRING * 2
DIM Reserved AS STRING * 4
DIM Offset AS STRING * 4
DIM HeaderSize AS STRING * 4
DIM Width AS STRING * 4
DIM Height AS STRING * 4
DIM Planes AS STRING * 2
DIM BitsPerPixel AS STRING * 2
DIM Compression AS STRING * 4
DIM ImageSize AS STRING * 4
DIM XPelsPerMeter AS STRING * 4
DIM YPelsPerMeter AS STRING * 4
DIM ColorsUsed AS STRING * 4
DIM ImportantColors AS STRING * 4
DIM Blue AS STRING * 1
DIM Green AS STRING * 1
DIM Red AS STRING * 1

' Create a standard 24-bit Windows BMP file
OPEN DateTime$ + ".bmp" FOR BINARY AS #3

' BITMAPFILEHEADER (14 bytes)
Signature = CHR$(66) + CHR$(77) ' "BM" signature
PUT #3, , Signature
FileSize = 54 + SCREENWIDTH * SCREENHEIGHT * 3 ' Header + pixel data (24-bit)
FileSizeStr$ = MKL$(FileSize) ' Predefine MKL$ result
PUT #3, , FileSizeStr$ ' File size
Reserved = STRING$(4, 0) ' Reserved bytes
PUT #3, , Reserved
Offset = MKL$(54) ' Offset to pixel data
PUT #3, , Offset

' BITMAPINFOHEADER (40 bytes)
HeaderSize = MKL$(40) ' Header size
PUT #3, , HeaderSize
Width = MKL$(SCREENWIDTH) ' Width
PUT #3, , Width
Height = MKL$(SCREENHEIGHT) ' Height
PUT #3, , Height
Planes = MKI$(1) ' Planes
PUT #3, , Planes
BitsPerPixel = MKI$(24) ' Bits per pixel
PUT #3, , BitsPerPixel
Compression = MKL$(0) ' Compression (none)
PUT #3, , Compression
ImageSize = MKL$(SCREENWIDTH * SCREENHEIGHT * 3) ' Image size
PUT #3, , ImageSize
XPelsPerMeter = MKL$(2835) ' X pixels per meter (approx 72 DPI)
PUT #3, , XPelsPerMeter
YPelsPerMeter = MKL$(2835) ' Y pixels per meter
PUT #3, , YPelsPerMeter
ColorsUsed = MKL$(0) ' Colors used (0 for 24-bit)
PUT #3, , ColorsUsed
ImportantColors = MKL$(0) ' Important colors
PUT #3, , ImportantColors

' Pixel data (bottom-up, 24-bit RGB)
FOR y = SCREENHEIGHT - 1 TO 0 STEP -1
    FOR x = 0 TO SCREENWIDTH - 1
        c = POINT(x, y) ' Get pixel color index (0-15)
        IF c < 0 OR c > 15 THEN c = 0 ' Validate color index to prevent errors
        rgb = EMFPalette(c) ' Get RGB value from palette
        r = _RED32(rgb) ' Extract red using QB64 built-in
        g = _GREEN32(rgb) ' Extract green using QB64 built-in
        b = _BLUE32(rgb) ' Extract blue using QB64 built-in
        Blue = CHR$(b)
        Green = CHR$(g)
        Red = CHR$(r)
        PUT #3, , Blue
        PUT #3, , Green
        PUT #3, , Red ' Write BGR one byte at a time
    NEXT x
NEXT y

CLOSE #3
RETURN

ShowHelp:
CLS
LOCATE 1, 1: PRINT "Key Mappings:"
LOCATE 2, 1: PRINT "Up arrow: Widen scope (zoom out) by multiplier * 10 points"
LOCATE 3, 1: PRINT "Down arrow: Thin scope (zoom in) by multiplier * 10 points"
LOCATE 4, 1: PRINT "Left arrow: Shift left by multiplier * 10 points"
LOCATE 5, 1: PRINT "Right arrow: Shift right by multiplier * 10 points"
LOCATE 6, 1: PRINT "Ctrl + Left/Right: Shift by 1000 points"
LOCATE 7, 1: PRINT "1-9: Set multiplier to 1-9"
LOCATE 8, 1: PRINT "Home: View last 1000 points, enable auto-reload every 2s"
LOCATE 9, 1: PRINT "End: View full dataset, disable auto-reload"
LOCATE 10, 1: PRINT "S/s: Save screenshot as BMP (date_time.bmp)"
LOCATE 11, 1: PRINT "H/h: This help screen"
LOCATE 12, 1: PRINT "P/p: Toggle grayscale mode for printing (white background)"
LOCATE 14, 1: PRINT "Press any key to continue..."
DO
    k$ = INKEY$
LOOP WHILE k$ = ""
RETURN

SUB QuickSort (arr() AS SINGLE, low AS LONG, high AS LONG)
    DIM stack(1 TO 100) AS LONG ' Stack for iterative QuickSort, max depth 100
    DIM top AS INTEGER
    top = 0
    top = top + 1: stack(top) = low
    top = top + 1: stack(top) = high
    DO WHILE top > 0
        high = stack(top): top = top - 1
        low = stack(top): top = top - 1
        IF low < high THEN
            pivot! = arr((low + high) \ 2)
            i& = low
            j& = high
            DO
                WHILE arr(i&) < pivot!: i& = i& + 1: WEND
                WHILE arr(j&) > pivot!: j& = j& - 1: WEND
                IF i& <= j& THEN
                    tempSwap! = arr(i&): arr(i&) = arr(j&): arr(j&) = tempSwap!
                    i& = i& + 1
                    j& = j& - 1
                END IF
            LOOP WHILE i& <= j&
            IF low < j& THEN
                top = top + 1: stack(top) = low
                top = top + 1: stack(top) = j&
            END IF
            IF i& < high THEN
                top = top + 1: stack(top) = i&
                top = top + 1: stack(top) = high
            END IF
        END IF
    LOOP
END SUB

SUB SetPalette (mode AS INTEGER)
    IF mode = 0 THEN
        _PALETTECOLOR 0, _RGB32(0, 0, 0) ' Black
        _PALETTECOLOR 1, _RGB32(0, 0, 170) ' Blue
        _PALETTECOLOR 10, _RGB32(0, 170, 0) ' Green
        _PALETTECOLOR 11, _RGB32(0, 170, 170) ' Cyan
        _PALETTECOLOR 12, _RGB32(170, 0, 0) ' Red
        _PALETTECOLOR 15, _RGB32(255, 255, 255) ' White
        EMFPalette(0) = _RGB32(0, 0, 0)
        EMFPalette(1) = _RGB32(0, 0, 170)
        EMFPalette(10) = _RGB32(0, 170, 0)
        EMFPalette(11) = _RGB32(0, 170, 170)
        EMFPalette(12) = _RGB32(170, 0, 0)
        EMFPalette(15) = _RGB32(255, 255, 255)
        AxesColor = 15
    ELSE
        _PALETTECOLOR 0, _RGB32(255, 255, 255) ' White background
        _PALETTECOLOR 1, _RGB32(50, 50, 50) ' Dark gray for X
        _PALETTECOLOR 10, _RGB32(100, 100, 100) ' Medium gray for Y
        _PALETTECOLOR 11, _RGB32(150, 150, 150) ' Light medium gray for Z
        _PALETTECOLOR 12, _RGB32(200, 200, 200) ' Light gray for Mag
        _PALETTECOLOR 15, _RGB32(0, 0, 0) ' Black for axes and text
        EMFPalette(0) = _RGB32(255, 255, 255)
        EMFPalette(1) = _RGB32(50, 50, 50)
        EMFPalette(10) = _RGB32(100, 100, 100)
        EMFPalette(11) = _RGB32(150, 150, 150)
        EMFPalette(12) = _RGB32(200, 200, 200)
        EMFPalette(15) = _RGB32(0, 0, 0)
        AxesColor = 15
    END IF
END SUB

Language: qBASIC 4.5