Как стать автором
Обновить

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

Время на прочтение4 мин
Количество просмотров36K
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, то ссылку я добавлю. Если есть какие-то вопросы или замечания, пишите в комментариях — обязательно отвечу.
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 18: ↑12 и ↓6+6
Комментарии17

Публикации

Истории

Работа

QT разработчик
3 вакансии
Программист C++
104 вакансии

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань