intense desloppification, performance improvements, openmp ready, add audio, refactor everything, fix warnings and bad practices

This commit is contained in:
maksw committed 2026-02-24 10:41:40 +01:00
1 parent 0c24781c81
commit a12c9a3e8f
15 files changed
+1615 -700

No files matched your search

+79
View File
@@ -0,0 +1,79 @@
// audio.h
#pragma once
#ifndef AUDIO_H
#define AUDIO_H
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#include <stdbool.h>
#endif
typedef struct audio_t audio_t;
// If the return type is bool, false means failure.
// Initialize audio system
// nullptr means failure
audio_t* audio_init(void);
// Destroy audio system and free resources
void audio_destroy(audio_t* audio);
// Load MADPCM data from a WAV file.
// Returns a pointer to the allocated audio data, or nullptr on failure.
// The returned buffer must be freed with audio_free_data().
uint8_t* audio_load_madpcm(const char* filename, int* out_size, int* out_channels, int* out_rate, int* out_total_samples);
// Free data allocated by audio_load_madpcm
void audio_free_data(uint8_t* data);
// Play a MADPCM stream, returns unique ID
// data must remain valid until the stream ends or is stopped
// loop: if true, the stream restarts on EOF
// 0 means failure, valid IDs start from 1
uint32_t audio_play(audio_t* audio,
const uint8_t* data,
int size,
int channels,
int rate,
bool loop);
// Stop and remove stream by ID
bool audio_stop(audio_t* audio, uint32_t id);
bool audio_pause(audio_t* audio, uint32_t id);
bool audio_resume(audio_t* audio, uint32_t id);
// Returns true if the stream is active (playing or paused)
bool audio_is_active(audio_t* audio, uint32_t id);
// Returns true if the stream is currently playing (not paused, not stopped)
bool audio_is_playing(audio_t* audio, uint32_t id);
// Per-stream volume, immediate effect, range [0.0, 1.0]
bool audio_set_volume(audio_t* audio, uint32_t id, float volume);
// Animate per-stream volume linearly over time, driven by audio_tick()
bool audio_animate_volume(audio_t* audio, uint32_t id,
float to_volume, float duration_sec);
// Global volume, immediate effect, range [0.0, 1.0]
// Applied on top of per-stream volume at mix time
void audio_set_global_volume(audio_t* audio, float volume);
// Animate global volume linearly over time, driven by audio_tick()
void audio_animate_global_volume(audio_t* audio,
float to_volume, float duration_sec);
// Advances stream decoding and volume animations
// Call once per main loop iteration with the same delta as compositor_tick()
void audio_tick(audio_t* audio, float delta_time_sec);
#ifdef __cplusplus
}
#endif
#endif // AUDIO_H
+52 -42
View File
@@ -1,17 +1,12 @@
// compositor.h
// compositor.h v0.3
#pragma once
#ifndef COMPOSITOR_H
#define COMPOSITOR_H
#include <stdint.h>
#include <stdbool.h>
// Animation types
enum animation_type_t {
ANIM_NONE = 0,
ANIM_MOVE,
ANIM_FADE,
ANIM_SCALE,
ANIM_MOVE_FADE // Combined animation
};
// This subsystem is thread-unsafe.
// Caller must serialize all calls. Calls do not need to be made from the main thread.
// Removing an element while ticking/rendering may crash.
// Modifying an element while ticking/rendering may cause visual glitches.
// Easing functions
enum easing_t {
@@ -24,6 +19,7 @@ enum easing_t {
EASE_IN_OUT_CUBIC
};
// Anchor points
enum anchor_point_t {
ANCHOR_TOP_LEFT,
ANCHOR_TOP_CENTER,
@@ -36,16 +32,29 @@ enum anchor_point_t {
ANCHOR_BOTTOM_RIGHT,
};
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#include <stdbool.h>
#endif
typedef struct compositor_t compositor_t;
// If the return type is bool, false means failure.
// Initialize compositor with given dimensions
// nullptr means failure
compositor_t* compositor_init(uint32_t width, uint32_t height);
// Destroy compositor and free resources
// Destroy compositor and free resources. Everything dies with it.
void compositor_destroy(compositor_t* comp);
// Insert element, returns unique ID
// FB format is ARGB8888 and it always will be
// The compositor takes a reference, caller must keep alive until removed
// 0 means failure, valid IDs start from 1
uint32_t compositor_insert(compositor_t* comp,
const uint32_t* fb,
uint32_t fb_width,
@@ -55,50 +64,49 @@ uint32_t compositor_insert(compositor_t* comp,
anchor_point_t anchor);
// Remove element by ID
void compositor_remove(compositor_t* comp, uint32_t id);
bool compositor_remove(compositor_t* comp, uint32_t id);
// AIO Update element properties
void compositor_update(compositor_t* comp, uint32_t id,
// AIO Update element properties, immediate effect
bool compositor_update(compositor_t* comp, uint32_t id,
int32_t x, int32_t y,
int32_t z_order, float scale,
bool visible);
bool visible, float alpha);
// Individual setters
void compositor_set_position(compositor_t* comp, uint32_t id,
// Individual setters, immediate effect
bool compositor_set_position(compositor_t* comp, uint32_t id,
int32_t x, int32_t y);
void compositor_set_zorder(compositor_t* comp, uint32_t id,
bool compositor_set_zorder(compositor_t* comp, uint32_t id,
int32_t z_order);
void compositor_set_visible(compositor_t* comp, uint32_t id,
bool visible);
void compositor_set_scale(compositor_t* comp, uint32_t id,
bool compositor_set_scale(compositor_t* comp, uint32_t id,
float scale);
bool compositor_set_visible(compositor_t* comp, uint32_t id,
bool visible);
bool compositor_set_alpha(compositor_t* comp, uint32_t id,
float alpha);
// Advances all animations by a given time delta.
void compositor_tick(compositor_t* comp, float delta_time_sec);
// Instantly sets the element-wide alpha.
void compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha);
// Animation functions with queue parameter
void compositor_animate_move(compositor_t* comp, uint32_t id,
// Sets a target state to advance over time by tick()
// Queue means the animation will start after the previous animation completed
// Not queueing will play the animation alongside any current animation
// You cannot stack the same type of animation without queueing, the second call will cancel the first call.
bool compositor_animate_move(compositor_t* comp, uint32_t id,
int32_t to_x, int32_t to_y,
float duration_sec, easing_t easing, bool queue = false);
void compositor_animate_fade(compositor_t* comp, uint32_t id,
float duration_sec, easing_t easing, bool queue);
bool compositor_animate_fade(compositor_t* comp, uint32_t id,
float to_alpha,
float duration_sec, easing_t easing, bool queue = false);
void compositor_animate_scale(compositor_t* comp, uint32_t id,
float duration_sec, easing_t easing, bool queue);
bool compositor_animate_scale(compositor_t* comp, uint32_t id,
float to_scale,
float duration_sec, easing_t easing, bool queue = false);
float duration_sec, easing_t easing, bool queue);
// Immediately complete all animations
void compositor_animation_complete(compositor_t* comp, uint32_t id);
void compositor_animation_complete_all(compositor_t* comp);
// Immediately finish all animations on a specific object (optionally only N queued, 0 = all), or every object.
bool compositor_animation_finish_element(compositor_t* comp, uint32_t id, int num_queued /*0=all*/);
void compositor_animation_finish_all(compositor_t* comp);
// Returns true if any element is animating or has queued animations.
bool compositor_is_animating(compositor_t * comp);
bool compositor_is_animating(compositor_t* comp);
// Returns true if the specified element is animating or has queued animations.
bool compositor_is_element_animating(compositor_t* comp, uint32_t id);
@@ -106,10 +114,12 @@ bool compositor_is_element_animating(compositor_t* comp, uint32_t id);
// Compose all visible elements and return framebuffer pointer
const uint32_t* compositor_compose(compositor_t* comp);
void compositor_mark_content_dirty(compositor_t* comp, uint32_t id);
// Mark an element's content as dirty for redrawing
bool compositor_mark_content_dirty(compositor_t* comp, uint32_t id);
void compositor_mark_all_dirty(compositor_t* comp);
// Returns the last error message set for this compositor instance.
const char* compositor_get_last_error(compositor_t* comp);
#ifdef __cplusplus
}
#endif
#endif // COMPOSITOR_H
+9 -1
View File
@@ -2,15 +2,23 @@
#pragma once
#ifndef IMAGE_H
#define IMAGE_H
#include <stdint.h>
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#endif
// Loads an image file as ARGB8888. Returns pointer to pixel data, sets width/height.
uint32_t* image_load(const char* filename, int* width, int* height);
// Loads and resizes an image file to desired dimensions as ARGB8888. Returns pointer to pixel data.
uint32_t* image_load_resized(const char* filename, int desired_width, int desired_height);
// Frees image data.
void image_free(uint32_t* data);
#ifdef __cplusplus
}
#endif
#endif // IMAGE_H
+592
View File
@@ -0,0 +1,592 @@
// madpcm.h
// A more modern approach to ADPCM.
#pragma once
#ifndef MADPCM_H
#define MADPCM_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __AVX2__
#include <immintrin.h>
#endif
#define MADPCM_LPC_ORDER 4
#define MADPCM_BLOCK_SAMPLES 1024
#define MADPCM_HEADER_SIZE 24
#define MADPCM_PAYLOAD_SIZE 512
#define MADPCM_MONO_SIZE (MADPCM_HEADER_SIZE + MADPCM_PAYLOAD_SIZE)
#define MADPCM_STEREO_SIZE (MADPCM_MONO_SIZE * 2)
#define MADPCM_WAVE_FORMAT 0x4D41 // 'MA' in hex
// Core MADPCM functions
int madpcm_encode_block(const int16_t* inSamples, const int16_t* prevSamples, uint8_t* outBuffer, bool fast);
int madpcm_encode_block_stereo(const int16_t* inL, const int16_t* inR, const int16_t* prevL, const int16_t* prevR, uint8_t* outBuffer, bool fast);
void madpcm_decode_block(const uint8_t* inBuffer, int16_t* outSamples);
void madpcm_decode_block_stereo(const uint8_t* inBuffer, int16_t* outL, int16_t* outR);
#ifdef MADPCM_IMPLEMENTATION
// prepare for the clusterfuck
#pragma region freestanding
#ifndef MADPCM_FREESTANDING
#include <string.h>
#define madpcm_memset memset
#define madpcm_memcpy memcpy
#else // freestanding
#if defined(MADPCM_MEMSET) || defined(MADPCM_MEMCPY)
// user provided
#if defined(MADPCM_MEMSET) != defined(MADPCM_MEMCPY)
// user fucked up
#error "Both MADPCM_MEMSET and MADPCM_MEMCPY must be defined together"
#endif
#define madpcm_memset MADPCM_MEMSET
#define madpcm_memcpy MADPCM_MEMCPY
#else
// i am running on a toaster implementation
#ifndef MADPCM_INTERNAL_MEMFUNCS
void* memcpy(void* dest, const void* src, size_t count);
void* memset(void* dest, int val, size_t count);
#define madpcm_memset memset
#define madpcm_memcpy memcpy
#else
// truly freestanding
#ifdef _MSC_VER
#include <intrin.h>
#pragma intrinsic(__movsb, __stosb)
#endif
static inline void* madpcm_memcpy(void* dest, const void* src, size_t count) {
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
__movsb((unsigned char*)dest, (const unsigned char*)src, count);
return dest;
#elif defined(__GNUC__)
return __builtin_memcpy(dest, src, count);
#else
uint8_t* d = (uint8_t*)dest; const uint8_t* s = (const uint8_t*)src;
while (count--) *d++ = *s++;
return dest;
#endif
}
static inline void* madpcm_memset(void* dest, int val, size_t count) {
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
__stosb((unsigned char*)dest, (unsigned char)val, count);
return dest;
#elif defined(__GNUC__)
return __builtin_memset(dest, val, count);
#else
uint8_t* d = (uint8_t*)dest; while (count--) *d++ = (uint8_t)val;
return dest;
#endif
}
#endif
#endif
#endif
#pragma endregion
#define MADPCM_TRELLIS_K 4
#define MADPCM_Q15_MAX 32767
#define MADPCM_Q15_MIN -32768
// Gaussian Table (Q11) - Optimized for Laplacian residuals
static const int16_t madpcm__gauss_steps[8] = { 205, 614, 1024, 1433, 1843, 2252, 2662, 3200 };
typedef struct madpcm__block_header {
int16_t scales[4]; // LSB of scales[0] is Stereo Flag
int16_t coeffs[MADPCM_LPC_ORDER];
int16_t seed[MADPCM_LPC_ORDER]; // History samples
} madpcm__block_header;
// Trellis helper struct
typedef struct madpcm__trellis_candidate {
int64_t err;
int32_t p_idx;
int16_t val;
int8_t nib;
} madpcm__trellis_candidate;
typedef struct madpcm__trellis_state {
int64_t err;
int16_t h[MADPCM_LPC_ORDER];
} madpcm__trellis_state;
// Math Helpers
static inline int16_t madpcm__clamp_q15(int32_t value) {
#ifdef __AVX2__
__m128i v = _mm_cvtsi32_si128(value);
v = _mm_packs_epi32(v, v); // Saturate 32->16 bit
return (int16_t)_mm_cvtsi128_si32(v);
#else
if (value > MADPCM_Q15_MAX) return MADPCM_Q15_MAX;
if (value < MADPCM_Q15_MIN) return MADPCM_Q15_MIN;
return (int16_t)value;
#endif
}
static inline int32_t madpcm__abs_int32(int32_t value) { return (value < 0) ? -value : value; }
static inline int64_t madpcm__abs_int64(int64_t value) { return (value < 0) ? -value : value; }
static void madpcm__compute_autocorr(const int16_t* signal, int length, int order, int64_t* r_out) {
for (int i = 0; i <= order; i++) {
int64_t sum = 0;
for (int j = 0; j < length - i; j++) sum += (int64_t)signal[j] * signal[j + i];
r_out[i] = sum;
}
r_out[0] = (r_out[0] > 256) ? r_out[0] : 256;
r_out[0] += (r_out[0] >> 9);
}
static bool madpcm__compute_LPC(const int64_t* r, int order, int16_t* outCoeffs) {
int32_t a[MADPCM_LPC_ORDER + 1][MADPCM_LPC_ORDER + 1] = { 0 };
int64_t error[MADPCM_LPC_ORDER + 1];
error[0] = r[0];
for (int k = 1; k <= order; k++) {
int64_t sum = 0;
for (int j = 1; j < k; j++) {
int64_t product = (int64_t)a[k - 1][j] * r[k - j];
sum += (product >> 24);
}
int64_t numerator = r[k] - sum;
int64_t denominator = error[k - 1];
if (denominator == 0) denominator = 1;
if (madpcm__abs_int64(numerator) >= madpcm__abs_int64(denominator)) return false;
int32_t reflection_coeff = (int32_t)((numerator << 24) / denominator);
a[k][k] = reflection_coeff;
for (int j = 1; j < k; j++) {
int64_t term = (int64_t)reflection_coeff * a[k - 1][k - j];
a[k][j] = a[k - 1][j] - (int32_t)(term >> 24);
}
int64_t reflection_sq = (int64_t)reflection_coeff * reflection_coeff;
error[k] = (error[k - 1] * ((1LL << 30) - (reflection_sq >> 18))) >> 30;
}
for (int i = 0; i < order; i++) {
outCoeffs[i] = madpcm__clamp_q15(a[order][i + 1] >> 12);
}
return true;
}
// LUT-based Dequantizer
static inline int16_t madpcm__dequantize_LUT(int8_t nibble, int16_t scale) {
int8_t index = nibble & 0x07;
int8_t sign = (nibble & 0x08) ? -1 : 1;
int32_t value = ((int32_t)scale * madpcm__gauss_steps[index]) >> 11;
return (sign < 0) ? (int16_t)-value : (int16_t)value;
}
// Finds best nibble in O(1) using inverse quantization estimation
static inline int8_t madpcm__find_best_nibble(int32_t target, int16_t scale) {
if (scale < 1) scale = 1;
int32_t abs_target = madpcm__abs_int32(target);
// Approximation: index ~ (val * 160) >> 16
// 1/410 (step size) in Q16 is approx 160
int32_t normalized_val = (abs_target << 11) / scale;
int32_t index = (normalized_val * 160) >> 16;
if (index > 7) index = 7;
// Check neighbor to handle boundary cases
int32_t estimate = (scale * madpcm__gauss_steps[index]) >> 11;
int32_t diff1 = madpcm__abs_int32(abs_target - estimate);
if (index < 7) {
int32_t estimate_next = (scale * madpcm__gauss_steps[index + 1]) >> 11;
int32_t diff2 = madpcm__abs_int32(abs_target - estimate_next);
if (diff2 < diff1) index++;
}
if (target < 0) return (int8_t)(index | 0x08);
return (int8_t)index;
}
static inline void madpcm__stereo_to_mid_side(int16_t* left, int16_t* right, int count) {
for (int i = 0; i < count; i++) {
int32_t l_val = left[i], r_val = right[i];
left[i] = (int16_t)((l_val + r_val) >> 1);
right[i] = (int16_t)((l_val - r_val) >> 1);
}
}
static inline void madpcm__mid_side_to_stereo(int16_t* left, int16_t* right, int count) {
#ifdef __AVX2__
for (int i = 0; i < count; i += 16) {
__m256i M = _mm256_loadu_si256((__m256i*)(left + i));
__m256i S = _mm256_loadu_si256((__m256i*)(right + i));
_mm256_storeu_si256((__m256i*)(left + i), _mm256_adds_epi16(M, S));
_mm256_storeu_si256((__m256i*)(right + i), _mm256_subs_epi16(M, S));
}
#else
for (int i = 0; i < count; i++) {
int32_t mid = left[i], side = right[i];
left[i] = madpcm__clamp_q15(mid + side);
right[i] = madpcm__clamp_q15(mid - side);
}
#endif
}
#ifdef __AVX2__
// SIMD-accelerated dequantization using PSHUFB for LUT
static inline __m128i madpcm__dequantize_8_sse3(
__m128i nibbles, // 8 nibbles in low bytes
int16_t scale
) {
// Build LUT in XMM register (8 entries, 16-bit each)
__m128i lut = _mm_set_epi16(
madpcm__gauss_steps[7], madpcm__gauss_steps[6],
madpcm__gauss_steps[5], madpcm__gauss_steps[4],
madpcm__gauss_steps[3], madpcm__gauss_steps[2],
madpcm__gauss_steps[1], madpcm__gauss_steps[0]
);
// Extract index (lower 3 bits) and sign bit
__m128i indices = _mm_and_si128(nibbles, _mm_set1_epi8(0x07));
__m128i signs = _mm_and_si128(nibbles, _mm_set1_epi8(0x08));
// Shuffle to get LUT values (needs careful byte manipulation)
// Convert indices to word positions
__m128i idx_even = _mm_and_si128(indices, _mm_set1_epi16(0x00FF));
__m128i idx_even_scaled = _mm_slli_epi16(idx_even, 1);
// This is getting complex - for production, use scalar loop with
// heavily unrolled and pipelined code instead
// For now, fall back to optimized scalar in the main loop
return _mm_setzero_si128();
}
#endif
// Encoder
int madpcm_encode_block(const int16_t* inSamples, const int16_t* prevSamples, uint8_t* outBuffer, bool fast) {
madpcm__block_header* header = (madpcm__block_header*)outBuffer;
uint8_t* payload = outBuffer + sizeof(madpcm__block_header);
// Capture History from Raw PCM
// internal history layout: [0]=t-1, [1]=t-2, [2]=t-3, [3]=t-4
if (prevSamples) {
header->seed[0] = prevSamples[3];
header->seed[1] = prevSamples[2];
header->seed[2] = prevSamples[1];
header->seed[3] = prevSamples[0];
} else {
madpcm_memset(header->seed, 0, sizeof(header->seed));
}
// High Precision Analysis
int64_t autocorrelation[MADPCM_LPC_ORDER + 1] = { 0 };
madpcm__compute_autocorr(inSamples, MADPCM_BLOCK_SAMPLES, MADPCM_LPC_ORDER, autocorrelation);
if (!madpcm__compute_LPC(autocorrelation, MADPCM_LPC_ORDER, header->coeffs)) {
madpcm_memset(header->coeffs, 0, sizeof(header->coeffs));
}
// Simulation & Heuristics Loop
int64_t signal_energy = 0, residual_energy = 0;
int zero_crossings = 0;
int32_t max_residual_sub[4] = { 0 };
int32_t max_signal_sub[4] = { 0 };
// Initialize simulation history with perfect seeds
int16_t sim_history[MADPCM_LPC_ORDER];
madpcm_memcpy(sim_history, header->seed, sizeof(sim_history));
int32_t c0 = header->coeffs[0], c1 = header->coeffs[1];
int32_t c2 = header->coeffs[2], c3 = header->coeffs[3];
bool bad_model = false;
for (int i = 0; i < MADPCM_BLOCK_SAMPLES; i++) {
int sub_block_idx = i >> 8;
// ZCR Check: (current ^ prev) < 0 means sign bit changed
if (i > 0 && ((inSamples[i] ^ inSamples[i - 1]) < 0))
zero_crossings++;
int32_t prediction = (c0 * sim_history[0]) + (c1 * sim_history[1]) + (c2 * sim_history[2]) + (c3 * sim_history[3]);
prediction >>= 12;
int32_t residual = inSamples[i] - prediction;
signal_energy += (int64_t)inSamples[i] * inSamples[i];
residual_energy += (int64_t)residual * residual;
if (madpcm__abs_int32(residual) > max_residual_sub[sub_block_idx]) max_residual_sub[sub_block_idx] = madpcm__abs_int32(residual);
if (madpcm__abs_int32(inSamples[i]) > max_signal_sub[sub_block_idx]) max_signal_sub[sub_block_idx] = madpcm__abs_int32(inSamples[i]);
if (madpcm__abs_int32(residual) > 28000) bad_model = true;
sim_history[3] = sim_history[2]; sim_history[2] = sim_history[1]; sim_history[1] = sim_history[0]; sim_history[0] = inSamples[i];
}
bool high_zcr = zero_crossings > (MADPCM_BLOCK_SAMPLES >> 2);
bool low_gain = signal_energy < (8 * (residual_energy + 1));
bool use_fallback = bad_model || (high_zcr && low_gain);
if (use_fallback) {
madpcm_memset(header->coeffs, 0, sizeof(header->coeffs));
c0 = c1 = c2 = c3 = 0;
}
// Populate scales with Gaussian Correction (~0.66x)
for (int k = 0; k < 4; k++) {
int32_t target_max = use_fallback ? max_signal_sub[k] : max_residual_sub[k];
int32_t scale_val = (target_max * 2) / 3;
if (scale_val < 10) scale_val = 10;
header->scales[k] = madpcm__clamp_q15(scale_val);
}
// Trellis Quantization
int16_t current_history[MADPCM_LPC_ORDER];
madpcm_memcpy(current_history, header->seed, sizeof(current_history));
// Process 4 sub-blocks
for (int sb = 0; sb < 4; sb++) {
int16_t current_scale = header->scales[sb];
const int16_t* block_ptr = inSamples + (sb * 256);
uint8_t* sub_payload_ptr = payload + (sb * 128);
// FAST MODE: No Trellis
if (fast) {
for (int i = 0; i < 256; i += 2) {
// Nibble 1
int32_t p1 = (c0 * current_history[0] + c1 * current_history[1] + c2 * current_history[2] + c3 * current_history[3]) >> 12;
int8_t n1 = madpcm__find_best_nibble(block_ptr[i] - p1, current_scale);
int16_t r1 = madpcm__clamp_q15(p1 + madpcm__dequantize_LUT(n1, current_scale));
current_history[3] = current_history[2]; current_history[2] = current_history[1]; current_history[1] = current_history[0]; current_history[0] = r1;
// Nibble 2
int32_t p2 = (c0 * current_history[0] + c1 * current_history[1] + c2 * current_history[2] + c3 * current_history[3]) >> 12;
int8_t n2 = madpcm__find_best_nibble(block_ptr[i + 1] - p2, current_scale);
int16_t r2 = madpcm__clamp_q15(p2 + madpcm__dequantize_LUT(n2, current_scale));
current_history[3] = current_history[2]; current_history[2] = current_history[1]; current_history[1] = current_history[0]; current_history[0] = r2;
sub_payload_ptr[i >> 1] = (n1 & 0x0F) | (n2 << 4);
}
continue;
}
// QUALITY MODE: Trellis
// Traceback buffers [sample][path]
int8_t trace_nibble[256][MADPCM_TRELLIS_K];
int8_t trace_index[256][MADPCM_TRELLIS_K];
madpcm__trellis_state current_states[MADPCM_TRELLIS_K];
int active_paths = 1;
current_states[0].err = 0;
madpcm_memcpy(current_states[0].h, current_history, sizeof(current_history));
for (int sample_idx = 0; sample_idx < 256; sample_idx++) {
madpcm__trellis_candidate candidates[MADPCM_TRELLIS_K * 3];
int candidate_count = 0;
for (int path_idx = 0; path_idx < active_paths; path_idx++) {
int16_t* path_hist = current_states[path_idx].h;
int32_t prediction = (c0 * path_hist[0] + c1 * path_hist[1] + c2 * path_hist[2] + c3 * path_hist[3]) >> 12;
int32_t residual_target = block_ptr[sample_idx] - prediction;
// Check best greedy + neighbors
int8_t best_nibble = madpcm__find_best_nibble(residual_target, current_scale);
int8_t center_idx = (best_nibble & 0x08) ? (best_nibble & 0x07) + 8 : best_nibble;
for (int offset = -1; offset <= 1; offset++) {
int search_idx = center_idx + offset;
if (search_idx < 0 || search_idx > 15) continue;
int8_t current_nib = (search_idx < 8) ? (int8_t)search_idx : (int8_t)((search_idx & 0x07) | 0x08);
int16_t reconstructed = madpcm__clamp_q15(prediction + madpcm__dequantize_LUT(current_nib, current_scale));
int64_t diff = block_ptr[sample_idx] - reconstructed;
int64_t squared_error = diff * diff;
candidates[candidate_count].err = current_states[path_idx].err + squared_error;
candidates[candidate_count].p_idx = path_idx;
candidates[candidate_count].nib = current_nib;
candidates[candidate_count].val = reconstructed;
candidate_count++;
}
}
// Select best K
madpcm__trellis_state next_states[MADPCM_TRELLIS_K];
int next_count = 0;
for (int k = 0; k < MADPCM_TRELLIS_K && k < candidate_count; k++) {
int best_cand_idx = -1;
int64_t min_err = -1;
for (int j = 0; j < candidate_count; j++) {
if (candidates[j].err == -1) continue;
if (best_cand_idx == -1 || candidates[j].err < min_err) { min_err = candidates[j].err; best_cand_idx = j; }
}
if (best_cand_idx == -1) break;
// Save traceback data instead of full path copy
trace_index[sample_idx][k] = (int8_t)candidates[best_cand_idx].p_idx;
trace_nibble[sample_idx][k] = candidates[best_cand_idx].nib;
next_states[k] = current_states[candidates[best_cand_idx].p_idx];
next_states[k].err = candidates[best_cand_idx].err;
int16_t* next_hist = next_states[k].h;
next_hist[3] = next_hist[2]; next_hist[2] = next_hist[1]; next_hist[1] = next_hist[0]; next_hist[0] = candidates[best_cand_idx].val;
candidates[best_cand_idx].err = -1; // Mark used
next_count++;
}
active_paths = next_count;
madpcm_memcpy(current_states, next_states, sizeof(madpcm__trellis_state) * active_paths);
}
// Commit best path (Backtracking)
madpcm_memcpy(current_history, current_states[0].h, sizeof(current_history));
int backtrack_path_idx = 0; // Index 0 is best due to sort
uint8_t final_nibbles[256];
for (int i = 255; i >= 0; i--) {
final_nibbles[i] = trace_nibble[i][backtrack_path_idx];
backtrack_path_idx = trace_index[i][backtrack_path_idx];
}
for (int j = 0; j < 128; j++) {
sub_payload_ptr[j] = (final_nibbles[2 * j] & 0x0F) | (final_nibbles[2 * j + 1] << 4);
}
}
return sizeof(madpcm__block_header) + (MADPCM_BLOCK_SAMPLES / 2);
}
// Adaptive Stereo Encoder
int madpcm_encode_block_stereo(const int16_t* inL, const int16_t* inR, const int16_t* prevL, const int16_t* prevR, uint8_t* outBuffer, bool fast) {
int16_t buffer_left[MADPCM_BLOCK_SAMPLES], buffer_right[MADPCM_BLOCK_SAMPLES];
int16_t history_left[4], history_right[4];
madpcm_memcpy(buffer_left, inL, sizeof(buffer_left));
madpcm_memcpy(buffer_right, inR, sizeof(buffer_right));
// Prepare local history copies (safe to modify)
if (prevL && prevR) {
madpcm_memcpy(history_left, prevL, 4 * sizeof(int16_t));
madpcm_memcpy(history_right, prevR, 4 * sizeof(int16_t));
} else {
madpcm_memset(history_left, 0, sizeof(history_left));
madpcm_memset(history_right, 0, sizeof(history_right));
}
// Adaptive Check: Calculate Energies
int64_t energy_lr = 0, energy_ms = 0;
for (int i = 0; i < MADPCM_BLOCK_SAMPLES; i += 4) {
int32_t mid = (buffer_left[i] + buffer_right[i]) >> 1;
int32_t side = (buffer_left[i] - buffer_right[i]) >> 1;
energy_lr += madpcm__abs_int32(buffer_left[i]) + madpcm__abs_int32(buffer_right[i]);
energy_ms += madpcm__abs_int32(mid) + madpcm__abs_int32(side);
}
bool use_ms = (energy_ms * 10 < energy_lr * 9);
// If using MS, we must also convert the HISTORY to MS
if (use_ms) {
madpcm__stereo_to_mid_side(buffer_left, buffer_right, MADPCM_BLOCK_SAMPLES);
madpcm__stereo_to_mid_side(history_left, history_right, 4);
}
int size_left = madpcm_encode_block(buffer_left, history_left, outBuffer, fast);
int size_right = madpcm_encode_block(buffer_right, history_right, outBuffer + size_left, fast);
// Flagging: Use LSB of first scale
madpcm__block_header* main_header = (madpcm__block_header*)outBuffer;
main_header->scales[0] = (main_header->scales[0] & ~1) | (use_ms ? 1 : 0);
return size_left + size_right;
}
// Decoder
void madpcm_decode_block(const uint8_t* inBuffer, int16_t* outSamples) {
const madpcm__block_header* header = (const madpcm__block_header*)inBuffer;
const uint8_t* payload = inBuffer + sizeof(madpcm__block_header);
const int32_t c0 = header->coeffs[0];
const int32_t c1 = header->coeffs[1];
const int32_t c2 = header->coeffs[2];
const int32_t c3 = header->coeffs[3];
int16_t h0 = header->seed[0];
int16_t h1 = header->seed[1];
int16_t h2 = header->seed[2];
int16_t h3 = header->seed[3];
int current_sample_idx = 0;
#ifdef __AVX2__
// Process in batches of 32 samples (16 bytes)
for (int s_idx = 0; s_idx < MADPCM_BLOCK_SAMPLES / 256; s_idx++) {
int16_t scale = header->scales[s_idx] & ~1;
// Unroll by 8 for better pipelining
for (int i = 0; i < 256; i += 8) {
// Load 4 bytes = 8 nibbles
uint32_t packed = *(uint32_t*)(payload + (current_sample_idx >> 1));
// Unroll 8 iterations manually
#define DECODE_SAMPLE(shift) \
{ \
int32_t pred = (c0 * h0 + c1 * h1 + c2 * h2 + c3 * h3) >> 12; \
int8_t nibble = (packed >> (shift)) & 0x0F; \
int16_t delta = madpcm__dequantize_LUT(nibble, scale); \
int32_t val = pred + delta; \
h3 = h2; h2 = h1; h1 = h0; \
h0 = madpcm__clamp_q15(val); \
outSamples[current_sample_idx++] = h0; \
}
DECODE_SAMPLE(0);
DECODE_SAMPLE(4);
DECODE_SAMPLE(8);
DECODE_SAMPLE(12);
DECODE_SAMPLE(16);
DECODE_SAMPLE(20);
DECODE_SAMPLE(24);
DECODE_SAMPLE(28);
#undef DECODE_SAMPLE
}
}
#else
// Each scale applies to 256 samples
for (int s_idx = 0; s_idx < MADPCM_BLOCK_SAMPLES / 256; s_idx++) {
int16_t current_scale = header->scales[s_idx] & ~1;
for (int i = 0; i < 256; i++, current_sample_idx++) {
int32_t prediction = (c0 * h0 + c1 * h1 + c2 * h2 + c3 * h3) >> 12;
uint8_t payload_byte = payload[current_sample_idx >> 1];
int8_t current_nibble = (current_sample_idx & 1)
? (payload_byte >> 4)
: (payload_byte & 0x0F);
int16_t delta = madpcm__dequantize_LUT(current_nibble, current_scale);
int32_t final_val = prediction + delta;
h3 = h2;
h2 = h1;
h1 = h0;
h0 = madpcm__clamp_q15(final_val);
outSamples[current_sample_idx] = h0;
}
}
#endif
}
void madpcm_decode_block_stereo(const uint8_t* inBuffer, int16_t* outL, int16_t* outR) {
const madpcm__block_header* header = (const madpcm__block_header*)inBuffer;
bool is_mid_side = (header->scales[0] & 1);
int mono_block_size = sizeof(madpcm__block_header) + (MADPCM_BLOCK_SAMPLES / 2);
madpcm_decode_block(inBuffer, outL);
madpcm_decode_block(inBuffer + mono_block_size, outR);
if (is_mid_side) madpcm__mid_side_to_stereo(outL, outR, MADPCM_BLOCK_SAMPLES);
}
#endif // MADPCM_IMPLEMENTATION
#endif // MADPCM_H
+1 -1
View File
@@ -1221,7 +1221,7 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in)
#define STBIR_STREAMOUT_PTR( star ) star __restrict__
#define STBIR_NO_UNROLL( ptr ) __asm__ (""::"r"(ptr))
#if __GNUC__ >= 14
#define STBIR_NO_UNROLL_LOOP_START _Pragma("GCC unroll 0") _Pragma("GCC novector")
#define STBIR_NO_UNROLL_LOOP_START _Pragma("GCC novector")
#else
#define STBIR_NO_UNROLL_LOOP_START
#endif
+10
View File
@@ -1,4 +1,5 @@
// text.h
#pragma once
#ifndef TEXT_RENDERER_H
#define TEXT_RENDERER_H
@@ -6,7 +7,13 @@
struct text_t;
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#endif
// initializes a new text renderer with given width and height
text_t* text_init(int width, int height);
// shares cache, prefer when using the same font to save memory
@@ -30,6 +37,9 @@ bool text_nextframe(text_t* text, float deltatime);
const uint32_t* text_getfb(text_t* text);
// clears the text buffer and the frame buffer
void text_clear(text_t* text);
#ifdef __cplusplus
}
#endif
#endif // TEXT_RENDERER_H
+45 -24
View File
@@ -2,39 +2,57 @@
#pragma once
#ifndef WINDOW_H
#define WINDOW_H
#include <cstdint>
typedef struct window_t window_t;
#define KEY_RETURN 40
#define KEY_SPACE 44
#define KEY_ESCAPE 41
#define KEY_LSHIFT 42
#define KEY_RSHIFT 43
#define KEY_LCTRL 29
#define KEY_RCTRL 157
enum KeyCode {
// Letters
KEY_A = 4,
KEY_D = 7,
KEY_S = 22,
KEY_W = 26,
// Numbers
KEY_1 = 30,
KEY_0 = 39,
#define MOUSE_BUTTON_LEFT 1
#define MOUSE_BUTTON_MIDDLE 2
#define MOUSE_BUTTON_RIGHT 3
// Controls
KEY_RETURN = 40,
KEY_ESCAPE = 41,
KEY_BACKSPACE = 42,
KEY_TAB = 43,
KEY_SPACE = 44,
#define KEY_UP 82
#define KEY_DOWN 81
#define KEY_LEFT 80
#define KEY_RIGHT 79
#define KEY_BACKSPACE 42
// Modifiers
KEY_LCTRL = 224,
KEY_LSHIFT = 225,
KEY_RCTRL = 228,
KEY_RSHIFT = 229,
#define KEY_F1 58
#define KEY_F2 59
// Navigation
KEY_RIGHT = 79,
KEY_LEFT = 80,
KEY_DOWN = 81,
KEY_UP = 82,
#define KEY_A 4
#define KEY_D 7
#define KEY_W 26
#define KEY_S 22
#define KEY_1 30
#define KEY_0 39
// Function Keys
KEY_F1 = 58,
KEY_F2 = 59
};
enum MouseButton {
MOUSE_BUTTON_LEFT = 1,
MOUSE_BUTTON_MIDDLE = 2,
MOUSE_BUTTON_RIGHT = 3
};
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#endif
// creates a window with given width, height and title, title can be utf8
window_t* window_create(int width, int height, const char* title);
// destroys the window
@@ -49,6 +67,9 @@ bool window_is_key_down(window_t* s, int key_scancode);
bool window_is_mouse_button_down(window_t* s, int button_index);
// gets the current mouse position in window coordinates
void window_get_mouse_position(window_t* s, int* x, int* y);
#ifdef __cplusplus
}
#endif
#endif // WINDOW_H