| /* |
| * 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); |
| } |