├── .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 |
10 |
11 |
12 | 
13 | 
14 | 
15 | [](https://app.codacy.com/gh/alvarorichard/DuckWave/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
16 | [](https://github.com/alvarorichard/DuckWave/actions)
17 | 
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 |
94 |
95 |
--------------------------------------------------------------------------------
/README_pt-BR.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Рortuguês |
4 | English
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | 
14 | 
15 | 
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 |
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 |
--------------------------------------------------------------------------------