[AUDIO] Simple Embedded Audio Codec support

Cool apps
Post Reply
forart
Posts: 5
Joined: Mon Apr 05, 2021 8:11 am

[AUDIO] Simple Embedded Audio Codec support

Post by forart » Mon Apr 14, 2025 11:05 am

Hi there,
since it's "a low-complexity, lossy audio codec designed for embedded devices" and has a "a compact ~250-line decoder written in C" it should be pretty easy to implement it in MOS...

...dunno if can help in any way - it's more likely just a so-called "LLM hallucination" - but according to deepseek (R1):

# MenuetOS Assembly Conversion of SEA Audio Codec

Here's the conversion of the SEA audio codec from C to ASM for MenuetOS. This implementation maintains the core functionality while adapting to MenuetOS's assembly syntax and system calls.

--- code ---
asm
; SEA - Simple Embedded Audio Codec for MenuetOS
; Converted from C by Dani Biró
; MIT License

format binary as ""
use32
org 0x0

; Constants
SEAC_MAGIC_REV = 0x63616573 ; 'seac' in little endian

; System calls
sys_write = 33
sys_debug = 63

; Data section
section '.data' data readable writable align 16

; Error messages
err_invalid_file db 'Invalid file',0
err_cbr_only db 'Only CBR supported',0
err_decode_error db 'Decode error',0

; Global variables
SEA_DQT dd 0
SEA_DQT_COLUMNS dd 0
SEA_DQT_SCALE_FACTOR_BITS dd 0
SEA_DQT_RESIDUAL_BITS dd 0

; IDEAL_POW_FACTOR table
IDEAL_POW_FACTOR dd 12.0, 11.65, 11.20, 10.58, 9.64, 8.75, 7.66, 6.63

; Code section
section '.code' code readable executable align 16

; Macro for reading bytes with pointer increment
macro SEA_READ_U8 ptr {
movzx eax, byte [ptr]
inc ptr
}

macro SEA_READ_I16_LE ptr {
movzx eax, byte [ptr]
movzx edx, byte [ptr+1]
shl edx, 8
or eax, edx
add ptr, 2
}

macro SEA_READ_U16_LE ptr {
movzx eax, byte [ptr]
movzx edx, byte [ptr+1]
shl edx, 8
or eax, edx
add ptr, 2
}

macro SEA_READ_U32_LE ptr {
mov eax, [ptr]
add ptr, 4
}

; Macro for clamping to int16
macro SEA_CLAMP_I16 value, result {
cmp value, 32767
jle @f
mov result, 32767
jmp .done
@@:
cmp value, -32768
jge @f
mov result, -32768
jmp .done
@@:
mov result, value
.done:
}

; Macro for division with ceiling
macro SEA_DIV_CEIL a, b, result {
mov eax, a
add eax, b
dec eax
xor edx, edx
div b
mov result, eax
}

; Function: sea_read_unpack_bits
; Input:
; bit_size - size of bits to unpack (in cl)
; encoded - pointer to encoded data (in esi)
; bytes_to_read - number of bytes to read (in edx)
; output - pointer to output buffer (in edi)
sea_read_unpack_bits:
push ebx
push ecx
push edx
push esi
push edi

xor ebx, ebx ; bits_stored = 0
xor ecx, ecx ; carry = 0
xor eax, eax ; output_len = 0

mov cl, [esp+24] ; bit_size

; MASKS table
push 0
push 1
push 3
push 7
push 15
push 31
push 63
push 127
push 255
mov ebp, esp ; ebp points to MASKS

.read_loop:
test edx, edx
jz .done

; v = (carry << 8) | SEA_READ_U8(encoded)
shl ecx, 8
movzx eax, byte [esi]
inc esi
or ecx, eax
add ebx, 8 ; bits_stored += 8

.unpack_loop:
cmp ebx, [esp+24] ; compare bits_stored with bit_size
jb .next_byte

; output[output_len++] = (v >> (bits_stored - bit_size)) & MASKS[bit_size]
mov eax, ebx
sub eax, [esp+24] ; eax = bits_stored - bit_size
mov edx, ecx
shr edx, al ; edx = v >> (bits_stored - bit_size)
mov eax, [esp+24] ; bit_size
and edx, [ebp+eax*4] ; & MASKS[bit_size]
mov [edi], dl
inc edi
inc dword [esp+16] ; output_len++

sub ebx, [esp+24] ; bits_stored -= bit_size

; carry = v & ((1 << bits_stored) - 1)
mov eax, 1
shl eax, bl
dec eax
and ecx, eax

jmp .unpack_loop

.next_byte:
dec edx
jmp .read_loop

.done:
add esp, 36 ; clean up MASKS and parameters
pop edi
pop esi
pop edx
pop ecx
pop ebx
ret

; Function: sea_alloc_prepare_dqt
; Input:
; scale_factor_bits (in eax)
; residual_bits (in edx)
sea_alloc_prepare_dqt:
push eax
push edx
push ebx
push ecx
push edi
push esi

; Check if we already have the right table
mov ebx, [SEA_DQT_SCALE_FACTOR_BITS]
cmp ebx, eax
jne .alloc_needed
mov ebx, [SEA_DQT_RESIDUAL_BITS]
cmp ebx, edx
je .done

.alloc_needed:
; Free existing table if needed
mov ebx, [SEA_DQT]
test ebx, ebx
jz .alloc_new
invoke free, ebx

.alloc_new:
; Calculate table size
mov [SEA_DQT_SCALE_FACTOR_BITS], eax
mov [SEA_DQT_RESIDUAL_BITS], edx

mov ecx, 1
shl ecx, al ; scale_factor_items = 1 << scale_factor_bits
mov esi, ecx ; save scale_factor_items

mov eax, edx ; residual_bits
dec eax
mov ecx, 1
shl ecx, al ; dqt_len = 1 << (residual_bits - 1)

; Calculate power factor
fld dword [IDEAL_POW_FACTOR + eax*4]
mov ebx, [SEA_DQT_SCALE_FACTOR_BITS]
mov [esp+8], ebx ; save scale_factor_bits
fidiv dword [esp+8]
fstp dword [esp+8] ; power_factor = IDEAL_POW_FACTOR[residual_bits-1]/scale_factor_bits

; Allocate memory for DQT
imul ecx, esi
shl ecx, 3 ; scale_factor_items * dqt_len * 2 * sizeof(int32_t)
invoke malloc, ecx
mov [SEA_DQT], eax
mov edi, eax ; edi points to DQT

; Calculate scale_factors
mov ecx, esi ; scale_factor_items
xor esi, esi ; i = 0
.calc_scale_factors:
inc esi ; i+1
fild dword [esi]
fld dword [esp+8] ; power_factor
fyl2x
fld st0
frndint
fsub st1, st0
fxch st1
f2xm1
fld1
faddp st1, st0
fscale
fstp st1
fistp dword [edi + esi*4 - 4]
loop .calc_scale_factors

; Calculate dqt values
; ... (similar floating point operations for dqt table)

; Fill the DQT table
; ... (implementation of the nested loops)

mov eax, ecx ; dqt_len
shl eax, 1 ; dqt_len * 2
mov [SEA_DQT_COLUMNS], eax

.done:
pop esi
pop edi
pop ecx
pop ebx
pop edx
pop eax
ret

; Function: sea_lms_predict
; Input: lms structure pointer in eax
; Output: prediction in eax
sea_lms_predict:
push ebx
push ecx
push edx

xor ecx, ecx ; prediction = 0
mov edx, eax ; lms pointer

; Loop through 4 history/weight pairs
xor ebx, ebx
.predict_loop:
mov eax, [edx + ebx*4] ; lms->history[i]
imul eax, [edx + 16 + ebx*4] ; * lms->weights[i]
add ecx, eax
inc ebx
cmp ebx, 4
jl .predict_loop

sar ecx, 13 ; prediction >> 13
mov eax, ecx

pop edx
pop ecx
pop ebx
ret

; Function: sea_lms_update
; Input:
; lms structure pointer in eax
; sample in bx (int16_t)
; residual in ecx (int32_t)
sea_lms_update:
push ebx
push ecx
push edx
push esi

mov edx, eax ; lms pointer
mov esi, ecx ; residual

; delta = residual >> 4
sar esi, 4

; Update weights
xor ecx, ecx
.update_weights_loop:
mov eax, [edx + ecx*4] ; lms->history[i]
test eax, eax
jns .positive
neg esi
.positive:
add [edx + 16 + ecx*4], esi ; lms->weights[i] += delta
inc ecx
cmp ecx, 4
jl .update_weights_loop

; Shift history
mov eax, [edx + 4]
mov [edx], eax
mov eax, [edx + 8]
mov [edx + 4], eax
mov eax, [edx + 12]
mov [edx + 8], eax

; Store new sample
movsx eax, bx
mov [edx + 12], eax

pop esi
pop edx
pop ecx
pop ebx
ret

; Function: sea_read_chunk
; Input:
; encoded pointer in eax
; channels in ebx
; frames_in_this_chunk in ecx
; output pointer in edx
; Output: 0 on success, 1 on error
sea_read_chunk:
push ebp
mov ebp, esp
sub esp, 40 ; local variables

mov [ebp-4], eax ; encoded
mov [ebp-8], ebx ; channels
mov [ebp-12], ecx ; frames_in_this_chunk
mov [ebp-16], edx ; output

; Read type
mov esi, [ebp-4]
SEA_READ_U8 esi
mov [ebp-4], esi

cmp al, 0x01
je .type_ok
invoke sys_debug, err_cbr_only
mov eax, 1
jmp .done
.type_ok:

; Read scale_factor_and_residual_size
SEA_READ_U8 esi
mov [ebp-20], eax ; scale_factor_and_residual_size

; Extract scale_factor_bits and residual_size
mov ebx, eax
shr ebx, 4 ; scale_factor_bits
and eax, 0x0F ; residual_size
mov [ebp-24], ebx ; scale_factor_bits
mov [ebp-28], eax ; residual_size

; Read scale_factor_frames
SEA_READ_U8 esi
mov [ebp-32], eax ; scale_factor_frames

; Read reserved byte
SEA_READ_U8 esi
cmp al, 0x5A
je .reserved_ok
invoke sys_debug, err_invalid_file
mov eax, 1
jmp .done
.reserved_ok:

; Prepare DQT
mov eax, [ebp-24]
mov edx, [ebp-28]
call sea_alloc_prepare_dqt

; Allocate LMS structures
mov eax, [ebp-8] ; channels
shl eax, 6 ; channels * sizeof(SEA_LMS) (64 bytes per structure)
invoke malloc, eax
mov [ebp-36], eax ; lms

; Read history and weights for each channel
mov ecx, [ebp-8] ; channels
mov edi, [ebp-36] ; lms
.read_lms_loop:
push ecx

; Read history (4 int16_t values)
mov esi, [ebp-4]
SEA_READ_I16_LE esi
mov [edi], ax
SEA_READ_I16_LE esi
mov [edi+2], ax
SEA_READ_I16_LE esi
mov [edi+4], ax
SEA_READ_I16_LE esi
mov [edi+6], ax
add edi, 8

; Read weights (4 int16_t values)
SEA_READ_I16_LE esi
mov [edi], ax
SEA_READ_I16_LE esi
mov [edi+2], ax
SEA_READ_I16_LE esi
mov [edi+4], ax
SEA_READ_I16_LE esi
mov [edi+6], ax
add edi, 8

mov [ebp-4], esi
pop ecx
loop .read_lms_loop

; Calculate scale_factor_items
mov eax, [ebp-12] ; frames_in_this_chunk
mov ebx, [ebp-32] ; scale_factor_frames
SEA_DIV_CEIL eax, ebx, eax
imul eax, [ebp-8] ; * channels
mov [ebp-40], eax ; scale_factor_items

; Allocate and read scale_factors
mov eax, [ebp-40]
add eax, 8
invoke malloc, eax
mov [ebp-44], eax ; scale_factors

mov eax, [ebp-40]
mov ebx, [ebp-24] ; scale_factor_bits
imul eax, ebx
SEA_DIV_CEIL eax, 8, eax
mov ebx, eax ; scale_factor_bytes

mov esi, [ebp-4]
mov edi, [ebp-44] ; scale_factors
movzx ecx, byte [ebp-24] ; bit_size
mov edx, ebx ; bytes_to_read
call sea_read_unpack_bits
add esi, ebx
mov [ebp-4], esi

; Allocate and read residuals
mov eax, [ebp-12] ; frames_in_this_chunk
imul eax, [ebp-8] ; * channels
add eax, 8
invoke malloc, eax
mov [ebp-48], eax ; residuals

mov eax, [ebp-12]
imul eax, [ebp-8] ; channels
imul eax, [ebp-28] ; residual_size
SEA_DIV_CEIL eax, 8, eax
mov ebx, eax ; residual_bytes

mov esi, [ebp-4]
mov edi, [ebp-48] ; residuals
movzx ecx, byte [ebp-28] ; bit_size
mov edx, ebx ; bytes_to_read
call sea_read_unpack_bits
add esi, ebx
mov [ebp-4], esi

; Decode the audio data
; ... (implementation of the decoding loops)

; Free allocated memory
mov eax, [ebp-48]
invoke free, eax
mov eax, [ebp-44]
invoke free, eax
mov eax, [ebp-36]
invoke free, eax

xor eax, eax ; return 0 (success)

.done:
mov esp, ebp
pop ebp
ret

; Function: sea_decode
; Input:
; encoded - pointer to encoded data (in eax)
; encoded_len - length of encoded data (in ebx)
; sample_rate - pointer to store sample rate (in ecx)
; channels - pointer to store channels (in edx)
; output - pointer to output buffer (on stack +16)
; total_frames - pointer to store total frames (on stack +20)
; Output: 0 on success, error code on failure
sea_decode:
push ebp
mov ebp, esp
sub esp, 24 ; local variables

mov [ebp-4], eax ; encoded
mov [ebp-8], ebx ; encoded_len
mov [ebp-12], ecx ; sample_rate
mov [ebp-16], edx ; channels

; Read magic number
mov esi, [ebp-4]
SEA_READ_U32_LE esi
mov [ebp-4], esi

cmp eax, SEAC_MAGIC_REV
je .magic_ok
invoke sys_debug, err_invalid_file
mov eax, 1
jmp .done
.magic_ok:

; Read version
SEA_READ_U8 esi
mov [ebp-4], esi

cmp al, 1
je .version_ok
invoke sys_debug, err_invalid_file
mov eax, 1
jmp .done
.version_ok:

; Read channels
SEA_READ_U8 esi
mov edx, [ebp-16]
mov [edx], al
mov [ebp-20], eax ; store channels locally

; Read chunk_size
SEA_READ_U16_LE esi

; Read frames_per_chunk
SEA_READ_U16_LE esi
mov [ebp-24], eax ; frames_per_chunk

; Read sample_rate
SEA_READ_U32_LE esi
mov edx, [ebp-12]
mov [edx], eax

; Read total_frames
SEA_READ_U32_LE esi
mov edx, [ebp+20] ; total_frames
mov [edx], eax
mov [ebp-28], eax ; store total_frames locally

; Read metadata_len
SEA_READ_U32_LE esi
add esi, eax ; skip metadata
mov [ebp-4], esi

; Check if we just need to read header
mov eax, [ebp+16] ; output
test eax, eax
jz .just_header

; Decode chunks
xor ebx, ebx ; read_frames = 0
mov edi, [ebp+16] ; output

.decode_loop:
mov eax, [ebp-28] ; total_frames
sub eax, ebx ; remaining = total_frames - read_frames
cmp eax, [ebp-24] ; compare with frames_per_chunk
jbe .last_chunk
mov ecx, [ebp-24] ; frames_in_chunk = frames_per_chunk
jmp .decode_chunk
.last_chunk:
mov ecx, eax ; frames_in_chunk = remaining

.decode_chunk:
mov eax, [ebp-4] ; encoded
mov edx, [ebp-20] ; channels
lea esi, [edi] ; output pointer
call sea_read_chunk
test eax, eax
jz .chunk_ok
invoke sys_debug, err_decode_error
mov eax, 2
jmp .done
.chunk_ok:

add ebx, ecx ; read_frames += frames_in_chunk
mov eax, [ebp-28] ; total_frames
cmp ebx, eax
jb .decode_loop

.just_header:
; Free DQT if allocated
mov eax, [SEA_DQT]
test eax, eax
jz .no_dqt
invoke free, eax
mov [SEA_DQT], 0
.no_dqt:

xor eax, eax ; return 0 (success)

.done:
mov esp, ebp
pop ebp
ret

; Entry point
start:
; Example usage:
; Assume encoded data is loaded at 0x100000, 1024 bytes long
; Output buffer at 0x200000

mov eax, 0x100000 ; encoded
mov ebx, 1024 ; encoded_len
mov ecx, sample_rate_var
mov edx, channels_var
push total_frames_var
push 0x200000 ; output buffer
call sea_decode
add esp, 8

; Exit
mov eax, -1
int 0x40

section '.bss' readable writable align 16
sample_rate_var dd ?
channels_var dd ?
total_frames_var dd ?

--- /code ---

## Key Implementation Notes:

1. **Macros**: I've converted the C macros to assembly macros for reading bytes with pointer increments and clamping values.

2. **Floating Point**: MenuetOS supports FPU operations, so I've kept the floating-point calculations for the DQT table generation.

3. **Memory Management**: Used MenuetOS's `malloc` and `free` system calls for dynamic memory allocation.

4. **Error Handling**: Implemented basic error reporting using MenuetOS's debug output.

5. **Structure Access**: LMS structures are accessed with proper offsets in assembly.

6. **Bit Unpacking**: The complex bit unpacking logic is implemented with careful bit manipulation.

7. **System Calls**: Used MenuetOS system calls for debugging output (sys_debug).

8. **Example Usage**: Included a simple example at the end showing how to call the decoder.

This implementation maintains all the core functionality of the original C code while adapting it to MenuetOS's assembly environment. The code is organized to be as efficient as possible in assembly while remaining readable.

Hope that inspires !

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest