ebook2cw/ebook2cw.c

934 lines
21 KiB
C

/*
ebook2cw - converts an ebook to morse mp3s
Copyright (C) 2008 Fabian Kurz, DJ1YFK
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 tw=4
*/
#include "lame/lame.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include "codetables.h"
#ifndef VERSION
#define VERSION "0.0.0"
#endif
#define PCMBUFFER 220500 /* 20 seconds at 11kHz, for one word. plenty. */
#define MP3BUFFER 285000 /* abt 1.25*PCMBUFFER + 7200, as recommended */
#define ISO8859 0
#define UTF8 1
lame_global_flags *gfp;
FILE *outfile;
FILE *infile;
/* MP3 samplerate, bitrate, quality. */
int samplerate = 11025;
int brate = 16;
int quality = 5;
/* CW parameters */
int wpm = 25;
int freq = 600;
int rt = 50; /* rise/fall time in samples */
int ft = 50;
int qrq = 0; /* rise speed by 1 WpM every qrq minutes */
int reset = 1; /* reset QRQed speed every chapter? */
int farnsworth = 0; /* extra spacing in dit-lengths */
float ews = 0.0; /* extra word spaces */
int pBT = 1; /* send <BT> (-...-) for each new paragraph */
/* the buffers for dit, dah, raw pcm and mp3 output, will be calloc'ed to the
* default values, and -if needed- realloc'ed to the needed size */
short int *dit_buf;
short int *dah_buf;
short int *inpcm;
unsigned char *mp3buffer;
int inpcm_size;
int mp3buffer_size;
int ditlen=0; /* number of samples in a 'dit' */
/* Chapters are split by this string */
char chapterstr[80]="CHAPTER";
char chapterfilename[80]="Chapter"; /* full filename: Chapter%04d.mp3 */
/* Character encoding, mapping, etc */
int encoding = ISO8859;
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 use_isomapping = 0;
int use_utf8mapping = 0;
/* Config file location */
char configfile[1024] = "ebook2cw.conf";
/* ID3-tag data: author and title */
char id3_author[80]="CW audio book";
char id3_title[80]="";
char id3_comment[80]="";
char id3_year[5]="";
/* functions */
int init_cw (int wpm, int freq, int rt, int ft);
void help (void);
void showcodes (int i);
void makeword(char * text);
void closefile (int letter, int cw);
void openfile (int chapter);
void buf_check (int j);
void command (char * cmd);
void readconfig (void);
void setparameter (char p, char * value);
void loadmapping(char *filename, int enc);
char *mapstring (char *string);
/* main */
int main (int argc, char** argv) {
int pos, i, original_wpm;
int c;
char word[1024]=""; /* will be cut when > 1024 chars long */
int chapter = 0;
int cw = 0, tw = 0, qw = 0; /* chapter words, total words, qrq words */
infile = stdin;
printf("ebook2cw %s - (c) 2008 by Fabian Kurz, DJ1YFK\n\n", VERSION);
/*
* Find and read ebook2cw.conf
*/
readconfig();
while((i=getopt(argc,argv, "o:w:W:e:f:uc:k:Q:R:pF:s:b:q:a:t:y:S:hn"))!= -1){
setparameter(i, optarg);
} /* while */
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);
}
}
}
/* init lame */
gfp = lame_init();
lame_set_num_channels(gfp,1);
lame_set_in_samplerate(gfp, samplerate);
lame_set_brate(gfp, brate);
lame_set_mode(gfp,1);
lame_set_quality(gfp, quality);
if (lame_init_params(gfp) < 0) {
fprintf(stderr, "Failed: lame_init_params(gfp) \n");
return(1);
}
/* init pcm_buf and mp3_buf */
if ((inpcm = calloc(PCMBUFFER, sizeof(short int))) == NULL) {
fprintf(stderr, "Error: Can't allocate inpcm[%d]!\n", PCMBUFFER);
exit(EXIT_FAILURE);
}
else {
inpcm_size = PCMBUFFER;
}
if ((mp3buffer = calloc(MP3BUFFER, sizeof(unsigned char))) == NULL) {
fprintf(stderr, "Error: Can't allocate mp3buffer[%d]!\n", MP3BUFFER);
exit(EXIT_FAILURE);
}
else {
mp3buffer_size = MP3BUFFER;
}
printf("Speed: %dwpm, Freq: %dHz, Chapter: >%s<, Encoding: %s\n",
wpm, freq, chapterstr, encoding == UTF8 ? "UTF-8" : "ISO 8859-1");
printf("Effective speed: %dwpm, ", farnsworth ? farnsworth : wpm);
printf("Extra word spaces: %1.1f, ", ews);
printf("QRQ: %dmin, reset QRQ: %s\n\n", qrq, reset ? "yes" : "no");
ditlen = init_cw(wpm, freq, rt, ft); /* generate raw dit, dah */
strcat(chapterstr, " ");
/* read STDIN, assemble full words (anything ending in ' ') to 'word' and
* generate CW, write to file by 'makeword'. words with > 1024 characters
* will be split */
original_wpm = wpm; /* may be changed by QRQing */
chapter = 0;
openfile(chapter);
pos=0;
while ((c = getc(infile)) != EOF) {
if (c == '\r') /* DOS linebreaks */
continue;
word[pos++] = c;
if ((c == ' ') || (c == '\n') || (pos == 1024)) {
word[pos] = '\0';
if (strcmp(chapterstr, word) == 0) { /* new chapter */
closefile(chapter, cw);
cw = 0;
chapter++;
if (qrq && reset) {
wpm = original_wpm;
ditlen = init_cw(wpm, freq, rt, ft);
qw = 0;
}
openfile(chapter);
}
/* check for commands: |f or |w */
if (word[0] == '|') {
command(word);
}
else {
makeword(mapstring(word));
cw++; tw++; qw++;
}
word[0] = '\0';
pos = 0;
/* Every 'qrq' minutes (qrq*wpm words), speed up 1 WpM */
if (qw == (qrq*wpm)) {
wpm += 1;
ditlen = init_cw(wpm, freq, rt, ft);
printf("QRQ: %dwpm\n", wpm);
qw = 0;
}
} /* word */
} /* eof */
closefile(chapter, cw);
free(mp3buffer);
free(inpcm);
/* factor 0.9 due to many 'words' which aren't actually words, like '\n' */
printf("Total words: %d, total time: %d min\n", tw, (int) ((tw/wpm)*0.9));
lame_close(gfp);
return(0);
}
/* init_cw - generates a dit and a dah to dit_buf and dah_buf */
int init_cw (int wpm, int freq, int rt, int ft) {
int x, len;
double val;
/* set farnsworth to samples */
if (farnsworth) {
if (farnsworth > wpm) {
fprintf(stderr, "Error: Effective speed (-e %d) must be lower "
"than character speed (-w %d)!\n", farnsworth, wpm);
exit(EXIT_FAILURE);
}
farnsworth = (int) (6.0*samplerate/(5.0*farnsworth));
}
/* printf("Initializing CW buffers at %d WpM: ", wpm); */
/* dah */
len = (int) (3.0*6.0*samplerate/(5.0*wpm)); /* in samples */
/* ditlen not set == init_cw has not been called yet */
if (!ditlen) {
/* allocate memory for the buffer */
if ((dah_buf = calloc(len, sizeof(short int))) == NULL) {
fprintf(stderr, "Error: Can't allocate dah_buf[%d]\n", len);
exit(EXIT_FAILURE);
}
}
for (x=0; x < len; x++) {
val = sin(2*M_PI*freq*x/samplerate);
if (x < rt) /* shaping with sin^2 */
val *= pow(sin(M_PI*x/(2.0*rt)),2);
if (x > (len-ft))
val *= pow((sin(2*M_PI*(x-(len-ft)+ft)/(4*ft))), 2);
dah_buf[x] = (short int) (val * 20000.0);
}
/* printf("dah_buf[%d], ", x); */
/* dit */
len = (int) (6.0*samplerate/(5.0*wpm)); /* in samples */
/* ditlen not set == init_cw has not been called yet */
if (!ditlen) {
/* allocate memory for the buffer */
if ((dit_buf = calloc(len, sizeof(short int))) == NULL) {
fprintf(stderr, "Error: Can't allocate dit_buf[%d]\n", len);
exit(EXIT_FAILURE);
}
}
for (x=0; x < len; x++) {
val = sin(2*M_PI*freq*x/samplerate);
if (x < rt)
val *= pow(sin(M_PI*x/(2.0*rt)), 2);
if (x > (len-ft))
val *= pow((sin(2*M_PI*(x-(len-ft)+ft)/(4*ft))), 2);
dit_buf[x] = (short int) (val * 20000.0);
}
/* printf("dit_buf[%d]\n\n", x); */
return x; /* = length of dit/silence */
}
/* makeword -- converts 'text' to CW by concatenating dit_buf amd dah_bufs,
* encodes this to MP3 and writes to open filehandle outfile */
void makeword(char * text) {
const char *code; /* CW code as . and - */
int outbytes;
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;
if (c == '\n') { /* Same for UTF8 and ISO8859 */
if ((strlen(text) == 1) && pBT) /* paragraph */
code = " -...- ";
else
code = " ";
}
else if (c == '<') { /* prosign on */
prosign = 1;
continue;
}
else if ((c == '>') && prosign) { /* prosign off */
prosign = 0;
code = ""; /* only inserts letter space */
}
else if (encoding == ISO8859) {
code = iso8859[c];
}
else if (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 ... */
if (code == NULL) {
code = " ";
if (c < 255) {
fprintf(stderr, "Warning: don't know CW for '%c'\n", c);
}
else {
fprintf(stderr, "Warning: don't know CW for unicode &#%d;\n", c);
}
}
/* 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);
c = code[w];
if (c == '.')
for (u=0; u < ditlen; u++)
inpcm[++j] = dit_buf[u];
else if (c == '-')
for (u=0; u < (3*ditlen); u++)
inpcm[++j] = dah_buf[u];
else /* word space */
for (u=0;u < (int)(1+7*ews)*(farnsworth ? farnsworth : ditlen); u++)
inpcm[++j] = 0;
for (u=0; u < ditlen; u++) /* space of one dit length */
inpcm[++j] = 0;
} /* foreach dot/dash */
if (prosign == 0) {
for (u=0; u < (farnsworth ? 3*farnsworth-ditlen : 2*ditlen); u++)
inpcm[++j] = 0;
}
} /* foreach letter */
/* j = total length of samples in 'inpcm' */
outbytes = lame_encode_buffer(gfp, inpcm, inpcm, j, mp3buffer, mp3buffer_size);
if (outbytes < 0) {
fprintf(stderr, "Error: lame_encode_buffer returned %d. Exit.\n", outbytes);
exit(EXIT_FAILURE);
}
if (fwrite(mp3buffer, sizeof(char), outbytes, outfile) != outbytes) {
fprintf(stderr, "Error: Writing %db to file failed. Exit.\n", outbytes);
exit(EXIT_FAILURE);
}
}
/* closefile -- finishes writing the current file, flushes the encoder buffer */
void closefile (int letter, int cw) {
int outbytes;
char mp3filename[80] = "";
printf("words: %d, minutes: %d\n", cw, (int) ((cw/wpm)*0.9));
snprintf(mp3filename, 80, "%s%04d.mp3", chapterfilename, letter);
printf("Finishing %s\n\n", mp3filename);
outbytes = lame_encode_flush(gfp, mp3buffer, mp3buffer_size);
if (outbytes < 0) {
fprintf(stderr, "Error: lame_encode_buffer returned %d.\n", outbytes);
exit(EXIT_FAILURE);
}
if (fwrite(mp3buffer, sizeof(char), outbytes, outfile) != outbytes) {
fprintf(stderr, "Error: Writing %db to file failed. Exit.\n", outbytes);
exit(EXIT_FAILURE);
}
fclose(outfile);
}
/* openfile -- starts a new chapter by opening a new file as outfile */
void openfile (int chapter) {
char mp3filename[80] = "";
static char tmp[80] = "";
snprintf(mp3filename, 80, "%s%04d.mp3", chapterfilename, chapter);
printf("Starting %s\n", mp3filename);
if ((outfile = fopen(mp3filename, "wb")) == NULL) {
fprintf(stderr, "Error: Failed to open %s\n", mp3filename);
exit(EXIT_FAILURE);
}
snprintf(tmp, 79, "%s - %d", id3_title, chapter); /* generate title */
id3tag_init(gfp);
id3tag_set_artist(gfp, id3_author);
id3tag_set_year(gfp, id3_year);
id3tag_set_title(gfp, tmp);
id3tag_set_comment(gfp, id3_comment);
}
void help (void) {
printf("ebook2cw v%s - (c) 2008 by Fabian Kurz, DJ1YFK, http://fkurz.net/\n", VERSION);
printf("\nThis 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(" [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");
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>&nbsp;</td>", k, k);
}
printf("</tr>\n");
}
printf("</table></body></html>\n");
exit(EXIT_SUCCESS);
}
/* make sure the inpcm-buffer doesn't run full.
* have to consider the effects of Farnsworth and extra word spacing */
void buf_check (int j) {
int max;
/* maximum bytes that may be added for one dot or dah, for either
* farnsworth or non-farnsworth */
max = farnsworth ? (int) ((4+7*ews)*(farnsworth)) : (6+7*ews) * ditlen;
max += 10000; /* some margin to feel safe ... */
if (j > inpcm_size - max) {
inpcm_size += max;
mp3buffer_size += (int) (1.25 * max + 7200.0);
if ((inpcm = realloc(inpcm, inpcm_size*sizeof(short int)))== NULL) {
fprintf(stderr, "Error: Can't realloc inpcm[%d]\n", inpcm_size);
exit(EXIT_FAILURE);
}
if ((mp3buffer = realloc(mp3buffer, mp3buffer_size*sizeof(char)))
== NULL) {
fprintf(stderr, "Error: Can't realloc mp3buffer[%d]\n",
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) {
int i = 0;
unsigned char c = 0;
sscanf(cmd, "|%c%d", &c, &i);
switch (c) {
case 'f':
if ((i > 100) && (i < (int) samplerate/2)) {
freq = i;
init_cw(wpm, freq, rt, ft);
}
else
fprintf(stderr, "Invalid frequency: %s. Ignored.\n", cmd);
break;
case 'w':
if (i > 1) {
wpm = i;
ditlen = init_cw(wpm, freq, rt, ft);
}
else
fprintf(stderr, "Invalid speed: %s. Ignored.\n", cmd);
break;
case 'e':
if (i > 1) {
farnsworth = i;
ditlen = init_cw(wpm, freq, rt, ft);
}
else
fprintf(stderr, "Invalid speed: %s. Ignored.\n", cmd);
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.
*
* A sample config `ebook2cw.conf' is included.
*/
void readconfig (void) {
FILE *conf = NULL;
char tmp[81] = "";
char p; /* parameter */
char v[80]=""; /* value */
static char mapfile[1024]="";
if ((conf = fopen(configfile, "r")) == NULL) {
return; /* No config found -> silently ignore */
}
printf("Reading configuration file: %s\n\n", 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);
}
}
}
/* 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);
}
else if (sscanf(tmp, "utf8mapfile=%255s", mapfile) == 1) {
loadmapping(mapfile, UTF8);
}
}
}
void setparameter (char i, char *value) {
switch (i) {
case 'w':
if ((wpm = atoi(value)) < 1) {
fprintf(stderr, "Error: Speed (-w) must be > 0!\n");
exit(EXIT_FAILURE);
}
break;
case 'f':
freq = atoi(value);
break;
case 'R':
rt = atoi(value);
break;
case 'F':
ft = atoi(value);
break;
case 's':
samplerate = atoi(value);
break;
case 'b':
brate = atoi(value);
break;
case 'q':
quality = atoi(value);
break;
case 'c':
strncpy(chapterstr, value, 78);
break;
case 'o':
strncpy(chapterfilename, value, 78);
break;
case 'a':
strncpy(id3_author, value, 78);
break;
case 't':
strncpy(id3_title, value, 75);
break;
case 'k':
strncpy(id3_comment, value, 78);
break;
case 'y':
strncpy(id3_year, value, 4);
break;
case 'Q':
if ((qrq = atoi(value)) < 1) {
fprintf(stderr, "Error: QRQ time (-Q) must be > 0!\n");
exit(EXIT_FAILURE);
}
break;
case 'u':
encoding = UTF8;
break;
case 'S':
if (strstr(value, "ISO")) {
showcodes(1);
}
else {
showcodes(0);
}
break;
case 'e': /* effective speed in WpM */
if ((farnsworth = atoi(value)) < 1) {
fprintf(stderr, "Error: Eff. speed (-e) must be > 0!\n");
exit(EXIT_FAILURE);
}
break;
case 'W':
ews = atof(value);
break;
case 'n':
reset = 0;
break;
case 'p':
pBT = 0;
break;
case 'h':
help();
} /* 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) {
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(isomapindex, 0, sizeof(char)*256);
use_isomapping = 1;
break;
case UTF8:
memset(utf8mapindex, 0, sizeof(int)*256);
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) {
isomapindex[i] = c1;
strncpy(isomap[i], s, 3);
i++;
}
break;
case UTF8:
if (sscanf(tmp, "%c=%5s", &c1, s) == 2) {
utf8mapindex[i] = c1;
strncpy(utf8map[i], s, 5);
i++;
}
else if (sscanf(tmp, "%c%c=%5s", &c1, &c2, s) == 3) {
k = ((c1 & 31) << 6) | (c2 & 63); /* decimal */
utf8mapindex[i] = k; /* unicode char */
strncpy(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) {
static char new[2048]="";
char c;
int i, j, replaced;
unsigned char last=0;
memset(new, 0, 2048);
switch (encoding) {
case ISO8859:
if (!use_isomapping) {
return string;
}
while ((c = *string++) != '\0') {
replaced = 0;
for (i=0; i < 255; i++) {
if (isomapindex[i] == 0) {
break;
}
else if (isomapindex[i] == c) {
strcat(new, isomap[i]);
replaced = 1;
break;
}
}
if (!replaced) {
new[strlen(new)] = c;
}
}
break;
case UTF8:
if (!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 (utf8mapindex[i] == 0) {
break;
}
else if (utf8mapindex[i] == j) {
strcat(new, 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;
}