7 Commits

Author SHA1 Message Date
  Xerbo 66b8dd026e
Fix memory leak and move state entirely into aptdec_t 1 year ago
  Xerbo 071a9c8f84
Better naming, but valid this time 1 year ago
  Xerbo c9d89ea210
Better naming 1 year ago
  Xerbo 61ec605ffd
Fix syntax 1 year ago
  Xerbo 8c6cba0aa3
ARM builds 1 year ago
  Xerbo 711d0e0163
MSVC build in GitHub actions 1 year ago
  Xerbo c551e70335
Include missing header and fix stddev 1 year ago
13 changed files with 172 additions and 56 deletions
Unified View
  1. +2
    -3
      .github/ISSUE_TEMPLATE/bug-report.md
  2. +49
    -6
      .github/workflows/build.yml
  3. +1
    -0
      .gitignore
  4. +3
    -2
      CMakeLists.txt
  5. +34
    -0
      UPGRADE_GUIDE.md
  6. +1
    -0
      aptdec-cli/main.c
  7. +18
    -0
      build_arm.sh
  8. +13
    -11
      build_windows.bat
  9. +19
    -0
      cmake/toolchain-armhf.cmake
  10. +1
    -1
      cmake/toolchain-mingw32.cmake
  11. +3
    -6
      libaptdec/algebra.c
  12. +27
    -27
      libaptdec/dsp.c
  13. +1
    -0
      libaptdec/effects.c

+ 2
- 3
.github/ISSUE_TEMPLATE/bug-report.md View File

@@ -4,7 +4,6 @@ about: Create a bug report to improve aptdec
title: '' title: ''
labels: bug labels: bug
assignees: '' assignees: ''

--- ---


**Describe the bug** **Describe the bug**
@@ -17,7 +16,7 @@ Steps to reproduce the behavior.
A clear description of what you expected to happen. A clear description of what you expected to happen.


**Link to audio** **Link to audio**
A link to the audio that caused the problem.
A link to the audio file that caused the problem (if applicable).


**Build information** **Build information**
The commit of aptdec you are running (check with `git rev-parse HEAD`), make sure it's up to date before opening this issue.
The version of aptdec you are running (check with `git describe --tag`), make sure it's up to date before opening this issue.

+ 49
- 6
.github/workflows/build.yml View File

@@ -29,16 +29,16 @@ jobs:
- name: Build and package - name: Build and package
run: cmake --build build -j$(nproc) && cmake --build build --target package run: cmake --build build -j$(nproc) && cmake --build build --target package


- name: Upload TGZ package
- name: Upload TGZ archive
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: TGZ package
name: TGZ archive (x86_64-gcc)
path: build/aptdec_*.tar.gz path: build/aptdec_*.tar.gz


- name: Upload DEB package
- name: Upload Debian package
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: Debian package
name: Debian package (x86_64-gcc)
path: build/aptdec_*.deb path: build/aptdec_*.deb


build_windows: build_windows:
@@ -56,8 +56,51 @@ jobs:
- name: Run build script - name: Run build script
run: ./build_windows.sh $BUILD_TYPE run: ./build_windows.sh $BUILD_TYPE


- name: Upload ZIP package
- name: Upload ZIP archive
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: ZIP package
name: ZIP archive (x86_64-MinGW)
path: winbuild/aptdec_*.zip path: winbuild/aptdec_*.zip
build_windows_msvc:
runs-on: windows-latest

steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
fetch-depth: 0

- name: Run build script
shell: cmd
run: '"C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat" & build_windows.bat'

- name: Upload ZIP archive
uses: actions/upload-artifact@v3
with:
name: ZIP archive (x86_64-MSVC)
path: winbuild/aptdec_*.zip
build_linux_armhf:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
fetch-depth: 0

- name: Run build script
run: docker run -v $(pwd):/aptdec:z -w /aptdec debian:11 ./build_arm.sh

- name: Upload Debian package
uses: actions/upload-artifact@v3
with:
name: Debian package (armhf-gcc)
path: build/aptdec_*.deb

- name: Upload TGZ archive
uses: actions/upload-artifact@v3
with:
name: TGZ archive (armhf-gcc)
path: build/aptdec_*.tar.gz

+ 1
- 0
.gitignore View File

@@ -89,3 +89,4 @@ zlib/
libpng/ libpng/
libsndfile-*-win64.zip libsndfile-*-win64.zip
libsndfile-*-win64/ libsndfile-*-win64/
root/

+ 3
- 2
CMakeLists.txt View File

@@ -79,13 +79,14 @@ endif()
install(TARGETS aptdec PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/aptdec LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) install(TARGETS aptdec PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/aptdec LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})


# Packaging # Packaging
set(CPACK_PACKAGE_VERSION "${VERSION}")
string(REPLACE v "" CPACK_PACKAGE_VERSION ${VERSION})
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
set(CPACK_PACKAGE_NAME "aptdec") set(CPACK_PACKAGE_NAME "aptdec")
set(CPACK_PACKAGE_CONTACT "Xerbo (xerbo@protonmail.com)") set(CPACK_PACKAGE_CONTACT "Xerbo (xerbo@protonmail.com)")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "NOAA APT satellite imagery decoder") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "NOAA APT satellite imagery decoder")
set(CPACK_PACKAGE_DESCRIPTION "Aptdec is a FOSS library/program that decodes images transmitted by the NOAA POES weather satellites. These satellites transmit constantly (among other things) medium resolution (4km/px) images of the earth over a analog mode called APT.") set(CPACK_PACKAGE_DESCRIPTION "Aptdec is a FOSS library/program that decodes images transmitted by the NOAA POES weather satellites. These satellites transmit constantly (among other things) medium resolution (4km/px) images of the earth over a analog mode called APT.")
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}.${CMAKE_SYSTEM_PROCESSOR}") set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}.${CMAKE_SYSTEM_PROCESSOR}")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libsndfile1, libpng16-16")


if(WIN32) if(WIN32)
file(GLOB DLLS ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/*.dll) file(GLOB DLLS ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/*.dll)


+ 34
- 0
UPGRADE_GUIDE.md View File

@@ -0,0 +1,34 @@
# Upgrading from aptdec 1.8.0 to 2.0.0

# `aptdec` (now `aptdec-cli`)

## Removed

- The `-d` (output directory) flag
- The `-g` (gamma) flag
- Map overlays/MCIR
- Reading raw PNG images

(Some of these features have been moved into `aptdec_post.py`)

## Changes

- "palettes" are now called "luts"
- Image type and effects are now full English words (separated by commas) instead of letters
- Output format of realtime images is now `CURRENT_TIME.png` instead of `CURRENT_TIME-r.png`
- Channel A/B images include telemetry

## Examples

| v1.8 | v2.0.0 |
|---------------------------------------|-------------------------------------------|
| `aptdec file.wav` | `aptdec-cli file.wav` |
| `aptdec -i rt file.wav` | `aptdec-cli -i raw,thermal file.wav` |
| `aptdec -i ab file.wav` | `aptdec-cli -i a,b -e strip file.wav` |
| `aptdec -i p -p palette.png file.wav` | `aptdec-cli -i lut -l lut.png file.wav` |
| `aptdec -d out *.wav` | `mkdir out; cd out; aptdec-cli ../*.wav` |


# `libapt` (now `libaptdec`)

To avoid confusion with `apt` (the Debian package manager) the name of the aptdec library has been changed to `libaptdec`.

+ 1
- 0
aptdec-cli/main.c View File

@@ -244,6 +244,7 @@ static int process_file(const char *path, options_t *opts) {
// Normalize // Normalize
int error; int error;
apt_image_t img = apt_normalize(data, rows, opts->satellite, &error); apt_image_t img = apt_normalize(data, rows, opts->satellite, &error);
free(data);
if (error) { if (error) {
error_noexit("Normalization failed"); error_noexit("Normalization failed");
return 0; return 0;


+ 18
- 0
build_arm.sh View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# This script only works on Debian Bullseye
# If you have docker this is trivially easy:
# docker run -v $(pwd):/aptdec:z -w /aptdec debian:11 ./build_arm.sh

apt-get update
apt-get install -y debootstrap cmake gcc-arm-linux-gnueabihf

# Prepare armhf root environment
if [ ! -d root ]; then
debootstrap --keyring=/usr/share/keyrings/debian-archive-keyring.gpg --arch=armhf --include=libsndfile1-dev,libpng-dev --download-only bullseye root http://deb.debian.org/debian/
for i in root/var/cache/apt/archives/*.deb; do dpkg -x "$i" root; done
fi

# Build aptdec
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-armhf.cmake -DCMAKE_INSTALL_PREFIX=/aptdec/root
cmake --build build -j$(nproc)
cmake --build build --target package

+ 13
- 11
build_windows.bat View File

@@ -1,36 +1,38 @@
REM Build using MSVC on Windows REM Build using MSVC on Windows
REM Requires: git, cmake and ninja REM Requires: git, cmake and ninja
REM You need to run vcvars before running this
REM Build zlib REM Build zlib
IF NOT EXIST zlib ( IF NOT EXIST zlib (
git clone -b v1.2.13 https://github.com/madler/zlib
git clone --depth 1 -b v1.2.13 https://github.com/madler/zlib
cd zlib cd zlib
cmake -B build -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DMSVC_TOOLSET_VERSION=190 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../winpath
cmake --build build -j4
cmake -B build -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../winpath
cmake --build build -j%NUMBER_OF_PROCESSORS%
cmake --build build --target install cmake --build build --target install
cd .. cd ..
) )
REM Build libpng REM Build libpng
IF NOT EXIST libpng ( IF NOT EXIST libpng (
git clone -b v1.6.39 https://github.com/glennrp/libpng
git clone --depth 1 -b v1.6.39 https://github.com/glennrp/libpng
cd libpng cd libpng
cmake -B build -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DMSVC_TOOLSET_VERSION=190 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../winpath -DPNG_STATIC=OFF -DPNG_EXECUTABLES=OFF -DPNG_TESTS=OFF
cmake --build build -j4
cmake -B build -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../winpath -DPNG_STATIC=OFF -DPNG_EXECUTABLES=OFF -DPNG_TESTS=OFF
cmake --build build -j%NUMBER_OF_PROCESSORS%
cmake --build build --target install cmake --build build --target install
cd .. cd ..
) )
REM Build libsndfile, only with WAV support REM Build libsndfile, only with WAV support
IF NOT EXIST libsndfile ( IF NOT EXIST libsndfile (
git clone -b 1.2.0 https://github.com/libsndfile/libsndfile
git clone --depth 1 -b 1.2.0 https://github.com/libsndfile/libsndfile
cd libsndfile cd libsndfile
cmake -B build -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DMSVC_TOOLSET_VERSION=190 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../winpath -BUILD_SHARED_LIBS=ON -DBUILD_EXAMPLES=OFF -DBUILD_PROGRAMS=OFF
cmake --build build -j4
cmake -B build -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../winpath -DBUILD_SHARED_LIBS=ON -DBUILD_EXAMPLES=OFF -DBUILD_PROGRAMS=OFF
cmake --build build -j%NUMBER_OF_PROCESSORS%
cmake --build build --target install cmake --build build --target install
cd .. cd ..
) )
REM Build aptdec REM Build aptdec
cmake -B winbuild -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DMSVC_TOOLSET_VERSION=190 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../winpath
cmake --build winbuil -j4
cmake -B winbuild -G Ninja -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../winpath
cmake --build winbuild -j%NUMBER_OF_PROCESSORS%
cmake --build winbuild --target package

+ 19
- 0
cmake/toolchain-armhf.cmake View File

@@ -0,0 +1,19 @@
# the name of the target operating system
SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_PROCESSOR armhf)

# which compilers to use for C and C++
SET(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
SET(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)

# here is the target environment located
SET(CMAKE_FIND_ROOT_PATH /usr/arm-linux-gnueabihf/ ${CMAKE_INSTALL_PREFIX})
# Hack
link_directories(${CMAKE_INSTALL_PREFIX}/lib/arm-linux-gnueabihf)

# adjust the default behaviour of the FIND_XXX() commands:
# search headers and libraries in the target environment, search
# programs in the host environment
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

+ 1
- 1
cmake/toolchain-mingw32.cmake View File

@@ -1,6 +1,6 @@
# the name of the target operating system # the name of the target operating system
SET(CMAKE_SYSTEM_NAME Windows) SET(CMAKE_SYSTEM_NAME Windows)
SET(CMAKE_SYSTEM_PROCESSOR amd64)
SET(CMAKE_SYSTEM_PROCESSOR x86_64)


# which compilers to use for C and C++ # which compilers to use for C and C++
SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)


+ 3
- 6
libaptdec/algebra.c View File

@@ -52,12 +52,9 @@ linear_t linear_regression(const float *independent, const float *dependent, siz
return (linear_t){a, b}; return (linear_t){a, b};
} }


// "Sample" standard deviation
float standard_deviation(const float *data, size_t len) { float standard_deviation(const float *data, size_t len) {
float mean = 0.0f;
for (size_t i = 0; i < len; i++) {
mean += data[i];
}
mean /= (float)len;
float mean = meanf(data, len);


float deviation_mean = 0.0f; float deviation_mean = 0.0f;
for (size_t i = 0; i < len; i++) { for (size_t i = 0; i < len; i++) {
@@ -65,7 +62,7 @@ float standard_deviation(const float *data, size_t len) {
deviation_mean += deviation * deviation; deviation_mean += deviation * deviation;
} }


return sqrtf(deviation_mean / (float)len);
return sqrtf(deviation_mean / (float)(len-1));
} }


float sumf(const float *x, size_t len) { float sumf(const float *x, size_t len) {


+ 27
- 27
libaptdec/dsp.c View File

@@ -32,6 +32,10 @@
#define CARRIER_FREQ 2400.0f #define CARRIER_FREQ 2400.0f
#define MAX_CARRIER_OFFSET 10.0f #define MAX_CARRIER_OFFSET 10.0f


const float sync_pattern[] = {-1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1,
1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 0};
#define SYNC_SIZE (sizeof(sync_pattern)/sizeof(sync_pattern[0]))

typedef struct { typedef struct {
float alpha; float alpha;
float beta; float beta;
@@ -58,6 +62,11 @@ struct aptdec_t {
fir_t *hilbert; fir_t *hilbert;


float low_pass[LOW_PASS_SIZE]; float low_pass[LOW_PASS_SIZE];
float row_buffer[APT_IMG_WIDTH + SYNC_SIZE + 2];

float interpolator_buffer[APTDEC_BUFFER_SIZE];
size_t interpolator_n;
float interpolator_offset;
}; };


char *aptdec_get_version(void) { char *aptdec_get_version(void) {
@@ -162,47 +171,38 @@ static int am_demod(aptdec_t *apt, float *out, size_t count, aptdec_callback_t c
} }


static int get_pixels(aptdec_t *apt, float *out, size_t count, aptdec_callback_t callback, void *context) { static int get_pixels(aptdec_t *apt, float *out, size_t count, aptdec_callback_t callback, void *context) {
static float buffer[APTDEC_BUFFER_SIZE];
static size_t n = APTDEC_BUFFER_SIZE;
static float offset = 0.0;

float ratio = apt->sample_rate / (4160.0f * apt->sync_frequency); float ratio = apt->sample_rate / (4160.0f * apt->sync_frequency);


for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
// Get more samples if there are less than `LOW_PASS_SIZE` available // Get more samples if there are less than `LOW_PASS_SIZE` available
if (n + LOW_PASS_SIZE > APTDEC_BUFFER_SIZE) {
memcpy(buffer, &buffer[n], (APTDEC_BUFFER_SIZE-n) * sizeof(float));
if (apt->interpolator_n + LOW_PASS_SIZE > APTDEC_BUFFER_SIZE) {
memcpy(apt->interpolator_buffer, &apt->interpolator_buffer[apt->interpolator_n], (APTDEC_BUFFER_SIZE-apt->interpolator_n) * sizeof(float));


size_t read = am_demod(apt, &buffer[APTDEC_BUFFER_SIZE-n], n, callback, context);
if (read != n) {
size_t read = am_demod(apt, &apt->interpolator_buffer[APTDEC_BUFFER_SIZE-apt->interpolator_n], apt->interpolator_n, callback, context);
if (read != apt->interpolator_n) {
return i; return i;
} }
n = 0;
apt->interpolator_n = 0;
} }


out[i] = interpolating_convolve(&buffer[n], apt->low_pass, LOW_PASS_SIZE, offset);
out[i] = interpolating_convolve(&apt->interpolator_buffer[apt->interpolator_n], apt->low_pass, LOW_PASS_SIZE, apt->interpolator_offset);


// Do not question the sacred code // Do not question the sacred code
int shift = ceilf(ratio - offset);
offset = shift + offset - ratio;
n += shift;
int shift = ceilf(ratio - apt->interpolator_offset);
apt->interpolator_offset = shift + apt->interpolator_offset - ratio;
apt->interpolator_n += shift;
} }


return count; return count;
} }


const float sync_pattern[] = {-1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1,
1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 0};
#define SYNC_SIZE (sizeof(sync_pattern)/sizeof(sync_pattern[0]))

// Get an entire row of pixels, aligned with sync markers // Get an entire row of pixels, aligned with sync markers
int aptdec_getrow(aptdec_t *apt, float *row, aptdec_callback_t callback, void *context) { int aptdec_getrow(aptdec_t *apt, float *row, aptdec_callback_t callback, void *context) {
static float pixels[APT_IMG_WIDTH + SYNC_SIZE + 2];

// Wrap the circular buffer // Wrap the circular buffer
memcpy(pixels, &pixels[APT_IMG_WIDTH], (SYNC_SIZE + 2) * sizeof(float));
memcpy(apt->row_buffer, &apt->row_buffer[APT_IMG_WIDTH], (SYNC_SIZE + 2) * sizeof(float));

// Get a lines worth (APT_IMG_WIDTH) of samples // Get a lines worth (APT_IMG_WIDTH) of samples
if (get_pixels(apt, &pixels[SYNC_SIZE + 2], APT_IMG_WIDTH, callback, context) != APT_IMG_WIDTH) {
if (get_pixels(apt, &apt->row_buffer[SYNC_SIZE + 2], APT_IMG_WIDTH, callback, context) != APT_IMG_WIDTH) {
return 0; return 0;
} }


@@ -213,9 +213,9 @@ int aptdec_getrow(aptdec_t *apt, float *row, aptdec_callback_t callback, void *c
size_t phase = 0; size_t phase = 0;


for (size_t i = 0; i < APT_IMG_WIDTH; i++) { for (size_t i = 0; i < APT_IMG_WIDTH; i++) {
float _left = convolve(&pixels[i + 0], sync_pattern, SYNC_SIZE);
float _middle = convolve(&pixels[i + 1], sync_pattern, SYNC_SIZE);
float _right = convolve(&pixels[i + 2], sync_pattern, SYNC_SIZE);
float _left = convolve(&apt->row_buffer[i + 0], sync_pattern, SYNC_SIZE);
float _middle = convolve(&apt->row_buffer[i + 1], sync_pattern, SYNC_SIZE);
float _right = convolve(&apt->row_buffer[i + 2], sync_pattern, SYNC_SIZE);
if (_middle > middle) { if (_middle > middle) {
left = _left; left = _left;
middle = _middle; middle = _middle;
@@ -226,11 +226,11 @@ int aptdec_getrow(aptdec_t *apt, float *row, aptdec_callback_t callback, void *c


// Frequency // Frequency
float bias = (left / middle) - (right / middle); float bias = (left / middle) - (right / middle);
apt->sync_frequency = 1.0f + bias / APT_IMG_WIDTH / 2.0f;
apt->sync_frequency = 1.0f + bias / APT_IMG_WIDTH / 4.0f;


// Phase // Phase
memcpy(&row[APT_IMG_WIDTH], &pixels[phase], (APT_IMG_WIDTH - phase) * sizeof(float));
memcpy(&row[APT_IMG_WIDTH - phase], pixels, phase * sizeof(float));
memcpy(&row[APT_IMG_WIDTH], &apt->row_buffer[phase], (APT_IMG_WIDTH - phase) * sizeof(float));
memcpy(&row[APT_IMG_WIDTH - phase], apt->row_buffer, phase * sizeof(float));


return 1; return 1;
} }

+ 1
- 0
libaptdec/effects.c View File

@@ -20,6 +20,7 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
#include <stdlib.h>


#include "algebra.h" #include "algebra.h"
#include "util.h" #include "util.h"


Loading…
Cancel
Save