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