commit 072d068dcd4932ee574463201210684cb0698809 Author: Mikhail Romanko Date: Wed Aug 19 09:22:12 2026 +0300 Initial commit diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..01e9e41 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,89 @@ +name: CI + +on: + push: + branches: [trunk] + pull_request: + branches: [trunk] + +jobs: + build-and-analyze: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install tools + run: | + sudo apt-get update -qq + sudo apt-get install -y build-essential clang clang-tools clang-tidy cppcheck gcc-mingw-w64-x86-64 cmake + + - name: Build with GCC + run: | + make -f Makefile.posix clean + make -f Makefile.posix CC=gcc CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -O2" + + - name: Build with Clang + run: | + make -f Makefile.posix clean + make -f Makefile.posix CC=clang CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -O2" + + - name: Build with MinGW + run: | + make -f Makefile.posix clean + make -f Makefile.mingw CC=x86_64-w64-mingw32-gcc AR=x86_64-w64-mingw32-ar CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -O2" + + - name: Build with CMake + run: | + cmake -S . -B build + cmake --build build + + - name: Linting checks + run: | + echo "Running all linting checks..." + FAILED=0 + + # cppcheck + echo "=== Check 1/4: cppcheck ===" + if make -f Makefile.lint cppcheck; then + echo "cppcheck: PASSED" + else + echo "cppcheck: FAILED" + FAILED=1 + fi + + # clang-tidy + echo "=== Check 2/4: clang-tidy ===" + if make -f Makefile.lint clang-tidy; then + echo "clang-tidy: PASSED" + else + echo "clang-tidy: FAILED" + FAILED=1 + fi + + # scan-build + echo "=== Check 3/4: scan-build ===" + if make -f Makefile.lint scan-build; then + echo "scan-build: PASSED" + else + echo "scan-build: FAILED" + FAILED=1 + fi + + # security-check + echo "=== Check 4/4: security-check ===" + if make -f Makefile.lint security-check; then + echo "security-check: PASSED" + else + echo "security-check: FAILED" + FAILED=1 + fi + + # Final result + if [ $FAILED -ne 0 ]; then + echo "One or more linting checks failed." + exit 1 + else + echo "All linting checks passed." + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f765426 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# ---> C +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# ---> CMake +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps +CMakeUserPresets.json + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..32bc7d0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10) +project(CgeSample LANGUAGES C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +set(SOURCES + Sample.c +) + +set(HEADERS + CgeSample.h +) + +add_library(CgeSample STATIC ${SOURCES} ${HEADERS}) + +target_include_directories(CgeSample PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +install(TARGETS CgeSample + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +install(FILES ${HEADERS} DESTINATION include) diff --git a/CgeSample.h b/CgeSample.h new file mode 100644 index 0000000..bbd8232 --- /dev/null +++ b/CgeSample.h @@ -0,0 +1,46 @@ +#ifndef CGE_SAMPLE_H +#define CGE_SAMPLE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +enum CgeSampleFormat { + CGE_SAMPLE_UNKNOWN, /* Invalid or unknown format */ + CGE_SAMPLE_S8, /* 8-bit signed integer, range: -128 to 127 */ + CGE_SAMPLE_U8, /* 8-bit unsigned integer, range: 0 to 255 */ + CGE_SAMPLE_S16, /* 16-bit signed integer, range: -32768 to 32767 */ + CGE_SAMPLE_U16, /* 16-bit unsigned integer, range: 0 to 65535 */ + CGE_SAMPLE_S24, /* 24-bit signed integer */ + CGE_SAMPLE_U24, /* 24-bit unsigned integer */ + CGE_SAMPLE_S32, /* 32-bit signed integer */ + CGE_SAMPLE_U32, /* 32-bit unsigned integer */ + CGE_SAMPLE_F32, /* 32-bit IEEE 754 float */ + CGE_SAMPLE_F64, /* 64-bit IEEE 754 double */ +}; + +typedef struct CgeSampleBuf { + void *data; + int format; + size_t count; + size_t stride; +} CgeSampleBuf; + +size_t CgeSampleInitLayout(CgeSampleBuf *buf, int format, size_t count); +float CgeSampleGetF32(const CgeSampleBuf *buf, size_t index); +void CgeSampleSetF32(CgeSampleBuf *buf, size_t index, float value); +int16_t CgeSampleGetS16(const CgeSampleBuf *buf, size_t index); +void CgeSampleSetS16(CgeSampleBuf *buf, size_t index, int16_t value); + +void CgeSampleConvert(void *src, int srcFormat, size_t srcStride, + void *dst, int dstFormat, size_t dstStride, + size_t count); + +#ifdef __cplusplus +} +#endif + +#endif /* CGE_SAMPLE_H */ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3d44ffd --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +Copyright (C) 2026 by blankhex me@blankhex.com + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile.lint b/Makefile.lint new file mode 100644 index 0000000..3897d63 --- /dev/null +++ b/Makefile.lint @@ -0,0 +1,29 @@ +# Makefile.lint - Individual linting targets + +.PHONY: all +all: cppcheck clang-tidy scan-build security-check + @echo "All linting scheduled" + +.PHONY: cppcheck +cppcheck: + @echo "Running cppcheck..." + @cppcheck --enable=warning,performance,portability --std=c99 --quiet -Iinclude -I. . + +.PHONY: clang-tidy +clang-tidy: + @echo "Running clang-tidy..." + @for file in $$(find . -name "*.c" -type f); do \ + clang-tidy "$$file" -- -I. -Iinclude -std=c99; \ + done + +.PHONY: scan-build +scan-build: + @echo "Running scan-build..." + @scan-build --status-bugs make -f Makefile.posix clean all CC=clang + +.PHONY: security-check +security-check: + @echo "Scanning for unsafe C functions..." + @grep -rnE '\b(strcpy|strcat|sprintf|gets|scanf|sscanf|realpath|mktemp|tempnam|tmpnam|getwd|getlogin)\b' \ + . --include="*.c" --include="*.h" | grep -v "^Binary file" && \ + exit 1 || exit 0 \ No newline at end of file diff --git a/Makefile.mingw b/Makefile.mingw new file mode 100644 index 0000000..c40d6c3 --- /dev/null +++ b/Makefile.mingw @@ -0,0 +1,23 @@ +# MinGW Makefile for CgeSample + +CC = gcc +AR = ar +CFLAGS = -std=c99 -O2 -Wall -Wextra +ARFLAGS = rcs +TARGET = libCgeSample.a + +SOURCES = Sample.c +OBJECTS = $(SOURCES:.c=.o) + +.PHONY: all clean + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(AR) $(ARFLAGS) $@ $^ + +%.o: %.c CgeSample.h + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + del $(OBJECTS) $(TARGET) 2>nul || exit 0 diff --git a/Makefile.posix b/Makefile.posix new file mode 100644 index 0000000..ee54632 --- /dev/null +++ b/Makefile.posix @@ -0,0 +1,33 @@ +# POSIX Makefile for CgeSample + +CC = gcc +AR = ar +CFLAGS = -std=c99 -O2 -Wall -Wextra -fPIC +ARFLAGS = rcs +TARGET = libCgeSample.a + +SOURCES = Sample.c +OBJECTS = $(SOURCES:.c=.o) + +.PHONY: all clean install + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(AR) $(ARFLAGS) $@ $^ + +%.o: %.c CgeSample.h + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJECTS) $(TARGET) + +install: $(TARGET) + cp $(TARGET) /usr/local/lib/ + cp CgeSample.h /usr/local/include/ + ldconfig || echo "Run ldconfig manually if needed" + +uninstall: + rm -f /usr/local/lib/libCgeSample.a + rm -f /usr/local/include/CgeSample.h + ldconfig || true diff --git a/Makefile.win32 b/Makefile.win32 new file mode 100644 index 0000000..2365c3a --- /dev/null +++ b/Makefile.win32 @@ -0,0 +1,23 @@ +# Makefile.win32 for MSVC (NMake) +# Usage: Open "x86 Native Tools Command Prompt", then: +# nmake -f Makefile.win32 + +CC = cl +LIB = lib +CFLAGS = /c /nologo /W3 /O2 +LIBFLAGS = /nologo +TARGET = CgeSample.lib + +SOURCES = Sample.c +OBJECTS = $(SOURCES:.c=.obj) + +$(TARGET): $(OBJECTS) + $(LIB) $(LIBFLAGS) /OUT:$(TARGET) $(OBJECTS) + +{.}.c{}.obj: + $(CC) $(CFLAGS) /Fo$@ $< + +clean: + del $(OBJECTS) $(TARGET) 2>nul + +.PHONY: clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..8dda678 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# CgeSample + +TODO + +## License + +0BSD - a permissive license with no attribution required. diff --git a/Sample.c b/Sample.c new file mode 100644 index 0000000..c1e63a9 --- /dev/null +++ b/Sample.c @@ -0,0 +1,338 @@ +#include "CgeSample.h" +#include + +static size_t bytesPerSample(int format) { + switch (format) { + default: + return 0; + + case CGE_SAMPLE_U8: + case CGE_SAMPLE_S8: + return 1; + + case CGE_SAMPLE_U16: + case CGE_SAMPLE_S16: + return 2; + + case CGE_SAMPLE_U24: + case CGE_SAMPLE_S24: + case CGE_SAMPLE_U32: + case CGE_SAMPLE_S32: + case CGE_SAMPLE_F32: + return 4; + + case CGE_SAMPLE_F64: + return 8; + } +} + +size_t CgeSampleInitLayout(CgeSampleBuf *buf, int format, size_t count) { + buf->count = count; + buf->format = format; + buf->data = NULL; + buf->stride = bytesPerSample(format); + + return buf->stride * count; +} + +float CgeSampleGetF32(const CgeSampleBuf *buf, size_t index) { + void *data; + + data = (char *)buf->data + index * buf->stride; + switch (buf->format) { + default: + case CGE_SAMPLE_UNKNOWN: return 0.0f; + case CGE_SAMPLE_S8: return (float)(*(int8_t*)data) / 128.0f; + case CGE_SAMPLE_U8: return ((float)(*(uint8_t*)data) - 128.0f) / 128.0f; + case CGE_SAMPLE_S16: return (float)(*(int16_t*)data) / 32768.0f; + case CGE_SAMPLE_U16: return ((float)(*(uint16_t*)data) - 32768.0f) / 32768.0f; + case CGE_SAMPLE_S24: return (float)(*(int32_t*)data) / 8388608.0f; + case CGE_SAMPLE_U24: return ((float)(*(uint32_t*)data) - 8388608.0f) / 8388608.0f; + case CGE_SAMPLE_S32: return (float)(*(int32_t*)data) / 2147483648.0f; + case CGE_SAMPLE_U32: return ((float)(*(uint32_t*)data) - 2147483648.0f) / 2147483648.0f; + case CGE_SAMPLE_F32: return (*(float*)data); + case CGE_SAMPLE_F64: return (*(double*)data); + } +} + +void CgeSampleSetF32(CgeSampleBuf *buf, size_t index, float value) { + void *data; + + data = (char *)buf->data + index * buf->stride; + switch (buf->format) { + default: + case CGE_SAMPLE_UNKNOWN: + break; + + case CGE_SAMPLE_S8: + value *= 128.0f; + value += (value >= 0.0f) ? 0.5f : -0.5f; + if (value > 127.0f) value = 127.0f; + if (value < -128.0f) value = -128.0f; + *(int8_t*)data = value; + break; + + case CGE_SAMPLE_U8: + value = value * 128.0f + 128.0f; + value += (value >= 0.0f) ? 0.5f : -0.5f; + if (value > 255.0f) value = 255.0f; + if (value < 0.0f) value = 0.0f; + *(uint8_t*)data = value; + break; + + case CGE_SAMPLE_S16: + value *= 32768.0f; + value += (value >= 0.0f) ? 0.5f : -0.5f; + if (value > 32767.0f) value = 32767.0f; + if (value < -32768.0f) value = -32768.0f; + *(int16_t*)data = value; + break; + + case CGE_SAMPLE_U16: + value = value * 32768.0f + 32768.0f; + value += (value >= 0.0f) ? 0.5f : -0.5f; + if (value > 65535.0f) value = 65535.0f; + if (value < 0.0f) value = 0.0f; + *(uint16_t*)data = value; + break; + + case CGE_SAMPLE_S24: + value *= 8388608.0f; + value += (value >= 0.0f) ? 0.5f : -0.5f; + if (value > 8388607.0f) value = 8388607.0f; + if (value < -8388608.0f) value = -8388608.0f; + *(int32_t*)data = value; + break; + + case CGE_SAMPLE_U24: + value = value * 8388608.0f + 8388608.0f; + value += (value >= 0.0f) ? 0.5f : -0.5f; + if (value > 16777215.0f) value = 16777215.0f; + if (value < 0.0f) value = 0.0f; + *(uint32_t*)data = value; + break; + + case CGE_SAMPLE_S32: { + double scaled = value; + scaled *= 2147483648.0; + scaled += (scaled >= 0.0) ? 0.5 : -0.5; + if (scaled > 2147483647.0) scaled = 2147483647.0; + if (scaled < -2147483648.0) scaled = -2147483648.0; + *(int32_t*)data = scaled; + break; + } + + case CGE_SAMPLE_U32: { + double scaled = value; + scaled = scaled * 2147483648.0 + 2147483648.0; + scaled += (scaled >= 0.0) ? 0.5 : -0.5; + if (scaled > 4294967295.0) scaled = 4294967295.0; + if (scaled < 0.0) scaled = 0.0; + *(uint32_t*)data = scaled; + break; + } + + case CGE_SAMPLE_F32: + *(float*)data = value; + break; + + case CGE_SAMPLE_F64: + *(double*)data = (double)value; + break; + } +} + +int16_t CgeSampleGetS16(const CgeSampleBuf *buf, size_t index) { + void *data; + + data = (char *)buf->data + index * buf->stride; + switch (buf->format) { + default: + case CGE_SAMPLE_UNKNOWN: return 0; + case CGE_SAMPLE_S8: return (int16_t)*(int8_t*)data * 256; + case CGE_SAMPLE_U8: return ((int16_t)*(uint8_t*)data - 128) * 256; + case CGE_SAMPLE_S16: return *(int16_t*)data; + case CGE_SAMPLE_U16: return ((uint32_t)*(uint16_t*)data - 32768); + case CGE_SAMPLE_S24: return (*(int32_t*)data / 256); + case CGE_SAMPLE_U24: return ((*(uint32_t*)data / 256) - 32768); + case CGE_SAMPLE_S32: return (*(int32_t*)data / 65536); + case CGE_SAMPLE_U32: return ((*(uint32_t*)data / 65536) - 32768); + case CGE_SAMPLE_F32: { + float value = *(float*)data; + value *= 32768.0f; + value += (value >= 0.0f) ? 0.5f : -0.5f; + if (value > 32767.0f) value = 32767.0f; + if (value < -32768.0f) value = -32768.0f; + return (int16_t)value; + } + case CGE_SAMPLE_F64: { + float value = (float)(*(double*)data); + value *= 32768.0; + value += (value >= 0.0) ? 0.5 : -0.5; + if (value > 32767.0) value = 32767.0; + if (value < -32768.0) value = -32768.0; + return (int16_t)value; + } + } +} + +void CgeSampleSetS16(CgeSampleBuf *buf, size_t index, int16_t value) { + void *data; + int32_t scaled; + uint32_t unscaled; + + data = (char *)buf->data + index * buf->stride; + switch (buf->format) { + default: + case CGE_SAMPLE_UNKNOWN: + break; + + case CGE_SAMPLE_S8: + *(int8_t*)data = (int8_t)(value / 256); + break; + + case CGE_SAMPLE_U8: + *(uint8_t*)data = (uint8_t)(value / 256 + 128); + break; + + case CGE_SAMPLE_S16: + *(int16_t*)data = value; + break; + + case CGE_SAMPLE_U16: + *(uint16_t*)data = (uint16_t)((int32_t)value + 32768); + break; + + case CGE_SAMPLE_S24: + scaled = (int32_t)value * 256; + if (scaled > 8388607) scaled = 8388607; + if (scaled < -8388608) scaled = -8388608; + *(int32_t*)data = scaled; + break; + + case CGE_SAMPLE_U24: + scaled = (((int32_t)value + 32768) & 0xFFFF) * 256; + if (scaled > 16777215) scaled = 16777215; + *(uint32_t*)data = (uint32_t)scaled; + break; + + case CGE_SAMPLE_S32: + *(int32_t*)data = (int32_t)value * 65536; + break; + + case CGE_SAMPLE_U32: + unscaled = (((uint32_t)((int32_t)value + 32768)) & 0xFFFFu) * 65536u; + *(uint32_t*)data = unscaled; + break; + + case CGE_SAMPLE_F32: + *(float*)data = (float)value / 32768.0f; + break; + + case CGE_SAMPLE_F64: + *(double*)data = (double)value / 32768.0; + break; + } +} + +static void fastCopy(void *src, size_t srcStride, void *dst, + size_t dstStride, size_t count, size_t size) { + size_t i; + + for (i = 0; i < count; i++) { + memcpy(dst, src, size); + src = (char*)src + srcStride; + dst = (char*)dst + dstStride; + } +} + +static void fastS16ToF32(void *src, size_t srcStride, void *dst, + size_t dstStride, size_t count) { + const float scale = 1.0f / 32768.0f; + size_t i; + + for (i = 0; i < count; i++) { + *(float *)dst = *(int16_t *)src * scale; + src = (char *)src + srcStride; + dst = (char *)dst + dstStride; + } +} + +static void fastF32ToS16(void *src, size_t srcStride, void *dst, + size_t dstStride, size_t count) { + size_t i; + + for (i = 0; i < count; i++) { + int32_t v = (int32_t)(*(float *)src * 32768.0f); + + if (v > 32767) + v = 32767; + else if (v < -32768) + v = -32768; + + *(int16_t *)dst = (int16_t)v; + src = (void *)((char *)src + srcStride); + dst = (void *)((char *)dst + dstStride); + } +} + +static void fastS24ToF32(void *src, size_t srcStride, void *dst, + size_t dstStride, size_t count) { + const float scale = 1.0f / 8388608.0f; + size_t i; + + for (i = 0; i < count; i++) { + *(float *)dst = *(int32_t *)src * scale; + src = (void *)((char *)src + srcStride); + dst = (void *)((char *)dst + dstStride); + } +} + +static void fastF32ToS24(void *src, size_t srcStride, void *dst, + size_t dstStride, size_t count) { + size_t i; + for (i = 0; i < count; i++) { + int32_t v = (int32_t)(*(float *)src * 8388608.0f); + + if (v > 8388607) + v = 8388607; + else if (v < -8388608) + v = -8388608; + + *(int32_t *)dst = (int32_t)v; + src = (void *)((char *)src + srcStride); + dst = (void *)((char *)dst + dstStride); + } +} + +void CgeSampleConvert(void *src, int srcFormat, size_t srcStride, + void *dst, int dstFormat, size_t dstStride, + size_t count) { + CgeSampleBuf srcBuf, dstBuf; + size_t i; + + srcStride = (srcStride) ? (srcStride) : bytesPerSample(srcFormat); + dstStride = (dstStride) ? (dstStride) : bytesPerSample(dstFormat); + + if (srcFormat == dstFormat) { + fastCopy(src, srcStride, dst, dstStride, count, bytesPerSample(srcFormat)); + } else if (srcFormat == CGE_SAMPLE_S16 && dstFormat == CGE_SAMPLE_F32) { + fastS16ToF32(src, srcStride, dst, dstStride, count); + } else if (srcFormat == CGE_SAMPLE_F32 && dstFormat == CGE_SAMPLE_S16) { + fastF32ToS16(src, srcStride, dst, dstStride, count); + } else if (srcFormat == CGE_SAMPLE_F32 && dstFormat == CGE_SAMPLE_S24) { + fastF32ToS24(src, srcStride, dst, dstStride, count); + } else if (srcFormat == CGE_SAMPLE_S24 && dstFormat == CGE_SAMPLE_F32) { + fastS24ToF32(src, srcStride, dst, dstStride, count); + } else { + CgeSampleInitLayout(&srcBuf, srcFormat, count); + CgeSampleInitLayout(&dstBuf, dstFormat, count); + srcBuf.stride = srcStride; + dstBuf.stride = dstStride; + srcBuf.data = src; + dstBuf.data = dst; + + for (i = 0; i < count; i++) + CgeSampleSetF32(&dstBuf, i, CgeSampleGetF32(&srcBuf, i)); + } +}