1 /** @file
2     Various utility functions handling file formats.
3 
4     Copyright (C) 2018 Christian Zuckschwerdt
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 */
11 
12 #include <string.h>
13 #include <stdlib.h>
14 #ifdef _MSC_VER
15 #ifndef strncasecmp // Microsoft Visual Studio
16 #define strncasecmp  _strnicmp
17 #endif
18 #else
19 #include <strings.h>
20 #endif
21 //#include "optparse.h"
22 #include "fileformat.h"
23 
24 #ifdef _WIN32
25 #define PATH_SEPARATOR '\\'
26 #else
27 #define PATH_SEPARATOR '/'
28 #endif
29 
file_basename(char const * path)30 char const *file_basename(char const *path)
31 {
32     char const *p = strrchr(path, PATH_SEPARATOR);
33     if (p)
34         return p + 1;
35     else
36         return path;
37 }
38 
file_info_clear(file_info_t * info)39 void file_info_clear(file_info_t *info)
40 {
41     if (info) {
42         *info = (file_info_t const){0};
43     }
44 }
45 
file_info_check_read(file_info_t * info)46 void file_info_check_read(file_info_t *info)
47 {
48     if (info->format != CU8_IQ
49             && info->format != CS8_IQ
50             && info->format != CS16_IQ
51             && info->format != CF32_IQ
52             && info->format != S16_AM
53             && info->format != PULSE_OOK) {
54         fprintf(stderr, "File type not supported as input (%s).\n", info->spec);
55         exit(1);
56     }
57 }
58 
file_info_check_write(file_info_t * info)59 void file_info_check_write(file_info_t *info)
60 {
61     if (info->format != CU8_IQ
62             && info->format != CS8_IQ
63             && info->format != S16_AM
64             && info->format != S16_FM
65             && info->format != CS16_IQ
66             && info->format != CF32_IQ
67             && info->format != F32_AM
68             && info->format != F32_FM
69             && info->format != F32_I
70             && info->format != F32_Q
71             && info->format != U8_LOGIC
72             && info->format != VCD_LOGIC) {
73         fprintf(stderr, "File type not supported as output (%s).\n", info->spec);
74         exit(1);
75     }
76 }
77 
file_info_string(file_info_t * info)78 char const *file_info_string(file_info_t *info)
79 {
80     switch (info->format) {
81     case CU8_IQ:    return "CU8 IQ (2ch uint8)";
82     case S16_AM:    return "S16 AM (1ch int16)";
83     case S16_FM:    return "S16 FM (1ch int16)";
84     case CF32_IQ:   return "CF32 IQ (2ch float32)";
85     case CS16_IQ:   return "CS16 IQ (2ch int16)";
86     case F32_AM:    return "F32 AM (1ch float32)";
87     case F32_FM:    return "F32 FM (1ch float32)";
88     case F32_I:     return "F32 I (1ch float32)";
89     case F32_Q:     return "F32 Q (1ch float32)";
90     case VCD_LOGIC: return "VCD logic (text)";
91     case U8_LOGIC:  return "U8 logic (1ch uint8)";
92     case PULSE_OOK: return "OOK pulse data (text)";
93     default:        return "Unknown";
94     }
95 }
96 
file_type_set_format(uint32_t * type,uint32_t val)97 static void file_type_set_format(uint32_t *type, uint32_t val)
98 {
99     *type = (*type & 0xffff0000) | val;
100 }
101 
file_type_set_content(uint32_t * type,uint32_t val)102 static void file_type_set_content(uint32_t *type, uint32_t val)
103 {
104     *type = (*type & 0x0000ffff) | val;
105 }
106 
file_type_guess_auto_format(uint32_t type)107 static uint32_t file_type_guess_auto_format(uint32_t type)
108 {
109     if (type == 0) return CU8_IQ;
110     else if (type == F_IQ) return CU8_IQ;
111     else if (type == F_AM) return S16_AM;
112     else if (type == F_FM) return S16_FM;
113     else if (type == F_I) return F32_I;
114     else if (type == F_Q) return F32_Q;
115     else if (type == F_LOGIC) return U8_LOGIC;
116 
117     else if (type == F_CU8) return CU8_IQ;
118     else if (type == F_CS8) return CS8_IQ;
119     else if (type == F_S16) return S16_AM;
120     else if (type == F_U8) return U8_LOGIC;
121     else if (type == F_VCD) return VCD_LOGIC;
122     else if (type == F_OOK) return PULSE_OOK;
123     else if (type == F_CS16) return CS16_IQ;
124     else if (type == F_CF32) return CF32_IQ;
125     else return type;
126 }
127 
file_type(char const * filename,file_info_t * info)128 static void file_type(char const *filename, file_info_t *info)
129 {
130     if (!filename || !*filename) {
131         return;
132     }
133 
134     char const *p = filename;
135     while (*p) {
136         if (*p >= '0' && *p <= '9') {
137             char const *n = p; // number starts here
138             while (*p >= '0' && *p <= '9')
139                 ++p;
140             if (*p == '.') {
141                 ++p;
142                 // if not [0-9] after '.' abort
143                 if (*p < '0' || *p > '9')
144                     continue;
145                 while (*p >= '0' && *p <= '9')
146                     ++p;
147             }
148             char const *s = p; // number ends and unit starts here
149             while ((*p >= 'A' && *p <= 'Z')
150                     || (*p >= 'a' && *p <= 'z'))
151                 ++p;
152             double num = atof(n); // atouint32_metric() ?
153             size_t len = p - s;
154             double scale = 1.0;
155             switch (*s) {
156             case 'k':
157             case 'K':
158                 scale *= 1e3;
159                 break;
160             case 'M':
161             case 'm':
162                 scale *= 1e6;
163                 break;
164             case 'G':
165             case 'g':
166                 scale *= 1e9;
167                 break;
168             }
169             if (len == 1 && !strncasecmp("M", s, 1)) info->center_frequency = num * 1e6;
170             else if (len == 1 && !strncasecmp("k", s, 1)) info->sample_rate = num * 1e3;
171             else if (len == 2 && !strncasecmp("Hz", s, 2)) info->center_frequency = num;
172             else if (len == 3 && !strncasecmp("sps", s, 3)) info->sample_rate = num;
173             else if (len == 3 && !strncasecmp("Hz", s+1, 2) && scale > 1.0) info->center_frequency = num * scale;
174             else if (len == 4 && !strncasecmp("sps", s+1, 3) && scale > 1.0) info->sample_rate = num * scale;
175             //fprintf(stderr, "Got number %g, f is %u, s is %u\n", num, info->center_frequency, info->sample_rate);
176         } else if ((*p >= 'A' && *p <= 'Z')
177                 || (*p >= 'a' && *p <= 'z')) {
178             char const *t = p; // type starts here
179             while ((*p >= '0' && *p <= '9')
180                     || (*p >= 'A' && *p <= 'Z')
181                     || (*p >= 'a' && *p <= 'z'))
182                 ++p;
183             size_t len = p - t;
184             if (len == 1 && !strncasecmp("i", t, 1)) file_type_set_content(&info->format, F_I);
185             else if (len == 1 && !strncasecmp("q", t, 1)) file_type_set_content(&info->format, F_Q);
186             else if (len == 2 && !strncasecmp("iq", t, 2)) file_type_set_content(&info->format, F_IQ);
187             else if (len == 2 && !strncasecmp("am", t, 2)) file_type_set_content(&info->format, F_AM);
188             else if (len == 2 && !strncasecmp("fm", t, 2)) file_type_set_content(&info->format, F_FM);
189             else if (len == 2 && !strncasecmp("u8", t, 2)) file_type_set_format(&info->format, F_U8);
190             else if (len == 2 && !strncasecmp("s8", t, 2)) file_type_set_format(&info->format, F_S8);
191             else if (len == 3 && !strncasecmp("cu8", t, 3)) file_type_set_format(&info->format, F_CU8);
192             else if (len == 4 && !strncasecmp("data", t, 4)) file_type_set_format(&info->format, F_CU8); // compat
193             else if (len == 3 && !strncasecmp("cs8", t, 3)) file_type_set_format(&info->format, F_CS8);
194             else if (len == 3 && !strncasecmp("u16", t, 3)) file_type_set_format(&info->format, F_U16);
195             else if (len == 3 && !strncasecmp("s16", t, 3)) file_type_set_format(&info->format, F_S16);
196             else if (len == 3 && !strncasecmp("u32", t, 3)) file_type_set_format(&info->format, F_U32);
197             else if (len == 3 && !strncasecmp("s32", t, 3)) file_type_set_format(&info->format, F_S32);
198             else if (len == 3 && !strncasecmp("f32", t, 3)) file_type_set_format(&info->format, F_F32);
199             else if (len == 3 && !strncasecmp("vcd", t, 3)) file_type_set_content(&info->format, F_VCD);
200             else if (len == 3 && !strncasecmp("ook", t, 3)) file_type_set_content(&info->format, F_OOK);
201             else if (len == 4 && !strncasecmp("cs16", t, 4)) file_type_set_format(&info->format, F_CS16);
202             else if (len == 4 && !strncasecmp("cs32", t, 4)) file_type_set_format(&info->format, F_CS32);
203             else if (len == 4 && !strncasecmp("cf32", t, 4)) file_type_set_format(&info->format, F_CF32);
204             else if (len == 5 && !strncasecmp("cfile", t, 5)) file_type_set_format(&info->format, F_CF32); // compat
205             else if (len == 5 && !strncasecmp("logic", t, 5)) file_type_set_content(&info->format, F_LOGIC);
206             else if (len == 3 && !strncasecmp("complex16u", t, 10)) file_type_set_format(&info->format, F_CU8); // compat
207             else if (len == 3 && !strncasecmp("complex16s", t, 10)) file_type_set_format(&info->format, F_CS8); // compat
208             else if (len == 4 && !strncasecmp("complex", t, 7)) file_type_set_format(&info->format, F_CF32); // compat
209             //else fprintf(stderr, "Skipping type (len %ld) %s\n", len, t);
210         } else {
211             p++; // skip non-alphanum char otherwise
212         }
213     }
214 }
215 
216 // return the last colon not followed by a backslash, otherwise NULL
last_plain_colon(char const * p)217 static char const *last_plain_colon(char const *p)
218 {
219     char const *found = NULL;
220     char const *next = strchr(p, ':');
221     while (next && next[1] != '\\') {
222         found = next;
223         next = strchr(next+1, ':');
224     }
225     return found;
226 }
227 
228 /**
229 This will detect file info and overrides.
230 
231 Parse "[0-9]+(\.[0-9]+)?[A-Za-z]"
232  as frequency (suffix "M" or "[kMG]?Hz")
233  or sample rate (suffix "k" or "[kMG]?sps")
234 
235 Parse "[A-Za-z][0-9A-Za-z]+" as format or content specifier:
236 
237 2ch formats: "cu8", "cs8", "cs16", "cs32", "cf32"
238 1ch formats: "u8", "s8", "s16", "u16", "s32", "u32", "f32"
239 text formats: "vcd", "ook"
240 content types: "iq", "i", "q", "am", "fm", "logic"
241 
242 Parses left to right, with the exception of a prefix up to the last colon ":"
243 This prefix is the forced override, parsed last and removed from the filename.
244 
245 All matches are case-insensitive.
246 
247 default detection, e.g.: path/filename.am.s16
248 overrides, e.g.: am:s16:path/filename.ext
249 other styles are detected but discouraged, e.g.:
250   am-s16:path/filename.ext, am.s16:path/filename.ext, path/filename.am_s16
251 */
file_info_parse_filename(file_info_t * info,char const * filename)252 int file_info_parse_filename(file_info_t *info, char const *filename)
253 {
254     if (!filename) {
255         return 0;
256     }
257 
258     info->spec = filename;
259 
260     char const *p = last_plain_colon(filename);
261     if (p && p - filename < 64) {
262         size_t len = p - filename;
263         char forced[64];
264         memcpy(forced, filename, len);
265         forced[len] = '\0';
266         p++;
267         file_type(p, info);
268         file_type(forced, info);
269         info->path = p;
270     } else {
271         file_type(filename, info);
272         info->path = filename;
273     }
274     info->raw_format = info->format;
275     info->format = file_type_guess_auto_format(info->format);
276     return info->format;
277 }
278 
279 // Unit testing
280 #ifdef _TEST
assert_file_type(int check,char const * spec)281 static void assert_file_type(int check, char const *spec)
282 {
283     file_info_t info = {0};
284     int ret = file_info_parse_filename(&info, spec);
285     if (check != ret) {
286         fprintf(stderr, "\nTEST failed: determine_file_type(\"%s\", &foo) = %8x == %8x\n", spec, ret, check);
287     } else {
288         fprintf(stderr, ".");
289     }
290 }
291 
assert_str_equal(char const * a,char const * b)292 static void assert_str_equal(char const *a, char const *b)
293 {
294     if (a != b && (!a || !b || strcmp(a, b))) {
295         fprintf(stderr, "\nTEST failed: \"%s\" == \"%s\"\n", a, b);
296     } else {
297         fprintf(stderr, ".");
298     }
299 }
300 
main(void)301 int main(void)
302 {
303     fprintf(stderr, "Testing:\n");
304 
305     assert_str_equal(last_plain_colon("foo:bar:baz"), ":baz");
306     assert_str_equal(last_plain_colon("foo"), NULL);
307     assert_str_equal(last_plain_colon(":foo"), ":foo");
308     assert_str_equal(last_plain_colon("foo:"), ":");
309     assert_str_equal(last_plain_colon("foo:bar:C:\\path.txt"), ":C:\\path.txt");
310     assert_str_equal(last_plain_colon("foo:bar:C:\\path.txt:baz"), ":C:\\path.txt:baz");
311 
312     assert_file_type(CU8_IQ, "cu8:");
313     assert_file_type(CS16_IQ, "cs16:");
314     assert_file_type(CF32_IQ, "cf32:");
315     assert_file_type(S16_AM, "am:");
316     assert_file_type(S16_AM, "am.s16:");
317     assert_file_type(S16_AM, "am-s16:");
318     assert_file_type(S16_AM, "am_s16:");
319     assert_file_type(S16_AM, "s16.am:");
320     assert_file_type(S16_AM, "s16-am:");
321     assert_file_type(S16_AM, "s16_am:");
322     assert_file_type(S16_AM, "am-s16.am:");
323     assert_file_type(S16_FM, "fm:");
324     assert_file_type(S16_FM, "fm.s16:");
325     assert_file_type(S16_FM, "fm-s16:");
326     assert_file_type(S16_FM, "fm_s16:");
327     assert_file_type(S16_FM, "s16.fm:");
328     assert_file_type(S16_FM, "s16-fm:");
329     assert_file_type(S16_FM, "s16_fm:");
330     assert_file_type(S16_FM, "fm+s16:");
331     assert_file_type(S16_FM, "s16,fm:");
332 
333     assert_file_type(CU8_IQ, ".cu8");
334     assert_file_type(CS16_IQ, ".cs16");
335     assert_file_type(CF32_IQ, ".cf32");
336     assert_file_type(S16_AM, ".am");
337     assert_file_type(S16_AM, ".am.s16");
338     assert_file_type(S16_AM, ".am-s16");
339     assert_file_type(S16_AM, ".am_s16");
340     assert_file_type(S16_AM, ".s16+am");
341     assert_file_type(S16_AM, ".s16.am");
342     assert_file_type(S16_AM, ".s16-am");
343     assert_file_type(S16_AM, ".am-s16.am");
344     assert_file_type(S16_FM, ".fm");
345     assert_file_type(S16_FM, ".fm.s16");
346     assert_file_type(S16_FM, ".fm-s16");
347     assert_file_type(S16_FM, ".fm_s16");
348     assert_file_type(S16_FM, ".fm+s16");
349     assert_file_type(S16_FM, ".s16.fm");
350     assert_file_type(S16_FM, ".s16-fm");
351     assert_file_type(S16_FM, ".s16_fm");
352     assert_file_type(S16_FM, ".s16,fm");
353 
354     fprintf(stderr, "\nDone!\n");
355 }
356 #endif /* _TEST */
357