Программа для генерации звукового кода Морзе

от автора

image

Последнее время я занимаюсь изучением азбуки Морзе с помощью данной программы. Но она рассчитана на изучение кодов кириллических букв, что является неактуальным в современной радиосвязи (все используют латинский алфавит, кроме нашей доблестной армии).

Такая ситуация меня не устроила, и было принято решение написать программу для генерации звукового кода Морзе из некоторого текста с настройкой скорости и возможностью добавления кодов динамически. Решение получилось достаточно оригинальным и гибким (ИМХО, конечно же). И я решил поделиться программой с общественностью: возможно, она будет кому-то полезна или покажется интересной.

В качестве инструмента реализации идеи был выбран С++ в связке с Qt.

Основная идея программы

Атомом (единицей времени) кода Морзе является точка, относительно нее формируется длительность всех остальных элементов:

  1. Тире равняется трем звучащим точкам;
  2. Пауза между элементами одного символа (знака) — одна незвучащая точка;
  3. Между знаками — три точки;
  4. Между словами — семь точек.

Как видите, любой код, основанный на азбуке морзе, можно представить в виде набора звучащих и незвучащих точек: от этой идеи я и отталкивался, причем такое решение мне показалось достаточно оригинальным.

Первоначальный вариант реализации

В первой версии программы комбинация звучащих и незвучащих точек хранилась в виде вектора с булевыми элементами, где true соответствовал включению звука, а false — выключению.

Как вы уже поняли, для получения конечного сигнала я всего лишь «дергал» звук с некоторой задержкой (с помощью таймера, равного длительности точки в миллисекундах) при бесконечно воспроизводящемся .wav файле с записью синуса. Но данный подход имел значительный минус и заключался он в том, что приходилось каждую точку загружать отдельно с помощью перегруженного оператора или специального метода. Из-за такого подхода пришлось писать отдельный макрос для каждой буквы (вроде такого — #define I DOT << false << DOT) и создать огромный жуткий switch для воспроизведения переданной строки. Это было ужасно, но если вам любопытно, то вы можете ознакомиться
с первой версией программы тут (у меня не получилось полностью загрузить на GitHub локальный репозиторий — только последнюю версию).

Кусочек жуткого свитча:

bool Morse::StringToMorse (QString &line) {    line += '\0';    for (int i = 0; i < line.size () - 1; ++i) {     switch (line.at(i).unicode ()) {        case 'A':         *this << A;         if (line.at (i + 1) == ' ')           continue;         else           *this << MINI_SPACE;       break;        case 'B':         *this << B;         if (line.at (i + 1) == ' ')           continue;         else           *this << MINI_SPACE;       break;  // И так далее 

А вот так происходило включение и выключение звука (собственно, генерация звукового кода):

void Morse::PlayLinePoints () {    QTimer::singleShot (duration_point_, this, SLOT ( Mute () ));    sound_.play (); }  void Morse::Mute () {   if (line_points_.empty ()) { //Останавливаем воспроизведение     sound_.stop ();     return;   }    if (line_points_.at (0)) { //Включаем звук     sound_.setMuted (false);     line_points_.remove (0);     QTimer::singleShot (duration_point_, this, SLOT ( Mute () ));     return;   } else {     sound_.setMuted (true); //Выключаем звук     line_points_.remove (0);     QTimer::singleShot (duration_point_, this, SLOT ( Mute () ));     return;   } } 

Окончательная версия

Очень уж оказались эти макросы громоздки, и мой перфекционизм не смог больше смотреть на эти монструозные конструкции. Поразмыслив немного, пришел к мысли, что идея у меня хорошая, но хранение кодов в виде макросов очень неудобно и, если решить эту проблему, то все будет хорошо. В итоге для хранения кодов стал использоваться QMap:

//Хранит соответствующие комбинации точек и тире символов   QMap<QChar, QBitArray> codes_; 

Такой подход оказался очень удобным. Теперь я всего лишь использовал текущий воспроизводимый символ в качестве ключа и получал готовый
для воспроизведения код (набор булевых значений), правда, алгоритм воспроизведения немного усложнился: понадобилось ввести счетчик текущего элемента символа и счетчик символов в строке:

Новая реализация воспроизведения:

void Morse::MiniSpace () {    if (stop_) {     this->Stop ();     return;   }    sound_.setMuted (true);    ++id_element_; //Преходим на другой элемент кода   if (  id_element_ == codes_.value ( string_to_play_.at (id_char_) ).size ()  ) {     ++id_char_;     id_element_ = 0;     QTimer::singleShot (duration_dot_ * 3, this, SLOT ( Mute() )); //Пауза между символами     return;   }    QTimer::singleShot (duration_dot_, this, SLOT ( Mute() )); //Пауза между элементами символа }    void Morse::Space () {    if (stop_) {     this->Stop ();     return;   }    sound_.setMuted (true);   //Пауза длится 7 точек   //Но так как после символа идет пауза в три точки, то доп паузу нужно выставить длиной в 4 точки   QTimer::singleShot (duration_dot_ * 4, this, SLOT ( Mute() )); }    void Morse::Mute () {    if (stop_) {     this->Stop ();     return;   }    if (id_char_ == string_to_play_.size ()) { // Строка закончилась     this->Stop ();     return;   }    if (string_to_play_.at (id_char_)  == ' ') {     Space();     ++id_char_; //Преходим на другой элемент кода     return;   }    if (codes_.find ( string_to_play_.at (id_char_) ) == codes_.end ()) {     qDebug() << string_to_play_.at (id_char_) << ": No code!";     sound_.stop ();     return;   }    sound_.setMuted (false); //Включаем звук    if (  codes_.value ( string_to_play_.at (id_char_) ).at (id_element_)) {     QTimer::singleShot (duration_dot_, this, SLOT ( MiniSpace() )); //Воспроизводим точку   } else {     QTimer::singleShot (duration_dot_ * 3, this, SLOT ( MiniSpace() )); //Воспроизводим тире   }  }  bool Morse::Play () {    if (!stop_)     return false;    if (string_to_play_ == "")     return false;    stop_ = false;    id_char_ = 0;   id_element_ = 0;   sound_.setMuted (true); //Выключаем звук   sound_.play ();   Mute (); }  void Morse::Stop () {    if (stop_)     return;    sound_.stop ();   id_char_ = 0;   id_element_ = 0;   stop_ = true; } 

Флаг stop_ был введен для предотвращения некорректной работы программы (два вызова подряд Play() и прочих нехороших вещей).
Остальную часть исходных кодов и заголовочные файлы не вижу смысла приводить в теле статьи, так как там все достаточно очевидно и прозрачно.

Полный набор исходников последней версии вы можете скачать на гитхабе. Написание графического интерфейса является тривиальной задачей, но все же, если будет создан GUI, то ссылку я добавлю. Если есть какие-то вопросы или замечания, пишите в комментариях — обязательно отвечу.

ссылка на оригинал статьи http://habrahabr.ru/post/267605/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *