shithub: dumb

Download patch

ref: e41744273f75f23a5dc9d1d705bb517fff51b7b1
parent: 0048a15cd919c3cc83b2e87468385fd9c3137d11
parent: 1f29f914515697d35d5fff400f9f396bf5b4ffaa
author: Christopher Snowhill <[email protected]>
date: Mon Dec 14 15:30:05 EST 2015

Merge pull request #20 from katajakasa/ttv-dumbplay

Add dumbplay example

--- a/dumb/cmake/CMakeLists.txt
+++ b/dumb/cmake/CMakeLists.txt
@@ -16,6 +16,7 @@
 
 if(BUILD_EXAMPLES)
     find_package(argtable2)
+    find_package(SDL2)
 else()
     message(STATUS "Not building examples")
 endif()
@@ -119,9 +120,17 @@
 
 if(BUILD_EXAMPLES)
     add_executable(dumbout ../examples/dumbout.c)
+    add_executable(dumbplay ../examples/dumbplay.c)
+
+    if(MINGW)
+        target_link_libraries(dumbplay mingw32)
+    endif()
+
     target_link_libraries(dumbout ${ARGTABLE2_LIBRARY} m dumb)
-    include_directories(${ARGTABLE2_INCLUDE_DIR} "../examples/")
-    set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} "dumbout")
+    target_link_libraries(dumbplay ${ARGTABLE2_LIBRARY} ${SDL2_LIBRARY} dumb)
+
+    include_directories(${ARGTABLE2_INCLUDE_DIR} ${SDL2_INCLUDE_DIR} "../examples/")
+    set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} "dumbout" "dumbplay")
 endif()
 
 # Make sure the dylib install name path is set on OSX so you can include dumb in app bundles
--- /dev/null
+++ b/dumb/cmake/cmake-scripts/FindSDL2.cmake
@@ -1,0 +1,42 @@
+
+FIND_PACKAGE(Threads)
+
+SET(SDL2_SEARCH_PATHS
+    /usr/local/
+    /usr/
+)
+
+FIND_PATH(SDL2_INCLUDE_DIR SDL2/SDL.h
+    HINTS
+    PATH_SUFFIXES include
+    PATHS ${SDL2_SEARCH_PATHS}
+)
+
+FIND_LIBRARY(SDL2_LIBRARY
+    NAMES SDL2
+    HINTS
+    PATH_SUFFIXES lib64 lib
+    PATHS ${SDL2_SEARCH_PATHS}
+)
+
+IF(MINGW)
+    FIND_LIBRARY(SDL2MAIN_LIBRARY
+        NAMES SDL2main
+        HINTS
+        PATH_SUFFIXES lib64 lib
+        PATHS ${SDL2_SEARCH_PATHS}
+    )
+ELSE()
+    SET(SDL2MAIN_LIBRARY "")
+ENDIF()
+
+IF(SDL2_INCLUDE_DIR AND SDL2_LIBRARY)
+   SET(SDL2_FOUND TRUE)
+ENDIF (SDL2_INCLUDE_DIR AND SDL2_LIBRARY)
+
+IF(SDL2_FOUND)
+    SET(SDL2_LIBRARY ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY} ${CMAKE_THREAD_LIBS_INIT})
+    MESSAGE(STATUS "Found SDL2: ${SDL2_LIBRARY}")
+ELSE(SDL2_FOUND)
+    MESSAGE(WARNING "Could not find SDL2")
+ENDIF(SDL2_FOUND)
\ No newline at end of file
--- a/dumb/cmake/readme.txt
+++ b/dumb/cmake/readme.txt
@@ -17,7 +17,7 @@
 -----
 
 1. Create a new temporary build directory and cd into it
-2. Run libdumb cmake file with cmake (eg. `cmake -DCMAKE_INSTALL_PREFIX=/install/dir -DBUILD_SHARED_LIBS:BOOL=OFF -DCMAKE_BUILD_TYPE=Release path/to/dumb/cmake/dir`).
+2. Run libdumb cmake file with cmake (eg. `cmake -DCMAKE_INSTALL_PREFIX=/install/dir -DBUILD_SHARED_LIBS:BOOL=ON -DCMAKE_BUILD_TYPE=Release path/to/dumb/cmake/dir`).
 3. Run make (eg. just `make` or `mingw32-make` or something).
 4. If needed, run make install.
 
@@ -26,6 +26,6 @@
 
 * CMAKE_INSTALL_PREFIX sets the installation path prefix
 * CMAKE_BUILD_TYPE sets the build type (eg. Release, Debug, RelWithDebInfo, MinSizeRel). Debug libraries will be named libdumbd, release libraries libdumb.
-* BUILD_SHARED_LIBS selects whether cmake should build dynamic or static library (On=shared, OFF=static)
-* BUILD_EXAMPLES selects example binaries. Note that example binaries require libargtable2 library. Enabled by default.
+* BUILD_SHARED_LIBS selects whether cmake should build dynamic or static library. On by default. (On=shared, OFF=static)
+* BUILD_EXAMPLES selects example binaries. Note that example binaries require argtable2 and SDL2 libraries. On by default.
 * You may also need to tell cmake what kind of makefiles to create with the "-G" flag. Eg. for MSYS one would say something like `cmake -G "MSYS Makefiles" .`.
--- /dev/null
+++ b/dumb/examples/dumbplay.c
@@ -1,0 +1,284 @@
+#include <argtable2.h>
+#include <dumb.h>
+#include <stdint.h>
+#include <string.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <SDL2/SDL.h>
+
+// How many samples we should handle per callback call
+#define SAMPLES 8192
+
+// How many chars wide the progress bar is
+#define PROGRESSBAR_LENGTH 25
+
+typedef struct {
+    // Library contexts
+    DUH_SIGRENDERER *renderer;
+    DUH *src;
+    SDL_AudioDeviceID dev;
+
+    // Runtime vars
+    float delta;
+    int spos;  // Samples read
+    int ssize;  // Total samples available
+    int sbytes;  // bytes per sample
+    bool ended;
+
+    // Config switches
+    int bits;
+    int freq;
+    int quality;
+    int n_channels;
+    bool no_progress;
+    float volume;
+    const char *input;
+} streamer_t;
+
+// Set to true if SIGTERM or SIGINT is caught
+static bool stop_signal = false;
+
+// Simple signal handler function
+static void sig_fn(int signo) {
+    stop_signal = true;
+}
+
+// This is called from SDL2, and should read data from a source and hand it over to SDL2 via the stream buffer.
+void stream_audio(void* userdata, Uint8* stream, int len) {
+    streamer_t *streamer = (streamer_t*)userdata;
+
+    // Read samples from libdumb save them to the SDL buffer. Note that we are reading SAMPLES, not bytes!
+    int r_samples = len / streamer->sbytes;
+    int got = duh_render(streamer->renderer, streamer->bits, 0, streamer->volume, streamer->delta, r_samples, stream);
+    if(got == 0) {
+        streamer->ended = true;
+    }
+
+    // Get current position from libdumb for the playback display. If we get position that is 0, it probably
+    // means that the song ended and duh_sigrenderer_get_position points to the start of the file.
+    streamer->spos = duh_sigrenderer_get_position(streamer->renderer);
+    if(streamer->spos == 0) {
+        streamer->spos = streamer->ssize;
+    }
+}
+
+// Simply formats timestamp to string
+void format_ms(int ticks) {
+    int total_seconds = ticks / 1000;
+    int hours = 0;
+    int minutes = 0;
+    int seconds = 0;
+
+    // Calculate hours, minutes and seconds
+    if(total_seconds > 3600) {
+        hours = total_seconds / 3600;
+        total_seconds = total_seconds % 3600;
+    }
+    if(total_seconds > 60) {
+        minutes = total_seconds / 60;
+        total_seconds = total_seconds % 60;
+    }
+    seconds = total_seconds;
+
+    // If necessary, show hours. Otherwise only minutes and seconds.
+    if(hours > 0) {
+        printf("%02d:%02d:%02d", hours, minutes, seconds);
+    } else {
+        printf("%02d:%02d", minutes, seconds);
+    }
+}
+
+// Shows progressbar and time played
+void show_progress(int width, float progress, int ticks) {
+    int d = progress * width;
+    printf("%3d%% [", (int)(progress*100));
+    for(int x = 0; x < d; x++) {
+        printf("=");
+    }
+    for(int x = 0; x < (width-d); x++) {
+        printf(" ");
+    }
+    printf("] ");
+    format_ms(ticks);
+    printf("\r");
+    fflush(stdout);
+}
+
+int main(int argc, char *argv[]) {
+    int retcode = 1;
+    int nerrors = 0;
+    streamer_t streamer;
+    memset(&streamer, 0, sizeof(streamer_t));
+
+    // Signal handlers
+    signal(SIGINT, sig_fn);
+    signal(SIGTERM, sig_fn);    
+
+    // Initialize SDL2
+    if(SDL_Init(SDL_INIT_AUDIO) != 0) {
+        fprintf(stderr, "%s\n", SDL_GetError());
+        return 1;
+    }
+
+    // Defaults
+    streamer.freq = 44100;
+    streamer.n_channels = 2;
+    streamer.bits = 16;
+    streamer.volume = 1.0f;
+    streamer.quality = DUMB_RQ_CUBIC;
+
+    // commandline argument parser options
+    struct arg_lit *arg_help = arg_lit0("h", "help", "print this help and exits");
+    struct arg_dbl *arg_volume = arg_dbl0("v", "volume", "<volume", "sets the output volume (-8.0 to +8.0, default 1.0)");
+    struct arg_int *arg_samplerate = arg_int0("s", "samplerate", "<freq>", "sets the sampling rate (default 44100)");
+    struct arg_int *arg_quality = arg_int0("r", "quality", "<quality>", "specify the resampling quality to use");
+    struct arg_lit *arg_mono = arg_lit0("m", "mono", "generate mono output instead of stereo");
+    struct arg_lit *arg_eight = arg_lit0("8", "eight", "generate 8-bit instead of 16-bit");
+    struct arg_lit *arg_noprogress = arg_lit0("n", "noprogress", "hide progress bar");
+    struct arg_file *arg_output = arg_file0("o", "output", "<file>", "output file");
+    struct arg_file *arg_input = arg_file1(NULL, NULL, "<file>", "input module file");
+    struct arg_end *arg_fend = arg_end(20);
+    void* argtable[] = {arg_help, arg_input, arg_volume, arg_samplerate, arg_quality, arg_mono, arg_eight, arg_noprogress, arg_fend};
+    const char* progname = "dumbplay";
+
+    // Make sure everything got allocated
+    if(arg_nullcheck(argtable) != 0) {
+        fprintf(stderr, "%s: insufficient memory\n", progname);
+        goto exit_0;
+    }
+
+    // Parse inputs
+    nerrors = arg_parse(argc, argv, argtable);
+
+    // Handle help
+    if(arg_help->count > 0) {
+        fprintf(stderr, "Usage: %s", progname);
+        arg_print_syntax(stderr, argtable, "\n");
+        fprintf(stderr, "\nArguments:\n");
+        arg_print_glossary(stderr, argtable, "%-25s %s\n");
+        goto exit_0;
+    }
+
+    // Handle libargtable errors
+    if(nerrors > 0) {
+        arg_print_errors(stderr, arg_fend, progname);
+        fprintf(stderr, "Try '%s --help' for more information.\n", progname);
+        goto exit_0;
+    }
+
+    // Handle the switch options
+    streamer.input = arg_input->filename[0];
+    if(arg_eight->count > 0) { streamer.bits = 8; }
+    if(arg_mono->count > 0) { streamer.n_channels = 1; }
+    if(arg_noprogress->count > 0) { streamer.no_progress = true; }
+
+    if(arg_volume->count > 0) {
+        streamer.volume = arg_volume->dval[0];
+        if(streamer.volume < -8.0f || streamer.volume > 8.0f) {
+            fprintf(stderr, "Volume must be between -8.0f and 8.0f.\n");
+            goto exit_0;
+        }
+    }
+
+    if(arg_samplerate->count > 0) {
+        streamer.freq = arg_samplerate->ival[0];
+        if(streamer.freq < 1 || streamer.freq > 96000) {
+            fprintf(stderr, "Sampling rate must be between 1 and 96000.\n");
+            goto exit_0;
+        }
+    }
+
+    if(arg_quality->count > 0) {
+        streamer.quality = arg_quality->ival[0];
+        if(streamer.quality < 0 || streamer.quality >= DUMB_RQ_N_LEVELS) {
+            fprintf(stderr, "Quality must be between %d and %d.\n", 0, DUMB_RQ_N_LEVELS-1);
+            goto exit_0;
+        }
+    }
+
+    // Load source file.
+    dumb_register_stdfiles();
+    streamer.src = dumb_load_any(streamer.input, 0, 0);
+    if(!streamer.src) {
+        fprintf(stderr, "Unable to load file %s for playback!\n", streamer.input);
+        goto exit_0;
+    }
+
+    // Set up playback
+    streamer.renderer = duh_start_sigrenderer(streamer.src, 0, streamer.n_channels, 0);
+    streamer.delta = 65536.0f / streamer.freq;
+    streamer.sbytes = (streamer.bits / 8) * streamer.n_channels;
+    streamer.ssize = duh_get_length(streamer.src);
+
+    // Stop producing samples on module end
+    DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(streamer.renderer);
+    dumb_it_set_loop_callback(itsr, &dumb_it_callback_terminate, NULL);
+    dumb_it_set_xm_speed_zero_callback(itsr, &dumb_it_callback_terminate, NULL);
+    dumb_it_set_resampling_quality(itsr, streamer.quality);
+
+    // Set up the SDL2 format we want for playback.
+    SDL_AudioSpec want;
+    SDL_zero(want);
+    want.freq = streamer.freq;
+    want.format = (streamer.bits == 16) ? AUDIO_S16 : AUDIO_S8;
+    want.channels = streamer.n_channels;
+    want.samples = SAMPLES;
+    want.callback = stream_audio;
+    want.userdata = &streamer;
+
+    // Find SDL2 audio device, and request the format we just set up.
+    // SDL2 will tell us what we got in the "have" struct.
+    SDL_AudioSpec have;
+    streamer.dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
+    if(streamer.dev == 0) {
+        fprintf(stderr, "%s\n", SDL_GetError());
+        goto exit_1;
+    }
+
+    // Make sure we got the format we wanted. If not, stop here.
+    if(have.format != want.format) {
+        fprintf(stderr, "Could not get correct playback format.\n");
+        goto exit_2;
+    }
+
+    // Play file
+    SDL_PauseAudioDevice(streamer.dev, 0);
+
+    // Show initial state of the progress bar (if it is enabled)
+    int time_start = SDL_GetTicks();
+    float seek = 0.0f;
+    int ms_played = 0;
+    if(!streamer.no_progress) {
+        show_progress(PROGRESSBAR_LENGTH, seek, ms_played);
+    }
+
+    // Loop while dumb is still giving data. Update progressbar if enabled.
+    while(!stop_signal && !streamer.ended) {
+        if(!streamer.no_progress) {
+            seek = ((float)streamer.spos) / ((float)streamer.ssize);
+            ms_played = SDL_GetTicks() - time_start;
+            show_progress(PROGRESSBAR_LENGTH, seek, ms_played);
+        }
+        SDL_Delay(100);
+    }
+
+    // We made it this far without crashing, so let's just exit with no error :)
+    retcode = 0;
+
+    // Free up resources and exit.
+exit_2:
+    SDL_CloseAudioDevice(streamer.dev);
+
+exit_1:
+    if(streamer.renderer) {
+        duh_end_sigrenderer(streamer.renderer);
+    }
+    if(streamer.src) {
+        unload_duh(streamer.src);
+    }
+
+exit_0:
+    arg_freetable(argtable, sizeof(argtable)/sizeof(argtable[0]));
+    SDL_Quit();
+    return retcode;
+}