1 #include <argtable2.h>
2 #include <dumb.h>
3 #include <stdint.h>
4 #include <string.h>
5 #include <stdbool.h>
6 #include <math.h>
7 
8 static const int endian_test = 1;
9 #define is_bigendian() ((*(char*)&endian_test) == 0)
10 
11 enum ENDIANNESS {
12     DUMB_LITTLE_ENDIAN = 0,
13     DUMB_BIG_ENDIAN
14 };
15 
16 typedef struct {
17     DUH_SIGRENDERER *renderer;
18     DUH *src;
19     FILE *dst;
20     float delta;
21     int bufsize;
22     bool is_stdout;
23 } streamer_t;
24 
25 typedef struct {
26     int bits;
27     int endianness;
28     int is_unsigned;
29     int freq;
30     int quality;
31     int n_channels;
32     float volume;
33     float delay;
34     const char *input;
35     char *output;
36 } settings_t;
37 
38 
main(int argc,char * argv[])39 int main(int argc, char *argv[]) {
40     int retcode = 1;
41     int nerrors = 0;
42     streamer_t streamer;
43     settings_t settings;
44     memset(&streamer, 0, sizeof(streamer_t));
45     memset(&settings, 0, sizeof(settings_t));
46 
47     // Defaults
48     settings.freq = 44100;
49     settings.n_channels = 2;
50     settings.bits = 16;
51     settings.endianness = DUMB_LITTLE_ENDIAN;
52     settings.is_unsigned = false;
53     settings.volume = 1.0f;
54     settings.delay = 0.0f;
55     settings.quality = DUMB_RQ_CUBIC;
56 
57     // commandline argument parser options
58     struct arg_lit *arg_help = arg_lit0("h", "help", "print this help and exits");
59     struct arg_dbl *arg_delay = arg_dbl0("d", "delay", "<delay>", "sets the initial delay in seconds (0.0 to 64.0, default 0.0)");
60     struct arg_dbl *arg_volume = arg_dbl0("v", "volume", "<volume", "sets the output volume (-8.0 to +8.0, default 1.0)");
61     struct arg_int *arg_samplerate = arg_int0("s", "samplerate", "<freq>", "sets the sampling rate (default 44100)");
62     struct arg_int *arg_quality = arg_int0("r", "quality", "<quality>", "specify the resampling quality to use");
63     struct arg_lit *arg_mono = arg_lit0("m", "mono", "generate mono output instead of stereo");
64     struct arg_lit *arg_bigendian = arg_lit0("b", "bigendian", "generate big-endian data instead of little-endian");
65     struct arg_lit *arg_eight = arg_lit0("8", "eight", "generate 8-bit instead of 16-bit");
66     struct arg_lit *arg_unsigned = arg_lit0("u", "unsigned", "generate unsigned output instead of signed");
67     struct arg_file *arg_output = arg_file0("o", "output", "<file>", "output file");
68     struct arg_file *arg_input = arg_file1(NULL, NULL, "<file>", "input module file");
69     struct arg_end *arg_fend = arg_end(20);
70     void* argtable[] = {arg_help, arg_input, arg_output, arg_delay, arg_volume, arg_samplerate, arg_quality,
71                         arg_mono, arg_bigendian, arg_eight, arg_unsigned, arg_fend};
72     const char* progname = "dumbout";
73 
74     // Make sure everything got allocated
75     if(arg_nullcheck(argtable) != 0) {
76         fprintf(stderr, "%s: insufficient memory\n", progname);
77         goto exit_0;
78     }
79 
80     // Parse inputs
81     nerrors = arg_parse(argc, argv, argtable);
82 
83     // Handle help
84     if(arg_help->count > 0) {
85         fprintf(stderr, "Usage: %s", progname);
86         arg_print_syntax(stderr, argtable, "\n");
87         fprintf(stderr, "\nArguments:\n");
88         arg_print_glossary(stderr, argtable, "%-25s %s\n");
89         goto exit_0;
90     }
91 
92     // Handle errors
93     if(nerrors > 0) {
94         arg_print_errors(stderr, arg_fend, progname);
95         fprintf(stderr, "Try '%s --help' for more information.\n", progname);
96         goto exit_0;
97     }
98 
99     // Get input and output filenames
100     settings.input = arg_input->filename[0];
101     if(arg_output->count > 0) {
102         settings.output = malloc(strlen(arg_output->filename[0])+1);
103         strcpy(settings.output, arg_output->filename[0]);
104     } else {
105         settings.output = malloc(strlen(arg_output->basename[0])+5);
106         sprintf(settings.output, "%s%s", arg_output->basename[0], ".pcm");
107     }
108 
109     // Handle the switch options
110     if(arg_bigendian->count > 0) { settings.endianness = DUMB_BIG_ENDIAN; }
111     if(arg_eight->count > 0) { settings.bits = 8; }
112     if(arg_unsigned->count > 0) { settings.is_unsigned = true; }
113     if(arg_mono->count > 0) { settings.n_channels = 1; }
114 
115     if(arg_delay->count > 0) {
116         settings.delay = arg_delay->dval[0];
117         if(settings.delay < 0.0f || settings.delay >= 64.0f) {
118             fprintf(stderr, "Initial delay must be between 0.0f and 64.0f.\n");
119             goto exit_0;
120         }
121     }
122 
123     if(arg_volume->count > 0) {
124         settings.volume = arg_volume->dval[0];
125         if(settings.volume < -8.0f || settings.volume > 8.0f) {
126             fprintf(stderr, "Volume must be between -8.0f and 8.0f.\n");
127             goto exit_0;
128         }
129     }
130 
131     if(arg_samplerate->count > 0) {
132         settings.freq = arg_samplerate->ival[0];
133         if(settings.freq < 1 || settings.freq > 96000) {
134             fprintf(stderr, "Sampling rate must be between 1 and 96000.\n");
135             goto exit_0;
136         }
137     }
138 
139     if(arg_quality->count > 0) {
140         settings.quality = arg_quality->ival[0];
141         if(settings.quality < 0 || settings.quality >= DUMB_RQ_N_LEVELS) {
142             fprintf(stderr, "Quality must be between %d and %d.\n", 0, DUMB_RQ_N_LEVELS-1);
143             goto exit_0;
144         }
145     }
146 
147     // dumb settings stuff
148     dumb_register_stdfiles();
149 
150     // Load source
151     streamer.src = dumb_load_any(settings.input, 0, 0);
152     if(!streamer.src) {
153         fprintf(stderr, "Unable to load file %s for playback!\n", settings.input);
154         goto exit_0;
155     }
156 
157     // Set up playback
158     streamer.renderer = duh_start_sigrenderer(streamer.src, 0, settings.n_channels, 0);
159     streamer.delta = 65536.0f / settings.freq;
160     streamer.bufsize = 4096 * (settings.bits / 8) * settings.n_channels;
161 
162     // Stop producing samples on module end
163     DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(streamer.renderer);
164     dumb_it_set_loop_callback(itsr, &dumb_it_callback_terminate, NULL);
165     dumb_it_set_xm_speed_zero_callback(itsr, &dumb_it_callback_terminate, NULL);
166     dumb_it_set_resampling_quality(itsr, settings.quality);
167 
168     // Open output
169     if(strcmp(settings.output, "-") == 0) {
170         streamer.dst = stdout;
171         streamer.is_stdout = true;
172     } else {
173         streamer.dst = fopen(settings.output, "wb");
174         streamer.is_stdout = false;
175         if(!streamer.dst) {
176             fprintf(stderr, "Could not open output file %s!", settings.output);
177             goto exit_1;
178         }
179     }
180 
181     bool run = true;
182     char *buffer = malloc(streamer.bufsize);
183     int read_samples;
184     int read_bytes;
185 
186     // If output endianness is different than machine endianness, and output is 16 bits, reorder bytes.
187     int switch_endianness = ((is_bigendian() && settings.endianness == DUMB_LITTLE_ENDIAN) ||
188                             (!is_bigendian() && settings.endianness == DUMB_BIG_ENDIAN));
189 
190     // Write the initial delay to the file if one was requested.
191     long d = ((long)floor(settings.delay * settings.freq + 0.5f)) * settings.n_channels * (settings.bits / 8);
192     if(d) {
193         // Fill the buffer with silence. Remember to take into account endianness
194         if(settings.is_unsigned) {
195             if(settings.bits == 16) {
196                 if(settings.endianness == DUMB_BIG_ENDIAN) {
197                     // Unsigned 16bits big endian
198                     for(int i = 0; i < streamer.bufsize; i += 2) {
199                         buffer[i  ] = (char)0x80;
200                         buffer[i+1] = (char)0x00;
201                     }
202                 } else {
203                     // Unsigned 16bits little endian
204                     for(int i = 0; i < streamer.bufsize; i += 2) {
205                         buffer[i  ] = (char)0x00;
206                         buffer[i+1] = (char)0x80;
207                     }
208                 }
209             } else {
210                 // Unsigned 8 bits
211                 memset(buffer, 0x80, streamer.bufsize);
212             }
213         } else {
214             // Signed
215             memset(buffer, 0, streamer.bufsize);
216         }
217 
218         while(d >= streamer.bufsize) {
219             fwrite(buffer, 1, streamer.bufsize, streamer.dst);
220             d -= streamer.bufsize;
221         }
222         if(d) {
223             fwrite(buffer, 1, d, streamer.dst);
224         }
225     }
226 
227     // Loop until we have nothing to loop through. Dumb will stop giving out bytes when the file is done.
228     while(run) {
229         read_samples = duh_render(streamer.renderer,
230                                   settings.bits,
231                                   (int)settings.is_unsigned,
232                                   settings.volume,
233                                   streamer.delta,
234                                   4096, buffer);
235         read_bytes = read_samples * (settings.bits / 8) * settings.n_channels;
236 
237         // switch endianness if required
238         if(switch_endianness && settings.bits == 16) {
239             char tmp;
240             for(int i = 0; i < read_bytes / 2; i++) {
241                 tmp = buffer[i*2+0];
242                 buffer[i*2+0] = buffer[i*2+1];
243                 buffer[i*2+1] = tmp;
244             }
245         }
246 
247         // Write to output stream and flush if it happens to be stdout
248         fwrite(buffer, 1, read_bytes, streamer.dst);
249         if(streamer.is_stdout) {
250             fflush(streamer.dst);
251         }
252         run = (read_samples > 0);
253     }
254     free(buffer);
255 
256     // We made it this far without crashing, so let's just exit with no error :)
257     retcode = 0;
258 
259     if(!streamer.is_stdout && streamer.dst) {
260         fclose(streamer.dst);
261     }
262 
263 exit_1:
264     if(streamer.renderer) {
265         duh_end_sigrenderer(streamer.renderer);
266     }
267     if(streamer.src) {
268         unload_duh(streamer.src);
269     }
270 
271 exit_0:
272     free(settings.output);
273     arg_freetable(argtable, sizeof(argtable)/sizeof(argtable[0]));
274     return retcode;
275 }
276