├── .clang-format ├── .deepsource.toml ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_pt-BR.md ├── include ├── animation.h ├── duckwave.h └── miniaudio.h ├── install.sh ├── src ├── animation.c ├── duckwave.c └── main.c ├── test ├── test_init_device_playback.c ├── test_init_file_decoder.c ├── test_playback_callback.c ├── test_start_playsound_thread.c └── test_timestamp.c └── xmake.lua /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | IndentWidth: 2 3 | ColumnLimit: 80 4 | UseTab: Never 5 | 6 | BraceWrapping: 7 | AfterControlStatement: true 8 | BeforeElse: true 9 | 10 | AllowShortIfStatementsOnASingleLine: false 11 | AllowShortLoopsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: false 13 | IndentCaseLabels: false 14 | SpacesBeforeTrailingComments: 1 15 | AccessModifierOffset: -2 16 | BreakBeforeBraces: Linux 17 | -------------------------------------------------------------------------------- /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "cxx" -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ main, dev ] 6 | pull_request: 7 | branches: [ main, dev ] 8 | schedule: 9 | - cron: '0 0 * * *' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | 19 | - name: Install Homebrew 20 | run: | 21 | sudo apt-get update 22 | sudo apt-get install -y build-essential libncurses5-dev libncursesw5-dev cmake curl 23 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 24 | echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.profile 25 | echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.bashrc 26 | echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.zshrc 27 | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 28 | 29 | - name: Verify Homebrew installation 30 | run: | 31 | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 32 | brew --version 33 | 34 | - name: Install xmake and mold via Homebrew 35 | run: | 36 | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 37 | brew install xmake 38 | brew install mold 39 | 40 | - name: Install xxHash 41 | run: | 42 | git clone https://github.com/Cyan4973/xxHash.git 43 | cd xxHash 44 | make 45 | sudo make install 46 | cd .. 47 | 48 | - name: Configure xmake with mold linker 49 | run: | 50 | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 51 | xmake f --toolchain=dwtc --plat=linux --arch=x86_64 52 | 53 | - name: Build project 54 | run: | 55 | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 56 | xmake build 57 | 58 | 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Formatos de audio para teste 2 | *.mp3 3 | *.wav 4 | *.flac 5 | *.ogg 6 | 7 | # Local para as bibliotecas vendorizadas 8 | # /vendor 9 | # /.vendor 10 | 11 | # Pastas para desenvolvimento local 12 | /.env 13 | /env 14 | /.tmp 15 | /tmp 16 | 17 | * .idea 18 | 19 | # ARQUIVOS RELACIONADOS AO ZIG 20 | # ---------------------------- 21 | 22 | /zig-cache 23 | /zig-out 24 | 25 | # GITIGNORE COMUM PARA PROJETOS EM C 26 | # ---------------------------------- 27 | 28 | # Valgrind output 29 | /valgrind-out 30 | 31 | # Vgcore files 32 | /vgcore.* 33 | 34 | # Prerequisites 35 | *.d 36 | 37 | # Object files 38 | *.o 39 | *.ko 40 | *.obj 41 | *.elf 42 | 43 | # Linker output 44 | *.ilk 45 | *.map 46 | *.exp 47 | 48 | # Precompiled Headers 49 | *.gch 50 | *.pch 51 | 52 | # Libraries 53 | *.lib 54 | *.a 55 | *.la 56 | *.lo 57 | 58 | # Shared objects (inc. Windows DLLs) 59 | *.dll 60 | *.so 61 | *.so.* 62 | *.dylib 63 | 64 | # Executables 65 | *.exe 66 | *.out 67 | *.app 68 | *.i*86 69 | *.x86_64 70 | *.hex 71 | 72 | # Debug files 73 | *.dSYM/ 74 | *.su 75 | *.idb 76 | *.pdb 77 | 78 | # Kernel Module Compile Results 79 | *.mod* 80 | *.cmd 81 | .tmp_versions/ 82 | modules.order 83 | Module.symvers 84 | Mkfile.old 85 | dkms.conf 86 | 87 | # Binnary output files 88 | spinning-heart 89 | main 90 | 91 | 92 | # Arquivos binários gerados pelo GCC e Clang 93 | *.out 94 | *.exe 95 | *.o 96 | 97 | # Diretórios de cache do xmake 98 | .xmake/linux/x86_64/cache 99 | .xmake/ui.log 100 | .xmake/linux/x86_64/tmp 101 | 102 | # Diretório de saida do xmake 103 | build 104 | 105 | # arquivo do vscode 106 | .vscode/settings.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2023 Krone 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to 6 | deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 12 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 13 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 14 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

3 | English | 4 | Рortuguês 5 |

6 |

7 | 8 |

9 | Imagem logo 10 |

11 | 12 | ![GitHub license](https://img.shields.io/github/license/alvarorichard/DuckWave) 13 | ![GitHub languages top](https://img.shields.io/github/languages/top/alvarorichard/DuckWave) 14 | ![GitHub last commit](https://img.shields.io/github/last-commit/alvarorichard/DuckWave) 15 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/f988a35b582642289c5ce2f35ab21b53)](https://app.codacy.com/gh/alvarorichard/DuckWave/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) 16 | [![Build Status](https://github.com/alvarorichard/DuckWave/actions/workflows/ci.yml/badge.svg)](https://github.com/alvarorichard/DuckWave/actions) 17 | ![GitHub contributors](https://img.shields.io/github/contributors/alvarorichard/DuckWave) 18 | 19 | DUCKWAVE is a straightforward audio player written in C, designed for simplicity and ease of use. It supports playing multiple audio formats and offers basic controls such as play, pause, and stop. The project is built with minimal dependencies and is focused on providing a user-friendly experience for playing audio files from the terminal. 20 | 21 | * Music "Phat Sketch" Kevin MacLeod (incompetech.com) 22 | 23 | Licensed under Creative Commons: By Attribution 4.0 License [link](http://creativecommons.org/licenses/by/4.0/) 24 | 25 | https://github.com/alvarorichard/DuckWave/assets/88117897/80ba79bc-8d38-40e7-aa3b-b951fbaa9cf2 26 | 27 | 28 | ## Supported Audio Formats 29 | 30 | - MP3 31 | - WAV 32 | - FLAC 33 | 34 | 35 | 36 | 37 | 38 | ## Installation 39 | 40 | #### Prerequisites 41 | 42 | Ensure you have the following libraries installed: 43 | 44 | - `xmake` build system(optional) 45 | - `ncurses` library 46 | - `mold` linker 47 | 48 | ### Compiling the Code 49 | 50 | ```bash 51 | xmake 52 | ``` 53 | 54 | ### Compiling the Code with the Script 55 | 56 | ```bash 57 | chmod +x install.sh 58 | ``` 59 | 60 | Run the script: 61 | 62 | ```bash 63 | ./install.sh 64 | ``` 65 | 66 | This will create an executable called mp3player in your current directory. 67 | 68 | ## Usage 69 | 70 | 1. Add your mp3 file to the project directory. 71 | 2. In the main() function of main.c, replace "add you music here .mp3" with your mp3 file name. 72 | 3. Compile the code. 73 | 4. Run the executable: 74 | 75 | ```C 76 | ./mp3player your music.mp3 77 | ``` 78 | 79 | > [!IMPORTANT] 80 | > Please note that this project is still under development and may be subject to changes and improvements. 81 | 82 | ## Contributing 83 | 84 | Contributions to this project are welcome. Please follow these steps to contribute: 85 | 86 | 1. Fork the repository. 87 | 2. Create a new branch for your feature or bug fix. 88 | 3. Commit your changes. 89 | 4. Push to the branch. 90 | 5. Submit a pull request. 91 | 92 |

93 | Imagem logo 94 |

95 | -------------------------------------------------------------------------------- /README_pt-BR.md: -------------------------------------------------------------------------------- 1 |

2 |

3 | Рortuguês | 4 | English 5 |

6 |

7 | 8 | 9 |

10 | Imagem logo 11 |

12 | 13 | ![GitHub license](https://img.shields.io/github/license/alvarorichard/DuckWave) 14 | ![GitHub languages top](https://img.shields.io/github/languages/top/alvarorichard/DuckWave) 15 | ![GitHub last commit](https://img.shields.io/github/last-commit/alvarorichard/DuckWave) 16 | 17 | DUCKWAVE é um reprodutor de áudio simples, escrito em C, projetado para simplicidade e facilidade de uso. Ele suporta a reprodução de vários formatos de áudio e oferece controles básicos como reproduzir, pausar e parar. O projeto é construído com dependências mínimas e focado em fornecer uma experiência amigável ao usuário para reproduzir arquivos de áudio a partir do terminal. 18 | 19 | Música "Phat Sketch" de Kevin MacLeod (incompetech.com) 20 | 21 | Licenciada sob Creative Commons: By Attribution 4.0 License [link](http://creativecommons.org/licenses/by/4.0/) 22 | 23 | https://github.com/alvarorichard/DuckWave/assets/88117897/80ba79bc-8d38-40e7-aa3b-b951fbaa9cf2 24 | 25 | ## Formatos de Áudio Suportados 26 | 27 | - MP3 28 | - WAV 29 | - FLAC 30 | 31 | 32 | 33 | ## Instalação 34 | 35 | 36 | #### Pré-requisitos 37 | 38 | Certifique-se de que você tenha as seguintes bibliotecas instaladas: 39 | 40 | * `xmake` sistema de compilação 41 | * `ncurses` biblioteca 42 | * `mold` linker 43 | 44 | 45 | 46 | ### Compilando o Código 47 | ```bash 48 | xmake 49 | ``` 50 | ### Compilar o Código com o Script 51 | ```bash 52 | chmod +x install.sh 53 | ``` 54 | Execute o script: 55 | ```bash 56 | ./install.sh 57 | ``` 58 | 59 | Isso criará um executável chamado mp3player no seu diretório atual. 60 | 61 | ## Uso 62 | 63 | 1. Adicione seu arquivo mp3 ao diretório do projeto. 64 | 2. Na função main() do main.c, substitua "add you music here .mp3" pelo nome do seu arquivo mp3. 65 | 3. Compile o código. 66 | 4. Execute o executável: 67 | 68 | ```C 69 | ./mp3player sua musica.mp3 70 | ``` 71 | >[!IMPORTANTE] 72 | > Observe que este projeto ainda está em desenvolvimento e pode estar sujeito a alterações e melhorias. 73 | 74 | 75 | ## Contribuindo 76 | Contribuições para este projeto são bem-vindas. Por favor, siga estes passos para contribuir: 77 | 78 | 1. Faça um fork do repositório. 79 | 2. Crie uma nova branch para sua funcionalidade ou correção de bug. 80 | 3. Faça o commit das suas alterações. 81 | 4. Faça o push para a branch. 82 | 5. Envie um pull request. 83 | 84 |

85 | Imagem logo 86 |

87 | 88 | -------------------------------------------------------------------------------- /include/animation.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file animation.h 3 | * @brief Header file for animation functions and variables used in visualizing 4 | * audio playback. 5 | * 6 | * This file contains the declarations of functions and variables used to create 7 | * visual representations of audio playback. It includes functions for drawing 8 | * bars, handling playback callbacks, cleaning up resources, and initializing 9 | * ncurses. 10 | */ 11 | 12 | #ifndef ANIMATION_H 13 | #define ANIMATION_H 14 | 15 | #include "../include/duckwave.h" 16 | #include "../include/miniaudio.h" 17 | 18 | /** 19 | * @brief The total duration of the audio track in seconds. 20 | */ 21 | extern float song_duration; 22 | 23 | /** 24 | * @brief The current playback position in seconds. 25 | */ 26 | extern float song_cursor; 27 | 28 | /** 29 | * @brief Flag indicating whether the song has finished playing. 30 | */ 31 | extern bool song_finished; 32 | 33 | /** 34 | * @brief Flag indicating whether the playback is paused. 35 | */ 36 | extern bool is_paused; 37 | 38 | /** 39 | * @brief Structure containing sound data and playback device configuration. 40 | */ 41 | extern DuckWaveSoundData dw_sdata; 42 | 43 | /** 44 | * @brief Draws bars representing audio data and displays the current playback 45 | * time. 46 | * 47 | * This function clears the screen and draws a bar graph to visualize the audio 48 | * data. It uses smoothed data to create a smoother animation effect. The bars 49 | * are colored with a gradient effect, and the current playback time and total 50 | * duration are displayed. 51 | * 52 | * @param data An array of floating-point values representing the audio data to 53 | * be visualized. 54 | * @param count The number of bars to draw, corresponding to the number of 55 | * elements in the data array. 56 | * @param current_time The current playback time in seconds. 57 | * @param total_time The total duration of the audio track in seconds. 58 | */ 59 | void draw_bars_and_time(float* data, int count, float current_time, 60 | float total_time); 61 | 62 | /** 63 | * @brief Callback function for audio playback with visual representation. 64 | * 65 | * This function is a callback for audio playback using the MiniAudio library. 66 | * It reads PCM frames from the audio decoder, processes the audio data to 67 | * calculate bar heights for visualization, and updates the visual display. It 68 | * also handles the end of the audio track and pauses the playback if necessary. 69 | * 70 | * @param pDevice A pointer to the audio device structure. 71 | * @param pOutput A pointer to the buffer where the decoded audio frames will be 72 | * written. 73 | * @param pInput A pointer to the buffer where the input audio frames would be 74 | * read from (unused in this function). 75 | * @param frameCount The number of audio frames to process. 76 | */ 77 | void duckwave_playback_callback_with_visual(ma_device* pDevice, void* pOutput, 78 | const void* pInput, 79 | unsigned int frameCount); 80 | 81 | /** 82 | * @brief Cleans up resources and stops the ncurses environment. 83 | * 84 | * This function is registered to be called at program exit to ensure that 85 | * the ncurses environment is properly cleaned up and the audio device is 86 | * uninitialized. 87 | */ 88 | void cleanup(); 89 | 90 | /** 91 | * @brief Initializes the ncurses environment for visual display. 92 | * 93 | * This function sets up the ncurses environment, including color pairs 94 | * for the gradient effect used in the bar graph visualization. 95 | */ 96 | void init_ncurses(); 97 | 98 | #endif // ANIMATION_H 99 | -------------------------------------------------------------------------------- /include/duckwave.h: -------------------------------------------------------------------------------- 1 | #ifndef DUCKWAVE_H 2 | #define DUCKWAVE_H 3 | 4 | #include "./miniaudio.h" 5 | #include 6 | #include 7 | 8 | /** 9 | * Given a time in seconds (`float`), this function will write to the reference 10 | * (`char*`) that same time but in the `hh:mm:ss` format. Expected values 11 | * could be `72:43:05` or `05:59`, for example. 12 | */ 13 | void generate_timestamp(float, char *); 14 | 15 | /// NOTE: Below this function, there should be similar ones; the documentation 16 | /// will be the same. WARN: All functions with `callback` in the name will be 17 | /// executed in a separate thread. 18 | 19 | /** 20 | * These functions are responsible for reading data from the `decoder` object to 21 | * actually play the music. But **attention**, these functions will be executed 22 | * in a separate thread, be careful when programming! 23 | */ 24 | void duckwave_default_playback_callback(ma_device *, void *, const void *, 25 | unsigned int); 26 | 27 | /** 28 | * Struct with the objects that miniaudio needs to configure a new device 29 | * to start playing the specified music. 30 | */ 31 | typedef struct DuckWaveSoundData { 32 | ma_decoder decoder; 33 | ma_device_config device_config; 34 | ma_device device; 35 | } DuckWaveSoundData; 36 | 37 | /** 38 | * Initializes the decoder using a string with the path to the file, maybe not 39 | * with some formats like MP4. 40 | */ 41 | void duckwave_init_file_decoder(DuckWaveSoundData *, char *); 42 | 43 | /** 44 | * Once the decoder is configured, this function will simply 45 | * build the device to play the music using the miniaudio playback type. 46 | */ 47 | void duckwave_init_device_playback(DuckWaveSoundData *); 48 | 49 | /** 50 | * Abstraction for the `miniaudio.h` library, it will start the routine to 51 | * play the music in the background (in a separate thread); checking and 52 | * handling errors as they appear. 53 | */ 54 | void duckwave_start_playsound_thread(DuckWaveSoundData *); 55 | 56 | void test_generate_timestamp(); 57 | 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Função para compilar o projeto 4 | function compile() { 5 | CC="clang" # ou "gcc" se preferir 6 | CFLAGS="-lm -O3" 7 | SRC_FILES=("src/main.c" "src/duckwave.c" "src/animation.c") 8 | OUTPUT="duckwave" 9 | BUILD_DIR="build" 10 | 11 | # Criar diretório de build se não existir 12 | mkdir -p "$BUILD_DIR" 13 | 14 | # Compilar o projeto 15 | "$CC" $CFLAGS -I"include" -o "$BUILD_DIR/$OUTPUT" "${SRC_FILES[@]}" -lncursesw -lm 16 | 17 | if [ $? -ne 0 ]; then 18 | echo "Erro na compilação do projeto" 19 | exit 1 20 | fi 21 | 22 | echo "Projeto compilado com sucesso" 23 | } 24 | 25 | # Função para instalar no macOS 26 | function install_macos() { 27 | sudo mv build/duckwave /usr/local/bin/duckwave 28 | sudo ln -sf /usr/local/bin/duckwave /usr/local/bin/duckwave 29 | } 30 | 31 | # Função para instalar em outros sistemas 32 | function install_others() { 33 | sudo mv build/duckwave /usr/bin/duckwave 34 | sudo ln -sf /usr/bin/duckwave /usr/bin/duckwave 35 | } 36 | 37 | # Função principal para compilar e instalar 38 | function start() { 39 | compile 40 | if [ "$(uname)" == "Darwin" ]; then 41 | install_macos 42 | else 43 | install_others 44 | fi 45 | } 46 | 47 | # Verificar se o script está sendo executado como sudo 48 | if [ "$EUID" -eq 0 ]; then 49 | start 50 | else 51 | echo "Este programa deve ser executado como sudo" 52 | exit 1 53 | fi 54 | -------------------------------------------------------------------------------- /src/animation.c: -------------------------------------------------------------------------------- 1 | 2 | // File: src/animation.c 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "../include/duckwave.h" 12 | #include "../include/animation.h" 13 | 14 | #define NUM_BARS 80 15 | #define SMOOTHING_FACTOR 0.8 16 | 17 | float smoothed_data[NUM_BARS] = {0}; 18 | 19 | /** 20 | * @file animation.c 21 | * @brief Animation functions for visualizing audio playback. 22 | */ 23 | 24 | /** 25 | * @brief Draws bars representing audio data and displays the current playback 26 | * time. 27 | * 28 | * This function clears the screen and draws a bar graph to visualize the audio 29 | * data. It uses smoothed data to create a smoother animation effect. The bars 30 | * are colored with a gradient effect, and the current playback time and total 31 | * duration are displayed. 32 | * 33 | * @param data An array of floating-point values representing the audio data to 34 | * be visualized. 35 | * @param count The number of bars to draw, corresponding to the number of 36 | * elements in the data array. 37 | * @param current_time The current playback time in seconds. 38 | * @param total_time The total duration of the audio track in seconds. 39 | * 40 | * The function uses the ncurses library to draw the bars and display the time. 41 | * The bars are drawn using a gradient of block characters and colors. 42 | */ 43 | 44 | void draw_bars_and_time(float* data, int count, float current_time, 45 | float total_time) 46 | { 47 | clear(); 48 | int max_height = LINES - 3; 49 | 50 | const wchar_t* blocks[] = {L" ", L"░", L"▒", L"▓", L"█"}; 51 | int colors[] = {COLOR_RED, COLOR_YELLOW, COLOR_GREEN, 52 | COLOR_CYAN, COLOR_BLUE, COLOR_MAGENTA}; 53 | 54 | for (int i = 0; i < count; i++) { 55 | smoothed_data[i] = SMOOTHING_FACTOR * smoothed_data[i] + 56 | (1.0 - SMOOTHING_FACTOR) * data[i]; 57 | int height = (int)(smoothed_data[i] * max_height); 58 | 59 | for (int j = 0; j < height; j++) { 60 | const wchar_t* ch; 61 | int color_index = (j * (sizeof(colors) / sizeof(int))) / height; 62 | attron(COLOR_PAIR(color_index + 1)); 63 | 64 | if (j < height * 0.2) { 65 | ch = blocks[1]; 66 | } else if (j < height * 0.4) { 67 | ch = blocks[2]; 68 | } else if (j < height * 0.6) { 69 | ch = blocks[3]; 70 | } else { 71 | ch = blocks[4]; 72 | } 73 | mvprintw(max_height - j, i * 2, "%ls", ch); 74 | attroff(COLOR_PAIR(color_index + 1)); 75 | } 76 | } 77 | 78 | char time_str[128]; 79 | char total_time_str[128]; 80 | generate_timestamp(current_time, time_str); 81 | generate_timestamp(total_time, total_time_str); 82 | mvprintw(LINES - 1, 0, "Time: %s / %s", time_str, total_time_str); 83 | 84 | refresh(); 85 | } 86 | /** 87 | * @brief Callback function for audio playback with visual representation. 88 | * 89 | * This function is a callback for audio playback using the MiniAudio library. 90 | * It reads PCM frames from the audio decoder, processes the audio data to 91 | * calculate bar heights for visualization, and updates the visual display. It 92 | * also handles the end of the audio track and pauses the playback if necessary. 93 | * 94 | * @param pDevice A pointer to the audio device structure. 95 | * @param pOutput A pointer to the buffer where the decoded audio frames will be 96 | * written. 97 | * @param pInput A pointer to the buffer where the input audio frames would be 98 | * read from (unused in this function). 99 | * @param frameCount The number of audio frames to process. 100 | * 101 | * The function uses the ncurses library to draw a bar graph representing the 102 | * audio data. If playback is paused, the output buffer is filled with silence. 103 | */ 104 | 105 | void duckwave_playback_callback_with_visual(ma_device* pDevice, void* pOutput, 106 | const void* pInput, 107 | unsigned int frameCount) 108 | { 109 | ma_decoder* pDecoder = pDevice->pUserData; 110 | if (pDecoder == NULL) 111 | return; 112 | 113 | if (!is_paused) 114 | { 115 | ma_decoder_read_pcm_frames(pDecoder, pOutput, frameCount, NULL); 116 | 117 | float* audio_data = (float*)pOutput; 118 | float bar_data[NUM_BARS]; 119 | int samples_per_bar = frameCount / NUM_BARS; 120 | for (int i = 0; i < NUM_BARS; i++) { 121 | float sum = 0; 122 | for (int j = 0; j < samples_per_bar; j++) { 123 | sum += fabsf(audio_data[i * samples_per_bar + j]); 124 | } 125 | bar_data[i] = sum / samples_per_bar; 126 | } 127 | 128 | song_cursor += (float)frameCount / pDevice->sampleRate; 129 | draw_bars_and_time(bar_data, NUM_BARS, song_cursor, song_duration); 130 | 131 | if (song_cursor >= song_duration) { 132 | song_finished = true; 133 | ma_device_stop(pDevice); 134 | } 135 | } else { 136 | memset(pOutput, 0, 137 | frameCount * ma_get_bytes_per_frame(pDevice->playback.format, 138 | pDevice->playback.channels)); 139 | } 140 | 141 | (void)pInput; 142 | } 143 | 144 | void cleanup() 145 | { 146 | endwin(); 147 | ma_device_uninit(&dw_sdata.device); 148 | fflush(stdout); 149 | } 150 | 151 | void init_ncurses() 152 | { 153 | setlocale(LC_ALL, ""); // Configurar a localidade para UTF-8 154 | 155 | initscr(); 156 | start_color(); 157 | cbreak(); 158 | noecho(); 159 | curs_set(FALSE); 160 | timeout(0); // Permitir getch() não bloqueante 161 | 162 | // Inicializar pares de cores para o gradiente 163 | init_pair(1, COLOR_RED, COLOR_BLACK); 164 | init_pair(2, COLOR_YELLOW, COLOR_BLACK); 165 | init_pair(3, COLOR_GREEN, COLOR_BLACK); 166 | init_pair(4, COLOR_CYAN, COLOR_BLACK); 167 | init_pair(5, COLOR_BLUE, COLOR_BLACK); 168 | init_pair(6, COLOR_MAGENTA, COLOR_BLACK); 169 | 170 | atexit(cleanup); 171 | } 172 | -------------------------------------------------------------------------------- /src/duckwave.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define MINIAUDIO_IMPLEMENTATION 5 | 6 | #include "../include/duckwave.h" 7 | 8 | /** 9 | * This function generates a timestamp string in the format hh:mm:ss or mm:ss 10 | * from a float representing seconds. 11 | * 12 | * @param secs The total time in seconds. 13 | * @param res The resulting formatted timestamp string. It should be large 14 | * enough to hold the resulting string (128 bytes is assumed here). 15 | */ 16 | void generate_timestamp(float secs, char *res) 17 | { 18 | int hours = (int)secs / 3600; 19 | secs = (int)secs % 3600; 20 | int mins = (int)secs / 60; 21 | secs = (int)secs % 60; 22 | 23 | if (hours > 0) 24 | snprintf(res, 128, "%d:%02d:%02.0f", hours, mins, secs); // Format hh:mm:ss 25 | else 26 | snprintf(res, 128, "%02d:%02.0f", mins, secs); // Format mm:ss 27 | } 28 | 29 | /** 30 | * This is the default playback callback function for miniaudio, which is called 31 | * periodically to provide audio data for playback. 32 | * 33 | * @param pDevice The audio device that is calling this callback. 34 | * @param pOutput Pointer to the buffer where audio data should be written. 35 | * @param _pInput Pointer to the buffer containing input audio data (unused 36 | * here). 37 | * @param frameCount The number of audio frames to be processed. 38 | */ 39 | void duckwave_default_playback_callback(ma_device *pDevice, void *pOutput, 40 | const void *_pInput, 41 | unsigned int frameCount) 42 | { 43 | ma_decoder *pDecoder = pDevice->pUserData; 44 | if (pDecoder == NULL) 45 | return; 46 | 47 | ma_decoder_read_pcm_frames(pDecoder, pOutput, frameCount, NULL); 48 | 49 | (void)_pInput; 50 | } 51 | 52 | /** 53 | * Initializes the file decoder using a given file path. 54 | * 55 | * @param dw_sdata Pointer to the DuckWaveSoundData structure which holds the 56 | * decoder and device configuration. 57 | * @param file Path to the audio file to be decoded. 58 | */ 59 | void duckwave_init_file_decoder(DuckWaveSoundData *dw_sdata, char *file) 60 | { 61 | ma_result result; 62 | 63 | result = ma_decoder_init_file(file, NULL, &dw_sdata->decoder); 64 | 65 | if (result != MA_SUCCESS) { 66 | printf("\nCould not decode current %s file.\n", file); 67 | printf("%s.\n\n", ma_result_description(result)); 68 | 69 | exit(EXIT_FAILURE); 70 | } 71 | } 72 | 73 | /** 74 | * Initializes the playback device configuration based on the decoder's output 75 | * format, channels, and sample rate. 76 | * 77 | * @param dw_sdata Pointer to the DuckWaveSoundData structure which holds the 78 | * decoder and device configuration. 79 | */ 80 | void duckwave_init_device_playback(DuckWaveSoundData *dw_sdata) 81 | { 82 | ma_decoder *decoder = &dw_sdata->decoder; 83 | ma_device_config *device_config = &dw_sdata->device_config; 84 | 85 | *device_config = ma_device_config_init(ma_device_type_playback); 86 | 87 | device_config->playback.format = decoder->outputFormat; 88 | device_config->playback.channels = decoder->outputChannels; 89 | device_config->sampleRate = decoder->outputSampleRate; 90 | device_config->dataCallback = duckwave_default_playback_callback; 91 | device_config->pUserData = decoder; 92 | } 93 | 94 | /** 95 | * Starts the audio playback on a separate thread using the initialized device 96 | * and decoder. 97 | * 98 | * @param dw_sdata Pointer to the DuckWaveSoundData structure which holds the 99 | * decoder and device configuration. 100 | */ 101 | void duckwave_start_playsound_thread(DuckWaveSoundData *dw_sdata) 102 | { 103 | ma_result result; 104 | 105 | ma_decoder *decoder = &dw_sdata->decoder; 106 | ma_device *device = &dw_sdata->device; 107 | ma_device_config *device_config = &dw_sdata->device_config; 108 | 109 | result = ma_device_init(NULL, device_config, device); 110 | 111 | if (result != MA_SUCCESS) { 112 | printf("\nCannot initialize device.\n"); 113 | printf("%s.\n\n", ma_result_description(result)); 114 | 115 | ma_decoder_uninit(decoder); 116 | 117 | exit(EXIT_FAILURE); 118 | } 119 | 120 | result = ma_device_start(device); 121 | 122 | if (result != MA_SUCCESS) { 123 | printf("\nCannot start device.\n"); 124 | printf("%s.\n\n", ma_result_description(result)); 125 | 126 | ma_device_uninit(device); 127 | ma_decoder_uninit(decoder); 128 | 129 | exit(EXIT_FAILURE); 130 | } 131 | } -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../include/animation.h" 10 | #include "../include/duckwave.h" 11 | 12 | float song_duration = 0.0f, song_cursor = 0.0f; 13 | bool song_finished = false; 14 | bool is_paused = false; 15 | 16 | DuckWaveSoundData dw_sdata; 17 | 18 | int main(int argc, char* argv[]) 19 | { 20 | if (argc < 2) 21 | { 22 | printf("Usage: %s \n", argv[0]); 23 | return -1; 24 | } 25 | 26 | duckwave_init_file_decoder(&dw_sdata, argv[1]); 27 | duckwave_init_device_playback(&dw_sdata); 28 | 29 | ma_uint64 length_in_pcm_frames; 30 | ma_result result = ma_decoder_get_length_in_pcm_frames(&dw_sdata.decoder, 31 | &length_in_pcm_frames); 32 | if (result != MA_SUCCESS) 33 | { 34 | printf("Failed to get length of audio file.\n"); 35 | return -1; 36 | } 37 | song_duration = 38 | (float)length_in_pcm_frames / dw_sdata.decoder.outputSampleRate; 39 | 40 | dw_sdata.device_config.dataCallback = duckwave_playback_callback_with_visual; 41 | 42 | init_ncurses(); 43 | 44 | result = ma_device_init(NULL, &dw_sdata.device_config, &dw_sdata.device); 45 | if (result != MA_SUCCESS) 46 | { 47 | endwin(); 48 | printf("Failed to initialize playback device.\n"); 49 | return -1; 50 | } 51 | 52 | result = ma_device_start(&dw_sdata.device); 53 | if (result != MA_SUCCESS) 54 | { 55 | endwin(); 56 | printf("Failed to start playback device.\n"); 57 | ma_device_uninit(&dw_sdata.device); 58 | return -1; 59 | } 60 | 61 | while (1) { 62 | int timeout_ms = (int)((song_duration - song_cursor) * 1000); 63 | timeout(timeout_ms); 64 | 65 | int ch = getch(); 66 | if (ch != ERR) { 67 | if (ch == ' ') { 68 | is_paused = !is_paused; 69 | if (is_paused) { 70 | ma_device_stop(&dw_sdata.device); 71 | } else { 72 | ma_device_start(&dw_sdata.device); 73 | } 74 | } else { 75 | break; 76 | } 77 | } 78 | 79 | if (song_finished) { 80 | break; 81 | } 82 | } 83 | 84 | 85 | cleanup(); 86 | 87 | if (song_finished) 88 | { 89 | printf("Playback finished. Exiting...\n"); 90 | } 91 | 92 | return EXIT_SUCCESS; 93 | } -------------------------------------------------------------------------------- /test/test_init_device_playback.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include // For access function 5 | #define MINIAUDIO_IMPLEMENTATION 6 | #include "../include/duckwave.h" 7 | #include "../include/miniaudio.h" 8 | 9 | // clang test.f -lpthread -lm 10 | 11 | /** 12 | * This function initializes the file decoder using a given file path. 13 | * 14 | * @param dw_sdata Pointer to the DuckWaveSoundData structure which holds the 15 | * decoder and device configuration. 16 | * @param file Path to the audio file to be decoded. 17 | */ 18 | void duckwave_init_file_decoder(DuckWaveSoundData *dw_sdata, char *file) 19 | { 20 | ma_result result; 21 | 22 | result = ma_decoder_init_file(file, NULL, &dw_sdata->decoder); 23 | 24 | if (result != MA_SUCCESS) { 25 | printf("\nCould not decode current %s file.\n", file); 26 | printf("%s.\n\n", ma_result_description(result)); 27 | 28 | exit(EXIT_FAILURE); 29 | } 30 | } 31 | 32 | /** 33 | * This function initializes the playback device configuration based on the 34 | * decoder's output format, channels, and sample rate. 35 | * 36 | * @param dw_sdata Pointer to the DuckWaveSoundData structure which holds the 37 | * decoder and device configuration. 38 | */ 39 | void duckwave_init_device_playback(DuckWaveSoundData *dw_sdata) 40 | { 41 | ma_decoder *decoder = &dw_sdata->decoder; 42 | ma_device_config *device_config = &dw_sdata->device_config; 43 | 44 | *device_config = ma_device_config_init(ma_device_type_playback); 45 | 46 | device_config->playback.format = decoder->outputFormat; 47 | device_config->playback.channels = decoder->outputChannels; 48 | device_config->sampleRate = decoder->outputSampleRate; 49 | device_config->dataCallback = duckwave_default_playback_callback; 50 | device_config->pUserData = decoder; 51 | } 52 | 53 | /** 54 | * This is the default playback callback function for miniaudio, which is called 55 | * periodically to provide audio data for playback. 56 | * 57 | * @param pDevice The audio device that is calling this callback. 58 | * @param pOutput Pointer to the buffer where audio data should be written. 59 | * @param _pInput Pointer to the buffer containing input audio data (unused 60 | * here). 61 | * @param frameCount The number of audio frames to be processed. 62 | */ 63 | void duckwave_default_playback_callback(ma_device *pDevice, void *pOutput, 64 | const void *_pInput, 65 | unsigned int frameCount) 66 | { 67 | ma_decoder *pDecoder = pDevice->pUserData; 68 | if (pDecoder == NULL) { 69 | printf("Decoder is NULL\n"); 70 | return; 71 | } 72 | 73 | ma_uint64 framesRead; 74 | ma_result result = 75 | ma_decoder_read_pcm_frames(pDecoder, pOutput, frameCount, &framesRead); 76 | 77 | if (result != MA_SUCCESS) { 78 | printf("Failed to read PCM frames: %s\n", ma_result_description(result)); 79 | } else { 80 | printf("Frames read: %llu / %u\n", framesRead, frameCount); 81 | } 82 | 83 | if (framesRead < frameCount) { 84 | // Zero out the rest of the buffer to avoid noise 85 | memset((unsigned char *)pOutput + 86 | framesRead * ma_get_bytes_per_frame(pDevice->playback.format, 87 | pDevice->playback.channels), 88 | 0, 89 | (frameCount - framesRead) * 90 | ma_get_bytes_per_frame(pDevice->playback.format, 91 | pDevice->playback.channels)); 92 | } 93 | 94 | (void)_pInput; 95 | } 96 | 97 | /** 98 | * Function to test the duckwave_init_device_playback function. 99 | * 100 | * @param file The path to the audio file to be tested. 101 | */ 102 | void test_duckwave_init_device_playback(const char *file) 103 | { 104 | DuckWaveSoundData dw_sdata; 105 | 106 | // Initialize the decoder with the provided file 107 | printf("Testing with file: %s\n", file); 108 | if (access(file, F_OK) != -1) { 109 | duckwave_init_file_decoder(&dw_sdata, (char *)file); 110 | printf("Decoder initialized successfully for file: %s\n", file); 111 | 112 | // Initialize the device playback configuration 113 | duckwave_init_device_playback(&dw_sdata); 114 | 115 | // Print the device configuration to verify 116 | printf("Device playback configuration initialized:\n"); 117 | printf("Format: %d\n", dw_sdata.device_config.playback.format); 118 | printf("Channels: %d\n", dw_sdata.device_config.playback.channels); 119 | printf("Sample Rate: %d\n", dw_sdata.device_config.sampleRate); 120 | printf("Data Callback: %p\n", (void *)dw_sdata.device_config.dataCallback); 121 | printf("User Data: %p\n", (void *)dw_sdata.device_config.pUserData); 122 | 123 | // Cleanup 124 | ma_decoder_uninit(&dw_sdata.decoder); 125 | } else { 126 | printf("File %s does not exist.\n", file); 127 | } 128 | } 129 | 130 | /** 131 | * The main function that initializes the audio playback. 132 | * 133 | * @param argc The number of command-line arguments. 134 | * @param argv The array of command-line arguments. 135 | * @return EXIT_SUCCESS on successful execution, EXIT_FAILURE otherwise. 136 | */ 137 | int main(int argc, char **argv) 138 | { 139 | if (argc < 2) { 140 | printf("Usage: %s \n", argv[0]); 141 | return EXIT_FAILURE; 142 | } 143 | 144 | const char *audio_file = argv[1]; 145 | test_duckwave_init_device_playback(audio_file); 146 | return EXIT_SUCCESS; 147 | } 148 | -------------------------------------------------------------------------------- /test/test_init_file_decoder.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #define MINIAUDIO_IMPLEMENTATION 8 | #include "../include/duckwave.h" 9 | #include "../include/miniaudio.h" 10 | 11 | // clang test.f -lpthread -lm 12 | 13 | /** 14 | * This function initializes the file decoder using a given file path. 15 | * 16 | * @param dw_sdata Pointer to the DuckWaveSoundData structure which holds the 17 | * decoder and device configuration. 18 | * @param file Path to the audio file to be decoded. 19 | */ 20 | void duckwave_init_file_decoder(DuckWaveSoundData *dw_sdata, char *file) 21 | { 22 | ma_result result; 23 | 24 | result = ma_decoder_init_file(file, NULL, &dw_sdata->decoder); 25 | 26 | if (result != MA_SUCCESS) { 27 | printf("\nCould not decode current %s file.\n", file); 28 | printf("%s.\n\n", ma_result_description(result)); 29 | 30 | exit(EXIT_FAILURE); 31 | } 32 | } 33 | 34 | /** 35 | * Function to test the duckwave_init_file_decoder function with a user-provided 36 | * file. 37 | * 38 | * @param file The path to the audio file to be tested. 39 | */ 40 | void test_duckwave_init_file_decoder(const char *file) 41 | { 42 | DuckWaveSoundData dw_sdata; 43 | struct stat file_stat1, file_stat2; 44 | int fd; 45 | 46 | printf("Testing with file: %s\n", file); 47 | 48 | if (lstat(file, &file_stat1) == -1) { 49 | perror("lstat"); 50 | printf("File %s does not exist or cannot be accessed.\n", file); 51 | return; 52 | } 53 | 54 | fd = open(file, O_RDONLY); 55 | if (fd == -1) { 56 | perror("open"); 57 | printf("Failed to open file %s.\n", file); 58 | return; 59 | } 60 | 61 | if (fstat(fd, &file_stat2) == -1) { 62 | perror("fstat"); 63 | close(fd); 64 | return; 65 | } 66 | 67 | // Compare the file attributes to ensure they match 68 | if (file_stat1.st_dev != file_stat2.st_dev || file_stat1.st_ino != file_stat2.st_ino) { 69 | printf("File %s has been modified between checks.\n", file); 70 | close(fd); 71 | return; 72 | } 73 | 74 | // Casting away const-ness for duckwave_init_file_decoder 75 | duckwave_init_file_decoder(&dw_sdata, (char *)file); 76 | printf("Decoder initialized successfully for file: %s\n", file); 77 | ma_decoder_uninit(&dw_sdata.decoder); 78 | 79 | close(fd); 80 | } 81 | 82 | /** 83 | * The main function that initializes the audio playback. 84 | * 85 | * @param argc The number of command-line arguments. 86 | * @param argv The array of command-line arguments. 87 | * @return EXIT_SUCCESS on successful execution, EXIT_FAILURE otherwise. 88 | */ 89 | int main(int argc, char **argv) 90 | { 91 | if (argc < 2) { 92 | printf("Usage: %s \n", argv[0]); 93 | return EXIT_FAILURE; 94 | } 95 | 96 | const char *audio_file = argv[1]; 97 | test_duckwave_init_file_decoder(audio_file); 98 | return EXIT_SUCCESS; 99 | } 100 | -------------------------------------------------------------------------------- /test/test_playback_callback.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #define MINIAUDIO_IMPLEMENTATION 5 | #include "../include/duckwave.h" 6 | #include "../include/miniaudio.h" 7 | 8 | // clang test.f -lpthread -lm 9 | 10 | /** 11 | * This is the default playback callback function for miniaudio, which is called 12 | * periodically to provide audio data for playback. 13 | * 14 | * @param pDevice The audio device that is calling this callback. 15 | * @param pOutput Pointer to the buffer where audio data should be written. 16 | * @param _pInput Pointer to the buffer containing input audio data (unused 17 | * here). 18 | * @param frameCount The number of audio frames to be processed. 19 | */ 20 | void duckwave_default_playback_callback(ma_device *pDevice, void *pOutput, 21 | const void *_pInput, 22 | unsigned int frameCount) 23 | { 24 | ma_decoder *pDecoder = pDevice->pUserData; 25 | if (pDecoder == NULL) { 26 | printf("Decoder is NULL\n"); 27 | return; 28 | } 29 | 30 | ma_uint64 framesRead; 31 | ma_result result = 32 | ma_decoder_read_pcm_frames(pDecoder, pOutput, frameCount, &framesRead); 33 | 34 | if (result != MA_SUCCESS) { 35 | printf("Failed to read PCM frames: %s\n", ma_result_description(result)); 36 | } else { 37 | printf("Frames read: %llu / %u\n", framesRead, frameCount); 38 | } 39 | 40 | if (framesRead < frameCount) { 41 | // Zero out the rest of the buffer to avoid noise 42 | memset((unsigned char *)pOutput + 43 | framesRead * ma_get_bytes_per_frame(pDevice->playback.format, 44 | pDevice->playback.channels), 45 | 0, 46 | (frameCount - framesRead) * 47 | ma_get_bytes_per_frame(pDevice->playback.format, 48 | pDevice->playback.channels)); 49 | } 50 | 51 | (void)_pInput; 52 | } 53 | 54 | /** 55 | * This function initializes the audio decoder and playback device, 56 | * reads some audio data for debugging, and starts the playback device 57 | * to play the audio. 58 | * 59 | * @param audio_file The path to the audio file to be played. 60 | */ 61 | void play_audio(const char *audio_file) 62 | { 63 | ma_result result; 64 | ma_decoder_config config; 65 | ma_decoder decoder; 66 | ma_device_config device_config; 67 | ma_device device; 68 | 69 | // Configure the decoder explicitly for ma_format_u8 70 | config = ma_decoder_config_init(ma_format_u8, 2, 48000); 71 | 72 | // Initialize the decoder with a provided audio file 73 | result = ma_decoder_init_file(audio_file, &config, &decoder); 74 | if (result != MA_SUCCESS) { 75 | printf("Failed to initialize decoder: %s\n", ma_result_description(result)); 76 | return; 77 | } 78 | 79 | printf("Decoder initialized successfully\n"); 80 | printf("Output format: %d\n", decoder.outputFormat); 81 | printf("Output channels: %d\n", decoder.outputChannels); 82 | printf("Output sample rate: %d\n", decoder.outputSampleRate); 83 | 84 | // Read some audio data for debugging 85 | ma_uint64 framesRead; 86 | ma_uint8 buffer[4096]; 87 | result = ma_decoder_read_pcm_frames( 88 | &decoder, buffer, 89 | sizeof(buffer) / 90 | ma_get_bytes_per_frame(decoder.outputFormat, decoder.outputChannels), 91 | &framesRead); 92 | if (result == MA_SUCCESS && framesRead > 0) { 93 | printf("Frames read: %llu\n", framesRead); 94 | 95 | // Check if the data is all zeros or not 96 | int all_zeros = 1; 97 | for (size_t i = 0; 98 | i < framesRead * ma_get_bytes_per_frame(decoder.outputFormat, 99 | decoder.outputChannels); 100 | ++i) { 101 | if (buffer[i] != 0) { 102 | all_zeros = 0; 103 | break; 104 | } 105 | } 106 | 107 | if (all_zeros) { 108 | printf("All bytes are zero\n"); 109 | } else { 110 | printf("Some bytes are non-zero\n"); 111 | } 112 | 113 | // Print the first bytes of audio data 114 | printf( 115 | "First bytes of audio data: %02x %02x %02x %02x %02x %02x %02x " 116 | "%02x...\n", 117 | buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], 118 | buffer[6], buffer[7]); 119 | } 120 | 121 | // Configure the audio device 122 | device_config = ma_device_config_init(ma_device_type_playback); 123 | device_config.playback.format = decoder.outputFormat; 124 | device_config.playback.channels = decoder.outputChannels; 125 | device_config.sampleRate = decoder.outputSampleRate; 126 | device_config.dataCallback = duckwave_default_playback_callback; 127 | device_config.pUserData = &decoder; 128 | 129 | // Initialize the audio device 130 | result = ma_device_init(NULL, &device_config, &device); 131 | if (result != MA_SUCCESS) { 132 | printf("Failed to initialize device: %s\n", ma_result_description(result)); 133 | ma_decoder_uninit(&decoder); 134 | return; 135 | } 136 | 137 | printf("Audio device initialized successfully\n"); 138 | 139 | // Start the audio device to play the audio 140 | result = ma_device_start(&device); 141 | if (result != MA_SUCCESS) { 142 | printf("Failed to start device: %s\n", ma_result_description(result)); 143 | ma_device_uninit(&device); 144 | ma_decoder_uninit(&decoder); 145 | return; 146 | } 147 | 148 | printf("Playing audio... Press Enter to stop.\n"); 149 | getchar(); // Wait for the user to press Enter to stop playback 150 | 151 | // Clean up resources 152 | ma_device_uninit(&device); 153 | ma_decoder_uninit(&decoder); 154 | } 155 | 156 | /** 157 | * The main function that initializes the audio playback. 158 | * 159 | * @param argc The number of command-line arguments. 160 | * @param argv The array of command-line arguments. 161 | * @return EXIT_SUCCESS on successful execution, EXIT_FAILURE otherwise. 162 | */ 163 | int main(int argc, char **argv) 164 | { 165 | if (argc < 2) { 166 | printf("Usage: %s \n", argv[0]); 167 | return EXIT_FAILURE; 168 | } 169 | 170 | const char *audio_file = argv[1]; 171 | play_audio(audio_file); 172 | return EXIT_SUCCESS; 173 | } 174 | -------------------------------------------------------------------------------- /test/test_start_playsound_thread.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include // For access function 5 | #define MINIAUDIO_IMPLEMENTATION 6 | #include "../include/duckwave.h" 7 | #include "../include/miniaudio.h" 8 | 9 | // clang test.f -lpthread -lm 10 | 11 | /** 12 | * This function initializes the file decoder using a given file path. 13 | * 14 | * @param dw_sdata Pointer to the DuckWaveSoundData structure which holds the 15 | * decoder and device configuration. 16 | * @param file Path to the audio file to be decoded. 17 | */ 18 | void duckwave_init_file_decoder(DuckWaveSoundData *dw_sdata, char *file) 19 | { 20 | ma_result result; 21 | 22 | result = ma_decoder_init_file(file, NULL, &dw_sdata->decoder); 23 | 24 | if (result != MA_SUCCESS) { 25 | printf("\nCould not decode current %s file.\n", file); 26 | printf("%s.\n\n", ma_result_description(result)); 27 | 28 | exit(EXIT_FAILURE); 29 | } 30 | } 31 | 32 | /** 33 | * This function initializes the playback device configuration based on the 34 | * decoder's output format, channels, and sample rate. 35 | * 36 | * @param dw_sdata Pointer to the DuckWaveSoundData structure which holds the 37 | * decoder and device configuration. 38 | */ 39 | void duckwave_init_device_playback(DuckWaveSoundData *dw_sdata) 40 | { 41 | ma_decoder *decoder = &dw_sdata->decoder; 42 | ma_device_config *device_config = &dw_sdata->device_config; 43 | 44 | *device_config = ma_device_config_init(ma_device_type_playback); 45 | 46 | device_config->playback.format = decoder->outputFormat; 47 | device_config->playback.channels = decoder->outputChannels; 48 | device_config->sampleRate = decoder->outputSampleRate; 49 | device_config->dataCallback = duckwave_default_playback_callback; 50 | device_config->pUserData = decoder; 51 | } 52 | 53 | /** 54 | * This is the default playback callback function for miniaudio, which is called 55 | * periodically to provide audio data for playback. 56 | * 57 | * @param pDevice The audio device that is calling this callback. 58 | * @param pOutput Pointer to the buffer where audio data should be written. 59 | * @param _pInput Pointer to the buffer containing input audio data (unused 60 | * here). 61 | * @param frameCount The number of audio frames to be processed. 62 | */ 63 | void duckwave_default_playback_callback(ma_device *pDevice, void *pOutput, 64 | const void *_pInput, 65 | unsigned int frameCount) 66 | { 67 | ma_decoder *pDecoder = pDevice->pUserData; 68 | if (pDecoder == NULL) { 69 | printf("Decoder is NULL\n"); 70 | return; 71 | } 72 | 73 | ma_uint64 framesRead; 74 | ma_result result = 75 | ma_decoder_read_pcm_frames(pDecoder, pOutput, frameCount, &framesRead); 76 | 77 | if (result != MA_SUCCESS) { 78 | printf("Failed to read PCM frames: %s\n", ma_result_description(result)); 79 | } else { 80 | printf("Frames read: %llu / %u\n", framesRead, frameCount); 81 | } 82 | 83 | if (framesRead < frameCount) { 84 | // Zero out the rest of the buffer to avoid noise 85 | memset((unsigned char *)pOutput + 86 | framesRead * ma_get_bytes_per_frame(pDevice->playback.format, 87 | pDevice->playback.channels), 88 | 0, 89 | (frameCount - framesRead) * 90 | ma_get_bytes_per_frame(pDevice->playback.format, 91 | pDevice->playback.channels)); 92 | } 93 | 94 | (void)_pInput; 95 | } 96 | 97 | /** 98 | * This function initializes and starts the playback device. 99 | * 100 | * @param dw_sdata Pointer to the DuckWaveSoundData structure which holds the 101 | * decoder and device configuration. 102 | */ 103 | void duckwave_start_playsound_thread(DuckWaveSoundData *dw_sdata) 104 | { 105 | ma_result result; 106 | 107 | ma_decoder *decoder = &dw_sdata->decoder; 108 | ma_device *device = &dw_sdata->device; 109 | ma_device_config *device_config = &dw_sdata->device_config; 110 | 111 | result = ma_device_init(NULL, device_config, device); 112 | 113 | if (result != MA_SUCCESS) { 114 | printf("\nCannot initialize device.\n"); 115 | printf("%s.\n\n", ma_result_description(result)); 116 | 117 | ma_decoder_uninit(decoder); 118 | 119 | exit(EXIT_FAILURE); 120 | } 121 | 122 | result = ma_device_start(device); 123 | 124 | if (result != MA_SUCCESS) { 125 | printf("\nCannot start device.\n"); 126 | printf("%s.\n\n", ma_result_description(result)); 127 | 128 | ma_device_uninit(device); 129 | ma_decoder_uninit(decoder); 130 | 131 | exit(EXIT_FAILURE); 132 | } 133 | } 134 | 135 | /** 136 | * Function to test the duckwave_start_playsound_thread function. 137 | * 138 | * @param file The path to the audio file to be tested. 139 | */ 140 | void test_duckwave_start_playsound_thread(const char *file) 141 | { 142 | DuckWaveSoundData dw_sdata; 143 | 144 | // Initialize the decoder with the provided file 145 | printf("Testing with file: %s\n", file); 146 | if (access(file, F_OK) != -1) { 147 | duckwave_init_file_decoder(&dw_sdata, (char *)file); 148 | printf("Decoder initialized successfully for file: %s\n", file); 149 | 150 | // Initialize the device playback configuration 151 | duckwave_init_device_playback(&dw_sdata); 152 | printf("Device playback configuration initialized.\n"); 153 | 154 | // Start the playback device 155 | duckwave_start_playsound_thread(&dw_sdata); 156 | printf("Playback device started successfully.\n"); 157 | 158 | // Wait for a short duration to simulate playback 159 | printf("Simulating playback for 5 seconds...\n"); 160 | sleep(5); 161 | 162 | // Cleanup 163 | ma_device_uninit(&dw_sdata.device); 164 | ma_decoder_uninit(&dw_sdata.decoder); 165 | printf("Resources cleaned up successfully.\n"); 166 | } else { 167 | printf("File %s does not exist.\n", file); 168 | } 169 | } 170 | 171 | int main(int argc, char **argv) 172 | { 173 | if (argc < 2) { 174 | printf("Usage: %s \n", argv[0]); 175 | return EXIT_FAILURE; 176 | } 177 | 178 | const char *audio_file = argv[1]; 179 | test_duckwave_start_playsound_thread(audio_file); 180 | return EXIT_SUCCESS; 181 | } 182 | -------------------------------------------------------------------------------- /test/test_timestamp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // clang test_timestamp.c -o test_timestamp 5 | 6 | /** 7 | * This function generates a timestamp string in the format hh:mm:ss or mm:ss 8 | * from a float representing seconds. 9 | * 10 | * @param secs The total time in seconds. 11 | * @param res The resulting formatted timestamp string. It should be large 12 | * enough to hold the resulting string (128 bytes is assumed here). 13 | */ 14 | void generate_timestamp(float secs, char *res) 15 | { 16 | int hours = (int)secs / 3600; 17 | secs = (int)secs % 3600; 18 | int mins = (int)secs / 60; 19 | secs = (int)secs % 60; 20 | 21 | if (hours > 0) 22 | snprintf(res, 128, "%d:%02d:%02.0f", hours, mins, secs); // Format hh:mm:ss 23 | else 24 | snprintf(res, 128, "%02d:%02.0f", mins, secs); // Format mm:ss 25 | } 26 | 27 | /** 28 | * Tests the generate_timestamp function with given input and expected output. 29 | * 30 | * @param input_secs The input time in seconds. 31 | * @param expected_output The expected formatted timestamp string. 32 | */ 33 | void test_generate_timestamp(float input_secs, const char *expected_output) 34 | { 35 | char result[128]; 36 | generate_timestamp(input_secs, result); 37 | if (strcmp(result, expected_output) == 0) { 38 | printf("Test passed for input %.2f seconds. Output: %s\n", input_secs, 39 | result); 40 | } else { 41 | printf("Test failed for input %.2f seconds. Expected: %s, but got: %s\n", 42 | input_secs, expected_output, result); 43 | } 44 | } 45 | 46 | int main() 47 | { 48 | // Test cases 49 | test_generate_timestamp(3661.0f, "1:01:01"); 50 | test_generate_timestamp(61.0f, "01:01"); 51 | test_generate_timestamp(59.0f, "00:59"); 52 | test_generate_timestamp(3600.0f, "1:00:00"); 53 | test_generate_timestamp(0.0f, "00:00"); 54 | test_generate_timestamp(125.0f, "02:05"); 55 | test_generate_timestamp(3601.0f, "1:00:01"); 56 | 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /xmake.lua: -------------------------------------------------------------------------------- 1 | set_xmakever("2.8.9") 2 | set_version("0.1.0", {build = "%Y%m%d%H%M"}) 3 | 4 | set_allowedmodes("debug", "release") 5 | set_defaultmode("debug") 6 | add_rules("mode.debug", "mode.release") 7 | 8 | toolchain("dwtc") -- recommended toolchain 9 | set_kind("standalone") 10 | 11 | set_toolset("cc", "clang") 12 | set_toolset("cxx", "clang++", "clang") 13 | set_toolset("ld", "clang", {force = true}) -- ensure mold is used as linker 14 | set_toolset("sh", "clang++", "clang") 15 | set_toolset("ar", "llvm-ar") 16 | toolchain_end() 17 | 18 | target("duckwave") 19 | set_kind("binary") 20 | add_includedirs("include") 21 | add_files("src/main.c", "src/duckwave.c","src/animation.c") 22 | 23 | if is_mode("release") then 24 | set_optimize("fastest") 25 | set_symbols("hidden") 26 | elseif is_mode("debug") then 27 | set_optimize("none") 28 | set_symbols("debug") 29 | end 30 | 31 | set_targetdir("bin") 32 | set_languages("c17") 33 | add_ldflags("-fuse-ld=mold", {force = true}) -- add mold linker flag 34 | -- add_links("avformat", "avcodec", "ao", "m") 35 | add_links("ncursesw", "m") 36 | target_end() 37 | 38 | 39 | 40 | 41 | --------------------------------------------------------------------------------