2121 lines
52 KiB
C
2121 lines
52 KiB
C
/*
|
|
ebook2cw - converts an ebook to Morse MP3/OGG-files
|
|
|
|
Copyright (C) 2007 - 2022 Fabian Kurz, DJ5CW
|
|
|
|
https://fkurz.net/ham/ebook2cw.html
|
|
|
|
This program is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU General Public License as published by the Free Software
|
|
Foundation; either version 2 of the License, or (at your option) any later
|
|
version.
|
|
|
|
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
|
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
|
this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
|
|
Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
source code looks properly indented with ts=4
|
|
|
|
*/
|
|
#ifdef LAME
|
|
#include <lame/lame.h>
|
|
#endif
|
|
#ifdef OGGV
|
|
#include <vorbis/vorbisenc.h>
|
|
#endif
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
#include <time.h>
|
|
|
|
#if !__MINGW32__
|
|
#include <locale.h> /* For GNU gettext */
|
|
#include <libintl.h>
|
|
#define _(String) gettext (String)
|
|
#define gettext_noop(String) String
|
|
#define N_(String) gettext_noop (String)
|
|
#else
|
|
#define _(String) (String)
|
|
#endif
|
|
|
|
#ifndef CGI
|
|
#include <signal.h> /* Ctrl-C handling with signalhandler() */
|
|
#include <setjmp.h> /* longjmp */
|
|
#endif
|
|
|
|
/* for mkdir, not used on Windows */
|
|
#if !__MINGW32__
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <errno.h>
|
|
#endif
|
|
|
|
#include <limits.h> /* PATH_MAX */
|
|
#ifndef PATH_MAX /* Not defined e.g. on GNU/hurd */
|
|
#define PATH_MAX 4096
|
|
#endif
|
|
|
|
#include "codetables.h"
|
|
|
|
#ifndef VERSION
|
|
#define VERSION "0.0.0"
|
|
#endif
|
|
|
|
#define PCMBUFFER 1048576 /* 90 seconds at 11kHz, for one word. plenty. */
|
|
#define NOISEBUFFER 1048576 /* 90 seconds at 11kHz, for one word. plenty. */
|
|
#define MP3BUFFER 1310720 /* abt 1.25*PCMBUFFER + 7200, as recommended */
|
|
|
|
#define ISO8859 0
|
|
#define UTF8 1
|
|
|
|
#define MP3 0
|
|
#define OGG 1
|
|
#define NOENC 2
|
|
|
|
#define SINE 0
|
|
#define SAWTOOTH 1
|
|
#define SQUARE 2
|
|
|
|
#define NOISEAMPLITUDE (10000.0)
|
|
#define CWAMPLITUDE (20000.0)
|
|
|
|
#ifdef LAME
|
|
/* Global for LAME */
|
|
lame_global_flags *gfp;
|
|
#endif
|
|
|
|
#ifdef OGGV
|
|
/* Globals for OGG/Vorbis */
|
|
ogg_stream_state os;
|
|
ogg_page og;
|
|
ogg_packet op;
|
|
vorbis_info vi;
|
|
vorbis_comment vc;
|
|
vorbis_dsp_state vd;
|
|
vorbis_block vb;
|
|
#endif
|
|
|
|
#ifndef CGI
|
|
/* Globals for longjmp */
|
|
static jmp_buf jmp;
|
|
#endif
|
|
|
|
/* Struct CWP to keep all CW parameters */
|
|
typedef struct {
|
|
/* CW parameters */
|
|
int wpm, /* speed, words per minute */
|
|
freq, /* audio frequency in Hz */
|
|
rt, /* risetime, in samples */
|
|
ft, /* falltime, in samples */
|
|
qrq, /* increase speed each qrq minutes */
|
|
reset, /* reset qrq each chapter? */
|
|
farnsworth, /* effective, farnsworth speed */
|
|
pBT, /* sent <BT> for each paragraph? */
|
|
waveform, /* waveform, sine(1), sawtooth(2), square(3) */
|
|
original_wpm, /* starting speed; if qrq is used */
|
|
wordspace, /* wordspace in samples, depends on Farnsworth*/
|
|
letterspace; /* inter-letter space */
|
|
float ews; /* extra word space */
|
|
/* Noise parameters */
|
|
int bandpassbw, /* Noise filter bandwidth */
|
|
bandpassfc, /* f_center of the bandpass */
|
|
addnoise, /* 1 if noise should be added, 0 if not */
|
|
snr; /* SNR of the CW with the noise */
|
|
/* mp3/ogg encoder parameters, buffers */
|
|
int encoder;
|
|
int samplerate,
|
|
brate,
|
|
quality;
|
|
int inpcm_size,
|
|
mp3buffer_size,
|
|
noisebuf_size,
|
|
ditlen, /* normal dit length in samples */
|
|
fditlen, /* farnsworth ditlen in samples */
|
|
maxbytes; /* max bytes of a 'dah' */
|
|
short int *dit_buf,
|
|
*dah_buf,
|
|
*inpcm,
|
|
*noisebuf;
|
|
unsigned char *mp3buffer;
|
|
/* Chapter splitting */
|
|
char chapterstr[80], /* split chapters by this string */
|
|
chapterfilename[PATH_MAX-8], /* Prefix, e.g. "Chapter-" */
|
|
outfilename[PATH_MAX]; /* Full name of current outputfile */
|
|
/* time based splitting, seconds */
|
|
int chaptertime;
|
|
/* word based splitting */
|
|
int chapterwords;
|
|
/* encoding and mapping */
|
|
int encoding, /* ISO8859 or UTF8 */
|
|
use_isomapping,
|
|
use_utf8mapping;
|
|
char isomapindex[256]; /* contains the chars to be mapped */
|
|
int utf8mapindex[256]; /* for utf8 as decimal code */
|
|
char isomap[256][4]; /* by these strings */
|
|
char utf8map[256][8];
|
|
|
|
int comment; /* status for comments (which will not
|
|
be translated to CW), 0 = off, 1 = on,
|
|
2 = off after next word */
|
|
|
|
/* Line of input file; position (byte) within the line */
|
|
int linecount;
|
|
int linepos;
|
|
|
|
char configfile[2048];
|
|
|
|
char id3_author[80],
|
|
id3_title[80],
|
|
id3_comment[80],
|
|
id3_year[5];
|
|
|
|
FILE *outfile;
|
|
unsigned int outfile_length;
|
|
} CWP;
|
|
|
|
|
|
/* functions */
|
|
void init_cw (CWP *cw);
|
|
void init_cwp (CWP *cw);
|
|
void init_encoder (CWP *cw);
|
|
void encode_buffer (int length, CWP *cw);
|
|
void ogg_encode_and_write (CWP *cw);
|
|
void flush_ogg (CWP *cw);
|
|
void help (void);
|
|
void showcodes (int i);
|
|
int makeword(char * text, CWP *cw);
|
|
void closefile (int letter, int chw, int chms, CWP *cw);
|
|
void openfile (int chapter, CWP *cw);
|
|
void buf_alloc (CWP *cw);
|
|
void buf_check (int j, CWP *cw);
|
|
void command (char * cmd, CWP *cw);
|
|
void readconfig (CWP *cw);
|
|
int install_config_files (char *homedir, CWP *cw);
|
|
void setparameter (char p, char * value, CWP *cw);
|
|
void loadmapping(char *filename, int enc, CWP *cw);
|
|
char *mapstring (char *string, CWP *cw);
|
|
void addnoise (int length, CWP *cw);
|
|
float snramplitude (int snr);
|
|
void fillnoisebuffer (short int *buf, int size, float amplitude);
|
|
void filterloop (short int *buf, int l, int b);
|
|
void scalebuffer(short int *buf, int length, float factor);
|
|
void addbuffer (short int *b1, short int *b2, int l);
|
|
char *timestring (int ms);
|
|
void guessencoding (char *filename);
|
|
void add_silence (int ms, CWP *cw);
|
|
#ifndef CGI
|
|
void signalhandler(int signal);
|
|
#endif
|
|
|
|
#ifdef CGI
|
|
int hexit (char c);
|
|
void urldecode(char *buf);
|
|
#endif
|
|
|
|
/* main */
|
|
|
|
int main (int argc, char** argv) {
|
|
int pos, i, c, tmp;
|
|
char word[1024]=""; /* will be cut when > 1024 chars long */
|
|
int chapter = 0;
|
|
int download = 0; /* CGI: Send content-disposition header? */
|
|
int chw = 0, tw = 0; /* chapter words, total words */
|
|
int chms = 0, tms = 0, qms = 0; /* millisec: chapter, total, since qrq */
|
|
time_t start_time, end_time; /* conversion time */
|
|
int finishchapter = 0; /* finish chapter after this sentence */
|
|
int interactive = 0; /* 0 for pipe/file input, 1 for tty */
|
|
FILE *infile;
|
|
|
|
#ifdef CGI
|
|
char *querystring;
|
|
static char text[10000];
|
|
static char cgi_outfilename[1024];
|
|
#endif
|
|
|
|
#ifdef CGIBUFFERED
|
|
char *cgibuf;
|
|
#endif
|
|
|
|
/* initializing the CW parameter struct with standard values */
|
|
CWP cw;
|
|
init_cwp(&cw);
|
|
|
|
infile = stdin;
|
|
|
|
start_time = time(NULL);
|
|
srand((unsigned int) start_time);
|
|
|
|
#if !__MINGW32__
|
|
/* Native Language Support by GNU gettext */
|
|
setlocale(LC_ALL, "" );
|
|
bindtextdomain( "ebook2cw", "/usr/share/locale" );
|
|
textdomain("ebook2cw");
|
|
#endif
|
|
|
|
#ifndef CGI
|
|
/* Signal handling for Ctrl-C -- Does not work on Win32 because
|
|
* SIGINT is not supported on that platform. There, the signal
|
|
* handler will be called but then the program terminates. */
|
|
|
|
if (signal(SIGINT, signalhandler) == SIG_ERR) {
|
|
fprintf(stderr, _("Failed to set up signal handler for SIGINT\n"));
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
printf(_("ebook2cw %s - (c) 2007 - 2022 by Fabian Kurz, DJ5CW\n\n"), VERSION);
|
|
|
|
/*
|
|
* Find and read ebook2cw.conf
|
|
*/
|
|
|
|
readconfig(&cw);
|
|
|
|
while((i=getopt(argc,argv, "XOo:w:W:e:f:uc:k:Q:R:pF:s:b:q:a:t:y:S:hnT:N:B:C:g:d:l:E:"))!= -1){
|
|
setparameter(i, optarg, &cw);
|
|
}
|
|
|
|
if (optind < argc) { /* something left? if so, use as infile */
|
|
if ((argv[optind][0] != '-') && (argv[optind][0] != '\0')) {
|
|
if ((infile = fopen(argv[optind], "r")) == NULL) {
|
|
fprintf(stderr, _("Error: Cannot open file %s. Exit.\n"),
|
|
argv[optind]);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If we are getting data from a tty, check what the actual encoding is.
|
|
* Historically, the default encoding of ebook2cw is ISO 8859-1, but
|
|
* nowadays most terminals should use UTF-8. */
|
|
if (isatty(fileno(infile))) {
|
|
interactive = 1;
|
|
#if !__MINGW32__
|
|
if (getenv("LANG") != NULL) {
|
|
if (strstr(getenv("LANG"), "utf") ||
|
|
strstr(getenv("LANG"), "UTF")) {
|
|
cw.encoding = UTF8;
|
|
}
|
|
}
|
|
#else
|
|
/* Assume Windows terminal to be UTF8 */
|
|
cw.encoding = UTF8;
|
|
#endif
|
|
}
|
|
|
|
#endif /* ifndef CGI */
|
|
|
|
/* init encoder (LAME or OGG/Vorbis) */
|
|
init_encoder(&cw);
|
|
|
|
/* Initially allocate inpcm, noisebuffer, mp3buffer, dit_buf, dah_buf */
|
|
buf_alloc(&cw);
|
|
|
|
#ifndef CGI
|
|
printf(_("Speed: %dwpm, Freq: %dHz, Chapter: >%s<, Encoding: %s\n"),cw.wpm,
|
|
cw.freq, cw.chapterstr, cw.encoding == UTF8 ? "UTF-8" : "ISO 8859-1");
|
|
|
|
printf(_("Effective speed: %dwpm, "), cw.farnsworth ? cw.farnsworth : cw.wpm);
|
|
printf(_("Extra word spaces: %1.1f, "), cw.ews);
|
|
printf(_("QRQ: %dmin, reset QRQ: %s\n"), cw.qrq, cw.reset ? _("yes") : _("no"));
|
|
|
|
if (cw.chapterwords) {
|
|
printf(_("Chapter limit: %d words, "), cw.chapterwords);
|
|
}
|
|
if (cw.chaptertime) {
|
|
printf(_("Chapter limit: %d seconds, "), cw.chaptertime);
|
|
}
|
|
|
|
printf(_("Encoder: %s\n\n"), (cw.encoder == OGG) ? "OGG" : "MP3");
|
|
|
|
if (interactive) {
|
|
printf(_("Interactive mode. Type in text and finish with Ctrl-D.\n\n"));
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
#ifdef CGI
|
|
/* CGI: utf8 input by default */
|
|
cw.encoding = UTF8;
|
|
/* CGI standard: write directly to stdout ==> no Content-Length possible
|
|
* CGI buffered: write to file /tmp/$time-$rnd and later generate output */
|
|
#ifndef CGIBUFFERED
|
|
cw.outfile = stdout;
|
|
#else
|
|
/* we need to generate a good random number for the file name;
|
|
* just using rand(time) won't do if two requests come in the
|
|
* same second. temporarily use cw.outfile FD to open urand */
|
|
if ((cw.outfile = fopen("/dev/urandom", "r")) == NULL) {
|
|
fprintf(stderr, _("Error: Failed to open /dev/urandom.\n"));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
fread(&i, sizeof(i), 1, cw.outfile);
|
|
srand(i);
|
|
i = rand();
|
|
fclose(cw.outfile);
|
|
|
|
snprintf(cgi_outfilename, 80, "/tmp/%d-%d", (int)start_time, i);
|
|
if ((cw.outfile = fopen(cgi_outfilename, "wb+")) == NULL) {
|
|
fprintf(stderr, _("Error: Failed to open %s\n"), cgi_outfilename);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
#endif
|
|
switch (cw.encoder) {
|
|
case MP3:
|
|
printf("Content-Type: audio/mpeg\n");
|
|
break;
|
|
case OGG:
|
|
printf("Content-Type: audio/ogg\n");
|
|
break;
|
|
}
|
|
#ifndef CGIBUFFERED /* header is finished */
|
|
printf("\n");
|
|
#endif
|
|
querystring = getenv("QUERY_STRING");
|
|
if ((querystring == NULL) || strlen(querystring) > 9000) {
|
|
exit(1);
|
|
}
|
|
|
|
/* the query parameters may look like:
|
|
* d=001&s=25&e=20&f=600&t=text => download, offer as filename "lcwo-001.mp3"
|
|
* s=25&e=20&f=600&t=text => return normal file
|
|
*/
|
|
|
|
if (querystring[0] == 'd') {
|
|
sscanf(querystring, "d=%d&s=%d&e=%d&f=%d&t=%9000s", &download, &cw.wpm, &cw.farnsworth, &cw.freq, text);
|
|
}
|
|
else {
|
|
sscanf(querystring, "s=%d&e=%d&f=%d&t=%9000s", &cw.wpm, &cw.farnsworth, &cw.freq, text);
|
|
}
|
|
strcat(text, " ");
|
|
urldecode(text);
|
|
|
|
if (cw.wpm == 0 || cw.freq == 0) {
|
|
exit(1);
|
|
}
|
|
|
|
flush_ogg(&cw);
|
|
#endif
|
|
|
|
init_cw(&cw); /* generate raw dit, dah */
|
|
|
|
if (strlen(cw.chapterstr)) {
|
|
strcat(cw.chapterstr, " ");
|
|
}
|
|
|
|
cw.original_wpm = cw.wpm; /* may be changed by QRQing */
|
|
chapter = 0;
|
|
#ifndef CGI
|
|
openfile(chapter, &cw);
|
|
/* Entry point for handling SIGINT; we jump back here from
|
|
* the signalhandler() function */
|
|
if (setjmp(jmp)) {
|
|
goto cleanup;
|
|
}
|
|
#endif
|
|
|
|
i=0;
|
|
pos=0;
|
|
|
|
/* 100ms of silence at the start; some decoders otherwise produce
|
|
* crackling noises */
|
|
add_silence(100, &cw);
|
|
|
|
|
|
/* read input, assemble full words (anything ending in ' ') to 'word' and
|
|
* generate CW, write to file by 'makeword'. words with > 1024 characters
|
|
* will be split */
|
|
|
|
#ifndef CGI
|
|
while ((c = getc(infile)) != EOF) {
|
|
#else
|
|
while ((c = text[i++]) != '\0') {
|
|
#endif
|
|
|
|
if (c == '\r') /* DOS linebreaks */
|
|
continue;
|
|
|
|
#if __MINGW32__
|
|
if (c == 0x04) /* EOT; Win32 console produces this for Ctl-D */
|
|
break;
|
|
#endif
|
|
|
|
if (c == '#')
|
|
cw.comment = 1;
|
|
|
|
if (cw.comment) {
|
|
if (c == '\n') {
|
|
cw.comment = 2;
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
word[pos++] = c;
|
|
|
|
if ((c == ' ') || (c == '\n') || (pos == 1024)) {
|
|
word[pos] = '\0';
|
|
#ifndef CGI
|
|
/* new chapter */
|
|
if ((strcmp(cw.chapterstr, word) == 0) || /* regular */
|
|
finishchapter == 2 /* timeout/max. words */
|
|
) {
|
|
|
|
closefile(chapter, chw, chms, &cw);
|
|
tw += chw;
|
|
tms += chms;
|
|
chw = 0;
|
|
chms = 0;
|
|
chapter++;
|
|
|
|
if (cw.qrq && cw.reset) {
|
|
cw.wpm = cw.original_wpm;
|
|
init_cw(&cw);
|
|
}
|
|
|
|
finishchapter = 0;
|
|
|
|
openfile(chapter, &cw);
|
|
}
|
|
#endif
|
|
|
|
/* check for commands: |f or |w */
|
|
if (word[0] == '|') {
|
|
command(word, &cw);
|
|
}
|
|
else {
|
|
tmp = makeword(mapstring(word, &cw), &cw);
|
|
chms += tmp;
|
|
qms += tmp;
|
|
chw++;
|
|
if (cw.comment == 2)
|
|
cw.comment = 0;
|
|
}
|
|
|
|
/* Every 'cw.qrq' minutes speed up 1 WpM */
|
|
if (cw.qrq && ((qms/60000.0) > cw.qrq)) {
|
|
cw.wpm += 1;
|
|
init_cw(&cw);
|
|
printf("QRQ: %dwpm\n", cw.wpm);
|
|
qms = 0;
|
|
}
|
|
|
|
/* word finished; reached word- or time-limit? */
|
|
if (finishchapter || (cw.chaptertime && chms/1000 >= cw.chaptertime) ||
|
|
(cw.chapterwords && chw == cw.chapterwords)) {
|
|
/* Yes! Finish sentence (i.e. until next '.', '!' or '?')
|
|
* and then start a new chapter */
|
|
finishchapter = 1;
|
|
|
|
/* End of sentence? */
|
|
if (word[pos-2] == '.' ||
|
|
word[pos-2] == '!' ||
|
|
word[pos-2] == '?') {
|
|
finishchapter = 2;
|
|
}
|
|
}
|
|
|
|
word[0] = '\0';
|
|
pos = 0;
|
|
|
|
|
|
} /* word */
|
|
} /* eof */
|
|
|
|
/* If the file ends without newline or space, but directly with EOF after
|
|
* the last word, this one is lost, so we need to add it... */
|
|
if (strlen(word) && word[0] != '|' && word[0] != '#') {
|
|
makeword(mapstring(word, &cw), &cw);
|
|
chw++;
|
|
}
|
|
|
|
/* CGI: Add some silence (500ms) to the end of the file */
|
|
#ifdef CGI
|
|
add_silence(500, &cw);
|
|
#endif
|
|
|
|
#ifndef CGI
|
|
closefile(chapter, chw, chms, &cw);
|
|
end_time = time(NULL);
|
|
printf(_("Total words: %d, total time: %s\n"), tw+chw, timestring(tms+chms));
|
|
printf(_("Conversion time: %s (Speedup: %.1fx)\n"),
|
|
timestring(1000.0 * difftime(end_time, start_time)),
|
|
((tms+chms)/(1000.0 * difftime(end_time,start_time)))
|
|
);
|
|
#else /* in CGI mode, we need to do this to close the file properly */
|
|
if (cw.encoder == OGG) { /* (otherwise done in closefile() */
|
|
#ifdef OGGV
|
|
vorbis_analysis_wrote(&vd,0);
|
|
ogg_encode_and_write(&cw);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
if (cw.encoder == MP3) {
|
|
#ifdef LAME
|
|
lame_close(gfp);
|
|
#endif
|
|
}
|
|
else {
|
|
#ifdef OGGV
|
|
ogg_stream_clear(&os);
|
|
vorbis_block_clear(&vb);
|
|
vorbis_dsp_clear(&vd);
|
|
vorbis_comment_clear(&vc);
|
|
vorbis_info_clear(&vi);
|
|
#endif
|
|
}
|
|
|
|
free(cw.mp3buffer);
|
|
free(cw.inpcm);
|
|
free(cw.noisebuf);
|
|
free(cw.dah_buf);
|
|
free(cw.dit_buf);
|
|
|
|
if (download) {
|
|
printf("Content-Disposition: attachment; filename=\"lcwo-%03d.mp3\"\n", download);
|
|
}
|
|
|
|
#ifdef CGIBUFFERED
|
|
/* File is completed, so we know the length and can send the
|
|
* content length header, and then the whole file */
|
|
printf("Content-Length: %d\n", cw.outfile_length);
|
|
printf("\n");
|
|
i = (int) ftell(cw.outfile);
|
|
rewind(cw.outfile);
|
|
/* maybe one day sendfile(2) will support writing to a
|
|
* file descriptor, not just a socket, to make this
|
|
* easier and faster... */
|
|
cgibuf = malloc((size_t) i+1);
|
|
if (cgibuf == NULL) {
|
|
fprintf(stderr, _("malloc() for cgibuf failed!\n"));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
fread(cgibuf, sizeof(char), (size_t) i, cw.outfile);
|
|
fclose(cw.outfile);
|
|
fwrite(cgibuf, sizeof(char), (size_t) i, stdout);
|
|
free(cgibuf);
|
|
|
|
unlink(cgi_outfilename);
|
|
#endif
|
|
|
|
/* NON-CGI operation: If we produced no output, remove the
|
|
* empty file */
|
|
#ifndef CGI
|
|
cleanup:
|
|
if (!chw) {
|
|
printf(_("Deleting empty file: %s\n"), cw.outfilename);
|
|
unlink(cw.outfilename);
|
|
}
|
|
#endif
|
|
|
|
return (EXIT_SUCCESS);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* init_cw - generates a dit and a dah to dit_buf and dah_buf */
|
|
|
|
void init_cw (CWP *cw) {
|
|
int samples_per_minute;
|
|
int x, len;
|
|
double val, val1, val2;
|
|
|
|
samples_per_minute = 60*cw->samplerate;
|
|
len = (int) (samples_per_minute/(50.0*cw->wpm));
|
|
|
|
/* size of dit_buf, dah_buf may have to be increased, when speed decreased */
|
|
if (len > cw->ditlen) {
|
|
if ((cw->dah_buf = realloc(cw->dah_buf, 3*len * sizeof(short int))) == NULL) {
|
|
fprintf(stderr, _("Error: Can't reallocate dah_buf[%d]\n"), 3*len);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if ((cw->dit_buf = realloc(cw->dit_buf, len * sizeof(short int))) == NULL) {
|
|
fprintf(stderr, _("Error: Can't allocate dit_buf[%d]\n"), len);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* both dah_buf and dit_buf are filled in this loop */
|
|
for (x=0; x < 3*len; x++) {
|
|
switch (cw->waveform) {
|
|
case SINE:
|
|
val = sin(2*M_PI*cw->freq*x/cw->samplerate);
|
|
break;
|
|
case SAWTOOTH:
|
|
val = ((1.0*cw->freq*x/cw->samplerate)-
|
|
floor(1.0*cw->freq*x/cw->samplerate))-0.5;
|
|
break;
|
|
case SQUARE:
|
|
val = ceil(sin(2*M_PI*cw->freq*x/cw->samplerate))-0.5;
|
|
break;
|
|
}
|
|
|
|
/* Shaping rising edge, same for dit and dah */
|
|
if (x < cw->rt)
|
|
val *= pow(sin(M_PI*x/(2.0*cw->rt)),2);
|
|
|
|
val1 = val2 = val;
|
|
|
|
/* Shaping falling edge, dit */
|
|
if (x < len) {
|
|
if (x > (len-cw->ft)) {
|
|
val1 *= pow((sin(2*M_PI*(x-(len-cw->ft)+cw->ft)/(4*cw->ft))), 2);
|
|
}
|
|
cw->dit_buf[x] = (short int) (val1 * CWAMPLITUDE);
|
|
}
|
|
|
|
/* Shaping falling edge, dah */
|
|
if (x > (3*len-cw->ft)) {
|
|
val2 *= pow((sin(2*M_PI*(x-(3*len-cw->ft)+cw->ft)/(4*cw->ft))), 2);
|
|
}
|
|
cw->dah_buf[x] = (short int) (val2 * CWAMPLITUDE);
|
|
} /* for */
|
|
|
|
cw->ditlen = len;
|
|
|
|
/* calculate farnsworth length samples */
|
|
if (cw->farnsworth) {
|
|
if (cw->farnsworth > cw->wpm) {
|
|
fprintf(stderr, _("Warning: Effective speed (-e %d) must be lower "
|
|
"than character speed (-w %d)! Speed adjusted to %d.\n"),
|
|
cw->farnsworth, cw->wpm, cw->wpm);
|
|
cw->farnsworth = cw->wpm;
|
|
}
|
|
/* by convention, wpm is measured using the word "Paris". With a dah
|
|
being 3 dits, and a spacing of one dit between sigs, this word
|
|
has a total length of
|
|
1+3+3+1 +3 +3 +1+3 +1 +3 +1+3+1 +2 +3 +1+1 +1 +3 +1+1+1 +2 = 43 dit lengths
|
|
Together with an additional word break of 7 dits this makes 50
|
|
dit lengths.
|
|
|
|
Actually, the total duration of only the sigs themselves is only
|
|
1+3+3+1 +3 +1+3 +1 +1+3+1 +2 +1+1 +1 +1+1+1 +2 = 31 dit lengths
|
|
and the other 12 dits are inter-sig pauses.
|
|
The definition of n "words per minute" speed means that this is
|
|
the speed where the operator gives the word "Paris" n times per
|
|
minute.
|
|
The sigs at a speed of cw->wpm have a total length of
|
|
cw->farnsworth * 31 dits
|
|
The total length of pauses is
|
|
cw->farnsworth * (12+7) dits
|
|
Note that the the effective wpm is relevant for the count here.
|
|
|
|
According to the farnsworth method, the inter-sig and inter-word
|
|
pauses are elongated such that the operator gives "Paris" only
|
|
cw->farnsworth times.
|
|
|
|
The total length of the sigs in samples is
|
|
*/
|
|
int total_sigs_samples = cw->farnsworth * 31 * cw->ditlen;
|
|
/*
|
|
This allows to determine the length of a ditlength during a
|
|
farnsworth break.*/
|
|
cw->fditlen = (samples_per_minute - total_sigs_samples) / (cw->farnsworth * (12+7));
|
|
}
|
|
|
|
/* Calculate maximum number of bytes per 'dah' for the PCM stream,
|
|
* this will be used later to check if the buffer runs full
|
|
* (10000 margin) */
|
|
|
|
if (cw->farnsworth) {
|
|
cw->maxbytes = (int) ((4+7.0*cw->ews)*(cw->fditlen)) + 10000;
|
|
}
|
|
else {
|
|
cw->maxbytes = (int) ((6+7.0*cw->ews) * len) + 10000;
|
|
}
|
|
|
|
/* Calculate word space, letter space, considering EWS and farnsworth */
|
|
cw->wordspace =(int)(1+7*cw->ews)*(cw->farnsworth ? cw->fditlen : cw->ditlen);
|
|
cw->letterspace = (cw->farnsworth ? 3*cw->fditlen - cw->ditlen : 2*cw->ditlen);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* makeword -- converts 'text' to CW by concatenating dit_buf amd dah_bufs,
|
|
* encodes this to MP3 and writes to open filehandle outfile */
|
|
|
|
int makeword(char * text, CWP *cw) {
|
|
const char *code; /* CW code as . and - */
|
|
|
|
int c, i, j, u, w;
|
|
int prosign = 0;
|
|
unsigned char last=0; /* for utf8 2-byte characters */
|
|
|
|
j = 0; /* position in 'inpcm' buffer */
|
|
|
|
for (i = 0; i < strlen(text); i++) {
|
|
c = (unsigned char) text[i];
|
|
code = NULL;
|
|
cw->linepos++;
|
|
|
|
if (c == '\n') { /* Same for UTF8 and ISO8859 */
|
|
if (cw->comment == 0 && strlen(text) == 1 && cw->pBT) /* paragraph */
|
|
code = " -...- ";
|
|
else if (cw->comment) { /* No spaces after comment line */
|
|
code = " ";
|
|
}
|
|
else { /* Space instead of newline */
|
|
code = " ";
|
|
}
|
|
cw->linepos = 0;
|
|
cw->linecount++;
|
|
}
|
|
else if (c == '<') { /* prosign on */
|
|
prosign = 1;
|
|
continue;
|
|
}
|
|
else if ((c == '>') && prosign) { /* prosign off */
|
|
prosign = 0;
|
|
code = ""; /* only inserts letter space */
|
|
}
|
|
else if (cw->encoding == ISO8859) {
|
|
code = iso8859[c];
|
|
}
|
|
else if (cw->encoding == UTF8) {
|
|
/* Character may be 1-byte ASCII or 2-byte UTF8 */
|
|
if (!(c & 128)) { /* MSB = 0 -> 7bit ASCII */
|
|
code = iso8859[c]; /* subset of iso8859 */
|
|
}
|
|
else {
|
|
if (last && (c < 192) ) { /* this is the 2nd byte */
|
|
/* 110yyyyy 10zzzzzz -> 00000yyy yyzzzzzz */
|
|
c = ((last & 31) << 6) | (c & 63);
|
|
code = utf8table[c];
|
|
last = 0;
|
|
}
|
|
else { /* this is the first byte */
|
|
last = c;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (last) continue; /* first of two-byte character read */
|
|
|
|
/* Not found anything, produce warning message */
|
|
if (code == NULL) {
|
|
#ifndef CGI
|
|
if (c < 128) {
|
|
fprintf(stderr, _("Warning: Don't know CW for '%c'. "), c);
|
|
}
|
|
else if (cw->encoding == ISO8859) {
|
|
fprintf(stderr, _("Warning: Don't know CW for '%c' (0x%01X) (try the -u switch to enable UTF-8). "), c, c);
|
|
}
|
|
else {
|
|
/* TODO: Consider using libunistring's unicode_character_name()
|
|
* function to provide the name of the character */
|
|
fprintf(stderr, _("Warning: Don't know CW for unicode code point U+%04X. "), c);
|
|
}
|
|
fprintf(stderr, _("[Line %d, Byte %d]\n"), cw->linecount, cw->linepos);
|
|
#endif
|
|
code = " ";
|
|
|
|
}
|
|
|
|
/* code contains letter as ./-, now assemble pcm buffer */
|
|
|
|
for (w = 0; w < strlen(code) ; w++) {
|
|
|
|
/* make sure the inpcm buffer doesn't run full,
|
|
* with a conservative margin. reallocate memory if neccesary */
|
|
buf_check(j, cw);
|
|
|
|
c = code[w];
|
|
|
|
if (c == '.') {
|
|
for (u=0; u < cw->ditlen; u++) {
|
|
cw->inpcm[++j] = cw->dit_buf[u];
|
|
}
|
|
}
|
|
else if (c == '-') {
|
|
for (u=0; u < (3*cw->ditlen); u++) {
|
|
cw->inpcm[++j] = cw->dah_buf[u];
|
|
}
|
|
}
|
|
else { /* word space */
|
|
for (u=0;u < cw->wordspace; u++)
|
|
cw->inpcm[++j] = 0;
|
|
}
|
|
|
|
for (u=0; u < cw->ditlen; u++) {
|
|
cw->inpcm[++j] = 0;
|
|
}
|
|
} /* foreach dot/dash */
|
|
|
|
if (prosign == 0) {
|
|
for (u=0; u < cw->letterspace; u++)
|
|
cw->inpcm[++j] = 0;
|
|
}
|
|
|
|
} /* foreach letter */
|
|
|
|
/* j = total length of samples in 'inpcm' */
|
|
|
|
if (cw->addnoise) {
|
|
addnoise(j, cw);
|
|
}
|
|
|
|
|
|
encode_buffer(j, cw);
|
|
|
|
return (int) (1000.0*j/cw->samplerate); /* encoded length in ms */
|
|
|
|
}
|
|
|
|
|
|
/* closefile -- finishes writing the current file, flushes the encoder buffer */
|
|
|
|
void closefile (int chapter, int chw, int chms, CWP *cw) {
|
|
int outbytes = 0;
|
|
|
|
printf(_("words: %d, time: %s\n"), chw, timestring(chms));
|
|
printf(_("Finishing %s\n\n"), cw->outfilename);
|
|
|
|
switch (cw->encoder) {
|
|
case MP3:
|
|
#ifdef LAME
|
|
outbytes = lame_encode_flush(gfp,cw->mp3buffer, cw->mp3buffer_size);
|
|
|
|
if (outbytes < 0) {
|
|
fprintf(stderr, "Error: lame_encode_buffer returned %d.\n",
|
|
outbytes);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (fwrite(cw->mp3buffer, sizeof(char), outbytes, cw->outfile) !=
|
|
outbytes) {
|
|
fprintf(stderr, "Error: Writing %db to file failed. Exit.\n",
|
|
outbytes);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
cw->outfile_length += outbytes;
|
|
#endif
|
|
break;
|
|
case OGG:
|
|
#ifdef OGGV
|
|
vorbis_analysis_wrote(&vd,0);
|
|
ogg_encode_and_write(cw);
|
|
#endif
|
|
break;
|
|
case NOENC:
|
|
/* Do nothing */
|
|
break;
|
|
}
|
|
|
|
if (cw->encoder != NOENC)
|
|
fclose(cw->outfile);
|
|
|
|
}
|
|
|
|
|
|
/* openfile -- starts a new chapter by opening a new file as outfile */
|
|
|
|
void openfile (int chapter, CWP *cw) {
|
|
#ifdef LAME
|
|
static char tmp[80] = "";
|
|
#endif
|
|
#ifdef OGGV
|
|
ogg_packet hdr;
|
|
ogg_packet hdr_comm;
|
|
ogg_packet hdr_code;
|
|
#endif
|
|
|
|
/* If we have a chapter separator string, use format Chapter0001.mp3 */
|
|
if (strlen(cw->chapterstr) && cw->chapterstr[0] != '-') {
|
|
snprintf(cw->outfilename, PATH_MAX, "%s%04d.%s", cw->chapterfilename,
|
|
chapter, (cw->encoder == MP3) ? "mp3" : "ogg");
|
|
}
|
|
else { /* otherwise just Chapter.mp3 */
|
|
snprintf(cw->outfilename, PATH_MAX, "%s.%s", cw->chapterfilename,
|
|
(cw->encoder == MP3) ? "mp3" : "ogg");
|
|
}
|
|
printf(_("Starting %s\n"), cw->outfilename);
|
|
|
|
if ((cw->encoder != NOENC) &&
|
|
(cw->outfile = fopen(cw->outfilename, "wb")) == NULL) {
|
|
fprintf(stderr, _("Error: Failed to open %s\n"), cw->outfilename);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
switch (cw->encoder) {
|
|
case MP3:
|
|
#ifdef LAME
|
|
snprintf(tmp, 79, "%s - %d", cw->id3_title, chapter); /* title */
|
|
id3tag_init(gfp);
|
|
id3tag_set_artist(gfp, cw->id3_author);
|
|
id3tag_set_year(gfp, cw->id3_year);
|
|
id3tag_set_title(gfp, tmp);
|
|
id3tag_set_comment(gfp, cw->id3_comment);
|
|
#endif
|
|
break;
|
|
case OGG:
|
|
#ifdef OGGV
|
|
vorbis_comment_init(&vc);
|
|
vorbis_comment_add_tag(&vc,"ENCODER","ebook2cw");
|
|
vorbis_analysis_init(&vd, &vi);
|
|
vorbis_block_init(&vd, &vb);
|
|
ogg_stream_init(&os,rand());
|
|
|
|
vorbis_analysis_headerout(&vd,&vc,&hdr,&hdr_comm,&hdr_code);
|
|
ogg_stream_packetin(&os,&hdr);
|
|
ogg_stream_packetin(&os,&hdr_comm);
|
|
ogg_stream_packetin(&os,&hdr_code);
|
|
flush_ogg(cw);
|
|
#endif
|
|
break;
|
|
case NOENC:
|
|
/* Do nothing */
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void help (void) {
|
|
printf(_("This is free software, and you are welcome to redistribute it\n"));
|
|
printf(_("under certain conditions (see COPYING).\n"));
|
|
printf("\n");
|
|
printf(_("ebook2cw [-w wpm] [-f freq] [-R risetime] [-F falltime]\n"));
|
|
printf(_(" [-s samplerate] [-b mp3bitrate] [-q mp3quality] [-p]\n"));
|
|
printf(_(" [-c chapter-separator] [-o outfile-name] [-Q minutes]\n"));
|
|
printf(_(" [-a author] [-t title] [-k comment] [-y year]\n"));
|
|
printf(_(" [-u] [-S ISO|UTF] [-n] [-e eff.wpm] [-W space]\n"));
|
|
printf(_(" [-N snr] [-B filter bandwidth] [-C filter center]\n"));
|
|
printf(_(" [-T 0..2] [-g filename] [-l words] [-d seconds]\n"));
|
|
printf(_(" [infile]\n\n"));
|
|
printf(_("defaults: 25 WpM, 600Hz, RT=FT=50, s=11025Hz, b=16kbps,\n"));
|
|
printf(_(" c=\"CHAPTER\", o=\"Chapter\" infile = stdin\n"));
|
|
printf("\n");
|
|
printf("Compile options: USE_LAME="
|
|
#ifdef LAME
|
|
"YES "
|
|
#else
|
|
"NO "
|
|
#endif
|
|
"USE_OGG="
|
|
#ifdef OGGV
|
|
"YES "
|
|
#else
|
|
"NO "
|
|
#endif
|
|
"DESTDIR="DESTDIR"\n");
|
|
printf("\n");
|
|
printf(_("Project website: http://fkurz.net/ham/ebook2cw.html\n"));
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
|
|
/*
|
|
* showcodes
|
|
* Generates a HTML table of the available CW symbols in codetables.h
|
|
* i = 1 -> ISO 8859-1, i=0 -> UTF-8
|
|
*/
|
|
|
|
void showcodes (int i) {
|
|
int k;
|
|
int n = i ? 256 : 1921;
|
|
|
|
printf("<html><head><META HTTP-EQUIV=\"CONTENT-TYPE\" CONTENT=\"text/html;"
|
|
"charset=%s\"></head>\n", i ? "iso-8859-1" : "utf-8");
|
|
printf("<body><h1>ebook2cw codetables.h</h1>\n");
|
|
printf("<h2>%s</h2><table border=\"1\">\n", i ? "ISO 8859-1" : "UTF-8");
|
|
printf("<tr><th>Decimal</th><th>Symbol</th><th>CW</th></tr>\n");
|
|
|
|
for (k=0; k < n; k++) {
|
|
printf("<tr>");
|
|
|
|
if ((k < 128+i*128) && (iso8859[k] != NULL)) {
|
|
printf("<td>%d</td><td>&#%d;</td><td>%s</td>", k, k, iso8859[k]);
|
|
}
|
|
else if (utf8table[k] != NULL) {
|
|
printf("<td>%d</td><td>&#%d;</td><td>%s</td>", k, k, utf8table[k]);
|
|
}
|
|
else {
|
|
printf("<td>%d</td><td>&#%d;</td><td> </td>", k, k);
|
|
}
|
|
|
|
printf("</tr>\n");
|
|
}
|
|
|
|
printf("</table></body></html>\n");
|
|
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
|
|
|
|
/* make sure the inpcm-buffer doesn't run full.
|
|
* has to consider the effects of Farnsworth and extra word spacing
|
|
* since memory is cheap and cpu cycles are precious, the size of the
|
|
* buffers is doubled each time they run full (if they ever do)
|
|
*/
|
|
|
|
void buf_check (int j, CWP *cw) {
|
|
|
|
if (j > cw->inpcm_size - cw->maxbytes) {
|
|
cw->inpcm_size *= 2;
|
|
cw->noisebuf_size = cw->inpcm_size;
|
|
cw->mp3buffer_size *= 2;
|
|
|
|
if ((cw->inpcm = realloc(cw->inpcm, cw->inpcm_size*sizeof(short int))) == NULL) {
|
|
fprintf(stderr, "Error: Can't realloc inpcm[%d]\n", cw->inpcm_size);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if ((cw->noisebuf = realloc(cw->noisebuf, cw->noisebuf_size*sizeof(short int)))== NULL) {
|
|
fprintf(stderr, "Error: Can't realloc noisebuf[%d]\n", cw->noisebuf_size);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
fillnoisebuffer(cw->noisebuf, cw->noisebuf_size, NOISEAMPLITUDE);
|
|
|
|
if ((cw->mp3buffer = realloc(cw->mp3buffer, cw->mp3buffer_size*sizeof(char))) == NULL) {
|
|
fprintf(stderr, "Error: Can't realloc mp3buffer[%d]\n", cw->mp3buffer_size);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* command
|
|
* Receives a 'word' which starts with a | and then (possibly) a command,
|
|
* to change speed or tone freq.
|
|
*/
|
|
|
|
void command (char * cmd, CWP *cw) {
|
|
|
|
int i = 0;
|
|
unsigned char c = 0;
|
|
|
|
sscanf(cmd, "|%c%d", &c, &i);
|
|
|
|
switch (c) {
|
|
case 'f':
|
|
if ((i > 100) && (i < (int) cw->samplerate/2)) {
|
|
cw->freq = i;
|
|
init_cw(cw);
|
|
}
|
|
else
|
|
fprintf(stderr, _("Invalid frequency: %s. Ignored.\n"), cmd);
|
|
break;
|
|
case 'w':
|
|
if (i > 1) {
|
|
cw->wpm = i;
|
|
init_cw(cw);
|
|
}
|
|
else
|
|
fprintf(stderr, _("Invalid speed: %s. Ignored.\n"), cmd);
|
|
break;
|
|
case 'e':
|
|
if (i > 1) {
|
|
cw->farnsworth = i;
|
|
init_cw(cw);
|
|
}
|
|
else
|
|
fprintf(stderr, _("Invalid speed: %s. Ignored.\n"), cmd);
|
|
break;
|
|
case 'T':
|
|
if (i >= 0 && i < 3) {
|
|
cw->waveform = i;
|
|
init_cw(cw);
|
|
}
|
|
else
|
|
fprintf(stderr, _("Invalid waveform: %d. Ignored.\n"), i);
|
|
break;
|
|
case 'v':
|
|
init_cw(cw);
|
|
scalebuffer(cw->dit_buf, cw->ditlen, (float) (i/100.0));
|
|
scalebuffer(cw->dah_buf, 3*cw->ditlen, (float) (i/100.0));
|
|
break;
|
|
case 'N':
|
|
cw->addnoise = 1;
|
|
cw->snr = i;
|
|
break;
|
|
case 'S':
|
|
if (i >= 1 && i <= 20000) {
|
|
add_silence(i, cw);
|
|
}
|
|
break;
|
|
case 'W':
|
|
if (i >= 0 && i <= 40) {
|
|
cw->ews = i;
|
|
init_cw(cw);
|
|
}
|
|
break;
|
|
default:
|
|
fprintf(stderr, _("Invalid command %s. Ignored.\n"), cmd);
|
|
}
|
|
}
|
|
|
|
/* readconfig
|
|
*
|
|
* reads a ebook2cw.conf file and sets parameters from the [settings]
|
|
* part, then looks for character mappings in the [mappings] part.
|
|
*
|
|
* ebook2cw.conf is first searched in the current directory, then
|
|
* ~/.ebook2cw/ and finally in DESTDIR/share/doc/ebook2cw/examples/
|
|
* and copied to ~/.ebook2cw/ if needed.
|
|
*
|
|
* A sample config `ebook2cw.conf' is included.
|
|
*/
|
|
|
|
void readconfig (CWP *cw) {
|
|
FILE *conf = NULL;
|
|
char tmp[1024] = "";
|
|
char p; /* parameter */
|
|
char v[80]=""; /* value */
|
|
static char mapfile[1024]="";
|
|
char *homedir = NULL;
|
|
|
|
/* Part 1:
|
|
* Find config file; if not found try to copy/install it to
|
|
* ~/.ebook2cw
|
|
*/
|
|
|
|
if ((conf = fopen(cw->configfile, "r")) == NULL) {
|
|
#if !__MINGW32__
|
|
/* ebook2cw.conf not in current directory */
|
|
if ((homedir = getenv("HOME")) == NULL) { /* no home or Windows */
|
|
return;
|
|
}
|
|
/* Look for ~/.ebook2cw/ebook2cw.conf */
|
|
snprintf(cw->configfile, 2048, "%s/.ebook2cw/ebook2cw.conf", homedir);
|
|
if ((conf = fopen(cw->configfile, "r")) == NULL) {
|
|
/* Not in ~/.ebook2cw/ either, look for it in
|
|
* DESTDIR/ebook2cw/examples/ */
|
|
if ((conf = fopen(DESTDIR
|
|
"/share/doc/ebook2cw/examples/ebook2cw.conf","r")) == NULL) {
|
|
/* cannot find ebook2cw.conf anywhere. silently return */
|
|
return;
|
|
}
|
|
/* First run, we install/copy the defaults to ~/.ebook2cw */
|
|
else if (install_config_files(homedir, cw) == 0) {
|
|
/* Files installed to ~/.ebook2cw/ */
|
|
snprintf(cw->configfile, 1024, "%s/.ebook2cw/ebook2cw.conf",
|
|
homedir);
|
|
|
|
/* open newly installed config file ... */
|
|
if ((conf = fopen(cw->configfile, "r")) == NULL) {
|
|
printf(_("Couldn't read %s! Continue without config."),
|
|
cw->configfile);
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
/* installing didn't work for some reason, silently
|
|
* return */
|
|
return;
|
|
}
|
|
}
|
|
#else
|
|
return; /* Win32 and no ebook2cw.conf in current dir -> return */
|
|
#endif
|
|
}
|
|
|
|
/* Part 2:
|
|
* Read config file
|
|
*/
|
|
|
|
printf(_("Reading configuration file: %s\n\n"), cw->configfile);
|
|
|
|
/* We start in the [settings] section.
|
|
* All settings are ^[a-zA-Z]=.+$
|
|
* */
|
|
|
|
while ((feof(conf) == 0) && (fgets(tmp, 80, conf) != NULL)) {
|
|
tmp[strlen(tmp)-1]='\0';
|
|
if (strstr(tmp, "[mappings]")) { /* all parameters read */
|
|
break;
|
|
}
|
|
else {
|
|
if (sscanf(tmp, "%c=%s", &p, v) == 2) {
|
|
setparameter(p, v, cw);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* mappings */
|
|
|
|
while ((feof(conf) == 0) && (fgets(tmp, 80, conf) != NULL)) {
|
|
tmp[strlen(tmp)-1]='\0';
|
|
if (sscanf(tmp, "isomapfile=%255s", mapfile) == 1) {
|
|
loadmapping(mapfile, ISO8859, cw);
|
|
}
|
|
else if (sscanf(tmp, "utf8mapfile=%255s", mapfile) == 1) {
|
|
loadmapping(mapfile, UTF8, cw);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void setparameter (char i, char *value, CWP *cw) {
|
|
|
|
switch (i) {
|
|
case 'w':
|
|
if ((cw->wpm = atoi(value)) < 1) {
|
|
fprintf(stderr, _("Error: Speed (-w) must be > 0!\n"));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
break;
|
|
case 'f':
|
|
cw->freq = atoi(value);
|
|
break;
|
|
case 'R':
|
|
cw->rt = atoi(value);
|
|
break;
|
|
case 'F':
|
|
cw->ft = atoi(value);
|
|
break;
|
|
case 's':
|
|
cw->samplerate = atoi(value);
|
|
break;
|
|
case 'b':
|
|
cw->brate = atoi(value);
|
|
break;
|
|
case 'q':
|
|
cw->quality = atoi(value);
|
|
break;
|
|
case 'd': /* chapter duration in seconds (optional!) */
|
|
cw->chaptertime = atoi(value);
|
|
break;
|
|
case 'l': /* chapter length in words (optional!) */
|
|
cw->chapterwords = atoi(value);
|
|
break;
|
|
case 'c':
|
|
strncpy(cw->chapterstr, value, 78);
|
|
break;
|
|
case 'o':
|
|
strncpy(cw->chapterfilename, value, PATH_MAX - 10);
|
|
break;
|
|
case 'a':
|
|
strncpy(cw->id3_author, value, 78);
|
|
break;
|
|
case 't':
|
|
strncpy(cw->id3_title, value, 75);
|
|
break;
|
|
case 'k':
|
|
strncpy(cw->id3_comment, value, 78);
|
|
break;
|
|
case 'y':
|
|
strncpy(cw->id3_year, value, 4);
|
|
break;
|
|
case 'Q':
|
|
if ((cw->qrq = atoi(value)) < 1) {
|
|
fprintf(stderr, _("Error: QRQ time (-Q) must be > 0!\n"));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
break;
|
|
case 'u':
|
|
cw->encoding = UTF8;
|
|
break;
|
|
case 'S':
|
|
if (strstr(value, "ISO")) {
|
|
showcodes(1);
|
|
}
|
|
else {
|
|
showcodes(0);
|
|
}
|
|
break;
|
|
case 'e': /* effective speed in WpM */
|
|
if ((cw->farnsworth = atoi(value)) < 1) {
|
|
fprintf(stderr, _("Error: Eff. speed (-e) must be > 0!\n"));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
break;
|
|
case 'W':
|
|
cw->ews = atof(value);
|
|
break;
|
|
case 'n':
|
|
cw->reset = 0;
|
|
break;
|
|
case 'O':
|
|
#ifdef OGGV
|
|
cw->encoder = OGG;
|
|
#else
|
|
fprintf(stderr, _("Warning: ebook2cw was compiled without OGG support! Using Lame encoder.\n\n"));
|
|
#endif
|
|
break;
|
|
case 'X': /* "Fake" */
|
|
cw->encoder = NOENC;
|
|
break;
|
|
case 'p':
|
|
cw->pBT = 0;
|
|
break;
|
|
case 'g':
|
|
guessencoding(value);
|
|
break;
|
|
case 'h':
|
|
help();
|
|
break;
|
|
case 'T':
|
|
if (strstr(value, "SINE") || strstr(value, "0")) {
|
|
cw->waveform = SINE;
|
|
}
|
|
else if (strstr(value, "SAWTOOTH") || strstr(value, "1")) {
|
|
cw->waveform = SAWTOOTH;
|
|
}
|
|
else if (strstr(value, "SQUARE") || strstr(value, "2")) {
|
|
cw->waveform = SQUARE;
|
|
}
|
|
break;
|
|
case 'N':
|
|
cw->snr = atoi(value);
|
|
cw->addnoise = 1;
|
|
if ((cw->snr < -10) || (cw->snr > 10)) {
|
|
fprintf(stderr, _("Warning: SNR %ddB not implemented."
|
|
" Noise disabled.\n\n"), cw->snr);
|
|
cw->addnoise = 0;
|
|
cw->snr = 0;
|
|
}
|
|
break;
|
|
case 'B':
|
|
cw->bandpassbw = atoi(value);
|
|
break;
|
|
case 'C':
|
|
cw->bandpassfc = atoi(value);
|
|
break;
|
|
case 'E':
|
|
strncpy(cw->configfile, value, 78);
|
|
readconfig(cw);
|
|
break;
|
|
|
|
} /* switch */
|
|
|
|
}
|
|
|
|
/* loadmapping
|
|
*
|
|
* opens a mapping file and writes the mappings to the mapping variables:
|
|
* index: utf8mapindex, isomapindex
|
|
* mappings: utf8map, isomap
|
|
*
|
|
*/
|
|
|
|
void loadmapping(char *file, int enc, CWP *cw) {
|
|
FILE *mp;
|
|
char tmp[81] = "";
|
|
int i=0;
|
|
int k=0;
|
|
char c1=0;
|
|
char c2=0;
|
|
char s[6]="";
|
|
|
|
if ((mp = fopen(file, "r")) == NULL) {
|
|
fprintf(stderr, _("Warning: Unable to open mapping file %s. Ignored.\n")
|
|
, file);
|
|
return;
|
|
}
|
|
|
|
switch (enc) {
|
|
case ISO8859:
|
|
memset(cw->isomapindex, 0, sizeof(char)*256);
|
|
cw->use_isomapping = 1;
|
|
break;
|
|
case UTF8:
|
|
memset(cw->utf8mapindex, 0, sizeof(int)*256);
|
|
cw->use_utf8mapping = 1;
|
|
break;
|
|
}
|
|
|
|
|
|
while ((feof(mp) == 0) && (fgets(tmp, 80, mp) != NULL)) {
|
|
tmp[strlen(tmp)-1]='\0';
|
|
|
|
switch (enc) {
|
|
case ISO8859:
|
|
if (sscanf(tmp, "%c=%3s", &c1, s) == 2) {
|
|
cw->isomapindex[i] = c1;
|
|
strncpy(cw->isomap[i], s, 3);
|
|
i++;
|
|
}
|
|
break;
|
|
case UTF8:
|
|
if (sscanf(tmp, "%c=%5s", &c1, s) == 2) {
|
|
cw->utf8mapindex[i] = c1;
|
|
strncpy(cw->utf8map[i], s, 5);
|
|
i++;
|
|
}
|
|
else if (sscanf(tmp, "%c%c=%5s", &c1, &c2, s) == 3) {
|
|
k = ((c1 & 31) << 6) | (c2 & 63); /* decimal */
|
|
cw->utf8mapindex[i] = k; /* unicode char */
|
|
strncpy(cw->utf8map[i], s, 5);
|
|
i++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* only memory for 256 mappings allocated. never going to happen */
|
|
if (i == 255) {
|
|
fprintf(stderr, _("Warning: Maximum number (256) of mappings "
|
|
"reached in %s! Stopped here."), file);
|
|
break;
|
|
}
|
|
|
|
} /* while */
|
|
|
|
}
|
|
|
|
|
|
/* mapstring
|
|
* receives a string and replaces all characters that show up in isomapindex /
|
|
* utf8mapindex with their replacement strings, and returns it.
|
|
*/
|
|
|
|
|
|
char *mapstring (char * string, CWP *cw) {
|
|
static char new[2048]="";
|
|
char c;
|
|
int i, j, replaced;
|
|
unsigned char last=0;
|
|
|
|
memset(new, 0, 2048);
|
|
|
|
switch (cw->encoding) {
|
|
case ISO8859:
|
|
if (!cw->use_isomapping) {
|
|
return string;
|
|
}
|
|
while ((c = *string++) != '\0') {
|
|
replaced = 0;
|
|
for (i=0; i < 255; i++) {
|
|
if (cw->isomapindex[i] == 0) {
|
|
break;
|
|
}
|
|
else if (cw->isomapindex[i] == c) {
|
|
strcat(new, cw->isomap[i]);
|
|
replaced = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!replaced) {
|
|
new[strlen(new)] = c;
|
|
}
|
|
}
|
|
break;
|
|
case UTF8:
|
|
if (!cw->use_utf8mapping) {
|
|
return string;
|
|
}
|
|
while ((c = *string++) != '\0') {
|
|
j = 0;
|
|
replaced = 0;
|
|
if (!last && (c & 128)) { /* first of a 2 char seq */
|
|
last = c;
|
|
continue;
|
|
}
|
|
if (last) {
|
|
/* 110yyyyy 10zzzzzz -> 00000yyy yyzzzzzz */
|
|
j = ((last & 31) << 6) | (c & 63);
|
|
}
|
|
else {
|
|
j = c;
|
|
}
|
|
for (i=0; i < 255; i++) {
|
|
if (cw->utf8mapindex[i] == 0) {
|
|
break;
|
|
}
|
|
else if (cw->utf8mapindex[i] == j) {
|
|
strcat(new, cw->utf8map[i]);
|
|
replaced = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!replaced) {
|
|
if (last) {
|
|
new[strlen(new)] = last;
|
|
new[strlen(new)] = c;
|
|
}
|
|
else {
|
|
new[strlen(new)] = c;
|
|
}
|
|
}
|
|
last = 0;
|
|
}
|
|
}
|
|
|
|
return new;
|
|
|
|
}
|
|
|
|
|
|
|
|
void buf_alloc(CWP *cw) {
|
|
|
|
/* pcm, noise and mp3 buffers will be increased later as needed, but the
|
|
* initial values should be sufficient for most speeds and reasonably long
|
|
* words */
|
|
if ((cw->inpcm = calloc(PCMBUFFER, sizeof(short int))) == NULL) {
|
|
fprintf(stderr, "Error: Can't allocate inpcm[%d]!\n", PCMBUFFER);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
cw->inpcm_size = PCMBUFFER;
|
|
|
|
if ((cw->noisebuf = calloc(NOISEBUFFER, sizeof(short int))) == NULL) {
|
|
fprintf(stderr, "Error: Can't allocate noisebuf[%d]!\n", NOISEBUFFER);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
cw->noisebuf_size = NOISEBUFFER;
|
|
fillnoisebuffer(cw->noisebuf, cw->noisebuf_size, NOISEAMPLITUDE);
|
|
|
|
if ((cw->mp3buffer = calloc(MP3BUFFER, sizeof(unsigned char))) == NULL) {
|
|
fprintf(stderr, "Error: Can't allocate mp3buffer[%d]!\n", MP3BUFFER);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
cw->mp3buffer_size = MP3BUFFER;
|
|
|
|
/* Just give it one byte now. It will be reallocated everytime init_cw is
|
|
* called, but to make it easy, allocate *something* to it now */
|
|
if ((cw->dah_buf = calloc(1, sizeof(short int))) == NULL) {
|
|
fprintf(stderr, "Error: Can't allocate dah_buf[1]!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if ((cw->dit_buf = calloc(1, sizeof(short int))) == NULL) {
|
|
fprintf(stderr, "Error: Can't allocate dit_buf[1]!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef CGI
|
|
|
|
/*
|
|
* URLDECODE stuff, needed for the CGI only:
|
|
* */
|
|
|
|
/* Anti-Web HTTPD */
|
|
/* Hardcore Software */
|
|
/*
|
|
This software is Copyright (C) 2001-2004 By Hardcore Software and
|
|
others. The software is distributed under the terms of the GNU General
|
|
Public License. See the file 'COPYING' for more details.
|
|
*/
|
|
|
|
/*
|
|
This code is Copyright (C) 2001 By Zas ( zas at norz.org )
|
|
The software is distributed under the terms of the GNU General Public
|
|
License. See the file 'COPYING' for more details.
|
|
*/
|
|
|
|
/* I might've modified this slightly, but it's definitley Zas'. -Fractal */
|
|
|
|
int hexit(char c) {
|
|
if ( c >= '0' && c <= '9' )
|
|
return c - '0';
|
|
if ( c >= 'a' && c <= 'f' )
|
|
return c - 'a' + 10;
|
|
if ( c >= 'A' && c <= 'F' )
|
|
return c - 'A' + 10;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Decode string %xx -> char (in place) */
|
|
void urldecode(char *buf) {
|
|
int v;
|
|
char *p, *s, *w;
|
|
|
|
w=p=buf;
|
|
while (*p) {
|
|
v=0;
|
|
|
|
if (*p=='%') {
|
|
s=p;
|
|
s++;
|
|
|
|
if (isxdigit((int) s[0]) && isxdigit((int) s[1]) ) {
|
|
v=hexit(s[0])*16+hexit(s[1]);
|
|
if (v) { /* do not decode %00 to null char */
|
|
*w=(char)v;
|
|
p=&s[1];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (!v) *w=*p;
|
|
p++; w++;
|
|
}
|
|
*w='\0';
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif /* ifdef CGI */
|
|
|
|
/* addnoise:
|
|
adds noise with bandwidth, center frequency and SNR as defined in CWP
|
|
noise amplitude is constant, the CW amplitude is scaled.
|
|
*/
|
|
|
|
void addnoise (int length, CWP *cw) {
|
|
scalebuffer(cw->inpcm, cw->inpcm_size, snramplitude(cw->snr));
|
|
addbuffer(cw->inpcm, cw->noisebuf, cw->inpcm_size);
|
|
filterloop(cw->inpcm, cw->inpcm_size, cw->bandpassbw);
|
|
}
|
|
|
|
/* snramplitude - returns the amplitude needed for a SNR of "snr" dB as
|
|
float; noise average amplitude considered to be 0.25
|
|
Currently implemented by a lookup table with fixed, precalculated values.
|
|
*/
|
|
float snramplitude (int snr) {
|
|
/* Lookup table; first value = -10dB, last +10dB */
|
|
float snrlookup[21] = { 0.042, 0.0475, 0.0533, 0.0599, 0.067, 0.075, 0.084, 0.094,
|
|
0.1055, 0.1187, 0.134, 0.15, 0.168, 0.19, 0.213, 0.238, 0.267, 0.3, 0.335, 0.378, 0.425 };
|
|
|
|
return snrlookup[snr+10];
|
|
}
|
|
|
|
|
|
void fillnoisebuffer (short int *buf, int size, float amplitude) {
|
|
int i;
|
|
|
|
for (i=0; i < size; i++) {
|
|
buf[i] = (short int) (2.0*amplitude*(rand()/(1.0*RAND_MAX)-0.5));
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Digital filter designed by mkfilter/mkshape/gencode A.J. Fisher Command
|
|
* line: /www/usr/fisher/helpers/mkfilter -Bu -Bp -o 3 -a 3.7500000000e-02
|
|
* 1.6250000000e-01 -l
|
|
*
|
|
* Modified to filter "in place" with one array (buf) of length l
|
|
*/
|
|
|
|
void filterloop (short int *buf, int l, int b) {
|
|
static float xv[7], yv[7];
|
|
short int *in;
|
|
|
|
/* 4 filters with each 6 coefficients */
|
|
static float c[4][6] = {
|
|
/* 100Hz */
|
|
{-0.6235385946, 3.3779247772, -8.2824221345, 11.5675604240,
|
|
-9.6967045468, 4.6299593645},
|
|
/* 500Hz */
|
|
{-0.4535459334, 2.5370880100, -6.4847845669, 9.5144072211,
|
|
-8.4472239809, 4.3051177389},
|
|
/* 1000Hz */
|
|
{-0.1978251873, 1.3168771088, -3.8803884946, 6.5449240143,
|
|
-6.6950970397, 3.9046544087},
|
|
/* 2100Hz */
|
|
{0.0131505881, 0.2450198734, -0.6698556894, 0.9667482217,
|
|
-1.8996423954, 2.3375093931}
|
|
};
|
|
|
|
static float gain[4] = { 7.637953014e+02, 1.886640723e+02, 3.154970680e+01,
|
|
5.346000407e+00 };
|
|
|
|
long int k;
|
|
|
|
/* bandwidth -> filter parameters */
|
|
if (b < 500 ) { b = 0; }
|
|
else if (b < 1000) { b = 1; }
|
|
else if (b < 2100) { b = 2; }
|
|
else { b = 4; }
|
|
|
|
in = calloc(l, sizeof (short int));
|
|
memcpy(in, buf, l*sizeof(short int));
|
|
|
|
for (k = 0; k < l; k++)
|
|
{
|
|
xv[0] = xv[1]; xv[1] = xv[2]; xv[2] = xv[3];
|
|
xv[3] = xv[4]; xv[4] = xv[5]; xv[5] = xv[6];
|
|
|
|
xv[6] = ((float) in[k] / 255) / gain[b];
|
|
|
|
yv[0] = yv[1]; yv[1] = yv[2]; yv[2] = yv[3];
|
|
yv[3] = yv[4]; yv[4] = yv[5]; yv[5] = yv[6];
|
|
|
|
yv[6] = (xv[6] - xv[0]) + 3 * (xv[2] - xv[4])
|
|
+ (c[b][0] * yv[0]) + (c[b][1] * yv[1])
|
|
+ (c[b][2] * yv[2]) + (c[b][3] * yv[3])
|
|
+ (c[b][4] * yv[4]) + (c[b][5] * yv[5]);
|
|
|
|
buf[k] = (short int) (yv[6] * 255.0);
|
|
}
|
|
free(in);
|
|
}
|
|
|
|
|
|
void scalebuffer(short int *buf, int length, float factor) {
|
|
int i;
|
|
for (i=0; i < length; i++) {
|
|
buf[i] = (short int) buf[i]*factor;
|
|
}
|
|
}
|
|
|
|
|
|
/* Adds buffer b2 to b1 with length l */
|
|
|
|
void addbuffer (short int *b1, short int *b2, int l) {
|
|
int i;
|
|
for (i=0; i < l; i++) {
|
|
b1[i] += b2[i];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void init_encoder (CWP *cw) {
|
|
#ifdef OGGV
|
|
ogg_packet hdr;
|
|
ogg_packet hdr_comm;
|
|
ogg_packet hdr_code;
|
|
#endif
|
|
|
|
if (cw->encoder == MP3) {
|
|
#ifdef LAME
|
|
gfp = lame_init();
|
|
lame_set_num_channels(gfp,1);
|
|
lame_set_brate(gfp, cw->brate);
|
|
lame_set_in_samplerate(gfp, cw->samplerate);
|
|
lame_set_out_samplerate(gfp, cw->samplerate);
|
|
lame_set_mode(gfp,1);
|
|
lame_set_quality(gfp, cw->quality);
|
|
|
|
if (lame_init_params(gfp) < 0) {
|
|
fprintf(stderr, "Failed: lame_init_params(gfp).\n\nPossible reason:\n * Bad sample rate, must be: 8k, 11.025k, 12k, 16k, 22.05k, 32k, 44.1k, 48k\n * Selected samplerate too high for selected bitrate.\n");
|
|
exit(1);
|
|
}
|
|
#endif
|
|
}
|
|
else { /* OGG */
|
|
#ifdef OGGV
|
|
vorbis_info_init(&vi);
|
|
|
|
if (vorbis_encode_init_vbr(&vi,1,cw->samplerate,0.7)) {
|
|
fprintf(stderr, "Failed: vorbis_encode_init_vbr()\n");
|
|
exit(1);
|
|
}
|
|
|
|
vorbis_comment_init(&vc);
|
|
vorbis_comment_add_tag(&vc,"ENCODER","ebook2cw");
|
|
vorbis_analysis_init(&vd, &vi);
|
|
vorbis_block_init(&vd, &vb);
|
|
ogg_stream_init(&os,rand());
|
|
|
|
vorbis_analysis_headerout(&vd,&vc,&hdr,&hdr_comm,&hdr_code);
|
|
ogg_stream_packetin(&os,&hdr);
|
|
ogg_stream_packetin(&os,&hdr_comm);
|
|
ogg_stream_packetin(&os,&hdr_code);
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void ogg_encode_and_write (CWP *cw) {
|
|
/* vorbis_analysis_wrote() must have been called */
|
|
#ifdef OGGV
|
|
/* Encode and write */
|
|
while (vorbis_analysis_blockout(&vd,&vb) == 1) {
|
|
vorbis_analysis(&vb, NULL);
|
|
vorbis_bitrate_addblock(&vb);
|
|
while (vorbis_bitrate_flushpacket(&vd,&op)) {
|
|
ogg_stream_packetin(&os,&op);
|
|
while (1) {
|
|
int result = ogg_stream_pageout(&os,&og);
|
|
if (result == 0) break;
|
|
fwrite(og.header,1,og.header_len, cw->outfile);
|
|
fwrite(og.body,1,og.body_len, cw->outfile);
|
|
cw->outfile_length += og.header_len;
|
|
cw->outfile_length += og.body_len;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* current content of the cw.inpcm buffer is encoded and written
|
|
* to the cw.outfile */
|
|
|
|
void encode_buffer (int length, CWP *cw) {
|
|
#ifdef LAME
|
|
int outbytes;
|
|
#endif
|
|
#ifdef OGGV
|
|
int i;
|
|
float **buffer; /* for OGG enc only */
|
|
#endif
|
|
|
|
switch (cw->encoder) {
|
|
case MP3:
|
|
#ifdef LAME
|
|
outbytes = lame_encode_buffer(gfp, cw->inpcm, cw->inpcm, length,
|
|
cw->mp3buffer, cw->mp3buffer_size);
|
|
if (outbytes < 0) {
|
|
fprintf(stderr, "Error: lame_encode_buffer returned %d. "
|
|
"Exit.\n", outbytes);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (fwrite(cw->mp3buffer, sizeof(char), outbytes, cw->outfile)
|
|
!= outbytes) {
|
|
fprintf(stderr, "Error: Writing %db to file failed. Exit.\n",
|
|
outbytes);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
cw->outfile_length += outbytes;
|
|
#endif
|
|
break;
|
|
case OGG:
|
|
#ifdef OGGV
|
|
buffer = vorbis_analysis_buffer(&vd, length);
|
|
for (i=0; i < length; i++) {
|
|
buffer[0][i] = (float) cw->inpcm[i]/(1.8*CWAMPLITUDE);
|
|
}
|
|
vorbis_analysis_wrote(&vd,i);
|
|
ogg_encode_and_write(cw);
|
|
#endif
|
|
break;
|
|
case NOENC:
|
|
/* do nothing at all */
|
|
break;
|
|
} /* switch */
|
|
|
|
}
|
|
|
|
/* pretty print of a input value (milliseconds) as a string in the
|
|
* form: hh:mm:ss or mm:ss or ss, depending on the length */
|
|
|
|
char *timestring (int ms) {
|
|
int h = 0, m = 0, s = 0;
|
|
static char t[32];
|
|
while (ms > 3600000) { /* hours */
|
|
h++;
|
|
ms -= 3600000;
|
|
}
|
|
while (ms > 60000) { /* minutes */
|
|
m++;
|
|
ms -= 60000;
|
|
}
|
|
while (ms > 1000) { /* seconds */
|
|
s++;
|
|
ms -= 1000;
|
|
}
|
|
|
|
if (h) {
|
|
snprintf(t, 31, "%d:%02d:%02d", h, m, s);
|
|
}
|
|
else if (m) {
|
|
snprintf(t, 31, "%02d:%02d", m, s);
|
|
}
|
|
else {
|
|
snprintf(t, 31, "%ds", s);
|
|
}
|
|
|
|
return t;
|
|
}
|
|
|
|
|
|
/* tries to guess the encoding (UTF8, ISO8859-1) of a file
|
|
*
|
|
* Indicator for UTF-8 (2-byte chars): 110xxxxx 10xxxxxx
|
|
*
|
|
*/
|
|
|
|
void guessencoding (char *filename) {
|
|
FILE *infile;
|
|
unsigned int c;
|
|
int is_iso = 1;
|
|
|
|
if ((infile = fopen(filename, "r")) == NULL) {
|
|
fprintf(stderr, _("Error: Cannot open file %s. Exit.\n"), filename);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
printf(_("Guessed file format of %s: "), filename);
|
|
|
|
while ((c = getc(infile)) != EOF) {
|
|
if ((c & 224) == 192) { /* check if highest 3 bits are 110=224 */
|
|
c = getc(infile); /* next byte 10xxxxxx? */
|
|
if ((c & 192) == 128) {
|
|
is_iso = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
printf("%s\n\n", (is_iso ? "ISO 8859-1" : "UTF-8"));
|
|
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
/* Flush OGG stream. Originally this was in the openfile()
|
|
* function but since openfile() is not called in CGI mode,
|
|
* but this flushing is necessary, it needed
|
|
* to be put in a separate function.
|
|
*/
|
|
|
|
void flush_ogg (CWP *cw) {
|
|
#ifdef OGGV
|
|
while (ogg_stream_flush(&os,&og)) {
|
|
fwrite(og.header,1,og.header_len,cw->outfile);
|
|
fwrite(og.body,1,og.body_len,cw->outfile);
|
|
cw->outfile_length += og.header_len;
|
|
cw->outfile_length += og.body_len;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Add ms milliseconds of silence to the current file
|
|
*/
|
|
|
|
void add_silence (int ms, CWP *cw) {
|
|
int i, pos;
|
|
i = ms*cw->samplerate/1000.0;
|
|
for (pos = 0; pos < i; pos++) {
|
|
cw->inpcm[pos] = 0;
|
|
}
|
|
encode_buffer(i, cw);
|
|
}
|
|
|
|
/* readconfig calls this if
|
|
* no config files were found in ~/.ebook2cw
|
|
* but in DESTDIT/share/doc/ebook2cw.
|
|
* They will then be compied to ~/.ebook2cw
|
|
*
|
|
* Return: Success NULL
|
|
* Some failure -1
|
|
*
|
|
* Moved from readfile to a separate function.
|
|
*/
|
|
|
|
int install_config_files (char *homedir, CWP *cw) {
|
|
#if !__MINGW32__
|
|
int j = 0;
|
|
char tmp[1024] = "";
|
|
|
|
printf(_("First run. Copying example configuration files to "
|
|
"%s/.ebook2cw/\n\n"), homedir);
|
|
snprintf(tmp, 1024, "%s/.ebook2cw/", homedir);
|
|
j = mkdir(tmp, 0777);
|
|
if (j && (errno != EEXIST)) {
|
|
printf(_("Failed to create %s. Resuming without config."), tmp);
|
|
return -1;
|
|
}
|
|
/* ~/.ebook2cw/ created. Now copy ebook2cw.conf and map files */
|
|
snprintf(tmp, 1024, "install -m 644 "DESTDIR
|
|
"/share/doc/ebook2cw/examples/ebook2cw.conf %s/.ebook2cw/",
|
|
homedir);
|
|
if (system(tmp)) {
|
|
printf(_("Failed to create ~/.ebook2cw/ebook2cw.conf. "
|
|
"Resuming without config."));
|
|
return -1;
|
|
}
|
|
|
|
snprintf(tmp, 1024, "install -m 644 "DESTDIR
|
|
"/share/doc/ebook2cw/examples/isomap.txt %s/.ebook2cw/",
|
|
homedir);
|
|
if (system(tmp)) {
|
|
printf(_("Failed to create ~/.ebook2cw/isomap.txt. "
|
|
"Resuming without config."));
|
|
return -1;
|
|
}
|
|
|
|
snprintf(tmp, 1024, "install -m 644 "DESTDIR
|
|
"/share/doc/ebook2cw/examples/utf8map.txt %s/.ebook2cw/",
|
|
homedir);
|
|
if (system(tmp)) {
|
|
printf(_("Failed to create ~/.ebook2cw/utf8map.txt. "
|
|
"Resuming without config."));
|
|
return -1;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/* We jump here in case of SIGINT. On Win32
|
|
* the program will terminate anyway, so the
|
|
* longjmp and cleanup procedure will not
|
|
* work. */
|
|
#ifndef CGI
|
|
void signalhandler (int signal) {
|
|
#if !__MINGW32__
|
|
printf(_("Caught SIGINT. Cleaning up.\n"));
|
|
longjmp(jmp,1);
|
|
#else
|
|
printf(_("Caught SIGINT. Terminating.\n"));
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/* Initialisation of cw parameter struct */
|
|
void init_cwp (CWP *cw) {
|
|
cw->wpm = 25;
|
|
cw->freq = 600;
|
|
cw->rt = 50;
|
|
cw->ft = 50;
|
|
cw->qrq = 0;
|
|
cw->reset = 1;
|
|
cw->farnsworth = 0;
|
|
cw->ews = 0.0;
|
|
cw->pBT = 1;
|
|
cw->waveform = SINE;
|
|
|
|
cw->bandpassbw = 500;
|
|
cw->bandpassfc = 800;
|
|
cw->addnoise = 0;
|
|
cw->snr = 0;
|
|
|
|
#ifdef LAME
|
|
cw->encoder = MP3;
|
|
#else
|
|
cw->encoder = OGG;
|
|
#endif
|
|
|
|
cw->samplerate = 11025;
|
|
cw->brate = 16;
|
|
cw->quality = 5;
|
|
|
|
cw->inpcm_size = PCMBUFFER;
|
|
cw->noisebuf_size = NOISEBUFFER;
|
|
cw->mp3buffer_size = MP3BUFFER;
|
|
cw->ditlen = 0;
|
|
strcpy(cw->chapterstr, _("CHAPTER"));
|
|
strcpy(cw->chapterfilename, _("Chapter"));
|
|
|
|
cw->chaptertime = 0;
|
|
cw->chapterwords = 0;
|
|
|
|
cw->encoding = ISO8859;
|
|
cw->use_isomapping = cw->use_utf8mapping = 0;
|
|
|
|
cw->comment = 0;
|
|
|
|
cw->linecount = 1;
|
|
cw->linepos = 0;
|
|
|
|
strcpy(cw->configfile, "ebook2cw.conf");
|
|
|
|
strcpy(cw->id3_author, _("CW audio book"));
|
|
strcpy(cw->id3_title, "");
|
|
strcpy(cw->id3_comment, _("Generated by ebook2cw"));
|
|
strcpy(cw->id3_year, "");
|
|
|
|
cw->outfile_length = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* vim: noai:ts=4:sw=4
|
|
* */
|