Browse Source

added WAV parsing and PCM player

Iver 4 days ago
commit
83df1d2228
16 changed files with 419 additions and 0 deletions
  1. 6 0
      LICENSE.md
  2. 13 0
      Makefile
  3. BIN
      audio/voice.wav
  4. BIN
      builds/libyuzuparse.so
  5. BIN
      builds/main.bin
  6. 1 0
      changelog.txt
  7. 1 0
      dbg
  8. 0 0
      issues.txt
  9. 16 0
      readme.md
  10. 1 0
      run
  11. 11 0
      src/headers/YZ_functions.h
  12. 20 0
      src/headers/YZ_types.h
  13. 13 0
      src/headers/yuzuparse.h
  14. 17 0
      src/launch program/main.c
  15. 319 0
      src/main/main.c
  16. 1 0
      val

+ 6 - 0
LICENSE.md

@@ -0,0 +1,6 @@
+Copyright (c) 2025 Iver Martinson
+
+This software is NOT LICENSED for use, distribution, or modification.
+Do not use, distribute, or modify this code without explicit permission from the author.
+
+The author may release a license in the future. Until then, all rights are reserved.

+ 13 - 0
Makefile

@@ -0,0 +1,13 @@
+COMPILER=gcc
+FLAGS_ALL=-g -Wall -Wextra -Wno-unused-variable -Wno-unused-parameter -Wno-sequence-point
+FLAGS_EXAMPLE=-Lbuilds/ -lyuzuparse -Wl,-rpath=builds/ -lm
+FLAGS_LIB=-fPIC -shared -lc -lm  -lportaudio
+
+main.bin: libyuzuparse.so
+	$(COMPILER) $(FLAGS_ALL) src/launch\ program/main.c -o builds/main.bin $(FLAGS_EXAMPLE) 
+
+libyuzuparse.so:
+	$(COMPILER) $(FLAGS_ALL) src/main/main.c -o builds/libyuzuparse.so $(FLAGS_LIB) 
+
+clean:
+	rm builds/*

BIN
audio/voice.wav


BIN
builds/libyuzuparse.so


BIN
builds/main.bin


+ 1 - 0
changelog.txt

@@ -0,0 +1 @@
+-added wav parsing & PCM player

+ 1 - 0
dbg

@@ -0,0 +1 @@
+gdb -ex run builds/main.bin

+ 0 - 0
issues.txt


+ 16 - 0
readme.md

@@ -0,0 +1,16 @@
+# YuzuParse, an audio file parser & player
+
+YuzuParse parses audio files into PCM streams and can play them with PortAudio.
+
+## Parsable/Playable Formats
+
+* [] MP3
+* [x] WAV
+* [] OGG
+* [] FLAC
+* [] AIFF
+* [] AAC
+
+## Etymology
+
+* Yuzu is a kind of citrus (I'm running out of ideas)

+ 1 - 0
run

@@ -0,0 +1 @@
+./builds/main.bin

+ 11 - 0
src/headers/YZ_functions.h

@@ -0,0 +1,11 @@
+#ifndef YZ_FUNCTIONS_H
+#define YZ_FUNCTIONS_H
+
+#include "YZ_types.h"
+
+YZ_audio_stream* YZ_load_audio_file(char* filename, unsigned char debug_mode);
+pa_callback_data* YZ_play_stream(YZ_audio_stream* audio_stream);
+void YZ_init_player();
+void YZ_kill_player();
+
+#endif

+ 20 - 0
src/headers/YZ_types.h

@@ -0,0 +1,20 @@
+#ifndef YZ_TYPES_H
+#define YZ_TYPES_H
+
+#include <stdint.h>
+
+typedef struct {
+    float* pcm_data;
+    double sample_rate; // samples per second
+    uint8_t channel_count;
+    uint32_t sample_count;
+} YZ_audio_stream;
+
+typedef struct {
+    uint32_t current_sample;
+    float* pcm_data;
+    uint32_t channel_count;
+    uint32_t sample_count;
+} pa_callback_data;
+
+#endif

+ 13 - 0
src/headers/yuzuparse.h

@@ -0,0 +1,13 @@
+#ifndef YUZUPARSE_H
+#define YUZUPARSE_H
+
+#include "YZ_functions.h"
+#include "YZ_types.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <portaudio.h>
+
+#endif

+ 17 - 0
src/launch program/main.c

@@ -0,0 +1,17 @@
+#include "../headers/yuzuparse.h"
+
+int main(){
+    YZ_audio_stream* audio_stream = YZ_load_audio_file("audio/voice.wav", 0);
+
+    pa_callback_data* callback_data;
+
+    if (audio_stream)
+        callback_data = YZ_play_stream(audio_stream);
+
+    // wait until audio is finished playing until exiting
+    while (callback_data->current_sample < callback_data->sample_count){}
+
+    YZ_kill_player();
+
+    return 0;
+}

+ 319 - 0
src/main/main.c

@@ -0,0 +1,319 @@
+#include "../headers/yuzuparse.h"
+
+int (*cprintf)(const char *__restrict __format, ...) = printf;
+
+int printf_override(const char *__restrict __format, ...){
+    return 0;
+}
+
+#define print (*cprintf)
+
+char* lsb_byte_to_binary(uint32_t byte, uint8_t bits){
+    char* string = malloc(sizeof(char) * (bits + 1));
+
+    for (int i = 0; i < bits; i++){
+        string[i] = ((byte >> i) & 1) + 0x30;
+    }
+
+    string[bits] = '\0';
+
+    return string; 
+}
+
+// MSB, most sig bit is first
+// 1 = 00000001
+char* msb_byte_to_binary(uint32_t byte, uint8_t bits){
+    char* string = malloc(sizeof(char) * (bits + 1));
+
+    for (int i = 0; i < bits; i++){
+        string[bits - 1 - i] = ((byte >> i) & 1) + 0x30;
+    }
+
+    string[bits] = '\0';
+
+    return string;
+}
+
+// MSB, most sig bit is first
+// 1 = 00000001
+uint16_t binary_to_int(char* binary, uint8_t number_of_bits){
+    uint16_t final_value = 0;
+
+    for (int i = 0; i < number_of_bits; i++){
+        final_value += (1 << i) * (binary[number_of_bits - i - 1] - 0x30);
+    }
+
+    return final_value;
+}
+
+// return individial bit's value. Zero indexed
+// msb_get_bit(00100, 2) == 1
+uint8_t msb_get_bit(uint32_t data, uint8_t bit){
+    return (data >> bit) & 1;
+}
+
+uint32_t current_byte = 0;
+uint8_t* file_buffer = NULL;
+
+// byte reading funcs
+
+void skip(int bytes_to_skip){
+    current_byte += bytes_to_skip;
+}
+
+int8_t get_1(){
+    return file_buffer[current_byte++];
+}
+
+int16_t get_2(){
+    int16_t result = file_buffer[current_byte] | (file_buffer[current_byte + 1] << 8);
+    current_byte += 2;
+    return result;
+}
+
+int32_t get_4(){
+    int32_t result = file_buffer[current_byte] | 
+                     (file_buffer[current_byte + 1] << 8) | 
+                     (file_buffer[current_byte + 2] << 16) | 
+                     (file_buffer[current_byte + 3] << 24);
+    current_byte += 4;
+    return result;
+}
+
+YZ_audio_stream* YZ_load_wav(){
+    YZ_audio_stream* audio_stream = malloc(sizeof(YZ_audio_stream));
+
+    char filename[5] = {get_1(), get_1(), get_1(), get_1(), '\0'};
+
+    if (strcmp(filename, "RIFF")){
+        printf("file is not a WAVE file\n");
+
+        return NULL;
+    }
+
+    uint32_t chunk_size = get_4();
+    char wave_id[5] = {get_1(), get_1(), get_1(), get_1(), '\0'};
+
+    unsigned char reading_file = 1;
+
+    uint32_t chunk_id;
+
+    uint16_t bits_per_sample;
+    uint16_t data_block_size;
+
+    skip(4);
+
+    print("reading format chunk at byte #%d\n", current_byte - 4);
+
+    uint32_t fmt_chunk_size = get_4();
+    uint16_t format_code = get_2();
+    uint16_t interleaved_channel_count = get_2();
+    uint32_t samples_per_second = get_4(); // blocks per second
+    uint32_t bytes_per_second = get_4();
+    data_block_size = get_2();
+    bits_per_sample = get_2();
+    uint16_t extension_size = 0;
+    if (fmt_chunk_size > 16) extension_size = get_2();
+    uint16_t valid_bits_per_sample = 0;
+    if (fmt_chunk_size > 18) valid_bits_per_sample = get_2();
+
+    if (fmt_chunk_size != 16){
+        printf("audio data is not PCM. cannot read this yet\n");
+
+        return NULL;
+    }
+
+    if (format_code != 1){
+        printf("audio data is compressed. cannot read this yet\n");
+
+        return NULL;
+    }
+
+    audio_stream->channel_count = interleaved_channel_count;
+    audio_stream->sample_rate = (double)samples_per_second;
+
+    print("fmt_chunk_size: %d\nformat_code: %d\ninterleaved_channel_count: %d\nsamples_per_second: %d\nbytes_per_second: %d\ndata_block_size: %d\nbits_per_sample: %d\nextension_size: %d\nvalid_bits_per_sample: %d\n", fmt_chunk_size, format_code, interleaved_channel_count, samples_per_second, bytes_per_second, data_block_size, bits_per_sample, extension_size, valid_bits_per_sample);
+
+    // data
+
+    while (get_4() != 1635017060) 
+        current_byte -= 3;
+
+    print("reading data chunk at byte #%d\n", current_byte - 4);
+
+    uint32_t data_chunk_size = get_4();
+    
+    audio_stream->pcm_data = malloc(sizeof(double) * (data_chunk_size / (bits_per_sample / 8)));
+
+    audio_stream->sample_count = data_chunk_size / (bits_per_sample / 8) / audio_stream->channel_count;
+
+    print("data chunk is %d bytes\n", data_chunk_size);
+    
+    uint32_t current_sample = 0;
+
+    for (uint32_t i = 0; i < data_chunk_size;){
+        uint32_t sample_index = current_sample++ * audio_stream->channel_count;
+        
+        uint32_t bytes_read = 0;
+
+        for (uint32_t j = 0; j < audio_stream->channel_count; j++){
+            double value;
+
+            if (bits_per_sample == 8){
+                value = ((double)get_1() - 128.0) / 128.0;
+                bytes_read += 1;
+            } else if (bits_per_sample == 16){
+                value = (double)(int16_t)get_2() / 32768.0;
+                bytes_read += 2;
+            } else if (bits_per_sample == 32){
+                printf("cannot read 32 bit per sample audio yet\n");
+
+                return NULL;
+            }
+
+            audio_stream->pcm_data[sample_index + j] = value;
+        }
+
+        i += bytes_read;
+    }
+
+    return audio_stream;
+}
+
+
+YZ_audio_stream* YZ_load_mp3(){return NULL;}
+YZ_audio_stream* YZ_load_ogg(){return NULL;}
+YZ_audio_stream* YZ_load_flac(){return NULL;}
+YZ_audio_stream* YZ_load_aiff(){return NULL;}
+
+YZ_audio_stream* YZ_load_audio_file(char* filename, unsigned char debug_mode){
+    FILE *fp = fopen(filename, "rb");
+
+    if (fp == NULL){
+        printf("file \"%s\" does not exist !!!\n", filename);
+
+        return NULL;
+    }
+
+    fseek(fp, 0, SEEK_END);
+    long size = ftell(fp);
+    rewind(fp);
+
+    file_buffer = malloc(size);
+
+    fread(file_buffer, 1, size, fp);
+    fclose(fp);
+
+    // override (*cprintf) so there is no debug output
+    if (!debug_mode){
+        cprintf = printf_override;
+    }
+
+    // see what the file type is
+
+    char* mutable_filename = malloc(sizeof(char) * (strlen(filename) + 1));
+    strcpy(mutable_filename, filename);
+
+    char *strtok_string = strtok(mutable_filename, ".");
+    char *filetype_string;
+    
+    while(strtok_string != NULL) {
+        filetype_string = strtok_string;
+        strtok_string = strtok(NULL, ".");
+    }
+    
+    current_byte = 0;
+
+    if (!strcmp(filetype_string, "wav") || !strcmp(filetype_string, "WAV") || !strcmp(filetype_string, "wave") || !strcmp(filetype_string, "WAVE")){ 
+        return YZ_load_wav();
+    } else 
+    if (!strcmp(filetype_string, "mp3") || !strcmp(filetype_string, "MP3") || !strcmp(filetype_string, "mpga") || !strcmp(filetype_string, "MPGA")){ 
+        return YZ_load_mp3();
+    } else 
+    if (!strcmp(filetype_string, "placeholder") || !strcmp(filetype_string, "placeholder")){ 
+        return YZ_load_ogg();
+    } else 
+    if (!strcmp(filetype_string, "placeholder") || !strcmp(filetype_string, "placeholder")){ 
+        return YZ_load_flac();
+    } else 
+    if (!strcmp(filetype_string, "placeholder") || !strcmp(filetype_string, "placeholder")){ 
+        return YZ_load_aiff();
+    }
+
+    (*cprintf)("file is unreadable by Yuzu\n");
+
+    return NULL;
+}
+
+int portaudio_callback(const void* input, void* output, unsigned long frame_count, const PaStreamCallbackTimeInfo* pa_time_info, PaStreamCallbackFlags pa_status_flags, void* audio_data){
+    pa_callback_data* data = (pa_callback_data*)audio_data;
+    
+    float* out = (float*)output;
+
+    for(uint32_t i = 0; i < frame_count; i++){
+        for (uint32_t j = 0; j < data->channel_count; j++) {
+            *out++ = data->pcm_data[(data->current_sample) * data->channel_count + j];
+        }
+
+        data->current_sample++;
+    }
+    
+    if (data->current_sample < data->sample_count) return 0;
+    else{
+        free(audio_data);
+
+        return 1;
+    }
+}
+
+unsigned char player_is_initialized = 0;
+
+void YZ_init_player(){
+    if (player_is_initialized){
+        printf("cannot init, already started");
+    
+        return;
+    }
+    
+    PaError error;
+
+    error = Pa_Initialize();
+    if(error != paNoError) { printf("PortAudio error: %d\n", error); exit(1);}
+    
+    player_is_initialized = 1;
+}
+
+void YZ_kill_player(){
+    if (!player_is_initialized){
+        printf("cannot kill, player isn't initilized");
+
+        return;
+    }
+
+    PaError error;
+
+    error = Pa_Terminate();
+    if(error != paNoError) { printf("PortAudio error: %d\n", error); exit(1);}
+}
+
+pa_callback_data* YZ_play_stream(YZ_audio_stream* audio_stream){
+    PaError error;
+    
+    PaStream *stream;
+
+    pa_callback_data* callback_data = malloc(sizeof(pa_callback_data));
+
+    callback_data->channel_count = audio_stream->channel_count;
+    callback_data->current_sample = 0;
+    callback_data->pcm_data = audio_stream->pcm_data;
+    callback_data->sample_count = audio_stream->sample_count;
+
+    if (!player_is_initialized) YZ_init_player();
+
+    error = Pa_OpenDefaultStream(&stream, 0, audio_stream->channel_count, paFloat32, audio_stream->sample_rate, paFramesPerBufferUnspecified, portaudio_callback, callback_data);
+    if(error != paNoError) { printf("PortAudio error: %d\n", error); exit(1);}
+
+    Pa_StartStream(stream);
+
+    return callback_data;
+}

+ 1 - 0
val

@@ -0,0 +1 @@
+valgrind --leak-check=full --track-origins=yes ./builds/main.bin