|
| 1 | + //@@@@ //@@@@@//@@ //@@//@@@@ //@@@@//@@@@@ //@@@@@@ ----------------------- |
| 2 | + //@@//@@/@@ //@@/@@ //@@/@@//@@//@@//@@//@@/@@//@/@@/@ a companion of CSW2CDT |
| 3 | +//@@ //@@ //@@ //@@ //@@/@@ //@@//@@ //@@ turning WAV audio into |
| 4 | +//@@ //@@@@@//@@/@/@@//@@@@//@@ //@@//@@ //@@ CSW v1 files, coded by |
| 5 | +//@@ //@@/@@@@@@@/@@ //@@ //@@//@@ //@@ Cesar Nicolas-Gonzalez |
| 6 | + //@@//@@/@@ //@@/@@@/@@@/@@//@@//@@//@@//@@/@@ //@@ since 2020/05/31-09:50 |
| 7 | + //@@@@ //@@@@@//@@ //@@/@@@@@@ //@@@@//@@@@@ //@@@@ ------------------------ |
| 8 | + |
| 9 | +#define MY_VERSION "20240224" |
| 10 | +#define MY_LICENSE "Copyright (C) 2020-2023 Cesar Nicolas-Gonzalez" |
| 11 | + |
| 12 | +#define GPL_3_INFO \ |
| 13 | + "This program comes with ABSOLUTELY NO WARRANTY; for more details" "\n" \ |
| 14 | + "please see the GNU General Public License. This is free software" "\n" \ |
| 15 | + "and you are welcome to redistribute it under certain conditions." |
| 16 | + |
| 17 | +/* This notice applies to the source code of CSW2CDT and its binaries. |
| 18 | +
|
| 19 | +This program is free software: you can redistribute it and/or modify |
| 20 | +it under the terms of the GNU General Public License as published by |
| 21 | +the Free Software Foundation, either version 3 of the License, or |
| 22 | +(at your option) any later version. |
| 23 | +
|
| 24 | +This program is distributed in the hope that it will be useful, |
| 25 | +but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 26 | +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 27 | +GNU General Public License for more details. |
| 28 | +
|
| 29 | +You should have received a copy of the GNU General Public License |
| 30 | +along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 31 | +
|
| 32 | +Contact information: <mailto:cngsoft@gmail.com> */ |
| 33 | + |
| 34 | +#include <stdio.h> // fopen,printf... |
| 35 | +#include <stdlib.h> // atoll,malloc... |
| 36 | +#include <string.h> // memcmp,strcmp... |
| 37 | + |
| 38 | +#define length(x) (sizeof(x)/sizeof*(x)) |
| 39 | +unsigned char buffer[512]; FILE *fi,*fo; |
| 40 | +char *autosuffix(char *t,char *s,char *x) // return a valid path, with a new suffix if required |
| 41 | +{ |
| 42 | + if (t) return t; else if (!s) return NULL; else if ((char*)buffer!=s) strcpy(buffer,s); |
| 43 | + if ((t=strrchr(buffer,'.'))&&(!(s=strrchr(buffer, |
| 44 | + #ifdef _WIN32 |
| 45 | + '\\' |
| 46 | + #else |
| 47 | + '/' |
| 48 | + #endif |
| 49 | + ))||t>s)) return strcpy(t,x),buffer; // override old suffix |
| 50 | + return strcat(buffer,x); // append new suffix |
| 51 | +} |
| 52 | + |
| 53 | +// I/O file operations, buffering and Intel lil-endian integer logic -------- // |
| 54 | + |
| 55 | +#define fread1(t,i) fread(t,1,i,fi) |
| 56 | +#define fwrite1(t,i) fwrite(t,1,i,fo) |
| 57 | +#define fput1(n) fputc(n,fo) |
| 58 | +int fput4(int i) { fput1(i); fput1(i>>8); fput1(i>>16); return fput1(i>>24); } // non-buffered! |
| 59 | + |
| 60 | +unsigned char tsrc[1<<12],ttgt[1<<12]; int isrc=0,ilen=0,itgt=0; // I/O file buffers |
| 61 | +#define flush1() fwrite1(ttgt,itgt) |
| 62 | +int frecv1(void) { while (isrc>=ilen) if (isrc-=ilen,!(ilen=fread1(tsrc,sizeof(tsrc)))) return -1; return tsrc[isrc++]; } |
| 63 | +int frecv4(void) { int i=frecv1(); i|=frecv1()<<8; i|=frecv1()<<16; return i|(frecv1()<<24); } |
| 64 | +int fsend1(int i) { ttgt[itgt++]=i; if (itgt>=length(ttgt)) { if (!flush1()) return -1; itgt=0; } return i; } |
| 65 | +int fsend4(int i) { fsend1(i); fsend1(i>>8); fsend1(i>>16); return fsend1(i>>24); } |
| 66 | + |
| 67 | +// the main procedure proper ------------------------------------------------ // |
| 68 | + |
| 69 | +const char csw24b[]="Compressed Square Wave\032\001",ok_bytes[]="%d:%d bytes.\n",bad_target[]="error: cannot create target!\n"; |
| 70 | +int plusatoi(char *s) { return atoi(*s=='+'?s+1:s); } // handle "+1234" as "1234" |
| 71 | +int readmmss(char *s) { int h=0,l=0,c; while ((c=*s++)>='0'&&c<='9') l=l*10+c-'0'; if (c==':') { h=l*60,l=0; while ((c=*s++)>='0'&&c<='9') l=l*10+c-'0'; } return c?-1:h+l; } |
| 72 | + |
| 73 | +int main(int argc,char *argv[]) |
| 74 | +{ |
| 75 | + int h,i,j,k,l; char *si=NULL,*so=NULL; |
| 76 | + int flag_m=0,flag_e=0,flag_lr=0,flag_t=0,flag_n=0,filter_l=0,filter_h=0,hz,mmss_init=0,mmss_exit=-1; |
| 77 | + for (i=1;i<argc;++i) |
| 78 | + if (argv[i][0]=='-') |
| 79 | + if (argv[i][1]) |
| 80 | + #define FETCH_INT(t,l,h) ((argv[i][++j]||(j=0,++i<argc))?t=plusatoi(&argv[i][j]),j=-1,t>=l&&t<h:0) |
| 81 | + for (j=0;++j>0&&i<argc&&argv[i][j];) |
| 82 | + switch (argv[i][j]) |
| 83 | + { |
| 84 | + case 'f': |
| 85 | + if (!FETCH_INT(filter_l,0,96000)) |
| 86 | + i=argc; // help! |
| 87 | + else if (++i>=argc||!FETCH_INT(filter_h,0,96000)) |
| 88 | + i=argc; // help! |
| 89 | + if (filter_l&&filter_h&&filter_l>filter_h) |
| 90 | + k=filter_l,filter_l=filter_h,filter_h=k; // swap if L > H |
| 91 | + break; |
| 92 | + case 'm': |
| 93 | + if (!FETCH_INT(flag_m,-127,+127)) |
| 94 | + i=argc; // help! |
| 95 | + break; |
| 96 | + case 'e': |
| 97 | + if (!FETCH_INT(flag_e,0,127)) |
| 98 | + i=argc; // help! |
| 99 | + break; |
| 100 | + case 'l': |
| 101 | + flag_lr=-1; |
| 102 | + break; |
| 103 | + case 'r': |
| 104 | + flag_lr=+1; |
| 105 | + break; |
| 106 | + case 'n': |
| 107 | + flag_n=1; |
| 108 | + break; |
| 109 | + case 't': |
| 110 | + if (!FETCH_INT(flag_t,0,100)) |
| 111 | + i=argc; // help! |
| 112 | + break; |
| 113 | + case '-': |
| 114 | + if (i+1<argc&&(j=readmmss(&argv[i][j+1]))>=0&&(mmss_init=j,(j=readmmss(argv[++i]))>=0)) |
| 115 | + mmss_exit=j,j=-1; |
| 116 | + else |
| 117 | + i=argc; // help! |
| 118 | + break; |
| 119 | + case 'h': |
| 120 | + default: |
| 121 | + i=argc; // help! |
| 122 | + break; |
| 123 | + } |
| 124 | + else |
| 125 | + i=argc; // help! |
| 126 | + else if (!si) |
| 127 | + si=argv[i]; |
| 128 | + else if (!so) |
| 129 | + so=argv[i]; |
| 130 | + else i=argc; // help! |
| 131 | + if (i>argc||!si) |
| 132 | + return printf( |
| 133 | + "CSW0 " MY_VERSION " " MY_LICENSE "\n" |
| 134 | + "\n" |
| 135 | + "encoding from WAV to CSW:\n" |
| 136 | + "\tcsw0 [option..] source.wav [target.csw]\n" |
| 137 | + "\t-f L H\tenable pass-band filter, in Hz\n" |
| 138 | + "\t-m N\tset middle point between -127 and 127\n" |
| 139 | + "\t-e N\tset error margin between 0 and 127\n" |
| 140 | + "\t-l\tleft channel only\n" |
| 141 | + "\t-r\tright channel only\n" |
| 142 | + "\t-n\tnegative polarity\n" |
| 143 | + "\t--M1:S1 M2:S2\tclip from M1 min S1 sec to M2 min M2 sec\n" |
| 144 | + "decoding from CSW to WAV:\n" |
| 145 | + "\tcsw0 [option..] source.csw [target.wav]\n" |
| 146 | + "\t-t N\tset signal shape between 0 and 100 (square-triangle)\n" |
| 147 | + "\t-n\tnegative polarity\n" |
| 148 | + "\n" GPL_3_INFO "\n" |
| 149 | + ),1; |
| 150 | + if (!(fi=fopen(si,"rb"))) |
| 151 | + return fprintf(stderr,"error: cannot open source!\n"),1; |
| 152 | + if (*buffer=0,fread1(buffer,12),!memcmp(buffer,"RIFF",4)&&!memcmp(&buffer[8],"WAVE",4)) // encoding? |
| 153 | + { |
| 154 | + while (j=frecv4(),k=frecv4(),k>=0&&j!=0x20746D66) // "fmt " |
| 155 | + isrc+=(k+1)&~1; // RIFF even-padding |
| 156 | + if (k<16) |
| 157 | + return fclose(fi),fprintf(stderr,"error: source lacks header!\n"),1; |
| 158 | + int stereo=(frecv4()>>17)&1; // channels-1 |
| 159 | + hz=frecv4(); |
| 160 | + frecv4(); // actual byte rate... |
| 161 | + int datasize,byteskip=((frecv4()>>19)&3)-1,bytesize=byteskip+1; // bitdepth 8, 16 or 24 (!) -> byteskip = 0,1,2 (unused bytes per sample); bytesize = 1,2,3 (sample size in bytes) |
| 162 | + isrc+=((k+1)&~1)-16; // RIFF even-padding |
| 163 | + while (j=frecv4(),datasize=frecv4(),datasize>=0&&j!=0x61746164) // "data" |
| 164 | + isrc+=datasize; |
| 165 | + if (datasize<0) |
| 166 | + return fclose(fi),fprintf(stderr,"error: source lacks body!\n"),1; |
| 167 | + if (!(fo=fopen(autosuffix(so,si,".csw"),"wb"))) |
| 168 | + return fclose(fi),fprintf(stderr,bad_target),1; |
| 169 | + fwrite1(csw24b,24); |
| 170 | + fsend4(hz*256+1+256*256*256); flag_m+=128; |
| 171 | + if (stereo) if (flag_lr<0) isrc-=bytesize; // avoid right channel |
| 172 | + if (mmss_init>0) { mmss_init*=(stereo+1)*bytesize*hz; isrc+=mmss_init,datasize-=mmss_init; } // source start... |
| 173 | + if (mmss_exit>0) { mmss_exit*=(stereo+1)*bytesize*hz; if (datasize>mmss_exit) datasize=mmss_exit; } // ...and end |
| 174 | + #define GETSAMPLE() (stereo? \ |
| 175 | + byteskip?(datasize-=2*bytesize,isrc+=byteskip,i=frecv1()^128,isrc+=byteskip,i=(((frecv1()^128)+i)>>1)): \ |
| 176 | + (datasize-=2,i=((frecv1()+frecv1())>>1)): \ |
| 177 | + byteskip?(datasize-=bytesize,isrc+=byteskip,i=frecv1()^128): \ |
| 178 | + (--datasize,i=frecv1())) |
| 179 | + #define SENDCHUNK do{ if (j) { if (j>255) fsend1(0),fsend4(j); else fsend1(j); j=0; } }while(0) |
| 180 | + GETSAMPLE(); fsend4(flag_n^(k=i>flag_m)); // store 1st signal! |
| 181 | + j=0; l=flag_m-flag_e; h=flag_m+flag_e; |
| 182 | + const float TWOPI=6.28318530717958647692528676655901; |
| 183 | + if (hz*filter_l>0) |
| 184 | + { |
| 185 | + if (hz*filter_h>0) // band-pass filter: H and L nonzero |
| 186 | + { |
| 187 | + int g=0; float h1=0,h2=0,high_alpha=1.0/(TWOPI*filter_l/hz+1.0), |
| 188 | + loww_alpha=(TWOPI*filter_h/hz)/(TWOPI*filter_h/hz+1.0); |
| 189 | + do |
| 190 | + { |
| 191 | + i-=flag_m; h2=high_alpha*(h2+i-g); g=i; // high-pass 1st |
| 192 | + h1=h1+loww_alpha*(h2-h1); // loww-pass 2nd |
| 193 | + if (k?(h1<=-flag_e):(h1>flag_e)) |
| 194 | + { SENDCHUNK; k=!k; } |
| 195 | + } |
| 196 | + while (++j,GETSAMPLE(),datasize>0&&ilen>0); |
| 197 | + } |
| 198 | + else // high-pass filter: H zero, L nonzero |
| 199 | + { |
| 200 | + int g=0; float hh=0,high_alpha=1.0/(TWOPI*filter_l/hz+1.0); |
| 201 | + do |
| 202 | + { |
| 203 | + i-=flag_m; hh=high_alpha*(hh+i-g); g=i; // high-pass |
| 204 | + if (k?(hh<=-flag_e):(hh>flag_e)) |
| 205 | + { SENDCHUNK; k=!k; } |
| 206 | + } |
| 207 | + while (++j,GETSAMPLE(),datasize>0&&ilen>0); |
| 208 | + } |
| 209 | + } |
| 210 | + else if (hz*filter_h>0) // low-pass filter: H nonzero, L zero |
| 211 | + { |
| 212 | + float hh=0,loww_alpha=(TWOPI*filter_h/hz)/(TWOPI*filter_h/hz+1.0); |
| 213 | + do |
| 214 | + { |
| 215 | + i-=flag_m; hh=hh+loww_alpha*(i-hh); // loww-pass |
| 216 | + if (k?(hh<=-flag_e):(hh>flag_e)) |
| 217 | + { SENDCHUNK; k=!k; } |
| 218 | + } |
| 219 | + while (++j,GETSAMPLE(),datasize>0&&ilen>0); |
| 220 | + } |
| 221 | + else // no filter |
| 222 | + { |
| 223 | + do |
| 224 | + { |
| 225 | + if (k?(i<=l):(i>h)) |
| 226 | + { SENDCHUNK; k=!k; } |
| 227 | + } |
| 228 | + while (++j,GETSAMPLE(),datasize>0&&ilen>0); |
| 229 | + } |
| 230 | + SENDCHUNK; flush1(); // flush! |
| 231 | + printf(ok_bytes,(int)ftell(fi),(int)ftell(fo)); |
| 232 | + return fclose(fi),fclose(fo),0; |
| 233 | + } |
| 234 | + if (fread1(&buffer[12],(32-12))==(32-12)&&!memcmp(buffer,csw24b,24)) // decoding? |
| 235 | + { |
| 236 | + hz=buffer[25]+buffer[26]*256+(buffer[27]-1)*65536; k=((flag_n^buffer[28])&1)?32:224; |
| 237 | + if (!(fo=fopen(autosuffix(so,si,".wav"),"wb"))) // this call destroys `buffer`! |
| 238 | + return fclose(fi),fprintf(stderr,bad_target),1; |
| 239 | + fwrite1("RIFFFFFFWAVEfmt \020\000\000\000\001\000\001\000~~~~~~~~\001\000\010\000dataaaaa",44); |
| 240 | + int g=2+(hz-1)/1000; // i.e. almost 2 ms |
| 241 | + while ((i=frecv1())>=0) |
| 242 | + { |
| 243 | + k^=192; if (!i) i=frecv4(); |
| 244 | + // long edges become flat at the end! |
| 245 | + l=0; if (i>g) l=i-g,i=g; |
| 246 | + // rising slope |
| 247 | + int g1=i*flag_t/200,g2=(i*flag_t+100)/200; i-=g1+g2; |
| 248 | + j=k-128; h=g1>>1; for (int n=1;n<=g1;++n) fsend1(128+(j*n+h)/g1); |
| 249 | + // ceiling, i.e. 128+96 / 128-96 |
| 250 | + j=k; while (i>0) fsend1(j),--i; |
| 251 | + // falling slope |
| 252 | + j=k-128; h=g2>>1; for (int n=g2;n>=1;--n) fsend1(128+(j*n+h)/g2); |
| 253 | + // floor |
| 254 | + while (l) fsend1(128),--l; |
| 255 | + } |
| 256 | + if (itgt&1) fsend1(0); flush1(); // RIFF even-padding + flush! |
| 257 | + printf(ok_bytes,(int)ftell(fi),i=(int)ftell(fo)); |
| 258 | + fseek(fo, 4,SEEK_SET); fput4(i-8); // file size-8 = chunk size+36 |
| 259 | + fseek(fo,24,SEEK_SET); fput4(hz); fput4(hz); // clock + bandwidth |
| 260 | + fseek(fo,40,SEEK_SET); fput4(i-8-36); // chunk size = filesize-44 |
| 261 | + return fclose(fi),fclose(fo),0; |
| 262 | + } |
| 263 | + return fprintf(stderr,"error: unknown source type!\n"),1; |
| 264 | +} |
0 commit comments