401 lines
15 KiB
C++
401 lines
15 KiB
C++
|
|
/* ArduinoFloppyReader (and writer) - Rotation Extractor
|
||
|
|
*
|
||
|
|
* Copyright (C) 2017-2021 Robert Smith (@RobSmithDev)
|
||
|
|
* https://amiga.robsmithdev.co.uk
|
||
|
|
*
|
||
|
|
* This library is free software; you can redistribute it and/or
|
||
|
|
* modify it under the terms of the GNU Library General Public
|
||
|
|
* License as published by the Free Software Foundation; either
|
||
|
|
* version 3 of the License, or (at your option) any later version.
|
||
|
|
*
|
||
|
|
* This library is distributed in the hope that it will be useful,
|
||
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
|
* Library General Public License for more details.
|
||
|
|
*
|
||
|
|
* You should have received a copy of the GNU Library General Public
|
||
|
|
* License along with this library; if not, see http://www.gnu.org/licenses/
|
||
|
|
*/
|
||
|
|
|
||
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
// Class to manage finding *exact* disk revolutions //
|
||
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
//
|
||
|
|
// Purpose:
|
||
|
|
// The class attempts to guess where an exact disk revolution occurs, and then re-aligns
|
||
|
|
// it such that it starts at the index pulse. This means we dont need to wait for an index
|
||
|
|
// pulse to work out a revolution of the disk. The first time a disk is used we calculate
|
||
|
|
// the time of a single revolution and then use that as a guide to how long a revolution will
|
||
|
|
// take in the future.
|
||
|
|
//
|
||
|
|
// This isn't 100% perfect but does seem to work.
|
||
|
|
|
||
|
|
#include "RotationExtractor.h"
|
||
|
|
|
||
|
|
// Finds the overlap between the start of the data and where we currently are. The returned position is where the NEXT revolution starts
|
||
|
|
unsigned int RotationExtractor::getOverlapPosition() const {
|
||
|
|
int bestScore = OVERLAP_SEQUENCE_MATCHES / 4; // must have *some* kind of match to be worthy
|
||
|
|
unsigned int bestScoreIndex = m_revolutionReadyAt;
|
||
|
|
|
||
|
|
// Working back from the mid-point
|
||
|
|
for (unsigned int midPoint = 0; midPoint < OVERLAP_SEQUENCE_MATCHES * (OVERLAP_EXTRA_BUFFER - 1); midPoint++) {
|
||
|
|
|
||
|
|
// Count the number of matching sequences
|
||
|
|
int scoreL = 0;
|
||
|
|
int scoreR = 0;
|
||
|
|
const int startPositionR = m_revolutionReadyAt + midPoint;
|
||
|
|
const int startPositionL = m_revolutionReadyAt - midPoint;
|
||
|
|
|
||
|
|
if (startPositionL >= 0) {
|
||
|
|
for (unsigned int pos = 0; pos < OVERLAP_SEQUENCE_MATCHES; pos++) {
|
||
|
|
if (m_sequences[pos].mfm == m_sequences[startPositionR + pos].mfm) scoreR++;
|
||
|
|
if (m_sequences[pos].mfm == m_sequences[startPositionL + pos].mfm) scoreL++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
for (unsigned int pos = 0; pos < OVERLAP_SEQUENCE_MATCHES; pos++) {
|
||
|
|
if (m_sequences[pos].mfm == m_sequences[startPositionR + pos].mfm) scoreR++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// A perfect score short-circuits the rest of the loop
|
||
|
|
if (scoreL > bestScore) {
|
||
|
|
bestScore = scoreL;
|
||
|
|
bestScoreIndex = startPositionL;
|
||
|
|
}
|
||
|
|
if (scoreR > bestScore) {
|
||
|
|
bestScore = scoreR;
|
||
|
|
bestScoreIndex = startPositionR;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (bestScore == OVERLAP_SEQUENCE_MATCHES) return bestScoreIndex;
|
||
|
|
}
|
||
|
|
|
||
|
|
// If we got here then there wasnt a perfect match. This would only happen if:
|
||
|
|
// 1. The drive speed is broken!
|
||
|
|
// 2. The overlap is unformatted, in which case it doesn't really matter anyway
|
||
|
|
// 3. The disk/head is damaged or dirty, so then there's no hope anyway
|
||
|
|
return bestScoreIndex;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Finds the true start position of the INDEX pulse based on previous revolutions. This is a lot like the above function
|
||
|
|
const unsigned int RotationExtractor::getTrueIndexPosition(const unsigned int nextRevolutionStart, const unsigned int startingPoint) {
|
||
|
|
// Where to start from
|
||
|
|
const int firstPoint = (startingPoint == INDEX_NOT_FOUND) ? m_sequenceIndex : startingPoint;
|
||
|
|
|
||
|
|
// First. Do we actually have a 'index marker' buffer?
|
||
|
|
if (!m_indexSequence.valid) {
|
||
|
|
// Not valid means we make it, and take our index as "true"
|
||
|
|
m_indexSequence.valid = true;
|
||
|
|
for (unsigned int pos = 0; pos < OVERLAP_SEQUENCE_MATCHES; pos++)
|
||
|
|
m_indexSequence.sequences[pos] = m_sequences[(firstPoint + pos) % nextRevolutionStart].mfm;
|
||
|
|
|
||
|
|
return firstPoint;
|
||
|
|
}
|
||
|
|
|
||
|
|
int bestScore = OVERLAP_SEQUENCE_MATCHES / 4; // must have *some* kind of match to be worthy
|
||
|
|
unsigned int bestScoreIndex = firstPoint;
|
||
|
|
|
||
|
|
// Working back from the mid-point
|
||
|
|
for (unsigned int midPoint = 0; midPoint < OVERLAP_SEQUENCE_MATCHES * (OVERLAP_EXTRA_BUFFER - 1); midPoint++) {
|
||
|
|
|
||
|
|
// Count the number of matching sequences
|
||
|
|
int scoreL = 0;
|
||
|
|
int scoreR = 0;
|
||
|
|
const int startPositionR = firstPoint + midPoint;
|
||
|
|
const int startPositionL = firstPoint - midPoint;
|
||
|
|
|
||
|
|
if (startPositionL >= 0) {
|
||
|
|
for (unsigned int pos = 0; pos < OVERLAP_SEQUENCE_MATCHES; pos++) {
|
||
|
|
if (m_indexSequence.sequences[pos] == m_sequences[(startPositionR + pos) % nextRevolutionStart].mfm) scoreR++;
|
||
|
|
if (m_indexSequence.sequences[pos] == m_sequences[(startPositionL + pos) % nextRevolutionStart].mfm) scoreL++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
for (unsigned int pos = 0; pos < OVERLAP_SEQUENCE_MATCHES; pos++) {
|
||
|
|
if (m_indexSequence.sequences[pos] == m_sequences[(startPositionR + pos) % nextRevolutionStart].mfm) scoreR++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// A perfect score short-circuits the rest of the loop
|
||
|
|
if (scoreL > bestScore) {
|
||
|
|
bestScore = scoreL;
|
||
|
|
bestScoreIndex = startPositionL;
|
||
|
|
}
|
||
|
|
if (scoreR > bestScore) {
|
||
|
|
bestScore = scoreR;
|
||
|
|
bestScoreIndex = startPositionR;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (bestScore == OVERLAP_SEQUENCE_MATCHES) return bestScoreIndex;
|
||
|
|
}
|
||
|
|
|
||
|
|
// If we got here then there wasnt a perfect match. This would only happen if:
|
||
|
|
// 1. The drive speed is broken!
|
||
|
|
// 2. The overlap is unformatted, in which case it doesn't really matter anyway
|
||
|
|
// 3. The disk/head is damaged or dirty, so then there's no hope anyway
|
||
|
|
return bestScoreIndex;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Write a bit into the stream
|
||
|
|
inline void writeStreamBit(RotationExtractor::MFMSample* output, unsigned int& pos, unsigned int& bit, bool value, const unsigned short valuespeed, const unsigned int maxLength) {
|
||
|
|
if (pos >= maxLength) return;
|
||
|
|
|
||
|
|
output[pos].mfmData <<= 1;
|
||
|
|
if (value) output[pos].mfmData |= 1;
|
||
|
|
|
||
|
|
#ifdef OUTPUT_TIME_IN_NS
|
||
|
|
output[pos].bittime[7 - bit] = valuespeed;
|
||
|
|
#else
|
||
|
|
#ifdef HIGH_RESOLUTION_MODE
|
||
|
|
output[pos].speed[7 - bit] = valuespeed;
|
||
|
|
#else
|
||
|
|
if (bit == 0) output[pos].speed = valuespeed; else output[pos].speed += valuespeed;
|
||
|
|
#endif
|
||
|
|
#endif
|
||
|
|
|
||
|
|
bit++;
|
||
|
|
if (bit >= 8) {
|
||
|
|
#ifndef OUTPUT_TIME_IN_NS
|
||
|
|
#ifndef HIGH_RESOLUTION_MODE
|
||
|
|
output[pos].speed /= 8;
|
||
|
|
#endif
|
||
|
|
#endif
|
||
|
|
pos++;
|
||
|
|
bit = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Submit a single sequence to the list
|
||
|
|
void RotationExtractor::submitSequence(const MFMSequenceInfo& sequence, const bool isIndex) {
|
||
|
|
// we reject the first 20uSec of data. Makes things so much more stable
|
||
|
|
m_timeReceived += sequence.timeNS;
|
||
|
|
if (m_timeReceived < 20000) return;
|
||
|
|
|
||
|
|
// And stop if we have too much. Shouldn't happen
|
||
|
|
if (m_sequencePos >= MAX_REVOLUTION_SEQUENCES) return;
|
||
|
|
|
||
|
|
// See if we can calculate the time it takes to get a single revolution from the disk
|
||
|
|
if ((m_revolutionTime == 0) && (!m_useIndex)) {
|
||
|
|
if (isIndex) {
|
||
|
|
if (m_sequenceIndex == INDEX_NOT_FOUND) m_sequenceIndex = 0; else m_nextSequenceIndex = m_sequencePos;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (m_sequenceIndex != INDEX_NOT_FOUND) {
|
||
|
|
// Store the sequence only if we found the first index
|
||
|
|
m_sequences[m_sequencePos++] = sequence;
|
||
|
|
m_currentTime += sequence.timeNS;
|
||
|
|
|
||
|
|
if (m_nextSequenceIndex == INDEX_NOT_FOUND)
|
||
|
|
m_revolutionTimeCounting += sequence.timeNS;
|
||
|
|
else {
|
||
|
|
m_revolutionTime = m_revolutionTimeCounting;
|
||
|
|
|
||
|
|
// Set the nearly complete marker to be at 90%. That would only need approx another 20ms to complete
|
||
|
|
m_revolutionTimeNearlyComplete = (unsigned int)(m_revolutionTime * 0.9f);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Handle index search
|
||
|
|
if (isIndex) {
|
||
|
|
if (m_sequenceIndex == INDEX_NOT_FOUND) m_sequenceIndex = m_sequencePos; else m_nextSequenceIndex = m_sequencePos;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Do different things depending on the mode in use
|
||
|
|
if (m_useIndex) {
|
||
|
|
if (m_sequenceIndex == INDEX_NOT_FOUND) {
|
||
|
|
// Store the data in the circular buffer
|
||
|
|
m_initialSequences[m_initialSequencesWritePos] = sequence;
|
||
|
|
m_initialSequencesWritePos = (m_initialSequencesWritePos + 1) % (OVERLAP_SEQUENCE_MATCHES * OVERLAP_EXTRA_BUFFER);
|
||
|
|
if (m_initialSequencesLength < OVERLAP_SEQUENCE_MATCHES * OVERLAP_EXTRA_BUFFER) m_initialSequencesLength++;
|
||
|
|
m_revolutionReady = false;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
// Was the FIRST index detected?
|
||
|
|
if ((isIndex) && (m_nextSequenceIndex == INDEX_NOT_FOUND) && (m_initialSequencesLength)) {
|
||
|
|
// Ok, shunt the buffer we have onto the output
|
||
|
|
m_sequencePos = m_initialSequencesLength;
|
||
|
|
m_sequenceIndex = m_initialSequencesLength;
|
||
|
|
|
||
|
|
// Go through all of them
|
||
|
|
while (m_initialSequencesLength) {
|
||
|
|
// Wind back one
|
||
|
|
m_initialSequencesLength--;
|
||
|
|
// Handle wrap-around buffer
|
||
|
|
if (m_initialSequencesWritePos == 0) m_initialSequencesWritePos = (OVERLAP_SEQUENCE_MATCHES * OVERLAP_EXTRA_BUFFER) - 1; else m_initialSequencesWritePos--;
|
||
|
|
// Save it
|
||
|
|
m_sequences[m_initialSequencesLength] = m_initialSequences[m_initialSequencesWritePos];
|
||
|
|
}
|
||
|
|
m_initialSequencesLength = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Store as normal
|
||
|
|
m_sequences[m_sequencePos++] = sequence;
|
||
|
|
|
||
|
|
// Check if ready
|
||
|
|
if (m_nextSequenceIndex != INDEX_NOT_FOUND) {
|
||
|
|
if (m_sequencePos > m_nextSequenceIndex + (OVERLAP_SEQUENCE_MATCHES * OVERLAP_EXTRA_BUFFER)) {
|
||
|
|
m_revolutionReadyAt = m_nextSequenceIndex;
|
||
|
|
m_revolutionReady = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
m_sequences[m_sequencePos++] = sequence;
|
||
|
|
m_currentTime += (unsigned int)sequence.timeNS;
|
||
|
|
|
||
|
|
// This is a sneaky check-ahead for a full rotation, like a virtual index marker
|
||
|
|
if (m_currentTime >= m_revolutionTime) {
|
||
|
|
if (m_revolutionReadyAt == INDEX_NOT_FOUND) m_revolutionReadyAt = m_sequencePos; else
|
||
|
|
if (m_sequencePos > m_revolutionReadyAt + (OVERLAP_SEQUENCE_MATCHES * OVERLAP_EXTRA_BUFFER))
|
||
|
|
m_revolutionReady = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Extracts a single rotation and updates the buffer to remove it. Returns FALSE if no rotation is available
|
||
|
|
bool RotationExtractor::extractRotation(MFMSample* output, unsigned int& outputBits, const unsigned int maxBufferSizeBytes) {
|
||
|
|
// Step 0: check if we're possibly ready
|
||
|
|
if (!canExtract()) return false;
|
||
|
|
|
||
|
|
if (m_useIndex) {
|
||
|
|
if (m_sequenceIndex == INDEX_NOT_FOUND) return false;
|
||
|
|
if (m_nextSequenceIndex == INDEX_NOT_FOUND) return false;
|
||
|
|
if (m_sequenceIndex > m_nextSequenceIndex) {
|
||
|
|
unsigned int s = m_sequenceIndex;
|
||
|
|
m_sequenceIndex = m_nextSequenceIndex;
|
||
|
|
m_nextSequenceIndex = s;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Step 1: Find where the first index position is
|
||
|
|
const unsigned int revolutionStart = getTrueIndexPosition(INDEX_NOT_FOUND, m_sequenceIndex);
|
||
|
|
|
||
|
|
// Step 2: find out where the next one is
|
||
|
|
const unsigned int nextRevolutionStart = getTrueIndexPosition(INDEX_NOT_FOUND, m_nextSequenceIndex);
|
||
|
|
|
||
|
|
// Markers
|
||
|
|
unsigned int outputStreamPos = 0;
|
||
|
|
unsigned int outputStreamBit = 0;
|
||
|
|
unsigned int totalSamples = nextRevolutionStart - revolutionStart;
|
||
|
|
// Work out revolution time anyway
|
||
|
|
unsigned int rTime = 0;
|
||
|
|
|
||
|
|
// Step 3: output. Data goes from 0 to nextRevolutionStart-1, but we need to output from indexPosition
|
||
|
|
for (unsigned int pos = 0; pos < totalSamples; pos++) {
|
||
|
|
const MFMSequenceInfo& sequence = m_sequences[pos + revolutionStart];
|
||
|
|
rTime += sequence.timeNS;
|
||
|
|
|
||
|
|
#ifdef OUTPUT_TIME_IN_NS
|
||
|
|
const unsigned int bitTime = sequence.timeNS / ((unsigned int)sequence.mfm + 2);
|
||
|
|
|
||
|
|
// And write the output stream
|
||
|
|
for (unsigned int s = 0; s <= (unsigned int)sequence.mfm; s++)
|
||
|
|
writeStreamBit(output, outputStreamPos, outputStreamBit, false, bitTime, maxBufferSizeBytes);
|
||
|
|
writeStreamBit(output, outputStreamPos, outputStreamBit, (sequence.mfm != MFMSequence::mfm0000), bitTime, maxBufferSizeBytes);
|
||
|
|
#else
|
||
|
|
const unsigned int speed = ((unsigned int)sequence.timeNS * 100) / (((unsigned int)sequence.mfm + 2) * 2000);
|
||
|
|
|
||
|
|
// And write the output stream
|
||
|
|
for (unsigned int s = 0; s <= (unsigned int)sequence.mfm; s++)
|
||
|
|
writeStreamBit(output, outputStreamPos, outputStreamBit, false, speed, maxBufferSizeBytes);
|
||
|
|
writeStreamBit(output, outputStreamPos, outputStreamBit, (sequence.mfm != MFMSequence::mfm0000), speed, maxBufferSizeBytes);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
// Need to shift the last ones onto place
|
||
|
|
if (outputStreamBit) {
|
||
|
|
output[outputStreamPos].mfmData <<= (8 - outputStreamBit);
|
||
|
|
#ifndef OUTPUT_TIME_IN_NS
|
||
|
|
#ifndef HIGH_RESOLUTION_MODE
|
||
|
|
output[outputStreamPos].speed /= outputStreamBit;
|
||
|
|
#endif
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
if (m_revolutionTime == 0) {
|
||
|
|
m_revolutionTime = rTime;
|
||
|
|
m_revolutionTimeNearlyComplete = (unsigned int)(m_revolutionTime * 0.9f);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate how much we wrote
|
||
|
|
outputBits = (outputStreamPos * 8) + outputStreamBit;
|
||
|
|
|
||
|
|
// Now shift the remaining data so that the next revolution starts at 0
|
||
|
|
for (unsigned int pos = 0; pos < m_sequencePos - nextRevolutionStart; pos++)
|
||
|
|
m_sequences[pos] = m_sequences[pos + nextRevolutionStart];
|
||
|
|
|
||
|
|
// And mark it
|
||
|
|
if (m_nextSequenceIndex > nextRevolutionStart) m_sequenceIndex = m_nextSequenceIndex - nextRevolutionStart; else m_sequenceIndex = 0;
|
||
|
|
m_nextSequenceIndex = INDEX_NOT_FOUND;
|
||
|
|
|
||
|
|
// And account for the shift
|
||
|
|
m_sequencePos -= nextRevolutionStart;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
|
||
|
|
// Step 1: Find the overlap between the start of the data and the end of the data
|
||
|
|
const unsigned int nextRevolutionStart = getOverlapPosition();
|
||
|
|
if (nextRevolutionStart < 1) return false;
|
||
|
|
|
||
|
|
// Step 2: wind it back to the INDEX position
|
||
|
|
const unsigned int indexPosition = getTrueIndexPosition(nextRevolutionStart);
|
||
|
|
|
||
|
|
// Markers
|
||
|
|
unsigned int outputStreamPos = 0;
|
||
|
|
unsigned int outputStreamBit = 0;
|
||
|
|
|
||
|
|
// Step 3: output. Data goes from 0 to nextRevolutionStart-1, but we need to output from indexPosition
|
||
|
|
for (unsigned int pos = 0; pos < nextRevolutionStart; pos++) {
|
||
|
|
const MFMSequenceInfo& sequence = m_sequences[(pos + indexPosition) % nextRevolutionStart];
|
||
|
|
m_currentTime -= (unsigned int)sequence.timeNS;
|
||
|
|
|
||
|
|
#ifdef OUTPUT_TIME_IN_NS
|
||
|
|
const unsigned int bitTime = sequence.timeNS / ((unsigned int)sequence.mfm + 2);
|
||
|
|
|
||
|
|
// And write the output stream
|
||
|
|
for (unsigned int s = 0; s <= (unsigned int)sequence.mfm; s++)
|
||
|
|
writeStreamBit(output, outputStreamPos, outputStreamBit, false, bitTime, maxBufferSizeBytes);
|
||
|
|
writeStreamBit(output, outputStreamPos, outputStreamBit, (sequence.mfm != MFMSequence::mfm0000), bitTime, maxBufferSizeBytes);
|
||
|
|
#else
|
||
|
|
const unsigned int speed = ((unsigned int)sequence.timeNS * 100) / (((unsigned int)sequence.mfm + 2) * 2000);
|
||
|
|
|
||
|
|
// And write the output stream
|
||
|
|
for (unsigned int s = 0; s <= (unsigned int)sequence.mfm; s++)
|
||
|
|
writeStreamBit(output, outputStreamPos, outputStreamBit, false, speed, maxBufferSizeBytes);
|
||
|
|
writeStreamBit(output, outputStreamPos, outputStreamBit, (sequence.mfm != MFMSequence::mfm0000), speed, maxBufferSizeBytes);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
// Need to shift the last ones onto place
|
||
|
|
if (outputStreamBit) {
|
||
|
|
output[outputStreamPos].mfmData <<= (8 - outputStreamBit);
|
||
|
|
#ifndef OUTPUT_TIME_IN_NS
|
||
|
|
#ifndef HIGH_RESOLUTION_MODE
|
||
|
|
output[outputStreamPos].speed /= outputStreamBit;
|
||
|
|
#endif
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate how much we wrote
|
||
|
|
outputBits = (outputStreamPos * 8) + outputStreamBit;
|
||
|
|
|
||
|
|
// Now it's extracted we need to remove it. First. Shift or reset the index marker
|
||
|
|
if ((m_nextSequenceIndex != INDEX_NOT_FOUND) && (m_nextSequenceIndex >= nextRevolutionStart)) m_sequenceIndex = m_nextSequenceIndex - nextRevolutionStart; else m_sequenceIndex = INDEX_NOT_FOUND;
|
||
|
|
m_nextSequenceIndex = INDEX_NOT_FOUND;
|
||
|
|
|
||
|
|
// Now shift the remaining data
|
||
|
|
for (unsigned int pos = 0; pos < m_sequencePos - nextRevolutionStart; pos++)
|
||
|
|
m_sequences[pos] = m_sequences[pos + nextRevolutionStart];
|
||
|
|
|
||
|
|
// And account for the shift
|
||
|
|
m_sequencePos -= nextRevolutionStart;
|
||
|
|
}
|
||
|
|
|
||
|
|
m_revolutionReadyAt = INDEX_NOT_FOUND;
|
||
|
|
m_revolutionReady = false;
|
||
|
|
m_initialSequencesLength = 0;
|
||
|
|
m_initialSequencesWritePos = 0;
|
||
|
|
|
||
|
|
// and success!
|
||
|
|
return true;
|
||
|
|
}
|