Cracked it, in the end.
Step 1) Extend the number of loops before checking for the time elapsed. So I wait for 5000 loops of 64Kb before working out anything.
Step 2) Have two time elements. One for every repeat, and one inside my progress loop (which is called after 5000 loops) which records the end time that the progress loop was last called. It is then used for the next loop, in another 5000 cycles, to compare the time of the most recent loop against that last computed time.
Step 3) Work out the total secotrs of the disk, less those processed since the last progress loop. Divide by the times computed and then multiply from Ms to S and from S to Mins.
Below is a snippet of code if it can help others:
var
Buffer : array [0..65535] of Byte; // 65536, 64Kb buffer
Offset : DWORD;
ctx : TSHA1Context;
Digest : TSHA1Digest;
index, ProgressCounter : integer;
NewPos, ExactDiskSize, SectorCount,
BytesPerMinute, TimeToRead,
TimeTakenForInterval, BytesExaminedDuringInterval,
BytesPerSecond, RemainingBytes : Int64;
StartTime, IntervalReadTime,
TimeStartRead, EndTime : TDateTime;
const
DataTransferTrigger : Integer = 5000; // The number of buffer reads to allow before a transfer speed computation
begin
...
try
// Does some disk seeking and stuff
repeat
ProgressCounter := ProgressCounter + 1; // We use this update the progress display occasionally, instead of every buffer read
TimeStartRead := Now; // For every repeat, we get the time.
// Stuff is done with the disk, reading bytes and stuff - the stuff I want to time!
// Only if the loop has gone round a while (2000 times?), update the progress display
if ProgressCounter = DataTransferTrigger then // If X disk loops have taken place, calculate progress
begin
BytesExaminedDuringInterval := 0;
// Work out how many bytes are left to do
RemainingBytes := ExactDiskSize - NewPos;
// This should always be the same but it is whatever loop cycle is chosen by the size of the chosen buffer, e.g. 5000 * 65536
BytesExaminedDuringInterval := (DataTransferTrigger * SizeOf(Buffer));
// Now compute the number of Ms between the start time of this
// particular buffer read and the time from when the last loop was execute, and convert to seconds
TimeTakenForInterval := Round(MilliSecondsBetween(IntervalReadTime, TimeStartRead) DIV 1000);
// So with the number of bytes examined since the last progress loop known,
// and with the time in seconds since the last progress loop know,
// divide the two for bytes p\s
BytesPerSecond := Round(BytesExaminedDuringInterval DIV TimeTakenForInterval);
// Then multiply by 60 seconds for a p\m rate
BytesPerMinute := BytesPerSecond * 60;
lblSpeedB.Caption := FormatByteSize(BytesPerMinute) + ' p\min';
// Reset the counter for another looping cycle
ProgressCounter := 0;
// And now get the time that this progress loop ended,
// in order to compute the time taken for next time
IntervalReadTime := Now;
end;
Application.ProcessMessages;
until (NewPos >= ExactDiskSize) or (Stop = true); // Stop looping over the disk