Imports system
Imports System.net
Imports System.Net.sockets
Imports System.Windows.Forms.Application
Imports System.Windows.Forms.Timer
Imports System.Buffer
Imports System.Threading.Thread
Imports System.IO
Imports System.math
Namespace RSS
Public Class RSSBase
''' Format string specifying how the file name is constructed from a DateTime object.
Public Shared fileformat As String = "yyyyMMddTHHmmss"
Public Const majorversion As Integer = 2
Public Const minorversion As Integer = 2
Public Const minorminorversion As Integer = 20
Public c As ColorRes = ColorRes.twelvebit
Friend Shared ff As Byte = 255
Friend Shared fefe() As Byte = New Byte() {254, 254}
Public Shared Function RSSDate(Optional ByVal x As DateTime = #12:00:00 AM#) As Double
Const timezero As DateTime = #12/30/1899#
If x = #12:00:00 AM# Then
Return Now.Subtract(timezero).TotalDays
Else
Return x.Subtract(timezero).TotalDays
End If
End Function
''' Minimum value in an array.
Public Overloads Shared Function minimum(ByVal data() As Short) As Short
Dim m As Short = Short.MaxValue
For i As Integer = 0 To data.Length - 1
If data(i) < m Then m = data(i)
Next
Return m
End Function
''' Maximum value in an array.
Public Overloads Shared Function maximum(ByVal data() As Short) As Short
Dim m As Short = Short.MinValue
For i As Integer = 0 To data.Length - 1
If data(i) > m Then m = data(i)
Next
Return m
End Function
Public Shared Function dB(ByVal x As Single, Optional ByVal power As Boolean = True) As Single
If power Then Return 10 * Log10(x) Else Return 20 * Log10(x)
End Function
Public Shared Function datafilename(ByVal path As String, _
ByVal curtime As DateTime, Optional ByVal ext As String = ".sps") As String
Dim d As String = path
If String.IsNullOrEmpty(path) Then
d = curtime.ToString("yyMM")
Else
d = My.Computer.FileSystem.CombinePath(d, curtime.ToString("yyMM"))
End If
If Not My.Computer.FileSystem.DirectoryExists(d) Then
My.Computer.FileSystem.CreateDirectory(d)
End If
d = My.Computer.FileSystem.CombinePath(d, curtime.ToString("yyMMdd"))
If Not My.Computer.FileSystem.DirectoryExists(d) Then
My.Computer.FileSystem.CreateDirectory(d)
End If
d = My.Computer.FileSystem.CombinePath(d, curtime.ToString("yyMMddhhmmss") & ext)
Return d
End Function
Public Shared Sub interleave(ByVal data() As Double, ByVal buffer() As Double)
' reorder the array to interleaved
Dim count As Integer = data.Length \ 2
Dim c1 As Integer = 0
Dim c2 As Integer = count
Dim c3 As Integer = 0
For i As Integer = 0 To count - 1
buffer(c3) = data(c1) : c1 += 1 : c3 += 1
buffer(c3) = data(c2) : c2 += 1 : c3 += 1
Next
End Sub
Public Shared Sub interleave(ByVal data() As Single, ByVal buffer() As Single)
' reorder the array to interleaved
Dim count As Integer = data.Length \ 2
Dim c1 As Integer = 0
Dim c2 As Integer = count
Dim c3 As Integer = 0
For i As Integer = 0 To count - 1
buffer(c3) = data(c1) : c1 += 1 : c3 += 1
buffer(c3) = data(c2) : c2 += 1 : c3 += 1
Next
End Sub
End Class
'''
''' SPS is a .Net class that writes spectrogram files readable by Jim Sky's Radio Sky Spectrograph
''' (RSS, at radiosky.com),
''' which records and displays radio spectra from radio observatories. These files consist of headers
''' containing observatory information and basic parameters describing the one table they contain. SPS files
''' can contain data for up to two receivers, but only one is supported as yet.
'''
''' Usage is relatively simple. One creates an instance, fills in the metadata, acquires the data,
''' writes the header, writes the data, and finally closes the file. The example code is in VB.Net.
''' ' create the object
''' Dim countofspectralchannels As Integer = ... ' number of spectral channels to be recorded
''' Dim countofspectra as Integer = ... ' number of spectra to be recorded
''' Dim spsfile As New SPS(countofspectra, countofspectralchannels, SPS.colorres.twelvebit)
'''
''' With spsfile
''' ' fill in the metadata
'''
''' ' observatory information
''' .latitude = "..." ' as double
''' .longitude = "..." ' in degrees east
''' .observatory = "..." ' name of the observatory
''' .location = "..." ' place
''' .observer = "..." ' my name
'''
''' ' other data
''' .freqcent = ... ' center frequency in Herz of each spectrum
''' .freqspan = ... ' the span of frequency of each spectrum
''' .datestart = DateTime.UTCNow ' the starting time of the spectrogram
'''
''' ' acquire the spectrogram, spectrum by spectrum; quit when full
''' Do Until .isfull
''' Dim spectrum () as Short = ...
''' .spectrum = spectrum
''' Loop
'''
''' ' alternative: acquire the spectrogram spectrum by spectrum from a float array
''' ' an offset is added, then the result is scaled
''' Do Until .isfull() OrElse quitflag
''' Dim spectrum () as Single = ...
''' .spectrum(offset, scale) = spectrum
''' Loop
'''
''' ' it is possible to allocate for additional or fewer spectra
''' .naxis1 = .naxis1 + 10000 ' additional spectra
''' .naxis1(.naxis2+5) = naxis1+100 ' both number of spectra and number of spectral channels
'''
''' .dateend = DateTime.UTCNow ' the ending time of the data
'''
''' ' write the file
''' .openFile(nameoffile, optionalpath) ' open the file
''' .writeHeader() ' write the header
''' .writeData() ' write the data
''' .close() ' close the file
''' End With
'''
''' The two spectrum methods can be combined, but it is up to the user to write the
''' number of spectra to the file that matches the .naxis1 count previously written to the header.
''' The number of spectra actually acquired can be less than countofspectra. When
''' writeHeader() later is called, the correct count will be written to the header.
'''
Public Class SPSWriter
Inherits rssbase
''' The time of the first spectrum.
Public dateobs As DateTime
''' The time of the last spectrum.
Public dateend As DateTime
''' The observatory's name.
Public observatory As String = Nothing
''' The observatory's location.
Public location As String = Nothing
''' Observatory latitude.
Public latitude As Double = 0 ' latitude
''' Observatory longitude
Public longitude As Double = 0 ' longitude
''' Observatory elevation (not used).
Public elevation As Double = 0 ' meters
''' Time zone relative to GMT of the observatory.
Public timezone As Short = 0
''' Observer's name.
Public observer As String = Nothing
''' File author's name (not used).
Public author As String = Nothing
''' The number of receivers interleaved in the spectra.
Public receivercount As Integer = 1
''' The number of spectral channels in each spectrum.
Public naxis2 As Short ' count of channels
''' The center frequency of each spectrum
Public freqcent As Double ' center frequency of the spectra
''' The span of frequencies of each spectrum.
Public freqspan As Double ' frequency span of the spectra
''' The number of bits of data in each spectral channel.
''' Unipolar types interpret data as spectral with .sps extension;
''' bipolar as non-spectral with .dat extension.
Private bitmax As Short
Private bitmin As Short
Private ext As String
Public data()() As Short
''' Whether data are written into a linear buffer (false) vs. circular buffer (true).
''' The property isfull can be true only for a linear buffer.
Public circular As Boolean = False
Friend fid As FileStream
Friend binWriter As BinaryWriter
' must be set after object is created
Public obs As ObservatoryData
''' The constructor.
''' The number of spectra to be written. Used to preallocate space
''' for the data. The actual number acquired can be less, in which case writeHeader detects
''' and accommodates the actual number.
''' The number of spectral channels in each spectrum (default 1).
''' The number of bits in each spectral channel (default 10 bits).
''' Whether the instance is to record spectra as a circular buffer.
Public Sub New(ByVal numberofspectra As Integer, _
ByVal numberofspectralchannels As Integer, _
ByVal circularmode As Boolean, _
Optional ByVal wordsize As ColorRes = ColorRes.twelvebit, _
Optional ByVal reccount As Integer = 1)
receivercount = reccount
c = wordsize
circular = circularmode
' set both naxis1 and naxis2, and allocate data
naxis1(numberofspectralchannels) = numberofspectra
' set the data word size limits
Select Case c
Case ColorRes.tenbit : bitmax = 1023 : bitmin = 0 : ext = ".sps"
Case ColorRes.twelvebit : bitmax = 4095 : bitmin = 0 : ext = ".sps"
Case ColorRes.bipolar : bitmax = 32767 : bitmin = -32768 : ext = ".dat"
Case Else ' error
End Select
End Sub
''' Opens the file.
''' The name of the file to be written to including the path.
''' If no file name is specified, then standardfilename() is used.
Public Sub OpenFile(Optional ByVal filename As String = Nothing, Optional ByVal path As String = Nothing)
' output file
If String.IsNullOrEmpty(filename) Then filename = standardfilename()
Dim x As String
If String.IsNullOrEmpty(path) Then x = filename Else x = My.Computer.FileSystem.CombinePath(path, filename)
fid = New FileStream(x, FileMode.Create)
binWriter = New BinaryWriter(fid)
End Sub
''' Provides a file name based on the current date and time (UTC).
''' String of the form yyyyMMddTHHmmss.sps derived from the current time UTC.
''' If the data are bipolar as set by the ColorRes setting, the file-name extension is .dat, otherwise it is .sps.
Public Overloads Function standardfilename()
Return DateTime.UtcNow.ToString(fileformat) & ext
End Function
''' Another file name derived from a date.
''' A file time from which to derive the UTC file name.
''' The optional path.
''' If the data are bipolar as set by the ColorRes setting, the file-name extension is .dat, otherwise it is .sps.
Public Overloads Function standardfilename(ByVal t As DateTime, Optional ByVal path As String = Nothing)
Dim fn As String = DateTime.FromFileTimeUtc(t.Ticks).ToString(fileformat) & ext
If Not String.IsNullOrEmpty(path) Then fn = My.Computer.FileSystem.CombinePath(path, fn)
Return fn
End Function
''' Writes the SPS file header.
''' The metadata must have previously been provided.
Public Sub WriteHeader(ByVal obs As ObservatoryData.observatory)
If Not IsNothing(obs) AndAlso Not IsNothing(obs) Then
With obs
observatory = .observatory
observer = .observer
author = .observer
timezone = .timezone
latitude = .latitude
longitude = .longitude
location = .Location
End With
End If
' check for not full
If Not circular AndAlso Not isfull Then naxis1 = currentspectrum
' fixed-length portion of the header
' major, minor, minor-minor version
binWriter.Write(sprintf("%05d%02d%03d", majorversion, minorversion, minorminorversion).ToCharArray)
' start and end date and time; don't know why the extra day is needed
binWriter.Write(RSSDate(dateobs))
binWriter.Write(RSSDate(dateend))
' latitude 0x1a
binWriter.Write(latitude)
' longitude 0x22
binWriter.Write(longitude)
' unused 0x2a
binWriter.Write(CDbl(0))
binWriter.Write(CDbl(0))
' time zone 0x3a
binWriter.Write(timezone)
' unused 10-byte field 0x3c
binWriter.Write(("0000000000").ToCharArray)
' author 0x46
binWriter.Write(author.PadRight(20).ToCharArray)
' observatory name 0x5a
binWriter.Write(observatory.PadRight(20).ToCharArray)
' observatory location 0x6e
binWriter.Write(location.PadRight(40).ToCharArray)
' frequency channels 0x96 0x96
binWriter.Write(naxis2)
' note (often contains metadata)
Dim note(7) As String
note(0) = sprintf("*[[*SWEEPS%d", naxis1)
note(1) = sprintf("LOWF%d", CInt(freqcent - freqspan / 2))
note(2) = sprintf("HIF%d", CInt(freqcent + freqspan / 2))
note(3) = sprintf("STEPS%d", naxis2)
note(4) = "DUALSPECFILE" & (receivercount <> 1).ToString
note(5) = sprintf("RCVR%d", receivercount)
' BANNER0
' BANNER1
' ANTENNATYPE
' ANTENNAELEVATION
' ANTENNAPOLARIZATION0
' ANTENNAPOLARIZATION1
' COLORFILE
' COLOROFFSET0
' COLOROFFSET1 if dual receivers
' COLORGAIN0
' COLORGAIN1 if dual receivers
' if correction file is loaded
' CORRECTIONFILENAME
' CAXFn freq, n indexed through the correction array
' CAS1n correction array element, n indexed through the correction array
' CLOCKMSGn clock metadata elements
note(6) = sprintf("COLORRES%d", c)
note(7) = sprintf("*]]*")
' write the length of the note 0x98
Dim notelength As Integer = 0
For i As Integer = 0 To note.Length - 1
notelength += note(i).Length
Next
binWriter.Write(notelength + 8)
' write the note itself 0x9c
For i As Integer = 0 To note.Length - 1
binWriter.Write(note(i).ToCharArray)
binWriter.Write(ff)
Next
End Sub
''' Writes the spectrogram in data to the file.
''' The metadata must have previously been provided.
Public Sub WriteData()
Static buf(2 * receivercount * naxis2 - 1) As Byte
If Not circular AndAlso currentspectrum < data.Length Then naxis1 = currentspectrum
Dim d2 As Integer = 2 * receivercount * naxis2
For i As Integer = currentspectrum To currentspectrum + naxis1 - 1
' write the array
BlockCopy(data(i Mod naxis1), 0, buf, 0, d2)
' swap bytes
Array.Reverse(buf, 0, d2)
' write to the file
binWriter.Write(buf, 0, d2)
' terminator characters
binWriter.Write(fefe)
Next
End Sub
''' Closes the file.
''' Once this method is called, the instance can no longer be used.
Public Sub close()
If fid IsNot Nothing Then
fid.Flush()
fid.Close()
fid = Nothing
End If
If binWriter IsNot Nothing Then
binWriter.Close()
binWriter = Nothing
End If
End Sub
''' Writes the file. Combines OpenFile, WriteHeader, WriteData, and close.
''' Name of the file to which to write.
''' Path of the file to which to write.
Public Sub export(ByVal obs As ObservatoryData.observatory, Optional ByVal filename As String = Nothing, Optional ByVal path As String = Nothing)
OpenFile(filename, path)
WriteHeader(obs)
WriteData()
close()
End Sub
''' Minimum value in the data member.
Public Overloads Function minimum() As Short
Dim m As Short = Short.MaxValue
For i As Integer = 0 To data.Length - 1
Dim x As Short = minimum(data(i))
If x < m Then m = x
Next
Return m
End Function
''' Maximum value in the data member.
Public Overloads Function maximum() As Short
Dim m As Short = Short.MinValue
For i As Integer = 0 To data.Length - 1
Dim x As Short = maximum(data(i))
If x > m Then m = x
Next
Return m
End Function
Private currentspectrum As Integer = 0
''' Writes a spectrum to the file.
''' The spectrum to be written.
''' When circular is false and the number of spectra written exceeds the number defined when the instance was created.
''' Keeps track of how many spectra have been acquired. When circular is false, see isfull().
''' When circular is true, isfull is always false, and writeData writes to the file
''' the entire data array starting from currentspectrum and wrapping around the end
''' when requested.
Public Overloads WriteOnly Property spectrum() As Short()
Set(ByVal value As Short())
If Not circular AndAlso currentspectrum >= naxis1 Then
Throw (New ApplicationException(sprintf("%s.spectrum - too many spectra written: %d (max %d).", Me.GetType.Name, currentspectrum + 1, naxis1)))
Else
Array.Copy(value, data(currentspectrum), value.Length)
currentspectrum += 1
End If
If circular Then currentspectrum = currentspectrum Mod naxis1
End Set
End Property
''' Writes a spectrum to the file.
''' An offset applied to the spectrum prior to scaling and short conversion.
''' A scale factor applied to the spectrum after the offset and before short conversion.
''' When the number of spectra acquired exceeds the number defined when the instance was created.
''' The acquired spectrum.
''' Keeps track of how many spectra have been acquired. When circular is false, see isfull().
''' When circular is true, isfull is always false, and writeData writes to the file
''' the entire data array starting from currentspectrum and wrapping around the end
''' when requested.
Public Overloads WriteOnly Property spectrum(ByVal offset As Single, ByVal scale As Single) As Single()
Set(ByVal value() As Single)
If Not circular AndAlso currentspectrum >= naxis1 Then
Throw (New ApplicationException(sprintf("%s.spectrum - too many spectra written: %d (max %d).", Me.GetType.Name, currentspectrum + 1, naxis1)))
Else
' convert to short
Dim x() As Short = data(currentspectrum)
' straight intensity scale
Dim s As Single = scale * (bitmax - bitmin)
Dim b1 As Single = CDbl(bitmin)
Dim b2 As Single = CDbl(bitmax)
For i As Integer = 0 To value.Length - 1
x(i) = CShort(Min(Max(b1, s * (value(i) + offset)), b2))
Next
End If
currentspectrum += 1
If circular Then currentspectrum = currentspectrum Mod naxis1
End Set
End Property
''' Writes a spectrum to the file.
''' An offset applied to the spectrum prior to scaling and short conversion.
''' A scale factor applied to the spectrum after the offset and before short conversion.
''' When the number of spectra acquired exceeds the number defined when the instance was created.
''' The acquired spectrum.
''' Keeps track of how many spectra have been acquired. When circular is false, see isfull().
''' When circular is true, isfull is always false, and writeData writes to the file
''' the entire data array starting from currentspectrum and wrapping around the end
''' when requested.
Public Overloads WriteOnly Property spectrum(ByVal offset As Double, ByVal scale As Double) As Double()
Set(ByVal value() As Double)
If Not circular AndAlso currentspectrum >= naxis1 Then
Throw (New ApplicationException(sprintf("%s.spectrum - too many spectra written: %d (max %d).", Me.GetType.Name, currentspectrum + 1, naxis1)))
End If
' more than one receiver
If receivercount > 1 Then
Static buf() As Double
If buf Is Nothing OrElse buf.Length <> value.Length Then ReDim buf(value.Length - 1)
' interleave the data
RSSBase.interleave(value, buf)
value = buf
End If
' convert to short
Dim x() As Short = data(currentspectrum)
Dim s As Double = scale * (bitmax - bitmin)
Dim b1 As Double = CDbl(bitmin)
Dim b2 As Double = CDbl(bitmax)
For i As Integer = 0 To value.Length - 1
x(i) = CShort(Min(Max(b1, s * (value(i) + offset)), b2))
Next
' increment pointer
currentspectrum += 1
'wrap
If circular Then currentspectrum = currentspectrum Mod naxis1
End Set
End Property
''' Whether the number of spectra acquired equals or exceeds the number defined
''' when the instance was created.
'''
''' False if fewer acquired; True if equal or greater.
''' Once full, the instance should written to the file, closed, and not used further.
''' But the instance need not be full to be written and closed.
Public ReadOnly Property isfull() As Boolean
Get
Return Not circular AndAlso currentspectrum = naxis1
End Get
End Property
''' Sets and returns the number of spectra in the spectrogram.
Dim naxis1var As Integer
Public Property naxis1(Optional ByVal newnaxis2 As Integer = 0) As Integer
Get
Return naxis1var
End Get
Set(ByVal value As Integer)
If newnaxis2 > 0 AndAlso naxis2 <> newnaxis2 Then
' new value of naxis2
ReDim data(value - 1)
naxis2 = newnaxis2
For i As Integer = 0 To value - 1
ReDim data(i)(receivercount * naxis2 - 1)
Next
Else
' naxis2 is unchanged
ReDim Preserve data(value - 1)
For i As Integer = naxis1 To value - 1
ReDim data(i)(receivercount * naxis2 - 1)
Next
End If
naxis1var = value
If circular AndAlso currentspectrum >= naxis1 Then currentspectrum = 0
End Set
End Property
End Class
'''
''' Provides an internet server attached to a radio receiver that feeds spectral data to Radio Sky
''' Spectrograph (RSS). RSS is software that displays data from a receiver and
''' optionally exports that data to the internet. It can also view data files.
'''
'''
''' To create an RSSServer object use:
''' dim client as New RSSServer(port, bincount, FrequencyStep, FrequencyCenter, FrequencyOffset)
''' where the first argument port is the socket port number,
''' the second parameter bincount is the number of spectral channels that are being transmitted,
''' and the three frequency parameters determine which of the spectral channels are being transmitted.
''' At this point RSS is set to server mode if not already, at which time it connects to the client and receivers
''' the number and frequency range of the data it subsequently receives.
''' To actually transmit data to RSS, one calls
''' client.DataHandler(buf, True)
''' where buf is a buffer containing bincount (u)shorts containing the spectral
''' data, and two additional bytes, each of which contains the hex code 0xFE.
''' The values transmitted must be limited to either 2^10-1 or 2^12-1 depending on the receiver word size.
''' The connection remains open and a stream of spectra may be transmitted arbitrarily.
''' RSS will assume they are generated at a uniform rate.
''' The connection is closed either by calling
''' client.stopall
''' or when RSS resets the connection, after which in either case the object
''' must be disposed of.
'''
Public Class RSSServer
Inherits TcpListener
Dim stream As NetworkStream
Dim WithEvents t As New Timer
Dim clientSocket As Socket
Dim rectype As ReceiverType
Private bitmin, bitmax As Single
Private c As ColorRes = ColorRes.twelvebit
'''
''' Creates and instance of the class with parameters set to receiver settings.
'''
''' The sockets port number. It must be 8888.
''' The number of spectral channels to be sent to RSS
''' The spectral resolution of the receiver's spectral data.
''' The center frequency of the frequency span to be sent to RSS, in HZ.
''' An offset frequency that is non-zero when the receiver employs down conversion.
''' The receiver type corresponding the to ReceiverType enumeration.
'''
Public Sub New(ByVal port As Integer, _
ByVal fCount As Integer, ByVal dfin As Double, ByVal fCent As Double, _
Optional ByVal fOffset As Double = 0, _
Optional ByVal rt As RSS.ReceiverType = ReceiverType.RTLDongle, _
Optional ByVal cx As ColorRes = ColorRes.twelvebit)
MyBase.New(New IPEndPoint(IPAddress.Loopback, port))
Debug.Print(rt.ToString)
df = dfin
ExportedChannelCount = fCount
freqcent = fCent
FreqOffset = fOffset
rectype = rt
Me.c = cx
Select Case c
Case ColorRes.tenbit : bitmin = 0 : bitmax = 1023
Case ColorRes.twelvebit : bitmin = 0 : bitmax = 4095
Case ColorRes.bipolar : bitmin = -32768 : bitmax = 32767
End Select
' start the listener
Debug.Print("rssserver.new " & rectype.ToString)
Start()
Debug.Print("rssserver.new listener started")
' start the pending-polling timer
t.Interval = 1000
t.Start()
End Sub
' Thread signal.
Public Shared clientConnected As New System.Threading.ManualResetEvent(False)
Sub listenertimerhandler(ByVal x As Object, ByVal y As System.EventArgs) Handles t.Tick
If Pending() Then
t.Stop()
' Set the event to nonsignaled state.
clientConnected.Reset()
BeginAcceptSocket(AddressOf BASHandler, Me)
' Wait until a connection is made and processed before continuing.
clientConnected.WaitOne()
End If
End Sub
''' Indicates that the underlying socket is listening for connections.
Public ReadOnly Property listening() As Boolean
Get
Return t.Enabled
End Get
End Property
Sub BASHandler(ByVal u As IAsyncResult)
'EndAcceptSocket(u)
' Get the listener that handles the client request.
Dim listener As TcpListener = CType(u.AsyncState, TcpListener)
' End the operation and display the received data on the
'console.
clientSocket = listener.EndAcceptSocket(u)
clientSocket.Blocking = False
' Process the connection here. (Add the client to a
' server table, read data, etc.)
printf("Client connected completed")
' Signal the calling thread to continue.
clientConnected.Set()
' send the intro string
Dim b() As Byte = IntroByte()
clientSocket.Send(IntroByte)
End Sub
'''
''' Returns the intro string that is sent to the remote host when a connection is first established.
'''
''' The intro string that is sent to the remote RSS client upon connecting.
''' The intro string informs the remote RSS clent when first connecting of the receiver's
''' frequency center, span, offset, and number of channels.
Public Function IntroString() As String
Dim r As String = sprintf( _
"F %d|S %d|O %d|C %d|R %d|", _
CInt(freqcent), CInt(freqspan), CInt(FreqOffset), ExportedChannelCount, ReceiverCount _
)
Debug.Print(r)
Return r
End Function
'''
''' Returns the intro string as an array of bytes containing the string's ascii codes.
'''
''' In byte form, the intro string that is sent to the remote RSS client upon connecting.
''' The intro string informs the remote RSS clent of the receiver's
''' frequency center, span, offset, and number of channels.
Function IntroByte() As Byte()
Return Text.Encoding.ASCII.GetBytes(IntroString)
End Function
'''
''' Data to be sent to RSS passes through this function.
'''
''' Data to be sent to the client. Receiver samples are interleaved,
''' and their are info.FreqChans of each.
Public Sub DataHandler(ByVal data() As Short, Optional ByVal bigendian As Boolean = False)
' byte buffer for writing to the stream
Dim datalen As Integer = ExportedChannelCount * ReceiverCount
Dim buflen As Integer = datalen * 2 + 2
Static buf() As Byte ' so that it is not continually created and disposed
If buf Is Nothing OrElse buf.Length <> buflen Then
ReDim buf(buflen - 1)
End If
If data.Length <> datalen Then
Throw (New ApplicationException(sprintf( _
"%s.DataHandler - array provided (%d) does not have the correct length (%d).", _
Me.GetType.Name, data.Length, datalen)))
Else
' reverse order of spectral channels (RSS spec)
Array.Reverse(data)
' copy to a byte array with termination code
BlockCopy(data, 0, buf, 0, 2 * data.Length)
' byte ordering (endianess)
If bigendian Then
For i As Integer = 0 To buf.Length - 2 Step 2
' swap bytes
Dim temp As Byte = buf(i)
buf(i) = buf(i + 1)
buf(i + 1) = temp
Next
End If
buf(buf.Length - 2) = 254
buf(buf.Length - 1) = 254
' send to the client
' debug->exceptions: uncheck 'break when socketexception is thrown'
clientSocket.Send(buf)
End If
End Sub
Public WriteOnly Property spectrum(Optional ByVal offset As Double = 0, Optional ByVal scale As Double = 1, Optional ByVal bigendian As Boolean = False) As Double()
Set(ByVal data() As Double)
' byte buffer for writing to the stream
Static buf1() As Short ' for the short data
Static buf2() As Byte ' for the raw byte buffer
Static buf3() As Double ' dual-receiver interleaved spectra
Dim datalen As Integer = ExportedChannelCount * ReceiverCount
Dim buflen As Integer = datalen * 2 + 2
' so these arrays are not continually created and disposed
If buf1 Is Nothing OrElse buf1.Length <> datalen Then
ReDim buf1(datalen - 1)
ReDim buf2(buflen - 1)
ReDim buf3(datalen - 1)
' add termination bytes to the output byte buffer
Array.Copy(RSSBase.fefe, 0, buf2, buf2.Length - 2, 2)
printf("RSS.RSSServer.spectrum - chan count %d; rcvrs x cc %d; byte len %d", ExportedChannelCount, datalen, buflen)
End If
' if dual receiver
If ReceiverCount = 2 Then
' test ramp
'Static ramp1(), ramp2() As Double
'If ramp1 Is Nothing Then
'ReDim ramp1(datalen \ 2 - 1)
'ReDim ramp2(datalen \ 2 - 1)
'For i As Integer = 0 To datalen \ 2 - 1
'ramp1(i) = i / 1000
'ramp2(i) = 1.5 * ramp1(i)
'Next
'End If
'Array.Copy(ramp1, 0, buf3, 0, datalen \ 2)
'Array.Copy(ramp2, 0, buf3, datalen \ 2, datalen \ 2)
' reorder the array to interleaved
'RSSBase.interleave(data, buf3)
'data = buf3
End If
' data-length mismatch
If data.Length <> datalen Then
Throw (New ArgumentException(sprintf( _
"%s.DataHandler - array provided (%d) does not have the correct length (%d).", _
Me.GetType.Name, data.Length, datalen)))
End If
' scale and clip
Dim s As Double = scale * bitmax
For i As Integer = 0 To datalen - 1
buf1(i) = CShort(Max(bitmin, Min(s * (data(i) + offset), bitmax)))
Next
' reverse order of spectral channels (RSS spec)
Array.Reverse(buf1)
' copy to the byte array, leaving termination code intact
BlockCopy(buf1, 0, buf2, 0, buf2.Length - 2)
' switch byte ordering (endianess), if necessary
If bigendian Then
For i As Integer = 0 To buf2.Length - 3 Step 2
' swap bytes
Dim temp As Byte = buf2(i)
Dim i1 As Integer = i + 1
buf2(i) = buf2(i1)
buf2(i1) = temp
Next
End If
' send to the client
' debug->exceptions: uncheck 'break when socketexception is thrown'
clientSocket.Send(buf2)
End Set
End Property
''' Float data to be sent to RSS passes through this function.
''' Data to be sent to the client. Receiver samples are interleaved,
''' and their are info.FreqChans of each.
Public Sub datahandler(ByVal d() As Single, ByRef offset As Single, ByRef scale As Single, Optional ByVal bigendian As Boolean = False)
Static dx() As Short
If dx Is Nothing Then ReDim dx(d.Length - 1)
For i As Integer = 0 To d.Length - 1
dx(i) = CShort(Min(Max(bitmin, (d(i) + offset) * scale), bitmax))
Next
DataHandler(dx, bigendian)
End Sub
''' Closes the socket and stops listening.
Public Shadows Sub stopall()
t.Stop()
Sleep(t.Interval)
DoEvents()
If clientSocket IsNot Nothing Then clientSocket.Close()
MyBase.Stop()
End Sub
''' The number of Fourier channels that are emitted by the RSS server.
''' This property, and freqcent, are used to set the exact Fourier channels that
''' are emitted.
''' The Fourier-channel frequency bins are set by the sample rate fs and N.
''' So when setting a parameter, such as freqcent, to a value, that value is adjusted
''' so that it aligns with a frequency bin. Thus the return value of freqcent is in general not the same
''' as its set value.
Public ExportedChannelCount As Integer
''' Center frequency of the span exported to the server.
''' Note that the value returned is slightly different from the value set.
Public Property freqcent() As Double
Get
Return 0.5 * (freq1 + freq2)
End Get
Set(ByVal value As Double)
chan1var = CInt(value / df - 0.5 * (ExportedChannelCount - 1))
chan2var = chan1var + ExportedChannelCount - 1
End Set
End Property
Public chan1var As Integer
''' Unliased Fourier channel number of the first channel exported by the server.
Public ReadOnly Property chan1() As Integer
Get
Return chan1var
End Get
End Property
Public chan2var As Integer
''' Unliased Fourier channel number of the last channel exported by the server.
Public ReadOnly Property chan2() As Integer
Get
Return chan2var
End Get
End Property
''' The span of frequencies emitted by the server.
''' This is the frequency difference between the last and first frequency
''' bin centers. The true span is one bin larger.
Public ReadOnly Property freqspan() As Double
Get
Return freq2 - freq1
End Get
End Property
''' Unliased center frequency of the first Fourier channel exported by the server.
Public ReadOnly Property freq1() As Double
Get
Return chan1var * df
End Get
End Property
''' >Unliased center frequency of the last Fourier channel exported by the server.
Public ReadOnly Property freq2() As Double
Get
Return chan2var * df
End Get
End Property
Private fovar As Double
Public Property FreqOffset() As Double
Get
Return fovar
End Get
Set(ByVal value As Double)
fovar = value
End Set
End Property
''' The number of receivers to be streamed.
''' Only one type is supported at the moment.
Public ReadOnly Property ReceiverCount() As Integer
Get
Select Case rectype
Case ReceiverType.DPS
Case ReceiverType.FSX10bit
Case ReceiverType.FSX12bit
Case ReceiverType.FSX12bitSwitching
Return 2
Case ReceiverType.FSX200
Case ReceiverType.ICOMTuneMode
Case ReceiverType.RadioComPort
Case ReceiverType.RTLDongle
Return 1
Case ReceiverType.RTLWideSpectrum
Case ReceiverType.SDR14
Case ReceiverType.SingleReceiver
Return 1
Case ReceiverType.DualReceiver
Return 2
End Select
End Get
End Property
Public df As Double
''' Waits first for listening == true, then waits for listening == false.
''' An abort flag reference. The function exits when it goes high.
''' The interval with which to poll, in ms.
Public Sub WaitForConnection(ByRef quit As Boolean, Optional ByVal intervalms As Integer = 300)
' wait for listening
Do Until listening OrElse quit : Sleep(intervalms) : DoEvents() : Loop
' wait for not listening
Do Until Not listening OrElse quit : Sleep(intervalms) : DoEvents() : Loop
End Sub
End Class
''' RSS receiver types.
''' Not all are supported.
Public Enum ReceiverType
ICOMTuneMode
RadioComPort
FSX200
FSX10bit
FSX12bit
FSX12bitSwitching
SDR14
RTLDongle
RTLWideSpectrum
DPS
SDRPlay
SingleReceiver
DualReceiver
End Enum
''' Represents the choice of number of bits in the spectral data, right justified in a sixteen-bit word.
Public Enum ColorRes
''' Sixteen-bit data.
bipolar = 0
''' Ten-bit data.
tenbit = 4
''' Twelve-bit data.
twelvebit = 1
End Enum
''' Reads .sps files.
''' This class is in only a skeleton state.
Public Class SPSReader
Inherits rssbase
''' The time of the first spectrum.
Public dateobs As DateTime
''' The time of the last spectrum.
Public dateend As DateTime
''' The observatory's name.
Public observatory As String
''' The observatory's location.
Public location As String
''' Observatory latitude.
Public latitude As Double
''' Observatory longitude
Public longitude As Double
''' Observatory elevation (not used).
Public elevation As Double
''' Time zone relative to GMT of the observatory.
Public timezone As Short
''' Observer's name.
Public observer As String
''' File author's name (not used).
Public author As String
''' The number of receivers interleaved in the spectra.
Public receivercount As Integer
''' The number of spectra in the spectrogram contained in the file.
''' This number must match the number of spectra actually written to the
''' file using the write methods.
Public naxis1 As Integer ' count of spectra
''' The number of spectral channels in each spectrum.
Public naxis2 As Short ' count of channels
''' The center frequency of each spectrum
Public freqcent As Double ' center frequency of the spectra
''' The span of frequencies of each spectrum.
Public freqspan As Double ' frequency span of the spectra
''' The number of bits of data in each spectral channel.
Private bitmax As Short
Private bitmin As Short
Private currentspectrum As Integer = 0
Friend fid As FileStream
Friend binReader As BinaryReader
Public Sub New(ByVal filename As String)
fid = New FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)
binReader = New BinaryReader(fid)
With binReader
Dim Version As String = .ReadChars(10)
Dim freqstart As Double = .ReadDouble
Dim freqend As Double = .ReadDouble
freqcent = 0.5 * (freqstart + freqend)
freqspan = freqend - freqstart
latitude = .ReadDouble
longitude = .ReadDouble
Dim fill1 As Double = .ReadDouble
fill1 = .ReadDouble
timezone = .ReadInt16
Dim fill2 As String = .ReadChars(10)
author = .ReadChars(20) : author = author.Trim
observatory = .ReadChars(20) : observatory = observatory.Trim
location = .ReadChars(40) : location = location.Trim
naxis1 = CInt(.ReadInt16)
Dim notelength As Integer = .ReadInt32
Dim note As String = System.Text.Encoding.ASCII.GetString(.ReadBytes(notelength))
note = note.Substring(4, note.Length - 9)
Dim ele() As String = note.Split(Chr(255), "?"c)
naxis1 = CInt(ele(0).Substring(6))
Dim freqlow As Double = CDbl(ele(1).Substring(4))
Dim freqhi As Double = CDbl(ele(2).Substring(3))
freqcent = 0.5 * (freqlow + freqhi)
freqspan = freqhi - freqlow
naxis2 = CInt(ele(3).Substring(5))
If ele(5).Length > 4 Then receivercount = CInt(ele(5).Substring(4)) Else receivercount = 1
c = CInt(ele(6).Substring(8))
End With
End Sub
Public ReadOnly Property spectrum(Optional ByVal data() As Short = Nothing) As Short()
Get
If currentspectrum < naxis1 Then
Dim d() As Short
If data Is Nothing Then
ReDim d(receivercount * naxis2 - 1)
Else
d = data
End If
For i As Integer = 0 To d.Length - 1
d(i) = binReader.ReadInt16
Next
Static buf(2 * receivercount * naxis2 - 1) As Byte
BlockCopy(d, 0, buf, 0, 2 * receivercount * naxis2)
Array.Reverse(buf)
BlockCopy(buf, 0, d, 0, 2 * receivercount * naxis2)
currentspectrum += 1
Return d
Else
Throw (New ArgumentException(sprintf("%s.spectrum - ran out of spectra.", Me.GetType.Name)))
End If
End Get
End Property
Public Sub close()
fid.Flush()
fid.Close()
binReader.Close()
End Sub
End Class
''' Merges .sps files.
''' Untested.
Public Class SPSMerge
''' Recipient of the data from the .sps files to be merged.
Dim writer As SPSWriter
Public Sub New(ByVal ParamArray filenames() As String)
If UBound(filenames) < 0 Then
' get a list of file names
Dim d As New OpenFileDialog
With d
d.CheckFileExists = True
d.Filter = "RSS files|.sps"
d.Title = "Load configuration"
d.Multiselect = True
d.ShowDialog()
filenames = d.FileNames
End With
End If
' read the files and append the spectra to data
Dim reader As SPSReader = Nothing
For Each filename As String In filenames
reader = New SPSReader(filename)
With reader
If writer Is Nothing Then
' create an spswriter for the reader's data
writer = New SPSWriter(.naxis1, .naxis2, False, .c, .receivercount)
writer.dateobs = reader.dateobs
Else
' expand the writer's data to hold the new reader's data
writer.naxis1 += .naxis1
End If
Do Until writer.isfull
' copy the spectra from the file to the merge
writer.spectrum = .spectrum
Loop
reader.close()
End With
Next
' copy last reader's metadata to the writer (excepting dateobs)
With writer
.dateend = reader.dateend
.author = reader.author
.observer = reader.observer
.circular = False
.freqcent = reader.freqcent
.freqspan = reader.freqspan
.observatory = reader.observatory
.latitude = reader.latitude
.location = reader.location
.longitude = reader.longitude
.elevation = reader.elevation
.receivercount = reader.receivercount
.timezone = reader.timezone
End With
End Sub
Public Sub subtractbackground(ByVal limits() As Integer)
With writer
Dim n As Integer = limits(1) - limits(2) + 1
' sum the background
Dim background(.naxis2 - 1) As Single
For j As Integer = 0 To .naxis2 - 1
Dim s As Single = 0
For i As Integer = 0 To n - 1
s += CSng(.data(i)(j))
Next
background(j) = s
Next
' compute the mean and convert to short
Dim sn As Single = 1 / CSng(n)
Dim bg(.naxis2 - 1) As Short
For i As Integer = 0 To .naxis2
bg(i) = CShort(background(i) * sn)
Next
' subtract the background from the entire array
For j As Integer = 0 To .naxis1 - 1
Dim s As Single = 0
Dim a() As Short = .data(j)
For i As Integer = 0 To .naxis2 - 1
a(i) = Min(-32768, Min(a(i) - bg(i), 32767))
Next
Next
End With
End Sub
Public Sub frequencysegment(Optional ByVal decimation As Integer = 1, Optional ByVal clip As Integer = 1000000000)
' decimate in frequency
With writer
For i As Integer = 0 To .naxis1 - 1
If decimation > 1 Then
Dim s() As Short = .data(i)
For j As Integer = 0 To .naxis2 - decimation Step decimation
Dim a As Integer = 0
For k As Integer = j To j + decimation - 1
a += s(k)
Next
s(j \ decimation) = CShort(a / decimation)
Next
End If
' shorten each spectrum to the correct size
Dim uab As Integer = Min(clip, .naxis2 \ decimation)
If uab < .naxis2 Then
.naxis2 = uab
ReDim Preserve .data(i)(.naxis2 - 1)
End If
Next
End With
End Sub
Public Sub timesegment(ByVal startindex As Integer, Optional ByVal endindex As Integer = 1000000000, Optional ByVal decimation As Integer = 1)
' decimate in time
With writer
' new start and end times
.dateend = .dateobs.Add(New TimeSpan(CLng(.dateend.Subtract(.dateobs).Ticks * endindex / .naxis1)))
.dateobs = .dateobs.Add(New TimeSpan(CLng(.dateend.Subtract(.dateobs).Ticks * startindex / .naxis1)))
' process change
Dim idx As Integer = 0
For i As Integer = startindex To Min(endindex, .naxis1 - decimation) Step decimation
Dim s() As Short = .data(i)
If decimation > 1 Then
For j As Integer = 0 To .naxis2 - 1
Dim a As Integer = 0
For k As Integer = i To i + decimation - 1
a += .data(k)(j)
Next
s(j) = CShort(a / decimation)
Next
End If
.data(idx) = s
idx += 1
' shorten spectrogram to the correct decimated size
If idx < .naxis1 Then
.naxis1 = idx
ReDim Preserve .data(.naxis1 - 1)
End If
Next
End With
End Sub
Public Sub export(ByVal obs As ObservatoryData.observatory, Optional ByVal filename As String = Nothing, Optional ByVal path As String = Nothing)
writer.export(obs, filename, path)
End Sub
End Class
End Namespace
' Jim Sky's code for writing header of .sps files
'Function GetModifiedNotes()
' 'here is where we can add any parameters we need to to the tempnotesstring
' 'this tempnotestring begins with a freeform notes area, and was expanded to allow the addition of an unlimited number
' 'of fields between *[[* and *]]* markers. Each field begins with capitalized token +value string+ CHR(255)
' On Error Resume Next
' If RCVR = stFSXdual Or RCVR = stDualEmulator Then DualSpecFile = True
' Dim TNSx As String
' TNSx = TempNotesString + "*[[*"
' If FileLoggedUsingUT Then TNSx = TNSx + "Logged Using UT" + Chr(255)
' If SaveView Then
' TNSx = TNSx + "SWEEPS" + Trim(Str$((EndSave - StartSave) + 1)) + Chr(255)
' Else
' TNSx = TNSx + "SWEEPS" + Trim(Str$(NumSweeps)) + Chr(255)
' End If
' TNSx = TNSx + "LOWF" + Trim(Str$(LowF)) + Chr(255)
' TNSx = TNSx + "HIF" + Trim(Str$(HiF)) + Chr$(255)
' TNSx = TNSx + "STEPS" + Trim(Str$(NumChannels)) + Chr$(255)
' TNSx = TNSx + "DUALSPECFILE" + Trim(CStr(DualSpecFile)) + Chr$(255)
' TNSx = TNSx + "COLORRES" + Trim(CStr(ColorRes)) + Chr$(255)
' TNSx = TNSx + "RCVR" + Trim(Str$(RCVR)) + Chr$(255)
' TNSx = TNSx + "BANNER0" + BannerTextTemplate(0) + Chr$(255)
' TNSx = TNSx + "BANNER1" + BannerTextTemplate(1) + Chr$(255)
' TNSx = TNSx + "ANTENNATYPE" + AntennaType + Chr$(255)
' TNSx = TNSx + "ANTENNAAZIMUTH" + AntennaAzimuth + Chr$(255)
' TNSx = TNSx + "ANTENNAELEVATION" + AntennaElevation + Chr$(255)
' TNSx = TNSx + "ANTENNAPOLARIZATION0" + AntennaPolarization0 + Chr$(255)
' TNSx = TNSx + "ANTENNAPOLARIZATION1" + AntennaPolarization1 + Chr$(255)
' TNSx = TNSx + "COLORFILE" + GetFileNameOnly(ColorFile) + Chr$(255)
' TNSx = TNSx + "COLOROFFSET0" + Str$(FixedColorOffset) + Chr$(255)
' If DualSpecFile Then TNSx = TNSx + "COLOROFFSET1" + Str$(FixedColorOffset2) + Chr$(255)
' TNSx = TNSx + "COLORGAIN0" + Str$(ColorGain) + Chr$(255)
' If DualSpecFile Then TNSx = TNSx + "COLORGAIN1" + Str$(ColorGain2) + Chr$(255)
' If CorrectionFileLoaded Then
' If OpMode = Client Then
' TNSx = TNSx + "CORRECTIONFILENAME" + "Not Local" + Chr$(255)
' Else
' TNSx = TNSx + "CORRECTIONFILENAME" + CorrectionArrayName + Chr$(255)
' End If
' For I = 0 To UBound(CorrectionArray)
' TNSx = TNSx + "CAXF" + Str$(I) + "|" + Str$(CorrectionArrayFreq(I)) + Chr$(255)
' Next I
' For I = 0 To UBound(CorrectionArray)
' TNSx = TNSx + "CAX1" + Str$(I) + "|" + Str(CorrectionArray(I)) + Chr$(255)
' Next I
' If RCVR = stDualEmulator Or RCVR = stFSXdual Then
' For I = 0 To UBound(CorrectionArray)
' TNSx = TNSx + "CAX2" + Str$(I) + "|" + Str$(CorrectionArray2(I)) + Chr$(255)
' Next I
' End If
' End If
' For I = 1 To UBound(ClockMetaData)
' TNSx = TNSx + "CLOCKMSG" + Trim(Str(I)) + " " + ClockMetaData(I) + Chr(255)
' Next I
' TNSx = TNSx + "*]]*"
' GetModifiedNotes = TNSx
'End Function