blob: 258b6226a58466f0f74d8af6f839240a2e018ae6 [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <string.h>
#include "log.h"
#include "morse.h"
#include "tune.h"
#include "utf8.h"
static const MorsePattern morsePatterns[] = {
[WC_C('a')] = 0B101,
[WC_C('b')] = 0B11110,
[WC_C('c')] = 0B11010,
[WC_C('d')] = 0B1110,
[WC_C('e')] = 0B11,
[WC_C('f')] = 0B11011,
[WC_C('g')] = 0B1100,
[WC_C('h')] = 0B11111,
[WC_C('i')] = 0B111,
[WC_C('j')] = 0B10001,
[WC_C('k')] = 0B1010,
[WC_C('l')] = 0B11101,
[WC_C('m')] = 0B100,
[WC_C('n')] = 0B110,
[WC_C('o')] = 0B1000,
[WC_C('p')] = 0B11001,
[WC_C('q')] = 0B10100,
[WC_C('r')] = 0B1101,
[WC_C('s')] = 0B1111,
[WC_C('t')] = 0B10,
[WC_C('u')] = 0B1011,
[WC_C('v')] = 0B10111,
[WC_C('w')] = 0B1001,
[WC_C('x')] = 0B10110,
[WC_C('y')] = 0B10010,
[WC_C('z')] = 0B11100,
#ifdef HAVE_WCHAR_H
[WC_C('ä')] = 0B10101,
[WC_C('á')] = 0B101001,
[WC_C('å')] = 0B101001,
[WC_C('é')] = 0B111011,
[WC_C('ñ')] = 0B100100,
[WC_C('ö')] = 0B11000,
[WC_C('ü')] = 0B10011,
#endif /* HAVE_WCHAR_H */
[WC_C('0')] = 0B100000,
[WC_C('1')] = 0B100001,
[WC_C('2')] = 0B100011,
[WC_C('3')] = 0B100111,
[WC_C('4')] = 0B101111,
[WC_C('5')] = 0B111111,
[WC_C('6')] = 0B111110,
[WC_C('7')] = 0B111100,
[WC_C('8')] = 0B111000,
[WC_C('9')] = 0B110000,
[WC_C('.')] = 0B1010101,
[WC_C(',')] = 0B1001100,
[WC_C('?')] = 0B1110011,
[WC_C('!')] = 0B1001010,
[WC_C(':')] = 0B1111000,
[WC_C('\'')] = 0B1100001,
[WC_C('"')] = 0B1101101,
[WC_C('(')] = 0B110010,
[WC_C(')')] = 0B1010010,
[WC_C('=')] = 0B101110,
[WC_C('+')] = 0B110101,
[WC_C('-')] = 0B1011110,
[WC_C('/')] = 0B110110,
[WC_C('&')] = 0B111101,
[WC_C('@')] = 0B1101001,
[0] = 0
};
MorsePattern
getMorsePattern (wchar_t character) {
character = towlower(character);
if (character < 0) return 0;
if (character >= ARRAY_COUNT(morsePatterns)) return 0;
return morsePatterns[character];
}
struct MorseObjectStruct {
struct {
unsigned int frequency;
unsigned int unit;
} parameters;
struct {
unsigned wasSpace:1;
} state;
struct {
ToneElement *array;
size_t size;
size_t count;
} elements;
};
static int
addMorseElement (MorseObject *morse, const ToneElement *element) {
if (morse->elements.count == morse->elements.size) {
size_t newSize = morse->elements.size? (morse->elements.size << 1): 0X10;
ToneElement *newArray = realloc(morse->elements.array, (newSize * sizeof(*newArray)));
if (!newArray) {
logMallocError();
return 0;
}
morse->elements.array = newArray;
morse->elements.size = newSize;
}
morse->elements.array[morse->elements.count++] = *element;
return 1;
}
static int
addMorseMark (MorseObject *morse, unsigned int units) {
ToneElement element = TONE_PLAY((morse->parameters.unit * units), morse->parameters.frequency);
return addMorseElement(morse, &element);
}
static int
addMorseGap (MorseObject *morse, unsigned int units) {
ToneElement element = TONE_REST((morse->parameters.unit * units));
return addMorseElement(morse, &element);
}
int
addMorsePattern (MorseObject *morse, MorsePattern pattern) {
if (pattern) {
int addGap = 0;
while (pattern != 0B1) {
if (!addGap) {
addGap = 1;
} else if (!addMorseGap(morse, MORSE_UNITS_GAP_SYMBOL)) {
return 0;
}
unsigned int units = (pattern & 0B1)? MORSE_UNITS_MARK_SHORT: MORSE_UNITS_MARK_LONG;
if (!addMorseMark(morse, units)) return 0;
pattern >>= 1;
}
}
return 1;
}
int
addMorseCharacter (MorseObject *morse, wchar_t character) {
if (!iswspace(character)) {
if (morse->state.wasSpace) {
morse->state.wasSpace = 0;
} else if (!addMorseGap(morse, MORSE_UNITS_GAP_LETTER)) {
return 0;
}
if (!addMorsePattern(morse, getMorsePattern(character))) return 0;
} else if (!morse->state.wasSpace) {
morse->state.wasSpace = 1;
if (!addMorseGap(morse, MORSE_UNITS_GAP_WORD)) return 0;
}
return 1;
}
int
addMorseSpace (MorseObject *morse) {
return addMorseCharacter(morse, WC_C(' '));
}
int
addMorseCharacters (MorseObject *morse, const wchar_t *characters, size_t count) {
const wchar_t *character = characters;
const wchar_t *end = character + count;
while (character < end) {
if (!addMorseCharacter(morse, *character++)) return 0;
}
return 1;
}
int
addMorseString (MorseObject *morse, const char *string) {
size_t size = strlen(string) + 1;
wchar_t characters[size];
const char *byte = string;
wchar_t *end = characters;
convertUtf8ToWchars(&byte, &end, size);
return addMorseCharacters(morse, characters, (end - characters));
}
int
playMorseSequence (MorseObject *morse) {
{
ToneElement element = TONE_STOP();
if (!addMorseElement(morse, &element)) return 0;
}
tunePlayTones(morse->elements.array);
tuneSynchronize();
return 1;
}
void
clearMorseSequence (MorseObject *morse) {
morse->elements.count = 0;
morse->state.wasSpace = 1;
}
unsigned int
getMorsePitch (MorseObject *morse) {
return morse->parameters.frequency;
}
int
setMorsePitch (MorseObject *morse, unsigned int frequency) {
if (frequency < 1) return 0;
if (frequency > 0XFFFF) return 0;
morse->parameters.frequency = frequency;
return 1;
}
static inline unsigned int
getMorseReferenceDuration (unsigned int unitsPerMinute) {
return 60000 / unitsPerMinute;
}
static unsigned int
getMorseSpeed (MorseObject *morse, unsigned int unitsPerMinute) {
return getMorseReferenceDuration(unitsPerMinute) / morse->parameters.unit;
}
static int
setMorseSpeed (MorseObject *morse, unsigned int speed, unsigned int unitsPerMinute) {
unsigned int unitDuration = getMorseReferenceDuration(unitsPerMinute) / speed;
if (unitDuration < 10) return 0;
morse->parameters.unit = unitDuration;
return 1;
}
unsigned int
getMorseWordsPerMinute (MorseObject *morse) {
return getMorseSpeed(morse, MORSE_UNITS_PER_WORD);
}
int
setMorseWordsPerMinute (MorseObject *morse, unsigned int speed) {
return setMorseSpeed(morse, speed, MORSE_UNITS_PER_WORD);
}
unsigned int
getMorseGroupsPerMinute (MorseObject *morse) {
return getMorseSpeed(morse, MORSE_UNITS_PER_GROUP);
}
int
setMorseGroupsPerMinute (MorseObject *morse, unsigned int speed) {
return setMorseSpeed(morse, speed, MORSE_UNITS_PER_GROUP);
}
void *
newMorseObject (void) {
MorseObject *morse;
if ((morse = malloc(sizeof(*morse)))) {
memset(morse, 0, sizeof(*morse));
setMorsePitch(morse, 440);
setMorseWordsPerMinute(morse, 20);
morse->elements.array = NULL;
morse->elements.size = 0;
clearMorseSequence(morse);
return morse;
} else {
logMallocError();
}
return NULL;
}
void
destroyMorseObject (MorseObject *morse) {
if (morse->elements.array) free(morse->elements.array);
free(morse);
}