#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; sample_t **sig_samples; long sig_samples_size; // 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 caughtstatic bool stop_signal = false; // Simple signal handler functionstatic 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_int(streamer->renderer, &streamer->sig_samples, &streamer->sig_samples_size, 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 stringvoid 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 playedvoid 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. if (streamer.sig_samples) { destroy_sample_buffer(streamer.sig_samples); } 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;}
#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;
sample_t **sig_samples;
long sig_samples_size;
// 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_int(streamer->renderer, &streamer->sig_samples,
&streamer->sig_samples_size, 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.
if (streamer.sig_samples) {
destroy_sample_buffer(streamer.sig_samples);
}
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;
}