├── 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 |
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 |
25 | Measured frequency:Measured frequency:
26 |
27 | Please enter address:Please enter address:
28 | HexadecimalHexadecimal
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
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 |
107 | CodeCode
108 |
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 |
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 |
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 |
25 | Measured frequency:Измеренная частота:
26 |
27 | Please enter address:Пожалуйста введите адрес:
28 | HexadecimalВ шестнадцатеричном виде
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
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 |
107 | CodeCode
108 |
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 |
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 |
--------------------------------------------------------------------------------