Skip to content

Commit

Permalink
init (#29)
Browse files Browse the repository at this point in the history
* init

added raw file test

Added more tests (I forgot how absolutely boring unit testing and line/branch coverage are). Fixed file under test to actually behave as expected. So, I guess there's that.

* Being forced to look at the code reminds you to remove commented out code, or something

* Got rid of duplicate code linter in the laziest way possible

* moved imports to only be invoked when needed on startup

* bumped version. tweaked readme
  • Loading branch information
peads authored Jul 13, 2024
1 parent 4012a1d commit 7152aab
Show file tree
Hide file tree
Showing 10 changed files with 337 additions and 39 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ package manager; or, with caution, using `pip` directly.
## Notes
### Input data
* For raw files, input is system-default endianness,
* For wave files, the endianness defined by the RIFF header
* For wave files, the endianness defined by the RIFF/X header
* For tcp connections, data is expected to be big-endian
* Endianness can be inverted using the `-X` flag
* When necessary to specify the input datatype (`-e` flag), the selections map exactly to the "integer" and "float" types listed [here](https://docs.python.org/3/library/struct.html#format-characters)
Expand All @@ -49,8 +49,8 @@ to install separately from the default version included with your copy of Window
`python -m sdrterm -i file.wav --omega-out=5k --decimation=64 --center-frequency="15k" --plot=spec --correct-iq -o out.bin`
#### General explanation of options
* Input source: wave file
* Input data type: determined by RIFF header metadata
* Sample rate: determined by RIFF header metadata ($fs$ $S \over s$)
* Input data type: determined by RIFF/X header metadata
* Sample rate: determined by RIFF/X header metadata ($fs$ $S \over s$)
* Output lp: 5 kHz
* Decimation factor: $64 \implies$ $fs \over 64$ $S \over s$
* Offset from tuned frequency: +15 kHz
Expand Down
5 changes: 4 additions & 1 deletion example.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
####
# Usage: ./example.sh <wave_file>
####
SDRTERM_EXEC="python -m sdrterm";
if [[ -z ${SDRTERM_EXEC} ]]; then
SDRTERM_EXEC="python -m sdrterm";
fi

if [[ -z ${DSD_CMD} ]]; then
DSD_CMD="dsd -q -i - -o /dev/null -n";
fi
Expand Down
10 changes: 7 additions & 3 deletions example_simo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,18 @@
# Usage: ./example_simo.sh -i [<file> | <host>:<port>] -eB -r1024k -d25 -c"-1.2E+3" -t123.456M" --vfos=15000,-15000,30000" -w5k
####
OIFS=$IFS
SDRTERM_EXEC="python -m sdrterm";

function log {
echo "$(date +'%F %T')Z: ${1}";
}

ts=$(date +%Y%m%d%H%M);
log "timestamp: ${ts}";

if [[ -z ${SDRTERM_EXEC} ]]; then
SDRTERM_EXEC="python -m sdrterm";
fi

if [[ -z ${DSD_CMD} ]]; then
DSD_CMD="dsd -q -i - -o /dev/null -n";
fi
Expand All @@ -48,7 +52,7 @@ for i in ${@:1}; do
done
unset i;

cmd="${SDRTERM_EXEC} ${params} --simo 2>&1 | tee /tmp/sdrterm-${ts}.log";
cmd="${SDRTERM_EXEC} ${params} --simo 2>&1";
set -u;
echo "LOG: ${cmd}";
coproc SDRTERM { eval "$cmd"; }
Expand All @@ -72,7 +76,7 @@ function cleanup {
while IFS= ; read -r line; do
log "${line}";
done < ${logFiles["$i"]}
# rm "${logFiles[$i]}";
rm "${logFiles[$i]}";
done
unset i;

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ sources = ["src"]

[project]
name = "sdrterm"
version = "0.2.1"
version = "0.2.2"
dependencies = [
'scipy',
'numpy>=1.26',
Expand Down
46 changes: 27 additions & 19 deletions src/misc/file_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@
from enum import Enum
from struct import unpack
from typing import Iterable
from numpy import dtype


from numpy import dtype

from misc.mappable_enum import MappableEnum

Expand Down Expand Up @@ -61,20 +60,20 @@ def __str__(self):
return self.name

@classmethod
def fromWav(cls, bits: int, aFormat: Enum, bFormat: Enum) -> dtype:
def fromWav(cls, bits: int, aFormat: Enum, bFormat: Enum, isRifx: bool) -> dtype:
eight = {'S': cls.b, 'U': cls.B, 'None': cls.B}
sixteen = {'S': cls.h, 'U': cls.H, 'None': cls.h}
thirtytwo = {'S': cls.i, 'U': cls.I, 'None': cls.i}

ret = None
splt = aFormat.name.split('_')
if WaveFormat.WAVE_FORMAT_IEEE_FLOAT == aFormat:
if 32 == bits:
ret = cls.f.value
elif 64 == bits:
ret = cls.d.value
elif WaveFormat.WAVE_FORMAT_PCM == aFormat or WaveFormat.WAVE_FORMAT_EXTENSIBLE == aFormat:
istZahl = None
splt = aFormat.name.split('_')

if bFormat is not None and bFormat in ExWaveFormat:
splt = bFormat.name.split('_')
Expand All @@ -87,18 +86,18 @@ def fromWav(cls, bits: int, aFormat: Enum, bFormat: Enum) -> dtype:
elif 32 == bits:
ret = thirtytwo[str(istZahl)].value

if 'BE' != splt[2]:
ret = ret.newbyteorder('<')
else:
ret = ret.newbyteorder('>')
if ret is None:
raise ValueError(f'Unsupported format: {bits}: {aFormat}')

if ret is not None:
return ret
if not (isRifx or 'BE' == splt[2]):
ret = ret.newbyteorder('<')
else:
ret = ret.newbyteorder('>')

raise ValueError(f'Unsupported format: {bits}: {aFormat}')
return ret


def parseRawType(file: str, fs: int, enc: str) -> dict:
def parseRawType(file: str | None, fs: int, enc: str) -> dict:
if fs is None or fs < 1 or enc is None:
raise ValueError('Sampling rate, encoding type and bit-size are required for raw pcm input')
fs = int(fs)
Expand All @@ -121,14 +120,23 @@ def zipRet(x: Iterable):
return val


def checkWavHeader(f: str, fs: int, enc: str) -> dict:
if not f or ':' in f:
def checkWavHeader(f, fs: int, enc: str) -> dict:
if f is None or issubclass(type(f), str) and ':' in f:
return parseRawType(f, fs, enc)

with open(f, 'rb') as file:
# derived from http://soundfile.sapp.org/doc/WaveFormat/ and https://bts.keep-cool.org/wiki/Specs/CodecsValues
if b'RIFF' != file.read(4):
headerStr = '<IHHIIHH'
if b'RIF' != file.read(3):
if '.wav' in f:
raise ValueError('Invalid: Expected raw pcm file, but got malformed RIFF header')
return parseRawType(f, fs, enc)
else:
temp = file.read(1)
if not (b'F' == temp or b'X' == temp):
raise ValueError('Invalid: malformed RIFF header')
elif b'X' == temp:
headerStr = '>IHHIIHH'
chunkSize, = unpack('<I', file.read(4))
if b'WAVE' != file.read(4):
raise ValueError('Invalid: not wave file')
Expand All @@ -144,7 +152,7 @@ def checkWavHeader(f: str, fs: int, enc: str) -> dict:
blockAlign, # 2
bitsPerSample, # 2
# 20
) = unpack('<IHHIIHH', file.read(20))
) = unpack(headerStr, file.read(20))
ret = zipRet(ret)
subFormat = None

Expand All @@ -157,7 +165,8 @@ def checkWavHeader(f: str, fs: int, enc: str) -> dict:
raise ValueError('Invalid SubFormat GUID')
subFormat = ExWaveFormat(subFormat)

ret['bitsPerSample'] = DataType.fromWav(bitsPerSample, WaveFormat(ret['audioFormat']), subFormat)
ret['bitsPerSample'] = DataType.fromWav(bitsPerSample, WaveFormat(ret['audioFormat']), subFormat,
'>' in headerStr)
ret['bitRate'] = (bitsPerSample * byteRate * blockAlign) >> 3

off = -1
Expand All @@ -168,7 +177,6 @@ def checkWavHeader(f: str, fs: int, enc: str) -> dict:
off += temp
file.seek(0)
buf = file.read(off + 4)
if buf[-4:] != b'data':
raise ValueError('Invalid: could not find data section.')
assert b'data' == buf[-4:]
ret['dataOffset'] = file.tell()
return ret
9 changes: 6 additions & 3 deletions src/misc/io_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,15 @@ def __initializeOutputHandlers(cls,
buffers: list[Queue] = None,
**kwargs) -> None:
import os
from dsp.dsp_processor import DspProcessor
from dsp.vfo_processor import VfoProcessor
from misc.general_util import eprint
from plots.util import selectDemodulation, selectPlotType

cls.strct['processor'] = DspProcessor if not simo else VfoProcessor
if not simo:
from dsp.dsp_processor import DspProcessor
cls.strct['processor'] = DspProcessor
else:
from dsp.vfo_processor import VfoProcessor
cls.strct['processor'] = VfoProcessor
cls.strct['processor'] = cls.strct['processor'](fs, **kwargs)
selectDemodulation(dm, cls.strct['processor'])()

Expand Down
1 change: 0 additions & 1 deletion src/plots/waterfall_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ def __init__(self,
self.widget = PlotWidget()
self.plot = self.widget.plotItem
self.axis = self.plot.getAxis("bottom")
# self.xscale = self._NFFT / self.fs

self.window.setCentralWidget(self.widget)
self.plot.addItem(self.item)
Expand Down
7 changes: 0 additions & 7 deletions src/sdr/control_rtl_tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,6 @@ class ControlRtlTcp(Controller):
def __init__(self, connection: Receiver, resetBuffers: Callable):
super().__init__(connection)
self._resetBuffers = resetBuffers
# if resetBuffers is not None:
# setattr(self, 'resetBuffers', resetBuffers)
# connection.sendall(pack('>BI', RtlTcpCommands.SET_GAIN_MODE.value, 1))
# connection.sendall(pack('>BI', RtlTcpCommands.SET_AGC_MODE.value, 0))
# connection.sendall(pack('>BI', RtlTcpCommands.SET_TUNER_GAIN_BY_INDEX.value, 0))
# connection.sendall(pack('>BI', RtlTcpCommands.SET_SAMPLE_RATE.value, 1024000))
# connection.sendall(pack('>BI', RtlTcpCommands.SET_BIAS_TEE.value, 0))

@property
def resetBuffers(self) -> Callable:
Expand Down
1 change: 0 additions & 1 deletion src/sdrcontrol.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,6 @@ def compose(self) -> ComposeResult:
classes='source',
type='integer',
valid_empty=False)
# validate_on=['submitted'])
yield Button('Connect', id='connector', classes='sources connect', variant='success')

yield Label('Tuning')
Expand Down
Loading

0 comments on commit 7152aab

Please sign in to comment.