diff --git a/ZXTapeReviver.pro b/ZXTapeReviver.pro
index 186da26..7389be2 100644
--- a/ZXTapeReviver.pro
+++ b/ZXTapeReviver.pro
@@ -19,14 +19,16 @@ SOURCES += \
sources/models/fileworkermodel.cpp \
sources/controls/waveformcontrol.cpp \
sources/core/waveformparser.cpp \
- sources/core/wavreader.cpp
+ sources/core/wavreader.cpp \
+ sources/models/suspiciouspointsmodel.cpp
HEADERS += \
sources/defines.h \
sources/models/fileworkermodel.h \
sources/controls/waveformcontrol.h \
sources/core/waveformparser.h \
- sources/core/wavreader.h
+ sources/core/wavreader.h \
+ sources/models/suspiciouspointsmodel.h
RESOURCES += qml/qml.qrc
diff --git a/qml/GoToAddress.qml b/qml/GoToAddress.qml
new file mode 100644
index 0000000..7152682
--- /dev/null
+++ b/qml/GoToAddress.qml
@@ -0,0 +1,61 @@
+//*******************************************************************************
+// ZX Tape Reviver
+//-----------------
+//
+// Author: Leonid Golouz
+// E-mail: lgolouz@list.ru
+//*******************************************************************************
+
+import QtQuick 2.3
+import QtQuick.Controls 1.3
+import QtQuick.Dialogs 1.3
+
+Dialog {
+ id: gotoAddressDialog
+
+ signal gotoAddress(int adr);
+
+ visible: false
+ title: "Go to address..."
+ standardButtons: StandardButton.Ok | StandardButton.Cancel
+ modality: Qt.WindowModal
+ width: 200
+ height: 120
+
+ Text {
+ id: textWithField
+ text: "Please enter address:"
+ }
+
+ TextField {
+ id: textField
+ anchors.top: textWithField.bottom
+ width: parent.width
+ }
+
+ CheckBox {
+ id: hexCheckbox
+
+ anchors.top: textField.bottom
+ anchors.topMargin: 3
+ checked: true
+ text: "Hexadecimal"
+ }
+
+ Text {
+ id: conversionField
+
+ function convertAddress(adr) {
+ return hexCheckbox.checked ? parseInt(adr, 16) : "0x" + parseInt(adr, 10).toString(16).toUpperCase();
+ }
+
+ text: "(" + convertAddress(textField.text) + ")"
+ anchors.left: hexCheckbox.right
+ anchors.leftMargin: 3
+ anchors.top: hexCheckbox.top
+ }
+
+ onAccepted: {
+ gotoAddress(parseInt(textField.text, hexCheckbox.checked ? 16 : 10));
+ }
+}
diff --git a/qml/main.qml b/qml/main.qml
index c349c40..d0716a7 100644
--- a/qml/main.qml
+++ b/qml/main.qml
@@ -20,7 +20,7 @@ ApplicationWindow {
id: mainWindow
readonly property int mainAreaWidth: width * 0.75
- property ListModel suspiciousPoints: ListModel { }
+ property var suspiciousPoints: SuspiciousPointsModel.suspiciousPoints
visible: true
width: 1600
@@ -31,26 +31,20 @@ ApplicationWindow {
return wfWidth * wfXScale / 2;
}
- function addSuspiciousPoint(idx) {
- console.log("Adding suspicious point: " + idx);
- for (var i = 0; i < suspiciousPoints.count; ++i) {
- var p = suspiciousPoints.get(i).idx;
- if (p === idx) {
- return;
- }
- else if (p > idx) {
- suspiciousPoints.insert(i, {idx: idx});
- return;
- }
- }
-
- suspiciousPoints.append({idx: idx});
- }
-
function getSelectedWaveform() {
return channelsComboBox.currentIndex == 0 ? waveformControlCh0 : waveformControlCh1;
}
+ function restoreWaveformView() {
+ waveformControlCh0.xScaleFactor = 1;
+ waveformControlCh0.yScaleFactor = 80000;
+ waveformControlCh0.wavePos = 0;
+
+ waveformControlCh1.xScaleFactor = 1;
+ waveformControlCh1.yScaleFactor = 80000;
+ waveformControlCh1.wavePos = 0;
+ }
+
menuBar: MenuBar {
Menu {
title: "File"
@@ -59,6 +53,16 @@ ApplicationWindow {
text: "Open WAV file..."
onTriggered: {
console.log("Opening WAV file");
+ openFileDialog.isWavOpening = true;
+ openFileDialog.open();
+ }
+ }
+
+ MenuItem {
+ text: "Open Waveform file..."
+ onTriggered: {
+ console.log("Opening Waveform file");
+ openFileDialog.isWavOpening = false;
openFileDialog.open();
}
}
@@ -116,15 +120,9 @@ ApplicationWindow {
title: "Waveform"
MenuItem {
- text: "Restore"
+ text: "Restore view"
onTriggered: {
- waveformControlCh0.xScaleFactor = 1;
- waveformControlCh0.yScaleFactor = 80000;
- waveformControlCh0.wavePos = 0;
-
- waveformControlCh1.xScaleFactor = 1;
- waveformControlCh1.yScaleFactor = 80000;
- waveformControlCh1.wavePos = 0;
+ restoreWaveformView();
}
}
@@ -137,15 +135,27 @@ ApplicationWindow {
FileDialog {
id: openFileDialog
+ property bool isWavOpening: true
+
title: "Please choose WAV file"
selectMultiple: false
sidebarVisible: true
- defaultSuffix: "wav"
- nameFilters: [ "WAV files (*.wav)" ]
+ defaultSuffix: isWavOpening ? "wav" : "wfm"
+ nameFilters: isWavOpening ? [ "WAV files (*.wav)" ] : [ "Waveform files (*.wfm)" ]
onAccepted: {
- console.log("Selected WAV file: " + openFileDialog.fileUrl);
- console.log("Open WAV file result: " + FileWorkerModel.openWavFileByUrl(openFileDialog.fileUrl));
+ var filetype = isWavOpening ? "WAV" : "Waveform";
+ console.log("Selected %1 file: ".arg(filetype) + openFileDialog.fileUrl);
+ var res = (isWavOpening
+ ? FileWorkerModel.openWavFileByUrl(openFileDialog.fileUrl)
+ : FileWorkerModel.openWaveformFileByUrl(openFileDialog.fileUrl));
+ console.log("Open %1 file result: ".arg(filetype) + res);
+ if (res === 0) {
+ if (isWavOpening) {
+ SuspiciousPointsModel.clearSuspiciousPoints();
+ }
+ restoreWaveformView();
+ }
}
onRejected: {
@@ -174,9 +184,11 @@ ApplicationWindow {
else {
waveformControlCh1.saveTap(saveFileDialog.fileUrl);
}
+ console.log("Tap saved: " + saveFileDialog.fileUrl)
}
else {
- //wavReader.saveWaveform();
+ FileWorkerModel.saveWaveformFileByUrl(saveFileDialog.fileUrl);
+ console.log("Waveform saved: " + saveFileDialog.fileUrl);
}
}
}
@@ -215,7 +227,7 @@ ApplicationWindow {
height: parent.height - parent.height / 2 - parent.spacerHeight / 2
onDoubleClick: {
- addSuspiciousPoint(idx);
+ SuspiciousPointsModel.addSuspiciousPoint(idx);
}
}
@@ -232,7 +244,7 @@ ApplicationWindow {
height: waveformControlCh0.height
onDoubleClick: {
- addSuspiciousPoint(idx);
+ SuspiciousPointsModel.addSuspiciousPoint(idx);
}
}
@@ -430,6 +442,23 @@ ApplicationWindow {
}
}
+ Button {
+ id: gotoAddressButton
+
+ text: "Goto address..."
+ anchors {
+ top: shiftWaveform.bottom
+ topMargin: 5
+ right: parent.right
+ rightMargin: 5
+ }
+ width: hZoomOutButton.width
+
+ onClicked: {
+ gotoAddressDialogLoader.item.open();
+ }
+ }
+
Button {
id: selectionModeToggleButton
@@ -525,7 +554,7 @@ ApplicationWindow {
idx = 0;
}
- waveformControlCh0.wavePos = waveformControlCh1.wavePos = idx ;
+ waveformControlCh0.wavePos = waveformControlCh1.wavePos = idx;
}
}
}
@@ -617,11 +646,11 @@ ApplicationWindow {
text: "Goto Suspicious point"
onClicked: {
- if (suspiciousPointsView.currentRow < 0 || suspiciousPointsView.currentRow >= suspiciousPoints.count) {
+ if (suspiciousPointsView.currentRow < 0 || suspiciousPointsView.currentRow >= SuspiciousPointsModel.size) {
return;
}
- var idx = suspiciousPoints.get(suspiciousPointsView.currentRow).idx - getWaveShiftIndex(waveformControlCh0.width, waveformControlCh0.xScaleFactor);
+ var idx = SuspiciousPointsModel.getSuspiciousPoint(suspiciousPointsView.currentRow) - getWaveShiftIndex(waveformControlCh0.width, waveformControlCh0.xScaleFactor);
if (idx < 0) {
idx = 0;
}
@@ -645,9 +674,9 @@ ApplicationWindow {
text: "Remove Suspicious point"
onClicked: {
- if (suspiciousPointsView.currentRow >= 0 && suspiciousPointsView.currentRow < suspiciousPoints.count) {
- console.log("Removing suspicious point: " + suspiciousPoints.get(suspiciousPointsView.currentRow).idx);
- suspiciousPoints.remove(suspiciousPointsView.currentRow);
+ if (suspiciousPointsView.currentRow >= 0 && suspiciousPointsView.currentRow < SuspiciousPointsModel.size) {
+ console.log("Removing suspicious point: " + SuspiciousPointsModel.getSuspiciousPoint(suspiciousPointsView.currentRow));
+ SuspiciousPointsModel.removeSuspiciousPoint(suspiciousPointsView.currentRow);
}
}
}
@@ -680,4 +709,25 @@ ApplicationWindow {
}
}
}
+
+ Loader {
+ id: gotoAddressDialogLoader
+ source: "GoToAddress.qml"
+ }
+
+ Connections {
+ target: gotoAddressDialogLoader.item
+ function onGotoAddress(adr) {
+ console.log("Goto address: " + adr);
+ var pos = WaveformParser.getPositionByAddress(channelsComboBox.currentIndex, parsedDataView.currentRow, adr);
+ if (pos !== 0) {
+ var idx = pos - getWaveShiftIndex(waveformControlCh0.width, waveformControlCh0.xScaleFactor);
+ if (idx < 0) {
+ idx = 0;
+ }
+
+ waveformControlCh0.wavePos = waveformControlCh1.wavePos = idx;
+ }
+ }
+ }
}
diff --git a/qml/qml.qrc b/qml/qml.qrc
index 5f6483a..f939205 100644
--- a/qml/qml.qrc
+++ b/qml/qml.qrc
@@ -1,5 +1,6 @@
main.qml
+ GoToAddress.qml
diff --git a/sources/controls/waveformcontrol.cpp b/sources/controls/waveformcontrol.cpp
index 7eebb4f..8cda5d0 100644
--- a/sources/controls/waveformcontrol.cpp
+++ b/sources/controls/waveformcontrol.cpp
@@ -403,7 +403,7 @@ void WaveformControl::repairWaveform()
{
if (!m_isWaveformRepaired) {
//mWavReader.repairWaveform(m_channelNumber);
- mWavReader.normalizeWaveform(0);
+ mWavReader.normalizeWaveform2(m_channelNumber);
update();
m_isWaveformRepaired = true;
emit isWaveformRepairedChanged();
diff --git a/sources/core/waveformparser.cpp b/sources/core/waveformparser.cpp
index 6a7db2b..5af5bc6 100644
--- a/sources/core/waveformparser.cpp
+++ b/sources/core/waveformparser.cpp
@@ -29,12 +29,6 @@ void WaveformParser::parse(uint chNum)
QVector parsed = parseChannel(channel);
const double sampleRate = mWavReader.getSampleRate();
- auto isFreqFitsInDelta = [&sampleRate](uint32_t length, uint32_t signalFreq, double signalDelta, double deltaDivider = 1.0) -> bool {
- const double freq = (sampleRate / length);
- const double delta = signalFreq * (signalDelta / deltaDivider);
- return freq >= (signalFreq - delta) && freq <= (signalFreq + delta);
- };
-
auto& parsedData = mParsedData[chNum];
parsedData.clear();
@@ -44,6 +38,7 @@ void WaveformParser::parse(uint chNum)
auto currentState = SEARCH_OF_PILOT_TONE;
auto it = parsed.begin();
QVector data;
+ QVector waveformData;
//WaveformSign signalDirection = POSITIVE;
uint32_t dataStart = 0;
int8_t bitIndex = 7;
@@ -54,9 +49,9 @@ void WaveformParser::parse(uint chNum)
auto prevIt = it;
switch (currentState) {
case SEARCH_OF_PILOT_TONE:
- it = std::find_if(it, parsed.end(), [&isFreqFitsInDelta, chNum, this](const WaveformPart& p) {
+ it = std::find_if(it, parsed.end(), [sampleRate, chNum, this](const WaveformPart& p) {
fillParsedWaveform(chNum, p, 0);
- return isFreqFitsInDelta(p.length, SignalFrequencies::PILOT_HALF_FREQ, pilotDelta, 2.0);
+ return isFreqFitsInDelta(sampleRate, p.length, SignalFrequencies::PILOT_HALF_FREQ, pilotDelta, 2.0);
});
if (it != parsed.end()) {
currentState = PILOT_TONE;
@@ -64,9 +59,9 @@ void WaveformParser::parse(uint chNum)
break;
case PILOT_TONE:
- it = std::find_if(it, parsed.end(), [&isFreqFitsInDelta, chNum, this](const WaveformPart& p) {
+ it = std::find_if(it, parsed.end(), [sampleRate, chNum, this](const WaveformPart& p) {
fillParsedWaveform(chNum, p, pilotTone ^ sequenceMiddle);
- return isFreqFitsInDelta(p.length, SignalFrequencies::SYNCHRO_FIRST_HALF_FREQ, synchroDelta);
+ return isFreqFitsInDelta(sampleRate, p.length, SignalFrequencies::SYNCHRO_FIRST_HALF_FREQ, synchroDelta);
});
if (it != parsed.end()) {
auto eIt = std::prev(it);
@@ -89,7 +84,7 @@ void WaveformParser::parse(uint chNum)
case SYNCHRO_SIGNAL:
it = std::next(it);
if (it != parsed.end()) {
- if ((isFreqFitsInDelta(it->length, SignalFrequencies::SYNCHRO_SECOND_HALF, synchroDelta)) && (isFreqFitsInDelta(it->length + prevIt->length, SignalFrequencies::SYNCHRO_FREQ, synchroDelta, 2.0))) {
+ if ((isFreqFitsInDelta(sampleRate, it->length, SignalFrequencies::SYNCHRO_SECOND_HALF, synchroDelta)) && (isFreqFitsInDelta(sampleRate, it->length + prevIt->length, SignalFrequencies::SYNCHRO_FREQ, synchroDelta, 2.0))) {
fillParsedWaveform(chNum, *prevIt, synchroSignal ^ sequenceMiddle);
fillParsedWaveform(chNum, *it, synchroSignal ^ sequenceMiddle);
parsedWaveform[prevIt->begin] = synchroSignal ^ sequenceBegin;
@@ -109,6 +104,7 @@ void WaveformParser::parse(uint chNum)
it = std::next(it);
dataStart = std::distance(parsed.begin(), it) + 1;
data.clear();
+ waveformData.clear();
bitIndex = 7;
bit ^= bit;
}
@@ -122,7 +118,7 @@ void WaveformParser::parse(uint chNum)
it = std::next(it);
if (it != parsed.end()) {
const auto len = it->length + prevIt->length;
- if (isFreqFitsInDelta(len, SignalFrequencies::ZERO_FREQ, zeroDelta)) { //ZERO
+ if (isFreqFitsInDelta(sampleRate, len, SignalFrequencies::ZERO_FREQ, zeroDelta)) { //ZERO
fillParsedWaveform(chNum, *prevIt, zeroBit ^ sequenceMiddle);
fillParsedWaveform(chNum, *it, zeroBit ^ sequenceMiddle);
parsedWaveform[prevIt->begin] = zeroBit ^ sequenceBegin;
@@ -145,11 +141,12 @@ void WaveformParser::parse(uint chNum)
parsedWaveform[it->end] ^= byteBound;
bitIndex = 7;
data.append(bit);
+ waveformData.append(*it);
parity ^= bit;
bit ^= bit;
}
}
- else if (isFreqFitsInDelta(len, SignalFrequencies::ONE_FREQ, oneDelta)) { //ONE
+ else if (isFreqFitsInDelta(sampleRate, len, SignalFrequencies::ONE_FREQ, oneDelta)) { //ONE
fillParsedWaveform(chNum, *prevIt, oneBit ^ sequenceMiddle);
fillParsedWaveform(chNum, *it, oneBit ^ sequenceMiddle);
parsedWaveform[prevIt->begin] = oneBit ^ sequenceBegin;
@@ -168,6 +165,7 @@ void WaveformParser::parse(uint chNum)
parsedWaveform[it->end] ^= byteBound;
bitIndex = 7;
data.append(bit);
+ waveformData.append(*it);
parity ^= bit;
bit ^= bit;
}
@@ -179,6 +177,7 @@ void WaveformParser::parse(uint chNum)
db.dataStart = parsed.at(dataStart).begin;
db.dataEnd = parsed.at(std::distance(parsed.begin(), it)).end;
db.data = data;
+ db.waveformData = waveformData;
parity ^= data.last(); //Removing parity byte from overal parity check sum
db.state = parity == data.last() ? DataState::OK : DataState::R_TAPE_LOADING_ERROR; //Should be checked with checksum
parity ^= parity; //Zeroing parity byte
@@ -193,6 +192,7 @@ void WaveformParser::parse(uint chNum)
db.dataStart = parsed.at(dataStart).begin;
db.dataEnd = parsed.at(parsed.size() - 1).end;
db.data = data;
+ db.waveformData = waveformData;
parity ^= data.last(); //Remove parity byte from overal parity check sum
db.state = parity == data.last() ? DataState::OK : DataState::R_TAPE_LOADING_ERROR; //Should be checked with checksum
parity ^= parity; //Zeroing parity byte
@@ -271,6 +271,14 @@ int WaveformParser::getBlockDataEnd(uint chNum, uint blockNum) const
return 0;
}
+int WaveformParser::getPositionByAddress(uint chNum, uint blockNum, uint addr) const
+{
+ if (chNum < mParsedData.size() && blockNum < mParsedData[chNum].size() && addr < mParsedData[chNum][blockNum].waveformData.size()) {
+ return mParsedData[chNum][blockNum].waveformData.at(addr).end;
+ }
+ return 0;
+}
+
QVariantList WaveformParser::getParsedChannelData(uint chNum) const
{
static QMap blockTypes {
diff --git a/sources/core/waveformparser.h b/sources/core/waveformparser.h
index 6af8810..e0143aa 100644
--- a/sources/core/waveformparser.h
+++ b/sources/core/waveformparser.h
@@ -57,24 +57,8 @@ const uint8_t sequenceEnd = 0b00000001; //end of signal sequence
private:
enum WaveformSign { POSITIVE, NEGATIVE };
enum StateType { SEARCH_OF_PILOT_TONE, PILOT_TONE, SYNCHRO_SIGNAL, DATA_SIGNAL, END_OF_DATA, NO_MORE_DATA };
- enum SignalFrequencies {
- PILOT_HALF_FREQ = 1620,
- PILOT_FREQ = 810,
- SYNCHRO_FIRST_HALF_FREQ = 4900,
- SYNCHRO_SECOND_HALF = 5500,
- SYNCHRO_FREQ = 2600,
- ZERO_HALF_FREQ = 4090,
- ZERO_FREQ = 2050,
- ONE_HALF_FREQ = 2045,
- ONE_FREQ = 1023
- };
enum DataState { OK, R_TAPE_LOADING_ERROR };
- const double pilotDelta = 0.1;
- const double synchroDelta = 0.3;
- const double zeroDelta = 0.3;//0.3;//0.18;
- const double oneDelta = 0.25;//0.25;//0.1;
-
struct WaveformPart
{
uint32_t begin;
@@ -88,6 +72,7 @@ const uint8_t sequenceEnd = 0b00000001; //end of signal sequence
uint32_t dataStart;
uint32_t dataEnd;
QVector data;
+ QVector waveformData;
DataState state;
};
@@ -141,7 +126,7 @@ const uint8_t sequenceEnd = 0b00000001; //end of signal sequence
Q_INVOKABLE int getBlockDataStart(uint chNum, uint blockNum) const;
Q_INVOKABLE int getBlockDataEnd(uint chNum, uint blockNum) const;
-
+ Q_INVOKABLE int getPositionByAddress(uint chNum, uint blockNum, uint addr) const;
//getters
QVariantList getParsedChannel0() const;
QVariantList getParsedChannel1() const;
diff --git a/sources/core/wavreader.cpp b/sources/core/wavreader.cpp
index 3c873d6..ac77de8 100644
--- a/sources/core/wavreader.cpp
+++ b/sources/core/wavreader.cpp
@@ -7,6 +7,7 @@
//*******************************************************************************
#include "wavreader.h"
+#include "sources/models/suspiciouspointsmodel.h"
#include
#include
#include
@@ -83,7 +84,7 @@ WavReader::ErrorCodesEnum WavReader::open()
return AlreadyOpened;
}
-QWavVectorType WavReader::getSample(QByteArray& buf, size_t& bufIndex, uint dataSize, uint compressionCode)
+QWavVectorType WavReader::getSample(QByteArray& buf, size_t& bufIndex, uint dataSize, uint compressionCode) const
{
QWavVectorType r { };
void* v;
@@ -198,17 +199,66 @@ WavReader::ErrorCodesEnum WavReader::close()
return Ok;
}
-void WavReader::saveWaveform() const
+void WavReader::loadWaveform(const QString& fname)
{
- QFile f(QString("waveform_%1.wfm").arg(QDateTime::currentDateTime().toString("dd.MM.yyyy hh-mm-ss.zzz")));
+ QFile f(fname);
+ f.open(QIODevice::ReadOnly);
+ QByteArray b(f.read(f.size()));
+ size_t idx = 0;
+ //Get header
+ mWavFormatHeader = *getData(b, idx);
+ for (auto i = 0; i < mWavFormatHeader.numberOfChannels; ++i) {
+ //Get channel length
+ const int32_t l { *getData(b, idx) };
+ auto& ch = i == 0 ? mChannel0 : mChannel1;
+ ch.reset(new QWavVector(l));
+ //Fill channel data
+ for (auto& v: *ch.get()) {
+ v = *getData(b, idx);
+ }
+ }
+
+ //Restore suspicious points
+ const int32_t l { *getData(b, idx) };
+ QVariantList sp;
+ for (auto i = 0; i < l; ++i) {
+ sp.append(*getData(b, idx));
+ }
+ SuspiciousPointsModel::instance()->setSuspiciousPoints(sp);
+
+ f.close();
+ mWavOpened = true;
+}
+
+void WavReader::saveWaveform(const QString& fname) const
+{
+ QFile f(fname.isEmpty() ? QString("waveform_%1.wfm").arg(QDateTime::currentDateTime().toString("dd.MM.yyyy hh-mm-ss.zzz")) : fname);
f.open(QIODevice::ReadWrite);
- const auto& ch = *getChannel0();
+ //Store header
QByteArray b;
- for (auto i = 0; i < ch.size(); ++i) {
- const QWavVectorType val = ch[i];
- b.append(reinterpret_cast(&val), sizeof(val));
+ appendData(b, mWavFormatHeader);
+
+ for (auto i = 0; i < mWavFormatHeader.numberOfChannels; ++i) {
+ const auto& ch = i == 0 ? *getChannel0() : *getChannel1();
+ //Store channel length
+ const int32_t l = ch.length();
+ appendData(b, l);
+ //Store channel
+ for (const auto& v: ch) {
+ appendData(b, v);
+ }
+ }
+
+ //Store suspicious points
+ const auto s = SuspiciousPointsModel::instance()->getSuspiciousPoints();
+ const int32_t l = s.length();
+ appendData(b, l);
+ for (const auto& p: s) {
+ uint32_t sp = p.toUInt();
+ appendData(b, sp);
}
+
f.write(b);
f.close();
}
@@ -224,7 +274,7 @@ void WavReader::shiftWaveform(uint chNum)
auto& ch = chNum == 0 ? mChannel0 : mChannel1;
storedCh.reset(new QWavVector(*ch.get()));
for (auto i = 0; i < mChannel0->size(); ++i) {
- ch->operator[](i) -= 1000;
+ ch->operator[](i) -= 1300;
}
}
@@ -328,6 +378,89 @@ void WavReader::normalizeWaveform(uint chNum)
}
}
+void WavReader::normalizeWaveform2(uint chNum)
+{
+ if (chNum >= mWavFormatHeader.numberOfChannels) {
+ qDebug() << "Channel number exceeds number of channels";
+ return;
+ }
+
+ auto& ch = *(chNum == 0 ? mChannel0 : mChannel1).get();
+ auto haveSameSign = [](QWavVectorType o1, QWavVectorType o2) {
+ return lessThanZero(o1) == lessThanZero(o2);
+ };
+
+ //Trying to find a sine
+ auto bIt = ch.begin();
+
+ while (bIt != ch.end()) {
+ auto prevIt = bIt;
+ auto it = std::next(prevIt);
+ QMap::iterator> peaks {{0, bIt}};
+
+ for (int i = 1; i < 4; ++i) {
+ //down-to-up part when i == 1, 3
+ //up-to-down part when i == 2
+ bool finished = true;
+ for (; it != ch.end();) {
+ if (haveSameSign(*prevIt, *it)) {
+ if ((i == 2 ? std::abs(*prevIt) >= std::abs(*it) : std::abs(*prevIt) <= std::abs(*it))) {
+ prevIt = it;
+ it = std::next(it);
+ }
+ else {
+ auto itNext = std::next(it);
+ if (itNext != ch.end() && ((i == 2 ? std::abs(*prevIt) >= std::abs(*itNext) : std::abs(*prevIt) <= std::abs(*itNext)))) {
+ prevIt = it;
+ it = itNext;
+ }
+ else {
+ peaks[i] = it;
+ break;
+ }
+ }
+ }
+ else {
+ bIt = it;
+ finished = false;
+ it = ch.end();
+ }
+ }
+
+ //Signal crosses zero level - not ours case
+ if (it == ch.end()) {
+ if (finished) {
+ bIt = it;
+ }
+ break;
+ }
+ }
+
+ //Looks like we've found a sine, normalizing it
+ if (it != ch.end()) {
+ bIt = it;
+ double freq = getSampleRate() / std::distance(peaks[0], peaks[3]);
+ if (freq <= ZERO_HALF_FREQ) {
+ auto it = peaks[2];
+ for (int i = 0; i < 2 && it != ch.end(); ++i, ++it) {
+ auto val = *it;
+ *it = val >= 0 ? -1000 : 1000;
+ }
+// for (auto i = 0; i < 3; ++i) {
+// auto middlePoint = std::distance(peaks[i], peaks[i + 1]) / 2;
+// auto middleIt = std::next(peaks[i], middlePoint);
+// auto middleVal = *middleIt;
+// auto incVal = QWavVectorType(-1) * middleVal;
+// std::for_each(i == 1 ? middleIt : peaks[i], i == 1 ? peaks[i + 1] : middleIt, [incVal](QWavVectorType& i) {
+// //qDebug() << "Old: " << i << "; new: " << (i+incVal);
+// i += incVal;
+// });
+// }
+ }
+ }
+ }
+}
+
WavReader::~WavReader()
{
if (mWavOpened) {
diff --git a/sources/core/wavreader.h b/sources/core/wavreader.h
index 3444001..2c5638f 100644
--- a/sources/core/wavreader.h
+++ b/sources/core/wavreader.h
@@ -81,13 +81,18 @@ class WavReader : public QObject
}
template
- T* getData(QByteArray& buf, size_t& bufIndex) {
+ T* getData(QByteArray& buf, size_t& bufIndex) const {
T* res = reinterpret_cast(buf.data() + bufIndex);
bufIndex += sizeof(T);
return res;
}
- uint8_t* getData(QByteArray& buf, size_t& bufIndex, uint dataSize) {
+ template
+ void appendData(QByteArray& buf, const T& data) const {
+ buf.append(reinterpret_cast(&data), sizeof(T));
+ }
+
+ uint8_t* getData(QByteArray& buf, size_t& bufIndex, uint dataSize) const {
if (dataSize == 0) {
return nullptr;
}
@@ -97,7 +102,7 @@ class WavReader : public QObject
return t;
}
- QWavVectorType getSample(QByteArray& buf, size_t& bufIndex, uint dataSize, uint compressionCode);
+ QWavVectorType getSample(QByteArray& buf, size_t& bufIndex, uint dataSize, uint compressionCode) const;
QWavVector* createVector(size_t bytesPerSample, size_t size);
WavFmt mWavFormatHeader;
@@ -137,11 +142,13 @@ class WavReader : public QObject
ErrorCodesEnum read();
ErrorCodesEnum close();
- void saveWaveform() const;
+ void loadWaveform(const QString& fname);
+ void saveWaveform(const QString& fname = QString()) const;
void shiftWaveform(uint chNum);
void storeWaveform(uint chNum);
void restoreWaveform(uint chNum);
void normalizeWaveform(uint chNum);
+ void normalizeWaveform2(uint chNum);
static WavReader* instance();
diff --git a/sources/defines.h b/sources/defines.h
new file mode 100644
index 0000000..476637e
--- /dev/null
+++ b/sources/defines.h
@@ -0,0 +1,37 @@
+#ifndef DEFINES_H
+#define DEFINES_H
+
+#include
+
+using QWavVectorType = float;
+using QWavVector = QVector;
+
+template
+inline bool lessThanZero(T t) {
+ return t < 0;
+}
+
+inline bool isFreqFitsInDelta(uint32_t sampleRate, uint32_t length, uint32_t signalFreq, double signalDelta, double deltaDivider = 1.0) {
+ const double freq = sampleRate / length;
+ const double delta = signalFreq * (signalDelta / deltaDivider);
+ return freq >= (signalFreq - delta) && freq <= (signalFreq + delta);
+};
+
+enum SignalFrequencies {
+ PILOT_HALF_FREQ = 1620,
+ PILOT_FREQ = 810,
+ SYNCHRO_FIRST_HALF_FREQ = 4900,
+ SYNCHRO_SECOND_HALF = 5500,
+ SYNCHRO_FREQ = 2600,
+ ZERO_HALF_FREQ = 4090,
+ ZERO_FREQ = 2050,
+ ONE_HALF_FREQ = 2045,
+ ONE_FREQ = 1023
+};
+
+const double pilotDelta = 0.1;
+const double synchroDelta = 0.3;
+const double zeroDelta = 0.3;//0.3;//0.18;
+const double oneDelta = 0.25;//0.25;//0.1;
+
+#endif // DEFINES_H
diff --git a/sources/main.cpp b/sources/main.cpp
index d1f2995..151786a 100644
--- a/sources/main.cpp
+++ b/sources/main.cpp
@@ -11,6 +11,7 @@
#include "sources/controls/waveformcontrol.h"
#include "sources/core/waveformparser.h"
#include "sources/models/fileworkermodel.h"
+#include "sources/models/suspiciouspointsmodel.h"
void registerTypes()
{
@@ -23,6 +24,7 @@ void registerTypes()
Q_UNUSED(scriptEngine)
return new FileWorkerModel();
});
+ qmlRegisterSingletonInstance("com.models.zxtapereviver", 1, 0, "SuspiciousPointsModel", SuspiciousPointsModel::instance());
qmlRegisterSingletonInstance("com.core.zxtapereviver", 1, 0, "WavReader", WavReader::instance());
qmlRegisterSingletonInstance("com.core.zxtapereviver", 1, 0, "WaveformParser", WaveformParser::instance());
}
diff --git a/sources/models/fileworkermodel.cpp b/sources/models/fileworkermodel.cpp
index f2847a6..7eb7f9f 100644
--- a/sources/models/fileworkermodel.cpp
+++ b/sources/models/fileworkermodel.cpp
@@ -42,6 +42,36 @@ FileWorkerModel::FileWorkerModel(QObject* parent) :
return result;
}
+/*WavReader::ErrorCodesEnum*/ int FileWorkerModel::openWaveformFileByUrl(const QString& fileNameUrl)
+{
+ QUrl u(fileNameUrl);
+ return openWaveformFile(u.toLocalFile());
+}
+
+/*WavReader::ErrorCodesEnum*/ int FileWorkerModel::openWaveformFile(const QString& fileName)
+{
+ auto& r = *WavReader::instance();
+ r.close();
+
+ r.loadWaveform(fileName);
+ m_wavFileName = fileName;
+ emit wavFileNameChanged();
+ return WavReader::Ok;
+}
+
+/*WavReader::ErrorCodesEnum*/ int FileWorkerModel::saveWaveformFileByUrl(const QString& fileNameUrl)
+{
+ QUrl u(fileNameUrl);
+ return saveWaveformFile(u.toLocalFile());
+}
+
+/*WavReader::ErrorCodesEnum*/ int FileWorkerModel::saveWaveformFile(const QString& fileName)
+{
+ auto& r = *WavReader::instance();
+ r.saveWaveform(fileName);
+ return WavReader::Ok;
+}
+
QString FileWorkerModel::getWavFileName() const
{
return m_wavFileName;
diff --git a/sources/models/fileworkermodel.h b/sources/models/fileworkermodel.h
index 2e631b3..e27484d 100644
--- a/sources/models/fileworkermodel.h
+++ b/sources/models/fileworkermodel.h
@@ -32,9 +32,13 @@ class FileWorkerModel : public QObject
//setters
- //QML invocable members
+ //QML invokable members
Q_INVOKABLE /*WavReader::ErrorCodesEnum*/ int openWavFileByUrl(const QString& fileNameUrl);
Q_INVOKABLE /*WavReader::ErrorCodesEnum*/ int openWavFile(const QString& fileName);
+ Q_INVOKABLE /*WavReader::ErrorCodesEnum*/ int openWaveformFileByUrl(const QString& fileNameUrl);
+ Q_INVOKABLE /*WavReader::ErrorCodesEnum*/ int openWaveformFile(const QString& fileName);
+ Q_INVOKABLE /*WavReader::ErrorCodesEnum*/ int saveWaveformFileByUrl(const QString& fileNameUrl);
+ Q_INVOKABLE /*WavReader::ErrorCodesEnum*/ int saveWaveformFile(const QString& fileName);
signals:
void wavFileNameChanged();
diff --git a/sources/models/suspiciouspointsmodel.cpp b/sources/models/suspiciouspointsmodel.cpp
new file mode 100644
index 0000000..b58bf75
--- /dev/null
+++ b/sources/models/suspiciouspointsmodel.cpp
@@ -0,0 +1,87 @@
+//*******************************************************************************
+// ZX Tape Reviver
+//-----------------
+//
+// Author: Leonid Golouz
+// E-mail: lgolouz@list.ru
+//*******************************************************************************
+
+#include "suspiciouspointsmodel.h"
+#include
+
+SuspiciousPointsModel::SuspiciousPointsModel(QObject* parent) : QObject(parent)
+{
+
+}
+
+bool SuspiciousPointsModel::addSuspiciousPoint(uint idx)
+{
+ qDebug() << QString("Adding suspicious point: %1").arg(idx);
+ for (auto i = 0; i < mSuspiciousPoints.size(); ++i) {
+ auto& p = mSuspiciousPoints[i];
+ if (p == idx) {
+ return false;
+ }
+ else if (p.toUInt() > idx) {
+ mSuspiciousPoints.insert(i, idx);
+ emit suspiciousPointsChanged();
+ emit sizeChanged();
+ return true;
+ }
+ }
+
+ mSuspiciousPoints.append(idx);
+ emit suspiciousPointsChanged();
+ emit sizeChanged();
+ return true;
+}
+
+bool SuspiciousPointsModel::removeSuspiciousPoint(int idx)
+{
+ qDebug() << QString("Removing suspicious point: %1").arg(idx);
+ const auto r = mSuspiciousPoints.size() > idx;
+ if (r) {
+ mSuspiciousPoints.removeAt(idx);
+ emit suspiciousPointsChanged();
+ emit sizeChanged();
+ }
+ return r;
+}
+
+uint SuspiciousPointsModel::getSuspiciousPoint(int idx)
+{
+ if (mSuspiciousPoints.size() > idx) {
+ return mSuspiciousPoints[idx].toUInt();
+ }
+ return 0;
+}
+
+void SuspiciousPointsModel::clearSuspiciousPoints()
+{
+ mSuspiciousPoints.clear();
+ emit suspiciousPointsChanged();
+ emit sizeChanged();
+}
+
+int SuspiciousPointsModel::getSize() const
+{
+ return mSuspiciousPoints.size();
+}
+
+QVariantList SuspiciousPointsModel::getSuspiciousPoints() const
+{
+ return mSuspiciousPoints;
+}
+
+void SuspiciousPointsModel::setSuspiciousPoints(const QVariantList& m)
+{
+ mSuspiciousPoints = m;
+ emit suspiciousPointsChanged();
+ emit sizeChanged();
+}
+
+SuspiciousPointsModel* SuspiciousPointsModel::instance()
+{
+ static SuspiciousPointsModel m;
+ return &m;
+}
diff --git a/sources/models/suspiciouspointsmodel.h b/sources/models/suspiciouspointsmodel.h
new file mode 100644
index 0000000..2937f88
--- /dev/null
+++ b/sources/models/suspiciouspointsmodel.h
@@ -0,0 +1,43 @@
+//*******************************************************************************
+// ZX Tape Reviver
+//-----------------
+//
+// Author: Leonid Golouz
+// E-mail: lgolouz@list.ru
+//*******************************************************************************
+
+#ifndef SUSPICIOUSPOINTSMODEL_H
+#define SUSPICIOUSPOINTSMODEL_H
+
+#include
+
+class SuspiciousPointsModel : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QVariantList suspiciousPoints READ getSuspiciousPoints NOTIFY suspiciousPointsChanged)
+ Q_PROPERTY(int size READ getSize NOTIFY sizeChanged)
+
+ QVariantList mSuspiciousPoints;
+
+protected:
+ explicit SuspiciousPointsModel(QObject* parent = nullptr);
+
+public:
+ virtual ~SuspiciousPointsModel() = default;
+ static SuspiciousPointsModel* instance();
+
+ int getSize() const;
+ QVariantList getSuspiciousPoints() const;
+ void setSuspiciousPoints(const QVariantList& m);
+
+ Q_INVOKABLE bool addSuspiciousPoint(uint idx);
+ Q_INVOKABLE bool removeSuspiciousPoint(int idx);
+ Q_INVOKABLE uint getSuspiciousPoint(int idx);
+ Q_INVOKABLE void clearSuspiciousPoints();
+
+signals:
+ void suspiciousPointsChanged();
+ void sizeChanged();
+};
+
+#endif // SUSPICIOUSPOINTSMODEL_H