Files
SyncHome/trunk/Arduino/FloppyDriveController.sketch/V2.6/ArduinoFloppyDiskReader-master/ArduinoFloppyReader/lib/RotationExtractor.h
2023-03-17 11:40:49 +00:00

200 lines
8.0 KiB
C++

#ifndef READERWRITER_ROTATION_EXTRACTOR
#define READERWRITER_ROTATION_EXTRACTOR
/* 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.
// With this defined you get speed data per bit. Undefined, per 8 bits. Nothing seems to notice much
// Inside *UAE it uses one speed value for 16 bits of MFM data so that's probably why.
// and besides, with this *undefined* we save about 700k of ram
#define HIGH_RESOLUTION_MODE
// Instead of outputting "speed" values this will output bit-times in ns
#define OUTPUT_TIME_IN_NS
// So worse case is the disk takes 210ms to spin, and every sequence is VERY fast, and every sequence is 01, this number is highly unlikely though
#define MAX_REVOLUTION_SEQUENCES 110000
// Number of sequences to match to find the index overlap position or the rotation overlap position
// The higher the number, the more chance of perfect revolution alignment, but higher processing required at the end of each revolution
#define OVERLAP_SEQUENCE_MATCHES 1024
// Extra window either side. this allows more of a search range
#define OVERLAP_EXTRA_BUFFER 6
// Signal for index was not found
#define INDEX_NOT_FOUND 0xFFFFFFFF
// Class to extract a single rotation from an incoming mfm data sequence.
class RotationExtractor {
public:
// Enum for the possible sequences we support
enum class MFMSequence : unsigned char { mfm01 = 0, mfm001 = 1, mfm0001 = 2, mfm0000 = 3 };
// A single sequence of MFM data
struct MFMSequenceInfo {
// Total time it took to read this in NS
unsigned short timeNS;
// The MFM sequence discovered
MFMSequence mfm;
};
// Decoded version of the above
struct MFMSample {
#ifdef OUTPUT_TIME_IN_NS
// This is the time for each 'bit'
unsigned short bittime[8];
#else
#ifdef HIGH_RESOLUTION_MODE
// This is the speed of each 'bit' as a %
unsigned short speed[8];
#else
// This is the average speed of all 8 bits as a %
unsigned short speed;
#endif
#endif
// This is the raw MFM bit-data
unsigned char mfmData;
};
// Struct for tracking what the index start looks like so we get it perfect (or at least consistant)
struct IndexSequenceMarker {
// Sequences found
MFMSequence sequences[OVERLAP_SEQUENCE_MATCHES];
// If this is actually valid
bool valid = false;
};
private:
// How long a revolution is
unsigned int m_revolutionTime = 0;
// An amount of time whereby we 'nearly' have a complete revolution of data
unsigned int m_revolutionTimeNearlyComplete = 0;
// Used while working out the above
unsigned int m_revolutionTimeCounting = 0;
// Where the first index pulse was discovered
unsigned int m_sequenceIndex = INDEX_NOT_FOUND;
// Where the second index pulse was discovered
unsigned int m_nextSequenceIndex = INDEX_NOT_FOUND;
// Where we were when we reached m_revolutionTime worth of data
unsigned int m_revolutionReadyAt = INDEX_NOT_FOUND;
// And a flag to set this as good
bool m_revolutionReady = false;
// If we should always use the index marker when finding revolutions
bool m_useIndex = false;
// Current amount of data in the buffer in ns
unsigned int m_currentTime = 0;
// Current position of the buffer
unsigned int m_sequencePos = 0;
// Used to track exactly how much data has been submitted
unsigned int m_timeReceived = 0;
// Sequences received thus far
MFMSequenceInfo m_sequences[MAX_REVOLUTION_SEQUENCES];
// In index mode, this holds the initial sequences before the first index marker
MFMSequenceInfo m_initialSequences[OVERLAP_SEQUENCE_MATCHES * OVERLAP_EXTRA_BUFFER];
// Length of the above datat in use
unsigned int m_initialSequencesLength = 0;
// Where we're writing to as its a circular buffer
unsigned int m_initialSequencesWritePos = 0;
// Sequences discovered around the index marker
IndexSequenceMarker m_indexSequence;
// Finds the overlap between the start of the data and where we currently are
unsigned int getOverlapPosition() const;
// is almost identical
const unsigned int getTrueIndexPosition(const unsigned int revolutionEnd, const unsigned int startingPoint = INDEX_NOT_FOUND);
public:
// Get and set the sequence identified as data round the INDEX pulse so that next time we get consistant revolution starting points
void setIndexSequence(const IndexSequenceMarker& sequence) { m_indexSequence = sequence; }
void getIndexSequence(IndexSequenceMarker& sequence) const { sequence = m_indexSequence; }
// Reset this back to "empty"
inline void reset() {
m_indexSequence.valid = false;
m_revolutionReadyAt = INDEX_NOT_FOUND;
m_sequencePos = 0;
m_sequenceIndex = INDEX_NOT_FOUND;
m_nextSequenceIndex = INDEX_NOT_FOUND;
m_currentTime = 0;
m_revolutionReady = false;
m_initialSequencesLength = 0;
m_initialSequencesWritePos = 0;
m_timeReceived = 0;
}
// Signal new disk, or maybe a motor restarted. Need to re-calculate rotation speed
inline void newDisk() {
reset();
m_revolutionTime = 0;
m_revolutionTimeCounting = 0;
m_revolutionTimeNearlyComplete = 0;
}
// Return the current revolution time
inline unsigned int getRevolutionTime() const { return m_revolutionTime; };
// Set the current revolution time
inline void setRevolutionTime(const unsigned int time) { m_revolutionTime = time; m_revolutionTimeNearlyComplete = (unsigned int)(time * 0.9f); };
// Return the total amount of time data received so far
inline unsigned int totalTimeReceived() const { return m_timeReceived; };
// Returns TRUE if this has learnt the time of a disk revolution
inline bool hasLearntRotationSpeed() const { return m_revolutionTime > 150000000; };
// Returns TRUE if we're in INDEX mode
inline bool isInIndexMode() const { return m_useIndex; };
// Sets the code so it always uses the index marker when finding revolutions
void setAlwaysUseIndex(bool useIndex) { m_useIndex = useIndex; };
// If this is about to spit out a revolution in a very small amount of time
inline bool isNearlyReady() const { return (m_revolutionTimeNearlyComplete) && (m_currentTime >= m_revolutionTimeNearlyComplete) && (!m_useIndex); }
// Submit a single sequence to the list
void submitSequence(const MFMSequenceInfo& sequence, const bool isIndex);
// Returns TRUE if we should be able to extract a revolution
inline bool canExtract() const { return (m_revolutionReadyAt != INDEX_NOT_FOUND) && (m_revolutionReady); };
// Extracts a single rotation and updates the buffer to remove it. Returns FALSE if no rotation is available
// If calculateSpeedFactor is true, we're in INDEX mode, and HIGH_RESOLUTION_MODE is defined then this will output time in NS rather than the speed factor value
bool extractRotation(MFMSample* output, unsigned int& outputBits, const unsigned int maxBufferSizeBytes);
};
#endif