Build/dist steps

This commit is contained in:
xssfox 2023-12-28 17:38:29 +11:00
parent 5bb774876b
commit 7db04d059a
12 changed files with 518 additions and 403 deletions

2
.gitignore vendored
View file

@ -9,3 +9,5 @@ TODO
*.s16
*.data
.DS_Store
build
dist

171
README.md
View file

@ -1,10 +1,163 @@
Work in progress
A KISS TNC using the freedv data modems.
Untested instructions:
1. Update `include_dirs` and `library_dirs` in `freedvtnc2/freedv_build.py` to point to codec2
2. Create venv / activate
3. cd into freedvtnc2
3. `pip install -r requirements.txt`
3. run `python freedv_build.py`
4. cd back to the main project dir
5. run `python -m freedvtnc2 --help`
![Screenshot of the freedvtnc2 modem cli showing the chat feature and a file being transfered](screenshot.png)
## Features
- KISS interface (virtual serial, or TCP)
- Chat
- DATAC1, DATAC3 and DATAC4 modes
## Unsupported
- Windows
## Requirements
- hamlib
- portaudio / pyaudio
- c build tools
## Install
Instructions for a raspberrypi. Requires bookworm or python 3.11 to be installed.
```sh
# install required system packages
sudo apt-get update
sudo apt install git build-essential cmake portaudio19-dev python3 python3-dev libhamlib-utils python3-venv
# install pipx - either like this
python3 -m pip install --user pipx
python3 -m pipx ensurepath
# or
sudo apt install pipx
pipx ensurepath
# make sure the PATH is set correctly
source ~/.profile
# install codec2 (the debian packaged versions do not contain DATAC4 modem)
git clone https://github.com/drowe67/codec2.git
cd codec2
mkdir build_linux
cd build_linux
cmake ..
make
sudo make install
# install freedvtnc2
pipx install freedvtnc2
```
If libcodec2 is installed somewhere else you can use `CFLAGS="-I/path/to/headers/ -L/path/to/lib/"` during to ensure that we can find libcodec2 during the install
## Running
```
# Run rigctld in background. See rigctld --help on how to configure your rig
rigctld -m 1 -r /dev/null &
# Test to make sure rigctl works
rigctl -m 2 T 1 # enable PTT
rigctl -m 2 T 0 # disable PTT
# Get argument help
freedvtnc2 --help
#list audio devices
freedvtnc2 --list-audio-devices
freedvtnc2 --output-device 0 --input-device 0 --log-level DEBUG # it's useful to have debug turned on when first testing
```
The connect your favorite kiss tools up to the TNC over TCP port 8001 or PTS interface if enabled
## Testing
The CLI has a handy `test_ptt` to make test that PTT and sound output is working.
## Command line arguments
```
usage: freedvtnc2 [-h] [-c C] [--no-cli] [--list-audio-devices] [--log-level {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}] [--input-device INPUT_DEVICE] [--output-device OUTPUT_DEVICE]
[--output-volume OUTPUT_VOLUME] [--mode {DATAC1,DATAC3,DATAC4}] [--follow] [--max-packets-combined MAX_PACKETS_COMBINED] [--pts] [--kiss-tcp-port KISS_TCP_PORT]
[--kiss-tcp-address KISS_TCP_ADDRESS] [--rigctld-port RIGCTLD_PORT] [--rigctld-host RIGCTLD_HOST] [--ptt-on-delay-ms PTT_ON_DELAY_MS] [--ptt-off-delay-ms PTT_OFF_DELAY_MS]
[--callsign CALLSIGN]
options:
-h, --help show this help message and exit
-c C, -config C config file path
--no-cli [env var: FREEDVTNC2_CLI]
--list-audio-devices
--log-level {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}
[env var: FREEDVTNC2_LOG_LEVEL]
--input-device INPUT_DEVICE
[env var: FREEDVTNC2_INPUT_DEVICE]
--output-device OUTPUT_DEVICE
[env var: FREEDVTNC2_OUTPUT_DEVICE]
--output-volume OUTPUT_VOLUME
in db. postive = louder, negative = quiter [env var: FREEDVTNC2_OUTPUT_DB]
--mode {DATAC1,DATAC3,DATAC4}
The TX mode for the modem. The modem will receive all modes at once
--follow When enabled change TX mode to the mode being received. This is useful for stations operating automatically. [env var: FREEDVTNC2_FOLLOW]
--max-packets-combined MAX_PACKETS_COMBINED
How many kiss packets to combine into a single transmission [env var: FREEDVTNC2_MAX_PACKETS]
--pts Disables TCP and instead creates a PTS 'fake serial' interface [env var: FREEDVTNC2_PTS]
--kiss-tcp-port KISS_TCP_PORT
[env var: FREEDVTNC2_KISS_TCP_PORT]
--kiss-tcp-address KISS_TCP_ADDRESS
[env var: FREEDVTNC2_KISS_TCP_ADDRESS]
--rigctld-port RIGCTLD_PORT
TCP port for rigctld - set to 0 to disable rigctld support [env var: FREEDVTNC2_RIGTCTLD_PORT]
--rigctld-host RIGCTLD_HOST
Host for rigctld [env var: FREEDVTNC2_RIGTCTLD_HOST]
--ptt-on-delay-ms PTT_ON_DELAY_MS
Delay after triggering PTT before sending data [env var: FREEDVTNC2_PTT_ON_DELAY_MS]
--ptt-off-delay-ms PTT_OFF_DELAY_MS
Delay after sending data before releasing PTT [env var: FREEDVTNC2_PTT_OFF_DELAY_MS]
--callsign CALLSIGN Currently only used for chat [env var: FREEDVTNC2_CALLSIGN]
Args that start with '--' can also be set in a config file (~/.freedvtnc2.conf or specified via -c). Config file syntax allows: key=value, flag=true, stuff=[a,b,c] (for details, see syntax at
https://goo.gl/R74nmi). In general, command-line values override environment variables which override config file values which override defaults.
```
## CLI commands
```
FreeDVTNC2 Help^
---------------
callsign
Sets callsign - example: callsign N0CALL
clear
Clears TX queues
debug
Open the debug shell
exception
Raises and exemption to test the shell
exit
Exits FreeDVTNC2
follow
Allows the tx modem to change to the mode last received - follow on
help
This help
list_audio_devices
Lists audio device parameters
log_level
Set the log level
max_packets_combined
Set the max number of packets to combine together for a single transmission
mode
Change TX Mode: mode [DATAC1, DATAC3, DATAC4]
msg
Send a message
save_config
Save a config file to ~/.freedvtnc2.conf. Warning this will override your current config
send_string
Sends string over the modem
test_ptt
Turns on PTT for 2 seconds
volume
Set the volume gain in db for output level - you probably want to use soundcard configuration or radio configuration rather than this.
```
Credits
--
David Rowe and the FreeDV team for developing the modem and libraries

9
build_lib.py Normal file
View file

@ -0,0 +1,9 @@
from setuptools import setup
import freedvtnc2.freedv_build as freedv_build
def build(a):
setup(
ext_modules=[freedv_build.ffibuilder.distutils_extension()],
)

View file

@ -11,7 +11,7 @@ from prompt_toolkit.formatted_text import HTML, to_formatted_text
logging.basicConfig()
if __name__ == '__main__':
def main():
p = configargparse.ArgParser(default_config_files=['~/.freedvtnc2.conf'], config_file_parser_class=configargparse.DefaultConfigFileParser)
p.add('-c', '-config', required=False, is_config_file=True, help='config file path')
@ -193,6 +193,9 @@ if __name__ == '__main__':
time.sleep(0.1)
except KeyboardInterrupt:
log_handler.shell = None
rig.ptt_disable()
if "rig" in locals():
rig.ptt_disable()
input_device.close()
output_device.close()
output_device.close()
if __name__ == '__main__':
main()

View file

@ -94,10 +94,8 @@ class InputDevice():
logging.debug(f"Opening {self.device.name} for input")
if self.device.input_channels > 2:
raise NotImplementedError("Inputs with greater than 2 channels not supported")
if self.device.input_channels == 2:
logging.warning("Stereo input detected - Only the left channel will be used")
if self.device.input_channels >= 2:
logging.warning("Stereo (or more) input detected - Only the first/left channel will be used")
if self.device.sample_rate < sample_rate:
logging.critical(f"Input audio device sample rate {self.device.sample_rate} is less than modems sample rate {sample_rate} - this will cause problems")
@ -204,12 +202,17 @@ class OutputDevice():
logging.debug(f"Opening {self.device.name} for output")
if self.device.input_channels > 2:
raise NotImplementedError("Inputs with greater than 2 channels not supported")
if self.device.output_channels > 2:
logging.warning("Output detected with more than 2 channels. We'll try to open the device with 2 channels")
self.device.output_channels = 2
if self.device.sample_rate < sample_rate:
logging.critical(f"Output audio device sample rate {self.device.sample_rate} is less than modems sample rate {sample_rate} - this will cause problems")
self.ptt_trigger = ptt_trigger
self.ptt_release = ptt_release
self.ptt = False
self.stream = p.open(format=FORMAT,
channels=self.device.output_channels,
rate=self.device.sample_rate,
@ -220,9 +223,7 @@ class OutputDevice():
frames_per_buffer=4096
)
self.ptt_trigger = ptt_trigger
self.ptt_release = ptt_release
self.ptt = False
def write_raw(self,data:bytes):
if self.device.sample_rate != self.sample_rate:

View file

@ -1,291 +0,0 @@
typedef struct {
float real;
float imag;
} COMP;
// available speech modes
#define FREEDV_MODE_1600 0
#define FREEDV_MODE_2400A 3
#define FREEDV_MODE_2400B 4
#define FREEDV_MODE_800XA 5
#define FREEDV_MODE_700C 6
#define FREEDV_MODE_700D 7
#define FREEDV_MODE_700E 13
#define FREEDV_MODE_2020 8
#define FREEDV_MODE_2020B 16
// available data modes
#define FREEDV_MODE_FSK_LDPC 9
#define FREEDV_MODE_DATAC1 10
#define FREEDV_MODE_DATAC3 12
#define FREEDV_MODE_DATAC0 14
#define FREEDV_MODE_DATAC4 18
#define FREEDV_MODE_DATAC13 19
// Sample rates used
#define FREEDV_FS_8000 8000
#define FREEDV_FS_16000 16000
// peak (complex) sample value from Tx modulator
#define FREEDV_PEAK 16384
// Return code flags for freedv_get_rx_status() function
#define FREEDV_RX_TRIAL_SYNC 0x1 // demodulator has trial sync
#define FREEDV_RX_SYNC 0x2 // demodulator has sync
#define FREEDV_RX_BITS 0x4 // data bits have been returned
#define FREEDV_RX_BIT_ERRORS \
0x8 // FEC may not have corrected all bit errors (not all parity checks OK)
// optional operator control of OFDM modem state machine
#define FREEDV_SYNC_UNSYNC \
0 // force sync state machine to lose sync, and search for new sync
#define FREEDV_SYNC_AUTO 1 // falls out of sync automatically
#define FREEDV_SYNC_MANUAL 2 // fall out of sync only under operator control
#define FREEDV_VARICODE_MAX_BITS 12 // max bits for each ASCII character
// These macros allow us to disable unwanted modes at compile tine, for example
// to save memory on embedded systems or the remove need to link other
// libraries. By default we enable all modes. Disable during compile time e.g
// -DFREEDV_MODE_1600_EN=0 will enable all but FreeDV 1600. Or the other way
// round -DFREEDV_MODE_EN_DEFAULT=0 -DFREEDV_MODE_1600_EN=1 will enable only
// FreeDV 1600
// struct that hold state information for one freedv instance
struct freedv;
// Some modes allow extra configuration parameters
struct freedv_advanced {
int interleave_frames; // now unused but remains to prevent breaking API for
// legacy apps
// parameters for FREEDV_MODE_FSK_LDPC
int M; // 2 or 4 FSK
int Rs; // Symbol rate Hz
int Fs; // Sample rate Hz
int first_tone; // Freq of first tone Hz
int tone_spacing; // Spacing between tones Hz
char *codename; // LDPC codename, from codes listed in ldpc_codes.c
};
// Called when text message char is decoded
typedef void (*freedv_callback_rx)(void *, char);
// Called when new text message char is needed
typedef char (*freedv_callback_tx)(void *);
typedef void (*freedv_calback_error_pattern)(void *error_pattern_callback_state,
short error_pattern[],
int sz_error_pattern);
// Protocol bits are packed MSB-first
// Called when a frame containing protocol data is decoded
typedef void (*freedv_callback_protorx)(void *, char *);
// Called when a frame containing protocol data is to be sent
typedef void (*freedv_callback_prototx)(void *, char *);
// Data packet callbacks
// Called when a packet has been received
typedef void (*freedv_callback_datarx)(void *, unsigned char *packet,
size_t size);
// Called when a new packet can be send
typedef void (*freedv_callback_datatx)(void *, unsigned char *packet,
size_t *size);
/*---------------------------------------------------------------------------*\
FreeDV API functions
\*---------------------------------------------------------------------------*/
// open, close ----------------------------------------------------------------
struct freedv *freedv_open_advanced(int mode, struct freedv_advanced *adv);
struct freedv *freedv_open(int mode);
void freedv_close(struct freedv *freedv);
// Transmit -------------------------------------------------------------------
void freedv_tx(struct freedv *freedv, short mod_out[], short speech_in[]);
void freedv_comptx(struct freedv *freedv, COMP mod_out[], short speech_in[]);
void freedv_datatx(struct freedv *f, short mod_out[]);
int freedv_data_ntxframes(struct freedv *freedv);
void freedv_rawdatatx(struct freedv *f, short mod_out[],
unsigned char *packed_payload_bits);
void freedv_rawdatacomptx(struct freedv *f, COMP mod_out[],
unsigned char *packed_payload_bits);
int freedv_rawdatapreambletx(struct freedv *f, short mod_out[]);
int freedv_rawdatapreamblecomptx(struct freedv *f, COMP mod_out[]);
int freedv_rawdatapostambletx(struct freedv *f, short mod_out[]);
int freedv_rawdatapostamblecomptx(struct freedv *f, COMP mod_out[]);
// Receive -------------------------------------------------------------------
int freedv_nin(struct freedv *freedv);
int freedv_rx(struct freedv *freedv, short speech_out[], short demod_in[]);
int freedv_shortrx(struct freedv *freedv, short speech_out[], short demod_in[],
float gain);
int freedv_floatrx(struct freedv *freedv, short speech_out[], float demod_in[]);
int freedv_comprx(struct freedv *freedv, short speech_out[], COMP demod_in[]);
int freedv_rawdatarx(struct freedv *freedv, unsigned char *packed_payload_bits,
short demod_in[]);
int freedv_rawdatacomprx(struct freedv *freedv,
unsigned char *packed_payload_bits, COMP demod_in[]);
// Helper functions
// -------------------------------------------------------------------
int freedv_codec_frames_from_rawdata(struct freedv *freedv,
unsigned char *codec_frames,
unsigned char *rawdata);
int freedv_rawdata_from_codec_frames(struct freedv *freedv,
unsigned char *rawdata,
unsigned char *codec_frames);
unsigned short freedv_gen_crc16(unsigned char *bytes, int nbytes);
void freedv_pack(unsigned char *bytes, unsigned char *bits, int nbits);
void freedv_unpack(unsigned char *bits, unsigned char *bytes, int nbits);
unsigned short freedv_crc16_unpacked(unsigned char *bits, int nbits);
int freedv_check_crc16_unpacked(unsigned char *unpacked_bits, int nbits);
// Set parameters ------------------------------------------------------------
void freedv_set_callback_txt(struct freedv *freedv, freedv_callback_rx rx,
freedv_callback_tx tx, void *callback_state);
void freedv_set_callback_protocol(struct freedv *freedv,
freedv_callback_protorx rx,
freedv_callback_prototx tx,
void *callback_state);
void freedv_set_callback_data(struct freedv *freedv,
freedv_callback_datarx datarx,
freedv_callback_datatx datatx,
void *callback_state);
void freedv_set_test_frames(struct freedv *freedv, int test_frames);
void freedv_set_test_frames_diversity(struct freedv *freedv,
int test_frames_diversity);
void freedv_set_squelch_en(struct freedv *freedv, bool squelch_en);
void freedv_set_snr_squelch_thresh(struct freedv *freedv,
float snr_squelch_thresh);
void freedv_set_clip(struct freedv *freedv, bool val);
void freedv_set_total_bit_errors(struct freedv *freedv, int val);
void freedv_set_total_bits(struct freedv *freedv, int val);
void freedv_set_total_bit_errors_coded(struct freedv *freedv, int val);
void freedv_set_total_bits_coded(struct freedv *freedv, int val);
void freedv_set_total_packets(struct freedv *freedv, int val);
void freedv_set_total_packet_errors(struct freedv *freedv, int val);
void freedv_set_callback_error_pattern(struct freedv *freedv,
freedv_calback_error_pattern cb,
void *state);
void freedv_set_varicode_code_num(struct freedv *freedv, int val);
void freedv_set_data_header(struct freedv *freedv, unsigned char *header);
void freedv_set_carrier_ampl(struct freedv *freedv, int c, float ampl);
void freedv_set_sync(struct freedv *freedv, int sync_cmd);
void freedv_set_verbose(struct freedv *freedv, int verbosity);
void freedv_set_tx_bpf(struct freedv *freedv, int val);
void freedv_set_tx_amp(struct freedv *freedv, float amp);
void freedv_set_ext_vco(struct freedv *f, int val);
void freedv_set_phase_est_bandwidth_mode(struct freedv *f, int val);
void freedv_set_eq(struct freedv *f, bool val);
void freedv_set_frames_per_burst(struct freedv *f, int framesperburst);
void freedv_passthrough_gain(struct freedv *f, float g);
int freedv_set_tuning_range(struct freedv *freedv, float val_fmin,
float val_fmax);
// Get parameters
// -------------------------------------------------------------------------
struct MODEM_STATS;
int freedv_get_version(void);
char *freedv_get_hash(void);
int freedv_get_mode(struct freedv *freedv);
void freedv_get_modem_stats(struct freedv *freedv, int *sync, float *snr_est);
void freedv_get_modem_extended_stats(struct freedv *freedv,
struct MODEM_STATS *stats);
int freedv_get_test_frames(struct freedv *freedv);
int freedv_get_speech_sample_rate(struct freedv *freedv);
int freedv_get_n_speech_samples(struct freedv *freedv);
int freedv_get_n_max_speech_samples(struct freedv *freedv);
int freedv_get_modem_sample_rate(struct freedv *freedv);
int freedv_get_modem_symbol_rate(struct freedv *freedv);
int freedv_get_n_max_modem_samples(struct freedv *freedv);
int freedv_get_n_nom_modem_samples(struct freedv *freedv);
int freedv_get_n_tx_modem_samples(struct freedv *freedv);
int freedv_get_n_tx_preamble_modem_samples(struct freedv *freedv);
int freedv_get_n_tx_postamble_modem_samples(struct freedv *freedv);
// bit error rate stats
int freedv_get_total_bits(struct freedv *freedv);
int freedv_get_total_bit_errors(struct freedv *freedv);
int freedv_get_total_bits_coded(struct freedv *freedv);
int freedv_get_total_bit_errors_coded(struct freedv *freedv);
int freedv_get_total_packets(struct freedv *freedv);
int freedv_get_total_packet_errors(struct freedv *freedv);
int freedv_get_rx_status(struct freedv *freedv);
void freedv_get_fsk_S_and_N(struct freedv *freedv, float *S, float *N);
int freedv_get_sync(struct freedv *freedv);
int freedv_get_sync_interleaver(struct freedv *freedv);
// access to speech codec states
struct FSK *freedv_get_fsk(struct freedv *f);
struct CODEC2 *freedv_get_codec2(struct freedv *freedv);
int freedv_get_bits_per_codec_frame(struct freedv *freedv);
int freedv_get_bits_per_modem_frame(struct freedv *freedv);
int freedv_get_sz_error_pattern(struct freedv *freedv);
int freedv_get_protocol_bits(struct freedv *freedv);
// #define MODEM_STATS_NC_MAX 50
// #define MODEM_STATS_NR_MAX 320
// #define MODEM_STATS_ET_MAX 8
// #define MODEM_STATS_EYE_IND_MAX 160
// #define MODEM_STATS_NSPEC 512
// #define MODEM_STATS_MAX_F_HZ 4000
// #define MODEM_STATS_MAX_F_EST 4
// struct MODEM_STATS {
// int Nc;
// float snr_est; /* estimated SNR of rx signal in dB (3 kHz noise BW) */
// COMP rx_symbols[MODEM_STATS_NR_MAX][MODEM_STATS_NC_MAX + 1];
// /* latest received symbols, for scatter plot */
// int nr; /* number of rows in rx_symbols */
// int sync; /* demod sync state */
// float foff; /* estimated freq offset in Hz */
// float rx_timing; /* estimated optimum timing offset in samples */
// float clock_offset; /* Estimated tx/rx sample clock offset in ppm */
// float sync_metric; /* number between 0 and 1 indicating quality of sync */
// int pre, post; /* preamble/postamble det counters for burst data */
// int uw_fails; /* Failed to detect Unique word (burst data) */
// /* FSK eye diagram traces */
// /* Eye diagram plot -- first dim is trace number, second is the trace idx */
// float rx_eye[MODEM_STATS_ET_MAX][MODEM_STATS_EYE_IND_MAX];
// int neyetr; /* How many eye traces are plotted */
// int neyesamp; /* How many samples in the eye diagram */
// /* optional for FSK modems - est tone freqs */
// float f_est[MODEM_STATS_MAX_F_EST];
// /* Buf for FFT/waterfall */
// float fft_buf[2 * MODEM_STATS_NSPEC];
// void *fft_cfg;
// };
// void modem_stats_open(struct MODEM_STATS *f);
// void modem_stats_close(struct MODEM_STATS *f);
// void modem_stats_get_rx_spectrum(struct MODEM_STATS *f, float mag_spec_dB[],
// COMP rx_fdm[], int nin);

View file

@ -3,7 +3,302 @@ ffibuilder = FFI()
# cdef() expects a single string declaring the C types, functions and
# globals needed to use the shared object. It must be in valid C syntax.
ffibuilder.cdef(open("freedv.h").read())
ffibuilder.cdef(
"""
typedef struct {
float real;
float imag;
} COMP;
// available speech modes
#define FREEDV_MODE_1600 0
#define FREEDV_MODE_2400A 3
#define FREEDV_MODE_2400B 4
#define FREEDV_MODE_800XA 5
#define FREEDV_MODE_700C 6
#define FREEDV_MODE_700D 7
#define FREEDV_MODE_700E 13
#define FREEDV_MODE_2020 8
#define FREEDV_MODE_2020B 16
// available data modes
#define FREEDV_MODE_FSK_LDPC 9
#define FREEDV_MODE_DATAC1 10
#define FREEDV_MODE_DATAC3 12
#define FREEDV_MODE_DATAC0 14
#define FREEDV_MODE_DATAC4 18
#define FREEDV_MODE_DATAC13 19
// Sample rates used
#define FREEDV_FS_8000 8000
#define FREEDV_FS_16000 16000
// peak (complex) sample value from Tx modulator
#define FREEDV_PEAK 16384
// Return code flags for freedv_get_rx_status() function
#define FREEDV_RX_TRIAL_SYNC 0x1 // demodulator has trial sync
#define FREEDV_RX_SYNC 0x2 // demodulator has sync
#define FREEDV_RX_BITS 0x4 // data bits have been returned
#define FREEDV_RX_BIT_ERRORS \
0x8 // FEC may not have corrected all bit errors (not all parity checks OK)
// optional operator control of OFDM modem state machine
#define FREEDV_SYNC_UNSYNC \
0 // force sync state machine to lose sync, and search for new sync
#define FREEDV_SYNC_AUTO 1 // falls out of sync automatically
#define FREEDV_SYNC_MANUAL 2 // fall out of sync only under operator control
#define FREEDV_VARICODE_MAX_BITS 12 // max bits for each ASCII character
// These macros allow us to disable unwanted modes at compile tine, for example
// to save memory on embedded systems or the remove need to link other
// libraries. By default we enable all modes. Disable during compile time e.g
// -DFREEDV_MODE_1600_EN=0 will enable all but FreeDV 1600. Or the other way
// round -DFREEDV_MODE_EN_DEFAULT=0 -DFREEDV_MODE_1600_EN=1 will enable only
// FreeDV 1600
// struct that hold state information for one freedv instance
struct freedv;
// Some modes allow extra configuration parameters
struct freedv_advanced {
int interleave_frames; // now unused but remains to prevent breaking API for
// legacy apps
// parameters for FREEDV_MODE_FSK_LDPC
int M; // 2 or 4 FSK
int Rs; // Symbol rate Hz
int Fs; // Sample rate Hz
int first_tone; // Freq of first tone Hz
int tone_spacing; // Spacing between tones Hz
char *codename; // LDPC codename, from codes listed in ldpc_codes.c
};
// Called when text message char is decoded
typedef void (*freedv_callback_rx)(void *, char);
// Called when new text message char is needed
typedef char (*freedv_callback_tx)(void *);
typedef void (*freedv_calback_error_pattern)(void *error_pattern_callback_state,
short error_pattern[],
int sz_error_pattern);
// Protocol bits are packed MSB-first
// Called when a frame containing protocol data is decoded
typedef void (*freedv_callback_protorx)(void *, char *);
// Called when a frame containing protocol data is to be sent
typedef void (*freedv_callback_prototx)(void *, char *);
// Data packet callbacks
// Called when a packet has been received
typedef void (*freedv_callback_datarx)(void *, unsigned char *packet,
size_t size);
// Called when a new packet can be send
typedef void (*freedv_callback_datatx)(void *, unsigned char *packet,
size_t *size);
/*---------------------------------------------------------------------------*\
FreeDV API functions
\*---------------------------------------------------------------------------*/
// open, close ----------------------------------------------------------------
struct freedv *freedv_open_advanced(int mode, struct freedv_advanced *adv);
struct freedv *freedv_open(int mode);
void freedv_close(struct freedv *freedv);
// Transmit -------------------------------------------------------------------
void freedv_tx(struct freedv *freedv, short mod_out[], short speech_in[]);
void freedv_comptx(struct freedv *freedv, COMP mod_out[], short speech_in[]);
void freedv_datatx(struct freedv *f, short mod_out[]);
int freedv_data_ntxframes(struct freedv *freedv);
void freedv_rawdatatx(struct freedv *f, short mod_out[],
unsigned char *packed_payload_bits);
void freedv_rawdatacomptx(struct freedv *f, COMP mod_out[],
unsigned char *packed_payload_bits);
int freedv_rawdatapreambletx(struct freedv *f, short mod_out[]);
int freedv_rawdatapreamblecomptx(struct freedv *f, COMP mod_out[]);
int freedv_rawdatapostambletx(struct freedv *f, short mod_out[]);
int freedv_rawdatapostamblecomptx(struct freedv *f, COMP mod_out[]);
// Receive -------------------------------------------------------------------
int freedv_nin(struct freedv *freedv);
int freedv_rx(struct freedv *freedv, short speech_out[], short demod_in[]);
int freedv_shortrx(struct freedv *freedv, short speech_out[], short demod_in[],
float gain);
int freedv_floatrx(struct freedv *freedv, short speech_out[], float demod_in[]);
int freedv_comprx(struct freedv *freedv, short speech_out[], COMP demod_in[]);
int freedv_rawdatarx(struct freedv *freedv, unsigned char *packed_payload_bits,
short demod_in[]);
int freedv_rawdatacomprx(struct freedv *freedv,
unsigned char *packed_payload_bits, COMP demod_in[]);
// Helper functions
// -------------------------------------------------------------------
int freedv_codec_frames_from_rawdata(struct freedv *freedv,
unsigned char *codec_frames,
unsigned char *rawdata);
int freedv_rawdata_from_codec_frames(struct freedv *freedv,
unsigned char *rawdata,
unsigned char *codec_frames);
unsigned short freedv_gen_crc16(unsigned char *bytes, int nbytes);
void freedv_pack(unsigned char *bytes, unsigned char *bits, int nbits);
void freedv_unpack(unsigned char *bits, unsigned char *bytes, int nbits);
unsigned short freedv_crc16_unpacked(unsigned char *bits, int nbits);
int freedv_check_crc16_unpacked(unsigned char *unpacked_bits, int nbits);
// Set parameters ------------------------------------------------------------
void freedv_set_callback_txt(struct freedv *freedv, freedv_callback_rx rx,
freedv_callback_tx tx, void *callback_state);
void freedv_set_callback_protocol(struct freedv *freedv,
freedv_callback_protorx rx,
freedv_callback_prototx tx,
void *callback_state);
void freedv_set_callback_data(struct freedv *freedv,
freedv_callback_datarx datarx,
freedv_callback_datatx datatx,
void *callback_state);
void freedv_set_test_frames(struct freedv *freedv, int test_frames);
void freedv_set_test_frames_diversity(struct freedv *freedv,
int test_frames_diversity);
void freedv_set_squelch_en(struct freedv *freedv, bool squelch_en);
void freedv_set_snr_squelch_thresh(struct freedv *freedv,
float snr_squelch_thresh);
void freedv_set_clip(struct freedv *freedv, bool val);
void freedv_set_total_bit_errors(struct freedv *freedv, int val);
void freedv_set_total_bits(struct freedv *freedv, int val);
void freedv_set_total_bit_errors_coded(struct freedv *freedv, int val);
void freedv_set_total_bits_coded(struct freedv *freedv, int val);
void freedv_set_total_packets(struct freedv *freedv, int val);
void freedv_set_total_packet_errors(struct freedv *freedv, int val);
void freedv_set_callback_error_pattern(struct freedv *freedv,
freedv_calback_error_pattern cb,
void *state);
void freedv_set_varicode_code_num(struct freedv *freedv, int val);
void freedv_set_data_header(struct freedv *freedv, unsigned char *header);
void freedv_set_carrier_ampl(struct freedv *freedv, int c, float ampl);
void freedv_set_sync(struct freedv *freedv, int sync_cmd);
void freedv_set_verbose(struct freedv *freedv, int verbosity);
void freedv_set_tx_bpf(struct freedv *freedv, int val);
void freedv_set_tx_amp(struct freedv *freedv, float amp);
void freedv_set_ext_vco(struct freedv *f, int val);
void freedv_set_phase_est_bandwidth_mode(struct freedv *f, int val);
void freedv_set_eq(struct freedv *f, bool val);
void freedv_set_frames_per_burst(struct freedv *f, int framesperburst);
void freedv_passthrough_gain(struct freedv *f, float g);
int freedv_set_tuning_range(struct freedv *freedv, float val_fmin,
float val_fmax);
// Get parameters
// -------------------------------------------------------------------------
struct MODEM_STATS;
int freedv_get_version(void);
char *freedv_get_hash(void);
int freedv_get_mode(struct freedv *freedv);
void freedv_get_modem_stats(struct freedv *freedv, int *sync, float *snr_est);
void freedv_get_modem_extended_stats(struct freedv *freedv,
struct MODEM_STATS *stats);
int freedv_get_test_frames(struct freedv *freedv);
int freedv_get_speech_sample_rate(struct freedv *freedv);
int freedv_get_n_speech_samples(struct freedv *freedv);
int freedv_get_n_max_speech_samples(struct freedv *freedv);
int freedv_get_modem_sample_rate(struct freedv *freedv);
int freedv_get_modem_symbol_rate(struct freedv *freedv);
int freedv_get_n_max_modem_samples(struct freedv *freedv);
int freedv_get_n_nom_modem_samples(struct freedv *freedv);
int freedv_get_n_tx_modem_samples(struct freedv *freedv);
int freedv_get_n_tx_preamble_modem_samples(struct freedv *freedv);
int freedv_get_n_tx_postamble_modem_samples(struct freedv *freedv);
// bit error rate stats
int freedv_get_total_bits(struct freedv *freedv);
int freedv_get_total_bit_errors(struct freedv *freedv);
int freedv_get_total_bits_coded(struct freedv *freedv);
int freedv_get_total_bit_errors_coded(struct freedv *freedv);
int freedv_get_total_packets(struct freedv *freedv);
int freedv_get_total_packet_errors(struct freedv *freedv);
int freedv_get_rx_status(struct freedv *freedv);
void freedv_get_fsk_S_and_N(struct freedv *freedv, float *S, float *N);
int freedv_get_sync(struct freedv *freedv);
int freedv_get_sync_interleaver(struct freedv *freedv);
// access to speech codec states
struct FSK *freedv_get_fsk(struct freedv *f);
struct CODEC2 *freedv_get_codec2(struct freedv *freedv);
int freedv_get_bits_per_codec_frame(struct freedv *freedv);
int freedv_get_bits_per_modem_frame(struct freedv *freedv);
int freedv_get_sz_error_pattern(struct freedv *freedv);
int freedv_get_protocol_bits(struct freedv *freedv);
// #define MODEM_STATS_NC_MAX 50
// #define MODEM_STATS_NR_MAX 320
// #define MODEM_STATS_ET_MAX 8
// #define MODEM_STATS_EYE_IND_MAX 160
// #define MODEM_STATS_NSPEC 512
// #define MODEM_STATS_MAX_F_HZ 4000
// #define MODEM_STATS_MAX_F_EST 4
// struct MODEM_STATS {
// int Nc;
// float snr_est; /* estimated SNR of rx signal in dB (3 kHz noise BW) */
// COMP rx_symbols[MODEM_STATS_NR_MAX][MODEM_STATS_NC_MAX + 1];
// /* latest received symbols, for scatter plot */
// int nr; /* number of rows in rx_symbols */
// int sync; /* demod sync state */
// float foff; /* estimated freq offset in Hz */
// float rx_timing; /* estimated optimum timing offset in samples */
// float clock_offset; /* Estimated tx/rx sample clock offset in ppm */
// float sync_metric; /* number between 0 and 1 indicating quality of sync */
// int pre, post; /* preamble/postamble det counters for burst data */
// int uw_fails; /* Failed to detect Unique word (burst data) */
// /* FSK eye diagram traces */
// /* Eye diagram plot -- first dim is trace number, second is the trace idx */
// float rx_eye[MODEM_STATS_ET_MAX][MODEM_STATS_EYE_IND_MAX];
// int neyetr; /* How many eye traces are plotted */
// int neyesamp; /* How many samples in the eye diagram */
// /* optional for FSK modems - est tone freqs */
// float f_est[MODEM_STATS_MAX_F_EST];
// /* Buf for FFT/waterfall */
// float fft_buf[2 * MODEM_STATS_NSPEC];
// void *fft_cfg;
// };
// void modem_stats_open(struct MODEM_STATS *f);
// void modem_stats_close(struct MODEM_STATS *f);
// void modem_stats_get_rx_spectrum(struct MODEM_STATS *f, float mag_spec_dB[],
// COMP rx_fdm[], int nin);
"""
)
# set_source() gives the name of the python extension module to
# produce, and some C source code as a string. This C code needs
@ -12,11 +307,10 @@ ffibuilder.cdef(open("freedv.h").read())
ffibuilder.set_source("_freedv_cffi",
"""
#include "freedv_api.h" // the C header of the library
#include "modem_stats.h" // the C header of the library
""",
libraries=['codec2'],
include_dirs = [ "/Users/mwheeler/src/codec2/src/"],
library_dirs = ["/Users/mwheeler/src/codec2/build_linux/src/"]
include_dirs = [ "/usr/include/codec2/", "/usr/local/include/codec2/", "/opt/homebrew/include/codec2/"],
library_dirs = ["/lib", "/usr/lib", "/usr/local/lib/", "/opt/homebrew/lib/"]
) # library name, for the linker
if __name__ == "__main__":

View file

@ -1,4 +1,4 @@
from ._freedv_cffi import ffi, lib
from _freedv_cffi import ffi, lib
from typing import Callable
from dataclasses import dataclass

View file

@ -1,83 +0,0 @@
cffi==1.16.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \
--hash=sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a \
--hash=sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417 \
--hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \
--hash=sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520 \
--hash=sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36 \
--hash=sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743 \
--hash=sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8 \
--hash=sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed \
--hash=sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684 \
--hash=sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56 \
--hash=sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324 \
--hash=sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d \
--hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \
--hash=sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e \
--hash=sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088 \
--hash=sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000 \
--hash=sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7 \
--hash=sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e \
--hash=sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673 \
--hash=sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c \
--hash=sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe \
--hash=sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2 \
--hash=sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098 \
--hash=sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8 \
--hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \
--hash=sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0 \
--hash=sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b \
--hash=sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896 \
--hash=sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e \
--hash=sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9 \
--hash=sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2 \
--hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \
--hash=sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6 \
--hash=sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404 \
--hash=sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f \
--hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \
--hash=sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4 \
--hash=sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc \
--hash=sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936 \
--hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \
--hash=sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872 \
--hash=sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb \
--hash=sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614 \
--hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1 \
--hash=sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d \
--hash=sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969 \
--hash=sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b \
--hash=sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4 \
--hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \
--hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \
--hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357
configargparse==1.7 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:d249da6591465c6c26df64a9f73d2536e743be2f244eb3ebe61114af2f94f86b \
--hash=sha256:e7067471884de5478c58a511e529f0f9bd1c66bfef1dea90935438d6c23306d1
kissfix==7.0.11 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:6d17a27c152a06be0329c701c2f32e1f5bdbc2c1a8361ae1af4c92e4d406baa8 \
--hash=sha256:aa2a90d19549eba1ad08adb83cca7b9d147ec805918c208cfbb85dc834e2eeb8
pyaudio==0.2.14 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:009f357ee5aa6bc8eb19d69921cd30e98c42cddd34210615d592a71d09c4bd57 \
--hash=sha256:126065b5e82a1c03ba16e7c0404d8f54e17368836e7d2d92427358ad44fefe61 \
--hash=sha256:12f2f1ba04e06ff95d80700a78967897a489c05e093e3bffa05a84ed9c0a7fa3 \
--hash=sha256:2a166fc88d435a2779810dd2678354adc33499e9d4d7f937f28b20cc55893e83 \
--hash=sha256:2dac0d6d675fe7e181ba88f2de88d321059b69abd52e3f4934a8878e03a7a074 \
--hash=sha256:506b32a595f8693811682ab4b127602d404df7dfc453b499c91a80d0f7bad289 \
--hash=sha256:5fce4bcdd2e0e8c063d835dbe2860dac46437506af509353c7f8114d4bacbd5b \
--hash=sha256:78dfff3879b4994d1f4fc6485646a57755c6ee3c19647a491f790a0895bd2f87 \
--hash=sha256:858caf35b05c26d8fc62f1efa2e8f53d5fa1a01164842bd622f70ddc41f55000 \
--hash=sha256:bbeb01d36a2f472ae5ee5e1451cacc42112986abe622f735bb870a5db77cf903 \
--hash=sha256:f745109634a7c19fa4d6b8b7d6967c3123d988c9ade0cd35d4295ee1acdb53e9
pycparser==2.21 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
--hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206
pydub==0.25.1 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6 \
--hash=sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f
pyserial==3.5 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb \
--hash=sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0
tabulate==0.9.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c \
--hash=sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f

View file

@ -166,6 +166,22 @@ files = [
[package.extras]
cp2110 = ["hidapi"]
[[package]]
name = "setuptools"
version = "69.0.3"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"},
{file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "tabulate"
version = "0.9.0"
@ -194,4 +210,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "04edbe4763c33088c098a98a234e973ec663bd489f67383785eacd5b74b242d3"
content-hash = "757c17914cf4ba280ceed0cddb4fd9d9cf419984a6a2310962c41c352c6b5bc6"

View file

@ -1,10 +1,14 @@
[tool.poetry]
name = "freedvtnc2"
version = "0.1.0"
version = "0.0.1"
description = ""
authors = ["xssfox <xss@sprocketfox.io>"]
readme = "README.md"
[tool.poetry.build]
script = "build_lib.py"
generate-setup-file = true
[tool.poetry.dependencies]
python = "^3.11"
cffi = "^1.16.0"
@ -14,8 +18,15 @@ tabulate = "^0.9.0"
pydub = "^0.25.1"
kissfix = "^7.0.11"
prompt-toolkit = "^3.0.43"
setuptools = "^69.0.3"
[tool.poetry.group.dev.dependencies]
setuptools = "^69.0.3"
[build-system]
requires = ["poetry-core"]
requires = ["poetry-core","setuptools","cffi"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
freedvtnc2 = 'freedvtnc2:__main__.main'

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB