Последнее время я занимаюсь изучением азбуки Морзе с помощью данной программы. Но она рассчитана на изучение кодов кириллических букв, что является неактуальным в современной радиосвязи (все используют латинский алфавит, кроме нашей доблестной армии).
Такая ситуация меня не устроила, и было принято решение написать программу для генерации звукового кода Морзе из некоторого текста с настройкой скорости и возможностью добавления кодов динамически. Решение получилось достаточно оригинальным и гибким (ИМХО, конечно же). И я решил поделиться программой с общественностью: возможно, она будет кому-то полезна или покажется интересной.
В качестве инструмента реализации идеи был выбран С++ в связке с Qt.
Основная идея программы
Атомом (единицей времени) кода Морзе является точка, относительно нее формируется длительность всех остальных элементов:
- Тире равняется трем звучащим точкам;
- Пауза между элементами одного символа (знака) — одна незвучащая точка;
- Между знаками — три точки;
- Между словами — семь точек.
Как видите, любой код, основанный на азбуке морзе, можно представить в виде набора звучащих и незвучащих точек: от этой идеи я и отталкивался, причем такое решение мне показалось достаточно оригинальным.
Первоначальный вариант реализации
В первой версии программы комбинация звучащих и незвучащих точек хранилась в виде вектора с булевыми элементами, где 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, то ссылку я добавлю. Если есть какие-то вопросы или замечания, пишите в комментариях — обязательно отвечу.