├── qml ├── qmldir ├── qml.qrc ├── Frequency.qml ├── GoToAddress.qml ├── About.qml ├── DataPlayer.qml ├── ParserSettings.qml ├── translations │ ├── zxtapereviver_en_US.xlf │ └── zxtapereviver_ru_RU.xlf └── Translations.qml ├── icon.ico ├── README.md ├── sources ├── util │ ├── enummetainfo.cpp │ └── enummetainfo.h ├── actions │ ├── actionbase.cpp │ ├── shiftwaveformaction.h │ ├── actionbase.h │ ├── editsampleaction.h │ ├── shiftwaveformaction.cpp │ └── editsampleaction.cpp ├── translations │ ├── translations.h │ ├── translations.cpp │ ├── translationmanager.h │ └── translationmanager.cpp ├── models │ ├── waveformmodel.cpp │ ├── waveformmodel.h │ ├── suspiciouspointsmodel.h │ ├── actionsmodel.cpp │ ├── actionsmodel.h │ ├── fileworkermodel.h │ ├── dataplayermodel.h │ ├── suspiciouspointsmodel.cpp │ ├── fileworkermodel.cpp │ ├── parsersettingsmodel.h │ ├── parsersettingsmodel.cpp │ └── dataplayermodel.cpp ├── defines.h ├── core │ ├── parseddata.cpp │ ├── parseddata.h │ ├── waveformparser.h │ ├── wavreader.h │ └── waveformparser.cpp ├── main.cpp ├── controls │ ├── waveformcontrol.h │ └── waveformcontrol.cpp └── configuration │ ├── configurationmanager.h │ └── configurationmanager.cpp ├── .gitignore └── ZXTapeReviver.pro /qml/qmldir: -------------------------------------------------------------------------------- 1 | singleton Translations 1.0 Translations.qml 2 | -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgolouz/ZXTapeReviver/HEAD/icon.ico -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZXTapeReviver 2 | 3 | ZxTapeReviver is a tool to convert ZX Spectrum tapes, recorded as `WAV` file, to a `TAP` files and provides a ways to manually revive poor records. 4 | -------------------------------------------------------------------------------- /qml/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | qmldir 4 | main.qml 5 | GoToAddress.qml 6 | Frequency.qml 7 | ParserSettings.qml 8 | About.qml 9 | Translations.qml 10 | DataPlayer.qml 11 | 12 | 13 | translations/zxtapereviver_en_US.qm 14 | translations/zxtapereviver_ru_RU.qm 15 | 16 | 17 | -------------------------------------------------------------------------------- /sources/util/enummetainfo.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "enummetainfo.h" 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated sources 2 | generated/ 3 | 4 | # C++ objects and libs 5 | *.slo 6 | *.lo 7 | *.o 8 | *.a 9 | *.la 10 | *.lai 11 | *.so 12 | *.so.* 13 | *.dll 14 | *.dylib 15 | 16 | # Qt-es 17 | object_script.*.Release 18 | object_script.*.Debug 19 | *_plugin_import.cpp 20 | /.qmake.cache 21 | /.qmake.stash 22 | *.pro.user 23 | *.pro.user.* 24 | *.qbs.user 25 | *.qbs.user.* 26 | *.moc 27 | moc_*.cpp 28 | moc_*.h 29 | qrc_*.cpp 30 | ui_*.h 31 | *.qmlc 32 | *.jsc 33 | Makefile* 34 | *build-* 35 | *.qm 36 | *.prl 37 | 38 | # Qt unit tests 39 | target_wrapper.* 40 | 41 | # QtCreator 42 | *.autosave 43 | 44 | # QtCreator Qml 45 | *.qmlproject.user 46 | *.qmlproject.user.* 47 | 48 | # QtCreator CMake 49 | CMakeLists.txt.user* 50 | 51 | # QtCreator 4.8< compilation database 52 | compile_commands.json 53 | 54 | # QtCreator local machine specific files for imported projects 55 | *creator.user* 56 | -------------------------------------------------------------------------------- /sources/actions/actionbase.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "actionbase.h" 15 | 16 | ActionBase::ActionBase(int channel, const QString& name) : 17 | m_channel(channel), 18 | m_actionName(name) 19 | { 20 | 21 | } 22 | 23 | int ActionBase::channel() const { 24 | return m_channel; 25 | } 26 | 27 | const QString& ActionBase::actionName() const { 28 | return m_actionName; 29 | } 30 | 31 | bool ActionBase::isActionValid(const QSharedPointer& wf) const { 32 | return !wf.isNull(); 33 | } 34 | -------------------------------------------------------------------------------- /sources/translations/translations.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef TRANSLATIONS_H 15 | #define TRANSLATIONS_H 16 | 17 | #include TRANSLATION_IDS_HEADER 18 | 19 | extern const char* ID_TIMELINE_SEC; 20 | extern const char* ID_OK; 21 | extern const char* ID_ERROR; 22 | extern const char* ID_UNKNOWN; 23 | extern const char* ID_HEADER; 24 | extern const char* ID_CODE; 25 | extern const char* ID_EDIT_ACTION; 26 | extern const char* ID_SHIFT_WAVEFORM_ACTION; 27 | extern const char* ID_PARITY_MESSAGE; 28 | 29 | #endif // TRANSLATIONS_H 30 | -------------------------------------------------------------------------------- /sources/actions/shiftwaveformaction.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef SHIFTWAVEFORMACTION_H 15 | #define SHIFTWAVEFORMACTION_H 16 | 17 | #include "actionbase.h" 18 | 19 | struct ShiftWaveFormActionParams { 20 | QWavVectorType offsetValue; 21 | }; 22 | 23 | class ShiftWaveFormAction : public ActionBase 24 | { 25 | const ShiftWaveFormActionParams m_params; 26 | 27 | public: 28 | ShiftWaveFormAction(int channel, const ShiftWaveFormActionParams& params); 29 | virtual ~ShiftWaveFormAction() = default; 30 | 31 | virtual bool apply() override; 32 | virtual void undo() override; 33 | }; 34 | 35 | #endif // SHIFTWAVEFORMACTION_H 36 | -------------------------------------------------------------------------------- /sources/models/waveformmodel.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "waveformmodel.h" 15 | 16 | WaveFormModel::WaveFormModel() 17 | { 18 | 19 | } 20 | 21 | void WaveFormModel::initialize(QPair, QSharedPointer> channels) { 22 | m_channels = QVector>({ channels.first, channels.second }); 23 | } 24 | 25 | QSharedPointer WaveFormModel::getChannel(int channel) { 26 | return channel < m_channels.size() ? m_channels.at(channel) : QSharedPointer::create(); 27 | } 28 | 29 | WaveFormModel* WaveFormModel::instance() { 30 | static WaveFormModel m; 31 | return &m; 32 | } 33 | -------------------------------------------------------------------------------- /sources/actions/actionbase.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef ACTIONBASE_H 15 | #define ACTIONBASE_H 16 | 17 | #include "sources/defines.h" 18 | #include 19 | #include 20 | 21 | class ActionBase 22 | { 23 | const int m_channel; 24 | const QString m_actionName; 25 | 26 | public: 27 | ActionBase(int channel, const QString& name = { }); 28 | virtual ~ActionBase() = default; 29 | 30 | int channel() const; 31 | const QString& actionName() const; 32 | 33 | virtual bool apply() = 0; 34 | virtual void undo() = 0; 35 | 36 | protected: 37 | virtual bool isActionValid(const QSharedPointer& wf) const; 38 | }; 39 | 40 | #endif // ACTIONBASE_H 41 | -------------------------------------------------------------------------------- /qml/Frequency.qml: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | import QtQuick 2.3 15 | import QtQuick.Controls 1.3 16 | import QtQuick.Dialogs 1.3 17 | 18 | Dialog { 19 | id: frequencyDialog 20 | 21 | property var frequency: 0 22 | 23 | visible: false 24 | title: Translations.id_measured_frequency_window_header 25 | standardButtons: StandardButton.Ok 26 | modality: Qt.WindowModal 27 | width: 200 28 | height: 120 29 | 30 | Text { 31 | id: textWithField 32 | text: Translations.id_measured_frequency 33 | } 34 | 35 | TextField { 36 | id: textField 37 | anchors.top: textWithField.bottom 38 | anchors.topMargin: 5 39 | width: parent.width 40 | text: frequency 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /sources/actions/editsampleaction.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef EDITSAMPLEACTION_H 15 | #define EDITSAMPLEACTION_H 16 | 17 | #include "actionbase.h" 18 | 19 | struct EditSampleActionParams { 20 | QWavVectorType previousValue; 21 | QWavVectorType newValue; 22 | int sample; 23 | }; 24 | 25 | class EditSampleAction : public ActionBase 26 | { 27 | const EditSampleActionParams m_params; 28 | 29 | public: 30 | EditSampleAction(int channel, const EditSampleActionParams& params); 31 | virtual ~EditSampleAction() = default; 32 | 33 | virtual bool apply() override; 34 | virtual void undo() override; 35 | 36 | private: 37 | virtual bool isActionValid(const QSharedPointer& wf) const; 38 | }; 39 | 40 | #endif // EDITSAMPLEACTION_H 41 | -------------------------------------------------------------------------------- /sources/translations/translations.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "translations.h" 15 | #include 16 | 17 | #include TRANSLATION_IDS_CODE 18 | 19 | const char* ID_TIMELINE_SEC = QT_TRID_NOOP("id_timeline_sec"); 20 | const char* ID_OK = QT_TRID_NOOP("id_ok"); 21 | const char* ID_ERROR = QT_TRID_NOOP("id_error"); 22 | const char* ID_UNKNOWN = QT_TRID_NOOP("id_unknown"); 23 | const char* ID_HEADER = QT_TRID_NOOP("id_header"); 24 | const char* ID_CODE = QT_TRID_NOOP("id_code"); 25 | const char* ID_EDIT_ACTION = QT_TRID_NOOP("id_edit_action"); 26 | const char* ID_SHIFT_WAVEFORM_ACTION = QT_TRID_NOOP("id_shift_waveform_action"); 27 | const char* ID_PARITY_MESSAGE = QT_TRID_NOOP("id_parity_message"); 28 | -------------------------------------------------------------------------------- /sources/models/waveformmodel.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef WAVEFORMMODEL_H 15 | #define WAVEFORMMODEL_H 16 | 17 | #include "sources/defines.h" 18 | #include 19 | #include 20 | 21 | class WaveFormModel final 22 | { 23 | QVector> m_channels; 24 | 25 | private: 26 | WaveFormModel(); 27 | 28 | public: 29 | ~WaveFormModel() = default; 30 | 31 | WaveFormModel(const WaveFormModel& other) = delete; 32 | WaveFormModel(WaveFormModel&& other) = delete; 33 | WaveFormModel& operator= (const WaveFormModel& other) = delete; 34 | WaveFormModel& operator= (WaveFormModel&& other) = delete; 35 | 36 | static WaveFormModel* instance(); 37 | 38 | void initialize(QPair, QSharedPointer> channels); 39 | QSharedPointer getChannel(int channel); 40 | }; 41 | 42 | #endif // WAVEFORMMODEL_H 43 | -------------------------------------------------------------------------------- /sources/actions/shiftwaveformaction.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "shiftwaveformaction.h" 15 | #include "sources/models/waveformmodel.h" 16 | #include "sources/translations/translations.h" 17 | 18 | ShiftWaveFormAction::ShiftWaveFormAction(int channel, const ShiftWaveFormActionParams& params) : 19 | ActionBase(channel, qtTrId(ID_SHIFT_WAVEFORM_ACTION)), 20 | m_params(params) 21 | { 22 | 23 | } 24 | 25 | bool ShiftWaveFormAction::apply() { 26 | auto wf { WaveFormModel::instance()->getChannel(channel()) }; 27 | const bool valid { isActionValid(wf) }; 28 | if (valid) { 29 | std::for_each(wf->begin(), wf->end(), [this](QWavVectorType& itm) { 30 | itm += m_params.offsetValue; 31 | }); 32 | } 33 | return valid; 34 | } 35 | 36 | void ShiftWaveFormAction::undo() { 37 | auto wf { WaveFormModel::instance()->getChannel(channel()) }; 38 | std::for_each(wf->begin(), wf->end(), [this](QWavVectorType& itm) { 39 | itm -= m_params.offsetValue; 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /sources/actions/editsampleaction.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "editsampleaction.h" 15 | #include "sources/models/waveformmodel.h" 16 | #include "sources/translations/translations.h" 17 | 18 | EditSampleAction::EditSampleAction(int channel, const EditSampleActionParams& params) : 19 | ActionBase(channel, qtTrId(ID_EDIT_ACTION)), 20 | m_params(params) 21 | { 22 | 23 | } 24 | 25 | bool EditSampleAction::apply() { 26 | auto wf { WaveFormModel::instance()->getChannel(channel()) }; 27 | const bool valid { isActionValid(wf) }; 28 | if (valid) { 29 | wf->operator[](m_params.sample) = m_params.newValue; 30 | } 31 | 32 | return valid; 33 | } 34 | 35 | void EditSampleAction::undo() { 36 | auto wf { WaveFormModel::instance()->getChannel(channel()) }; 37 | wf->operator[](m_params.sample) = m_params.previousValue; 38 | } 39 | 40 | bool EditSampleAction::isActionValid(const QSharedPointer& wf) const { 41 | return ActionBase::isActionValid(wf) && m_params.sample >= 0 && m_params.sample < wf->size(); 42 | } 43 | -------------------------------------------------------------------------------- /sources/util/enummetainfo.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef ENUMMETAINFO_H 15 | #define ENUMMETAINFO_H 16 | 17 | #include 18 | #include 19 | 20 | class EnumMetaInfo 21 | { 22 | protected: 23 | EnumMetaInfo() = default; 24 | virtual ~EnumMetaInfo() = default; 25 | EnumMetaInfo(const EnumMetaInfo& other) = delete; 26 | EnumMetaInfo(EnumMetaInfo&& other) = delete; 27 | EnumMetaInfo& operator= (const EnumMetaInfo& other) = delete; 28 | EnumMetaInfo& operator= (EnumMetaInfo&& other) = delete; 29 | 30 | template static O getEnumName(T state) { 31 | auto s = QMetaEnum::fromType().valueToKey(static_cast(state)); 32 | return s ? s : "Undefined"; 33 | } 34 | 35 | template T static getEnumValue(const QString& name, T def = T { }) { 36 | bool ok; 37 | auto v = static_cast(QMetaEnum::fromType().keyToValue(name.toStdString().c_str(), &ok)); 38 | return ok ? v : def; 39 | } 40 | }; 41 | 42 | #endif // ENUMMETAINFO_H 43 | -------------------------------------------------------------------------------- /sources/models/suspiciouspointsmodel.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef SUSPICIOUSPOINTSMODEL_H 15 | #define SUSPICIOUSPOINTSMODEL_H 16 | 17 | #include 18 | 19 | class SuspiciousPointsModel : public QObject 20 | { 21 | Q_OBJECT 22 | Q_PROPERTY(QVariantList suspiciousPoints READ getSuspiciousPoints NOTIFY suspiciousPointsChanged) 23 | Q_PROPERTY(int size READ getSize NOTIFY sizeChanged) 24 | 25 | QVariantList mSuspiciousPoints; 26 | 27 | protected: 28 | explicit SuspiciousPointsModel(QObject* parent = nullptr); 29 | 30 | public: 31 | virtual ~SuspiciousPointsModel() = default; 32 | static SuspiciousPointsModel* instance(); 33 | 34 | int getSize() const; 35 | QVariantList getSuspiciousPoints() const; 36 | void setSuspiciousPoints(const QVariantList& m); 37 | 38 | Q_INVOKABLE bool addSuspiciousPoint(uint idx); 39 | Q_INVOKABLE bool removeSuspiciousPoint(int idx); 40 | Q_INVOKABLE uint getSuspiciousPoint(int idx); 41 | Q_INVOKABLE void clearSuspiciousPoints(); 42 | 43 | signals: 44 | void suspiciousPointsChanged(); 45 | void sizeChanged(); 46 | }; 47 | 48 | #endif // SUSPICIOUSPOINTSMODEL_H 49 | -------------------------------------------------------------------------------- /sources/models/actionsmodel.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "actionsmodel.h" 15 | #include 16 | #include "sources/actions/shiftwaveformaction.h" 17 | 18 | ActionsModel::ActionsModel(QObject* parent) : 19 | QObject(parent) 20 | { 21 | 22 | } 23 | 24 | void ActionsModel::addAction(QSharedPointer action) { 25 | if (action->apply()) { 26 | m_actions.append(action); 27 | emit actionsChanged(); 28 | } 29 | } 30 | 31 | void ActionsModel::removeAction() { 32 | if (!m_actions.isEmpty()) { 33 | m_actions.takeLast()->undo(); 34 | emit actionsChanged(); 35 | } 36 | } 37 | 38 | void ActionsModel::shiftWaveform(double offset) { 39 | addAction(QSharedPointer::create(0, ShiftWaveFormActionParams { static_cast(offset) })); 40 | } 41 | 42 | 43 | QVariantList ActionsModel::getActions() const { 44 | QVariantList result; 45 | for (const auto& a: m_actions) { 46 | result.append(QVariantMap { { "name", a->actionName() } }); 47 | } 48 | return result; 49 | } 50 | 51 | ActionsModel* ActionsModel::instance() { 52 | static ActionsModel m; 53 | return &m; 54 | } 55 | -------------------------------------------------------------------------------- /sources/models/actionsmodel.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef ACTIONSMODEL_H 15 | #define ACTIONSMODEL_H 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "sources/actions/actionbase.h" 22 | 23 | class ActionsModel final : public QObject 24 | { 25 | Q_OBJECT 26 | 27 | Q_PROPERTY(QVariantList actions READ getActions NOTIFY actionsChanged) 28 | 29 | QList> m_actions; 30 | 31 | protected: 32 | explicit ActionsModel(QObject* parent = nullptr); 33 | 34 | public: 35 | virtual ~ActionsModel() = default; 36 | 37 | ActionsModel(const ActionsModel& other) = delete; 38 | ActionsModel(ActionsModel&& other) = delete; 39 | ActionsModel& operator= (const ActionsModel& other) = delete; 40 | ActionsModel& operator= (ActionsModel&& other) = delete; 41 | 42 | static ActionsModel* instance(); 43 | 44 | QVariantList getActions() const; 45 | 46 | void addAction(QSharedPointer action); 47 | Q_INVOKABLE void removeAction(); 48 | Q_INVOKABLE void shiftWaveform(double offset); 49 | 50 | signals: 51 | void actionsChanged(); 52 | }; 53 | 54 | #endif // ACTIONSMODEL_H 55 | -------------------------------------------------------------------------------- /qml/GoToAddress.qml: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | import QtQuick 2.3 15 | import QtQuick.Controls 1.3 16 | import QtQuick.Dialogs 1.3 17 | 18 | Dialog { 19 | id: gotoAddressDialog 20 | 21 | signal gotoAddress(int adr); 22 | 23 | visible: false 24 | title: Translations.id_goto_address_window_header 25 | standardButtons: StandardButton.Ok | StandardButton.Cancel 26 | modality: Qt.WindowModal 27 | width: 250 28 | height: 120 29 | 30 | Text { 31 | id: textWithField 32 | text: Translations.id_please_enter_address 33 | } 34 | 35 | TextField { 36 | id: textField 37 | anchors.top: textWithField.bottom 38 | width: parent.width 39 | } 40 | 41 | CheckBox { 42 | id: hexCheckbox 43 | 44 | anchors.top: textField.bottom 45 | anchors.topMargin: 3 46 | checked: true 47 | text: Translations.id_hexadecimal 48 | } 49 | 50 | Text { 51 | id: conversionField 52 | 53 | function convertAddress(adr) { 54 | return hexCheckbox.checked ? parseInt(adr, 16) : "0x" + parseInt(adr, 10).toString(16).toUpperCase(); 55 | } 56 | 57 | text: "(" + convertAddress(textField.text) + ")" 58 | anchors.left: hexCheckbox.right 59 | anchors.leftMargin: 3 60 | anchors.top: hexCheckbox.top 61 | } 62 | 63 | onAccepted: { 64 | gotoAddress(parseInt(textField.text, hexCheckbox.checked ? 16 : 10)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /sources/models/fileworkermodel.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef FILEWORKERMODEL_H 15 | #define FILEWORKERMODEL_H 16 | 17 | #include 18 | #include "sources/core/wavreader.h" 19 | 20 | class FileWorkerModel : public QObject 21 | { 22 | Q_OBJECT 23 | 24 | Q_PROPERTY(QString wavFileName READ getWavFileName NOTIFY wavFileNameChanged) 25 | 26 | public: 27 | enum FileWorkerResults { 28 | FW_OK, 29 | FW_ERR 30 | }; 31 | Q_ENUM(FileWorkerResults) 32 | 33 | explicit FileWorkerModel(QObject* parent = nullptr); 34 | virtual ~FileWorkerModel() override; 35 | //getters 36 | QString getWavFileName() const; 37 | 38 | //setters 39 | 40 | //QML invokable members 41 | Q_INVOKABLE /*WavReader::ErrorCodesEnum*/ int openTapFileByUrl(const QString& fileNameUrl); 42 | Q_INVOKABLE /*WavReader::ErrorCodesEnum*/ int openTapFile(const QString& fileName); 43 | Q_INVOKABLE /*WavReader::ErrorCodesEnum*/ int openWavFileByUrl(const QString& fileNameUrl); 44 | Q_INVOKABLE /*WavReader::ErrorCodesEnum*/ int openWavFile(const QString& fileName); 45 | Q_INVOKABLE /*WavReader::ErrorCodesEnum*/ int openWaveformFileByUrl(const QString& fileNameUrl); 46 | Q_INVOKABLE /*WavReader::ErrorCodesEnum*/ int openWaveformFile(const QString& fileName); 47 | Q_INVOKABLE /*WavReader::ErrorCodesEnum*/ int saveWaveformFileByUrl(const QString& fileNameUrl); 48 | Q_INVOKABLE /*WavReader::ErrorCodesEnum*/ int saveWaveformFile(const QString& fileName); 49 | 50 | signals: 51 | void wavFileNameChanged(); 52 | 53 | private: 54 | QString m_wavFileName; 55 | }; 56 | 57 | #endif // FILEWORKERMODEL_H 58 | -------------------------------------------------------------------------------- /sources/translations/translationmanager.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef TRANSLATIONMANAGER_H 15 | #define TRANSLATIONMANAGER_H 16 | 17 | #include 18 | #include 19 | #include 20 | #include "sources/util/enummetainfo.h" 21 | 22 | class TranslationManager : public QObject, private EnumMetaInfo 23 | { 24 | Q_OBJECT 25 | 26 | Q_PROPERTY(QString translationChanged READ getTranslationChanged NOTIFY translationChanged) 27 | Q_PROPERTY(QVariantList languages READ getLanguages CONSTANT) 28 | 29 | QScopedPointer m_translator; 30 | 31 | QVariantList getAvailableTranslations() const; 32 | QString getTranslationName() const; 33 | void installTranslator(bool firstRun = false); 34 | 35 | public: 36 | enum TranslationLanguages { 37 | COUNTRY_CODES 38 | }; 39 | Q_ENUM(TranslationLanguages) 40 | 41 | TranslationManager(QObject* parent = nullptr); 42 | virtual ~TranslationManager() = default; 43 | 44 | TranslationManager(const TranslationManager& other) = delete; 45 | TranslationManager(TranslationManager&& other) = delete; 46 | TranslationManager& operator= (const TranslationManager& other) = delete; 47 | TranslationManager& operator= (TranslationManager&& other) = delete; 48 | 49 | QString getTranslationChanged() const; 50 | Q_INVOKABLE void setTranslation(TranslationLanguages lng); 51 | QVariantList getLanguages() const; 52 | 53 | static TranslationManager* instance(); 54 | 55 | signals: 56 | void translationChanged(); 57 | 58 | private: 59 | TranslationLanguages m_currentLanguage; 60 | }; 61 | 62 | #endif // TRANSLATIONMANAGER_H 63 | -------------------------------------------------------------------------------- /qml/About.qml: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | import QtQuick 2.3 15 | import QtQuick.Controls 1.3 16 | import QtQuick.Dialogs 1.3 17 | import QtQuick.Layouts 1.15 18 | 19 | import com.models.zxtapereviver 1.0 20 | import "." 21 | 22 | Dialog { 23 | id: aboutDialog 24 | 25 | visible: false 26 | title: Translations.id_about_window_header 27 | standardButtons: StandardButton.Ok 28 | modality: Qt.WindowModal 29 | width: 300 30 | height: 140 31 | 32 | Text { 33 | id: zxTapeReviverText 34 | text: 'ZX Tape Reviver %1 (c) 2020-2022 Leonid Golouz'.arg(ConfigurationManager.zxTapeReviverVersion) 35 | onLinkActivated: Qt.openUrlExternally(link) 36 | } 37 | Text { 38 | id: skipText 39 | anchors.top: zxTapeReviverText.bottom 40 | text: "" 41 | } 42 | Text { 43 | id: emailText 44 | anchors.top: skipText.bottom 45 | text: Translations.id_email_link 46 | onLinkActivated: Qt.openUrlExternally(link) 47 | } 48 | Text { 49 | id: youtubeText 50 | anchors.top: emailText.bottom 51 | text: Translations.id_youtube_channel_link 52 | onLinkActivated: Qt.openUrlExternally(link) 53 | } 54 | Text { 55 | id: donationText 56 | anchors.top: youtubeText.bottom 57 | text: Translations.id_donations_link 58 | onLinkActivated: Qt.openUrlExternally(link) 59 | } 60 | 61 | Text { 62 | id: skip2Text 63 | anchors.top: donationText.bottom 64 | text: "" 65 | } 66 | Text { 67 | anchors.top: skip2Text.bottom 68 | text: Translations.id_please_click_to_open_link 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /sources/defines.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef DEFINES_H 15 | #define DEFINES_H 16 | 17 | #include 18 | 19 | using QWavVectorType = float; 20 | using QWavVector = QVector; 21 | 22 | template 23 | inline bool lessThanZero(T t) { 24 | return t < 0; 25 | } 26 | 27 | __attribute__((always_inline)) inline bool isFreqFitsInDelta(uint32_t sampleRate, uint32_t length, uint32_t signalFreq, double signalDelta, double deltaDivider = 1.0) { 28 | const double freq = sampleRate / length; 29 | const double delta = signalFreq * (signalDelta / deltaDivider); 30 | return freq >= (signalFreq - delta) && freq <= (signalFreq + delta); 31 | } 32 | 33 | __attribute__((always_inline)) inline bool isFreqFitsInDelta2(uint32_t sampleRate, uint32_t length, uint32_t signalFreq, double signalDeltaBelow, double signalDeltaAbove) { 34 | const double freq = sampleRate / length; 35 | const double deltaB = signalFreq * signalDeltaBelow; 36 | const double deltaA = signalFreq * signalDeltaAbove; 37 | return freq >= (signalFreq - deltaB) && freq <= (signalFreq + deltaA); 38 | } 39 | 40 | enum SignalFrequencies { 41 | PILOT_HALF_FREQ = 1660, 42 | PILOT_FREQ = 830, 43 | SYNCHRO_FIRST_HALF_FREQ = 6300, 44 | SYNCHRO_SECOND_HALF_FREQ = 5500, 45 | SYNCHRO_FREQ = 2950, 46 | ZERO_HALF_FREQ = 4200, 47 | ZERO_FIRST_HALF_FREQ = 0, 48 | ZERO_SECOND_HALF_FREQ = 0, 49 | ZERO_FREQ = 2100, 50 | ONE_HALF_FREQ = 2100, 51 | ONE_FREQ = 1050 52 | }; 53 | 54 | constexpr const bool preciseSynchroCheck = false; 55 | constexpr const bool checkForAbnormalSine = true; 56 | 57 | constexpr const double pilotDelta = 0.1; 58 | constexpr const double synchroDelta = 0.3; 59 | constexpr const double zeroDelta = 0.3;//0.3;//0.18; 60 | constexpr const double oneDelta = 0.25;//0.25;//0.1; 61 | constexpr const double sineCheckTolerance = 0.5; 62 | 63 | #endif // DEFINES_H 64 | -------------------------------------------------------------------------------- /sources/core/parseddata.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "parseddata.h" 15 | 16 | ParsedData::ParsedData(QObject* parent) : 17 | QObject(parent) 18 | { 19 | clear(); 20 | } 21 | 22 | void ParsedData::clear(size_t size) 23 | { 24 | mParsedWaveform.reset(new QVector(size)); 25 | mParsedData.reset(new QVector()); 26 | } 27 | 28 | void ParsedData::fillParsedWaveform(const ParsedData::WaveformPart& p, uint8_t val) 29 | { 30 | for (auto i = p.begin; i <= p.end; ++i) { 31 | setParsedWaveform(i, val); 32 | } 33 | } 34 | 35 | void ParsedData::fillParsedWaveform(const ParsedData::WaveformPart& p, uint8_t val, size_t begin, uint8_t begin_val, size_t end, uint8_t end_val) 36 | { 37 | fillParsedWaveform(p, val); 38 | setParsedWaveform(begin, begin_val); 39 | setParsedWaveform(end, end_val); 40 | } 41 | 42 | void ParsedData::fillParsedWaveform(const ParsedData::WaveformPart& begin, const ParsedData::WaveformPart& end, uint8_t val, uint8_t begin_val, uint8_t end_val) 43 | { 44 | fillParsedWaveform(begin, val); 45 | fillParsedWaveform(end, val); 46 | setParsedWaveform(begin.begin, begin_val); 47 | setParsedWaveform(end.end, end_val); 48 | } 49 | 50 | void ParsedData::storeData(QVector&& data, QMap&& dataMapping, size_t begin, size_t end, QVector&& waveformData, uint8_t parity) 51 | { 52 | DataBlock db; 53 | db.dataStart = begin; 54 | db.dataEnd = end; 55 | db.dataMapping = std::move(dataMapping); 56 | db.waveformData = std::move(waveformData); 57 | //Storing parity data 58 | db.parityAwaited = data.last(); 59 | db.parityCalculated = parity; 60 | db.state = parity == db.parityAwaited ? DataState::OK : DataState::R_TAPE_LOADING_ERROR; //Should be checked with checksum 61 | //Storing parsed data block 62 | db.data = std::move(data); 63 | 64 | mParsedData->append(db); 65 | } 66 | -------------------------------------------------------------------------------- /sources/models/dataplayermodel.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef DATAPLAYERMODEL_H 15 | #define DATAPLAYERMODEL_H 16 | 17 | #include 18 | #include 19 | #include 20 | #include "sources/core/waveformparser.h" 21 | 22 | class DataPlayerModel : public QObject 23 | { 24 | Q_OBJECT 25 | 26 | enum PlayingState { 27 | DP_Stopped = 0, 28 | DP_Playing, 29 | DP_Paused 30 | }; 31 | 32 | Q_PROPERTY(bool stopped READ getStopped NOTIFY stoppedChanged) 33 | Q_PROPERTY(int currentBlock READ getCurrentBlock NOTIFY currentBlockChanged) 34 | Q_PROPERTY(int blockTime READ getBlockTime NOTIFY blockTimeChanged) 35 | Q_PROPERTY(int processedTime READ getProcessedTime NOTIFY processedTimeChanged) 36 | Q_PROPERTY(QVariant blockData READ getBlockData NOTIFY currentBlockChanged) 37 | 38 | PlayingState m_playingState; 39 | QScopedPointer m_audio; 40 | QPair, QVector> m_data; 41 | QVariantList m_parserData; 42 | unsigned m_currentBlock; 43 | QTimer m_delayTimer; 44 | QBuffer m_buffer; 45 | const unsigned c_sampleRate { 44100 }; 46 | int m_blockTime; 47 | int m_processedTime; 48 | 49 | protected slots: 50 | void handleAudioOutputStateChanged(QAudio::State state); 51 | void handleAudioOutputNotify(); 52 | void handleNextDataRecord(); 53 | 54 | protected: 55 | explicit DataPlayerModel(QObject* parent = nullptr); 56 | void prepareNextDataRecord(); 57 | 58 | public: 59 | virtual ~DataPlayerModel() override; 60 | 61 | bool getStopped() const; 62 | int getCurrentBlock() const; 63 | int getBlockTime() const; 64 | int getProcessedTime() const; 65 | QVariant getBlockData() const; 66 | 67 | Q_INVOKABLE void playParsedData(uint chNum, uint currentBlock = 0); 68 | Q_INVOKABLE void stop(); 69 | //Q_INVOKABLE void pause(); 70 | //Q_INVOKABLE void resume(); 71 | 72 | static DataPlayerModel* instance(); 73 | 74 | signals: 75 | void stoppedChanged(); 76 | void currentBlockChanged(); 77 | void blockTimeChanged(); 78 | void processedTimeChanged(); 79 | }; 80 | 81 | #endif // DATAPLAYERMODEL_H 82 | -------------------------------------------------------------------------------- /sources/models/suspiciouspointsmodel.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "suspiciouspointsmodel.h" 15 | #include 16 | 17 | SuspiciousPointsModel::SuspiciousPointsModel(QObject* parent) : QObject(parent) 18 | { 19 | 20 | } 21 | 22 | bool SuspiciousPointsModel::addSuspiciousPoint(uint idx) 23 | { 24 | qDebug() << QString("Adding suspicious point: %1").arg(idx); 25 | const auto it { std::lower_bound(mSuspiciousPoints.begin(), mSuspiciousPoints.end(), idx, [](const QVariant& t1, uint t2) { return t1.toUInt() < t2; }) }; 26 | if (it == mSuspiciousPoints.end()) { 27 | mSuspiciousPoints.append(idx); 28 | } else if (idx == it->toUInt()) { 29 | return false; 30 | } else { 31 | mSuspiciousPoints.insert(it, idx); 32 | } 33 | 34 | emit suspiciousPointsChanged(); 35 | emit sizeChanged(); 36 | return true; 37 | } 38 | 39 | bool SuspiciousPointsModel::removeSuspiciousPoint(int idx) 40 | { 41 | qDebug() << QString("Removing suspicious point: %1").arg(idx); 42 | const auto r = mSuspiciousPoints.size() > idx; 43 | if (r) { 44 | mSuspiciousPoints.removeAt(idx); 45 | emit suspiciousPointsChanged(); 46 | emit sizeChanged(); 47 | } 48 | return r; 49 | } 50 | 51 | uint SuspiciousPointsModel::getSuspiciousPoint(int idx) 52 | { 53 | if (mSuspiciousPoints.size() > idx) { 54 | return mSuspiciousPoints[idx].toUInt(); 55 | } 56 | return 0; 57 | } 58 | 59 | void SuspiciousPointsModel::clearSuspiciousPoints() 60 | { 61 | mSuspiciousPoints.clear(); 62 | emit suspiciousPointsChanged(); 63 | emit sizeChanged(); 64 | } 65 | 66 | int SuspiciousPointsModel::getSize() const 67 | { 68 | return mSuspiciousPoints.size(); 69 | } 70 | 71 | QVariantList SuspiciousPointsModel::getSuspiciousPoints() const 72 | { 73 | return mSuspiciousPoints; 74 | } 75 | 76 | void SuspiciousPointsModel::setSuspiciousPoints(const QVariantList& m) 77 | { 78 | mSuspiciousPoints = m; 79 | emit suspiciousPointsChanged(); 80 | emit sizeChanged(); 81 | } 82 | 83 | SuspiciousPointsModel* SuspiciousPointsModel::instance() 84 | { 85 | static QScopedPointer m { new SuspiciousPointsModel() }; 86 | return m.get(); 87 | } 88 | -------------------------------------------------------------------------------- /sources/models/fileworkermodel.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "fileworkermodel.h" 15 | #include "sources/core/waveformparser.h" 16 | #include 17 | #include 18 | 19 | FileWorkerModel::FileWorkerModel(QObject* parent) : 20 | QObject(parent), 21 | m_wavFileName(QString()) 22 | { 23 | 24 | } 25 | 26 | /*WavReader::ErrorCodesEnum*/ int FileWorkerModel::openTapFileByUrl(const QString& fileNameUrl) { 27 | QUrl u(fileNameUrl); 28 | return openTapFile(u.toLocalFile()); 29 | } 30 | 31 | /*WavReader::ErrorCodesEnum*/ int FileWorkerModel::openTapFile(const QString& fileName) { 32 | auto& r = *WavReader::instance(); 33 | r.close(); 34 | 35 | r.loadTap(fileName); 36 | m_wavFileName = fileName; 37 | emit wavFileNameChanged(); 38 | return WavReader::Ok; 39 | } 40 | 41 | /*WavReader::ErrorCodesEnum*/ int FileWorkerModel::openWavFileByUrl(const QString& fileNameUrl) 42 | { 43 | QUrl u(fileNameUrl); 44 | return openWavFile(u.toLocalFile()); 45 | } 46 | 47 | /*WavReader::ErrorCodesEnum*/ int FileWorkerModel::openWavFile(const QString& fileName) 48 | { 49 | auto& r = *WavReader::instance(); 50 | r.close(); 51 | 52 | auto result = r.setFileName(fileName); 53 | result = r.open(); 54 | if (result == WavReader::Ok) { 55 | result = r.read(); 56 | if (result == WavReader::Ok) { 57 | m_wavFileName = fileName; 58 | emit wavFileNameChanged(); 59 | } 60 | } 61 | 62 | return result; 63 | } 64 | 65 | /*WavReader::ErrorCodesEnum*/ int FileWorkerModel::openWaveformFileByUrl(const QString& fileNameUrl) 66 | { 67 | QUrl u(fileNameUrl); 68 | return openWaveformFile(u.toLocalFile()); 69 | } 70 | 71 | /*WavReader::ErrorCodesEnum*/ int FileWorkerModel::openWaveformFile(const QString& fileName) 72 | { 73 | auto& r = *WavReader::instance(); 74 | r.close(); 75 | 76 | r.loadWaveform(fileName); 77 | m_wavFileName = fileName; 78 | emit wavFileNameChanged(); 79 | return WavReader::Ok; 80 | } 81 | 82 | /*WavReader::ErrorCodesEnum*/ int FileWorkerModel::saveWaveformFileByUrl(const QString& fileNameUrl) 83 | { 84 | QUrl u(fileNameUrl); 85 | return saveWaveformFile(u.toLocalFile()); 86 | } 87 | 88 | /*WavReader::ErrorCodesEnum*/ int FileWorkerModel::saveWaveformFile(const QString& fileName) 89 | { 90 | auto& r = *WavReader::instance(); 91 | r.saveWaveform(fileName); 92 | return WavReader::Ok; 93 | } 94 | 95 | QString FileWorkerModel::getWavFileName() const 96 | { 97 | return m_wavFileName; 98 | } 99 | 100 | FileWorkerModel::~FileWorkerModel() 101 | { 102 | qDebug() << "~FileWorkerModel"; 103 | } 104 | -------------------------------------------------------------------------------- /sources/main.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include 15 | #include 16 | #include "sources/controls/waveformcontrol.h" 17 | #include "sources/core/waveformparser.h" 18 | #include "sources/models/fileworkermodel.h" 19 | #include "sources/models/suspiciouspointsmodel.h" 20 | #include "sources/models/parsersettingsmodel.h" 21 | #include "sources/models/actionsmodel.h" 22 | #include "sources/models/dataplayermodel.h" 23 | #include "sources/translations/translationmanager.h" 24 | 25 | void registerTypes() 26 | { 27 | qmlRegisterSingletonInstance("com.models.zxtapereviver", 1, 0, "TranslationManager", TranslationManager::instance()); 28 | qmlRegisterUncreatableType("com.enums.zxtapereviver", 1, 0, "FileWorkerResults", QString()); 29 | qmlRegisterUncreatableType("com.enums.zxtapereviver", 1, 0, "ErrorCodesEnum", QString()); 30 | qmlRegisterUncreatableType("com.enums.zxtapereviver", 1, 0, "WaveformControlOperationModes", QString()); 31 | 32 | qmlRegisterType("WaveformControl", 1, 0, "WaveformControl"); 33 | qmlRegisterSingletonType("com.models.zxtapereviver", 1, 0, "FileWorkerModel", [](QQmlEngine* engine, QJSEngine* scriptEngine) -> QObject* { 34 | Q_UNUSED(engine) 35 | Q_UNUSED(scriptEngine) 36 | return new FileWorkerModel(); 37 | }); 38 | qmlRegisterSingletonInstance("com.models.zxtapereviver", 1, 0, "SuspiciousPointsModel", SuspiciousPointsModel::instance()); 39 | qmlRegisterSingletonInstance("com.core.zxtapereviver", 1, 0, "WavReader", WavReader::instance()); 40 | qmlRegisterSingletonInstance("com.core.zxtapereviver", 1, 0, "WaveformParser", WaveformParser::instance()); 41 | qmlRegisterSingletonInstance("com.models.zxtapereviver", 1, 0, "ParserSettingsModel", ParserSettingsModel::instance()); 42 | qmlRegisterSingletonInstance("com.models.zxtapereviver", 1, 0, "ActionsModel", ActionsModel::instance()); 43 | qmlRegisterSingletonInstance("com.models.zxtapereviver", 1, 0, "ConfigurationManager", ConfigurationManager::instance()); 44 | qmlRegisterSingletonInstance("com.models.zxtapereviver", 1, 0, "DataPlayerModel", DataPlayerModel::instance()); 45 | } 46 | 47 | int main(int argc, char *argv[]) 48 | { 49 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 50 | 51 | QGuiApplication app(argc, argv); 52 | 53 | registerTypes(); 54 | 55 | QQmlApplicationEngine engine; 56 | const QUrl url(QStringLiteral("qrc:/main.qml")); 57 | QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { 58 | if (!obj && url == objUrl) { 59 | QCoreApplication::exit(-1); 60 | } 61 | }, Qt::QueuedConnection); 62 | engine.load(url); 63 | 64 | return app.exec(); 65 | } 66 | -------------------------------------------------------------------------------- /sources/translations/translationmanager.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "translationmanager.h" 15 | #include 16 | #include 17 | #include 18 | #include "sources/configuration/configurationmanager.h" 19 | 20 | TranslationManager::TranslationManager(QObject* parent) : 21 | QObject(parent), 22 | m_translator(new QTranslator()), 23 | m_currentLanguage(ConfigurationManager::instance()->getApplicationCustomization()->translationLanguage()) 24 | { 25 | installTranslator(); 26 | } 27 | 28 | QVariantList TranslationManager::getAvailableTranslations() const { 29 | decltype (getAvailableTranslations()) result; 30 | const auto translations { QString(AVAILABLE_TRANSLATIONS).split(';', Qt::SkipEmptyParts) }; 31 | 32 | for (const auto& t: translations) { 33 | const auto translationMap { t.split(':', Qt::SkipEmptyParts) }; 34 | const auto language { translationMap.size() > 0 ? translationMap.first() : QString() }; 35 | const auto countryCode { getEnumValue(translationMap.size() > 1 ? translationMap.last() : QString()) }; 36 | const auto languageGenerated { QString("id_%1_language").arg(language.toLower()) }; 37 | const auto languageTranslated { qtTrId(languageGenerated.toStdString().c_str()) }; 38 | 39 | result.append(QVariantMap({ { "language", languageTranslated == languageGenerated || languageTranslated.isEmpty() ? language : languageTranslated }, 40 | { "countryCode", countryCode } })); 41 | } 42 | 43 | return result; 44 | } 45 | 46 | void TranslationManager::installTranslator(bool firstRun) { 47 | static const QString n_path { ":/translations/translations/" }; 48 | static const QString n_filename { "zxtapereviver_" }; 49 | 50 | if (!firstRun) { 51 | QCoreApplication::removeTranslator(m_translator.data()); 52 | } 53 | 54 | const QString name { n_filename + getTranslationName() }; 55 | if (!m_translator->load(name, n_path)) { 56 | qDebug() << "Error loading translation!"; 57 | if (!m_translator->load( n_filename + getEnumName(TranslationLanguages::en_US), n_path)) { 58 | qDebug() << "Error loading default translation!"; 59 | } 60 | } 61 | 62 | if (!QCoreApplication::installTranslator(m_translator.data())) { 63 | qDebug() << "Error installing translator!"; 64 | } 65 | } 66 | 67 | QString TranslationManager::getTranslationChanged() const { 68 | return QString(); 69 | } 70 | 71 | void TranslationManager::setTranslation(TranslationLanguages lng) { 72 | if (m_currentLanguage != lng) { 73 | m_currentLanguage = lng; 74 | 75 | auto& cm { *ConfigurationManager::instance() }; 76 | cm.getApplicationCustomization()->setTranslationLanguage(lng); 77 | cm.writeConfiguration(); 78 | 79 | installTranslator(); 80 | emit translationChanged(); 81 | } 82 | } 83 | 84 | QString TranslationManager::getTranslationName() const { 85 | return getEnumName(m_currentLanguage); 86 | } 87 | 88 | TranslationManager* TranslationManager::instance() { 89 | static TranslationManager manager; 90 | return &manager; 91 | } 92 | 93 | QVariantList TranslationManager::getLanguages() const { 94 | //We have to get available translations every time them asked to support dynamic translation of existing language values 95 | return getAvailableTranslations(); 96 | } 97 | -------------------------------------------------------------------------------- /sources/core/parseddata.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef PARSEDDATA_H 15 | #define PARSEDDATA_H 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | class ParsedData : public QObject 22 | { 23 | Q_OBJECT 24 | 25 | public: 26 | // |------------------------------- 1 - zero '0' data bit 27 | // | |----------------------------- 1 - one '1' data bit 28 | // | | |--------------------------- 1 - pilot tone 29 | // | | | |------------------------- 1 - synchro signal 30 | // | | | | |----------------------- 1 - byte bound 31 | // | | | | | |--------------------- 1 - begin of signal sequence 32 | // | | | | | | |------------------- 1 - middle of signal sequence 33 | // | | | | | | | |----------------- 1 - end of signal sequence 34 | // x x x x x x x x 35 | 36 | static constexpr const uint8_t zeroBit = 0b10000000; //zero data bit 37 | static constexpr const uint8_t oneBit = 0b01000000; //one bit 38 | static constexpr const uint8_t pilotTone = 0b00100000; //pilot tone 39 | static constexpr const uint8_t synchroSignal = 0b00010000; //synchro signal 40 | static constexpr const uint8_t byteBound = 0b00001000; //byte bound 41 | static constexpr const uint8_t sequenceBegin = 0b00000100; //begin of signal sequence 42 | static constexpr const uint8_t sequenceMiddle = 0b00000010; //middle of signal sequence 43 | static constexpr const uint8_t sequenceEnd = 0b00000001; //end of signal sequence 44 | 45 | enum WaveformSign { POSITIVE, NEGATIVE }; 46 | enum DataState { OK, R_TAPE_LOADING_ERROR }; 47 | 48 | struct WaveformPart 49 | { 50 | uint32_t begin; 51 | uint32_t end; 52 | uint32_t length; 53 | WaveformSign sign; 54 | }; 55 | 56 | struct DataBlock 57 | { 58 | uint32_t dataStart; 59 | uint32_t dataEnd; 60 | QVector data; 61 | QMap dataMapping; 62 | QVector waveformData; 63 | DataState state; 64 | uint8_t parityCalculated; 65 | uint8_t parityAwaited; 66 | }; 67 | 68 | explicit ParsedData(QObject* parent = nullptr); 69 | virtual ~ParsedData() override = default; 70 | 71 | void storeData(QVector&& data, QMap&& dataMapping, size_t begin, size_t end, QVector&& waveformData, uint8_t parity); 72 | void clear(size_t size = 0); 73 | void fillParsedWaveform(const ParsedData::WaveformPart& p, uint8_t val); 74 | void fillParsedWaveform(const ParsedData::WaveformPart& p, uint8_t val, size_t begin, uint8_t begin_val, size_t end, uint8_t end_val); 75 | void fillParsedWaveform(const ParsedData::WaveformPart& begin, const ParsedData::WaveformPart& end, uint8_t val, uint8_t begin_val, uint8_t end_val); 76 | __attribute__((always_inline)) inline void setParsedWaveform(size_t pos, uint8_t val) { 77 | mParsedWaveform.get()->operator [](pos) = val; 78 | } 79 | __attribute__((always_inline)) inline void orParsedWaveform(size_t pos, uint8_t val) { 80 | mParsedWaveform.get()->operator [](pos) |= val; 81 | } 82 | 83 | __attribute__((always_inline)) inline QSharedPointer> getParsedData() const { 84 | return mParsedData; 85 | } 86 | __attribute__((always_inline)) inline QSharedPointer> getParsedWaveform() const { 87 | return mParsedWaveform; 88 | } 89 | 90 | private: 91 | QSharedPointer> mParsedWaveform; 92 | QSharedPointer> mParsedData; 93 | 94 | }; 95 | 96 | #endif // PARSEDDATA_H 97 | -------------------------------------------------------------------------------- /sources/core/waveformparser.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef WAVEFORMPARSER_H 15 | #define WAVEFORMPARSER_H 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "sources/core/parseddata.h" 23 | #include "sources/core/wavreader.h" 24 | #include "sources/defines.h" 25 | 26 | class WaveformParser : public QObject 27 | { 28 | Q_OBJECT 29 | 30 | Q_PROPERTY(QVariantList parsedChannel0 READ getParsedChannel0 NOTIFY parsedChannel0Changed) 31 | Q_PROPERTY(QVariantList parsedChannel1 READ getParsedChannel1 NOTIFY parsedChannel1Changed) 32 | 33 | public: 34 | // enum SignalValue { ZERO, ONE, PILOT, SYNCHRO }; 35 | // struct WaveformData 36 | // { 37 | // uint32_t begin; 38 | // uint32_t end; 39 | // uint32_t waveBegin; 40 | // uint32_t waveEnd; 41 | // SignalValue value; 42 | // }; 43 | 44 | private: 45 | enum StateType { SEARCH_OF_PILOT_TONE, PILOT_TONE, SYNCHRO_SIGNAL, DATA_SIGNAL, END_OF_DATA, NO_MORE_DATA }; 46 | 47 | template 48 | QVector parseChannel(const QVector& ch) { 49 | decltype(parseChannel(ch)) result; 50 | if (ch.size() < 1) { 51 | return result; 52 | } 53 | 54 | auto it = ch.begin(); 55 | auto val = *it; 56 | while (it != ch.end()) { 57 | auto prevIt = it; 58 | it = std::find_if(it, ch.end(), [&val](const T& i) { return lessThanZero(val) != lessThanZero(i); }); 59 | typename std::remove_reference::type part; 60 | part.begin = std::distance(ch.begin(), prevIt); 61 | part.end = std::distance(ch.begin(), std::prev(it)); 62 | part.length = std::distance(prevIt, it); 63 | part.sign = lessThanZero(val) ? ParsedData::NEGATIVE : ParsedData::POSITIVE; 64 | 65 | result.append(part); 66 | 67 | if (it != ch.end()) { 68 | val = *it; 69 | } 70 | } 71 | 72 | return result; 73 | } 74 | 75 | //Helper methods intended to use in case of change we can made them only once 76 | __attribute__((always_inline)) inline bool isZeroFreqFitsInDelta(uint32_t sampleRate, uint32_t length, uint32_t signalFreq, double signalDeltaBelow, double signalDeltaAbove) const; 77 | __attribute__((always_inline)) inline bool isOneFreqFitsInDelta(uint32_t sampleRate, uint32_t length, uint32_t signalFreq, double signalDeltaBelow, double signalDeltaAbove) const; 78 | 79 | WavReader& mWavReader; 80 | QMap m_parsedData; 81 | // QMap> mParsedWaveform; 82 | // QMap> mParsedData; 83 | mutable QVector mSelectedBlocks; 84 | 85 | protected: 86 | explicit WaveformParser(QObject* parent = nullptr); 87 | QVariantList getParsedChannelData(uint chNum) const; 88 | __attribute__((always_inline)) inline ParsedData* getOrCreateParsedDataPtr(uint chNum); 89 | __attribute__((always_inline)) inline ParsedData* getParsedDataPtr(uint chNum) const; 90 | 91 | public: 92 | virtual ~WaveformParser() override = default; 93 | 94 | static WaveformParser* instance(); 95 | 96 | void parse(uint chNum); 97 | void saveTap(uint chNum, const QString& fileName = QString()); 98 | void saveWaveform(uint chNum); 99 | QVector getParsedWaveform(uint chNum) const; 100 | QPair, QVector> getParsedData(uint chNum) const; 101 | QSharedPointer> getParsedDataSharedPtr(uint chNum) const; 102 | 103 | void repairWaveform2(uint chNum); 104 | 105 | Q_INVOKABLE void toggleBlockSelection(int blockNum); 106 | Q_INVOKABLE int getBlockDataStart(uint chNum, uint blockNum) const; 107 | Q_INVOKABLE int getBlockDataEnd(uint chNum, uint blockNum) const; 108 | Q_INVOKABLE int getPositionByAddress(uint chNum, uint blockNum, uint addr) const; 109 | //getters 110 | QVariantList getParsedChannel0() const; 111 | QVariantList getParsedChannel1() const; 112 | 113 | signals: 114 | void parsedChannel0Changed(); 115 | void parsedChannel1Changed(); 116 | }; 117 | 118 | #endif // WAVEFORMPARSER_H 119 | -------------------------------------------------------------------------------- /sources/controls/waveformcontrol.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef WAVEFORMCONTROL_H 15 | #define WAVEFORMCONTROL_H 16 | 17 | #include 18 | #include 19 | #include "sources/core/wavreader.h" 20 | #include "sources/core/waveformparser.h" 21 | #include "sources/configuration/configurationmanager.h" 22 | #include "sources/models/waveformmodel.h" 23 | #include "sources/util/enummetainfo.h" 24 | 25 | class WaveformControl : public QQuickPaintedItem 26 | { 27 | Q_OBJECT 28 | 29 | Q_PROPERTY(uint channelNumber READ getChannelNumber WRITE setChannelNumber NOTIFY channelNumberChanged) 30 | Q_PROPERTY(int wavePos READ getWavePos WRITE setWavePos NOTIFY wavePosChanged) 31 | Q_PROPERTY(int waveLength READ getWaveLength NOTIFY waveLengthChanged) 32 | Q_PROPERTY(double xScaleFactor READ getXScaleFactor WRITE setXScaleFactor NOTIFY xScaleFactorChanged) 33 | Q_PROPERTY(double yScaleFactor READ getYScaleFactor WRITE setYScaleFactor NOTIFY yScaleFactorChanged) 34 | Q_PROPERTY(bool isWaveformRepaired READ getIsWaveformRepaired NOTIFY isWaveformRepairedChanged) 35 | Q_PROPERTY(WaveformControlOperationModes operationMode READ getOperationMode WRITE setOperationMode NOTIFY operationModeChanged) 36 | 37 | WavReader& mWavReader; 38 | WaveformParser& mWavParser; 39 | WaveFormModel& mWaveFormModel; 40 | ConfigurationManager::WaveformCustomization& m_customData; 41 | 42 | QColor getBackgroundColor() const; 43 | 44 | public: 45 | enum WaveformControlOperationModes { 46 | WaveformRepairMode, 47 | WaveformSelectionMode, 48 | WaveformMeasurementMode 49 | }; 50 | Q_ENUM(WaveformControlOperationModes) 51 | 52 | explicit WaveformControl(QQuickItem* parent = nullptr); 53 | 54 | uint getChannelNumber() const; 55 | int32_t getWavePos() const; 56 | int32_t getWaveLength() const; 57 | double getXScaleFactor() const; 58 | double getYScaleFactor() const; 59 | bool getIsWaveformRepaired() const; 60 | WaveformControlOperationModes getOperationMode() const; 61 | 62 | void setChannelNumber(uint chNum); 63 | void setWavePos(int wavPos); 64 | void setXScaleFactor(double xScaleFactor); 65 | void setYScaleFactor(double yScaleFactor); 66 | void setOperationMode(WaveformControlOperationModes mode); 67 | 68 | virtual void paint(QPainter* painter) override; 69 | virtual void mousePressEvent(QMouseEvent* event) override; 70 | virtual void mouseReleaseEvent(QMouseEvent* event) override; 71 | virtual void mouseMoveEvent(QMouseEvent *event) override; 72 | 73 | Q_INVOKABLE void reparse(); 74 | Q_INVOKABLE void saveTap(const QString& fileUrl = QString()); 75 | Q_INVOKABLE void saveWaveform(); 76 | Q_INVOKABLE void repairWaveform(); 77 | Q_INVOKABLE void restoreWaveform(); 78 | Q_INVOKABLE void shiftWaveform(); 79 | Q_INVOKABLE void copySelectedToAnotherChannel(); 80 | 81 | signals: 82 | void channelNumberChanged(); 83 | void wavePosChanged(); 84 | void waveLengthChanged(); 85 | void xScaleFactorChanged(); 86 | void yScaleFactorChanged(); 87 | void isWaveformRepairedChanged(); 88 | void operationModeChanged(); 89 | 90 | void doubleClick(int idx); 91 | void cannotSetMeasurementPoint(); 92 | void frequency(int freq); 93 | 94 | private: 95 | enum ClickStates { 96 | WaitForFirstPress, 97 | WaitForSecondPress, 98 | WaitForFirstRelease, 99 | WaitForSecondRelease 100 | }; 101 | 102 | uint m_channelNumber; 103 | bool m_isWaveformRepaired; 104 | bool m_allowToGrabPoint; 105 | bool m_pointGrabbed; 106 | int m_pointIndex; 107 | QWavVectorType m_initialValue; 108 | QWavVectorType m_newValue; 109 | int m_wavePos; 110 | double m_xScaleFactor; 111 | double m_yScaleFactor; 112 | ClickStates m_clickState; 113 | QDateTime m_clickTime; 114 | int m_clickPosition; 115 | WaveformControlOperationModes m_operationMode; 116 | bool m_rangeSelected; 117 | QPair m_selectionRange; 118 | int m_clickCount; 119 | 120 | QSharedPointer getChannel(uint* chNum = nullptr) const; 121 | int getWavPositionByMouseX(int x, int* point = nullptr, double* dx = nullptr) const; 122 | }; 123 | 124 | #endif // WAVEFORMCONTROL_H 125 | -------------------------------------------------------------------------------- /sources/core/wavreader.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef WAVREADER_H 15 | #define WAVREADER_H 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "sources/defines.h" 22 | 23 | class WavReader : public QObject 24 | { 25 | Q_OBJECT 26 | 27 | Q_PROPERTY(uint numberOfChannels READ getNumberOfChannels NOTIFY numberOfChannelsChanged) 28 | 29 | private: 30 | //Disable struct alignment 31 | #pragma pack(push, 1) 32 | struct Int24 { 33 | uint8_t b0; 34 | uint8_t b1; 35 | uint8_t b2; 36 | }; 37 | 38 | struct WavChunk 39 | { 40 | uint32_t chunkId; 41 | uint32_t chunkDataSize; 42 | }; 43 | 44 | struct WavHeader 45 | { 46 | WavChunk chunk; 47 | uint32_t riffType; 48 | }; 49 | 50 | struct WavFmt 51 | { 52 | WavChunk chunk; 53 | uint16_t compressionCode; 54 | uint16_t numberOfChannels; 55 | uint32_t sampleRate; 56 | uint32_t avgBytesPerSecond; 57 | uint16_t blockAlign; 58 | uint16_t significantBitsPerSample; 59 | }; 60 | 61 | template 62 | struct WavMonoSample 63 | { 64 | T channel; 65 | }; 66 | 67 | template 68 | struct WavStereoSample 69 | { 70 | T leftChannel; 71 | T rightChannel; 72 | }; 73 | #pragma pack(pop) 74 | 75 | const uint32_t riffId = 0x46464952; //"RIFF" 76 | const uint32_t waveId = 0x45564157; //"WAVE" 77 | const uint32_t fmt_Id = 0x20746D66; //"fmt " 78 | const uint32_t dataId = 0x61746164; //"data" 79 | 80 | template 81 | const T* readData(QByteArray& buf) { 82 | buf = mWavFile.read(sizeof(T)); 83 | if ((unsigned) buf.size() < sizeof(T)) { 84 | return nullptr; 85 | } 86 | return reinterpret_cast(buf.data()); 87 | } 88 | 89 | template 90 | T* getData(QByteArray& buf, size_t& bufIndex) const { 91 | T* res = reinterpret_cast(buf.data() + bufIndex); 92 | bufIndex += sizeof(T); 93 | return res; 94 | } 95 | 96 | template 97 | void appendData(QByteArray& buf, const T& data) const { 98 | buf.append(reinterpret_cast(&data), sizeof(T)); 99 | } 100 | 101 | uint8_t* getData(QByteArray& buf, size_t& bufIndex, uint dataSize) const { 102 | if (dataSize == 0) { 103 | return nullptr; 104 | } 105 | 106 | auto t = getData(buf, bufIndex); 107 | bufIndex += dataSize - 1; 108 | return t; 109 | } 110 | 111 | QWavVectorType getSample(QByteArray& buf, size_t& bufIndex, uint dataSize, uint compressionCode) const; 112 | QWavVector* createVector(size_t bytesPerSample, size_t size); 113 | unsigned calculateOnesInByte(uint8_t n); 114 | 115 | WavFmt mWavFormatHeader; 116 | WavChunk mCurrentChunk; 117 | bool mWavOpened; 118 | QFile mWavFile; 119 | QSharedPointer mChannel0; 120 | QSharedPointer mChannel1; 121 | QMap> mStoredChannels; 122 | 123 | protected: 124 | explicit WavReader(QObject* parent = nullptr); 125 | 126 | public: 127 | enum ErrorCodesEnum { 128 | Ok, 129 | AlreadyOpened, 130 | CantOpen, 131 | NotOpened, 132 | InvalidWavFormat, 133 | UnsupportedWavFormat, 134 | InsufficientData, 135 | EndOfBuffer 136 | }; 137 | Q_ENUM(ErrorCodesEnum) 138 | 139 | virtual ~WavReader() override; 140 | 141 | uint getNumberOfChannels() const; 142 | uint32_t getSampleRate() const; 143 | uint getBytesPerSample() const; 144 | QSharedPointer getChannel0() const; 145 | QSharedPointer getChannel1() const; 146 | 147 | ErrorCodesEnum setFileName(const QString& fileName); 148 | ErrorCodesEnum open(); 149 | ErrorCodesEnum read(); 150 | ErrorCodesEnum close(); 151 | 152 | void loadTap(const QString& fname); 153 | void loadWaveform(const QString& fname); 154 | void saveWaveform(const QString& fname = QString()) const; 155 | void shiftWaveform(uint chNum); 156 | void storeWaveform(uint chNum); 157 | void restoreWaveform(uint chNum); 158 | void repairWaveform(uint chNum); 159 | void normalizeWaveform(uint chNum); 160 | void normalizeWaveform2(uint chNum); 161 | 162 | static WavReader* instance(); 163 | 164 | signals: 165 | void numberOfChannelsChanged(); 166 | }; 167 | 168 | #endif // WAVREADER_H 169 | -------------------------------------------------------------------------------- /sources/models/parsersettingsmodel.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef PARSERSETTINGSMODEL_H 15 | #define PARSERSETTINGSMODEL_H 16 | 17 | #include 18 | 19 | class ParserSettingsModel : public QObject 20 | { 21 | Q_OBJECT 22 | 23 | Q_PROPERTY(int pilotHalfFreq READ getPilotHalfFreq WRITE setPilotHalfFreq NOTIFY pilotHalfFreqChanged) 24 | Q_PROPERTY(int pilotFreq READ getPilotFreq WRITE setPilotFreq NOTIFY pilotFreqChanged) 25 | Q_PROPERTY(int synchroFirstHalfFreq READ getSynchroFirstHalfFreq WRITE setSynchroFirstHalfFreq NOTIFY synchroFirstHalfFreqChanged) 26 | Q_PROPERTY(int synchroSecondHalfFreq READ getSynchroSecondHalfFreq WRITE setSynchroSecondHalfFreq NOTIFY synchroSecondHalfFreqChanged) 27 | Q_PROPERTY(int synchroFreq READ getSynchroFreq WRITE setSynchroFreq NOTIFY synchroFreqChanged) 28 | Q_PROPERTY(bool preciseSynchroCheck READ getPreciseSynchroCheck WRITE setPreciseSynchroCheck NOTIFY preciseSychroCheckChanged) 29 | Q_PROPERTY(int zeroHalfFreq READ getZeroHalfFreq WRITE setZeroHalfFreq NOTIFY zeroHalfFreqChanged) 30 | Q_PROPERTY(int zeroFreq READ getZeroFreq WRITE setZeroFreq NOTIFY zeroFreqChanged) 31 | Q_PROPERTY(int oneHalfFreq READ getOneHalfFreq WRITE setOneHalfFreq NOTIFY oneHalfFreqChanged) 32 | Q_PROPERTY(int oneFreq READ getOneFreq WRITE setOneFreq NOTIFY oneFreqChanged) 33 | Q_PROPERTY(double pilotDelta READ getPilotDelta WRITE setPilotDelta NOTIFY pilotDeltaChanged) 34 | Q_PROPERTY(double synchroDelta READ getSynchroDelta WRITE setSynchroDelta NOTIFY synchroDeltaChanged) 35 | Q_PROPERTY(double zeroDelta READ getZeroDelta WRITE setZeroDelta NOTIFY zeroDeltaChanged) 36 | Q_PROPERTY(double oneDelta READ getOneDelta WRITE setOneDelta NOTIFY oneDeltaChanged) 37 | Q_PROPERTY(bool checkForAbnormalSine READ getCheckForAbnormalSine WRITE setCheckForAbnormalSine NOTIFY checkForAbnormalSineChanged) 38 | Q_PROPERTY(double sineCheckTolerance READ getSineCheckTolerance WRITE setSineCheckTolerance NOTIFY sineCheckToleranceChanged) 39 | 40 | protected: 41 | explicit ParserSettingsModel(QObject* parent = nullptr); 42 | 43 | public: 44 | struct ParserSettings { 45 | int32_t pilotHalfFreq; 46 | int32_t pilotFreq; 47 | int32_t synchroFirstHalfFreq; 48 | int32_t synchroSecondHalfFreq; 49 | int32_t synchroFreq; 50 | bool preciseSynchroCheck; 51 | int32_t zeroHalfFreq; 52 | int32_t zeroFreq; 53 | int32_t oneHalfFreq; 54 | int32_t oneFreq; 55 | double pilotDelta; 56 | double synchroDelta; 57 | double zeroDelta; 58 | double oneDelta; 59 | bool checkForAbnormalSine; 60 | double sineCheckTolerance; 61 | }; 62 | 63 | virtual ~ParserSettingsModel() = default; 64 | const ParserSettings& getParserSettings() const; 65 | 66 | static ParserSettingsModel* instance(); 67 | 68 | Q_INVOKABLE void restoreDefaultSettings(); 69 | 70 | //Getters 71 | int getPilotHalfFreq() const; 72 | int getPilotFreq() const; 73 | int getSynchroFirstHalfFreq() const; 74 | int getSynchroSecondHalfFreq() const; 75 | int getSynchroFreq() const; 76 | bool getPreciseSynchroCheck() const; 77 | int getZeroHalfFreq() const; 78 | int getZeroFreq() const; 79 | int getOneHalfFreq() const; 80 | int getOneFreq() const; 81 | double getPilotDelta() const; 82 | double getSynchroDelta() const; 83 | double getZeroDelta() const; 84 | double getOneDelta() const; 85 | bool getCheckForAbnormalSine() const; 86 | double getSineCheckTolerance() const; 87 | 88 | //Setters 89 | void setPilotHalfFreq(int freq); 90 | void setPilotFreq(int freq); 91 | void setSynchroFirstHalfFreq(int freq); 92 | void setSynchroSecondHalfFreq(int freq); 93 | void setSynchroFreq(int freq); 94 | void setPreciseSynchroCheck(bool precise); 95 | void setZeroHalfFreq(int freq); 96 | void setZeroFreq(int freq); 97 | void setOneHalfFreq(int freq); 98 | void setOneFreq(int freq); 99 | void setPilotDelta(double delta); 100 | void setSynchroDelta(double delta); 101 | void setZeroDelta(double delta); 102 | void setOneDelta(double delta); 103 | void setCheckForAbnormalSine(bool check); 104 | void setSineCheckTolerance(double value); 105 | 106 | signals: 107 | void pilotHalfFreqChanged(); 108 | void pilotFreqChanged(); 109 | void synchroFirstHalfFreqChanged(); 110 | void synchroSecondHalfFreqChanged(); 111 | void synchroFreqChanged(); 112 | void preciseSychroCheckChanged(); 113 | void zeroHalfFreqChanged(); 114 | void zeroFreqChanged(); 115 | void oneHalfFreqChanged(); 116 | void oneFreqChanged(); 117 | void pilotDeltaChanged(); 118 | void synchroDeltaChanged(); 119 | void zeroDeltaChanged(); 120 | void oneDeltaChanged(); 121 | void checkForAbnormalSineChanged(); 122 | void sineCheckToleranceChanged(); 123 | 124 | private: 125 | ParserSettings m_parserSettings; 126 | }; 127 | 128 | #endif // PARSERSETTINGSMODEL_H 129 | -------------------------------------------------------------------------------- /ZXTapeReviver.pro: -------------------------------------------------------------------------------- 1 | #******************************************************************************* 2 | # ZX Tape Reviver 3 | #----------------- 4 | # 5 | # Author: Leonid Golouz 6 | # E-mail: lgolouz@list.ru 7 | # YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | # YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | # 10 | # Code modification and distribution of any kind is not allowed without direct 11 | # permission of the Author. 12 | #******************************************************************************* 13 | 14 | QT += quick gui quickcontrols2 multimedia 15 | 16 | # The following define makes your compiler emit warnings if you use 17 | # any Qt feature that has been marked deprecated (the exact warnings 18 | # depend on your compiler). Refer to the documentation for the 19 | # deprecated API to know how to port your code away from it. 20 | DEFINES += QT_DEPRECATED_WARNINGS 21 | 22 | # You can also make your code fail to compile if it uses deprecated APIs. 23 | # In order to do so, uncomment the following line. 24 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 25 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 26 | CONFIG += c++17 27 | 28 | #Dynamically generate translation names and country codes based on AVAILABLE_TRANSLATIONS variable 29 | AVAILABLE_TRANSLATIONS = English:en_US Russian:ru_RU 30 | 31 | TRANSLATION_FILENAME = zxtapereviver_ 32 | TRANSLATIONS_PATH = ./qml/translations/ 33 | 34 | for (T, AVAILABLE_TRANSLATIONS) { 35 | S = $$split(T, ":") 36 | TRANSLATION_LANGUAGE = $$take_first(S) 37 | COUNTRY_CODE = $$take_last(S) 38 | 39 | TRANSLATION_ID = ID_$${TRANSLATION_LANGUAGE}_LANGUAGE 40 | TRANSLATION_ID_UPPER = $$upper($${TRANSLATION_ID}) 41 | TRANSLATION_ID_LOWER = $$lower($${TRANSLATION_ID}) 42 | TRANSLATION_ID_HEADER = "extern const char* $${TRANSLATION_ID_UPPER};" 43 | TRANSLATION_ID_CODE = "const char* $${TRANSLATION_ID_UPPER}=QT_TRID_NOOP(\"$${TRANSLATION_ID_LOWER}\");" 44 | 45 | isEmpty(DEFINED_TRANSLATIONS) { 46 | COUNTRY_CODES = $${COUNTRY_CODE} 47 | DEFINED_TRANSLATIONS = $${T} 48 | } 49 | else { 50 | COUNTRY_CODES = $${COUNTRY_CODES},$${COUNTRY_CODE} 51 | DEFINED_TRANSLATIONS = $${DEFINED_TRANSLATIONS};$${T} 52 | } 53 | TRANSLATION_IDS_HEADER = $${TRANSLATION_IDS_HEADER} $${TRANSLATION_ID_HEADER} 54 | TRANSLATION_IDS_CODE = $${TRANSLATION_IDS_CODE} $${TRANSLATION_ID_CODE} 55 | 56 | system($$[QT_INSTALL_BINS]/lrelease -idbased $${TRANSLATIONS_PATH}/$${TRANSLATION_FILENAME}$${COUNTRY_CODE}.xlf -qm $${TRANSLATIONS_PATH}/$${TRANSLATION_FILENAME}$${COUNTRY_CODE}.qm) 57 | } 58 | DEFINES += AVAILABLE_TRANSLATIONS=\"\\\"$${DEFINED_TRANSLATIONS}\\\"\" \ 59 | COUNTRY_CODES=$${COUNTRY_CODES} 60 | 61 | TRANSLATIONS_GENERATED_FILENAME = $${PWD}/generated/translations_generated 62 | TRANSLATIONS_GENERATED_FILENAME_H = $${TRANSLATIONS_GENERATED_FILENAME}.h 63 | TRANSLATIONS_GENERATED_FILENAME_CPP = $${TRANSLATIONS_GENERATED_FILENAME}.cpp 64 | 65 | write_file($${TRANSLATIONS_GENERATED_FILENAME_H}, TRANSLATION_IDS_HEADER) 66 | write_file($${TRANSLATIONS_GENERATED_FILENAME_CPP}, TRANSLATION_IDS_CODE) 67 | 68 | contains(QMAKE_HOST.os, Windows) { 69 | GIT_CMD = \"$$system(where git)\" 70 | } 71 | else { 72 | GIT_CMD = $$system(which git) 73 | } 74 | ZXTAPEREVIVER_VERSION = $$system($${GIT_CMD} --git-dir $${PWD}/.git --work-tree $${PWD} describe --always --tags) 75 | 76 | DEFINES += TRANSLATION_IDS_HEADER="\\\"$${TRANSLATIONS_GENERATED_FILENAME_H}\\\"" \ 77 | TRANSLATION_IDS_CODE="\\\"$${TRANSLATIONS_GENERATED_FILENAME_CPP}\\\"" \ 78 | ZXTAPEREVIVER_VERSION=\\\"$${ZXTAPEREVIVER_VERSION}\\\" 79 | 80 | SOURCES += \ 81 | sources/actions/actionbase.cpp \ 82 | sources/actions/editsampleaction.cpp \ 83 | sources/actions/shiftwaveformaction.cpp \ 84 | sources/core/parseddata.cpp \ 85 | sources/main.cpp \ 86 | sources/models/actionsmodel.cpp \ 87 | sources/models/dataplayermodel.cpp \ 88 | sources/models/fileworkermodel.cpp \ 89 | sources/controls/waveformcontrol.cpp \ 90 | sources/core/waveformparser.cpp \ 91 | sources/core/wavreader.cpp \ 92 | sources/models/parsersettingsmodel.cpp \ 93 | sources/models/suspiciouspointsmodel.cpp \ 94 | sources/models/waveformmodel.cpp \ 95 | sources/translations/translationmanager.cpp \ 96 | sources/translations/translations.cpp \ 97 | sources/util/enummetainfo.cpp \ 98 | sources/configuration/configurationmanager.cpp 99 | 100 | HEADERS += \ 101 | sources/actions/actionbase.h \ 102 | sources/actions/editsampleaction.h \ 103 | sources/actions/shiftwaveformaction.h \ 104 | sources/core/parseddata.h \ 105 | sources/defines.h \ 106 | sources/models/actionsmodel.h \ 107 | sources/models/dataplayermodel.h \ 108 | sources/models/fileworkermodel.h \ 109 | sources/controls/waveformcontrol.h \ 110 | sources/core/waveformparser.h \ 111 | sources/core/wavreader.h \ 112 | sources/models/parsersettingsmodel.h \ 113 | sources/models/suspiciouspointsmodel.h \ 114 | sources/models/waveformmodel.h \ 115 | sources/translations/translationmanager.h \ 116 | sources/translations/translations.h \ 117 | sources/util/enummetainfo.h \ 118 | sources/configuration/configurationmanager.h 119 | 120 | RESOURCES += qml/qml.qrc 121 | 122 | RC_ICONS = icon.ico 123 | 124 | # Additional import path used to resolve QML modules in Qt Creator's code model 125 | QML_IMPORT_PATH = 126 | 127 | # Additional import path used to resolve QML modules just for Qt Quick Designer 128 | QML_DESIGNER_IMPORT_PATH = 129 | 130 | # Default rules for deployment. 131 | qnx: target.path = /tmp/$${TARGET}/bin 132 | else: unix:!android: target.path = /opt/$${TARGET}/bin 133 | !isEmpty(target.path): INSTALLS += target 134 | -------------------------------------------------------------------------------- /sources/configuration/configurationmanager.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #ifndef CONFIGURATIONMANAGER_H 15 | #define CONFIGURATIONMANAGER_H 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "sources/util/enummetainfo.h" 23 | #include "sources/translations/translationmanager.h" 24 | 25 | class ConfigurationManager final : public QObject, protected EnumMetaInfo { 26 | Q_OBJECT 27 | 28 | Q_PROPERTY(QString zxTapeReviverVersion READ getZxTapeReviverVersion CONSTANT) 29 | Q_PROPERTY(QString zxTapeReviverBuildTag READ getZxTapeReviverBuildTag CONSTANT) 30 | 31 | public: 32 | enum class INISections { 33 | COLOR, 34 | STYLE, 35 | BEHAVIOR, 36 | TRANSLATION 37 | }; 38 | Q_ENUM(INISections) 39 | 40 | enum class INIKeys { 41 | operationModeBgColor, 42 | selectionModeBgColor, 43 | measurementModeBgColor, 44 | rangeSelectionColor, 45 | xAxisColor, 46 | yAxisColor, 47 | blockStartColor, 48 | blockMarkerColor, 49 | blockEndColor, 50 | wavePositiveColor, 51 | waveNegativeColor, 52 | textColor, 53 | waveLineThickness, 54 | circleRadius, 55 | checkVerticalRange, 56 | language 57 | }; 58 | Q_ENUM(INIKeys) 59 | 60 | private: 61 | class INIValueBase { 62 | protected: 63 | INIValueBase() = default; 64 | 65 | public: 66 | INIValueBase(const INIValueBase& other) = delete; 67 | INIValueBase(INIValueBase&& other) = delete; 68 | INIValueBase& operator= (const INIValueBase& other) = delete; 69 | INIValueBase& operator= (INIValueBase&& other) = delete; 70 | virtual ~INIValueBase() = default; 71 | 72 | virtual QVariant getValue() const = 0; 73 | virtual void setValue(const QVariant& val) = 0; 74 | }; 75 | 76 | template 77 | class INIValueTypedBase : public INIValueBase { 78 | protected: 79 | T& m_val; 80 | 81 | INIValueTypedBase(T& t) : m_val(t) { } 82 | 83 | public: 84 | virtual QVariant getValue() const override { 85 | return { m_val }; 86 | } 87 | }; 88 | 89 | class INIQColorValue : public INIValueTypedBase { 90 | public: 91 | INIQColorValue(QColor& val); 92 | virtual QVariant getValue() const override; 93 | virtual void setValue(const QVariant& val) override; 94 | }; 95 | 96 | class INIUIntValue : public INIValueTypedBase { 97 | public: 98 | INIUIntValue(unsigned& val); 99 | virtual void setValue(const QVariant& val) override; 100 | }; 101 | 102 | class INIBoolValue : public INIValueTypedBase { 103 | public: 104 | INIBoolValue(bool& val); 105 | virtual void setValue(const QVariant& val) override; 106 | }; 107 | 108 | class INITranslationLanguageValue : public INIValueTypedBase, private EnumMetaInfo { 109 | public: 110 | INITranslationLanguageValue(TranslationManager::TranslationLanguages& val); 111 | virtual QVariant getValue() const override; 112 | virtual void setValue(const QVariant& val) override; 113 | }; 114 | 115 | QString getSettingKey(INISections section, INIKeys key) const; 116 | 117 | class CustomizationBase { 118 | const QMap>>>& m_ini; 119 | 120 | CustomizationBase(const CustomizationBase& other) = delete; 121 | CustomizationBase(CustomizationBase&& other) = delete; 122 | CustomizationBase& operator= (const CustomizationBase& other) = delete; 123 | CustomizationBase& operator= (CustomizationBase&& other) = delete; 124 | 125 | public: 126 | CustomizationBase(const QMap>>>& sections); 127 | virtual ~CustomizationBase() = default; 128 | 129 | virtual const QMap>>>& getSections() const; 130 | }; 131 | 132 | public: 133 | class WaveformCustomization : public CustomizationBase { 134 | QColor m_operationModeBgColor; 135 | QColor m_selectionModeBgColor; 136 | QColor m_measurementModeBgColor; 137 | QColor m_rangeSelectionColor; 138 | QColor m_xAxisColor; 139 | QColor m_yAxisColor; 140 | QColor m_blockStartColor; 141 | QColor m_blockMarkerColor; 142 | QColor m_blockEndColor; 143 | QColor m_wavePositiveColor; 144 | QColor m_waveNegativeColor; 145 | QColor m_textColor; 146 | unsigned m_waveLineThickness; 147 | unsigned m_circleRadius; 148 | bool m_checkVerticalRange; 149 | 150 | const QMap>>> m_waveformini; 151 | 152 | public: 153 | WaveformCustomization(); 154 | 155 | const QColor& operationModeBgColor() const; 156 | const QColor& selectioModeBgColor() const; 157 | const QColor& measurementModeBgColor() const; 158 | const QColor& rangeSelectionColor() const; 159 | const QColor& xAxisColor() const; 160 | const QColor& yAxisColor() const; 161 | const QColor& blockStartColor() const; 162 | const QColor& blockMarkerColor() const; 163 | const QColor& blockEndColor() const; 164 | const QColor& textColor() const; 165 | const QColor& wavePositiveColor() const; 166 | const QColor& waveNegativeColor() const; 167 | 168 | unsigned waveLineThickness() const; 169 | unsigned circleRadius() const; 170 | bool checkVerticalRange() const; 171 | 172 | }; 173 | 174 | class ApplicationCustomization : public CustomizationBase { 175 | TranslationManager::TranslationLanguages m_translationLanguage; 176 | 177 | const QMap>>> m_applicationini; 178 | 179 | public: 180 | ApplicationCustomization(); 181 | 182 | TranslationManager::TranslationLanguages translationLanguage() const; 183 | bool setTranslationLanguage(TranslationManager::TranslationLanguages lng); 184 | }; 185 | 186 | ConfigurationManager(QObject* parent = nullptr); 187 | ConfigurationManager(ConfigurationManager&& other) = delete; 188 | ConfigurationManager(const ConfigurationManager& other) = delete; 189 | 190 | ConfigurationManager& operator= (const ConfigurationManager& other) = delete; 191 | ConfigurationManager& operator= (ConfigurationManager&& other) = delete; 192 | 193 | void writeConfiguration(); 194 | 195 | WaveformCustomization* getWaveformCustomization(); 196 | ApplicationCustomization* getApplicationCustomization(); 197 | 198 | virtual ~ConfigurationManager() override; 199 | 200 | QString getZxTapeReviverVersion() const; 201 | QString getZxTapeReviverBuildTag() const; 202 | 203 | static ConfigurationManager* instance(); 204 | 205 | private: 206 | WaveformCustomization m_waveformCustomization; 207 | ApplicationCustomization m_applicationCustomization; 208 | const QList m_customizations; 209 | const QString m_configurationFile; 210 | }; 211 | 212 | #endif // CONFIGURATIONMANAGER_H 213 | -------------------------------------------------------------------------------- /qml/DataPlayer.qml: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | import QtQuick 2.15 15 | import QtQuick.Controls 1.3 16 | import QtQuick.Dialogs 1.3 17 | import QtQuick.Layouts 1.15 18 | import QtGraphicalEffects 1.12 19 | 20 | import com.models.zxtapereviver 1.0 21 | import "." 22 | 23 | Dialog { 24 | id: dataPlayerDialog 25 | 26 | property int selectedChannel: 0 27 | property var parsedChannel: undefined 28 | 29 | visible: false 30 | title: Translations.id_playing_parsed_data_window_header 31 | standardButtons: StandardButton.Close 32 | modality: Qt.WindowModal 33 | width: 500 34 | height: 400 35 | 36 | Connections { 37 | target: DataPlayerModel 38 | function onCurrentBlockChanged() { 39 | var cb = DataPlayerModel.currentBlock; 40 | parsedDataView.selection.forEach(function(rowIndex) { parsedDataView.selection.deselect(rowIndex); }); 41 | parsedDataView.selection.select(cb); 42 | } 43 | } 44 | 45 | TableView { 46 | id: parsedDataView 47 | 48 | width: parent.width 49 | //height: parent.height * 0.9 50 | anchors { 51 | top: parent.top 52 | left: parent.left 53 | right: parent.right 54 | bottom: progressBarItem.top 55 | bottomMargin: 5 56 | } 57 | 58 | TableViewColumn { 59 | title: Translations.id_block_number 60 | width: rightArea.width * 0.07 61 | role: "block" 62 | delegate: Item { 63 | property bool blkSelected: styleData.value.blockSelected 64 | property int blkNumber: styleData.value.blockNumber 65 | 66 | Rectangle { 67 | anchors.fill: parent 68 | border.width: 0 69 | color: parent.blkSelected ? "#A00000FF" : "transparent" 70 | Text { 71 | anchors.centerIn: parent 72 | color: parent.parent.blkSelected ? "white" : "black" 73 | text: blkNumber + 1 74 | } 75 | } 76 | 77 | // MouseArea { 78 | // anchors.fill: parent 79 | // onClicked: { 80 | // WaveformParser.toggleBlockSelection(blkNumber); 81 | // } 82 | // } 83 | } 84 | } 85 | 86 | TableViewColumn { 87 | title: Translations.id_block_type 88 | width: rightArea.width * 0.23 89 | role: "blockType" 90 | } 91 | 92 | TableViewColumn { 93 | title: Translations.id_block_name 94 | width: rightArea.width * 0.3 95 | role: "blockName" 96 | } 97 | 98 | TableViewColumn { 99 | title: Translations.id_block_size 100 | width: rightArea.width * 0.25 101 | role: "blockSize" 102 | } 103 | 104 | TableViewColumn { 105 | title: Translations.id_block_status 106 | width: rightArea.width * 0.45 107 | role: "blockStatus" 108 | } 109 | 110 | selectionMode: SelectionMode.SingleSelection 111 | model: parsedChannel 112 | itemDelegate: Text { 113 | text: styleData.value 114 | color: modelData.state === 0 ? "black" : "red" 115 | } 116 | } 117 | 118 | Button { 119 | id: playParsedData 120 | 121 | text: DataPlayerModel.stopped ? Translations.id_play_parsed_data : Translations.id_stop_playing_parsed_data 122 | anchors.bottom: parent.bottom 123 | anchors.left: parent.left 124 | 125 | onClicked: { 126 | if (DataPlayerModel.stopped) { 127 | DataPlayerModel.playParsedData(selectedChannel, parsedDataView.currentRow === -1 ? 0 : parsedDataView.currentRow); 128 | } else { 129 | DataPlayerModel.stop(); 130 | } 131 | } 132 | } 133 | 134 | Item { 135 | id: progressBarItem 136 | anchors { 137 | bottom:playParsedData.top 138 | left: parent.left 139 | right: parent.right 140 | bottomMargin: 5 141 | } 142 | height: progressBarRect.height + startDurationText.height 143 | 144 | Rectangle { 145 | id: progressBarRect 146 | 147 | anchors { 148 | left: parent.left 149 | right: parent.right 150 | top: parent.top 151 | } 152 | height: textFontMetrics.height * 1.25 153 | 154 | color: "transparent" 155 | border.color: "black" 156 | 157 | Rectangle { 158 | anchors { 159 | top: parent.top 160 | bottom: parent.bottom 161 | left: parent.left 162 | topMargin: parent.border.width 163 | bottomMargin: parent.border.width 164 | leftMargin: parent.border.width 165 | } 166 | width: (parent.width - 2 * parent.border.width) * (DataPlayerModel.processedTime / DataPlayerModel.blockTime) 167 | LinearGradient { 168 | anchors.fill: parent 169 | gradient: Gradient { 170 | GradientStop { position: 0.0; color: "#1B94EF" } 171 | GradientStop { position: 1.0; color: "#92C1E4" } 172 | } 173 | start: Qt.point(0, 0) 174 | end: Qt.point(parent.width, 0) 175 | } 176 | } 177 | 178 | Text { 179 | id: playingRecordText 180 | font.pixelSize: 12 181 | anchors.fill: parent 182 | verticalAlignment: Text.AlignVCenter 183 | horizontalAlignment: Text.AlignHCenter 184 | text: { 185 | var b = DataPlayerModel.blockData; 186 | return b == undefined ? "" : (b.block.blockNumber + 1) + ": " + b.blockType + " " + b.blockName; 187 | } 188 | } 189 | FontMetrics { 190 | id: textFontMetrics 191 | font: playingRecordText.font 192 | } 193 | } 194 | 195 | function getTimeText(t) { 196 | var bt_s = ~~(t / 1000); 197 | var btm = ~~(bt_s / 60); 198 | var bts = ~~(bt_s - (btm * 60)); 199 | return btm + ":" + String(bts).padStart(2, "0"); 200 | } 201 | 202 | Text { 203 | id: startDurationText 204 | text: progressBarItem.getTimeText(0) 205 | anchors { 206 | top: progressBarRect.bottom 207 | left: parent.left 208 | } 209 | } 210 | Text { 211 | id: endDurationText 212 | text: progressBarItem.getTimeText(DataPlayerModel.blockTime) 213 | anchors { 214 | top: progressBarRect.bottom 215 | right: parent.right 216 | } 217 | } 218 | Text { 219 | id: currentDurationText 220 | text: progressBarItem.getTimeText(DataPlayerModel.processedTime) 221 | anchors { 222 | top: progressBarRect.bottom 223 | left: parent.left 224 | right: parent.right 225 | } 226 | horizontalAlignment: Text.AlignHCenter 227 | } 228 | } 229 | 230 | onVisibleChanged: { 231 | if (!visible) { 232 | DataPlayerModel.stop(); 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /qml/ParserSettings.qml: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | import QtQuick 2.3 15 | import QtQuick.Controls 1.3 16 | import QtQuick.Dialogs 1.3 17 | import QtQuick.Layouts 1.15 18 | 19 | import com.models.zxtapereviver 1.0 20 | import "." 21 | 22 | Dialog { 23 | id: parserSettingsDialog 24 | 25 | visible: false 26 | title: Translations.id_parser_settings_window_header 27 | standardButtons: StandardButton.Ok | StandardButton.RestoreDefaults 28 | modality: Qt.WindowModal 29 | width: grid.width * 1.02 30 | 31 | Grid { 32 | id: grid 33 | columns: 4 34 | anchors.horizontalCenter: parent.horizontalCenter 35 | 36 | GroupBox { 37 | title: Translations.id_pilot_tone_settings 38 | ColumnLayout { 39 | Layout.fillHeight: true 40 | Text { 41 | text: Translations.id_pilot_half_frequency 42 | } 43 | 44 | TextField { 45 | text: ParserSettingsModel.pilotHalfFreq; 46 | onTextChanged: { 47 | ParserSettingsModel.pilotHalfFreq = parseInt(text, 10); 48 | } 49 | } 50 | 51 | Text { 52 | text: Translations.id_pilot_frequency 53 | } 54 | 55 | TextField { 56 | text: ParserSettingsModel.pilotFreq; 57 | onTextChanged: { 58 | ParserSettingsModel.pilotFreq = parseInt(text, 10); 59 | } 60 | } 61 | 62 | Text { 63 | text: Translations.id_pilot_delta 64 | } 65 | 66 | TextField { 67 | text: ParserSettingsModel.pilotDelta 68 | onTextChanged: { 69 | ParserSettingsModel.pilotDelta = text; 70 | } 71 | } 72 | } 73 | } 74 | 75 | GroupBox { 76 | title: Translations.id_synchro_signal_settings 77 | ColumnLayout { 78 | Text { 79 | text: Translations.id_synchro_first_half_frequency 80 | } 81 | 82 | TextField { 83 | text: ParserSettingsModel.synchroFirstHalfFreq; 84 | onTextChanged: { 85 | ParserSettingsModel.synchroFirstHalfFreq = parseInt(text, 10); 86 | } 87 | } 88 | 89 | Text { 90 | text: Translations.id_synchro_second_half_frequency 91 | } 92 | 93 | TextField { 94 | text: ParserSettingsModel.synchroSecondHalfFreq; 95 | onTextChanged: { 96 | ParserSettingsModel.synchroSecondHalfFreq = parseInt(text, 10); 97 | } 98 | } 99 | 100 | Text { 101 | text: Translations.id_synchro_frequency 102 | } 103 | 104 | TextField { 105 | text: ParserSettingsModel.synchroFreq; 106 | onTextChanged: { 107 | ParserSettingsModel.synchroFreq = parseInt(text, 10); 108 | } 109 | } 110 | 111 | Text { 112 | text: Translations.id_synchro_delta 113 | } 114 | 115 | TextField { 116 | text: ParserSettingsModel.synchroDelta 117 | onTextChanged: { 118 | ParserSettingsModel.synchroDelta = text; 119 | } 120 | } 121 | 122 | CheckBox { 123 | id: preciseSynchroCheck 124 | text: Translations.id_precise_synchro_check 125 | 126 | checked: ParserSettingsModel.preciseSynchroCheck 127 | onCheckedChanged: { 128 | ParserSettingsModel.preciseSynchroCheck = checked; 129 | } 130 | } 131 | } 132 | } 133 | 134 | GroupBox { 135 | title: Translations.id_zero_digit_settings 136 | ColumnLayout { 137 | Text { 138 | text: Translations.id_zero_half_frequency 139 | } 140 | 141 | TextField { 142 | text: ParserSettingsModel.zeroHalfFreq; 143 | onTextChanged: { 144 | ParserSettingsModel.zeroHalfFreq = parseInt(text, 10); 145 | } 146 | } 147 | 148 | Text { 149 | text: Translations.id_zero_frequency 150 | } 151 | 152 | TextField { 153 | text: ParserSettingsModel.zeroFreq; 154 | onTextChanged: { 155 | ParserSettingsModel.zeroFreq = parseInt(text, 10); 156 | } 157 | } 158 | 159 | Text { 160 | text: Translations.id_zero_delta 161 | } 162 | 163 | TextField { 164 | text: ParserSettingsModel.zeroDelta 165 | onTextChanged: { 166 | ParserSettingsModel.zeroDelta = text; 167 | } 168 | } 169 | } 170 | } 171 | 172 | GroupBox { 173 | title: Translations.id_one_digit_settings 174 | ColumnLayout { 175 | Text { 176 | text: Translations.id_one_half_frequency 177 | } 178 | 179 | TextField { 180 | text: ParserSettingsModel.oneHalfFreq; 181 | onTextChanged: { 182 | ParserSettingsModel.oneHalfFreq = parseInt(text, 10); 183 | } 184 | } 185 | 186 | Text { 187 | text: Translations.id_one_frequency 188 | } 189 | 190 | TextField { 191 | text: ParserSettingsModel.oneFreq; 192 | onTextChanged: { 193 | ParserSettingsModel.oneFreq = parseInt(text, 10); 194 | } 195 | } 196 | 197 | Text { 198 | text: Translations.id_one_delta 199 | } 200 | 201 | TextField { 202 | text: ParserSettingsModel.oneDelta 203 | onTextChanged: { 204 | ParserSettingsModel.oneDelta = text; 205 | } 206 | } 207 | } 208 | } 209 | } 210 | CheckBox { 211 | id: checkForAbnormalSineCheckbox 212 | anchors.top: grid.bottom 213 | anchors.topMargin: 5 214 | 215 | text: Translations.id_check_for_abnormal_sine_when_parsing 216 | checked: ParserSettingsModel.checkForAbnormalSine 217 | onCheckedChanged: { 218 | ParserSettingsModel.checkForAbnormalSine = checked; 219 | } 220 | } 221 | Text { 222 | id: sineCheckToleranceText 223 | anchors.top: checkForAbnormalSineCheckbox.bottom 224 | text: Translations.id_sine_check_tolerance 225 | visible: checkForAbnormalSineCheckbox.checked 226 | } 227 | TextField { 228 | anchors.top: sineCheckToleranceText.bottom 229 | text: ParserSettingsModel.sineCheckTolerance; 230 | onTextChanged: { 231 | ParserSettingsModel.sineCheckTolerance = text; 232 | } 233 | visible: checkForAbnormalSineCheckbox.checked 234 | } 235 | 236 | onReset: { 237 | ParserSettingsModel.restoreDefaultSettings(); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /sources/models/parsersettingsmodel.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "parsersettingsmodel.h" 15 | #include "sources/defines.h" 16 | #include 17 | 18 | ParserSettingsModel::ParserSettingsModel(QObject* parent) : 19 | QObject(parent), 20 | m_parserSettings { 21 | SignalFrequencies::PILOT_HALF_FREQ, 22 | SignalFrequencies::PILOT_FREQ, 23 | SignalFrequencies::SYNCHRO_FIRST_HALF_FREQ, 24 | SignalFrequencies::SYNCHRO_SECOND_HALF_FREQ, 25 | SignalFrequencies::SYNCHRO_FREQ, 26 | preciseSynchroCheck, 27 | SignalFrequencies::ZERO_HALF_FREQ, 28 | SignalFrequencies::ZERO_FREQ, 29 | SignalFrequencies::ONE_HALF_FREQ, 30 | SignalFrequencies::ONE_FREQ, 31 | pilotDelta, 32 | synchroDelta, 33 | zeroDelta, 34 | oneDelta, 35 | checkForAbnormalSine, 36 | sineCheckTolerance } 37 | { 38 | 39 | } 40 | 41 | void ParserSettingsModel::restoreDefaultSettings() 42 | { 43 | setPilotHalfFreq(SignalFrequencies::PILOT_HALF_FREQ); 44 | setPilotFreq(SignalFrequencies::PILOT_FREQ); 45 | setSynchroFirstHalfFreq(SignalFrequencies::SYNCHRO_FIRST_HALF_FREQ); 46 | setSynchroSecondHalfFreq(SignalFrequencies::SYNCHRO_SECOND_HALF_FREQ); 47 | setSynchroFreq(SignalFrequencies::SYNCHRO_FREQ); 48 | setPreciseSynchroCheck(preciseSynchroCheck); 49 | setZeroHalfFreq(SignalFrequencies::ZERO_HALF_FREQ); 50 | setZeroFreq(SignalFrequencies::ZERO_FREQ); 51 | setOneHalfFreq(SignalFrequencies::ONE_HALF_FREQ); 52 | setOneFreq(SignalFrequencies::ONE_FREQ); 53 | setPilotDelta(pilotDelta); 54 | setSynchroDelta(synchroDelta); 55 | setZeroDelta(zeroDelta); 56 | setOneDelta(oneDelta); 57 | setCheckForAbnormalSine(checkForAbnormalSine); 58 | setSineCheckTolerance(sineCheckTolerance); 59 | } 60 | 61 | const ParserSettingsModel::ParserSettings& ParserSettingsModel::getParserSettings() const 62 | { 63 | return m_parserSettings; 64 | } 65 | 66 | int ParserSettingsModel::getPilotHalfFreq() const 67 | { 68 | return m_parserSettings.pilotHalfFreq; 69 | } 70 | 71 | int ParserSettingsModel::getPilotFreq() const 72 | { 73 | return m_parserSettings.pilotFreq; 74 | } 75 | 76 | int ParserSettingsModel::getSynchroFirstHalfFreq() const 77 | { 78 | return m_parserSettings.synchroFirstHalfFreq; 79 | } 80 | 81 | int ParserSettingsModel::getSynchroSecondHalfFreq() const 82 | { 83 | return m_parserSettings.synchroSecondHalfFreq; 84 | } 85 | 86 | int ParserSettingsModel::getSynchroFreq() const 87 | { 88 | return m_parserSettings.synchroFreq; 89 | } 90 | 91 | bool ParserSettingsModel::getPreciseSynchroCheck() const 92 | { 93 | return m_parserSettings.preciseSynchroCheck; 94 | } 95 | 96 | int ParserSettingsModel::getZeroHalfFreq() const 97 | { 98 | return m_parserSettings.zeroHalfFreq; 99 | } 100 | 101 | int ParserSettingsModel::getZeroFreq() const 102 | { 103 | return m_parserSettings.zeroFreq; 104 | } 105 | 106 | int ParserSettingsModel::getOneHalfFreq() const 107 | { 108 | return m_parserSettings.oneHalfFreq; 109 | } 110 | 111 | int ParserSettingsModel::getOneFreq() const 112 | { 113 | return m_parserSettings.oneFreq; 114 | } 115 | 116 | double ParserSettingsModel::getPilotDelta() const 117 | { 118 | return m_parserSettings.pilotDelta; 119 | } 120 | 121 | double ParserSettingsModel::getSynchroDelta() const 122 | { 123 | return m_parserSettings.synchroDelta; 124 | } 125 | 126 | double ParserSettingsModel::getZeroDelta() const 127 | { 128 | return m_parserSettings.zeroDelta; 129 | } 130 | 131 | double ParserSettingsModel::getOneDelta() const 132 | { 133 | return m_parserSettings.oneDelta; 134 | } 135 | 136 | bool ParserSettingsModel::getCheckForAbnormalSine() const 137 | { 138 | return m_parserSettings.checkForAbnormalSine; 139 | } 140 | 141 | double ParserSettingsModel::getSineCheckTolerance() const { 142 | return m_parserSettings.sineCheckTolerance; 143 | } 144 | 145 | void ParserSettingsModel::setPilotHalfFreq(int freq) 146 | { 147 | if (m_parserSettings.pilotHalfFreq != freq) { 148 | m_parserSettings.pilotHalfFreq = freq; 149 | emit pilotHalfFreqChanged(); 150 | } 151 | } 152 | 153 | void ParserSettingsModel::setPilotFreq(int freq) 154 | { 155 | if (m_parserSettings.pilotFreq != freq) { 156 | m_parserSettings.pilotFreq = freq; 157 | emit pilotFreqChanged(); 158 | } 159 | } 160 | 161 | void ParserSettingsModel::setSynchroFirstHalfFreq(int freq) 162 | { 163 | if (m_parserSettings.synchroFirstHalfFreq != freq) { 164 | m_parserSettings.synchroFirstHalfFreq = freq; 165 | emit synchroFirstHalfFreqChanged(); 166 | } 167 | } 168 | 169 | void ParserSettingsModel::setSynchroSecondHalfFreq(int freq) 170 | { 171 | if (m_parserSettings.synchroSecondHalfFreq != freq) { 172 | m_parserSettings.synchroSecondHalfFreq = freq; 173 | emit synchroSecondHalfFreqChanged(); 174 | } 175 | } 176 | 177 | void ParserSettingsModel::setSynchroFreq(int freq) 178 | { 179 | if (m_parserSettings.synchroFreq != freq) { 180 | m_parserSettings.synchroFreq = freq; 181 | emit synchroFreqChanged(); 182 | } 183 | } 184 | 185 | void ParserSettingsModel::setPreciseSynchroCheck(bool precise) 186 | { 187 | if (m_parserSettings.preciseSynchroCheck != precise) { 188 | m_parserSettings.preciseSynchroCheck = precise; 189 | emit preciseSychroCheckChanged(); 190 | } 191 | } 192 | 193 | void ParserSettingsModel::setZeroHalfFreq(int freq) 194 | { 195 | if (m_parserSettings.zeroHalfFreq != freq) { 196 | m_parserSettings.zeroHalfFreq = freq; 197 | emit zeroHalfFreqChanged(); 198 | } 199 | } 200 | 201 | void ParserSettingsModel::setZeroFreq(int freq) 202 | { 203 | if (m_parserSettings.zeroFreq != freq) { 204 | m_parserSettings.zeroFreq = freq; 205 | emit zeroFreqChanged(); 206 | } 207 | } 208 | 209 | void ParserSettingsModel::setOneHalfFreq(int freq) 210 | { 211 | if (m_parserSettings.oneHalfFreq != freq) { 212 | m_parserSettings.oneHalfFreq = freq; 213 | emit oneHalfFreqChanged(); 214 | } 215 | } 216 | 217 | void ParserSettingsModel::setOneFreq(int freq) 218 | { 219 | if (m_parserSettings.oneFreq != freq) { 220 | m_parserSettings.oneFreq = freq; 221 | emit oneFreqChanged(); 222 | } 223 | } 224 | 225 | void ParserSettingsModel::setPilotDelta(double delta) 226 | { 227 | if (m_parserSettings.pilotDelta != delta) { 228 | m_parserSettings.pilotDelta = delta; 229 | emit pilotDeltaChanged(); 230 | } 231 | } 232 | 233 | void ParserSettingsModel::setSynchroDelta(double delta) 234 | { 235 | if (m_parserSettings.synchroDelta != delta) { 236 | m_parserSettings.synchroDelta = delta; 237 | emit synchroDeltaChanged(); 238 | } 239 | } 240 | 241 | void ParserSettingsModel::setZeroDelta(double delta) 242 | { 243 | if (m_parserSettings.zeroDelta != delta) { 244 | m_parserSettings.zeroDelta = delta; 245 | emit zeroDeltaChanged(); 246 | } 247 | } 248 | 249 | void ParserSettingsModel::setOneDelta(double delta) 250 | { 251 | if (m_parserSettings.oneDelta != delta) { 252 | m_parserSettings.oneDelta = delta; 253 | emit oneDeltaChanged(); 254 | } 255 | } 256 | 257 | void ParserSettingsModel::setCheckForAbnormalSine(bool check) 258 | { 259 | if (m_parserSettings.checkForAbnormalSine != check) { 260 | m_parserSettings.checkForAbnormalSine = check; 261 | emit checkForAbnormalSineChanged(); 262 | } 263 | } 264 | 265 | void ParserSettingsModel::setSineCheckTolerance(double value) { 266 | if (m_parserSettings.sineCheckTolerance != value) { 267 | m_parserSettings.sineCheckTolerance = value; 268 | emit sineCheckToleranceChanged(); 269 | } 270 | } 271 | 272 | ParserSettingsModel* ParserSettingsModel::instance() 273 | { 274 | static QScopedPointer m { new ParserSettingsModel() }; 275 | return m.get(); 276 | } 277 | -------------------------------------------------------------------------------- /sources/models/dataplayermodel.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "dataplayermodel.h" 15 | #include 16 | 17 | DataPlayerModel::DataPlayerModel(QObject* parent) : 18 | QObject(parent), 19 | m_playingState(DP_Stopped), 20 | m_blockTime(0), 21 | m_processedTime(0) 22 | { 23 | connect(&m_delayTimer, &QTimer::timeout, this, &DataPlayerModel::handleNextDataRecord); 24 | } 25 | 26 | void DataPlayerModel::playParsedData(uint chNum, uint currentBlock) { 27 | if (m_playingState != DP_Stopped) { 28 | return; 29 | } 30 | 31 | #if (Q_BYTE_ORDER == Q_BIG_ENDIAN) 32 | const auto endianness { QAudioFormat::BigEndian }; 33 | #else 34 | const auto endianness { QAudioFormat::LittleEndian }; 35 | #endif 36 | 37 | QAudioFormat format; 38 | // Set up the format 39 | format.setSampleRate(c_sampleRate); 40 | format.setChannelCount(1); 41 | format.setSampleSize(16); 42 | format.setCodec("audio/pcm"); 43 | format.setByteOrder(endianness); 44 | format.setSampleType(QAudioFormat::SignedInt); 45 | 46 | const QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); 47 | if (!info.isFormatSupported(format)) { 48 | qDebug() << "Audio format not supported, cannot play audio."; 49 | return; 50 | } 51 | 52 | m_audio.reset(new QAudioOutput(info, format)); 53 | m_audio->setNotifyInterval(30); 54 | connect(m_audio.data(), &QAudioOutput::stateChanged, this, &DataPlayerModel::handleAudioOutputStateChanged); 55 | connect(m_audio.data(), &QAudioOutput::notify, this, &DataPlayerModel::handleAudioOutputNotify); 56 | 57 | m_buffer.close(); 58 | m_currentBlock = currentBlock; 59 | m_data = WaveformParser::instance()->getParsedData(chNum); 60 | m_parserData = chNum == 0 ? WaveformParser::instance()->getParsedChannel0() : WaveformParser::instance()->getParsedChannel1(); 61 | handleNextDataRecord(); 62 | } 63 | 64 | void DataPlayerModel::handleNextDataRecord() { 65 | if (m_currentBlock >= (unsigned) m_data.first.size()) { 66 | return; 67 | } 68 | 69 | QByteArray array; 70 | 71 | const auto c_pilotHalfFreq { SignalFrequencies::PILOT_HALF_FREQ }; 72 | const auto c_synchroFirstHalfFreq { SignalFrequencies::SYNCHRO_FIRST_HALF_FREQ }; 73 | const auto c_synchroSecondHalfFreq { SignalFrequencies::SYNCHRO_SECOND_HALF_FREQ }; 74 | const auto c_zeroHalfFreq { SignalFrequencies::ZERO_HALF_FREQ }; 75 | const auto c_oneHalfFreq { SignalFrequencies::ONE_HALF_FREQ }; 76 | 77 | const auto oneFreq { c_oneHalfFreq / 2 }; 78 | const auto oneHalfFreq { oneFreq * 2 }; 79 | const auto zeroFreq { c_zeroHalfFreq / 2 }; 80 | const auto zeroHalfFreq { zeroFreq * 2 }; 81 | const auto synchroFirstHalf { c_synchroFirstHalfFreq }; 82 | const auto synchroSecondHalf { c_synchroSecondHalfFreq }; 83 | const auto pilotFreq { c_pilotHalfFreq / 2 }; 84 | const auto pilotHalfFreq { pilotFreq * 2 }; 85 | const auto pilotLen { 3 }; 86 | 87 | //Pilot 88 | size_t wavlen = c_sampleRate / pilotHalfFreq; 89 | auto threshold { c_sampleRate * pilotLen / (wavlen * 2) }; 90 | for (size_t i { 0 }; i < threshold; ++i) { 91 | for (auto p { 0 }; p <= 1; ++p) { 92 | const int16_t val { int16_t(32760 * (p ? 1 : -1)) }; 93 | for (size_t c { 0 }; c < wavlen; ++c) { 94 | array.append((char *)&val, sizeof(int16_t)); 95 | } 96 | } 97 | } 98 | //Synchro 99 | for (auto w { 0 }; w <= 1; ++w) { 100 | wavlen = c_sampleRate / (w ? synchroSecondHalf : synchroFirstHalf); 101 | const int16_t val { int16_t(32760 * (w ? 1 : -1)) }; 102 | for (size_t c { 0 }; c < wavlen; ++c) { 103 | array.append((char *)&val, sizeof(int16_t)); 104 | } 105 | } 106 | //Data 107 | for (const uint8_t byte: qAsConst(m_data.first[m_currentBlock].data)) { 108 | for (int i { 7 }; i >= 0; --i) { 109 | const uint8_t bit8 = 1 << i; 110 | const auto bit { byte & bit8 }; 111 | wavlen = c_sampleRate / (bit == 0 ? zeroHalfFreq : oneHalfFreq); 112 | for (auto b { 0 }; b <= 1; ++b) { 113 | const int16_t val { int16_t(32760 * (b ? 1 : -1)) }; 114 | for (size_t c { 0 }; c < wavlen; ++c) { 115 | array.append((char *)&val, sizeof(int16_t)); 116 | } 117 | } 118 | } 119 | } 120 | //Silence for 1 us (prevents R Tape loading error under Linux, ZXTR-48) 121 | wavlen = c_sampleRate / 1000; 122 | int16_t val { 0 }; 123 | for (unsigned i { 0 }; i < wavlen; ++i) { 124 | array.append((char *)&val, sizeof(int16_t)); 125 | } 126 | 127 | emit currentBlockChanged(); 128 | m_blockTime = (array.size() / sizeof(int16_t)) / (c_sampleRate / 1000); 129 | m_processedTime = 0; 130 | emit blockTimeChanged(); 131 | emit processedTimeChanged(); 132 | ++m_currentBlock; 133 | 134 | m_buffer.setData(array); 135 | m_buffer.open(QIODevice::ReadOnly); 136 | m_audio->start(&m_buffer); 137 | } 138 | 139 | void DataPlayerModel::prepareNextDataRecord() { 140 | const QVector& blockData { m_data.first }; 141 | const QVector& selectionData { m_data.second }; 142 | while (m_currentBlock < (unsigned) blockData.size()) { 143 | if ((unsigned) selectionData.size() < m_currentBlock || selectionData.at(m_currentBlock)) { 144 | break; 145 | } 146 | ++m_currentBlock; 147 | } 148 | 149 | m_buffer.close(); 150 | if (m_currentBlock < (unsigned) blockData.size()) { 151 | //half a second delay 152 | m_delayTimer.singleShot(500, this, [this]() { 153 | //We have to check for state == playing to be sure stop method is not executed previously. 154 | if (m_playingState == DP_Playing) { 155 | handleNextDataRecord(); 156 | } 157 | }); 158 | } else { 159 | m_audio->stop(); 160 | m_audio.reset(); 161 | m_playingState = DP_Stopped; 162 | emit currentBlockChanged(); 163 | emit stoppedChanged(); 164 | } 165 | } 166 | 167 | void DataPlayerModel::handleAudioOutputStateChanged(QAudio::State state) { 168 | switch (state) { 169 | case QAudio::IdleState: 170 | prepareNextDataRecord(); 171 | break; 172 | 173 | case QAudio::StoppedState: 174 | if (m_audio->error() != QAudio::NoError) { 175 | qDebug() << "Error playing: " << m_audio->error(); 176 | } 177 | break; 178 | 179 | case QAudio::ActiveState: 180 | m_playingState = DP_Playing; 181 | emit stoppedChanged(); 182 | break; 183 | 184 | default: 185 | break; 186 | } 187 | } 188 | 189 | void DataPlayerModel::stop() { 190 | if (m_playingState != DP_Stopped) { 191 | m_currentBlock = m_data.first.size(); 192 | prepareNextDataRecord(); 193 | } 194 | } 195 | 196 | void DataPlayerModel::handleAudioOutputNotify() { 197 | m_processedTime = m_audio->processedUSecs() / 1000; 198 | emit processedTimeChanged(); 199 | } 200 | 201 | bool DataPlayerModel::getStopped() const { 202 | return m_playingState == DP_Stopped; 203 | } 204 | 205 | int DataPlayerModel::getCurrentBlock() const { 206 | return m_currentBlock < (unsigned) m_data.first.size() ? m_currentBlock : -1; 207 | } 208 | 209 | int DataPlayerModel::getBlockTime() const { 210 | return m_blockTime; 211 | } 212 | 213 | int DataPlayerModel::getProcessedTime() const { 214 | return m_processedTime; 215 | } 216 | 217 | QVariant DataPlayerModel::getBlockData() const { 218 | const auto cb { getCurrentBlock() }; 219 | return cb < 0 ? QVariant() : m_parserData.at(cb); 220 | } 221 | 222 | DataPlayerModel::~DataPlayerModel() { 223 | m_audio.reset(); 224 | qDebug() << "~DataPlayerModel"; 225 | } 226 | 227 | DataPlayerModel* DataPlayerModel::instance() { 228 | static DataPlayerModel m; 229 | return &m; 230 | } 231 | -------------------------------------------------------------------------------- /sources/configuration/configurationmanager.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "configurationmanager.h" 15 | #include 16 | #include 17 | 18 | //INI Value helper classes 19 | ConfigurationManager::INIQColorValue::INIQColorValue(QColor& val) : INIValueTypedBase(val) 20 | { 21 | 22 | } 23 | 24 | QVariant ConfigurationManager::INIQColorValue::getValue() const { 25 | return m_val.name(m_val.alpha() == UINT8_MAX ? QColor::HexRgb : QColor::HexArgb).toUpper(); 26 | } 27 | 28 | void ConfigurationManager::INIQColorValue::setValue(const QVariant& val) { 29 | m_val = val.toString(); 30 | } 31 | 32 | ConfigurationManager::INIUIntValue::INIUIntValue(unsigned& val) : INIValueTypedBase(val) 33 | { 34 | 35 | } 36 | 37 | void ConfigurationManager::INIUIntValue::setValue(const QVariant& val) { 38 | m_val = val.toUInt(); 39 | } 40 | 41 | ConfigurationManager::INIBoolValue::INIBoolValue(bool& val) : INIValueTypedBase(val) 42 | { 43 | 44 | } 45 | 46 | void ConfigurationManager::INIBoolValue::setValue(const QVariant& val) { 47 | m_val = val.toBool(); 48 | } 49 | 50 | ConfigurationManager::INITranslationLanguageValue::INITranslationLanguageValue(TranslationManager::TranslationLanguages& val) : INIValueTypedBase(val) 51 | { 52 | 53 | } 54 | 55 | QVariant ConfigurationManager::INITranslationLanguageValue::getValue() const { 56 | return getEnumName(m_val); 57 | } 58 | 59 | void ConfigurationManager::INITranslationLanguageValue::setValue(const QVariant& val) { 60 | m_val = getEnumValue(val.toString(), TranslationManager::TranslationLanguages::en_US); 61 | } 62 | 63 | 64 | ConfigurationManager::CustomizationBase::CustomizationBase(const QMap>>>& sections) : 65 | m_ini(sections) 66 | { 67 | 68 | } 69 | 70 | const QMap>>>& ConfigurationManager::CustomizationBase::getSections() const { 71 | return m_ini; 72 | } 73 | 74 | ConfigurationManager::WaveformCustomization::WaveformCustomization() : 75 | ConfigurationManager::CustomizationBase(m_waveformini), 76 | m_operationModeBgColor(7, 7, 36), 77 | m_selectionModeBgColor(37, 37, 37), 78 | m_measurementModeBgColor(0, 17, 17), 79 | m_rangeSelectionColor(7, 7, 137, 128), 80 | m_xAxisColor(11, 60, 0), 81 | m_yAxisColor(11, 60, 0), 82 | m_blockStartColor(0, 0, 250), 83 | m_blockMarkerColor(0, 0, 250), 84 | m_blockEndColor(255, 242, 0), 85 | m_wavePositiveColor(50, 150, 0), 86 | m_waveNegativeColor(200, 0 , 0), 87 | m_textColor(255, 255, 255), 88 | m_waveLineThickness(1), 89 | m_circleRadius(2), 90 | m_checkVerticalRange(true), 91 | m_waveformini({ 92 | { INISections::COLOR, { 93 | qMakePair(INIKeys::operationModeBgColor, std::make_shared(m_operationModeBgColor)), 94 | qMakePair(INIKeys::selectionModeBgColor, std::make_shared(m_selectionModeBgColor)), 95 | qMakePair(INIKeys::measurementModeBgColor, std::make_shared(m_measurementModeBgColor)), 96 | qMakePair(INIKeys::rangeSelectionColor, std::make_shared(m_rangeSelectionColor)), 97 | qMakePair(INIKeys::xAxisColor, std::make_shared(m_xAxisColor)), 98 | qMakePair(INIKeys::yAxisColor, std::make_shared(m_yAxisColor)), 99 | qMakePair(INIKeys::blockStartColor, std::make_shared(m_blockStartColor)), 100 | qMakePair(INIKeys::blockMarkerColor, std::make_shared(m_blockMarkerColor)), 101 | qMakePair(INIKeys::blockEndColor, std::make_shared(m_blockEndColor)), 102 | qMakePair(INIKeys::wavePositiveColor, std::make_shared(m_wavePositiveColor)), 103 | qMakePair(INIKeys::waveNegativeColor, std::make_shared(m_waveNegativeColor)), 104 | qMakePair(INIKeys::textColor, std::make_shared(m_textColor)) 105 | } }, 106 | { INISections::STYLE, { 107 | qMakePair(INIKeys::waveLineThickness, std::make_shared(m_waveLineThickness)), 108 | qMakePair(INIKeys::circleRadius, std::make_shared(m_circleRadius)) 109 | } }, 110 | { INISections::BEHAVIOR, { 111 | qMakePair(INIKeys::checkVerticalRange, std::make_shared(m_checkVerticalRange)) 112 | } } 113 | }) 114 | { 115 | 116 | } 117 | 118 | const QColor& ConfigurationManager::WaveformCustomization::operationModeBgColor() const { 119 | return m_operationModeBgColor; 120 | } 121 | 122 | const QColor& ConfigurationManager::WaveformCustomization::selectioModeBgColor() const { 123 | return m_selectionModeBgColor; 124 | } 125 | 126 | const QColor& ConfigurationManager::WaveformCustomization::measurementModeBgColor() const { 127 | return m_measurementModeBgColor; 128 | } 129 | 130 | const QColor& ConfigurationManager::WaveformCustomization::rangeSelectionColor() const { 131 | return m_rangeSelectionColor; 132 | } 133 | 134 | const QColor& ConfigurationManager::WaveformCustomization::xAxisColor() const { 135 | return m_xAxisColor; 136 | } 137 | 138 | const QColor& ConfigurationManager::WaveformCustomization::yAxisColor() const { 139 | return m_yAxisColor; 140 | } 141 | 142 | const QColor& ConfigurationManager::WaveformCustomization::blockStartColor() const { 143 | return m_blockStartColor; 144 | } 145 | 146 | const QColor& ConfigurationManager::WaveformCustomization::blockMarkerColor() const { 147 | return m_blockMarkerColor; 148 | } 149 | 150 | const QColor& ConfigurationManager::WaveformCustomization::blockEndColor() const { 151 | return m_blockEndColor; 152 | } 153 | 154 | const QColor& ConfigurationManager::WaveformCustomization::textColor() const { 155 | return m_textColor; 156 | } 157 | 158 | const QColor& ConfigurationManager::WaveformCustomization::wavePositiveColor() const { 159 | return m_wavePositiveColor; 160 | } 161 | 162 | const QColor& ConfigurationManager::WaveformCustomization::waveNegativeColor() const { 163 | return m_waveNegativeColor; 164 | } 165 | 166 | unsigned ConfigurationManager::WaveformCustomization::waveLineThickness() const { 167 | return m_waveLineThickness; 168 | } 169 | 170 | unsigned ConfigurationManager::WaveformCustomization::circleRadius() const { 171 | return m_circleRadius; 172 | } 173 | 174 | bool ConfigurationManager::WaveformCustomization::checkVerticalRange() const { 175 | return m_checkVerticalRange; 176 | } 177 | 178 | 179 | ConfigurationManager::ApplicationCustomization::ApplicationCustomization() : 180 | ConfigurationManager::CustomizationBase(m_applicationini), 181 | m_translationLanguage(TranslationManager::TranslationLanguages::en_US), 182 | m_applicationini({ 183 | { INISections::TRANSLATION, { 184 | qMakePair(INIKeys::language, std::make_shared(m_translationLanguage)) 185 | } } 186 | }) 187 | { 188 | 189 | } 190 | 191 | TranslationManager::TranslationLanguages ConfigurationManager::ApplicationCustomization::translationLanguage() const { 192 | return m_translationLanguage; 193 | } 194 | 195 | bool ConfigurationManager::ApplicationCustomization::setTranslationLanguage(TranslationManager::TranslationLanguages lng) { 196 | m_translationLanguage = lng; 197 | return true; 198 | } 199 | 200 | 201 | //ConfigurationManager class 202 | ConfigurationManager::ConfigurationManager(QObject* parent) : 203 | QObject(parent), 204 | m_customizations({ &m_waveformCustomization, &m_applicationCustomization }), 205 | m_configurationFile("config.ini") 206 | { 207 | // UI settings 208 | QFileInfo ini_file(m_configurationFile); 209 | 210 | if (!ini_file.exists() || !ini_file.isFile()) { 211 | // If no INI file - create new one 212 | writeConfiguration(); 213 | } else { 214 | QSettings ini(ini_file.fileName(), QSettings::IniFormat); 215 | // Read settings from INI file 216 | for (const auto* customization: m_customizations) { 217 | for (auto it = customization->getSections().constKeyValueBegin(); it != customization->getSections().constKeyValueEnd(); ++it) { 218 | for (auto& key: it->second) { 219 | key.second->setValue(ini.value(getSettingKey(it->first, key.first))); 220 | } 221 | } 222 | } 223 | } 224 | } 225 | 226 | void ConfigurationManager::writeConfiguration() { 227 | QSettings ini(m_configurationFile, QSettings::IniFormat); 228 | 229 | for (const auto* customization: m_customizations) { 230 | for (auto it = customization->getSections().constKeyValueBegin(); it != customization->getSections().constKeyValueEnd(); ++it) { 231 | for (const auto& key: it->second) { 232 | ini.setValue(getSettingKey(it->first, key.first), key.second->getValue()); 233 | } 234 | } 235 | } 236 | } 237 | 238 | ConfigurationManager::~ConfigurationManager() 239 | { 240 | writeConfiguration(); 241 | } 242 | 243 | QString ConfigurationManager::getZxTapeReviverVersion() const { 244 | const auto split { getZxTapeReviverBuildTag().split('_', Qt::SkipEmptyParts) }; 245 | return split.isEmpty() ? QString() : split.last(); 246 | } 247 | 248 | QString ConfigurationManager::getZxTapeReviverBuildTag() const { 249 | return ZXTAPEREVIVER_VERSION; 250 | } 251 | 252 | QString ConfigurationManager::getSettingKey(INISections section, INIKeys key) const { 253 | return getEnumName(section) + "/" + getEnumName(key); 254 | } 255 | 256 | ConfigurationManager::WaveformCustomization* ConfigurationManager::getWaveformCustomization() { 257 | return &m_waveformCustomization; 258 | } 259 | 260 | ConfigurationManager::ApplicationCustomization* ConfigurationManager::getApplicationCustomization() { 261 | return &m_applicationCustomization; 262 | } 263 | 264 | ConfigurationManager* ConfigurationManager::instance() { 265 | static ConfigurationManager manager; 266 | return &manager; 267 | } 268 | -------------------------------------------------------------------------------- /qml/translations/zxtapereviver_en_US.xlf: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | %1 sec%1 sec 18 | AboutAbout 19 | E-mail: %1E-mail: %1 20 | YouTube channel: %1YouTube channel: %1 21 | Computer Enthusiast TipsComputer Enthusiast Tips 22 | Donate to program development: %1Donate program development: %1 23 | Please click the highlighted links to openPlease click the highlighted links to open 24 | Measured frequencyMeasured frequency 25 | Measured frequency:Measured frequency: 26 | Go to addressGo to address 27 | Please enter address:Please enter address: 28 | HexadecimalHexadecimal 29 | FileFile 30 | Open WAV file...Open WAV file... 31 | Open Waveform file...Open Waveform file... 32 | Open TAP file...Open TAP file... 33 | SaveSave 34 | ParsedParsed 35 | Left channel...Left channel... 36 | Right channel...Right channel... 37 | Waveform...Waveform... 38 | ExitExit 39 | WaveformWaveform 40 | Restore viewRestore view 41 | ReparseReparse 42 | Parser settings...Parser settings... 43 | HelpHelp 44 | About...About... 45 | Parser settingsParset settings 46 | Pilot-tone settings:Pilot-tone settings: 47 | Pilot half frequency:Pilot half frequency: 48 | Pilot frequency:Pilot frequency: 49 | Pilot delta:Pilot delta: 50 | Synchro signal settigns:Synchro signal settigns: 51 | Synchro first half frequency:Synchro first half frequency: 52 | Synchro second half frequency:Synchro second half frequency: 53 | Synchro frequency:Synchro frequency: 54 | Synchro delta:Synchro delta: 55 | Precise synchro checkPrecise synchro check 56 | Zero digit settings:Zero digit settings: 57 | Zero half frequency:Zero half frequency: 58 | Zero frequency:Zero frequency: 59 | Zero delta:Zero delta: 60 | One digit settings:One digit settings: 61 | One half frequency:One half frequency: 62 | One frequency:One frequency: 63 | One delta:One delta: 64 | Check for abnormal sine when parsingCheck for abnormal sine when parsing 65 | Please choose WAV filePlease choose WAV file 66 | Please choose WFM filePlease choose WFM file 67 | Please choose TAP filePlease choose TAP file 68 | WAV files (%1)WAV files (%1) 69 | Waveform files (%1)Waveform files (%1) 70 | TAP files (%1)TAP files (%1) 71 | Save TAP file...Save TAP file... 72 | Save WFM file...Save WFM file... 73 | Vertical Zoom INVertical Zoom IN 74 | Vertical Zoom OUTVertical Zoom OUT 75 | Horizontal Zoom INHorizontal Zoom IN 76 | Horizontal Zoom OUTHorizontal Zoom OUT 77 | >>>> 78 | <<<< 79 | ReparseReparse 80 | Save parsedSave parsed 81 | Save waveformSave waveform 82 | Restore waveformRestore waveform 83 | Repair waveformRepair waveform 84 | Shift waveformShift waveform 85 | Goto address...Goto address... 86 | Selection modeSelection mode 87 | Measurement modeMeasurement mode 88 | Copy from R to L (▲)Copy from R to L (▲) 89 | Copy from L to R (▼)Copy from L to R (▼) 90 | Left ChannelLeft Channel 91 | Right ChannelRight Channel 92 | << To the beginning of the block<< To the beginning of the block 93 | To the end of the block >>To the end of the block >> 94 | ## 95 | TypeType 96 | NameName 97 | Size (to be read)Size (to be read) 98 | StatusStatus 99 | Goto Suspicious pointGoto Suspicious point 100 | Remove Suspicious pointRemove Suspicious point 101 | ## 102 | PositionPosition 103 | OkOk 104 | ErrorError 105 | UnknownUnknown 106 | HeaderHeader 107 | CodeCode 108 | LanguageLanguage 109 | EnglishEnglish 110 | RussianRussian 111 | Hotkey: %1Hotkey: %1 112 | Remove actionRemove action 113 | Action nameAction name 114 | Edit SampleEdit Sample 115 | Shift WaveformShift Waveform 116 | Sine Check Tolerance:Sine Check Tolerance: 117 | Play parsed dataPlay parsed data 118 | Stop playingStop playing 119 | (Parity: %1 ; Should be: %2) (Parity: %1 ; Should be: %2) 120 | Playing parsed dataPlaying parsed data 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /qml/Translations.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | 3 | import QtQuick 2.0 4 | import com.models.zxtapereviver 1.0 5 | 6 | QtObject { 7 | id: tranlations 8 | 9 | readonly property string wav_file_suffix: "wav" 10 | readonly property string wfm_file_suffix: "wfm" 11 | readonly property string tap_file_suffix: "tap" 12 | readonly property string filename_wildcard: "*." 13 | 14 | property string id_about_window_header: qsTrId("id_about_window_header") + TranslationManager.translationChanged 15 | property string id_please_click_to_open_link: qsTrId("id_please_click_to_open_link") + TranslationManager.translationChanged 16 | property string id_email_link: qsTrId("id_email_link").arg('computerenthusiasttips@mail.ru') + TranslationManager.translationChanged 17 | property string id_youtube_channel_name: qsTrId("id_youtube_channel_name") + TranslationManager.translationChanged 18 | property string id_youtube_channel_link: qsTrId("id_youtube_channel_link").arg('%1'.arg(id_youtube_channel_name)) 19 | property string id_donations_link: qsTrId("id_donations_link").arg('https://destream.net/live/lgolouz/donate') 20 | property string id_measured_frequency_window_header: qsTrId("id_measured_frequency_window_header") + TranslationManager.translationChanged 21 | property string id_measured_frequency: qsTrId("id_measured_frequency") + TranslationManager.translationChanged 22 | property string id_goto_address_window_header: qsTrId("id_goto_address_window_header") + TranslationManager.translationChanged 23 | property string id_please_enter_address: qsTrId("id_please_enter_address") + TranslationManager.translationChanged 24 | property string id_hexadecimal: qsTrId("id_hexadecimal") + TranslationManager.translationChanged 25 | property string id_file_menu_item: qsTrId("id_file_menu_item") + TranslationManager.translationChanged 26 | property string id_open_wav_file_menu_item: qsTrId("id_open_wav_file_menu_item") + TranslationManager.translationChanged 27 | property string id_open_waveform_file_menu_item: qsTrId("id_open_waveform_file_menu_item") + TranslationManager.translationChanged 28 | property string id_open_tap_file_menu_item: qsTrId("id_open_tap_file_menu_item") + TranslationManager.translationChanged 29 | property string id_save_menu_item: qsTrId("id_save_menu_item") + TranslationManager.translationChanged 30 | property string id_save_parsed_menu_item: qsTrId("id_save_parsed_menu_item") + TranslationManager.translationChanged 31 | property string id_left_channel_menu_item: qsTrId("id_left_channel_menu_item") + TranslationManager.translationChanged 32 | property string id_right_channel_menu_item: qsTrId("id_right_channel_menu_item") + TranslationManager.translationChanged 33 | property string id_save_waveform_menu_item: qsTrId("id_save_waveform_menu_item") + TranslationManager.translationChanged 34 | property string id_exit_menu_item: qsTrId("id_exit_menu_item") + TranslationManager.translationChanged 35 | property string id_waveform_menu_item: qsTrId("id_waveform_menu_item") + TranslationManager.translationChanged 36 | property string id_restore_view_menu_item: qsTrId("id_restore_view_menu_item") + TranslationManager.translationChanged 37 | property string id_reparse_menu_item: qsTrId("id_reparse_menu_item") + TranslationManager.translationChanged 38 | property string id_parser_settings_menu_item: qsTrId("id_parser_settings_menu_item") + TranslationManager.translationChanged 39 | property string id_help_menu_item: qsTrId("id_help_menu_item") + TranslationManager.translationChanged 40 | property string id_about_menu_item: qsTrId("id_about_menu_item") + TranslationManager.translationChanged 41 | property string id_parser_settings_window_header: qsTrId("id_parser_settings_window_header") + TranslationManager.translationChanged 42 | property string id_pilot_tone_settings: qsTrId("id_pilot_tone_settings") + TranslationManager.translationChanged 43 | property string id_pilot_half_frequency: qsTrId("id_pilot_half_frequency") + TranslationManager.translationChanged 44 | property string id_pilot_frequency: qsTrId("id_pilot_frequency") + TranslationManager.translationChanged 45 | property string id_pilot_delta: qsTrId("id_pilot_delta") + TranslationManager.translationChanged 46 | property string id_synchro_signal_settings: qsTrId("id_synchro_signal_settings") + TranslationManager.translationChanged 47 | property string id_synchro_first_half_frequency: qsTrId("id_synchro_first_half_frequency") + TranslationManager.translationChanged 48 | property string id_synchro_second_half_frequency: qsTrId("id_synchro_second_half_frequency") + TranslationManager.translationChanged 49 | property string id_synchro_frequency: qsTrId("id_synchro_frequency") + TranslationManager.translationChanged 50 | property string id_synchro_delta: qsTrId("id_synchro_delta") + TranslationManager.translationChanged 51 | property string id_precise_synchro_check: qsTrId("id_precise_synchro_check") + TranslationManager.translationChanged 52 | property string id_zero_digit_settings: qsTrId("id_zero_digit_settings") + TranslationManager.translationChanged 53 | property string id_zero_half_frequency: qsTrId("id_zero_half_frequency") + TranslationManager.translationChanged 54 | property string id_zero_frequency: qsTrId("id_zero_frequency") + TranslationManager.translationChanged 55 | property string id_zero_delta: qsTrId("id_zero_delta") + TranslationManager.translationChanged 56 | property string id_one_digit_settings: qsTrId("id_one_digit_settings") + TranslationManager.translationChanged 57 | property string id_one_half_frequency: qsTrId("id_one_half_frequency") + TranslationManager.translationChanged 58 | property string id_one_frequency: qsTrId("id_one_frequency") + TranslationManager.translationChanged 59 | property string id_one_delta: qsTrId("id_one_delta") + TranslationManager.translationChanged 60 | property string id_check_for_abnormal_sine_when_parsing: qsTrId("id_check_for_abnormal_sine_when_parsing") + TranslationManager.translationChanged 61 | property string id_please_choose_wav_file: qsTrId("id_please_choose_wav_file") + TranslationManager.translationChanged 62 | property string id_please_choose_wfm_file: qsTrId("id_please_choose_wfm_file") + TranslationManager.translationChanged 63 | property string id_please_choose_tap_file: qsTrId("id_please_choose_tap_file") + TranslationManager.translationChanged 64 | property string id_wav_files: qsTrId("id_wav_files").arg(filename_wildcard + wav_file_suffix) + TranslationManager.translationChanged 65 | property string id_wfm_files: qsTrId("id_wfm_files").arg(filename_wildcard + wfm_file_suffix) + TranslationManager.translationChanged 66 | property string id_tap_files: qsTrId("id_tap_files").arg(filename_wildcard + tap_file_suffix) + TranslationManager.translationChanged 67 | property string id_save_tap_file: qsTrId("id_save_tap_file") + TranslationManager.translationChanged 68 | property string id_save_wfm_file: qsTrId("id_save_wfm_file") + TranslationManager.translationChanged 69 | property string id_vertical_zoom_in: qsTrId("id_vertical_zoom_in") + TranslationManager.translationChanged 70 | property string id_vertical_zoom_out: qsTrId("id_vertical_zoom_out") + TranslationManager.translationChanged 71 | property string id_horizontal_zoom_in: qsTrId("id_horizontal_zoom_in") + TranslationManager.translationChanged 72 | property string id_horizontal_zoom_out: qsTrId("id_horizontal_zoom_out") + TranslationManager.translationChanged 73 | property string id_waveform_shift_left: qsTrId("id_waveform_shift_left") + TranslationManager.translationChanged 74 | property string id_waveform_shift_right: qsTrId("id_waveform_shift_right") + TranslationManager.translationChanged 75 | property string id_reparse: qsTrId("id_reparse") + TranslationManager.translationChanged //Button caption 76 | property string id_save_parsed: qsTrId("id_save_parsed") + TranslationManager.translationChanged //Button caption 77 | property string id_save_waveform: qsTrId("id_save_waveform") + TranslationManager.translationChanged //Button caption 78 | property string id_restore_waveform: qsTrId("id_restore_waveform") + TranslationManager.translationChanged //Button caption 79 | property string id_repair_waveform: qsTrId("id_repair_waveform") + TranslationManager.translationChanged //Button caption 80 | property string id_shift_waveform: qsTrId("id_shift_waveform") + TranslationManager.translationChanged //Button caption 81 | property string id_goto_address: qsTrId("id_goto_address") + TranslationManager.translationChanged //Button caption 82 | property string id_selection_mode: qsTrId("id_selection_mode") + TranslationManager.translationChanged //Button caption 83 | property string id_measurement_mode: qsTrId("id_measurement_mode") + TranslationManager.translationChanged //Button caption 84 | property string id_copy_from_r_to_l: qsTrId("id_copy_from_r_to_l") + TranslationManager.translationChanged //Button caption 85 | property string id_copy_from_l_to_r: qsTrId("id_copy_from_l_to_r") + TranslationManager.translationChanged //Button caption 86 | property string id_left_channel: qsTrId("id_left_channel") + TranslationManager.translationChanged 87 | property string id_right_channel: qsTrId("id_right_channel") + TranslationManager.translationChanged 88 | property string id_to_the_beginning_of_the_block: qsTrId("id_to_the_beginning_of_the_block") + TranslationManager.translationChanged //Button caption 89 | property string id_to_the_end_of_the_block: qsTrId("id_to_the_end_of_the_block") + TranslationManager.translationChanged //Button caption 90 | property string id_block_number: qsTrId("id_block_number") + TranslationManager.translationChanged 91 | property string id_block_type: qsTrId("id_block_type") + TranslationManager.translationChanged 92 | property string id_block_name: qsTrId("id_block_name") + TranslationManager.translationChanged 93 | property string id_block_size: qsTrId("id_block_size") + TranslationManager.translationChanged 94 | property string id_block_status: qsTrId("id_block_status") + TranslationManager.translationChanged 95 | property string id_goto_suspicious_point: qsTrId("id_goto_suspicious_point") + TranslationManager.translationChanged //Button caption 96 | property string id_remove_suspicious_point: qsTrId("id_remove_suspicious_point") + TranslationManager.translationChanged //Button caption 97 | property string id_suspicious_point_number: qsTrId("id_suspicious_point_number") + TranslationManager.translationChanged 98 | property string id_suspicious_point_position: qsTrId("id_suspicious_point_position") + TranslationManager.translationChanged 99 | property string id_language_menu_item: qsTrId("id_language_menu_item") + TranslationManager.translationChanged 100 | property string id_hotkey_tooltip: qsTrId("id_hotkey_tooltip") + TranslationManager.translationChanged 101 | property string id_remove_action: qsTrId("id_remove_action") + TranslationManager.translationChanged //Button caption 102 | property string id_action_name: qsTrId("id_action_name") + TranslationManager.translationChanged 103 | property string id_sine_check_tolerance: qsTrId("id_sine_check_tolerance") + TranslationManager.translationChanged 104 | property string id_play_parsed_data: qsTrId("id_play_parsed_data") + TranslationManager.translationChanged 105 | property string id_stop_playing_parsed_data: qsTrId("id_stop_playing_parsed_data") + TranslationManager.translationChanged 106 | property string id_playing_parsed_data_window_header: qsTrId("id_playing_parsed_data_window_header") + TranslationManager.translationChanged 107 | } 108 | -------------------------------------------------------------------------------- /qml/translations/zxtapereviver_ru_RU.xlf: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | %1 sec%1 сек 18 | AboutО программе 19 | E-mail: %1Электронная почта: %1 20 | YouTube channel: %1YouTube канал: %1 21 | Donate to program development: %1Пожертвовать на развитие программы: %1 22 | Please click the highlighted links to openПожалуйста, нажмите на подсвеченные ссылки для их открытия 23 | Computer Enthusiast TipsСоветы компьютерного энтузиаста 24 | Measured frequencyИзмеритель частоты 25 | Measured frequency:Измеренная частота: 26 | Go to addressПереход на адрес 27 | Please enter address:Пожалуйста введите адрес: 28 | HexadecimalВ шестнадцатеричном виде 29 | FileФайл 30 | Open WAV file...Открыть WAV файл... 31 | Open Waveform file...Открыть файл с формой волны... 32 | Open TAP file...Открыть TAP файл... 33 | SaveСохранить 34 | ParsedРазобранный 35 | Left channel...Левый канал... 36 | Right channel...Правый канал... 37 | Waveform...Форму волны... 38 | ExitВыход 39 | WaveformФорма волны 40 | Restore viewВосстановить вид 41 | ReparseРазобрать 42 | Parser settings...Настройки парсера... 43 | HelpПомощь 44 | About...О программе... 45 | Parser settingsНастройки парсера 46 | Pilot-tone settings:Настройки пилот-тона: 47 | Pilot half frequency:Частота полупериода пилот-тона: 48 | Pilot frequency:Частота периода пилот-тона: 49 | Pilot delta:Допустимое отклонение частоты пилот-тона: 50 | Synchro signal settigns:Настройки синхро-сигнала: 51 | Synchro first half frequency:Частота первого полупериода синхро-сигнала: 52 | Synchro second half frequency:Частота второго полупериода синхро-сигнала: 53 | Synchro frequency:Частота периода синхро-сигнала: 54 | Synchro delta:Допустимое отклонение частоты синхро-сигнала: 55 | Precise synchro checkТочная проверка синхро-сигнала 56 | Zero digit settings:Настройки для "нуля": 57 | Zero half frequency:Частота полупериода "нуля": 58 | Zero frequency:Частота периода "нуля": 59 | Zero delta:Допустимое отклонение частоты "нуля": 60 | One digit settings:Настройки для "единицы": 61 | One half frequency:Частота полупериода "единицы": 62 | One frequency:Частота периода "единицы": 63 | One delta:Допустимое отклонение частоты "единицы": 64 | Check for abnormal sine when parsingПроверка на явно вырожденную синусоиду во время разбора 65 | Please choose WAV fileПожалуйста, выберите WAV файл 66 | Please choose WFM fileПожалуйста, выберите WFM файл 67 | Please choose TAP fileПожалуйста, выберите TAP 68 | WAV files (%1)Файлы WAV (%1) 69 | Waveform files (%1)Файлы формы волны (%1) 70 | TAP files (%1)Файлы TAP (%1) 71 | Save TAP file...Сохранить TAP файл... 72 | Save WFM file...Сохранить WFM файл... 73 | Vertical Zoom INВертикаль больше 74 | Vertical Zoom OUTВертикаль меньше 75 | Horizontal Zoom INГоризонталь больше 76 | Horizontal Zoom OUTГоризонталь меньше 77 | >>>> 78 | <<<< 79 | ReparseРазобрать 80 | Save parsedСохранить разобранное 81 | Save waveformСохранить форму волны 82 | Restore waveformВосстановить волну 83 | Repair waveformИсправить волну 84 | Shift waveformСдвинуть волну 85 | Goto address...Перейти на адрес 86 | Selection modeРежим выделения 87 | Measurement modeРежим измерения 88 | Copy from R to L (▲)Копировать из П в Л (▲) 89 | Copy from L to R (▼)Копировать из Л в П (▼) 90 | Left ChannelЛевый канал 91 | Right ChannelПравый канал 92 | << To the beginning of the block<< В начало блока 93 | To the end of the block >>В конец блока >> 94 | ## 95 | TypeТип 96 | NameИмя 97 | Size (to be read)Размер (должно быть считано) 98 | StatusСтатус 99 | Goto Suspicious pointПерейти к подозрительной точке 100 | Remove Suspicious pointУдалить подозрительную точку 101 | ## 102 | PositionПозиция 103 | OkОК 104 | ErrorОшибка 105 | UnknownНеизвестен 106 | HeaderHeader 107 | CodeCode 108 | LanguageЯзык 109 | EnglishАнглийский 110 | RussianРусский 111 | Hotkey: %1Горячая клавиша: %1 112 | Remove actionУдалить действие 113 | Action nameНазвание действия 114 | Edit SampleРедактирование семпла 115 | Shift WaveformСдвиг волны 116 | Sine Check Tolerance:Допуск при проверке синусоиды: 117 | Play parsed dataИграть разобранное 118 | Stop playingСтоп воспроизведения 119 | (Parity: %1 ; Should be: %2) (Сумма: %1 ; Ожидается: %2) 120 | Playing parsed dataВоспроизведение разобранных данных 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /sources/controls/waveformcontrol.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "waveformcontrol.h" 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "sources/translations/translations.h" 25 | #include "sources/models/actionsmodel.h" 26 | #include "sources/actions/editsampleaction.h" 27 | 28 | WaveformControl::WaveformControl(QQuickItem* parent) : 29 | QQuickPaintedItem(parent), 30 | mWavReader(*WavReader::instance()), 31 | mWavParser(*WaveformParser::instance()), 32 | mWaveFormModel(*WaveFormModel::instance()), 33 | m_customData(*ConfigurationManager::instance()->getWaveformCustomization()), 34 | m_channelNumber(0), 35 | m_isWaveformRepaired(false), 36 | m_wavePos(0), 37 | m_xScaleFactor(1), 38 | m_yScaleFactor(80000), 39 | m_clickState(WaitForFirstPress), 40 | m_clickPosition(0), 41 | m_operationMode(WaveformControlOperationModes::WaveformRepairMode), 42 | m_rangeSelected(false), 43 | m_clickCount(0) 44 | { 45 | setAcceptedMouseButtons(Qt::AllButtons); 46 | setEnabled(true); 47 | } 48 | 49 | QColor WaveformControl::getBackgroundColor() const { 50 | switch (m_operationMode) { 51 | case WaveformSelectionMode: 52 | return m_customData.selectioModeBgColor(); 53 | 54 | case WaveformMeasurementMode: 55 | return m_customData.measurementModeBgColor(); 56 | 57 | default: 58 | return m_customData.operationModeBgColor(); 59 | } 60 | } 61 | 62 | QSharedPointer WaveformControl::getChannel(uint* chNum) const { 63 | const auto c = chNum ? *chNum : m_channelNumber; 64 | return mWaveFormModel.getChannel(c); 65 | } 66 | 67 | int WaveformControl::getWavPositionByMouseX(int x, int* point, double* dx) const 68 | { 69 | const int xinc = getXScaleFactor() > 16.0 ? getXScaleFactor() / 16 : 1; 70 | const double scale = boundingRect().width() * getXScaleFactor(); 71 | double tdx; 72 | double& rdx = (dx ? *dx : tdx) = (boundingRect().width() / scale) * xinc; 73 | 74 | int tpoint; 75 | int& rpoint = (point ? *point : tpoint) = std::round(x / rdx); 76 | 77 | return rpoint + getWavePos() * xinc; 78 | } 79 | 80 | void WaveformControl::paint(QPainter* painter) { 81 | auto p = painter->pen(); 82 | painter->setBackground(QBrush(getBackgroundColor())); 83 | painter->setBackgroundMode(Qt::OpaqueMode); 84 | painter->setPen(m_customData.xAxisColor()); 85 | const auto& bRect = boundingRect(); 86 | const double waveHeight = bRect.height() - 100; 87 | const double halfHeight = waveHeight / 2; 88 | painter->fillRect(bRect, painter->background()); 89 | painter->drawLine(0, halfHeight, bRect.width(), halfHeight); 90 | int32_t scale = bRect.width() * getXScaleFactor(); 91 | int32_t pos = getWavePos(); 92 | const auto channel = getChannel(); 93 | if (channel == nullptr) { 94 | return; 95 | } 96 | 97 | // Time Marker 98 | // It should be painted only once 99 | p.setColor(m_customData.textColor()); 100 | painter->setPen(p); 101 | uint32_t sampleRate = mWavReader.getSampleRate(); 102 | double posStartSec = (double) pos / sampleRate; 103 | double posMidSec = (double) (pos + scale / 2) / sampleRate; 104 | double posEndSec = (double) (pos + scale) / sampleRate; 105 | painter->drawText(3, 3, 100 - 3, 20, Qt::AlignTop | Qt::AlignLeft, qtTrId(ID_TIMELINE_SEC).arg(QString::number(posStartSec, 'f', 3))); 106 | painter->drawText((int) bRect.width() / 2 + 3, 3, 100 - 3, 20, Qt::AlignTop | Qt::AlignLeft, qtTrId(ID_TIMELINE_SEC).arg(QString::number(posMidSec, 'f', 3))); 107 | painter->drawText((int) bRect.width() - 100, 3, 100 - 3, 20, Qt::AlignTop | Qt::AlignRight, qtTrId(ID_TIMELINE_SEC).arg(QString::number(posEndSec, 'f', 3))); 108 | p.setColor(m_customData.yAxisColor()); 109 | p.setWidth(1); 110 | p.setStyle(Qt::DashLine); 111 | painter->setPen(p); 112 | painter->drawLine((int) bRect.width() / 2, 0, (int) bRect.width() / 2, waveHeight); 113 | 114 | //Restore default line style 115 | p.setStyle(Qt::SolidLine); 116 | painter->setPen(p); 117 | 118 | const double maxy = getYScaleFactor(); 119 | double px = 0; 120 | double py = halfHeight; 121 | double x = 0; 122 | double y = py; 123 | const int xinc = getXScaleFactor() > 16.0 ? getXScaleFactor() / 16 : 1; 124 | double dx = (bRect.width() / (double) scale) * xinc; 125 | const auto parsedWaveform = mWavParser.getParsedWaveform(m_channelNumber); 126 | const auto parsedData = mWavParser.getParsedDataSharedPtr(m_channelNumber); 127 | bool printHint = false; 128 | m_allowToGrabPoint = dx > 2; 129 | const auto chsize = channel->size(); 130 | 131 | QFont fnt; 132 | const auto pixelSize { painter->fontInfo().pixelSize() }; 133 | 134 | for (int32_t t = pos; t < pos + scale; t += xinc) { 135 | if (t >= 0 && t < chsize) { 136 | const int val = channel->operator[](t); 137 | y = halfHeight - ((double) (val) / maxy) * waveHeight; 138 | p.setWidth(m_customData.waveLineThickness()); 139 | p.setColor(val >= 0 ? m_customData.wavePositiveColor() : m_customData.waveNegativeColor()); 140 | painter->setPen(p); 141 | painter->drawLine(px, py, x, y); 142 | if (m_allowToGrabPoint) { 143 | painter->drawEllipse(QPoint(x, y), m_customData.circleRadius(), m_customData.circleRadius()); 144 | } 145 | 146 | const auto pwf = parsedWaveform[t]; 147 | if (pwf & ParsedData::sequenceMiddle) { 148 | p.setWidth(3); 149 | painter->setPen(p); 150 | painter->drawLine(px, bRect.height() - 20, x, bRect.height() - 20); 151 | if (pwf & ParsedData::zeroBit || pwf & ParsedData::oneBit) { 152 | p.setColor(m_customData.blockMarkerColor()); 153 | painter->setPen(p); 154 | painter->drawLine(px, bRect.height() - 3, x, bRect.height() - 3); 155 | } 156 | 157 | if (printHint) { 158 | QString text = pwf & ParsedData::pilotTone 159 | ? "PILOT" 160 | : pwf & ParsedData::synchroSignal 161 | ? "SYNC" 162 | : pwf & ParsedData::zeroBit 163 | ? "\"0\"" 164 | : "\"1\""; 165 | p.setWidth(1); 166 | p.setColor(m_customData.textColor()); 167 | painter->setPen(p); 168 | painter->drawText(x + 3, bRect.height() - 20 - 10, text); 169 | printHint = false; 170 | } 171 | } 172 | else if (pwf & ParsedData::sequenceBegin || pwf & ParsedData::sequenceEnd) { 173 | printHint = pwf & ParsedData::sequenceBegin; 174 | auto p = painter->pen(); 175 | p.setWidth(3); 176 | painter->setPen(p); 177 | painter->drawLine(x, waveHeight + 2, x, bRect.height() - 20); 178 | 179 | if (pwf & ParsedData::byteBound) { 180 | bool seqBegin = printHint; 181 | p.setColor(seqBegin ? m_customData.blockStartColor() : m_customData.blockEndColor()); 182 | painter->setPen(p); 183 | painter->drawLine(x, bRect.height() - 10, x, bRect.height() - 3); 184 | 185 | auto parsedIt = std::find_if(parsedData->begin(), parsedData->end(), [t](const ParsedData::DataBlock& db) { 186 | return t >= db.dataStart && t <= db.dataEnd; 187 | }); 188 | 189 | if (parsedIt != parsedData->end()) { 190 | fnt.setPixelSize(9); 191 | painter->setFont(fnt); 192 | p.setWidth(1); 193 | p.setColor(m_customData.textColor()); 194 | painter->setPen(p); 195 | 196 | const auto toHexVal = [](uint val, uint count){ 197 | return QString("0x%1").arg(QString("%1").arg(val, count, 16, QLatin1Char('0')).toUpper()); 198 | }; 199 | 200 | const auto addrIt = (*parsedIt).dataMapping.find(t); 201 | if (addrIt != (*parsedIt).dataMapping.end()) { 202 | if (seqBegin) { 203 | painter->drawText(x + 5, bRect.height() - 6, toHexVal(*addrIt, *addrIt <= 65535 ? 4 : 6)); 204 | } else { 205 | painter->drawText(x - 5 - 19, bRect.height() - 6, toHexVal((*parsedIt).data[*addrIt], 2)); 206 | } 207 | } 208 | 209 | fnt.setPixelSize(pixelSize); 210 | painter->setFont(fnt); 211 | } 212 | } 213 | } 214 | } 215 | 216 | px = x; 217 | py = y; 218 | x += dx; 219 | } 220 | 221 | if (m_operationMode == WaveformSelectionMode && m_rangeSelected) { 222 | painter->setBackground(QBrush(m_customData.rangeSelectionColor())); 223 | auto bRect = boundingRect(); 224 | bRect.setX(m_selectionRange.first); 225 | bRect.setRight(m_selectionRange.second); 226 | painter->fillRect(bRect, painter->background()); 227 | } 228 | } 229 | 230 | int32_t WaveformControl::getWavePos() const 231 | { 232 | return m_wavePos; 233 | } 234 | 235 | int32_t WaveformControl::getWaveLength() const 236 | { 237 | return getChannel()->size(); 238 | } 239 | 240 | double WaveformControl::getXScaleFactor() const 241 | { 242 | return m_xScaleFactor; 243 | } 244 | 245 | double WaveformControl::getYScaleFactor() const 246 | { 247 | return m_yScaleFactor; 248 | } 249 | 250 | bool WaveformControl::getIsWaveformRepaired() const 251 | { 252 | return m_isWaveformRepaired; 253 | } 254 | 255 | WaveformControl::WaveformControlOperationModes WaveformControl::getOperationMode() const 256 | { 257 | return m_operationMode; 258 | } 259 | 260 | void WaveformControl::setWavePos(int32_t wavePos) 261 | { 262 | if (m_wavePos != wavePos) { 263 | m_wavePos = wavePos; 264 | update(); 265 | 266 | emit wavePosChanged(); 267 | } 268 | } 269 | 270 | void WaveformControl::setXScaleFactor(double xScaleFactor) 271 | { 272 | if (m_xScaleFactor != xScaleFactor) { 273 | m_xScaleFactor = xScaleFactor; 274 | update(); 275 | 276 | emit xScaleFactorChanged(); 277 | } 278 | } 279 | 280 | void WaveformControl::setYScaleFactor(double yScaleFactor) 281 | { 282 | if (m_yScaleFactor != yScaleFactor) { 283 | m_yScaleFactor = yScaleFactor; 284 | update(); 285 | 286 | emit yScaleFactorChanged(); 287 | } 288 | } 289 | 290 | void WaveformControl::setOperationMode(WaveformControlOperationModes mode) 291 | { 292 | if (m_operationMode != mode) { 293 | m_operationMode = mode; 294 | m_rangeSelected = false; 295 | m_clickCount = 0; 296 | update(); 297 | 298 | emit operationModeChanged(); 299 | } 300 | } 301 | 302 | void WaveformControl::mousePressEvent(QMouseEvent* event) 303 | { 304 | if (!event) { 305 | return; 306 | } 307 | 308 | //Checking for double-click 309 | if (event->button() == Qt::LeftButton) { 310 | auto now = QDateTime::currentDateTime(); 311 | qDebug() << "Click state: " << m_clickState << "; time: " << m_clickTime.msecsTo(now); 312 | if (m_clickState == WaitForFirstPress) { 313 | m_clickTime = QDateTime::currentDateTime(); 314 | m_clickState = WaitForFirstRelease; 315 | } 316 | else if (m_clickState == WaitForSecondPress && m_clickTime.msecsTo(now) <= 500) { 317 | m_clickState = WaitForSecondRelease; 318 | } 319 | else { 320 | m_clickState = WaitForFirstPress; 321 | } 322 | } 323 | 324 | if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton || event->button() == Qt::MiddleButton) { 325 | double dx; 326 | int point; 327 | m_clickPosition = getWavPositionByMouseX(event->x(), &point, &dx); 328 | event->accept(); 329 | 330 | if (m_operationMode == WaveformSelectionMode) { 331 | m_rangeSelected = true; 332 | m_selectionRange = { event->x(), event->x() }; 333 | update(); 334 | } 335 | 336 | if (dx <= 2.0) { 337 | if (m_operationMode == WaveformMeasurementMode) { 338 | emit cannotSetMeasurementPoint(); 339 | } 340 | return; 341 | } 342 | double dpoint = point * dx; 343 | const double waveHeight = boundingRect().height() - 100; 344 | const double halfHeight = waveHeight / 2; 345 | 346 | if (m_operationMode == WaveformRepairMode) { 347 | if (event->button() == Qt::MiddleButton) { 348 | //const auto p = point + getWavePos(); 349 | const auto d = getChannel()->operator[](m_clickPosition); 350 | qDebug() << "Inserting point: " << m_clickPosition; 351 | getChannel()->insert(m_clickPosition + (dpoint > event->x() ? 1 : -1), d); 352 | update(); 353 | } 354 | else { 355 | if (dpoint >= (event->x() - dx/2) && dpoint <= (event->x() + dx/2)) { 356 | const double maxy = getYScaleFactor(); 357 | auto initialVal { getChannel()->operator[](m_clickPosition) }; 358 | double y = halfHeight - ((double) (initialVal) / maxy) * waveHeight; 359 | if (!m_customData.checkVerticalRange() || (y >= event->y() - 2 && y <= event->y() + 2)) { 360 | if (event->button() == Qt::LeftButton) { 361 | m_pointIndex = point; 362 | m_initialValue = initialVal; 363 | m_pointGrabbed = true; 364 | qDebug() << "Grabbed point: " << initialVal; //getChannel()->operator[](m_clickPosition); 365 | } 366 | else { 367 | if (QGuiApplication::queryKeyboardModifiers() != Qt::ShiftModifier) { 368 | m_pointGrabbed = false; 369 | qDebug() << "Deleting point"; 370 | getChannel()->remove(m_clickPosition); 371 | update(); 372 | } 373 | } 374 | } 375 | } 376 | } 377 | } // m_operationMode 378 | } 379 | } 380 | 381 | void WaveformControl::mouseReleaseEvent(QMouseEvent* event) 382 | { 383 | if (!event) { 384 | return; 385 | } 386 | 387 | switch (event->button()) { 388 | case Qt::LeftButton: 389 | { 390 | //Double-click handling 391 | auto now = QDateTime::currentDateTime(); 392 | qDebug() << "Click state: " << m_clickState << "; time: " << m_clickTime.msecsTo(now); 393 | 394 | if (m_operationMode == WaveformSelectionMode && m_rangeSelected && m_selectionRange.first == m_selectionRange.second) { 395 | m_rangeSelected = false; 396 | } 397 | 398 | if (m_operationMode == WaveformRepairMode || m_operationMode == WaveformSelectionMode) { 399 | if (m_clickState == WaitForFirstRelease && m_clickTime.msecsTo(now) <= 500) { 400 | m_clickState = WaitForSecondPress; 401 | } 402 | else if (m_clickState == WaitForSecondRelease && m_clickTime.msecsTo(now) <= 500) { 403 | m_clickState = WaitForFirstPress; 404 | emit doubleClick(m_clickPosition); 405 | } 406 | else { 407 | m_clickState = WaitForFirstPress; 408 | } 409 | 410 | if (m_pointGrabbed) { 411 | ActionsModel::instance()->addAction(QSharedPointer::create(m_channelNumber, EditSampleActionParams { m_initialValue, m_newValue, m_clickPosition })); 412 | } 413 | } else if (m_operationMode == WaveformMeasurementMode) { 414 | auto& clickPoint = m_clickCount == 0 ? m_selectionRange.first : m_selectionRange.second; 415 | clickPoint = getWavPositionByMouseX(event->x()); 416 | if (m_clickCount == 1) { 417 | auto len = std::abs(m_selectionRange.first - m_selectionRange.second); 418 | int freq = mWavReader.getSampleRate() / (len == 0 ? 1 : len); 419 | qDebug() << "Frequency: " << freq; 420 | emit frequency(freq); 421 | } 422 | m_clickCount = (m_clickCount + 1) % 2; 423 | } 424 | 425 | m_pointGrabbed = false; 426 | event->accept(); 427 | } 428 | break; 429 | 430 | default: 431 | event->ignore(); 432 | break; 433 | } 434 | } 435 | 436 | void WaveformControl::mouseMoveEvent(QMouseEvent* event) 437 | { 438 | if (!event) { 439 | return; 440 | } 441 | 442 | switch (event->buttons()) { 443 | case Qt::LeftButton: { 444 | if (m_operationMode == WaveformSelectionMode && m_rangeSelected) { 445 | if (event->x() <= m_selectionRange.first) { 446 | m_selectionRange.first = event->x(); 447 | } 448 | else { 449 | m_selectionRange.second = event->x(); 450 | } 451 | } 452 | else if (m_operationMode == WaveformRepairMode) { 453 | const auto ch = getChannel(); 454 | if (!ch) { 455 | return; 456 | } 457 | 458 | const double waveHeight = boundingRect().height() - 100; 459 | const double halfHeight = waveHeight / 2; 460 | const auto pointerPos = halfHeight - event->y(); 461 | double val = halfHeight + (m_yScaleFactor / waveHeight * pointerPos); 462 | if (m_pointIndex + getWavePos() >= 0 && m_pointIndex + getWavePos() < ch->size()) { 463 | m_newValue = val; 464 | getChannel()->operator[](m_pointIndex + getWavePos()) = val; 465 | } 466 | qDebug() << "Setting point: " << m_pointIndex + getWavePos(); 467 | } 468 | event->accept(); 469 | update(); 470 | } 471 | break; 472 | 473 | // Smooth drawing at Repair mode 474 | case Qt::RightButton: { 475 | if (m_operationMode == WaveformRepairMode && QGuiApplication::queryKeyboardModifiers() == Qt::ShiftModifier) { 476 | const auto ch = getChannel(); 477 | if (!ch) { 478 | return; 479 | } 480 | double dx; 481 | int point; 482 | m_clickPosition = getWavPositionByMouseX(event->x(), &point, &dx); 483 | const double waveHeight = boundingRect().height() - 100; 484 | const double halfHeight = waveHeight / 2; 485 | const auto pointerPosY = halfHeight - event->y(); 486 | double val = halfHeight + (m_yScaleFactor / waveHeight * pointerPosY); 487 | getChannel()->operator[](m_clickPosition) = val; 488 | } 489 | event->accept(); 490 | update(); 491 | } 492 | break; 493 | 494 | default: 495 | event->ignore(); 496 | break; 497 | } 498 | } 499 | 500 | uint WaveformControl::getChannelNumber() const 501 | { 502 | return m_channelNumber; 503 | } 504 | 505 | void WaveformControl::setChannelNumber(uint chNum) 506 | { 507 | if (chNum != m_channelNumber) { 508 | m_channelNumber = chNum; 509 | emit channelNumberChanged(); 510 | } 511 | } 512 | 513 | void WaveformControl::reparse() 514 | { 515 | mWavParser.parse(m_channelNumber); 516 | update(); 517 | } 518 | 519 | void WaveformControl::saveTap(const QString& fileUrl) 520 | { 521 | QString fileName = fileUrl.isEmpty() ? fileUrl : QUrl(fileUrl).toLocalFile(); 522 | mWavParser.saveTap(m_channelNumber, fileName); 523 | } 524 | 525 | void WaveformControl::saveWaveform() 526 | { 527 | mWavReader.saveWaveform(); 528 | } 529 | 530 | void WaveformControl::repairWaveform() 531 | { 532 | if (!m_isWaveformRepaired) { 533 | mWavParser.repairWaveform2(m_channelNumber); 534 | //mWavReader.repairWaveform(m_channelNumber); 535 | //mWavReader.normalizeWaveform2(m_channelNumber); 536 | update(); 537 | m_isWaveformRepaired = true; 538 | emit isWaveformRepairedChanged(); 539 | } 540 | } 541 | 542 | void WaveformControl::restoreWaveform() 543 | { 544 | if (m_isWaveformRepaired) { 545 | mWavReader.restoreWaveform(m_channelNumber); 546 | update(); 547 | m_isWaveformRepaired = false; 548 | emit isWaveformRepairedChanged(); 549 | } 550 | } 551 | 552 | void WaveformControl::shiftWaveform() 553 | { 554 | mWavReader.shiftWaveform(m_channelNumber); 555 | update(); 556 | } 557 | 558 | void WaveformControl::copySelectedToAnotherChannel() 559 | { 560 | if (m_operationMode == WaveformSelectionMode && m_rangeSelected) { 561 | uint destChNum = getChannelNumber() == 0 ? 1 : 0; 562 | const auto sourceChannel = getChannel(); 563 | const auto destChannel = getChannel(&destChNum); 564 | const auto endIdx = getWavPositionByMouseX(m_selectionRange.second); 565 | for (auto i = getWavPositionByMouseX(m_selectionRange.first); i <= endIdx; ++i) { 566 | destChannel->operator[](i) = sourceChannel->operator[](i); 567 | } 568 | } 569 | } 570 | -------------------------------------------------------------------------------- /sources/core/waveformparser.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | // ZX Tape Reviver 3 | //----------------- 4 | // 5 | // Author: Leonid Golouz 6 | // E-mail: lgolouz@list.ru 7 | // YouTube channel: https://www.youtube.com/channel/UCz_ktTqWVekT0P4zVW8Xgcg 8 | // YouTube channel e-mail: computerenthusiasttips@mail.ru 9 | // 10 | // Code modification and distribution of any kind is not allowed without direct 11 | // permission of the Author. 12 | //******************************************************************************* 13 | 14 | #include "waveformparser.h" 15 | #include "sources/models/parsersettingsmodel.h" 16 | #include "sources/translations/translations.h" 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #define HARDCODED_DATA_SIGNAL_DELTA 0.75 24 | 25 | WaveformParser::WaveformParser(QObject* parent) : 26 | QObject(parent), 27 | mWavReader(*WavReader::instance()) 28 | { 29 | 30 | } 31 | 32 | void WaveformParser::repairWaveform2(uint chNum) { 33 | if (chNum >= mWavReader.getNumberOfChannels()) { 34 | qDebug() << "Trying to parse channel that exceeds overall number of channels"; 35 | return; 36 | } 37 | 38 | QWavVector& channel = *(chNum == 0 ? mWavReader.getChannel0() : mWavReader.getChannel1()); 39 | QVector parsed = parseChannel(channel); 40 | 41 | const auto& parserSettings = ParserSettingsModel::instance()->getParserSettings(); 42 | const double sampleRate = mWavReader.getSampleRate(); 43 | for (auto it { parsed.begin() }; it != parsed.end();) { 44 | auto itprev = it++; 45 | if (it != parsed.end()) { 46 | bool isZero = isZeroFreqFitsInDelta(sampleRate, (*it).length + (*itprev).length, parserSettings.zeroFreq, parserSettings.zeroDelta, HARDCODED_DATA_SIGNAL_DELTA); 47 | bool isOne = isOneFreqFitsInDelta(sampleRate, (*it).length + (*itprev).length, parserSettings.oneFreq, HARDCODED_DATA_SIGNAL_DELTA, parserSettings.oneDelta); 48 | if (isZero || isOne) { 49 | auto it1 = std::next(channel.begin(), (*itprev).begin); 50 | auto it2 = std::next(channel.begin(), (*it).end); 51 | auto itmiddle = std::next(it1, std::distance(it1, it2) / 2); 52 | const auto min_max = std::minmax_element(it1, it2); 53 | auto [val1, val2] = (*itprev).sign == ParsedData::WaveformSign::NEGATIVE ? min_max : decltype(min_max) {min_max.second, min_max.first}; 54 | auto itr = it1; 55 | for (; itr != itmiddle; ++itr) { 56 | *itr = *val1; 57 | } 58 | for (; itr != it2; ++itr) { 59 | *itr = *val2; 60 | } 61 | } 62 | ++it; 63 | } 64 | } 65 | } 66 | 67 | inline ParsedData* WaveformParser::getOrCreateParsedDataPtr(uint chNum) 68 | { 69 | auto it = m_parsedData.find(chNum); 70 | return it == m_parsedData.end() ? *m_parsedData.insert(chNum, new ParsedData(this)) : *it; 71 | } 72 | 73 | inline ParsedData* WaveformParser::getParsedDataPtr(uint chNum) const 74 | { 75 | auto parsedDataIt { m_parsedData.find(chNum) }; 76 | if (parsedDataIt == m_parsedData.end()) { 77 | qWarning() << "Unable to access parsed data with requested channel number."; 78 | return nullptr; 79 | } 80 | return *parsedDataIt; 81 | } 82 | 83 | QSharedPointer> WaveformParser::getParsedDataSharedPtr(uint chNum) const 84 | { 85 | auto p = getParsedDataPtr(chNum); 86 | return p == nullptr ? QSharedPointer> { } : p->getParsedData(); 87 | } 88 | 89 | inline bool WaveformParser::isZeroFreqFitsInDelta(uint32_t sampleRate, uint32_t length, uint32_t signalFreq, double signalDeltaBelow, double signalDeltaAbove) const 90 | { 91 | return isFreqFitsInDelta2(sampleRate, length, signalFreq, signalDeltaBelow, signalDeltaAbove); 92 | } 93 | 94 | inline bool WaveformParser::isOneFreqFitsInDelta(uint32_t sampleRate, uint32_t length, uint32_t signalFreq, double signalDeltaBelow, double signalDeltaAbove) const 95 | { 96 | return isFreqFitsInDelta2(sampleRate, length, signalFreq, signalDeltaBelow, signalDeltaAbove); 97 | } 98 | 99 | void WaveformParser::parse(uint chNum) 100 | { 101 | if (chNum >= mWavReader.getNumberOfChannels()) { 102 | qDebug() << "Trying to parse channel that exceeds overall number of channels"; 103 | return; 104 | } 105 | 106 | QWavVector& channel = *(chNum == 0 ? mWavReader.getChannel0() : mWavReader.getChannel1()); 107 | QVector parsed = parseChannel(channel); 108 | 109 | const double sampleRate = mWavReader.getSampleRate(); 110 | auto& parsedData = *getOrCreateParsedDataPtr(chNum); 111 | parsedData.clear(channel.size()); 112 | 113 | auto currentState = SEARCH_OF_PILOT_TONE; 114 | auto it = parsed.begin(); 115 | QVector data; 116 | QVector waveformData; 117 | QMap data_mapping; 118 | //WaveformSign signalDirection = POSITIVE; 119 | uint32_t dataStart = 0; 120 | uint8_t bitIndex = 0; 121 | uint8_t bit = 0; 122 | uint8_t parity = 0; 123 | 124 | const auto& parserSettings = ParserSettingsModel::instance()->getParserSettings(); 125 | auto isSineNormal = [&parserSettings, sampleRate](const ParsedData::WaveformPart& b, const ParsedData::WaveformPart& e, bool zeroCheck) -> bool { 126 | if (parserSettings.checkForAbnormalSine) { 127 | return isFreqFitsInDelta(sampleRate, b.length, zeroCheck ? parserSettings.zeroHalfFreq : parserSettings.oneHalfFreq, zeroCheck ? parserSettings.zeroDelta : parserSettings.oneDelta, parserSettings.sineCheckTolerance) && 128 | isFreqFitsInDelta(sampleRate, e.length, zeroCheck ? parserSettings.zeroHalfFreq : parserSettings.oneHalfFreq, zeroCheck ? parserSettings.zeroDelta : parserSettings.oneDelta, parserSettings.sineCheckTolerance); 129 | } 130 | return true; 131 | }; 132 | 133 | const auto isPilotHalfFreq = [&parserSettings, sampleRate](const ParsedData::WaveformPart& p) -> bool { 134 | return isFreqFitsInDelta(sampleRate, p.length, parserSettings.pilotHalfFreq, parserSettings.pilotDelta, 1.0); 135 | }; 136 | const auto isSynchroFirstHalfFreq = [&parserSettings, sampleRate](const ParsedData::WaveformPart& p, double deltaDivider = 1.0) -> bool { 137 | return isFreqFitsInDelta(sampleRate, p.length, parserSettings.synchroFirstHalfFreq, parserSettings.synchroDelta, deltaDivider); 138 | }; 139 | const auto isSynchroSecondHalfFreq = [&parserSettings, sampleRate](const ParsedData::WaveformPart& p, double deltaDivider = 1.0) -> bool { 140 | return isFreqFitsInDelta(sampleRate, p.length, parserSettings.synchroSecondHalfFreq, parserSettings.synchroDelta, deltaDivider); 141 | }; 142 | 143 | while (currentState != NO_MORE_DATA) { 144 | auto prevIt = it; 145 | switch (currentState) { 146 | case SEARCH_OF_PILOT_TONE: 147 | it = std::find_if(it, parsed.end(), [&isPilotHalfFreq, &parsedData, this](const ParsedData::WaveformPart& p) { 148 | parsedData.fillParsedWaveform(p, 0); 149 | return isPilotHalfFreq(p); 150 | }); 151 | if (it != parsed.end()) { 152 | currentState = PILOT_TONE; 153 | } 154 | break; 155 | 156 | case PILOT_TONE: 157 | for (; it != parsed.end() && isPilotHalfFreq(*it); ++it) { 158 | //Mark parsed waveform as pilot-tone 159 | parsedData.fillParsedWaveform(*it, ParsedData::pilotTone | ParsedData::sequenceMiddle); 160 | }; 161 | 162 | //Found the first half of SYNCHRO signal 163 | if (it != parsed.end()) { 164 | if (auto itnext { std::next(it) }; 165 | (parserSettings.preciseSynchroCheck && isSynchroFirstHalfFreq(*it)) || 166 | (!parserSettings.preciseSynchroCheck && 167 | (itnext != parsed.end() && 168 | isFreqFitsInDelta(sampleRate, it->length + itnext->length, parserSettings.synchroFreq, parserSettings.synchroDelta, 1.0)))) 169 | { 170 | auto eIt = std::prev(it); 171 | //Mark parsed waveform as pilot-tone and sets the begin and end bounds 172 | parsedData.fillParsedWaveform(*eIt, ParsedData::pilotTone | ParsedData::sequenceMiddle, 173 | prevIt->begin, ParsedData::pilotTone | ParsedData::sequenceBegin, 174 | eIt->end, ParsedData::pilotTone | ParsedData::sequenceEnd); 175 | currentState = SYNCHRO_SIGNAL; 176 | } 177 | else { 178 | currentState = SEARCH_OF_PILOT_TONE; 179 | } 180 | } 181 | else { 182 | currentState = SEARCH_OF_PILOT_TONE; 183 | } 184 | break; 185 | 186 | case SYNCHRO_SIGNAL: 187 | it = std::next(it); 188 | if (it != parsed.end()) { 189 | //Check for second half of SYNCHRO signal or if `preciseSynchroCheck` option is off - assume there is synchro, because we did the check on the previous step 190 | if (!parserSettings.preciseSynchroCheck || isSynchroSecondHalfFreq(*it)) { 191 | //Mark parsed waveform as syncro signal and sets the begin and end bounds 192 | parsedData.fillParsedWaveform(*prevIt, *it, ParsedData::synchroSignal | ParsedData::sequenceMiddle, 193 | ParsedData::synchroSignal | ParsedData::sequenceBegin, 194 | ParsedData::synchroSignal | ParsedData::sequenceEnd); 195 | 196 | //Initializing the currently parsing data block 197 | currentState = DATA_SIGNAL; 198 | 199 | it = std::next(it); 200 | dataStart = std::distance(parsed.begin(), it); 201 | data.clear(); 202 | waveformData.clear(); 203 | data_mapping.clear(); 204 | bitIndex ^= bitIndex; 205 | bit ^= bit; 206 | } 207 | else { 208 | //Got the synchro error 209 | currentState = SEARCH_OF_PILOT_TONE; 210 | } 211 | } 212 | break; 213 | 214 | case DATA_SIGNAL: 215 | it = std::next(it); 216 | if (it != parsed.end()) { 217 | const auto len = it->length + prevIt->length; 218 | const auto storeParsedByte = [&parsedData, &bitIndex, &data, &waveformData, &parity, &bit, it]() { 219 | //Store parsed byte in data buffer 220 | bitIndex ^= bitIndex; 221 | data.append(bit); 222 | waveformData.append(*it); 223 | parity ^= bit; 224 | bit ^= bit; 225 | }; 226 | 227 | //"0" - ZERO 228 | if (isZeroFreqFitsInDelta(sampleRate, len, parserSettings.zeroFreq, parserSettings.zeroDelta, HARDCODED_DATA_SIGNAL_DELTA) && isSineNormal(*prevIt, *it, true)) { 229 | //Mark parsed waveform as "0"-bit and sets the begin and end bounds 230 | parsedData.fillParsedWaveform(*prevIt, *it, ParsedData::zeroBit | ParsedData::sequenceMiddle, 231 | ParsedData::zeroBit | ParsedData::sequenceBegin | (bitIndex == 0 ? ParsedData::byteBound : 0), 232 | ParsedData::zeroBit | ParsedData::sequenceEnd | (bitIndex == 7 ? ParsedData::byteBound : 0)); 233 | 234 | if (bitIndex == 0) { 235 | data_mapping.insert((*prevIt).begin, data.size()); 236 | } 237 | 238 | if (bitIndex++ == 7) { 239 | data_mapping.insert((*it).end, data.size()); 240 | storeParsedByte(); 241 | // //Update byte bound mark 242 | // parsedData.orParsedWaveform(it->end, ParsedData::byteBound); 243 | // //Store parsed byte in data buffer 244 | // bitIndex ^= bitIndex; 245 | // data.append(bit); 246 | // waveformData.append(*it); 247 | // parity ^= bit; 248 | // bit ^= bit; 249 | } 250 | } 251 | // "1" - ONE 252 | else if (isOneFreqFitsInDelta(sampleRate, len, parserSettings.oneFreq, HARDCODED_DATA_SIGNAL_DELTA, parserSettings.oneDelta) && isSineNormal(*prevIt, *it, false)) { 253 | //Mark parsed waveform as "1"-bit and sets the begin and end bounds 254 | parsedData.fillParsedWaveform(*prevIt, *it, ParsedData::oneBit | ParsedData::sequenceMiddle, 255 | ParsedData::oneBit | ParsedData::sequenceBegin | (bitIndex == 0 ? ParsedData::byteBound : 0), 256 | ParsedData::oneBit | ParsedData::sequenceEnd | (bitIndex == 7 ? ParsedData::byteBound : 0)); 257 | 258 | if (bitIndex == 0) { 259 | data_mapping.insert((*prevIt).begin, data.size()); 260 | } 261 | 262 | //Set the currently parsed bit 263 | bit |= 1 << (7 - bitIndex); 264 | if (bitIndex++ == 7) { 265 | data_mapping.insert((*it).end, data.size()); 266 | storeParsedByte(); 267 | // //Update byte bound mark 268 | // parsedData.orParsedWaveform(it->end, ParsedData::byteBound); 269 | // //Store parsed byte in data buffer 270 | // bitIndex ^= bitIndex; 271 | // data.append(bit); 272 | // waveformData.append(*it); 273 | // parity ^= bit; 274 | // bit ^= bit; 275 | } 276 | } 277 | else { 278 | currentState = END_OF_DATA; 279 | if (!data.empty()) { 280 | parity ^= data.last(); //Removing parity byte from overal parity check sum 281 | //Storing parsed data 282 | parsedData.storeData(std::move(data), std::move(data_mapping), parsed.at(dataStart).begin, parsed.at(std::distance(parsed.begin(), it)).end, std::move(waveformData), parity); 283 | parity ^= parity; //Zeroing parity byte 284 | } 285 | } 286 | it = std::next(it); 287 | } 288 | else if (!data.empty()) { 289 | parity ^= data.last(); //Remove parity byte from overal parity check sum 290 | //Storing parsed data 291 | parsedData.storeData(std::move(data), std::move(data_mapping), parsed.at(dataStart).begin, parsed.at(parsed.size() - 1).end, std::move(waveformData), parity); 292 | parity ^= parity; //Zeroing parity byte 293 | } 294 | break; 295 | 296 | case END_OF_DATA: 297 | currentState = SEARCH_OF_PILOT_TONE; 298 | break; 299 | 300 | default: 301 | break; 302 | } 303 | 304 | if (it == parsed.end()) { 305 | currentState = NO_MORE_DATA; 306 | } 307 | } 308 | 309 | if (chNum == 0) { 310 | emit parsedChannel0Changed(); 311 | } 312 | else { 313 | emit parsedChannel1Changed(); 314 | } 315 | } 316 | 317 | void WaveformParser::saveTap(uint chNum, const QString& fileName) 318 | { 319 | auto parsedDataPtr { getParsedDataPtr(chNum) }; 320 | if (parsedDataPtr == nullptr) { 321 | return; 322 | } 323 | 324 | QFile f(fileName.isEmpty() ? QString("tape_%1_%2.tap").arg(QDateTime::currentDateTime().toString("dd.MM.yyyy hh-mm-ss.zzz")).arg(chNum ? "R" : "L") : fileName); 325 | f.remove(); //Remove file if exists 326 | f.open(QIODevice::WriteOnly); 327 | 328 | auto parsedDataSPtr { parsedDataPtr->getParsedData() }; 329 | auto& parsedData = *parsedDataSPtr.data(); 330 | for (auto i = 0; i < parsedData.size(); ++i) { 331 | if (i < mSelectedBlocks.size() && !mSelectedBlocks[i]) { 332 | continue; 333 | } 334 | 335 | QByteArray b; 336 | const auto& data { parsedData.at(i).data }; 337 | const uint16_t size = data.size(); 338 | b.append(reinterpret_cast(&size), sizeof(size)); 339 | b.append(reinterpret_cast(data.data()), size); 340 | f.write(b); 341 | } 342 | 343 | f.close(); 344 | } 345 | 346 | QVector WaveformParser::getParsedWaveform(uint chNum) const { 347 | auto parsedDataPtr { getParsedDataPtr(chNum) }; 348 | if (parsedDataPtr == nullptr) { 349 | return {}; 350 | } 351 | return *parsedDataPtr->getParsedWaveform();// mParsedWaveform[chNum]; 352 | } 353 | 354 | QPair, QVector> WaveformParser::getParsedData(uint chNum) const { 355 | auto parsedDataPtr { getParsedDataPtr(chNum) }; 356 | if (parsedDataPtr == nullptr) { 357 | return {}; 358 | } 359 | return { *parsedDataPtr->getParsedData(), mSelectedBlocks }; 360 | } 361 | 362 | void WaveformParser::toggleBlockSelection(int blockNum) { 363 | if (blockNum < mSelectedBlocks.size()) { 364 | auto& blk = mSelectedBlocks[blockNum]; 365 | blk = !blk; 366 | 367 | emit parsedChannel0Changed(); 368 | emit parsedChannel1Changed(); 369 | } 370 | } 371 | 372 | int WaveformParser::getBlockDataStart(uint chNum, uint blockNum) const 373 | { 374 | auto parsedDataPtr { getParsedDataPtr(chNum) }; 375 | if (parsedDataPtr == nullptr) { 376 | return 0; 377 | } 378 | auto parsedDataSPtr { parsedDataPtr->getParsedData() }; 379 | auto& parsedData { *parsedDataSPtr }; 380 | 381 | if (blockNum < (unsigned) parsedData.size()) { 382 | return parsedData[blockNum].dataStart; 383 | } 384 | return 0; 385 | } 386 | 387 | int WaveformParser::getBlockDataEnd(uint chNum, uint blockNum) const 388 | { 389 | auto parsedDataPtr { getParsedDataPtr(chNum) }; 390 | if (parsedDataPtr == nullptr) { 391 | return 0; 392 | } 393 | auto parsedDataSPtr { parsedDataPtr->getParsedData() }; 394 | auto& parsedData { *parsedDataSPtr }; 395 | 396 | if (blockNum < (unsigned) parsedData.size()) { 397 | return parsedData[blockNum].dataEnd; 398 | } 399 | return 0; 400 | } 401 | 402 | int WaveformParser::getPositionByAddress(uint chNum, uint blockNum, uint addr) const 403 | { 404 | auto parsedDataPtr { getParsedDataPtr(chNum) }; 405 | if (parsedDataPtr == nullptr) { 406 | return 0; 407 | } 408 | auto parsedDataSPtr { parsedDataPtr->getParsedData() }; 409 | auto& parsedData { *parsedDataSPtr }; 410 | 411 | if (blockNum < (unsigned) parsedData.size() && addr < (unsigned) parsedData[blockNum].waveformData.size()) { 412 | return parsedData[blockNum].waveformData[addr].begin; 413 | } 414 | return 0; 415 | } 416 | 417 | QVariantList WaveformParser::getParsedChannelData(uint chNum) const 418 | { 419 | auto parsedDataPtr { getParsedDataPtr(chNum) }; 420 | if (parsedDataPtr == nullptr) { 421 | return {}; 422 | } 423 | auto parsedDataSPtr { parsedDataPtr->getParsedData() }; 424 | auto& parsedData { *parsedDataSPtr }; 425 | 426 | static QMap blockTypes { 427 | {0x00, "Program"}, 428 | {0x01, "Number Array"}, 429 | {0x02, "Character Array"}, 430 | {0x03, "Bytes"} 431 | }; 432 | const QString id_header { qtTrId(ID_HEADER) }; 433 | const QString id_code { qtTrId(ID_CODE) }; 434 | const QString id_ok { qtTrId(ID_OK) }; 435 | const QString id_error { qtTrId(ID_ERROR) }; 436 | const QString id_unknown { qtTrId(ID_UNKNOWN) }; 437 | 438 | QVariantList r; 439 | uint blockNumber = 0; 440 | 441 | for (const auto& i: parsedData) { 442 | QVariantMap m; 443 | 444 | m.insert("block", QVariantMap { {"blockSelected", blockNumber < (unsigned) mSelectedBlocks.size() ? mSelectedBlocks[blockNumber] : (mSelectedBlocks.append(true), true)}, {"blockNumber", blockNumber++} }); 445 | if (i.data.size() > 0) { 446 | auto d = i.data.at(0); 447 | int blockType = -1; 448 | auto btIt = blockTypes.end(); 449 | QString blockTypeName; 450 | if (d == 0x00 && i.data.size() > 1) { 451 | d = i.data.at(1); 452 | btIt = blockTypes.find(d); 453 | blockType = btIt == blockTypes.end() ? -1 : d; 454 | blockTypeName = blockType == -1 ? QString::number(d, 16) : *btIt; 455 | } 456 | else { 457 | blockType = -2; 458 | blockTypeName = d == 0x00 ? id_header : id_code; 459 | } 460 | m.insert("blockType", blockTypeName); 461 | QString sizeText = QString::number(i.data.size()); 462 | if (i.data.size() > 13 && btIt != blockTypes.end()) { 463 | sizeText += QString(" (%1)").arg(i.data.at(13) * 256 + i.data.at(12)); 464 | } 465 | m.insert("blockSize", sizeText); 466 | QString nameText; 467 | if (blockType >= 0) { 468 | const auto loopRange { std::min(decltype(i.data.size())(12), i.data.size()) }; 469 | nameText = QByteArray((const char*) &i.data.data()[2], loopRange > 1 ? loopRange - 2 : 0); 470 | } 471 | m.insert("blockName", nameText); 472 | m.insert("blockStatus", (i.state == ParsedData::OK ? id_ok : id_error) + qtTrId(ID_PARITY_MESSAGE).arg(QString::number(i.parityCalculated, 16).toUpper().rightJustified(2, '0')).arg(QString::number(i.parityAwaited, 16).toUpper().rightJustified(2, '0'))); 473 | m.insert("state", i.state); 474 | } 475 | else { 476 | m.insert("blockType", id_unknown); 477 | m.insert("blockName", QString()); 478 | m.insert("blockSize", 0); 479 | m.insert("blockStatus", id_unknown); 480 | } 481 | r.append(m); 482 | } 483 | 484 | return r; 485 | } 486 | 487 | QVariantList WaveformParser::getParsedChannel0() const 488 | { 489 | return getParsedChannelData(0); 490 | } 491 | 492 | QVariantList WaveformParser::getParsedChannel1() const 493 | { 494 | return getParsedChannelData(1); 495 | } 496 | 497 | WaveformParser* WaveformParser::instance() 498 | { 499 | static QScopedPointer p { new WaveformParser() }; 500 | return p.get(); 501 | } 502 | --------------------------------------------------------------------------------