diff --git a/Bwg.Scsi/Command.cs b/Bwg.Scsi/Command.cs index 27e5a03..a2556d2 100644 --- a/Bwg.Scsi/Command.cs +++ b/Bwg.Scsi/Command.cs @@ -29,7 +29,7 @@ using System.Diagnostics; namespace Bwg.Scsi { - class Command : IDisposable + public class Command : IDisposable { [DllImport("ntdll.dll")] internal static extern void RtlZeroMemory(IntPtr dest, int size); diff --git a/Bwg.Scsi/Defs.cs b/Bwg.Scsi/Defs.cs index 3d3c96f..bbf7715 100644 --- a/Bwg.Scsi/Defs.cs +++ b/Bwg.Scsi/Defs.cs @@ -23,7 +23,7 @@ namespace Bwg.Scsi { - enum ScsiCommandCode + public enum ScsiCommandCode { TestUnitReady = 0x00, RequestSense = 0x03, diff --git a/Bwg.Scsi/Device.cs b/Bwg.Scsi/Device.cs index 40e3b47..f2a119b 100644 --- a/Bwg.Scsi/Device.cs +++ b/Bwg.Scsi/Device.cs @@ -939,7 +939,7 @@ namespace Bwg.Scsi } #endregion - private CommandStatus SendCommand(Command cmd) + public CommandStatus SendCommand(Command cmd) { return (m_ossize == 32) ? SendCommand32(cmd) : SendCommand64(cmd); } @@ -1971,7 +1971,7 @@ namespace Bwg.Scsi /// The buffer to receive the data /// The size of the buffer given by the data parameter /// - public CommandStatus Read(bool force, bool streaming, uint lba, uint length, IntPtr data, ushort size) + public CommandStatus Read(bool force, bool streaming, uint lba, uint length, IntPtr data, int size) { if (m_logger != null) { @@ -2134,15 +2134,16 @@ namespace Bwg.Scsi /// /// /// - /// subchannel mode - /// + /// subchannel mode (+16 bytes if equals 2) + /// report C2 errors (+296 bytes if true) + /// expected sector type /// /// /// /// the memory area /// the size of the memory area given by the data parameter /// - public CommandStatus ReadCDAndSubChannel(byte mode, byte exp, bool dap, uint start, uint length, IntPtr data, int size) + public CommandStatus ReadCDAndSubChannel(byte mode, bool c2, byte exp, bool dap, uint start, uint length, IntPtr data, int size) { if (m_logger != null) { @@ -2161,10 +2162,13 @@ namespace Bwg.Scsi byte b = (byte)((exp & 0x07) << 2); if (dap) b |= 0x02; + byte byte9 = 0x10; + if (c2) + byte9 |= 0x02; cmd.SetCDB8(1, b); cmd.SetCDB32(2, start); cmd.SetCDB24(6, length); - cmd.SetCDB8(9, 0x10); // User data only + cmd.SetCDB8(9, byte9); // User data + possibly c2 errors cmd.SetCDB8(10, mode); // Subchannel CommandStatus st = SendCommand(cmd); diff --git a/CUETools.CDImage/CDImage.cs b/CUETools.CDImage/CDImage.cs index d808f13..731651f 100644 --- a/CUETools.CDImage/CDImage.cs +++ b/CUETools.CDImage/CDImage.cs @@ -52,12 +52,13 @@ namespace CUETools.CDImage public class CDTrack : ICloneable { - public CDTrack(uint number, uint start, uint length, bool isAudio) + public CDTrack(uint number, uint start, uint length, bool isAudio, bool preEmpasis) { _number = number; _start = start; _length = length; _isAudio = isAudio; + _preEmphasis = preEmpasis; _indexes = new List(); _indexes.Add(new CDTrackIndex(0, start)); _indexes.Add(new CDTrackIndex(1, start)); @@ -69,6 +70,7 @@ namespace CUETools.CDImage _start = src._start; _length = src._length; _isAudio = src._isAudio; + _preEmphasis = src._preEmphasis; _indexes = new List(); for (int i = 0; i < src._indexes.Count; i++) _indexes.Add(new CDTrackIndex(src._indexes[i])); @@ -187,6 +189,18 @@ namespace CUETools.CDImage } } + public bool PreEmphasis + { + get + { + return _preEmphasis; + } + set + { + _preEmphasis = value; + } + } + public void AddIndex(CDTrackIndex index) { if (index.Index < 2) @@ -198,6 +212,7 @@ namespace CUETools.CDImage IList _indexes; string _isrc; bool _isAudio; + bool _preEmphasis; uint _start; uint _length; uint _number; diff --git a/CUETools.Ripper.Console/Program.cs b/CUETools.Ripper.Console/Program.cs index 6ca17b7..26edf2e 100644 --- a/CUETools.Ripper.Console/Program.cs +++ b/CUETools.Ripper.Console/Program.cs @@ -27,13 +27,60 @@ using CUETools.Ripper.SCSI; using CUETools.Codecs; using CUETools.CDImage; using CUETools.AccurateRip; -using FLACDotNet; +//using FLACDotNet; using MusicBrainz; namespace CUERipper { + class ProgressMeter + { + public DateTime realStart; + + public ProgressMeter() + { + realStart = DateTime.Now; + //TimeSpan lastPrint = TimeSpan.FromMilliseconds(0); + } + + public void ReadProgress(object sender, ReadProgressArgs e) + { + CDDriveReader audioSource = (CDDriveReader)sender; + int processed = e.Position - e.PassStart; + TimeSpan elapsed = DateTime.Now - e.PassTime; + double speed = elapsed.TotalSeconds > 0 ? processed / elapsed.TotalSeconds / 75 : 1.0; + TimeSpan totalElapsed = DateTime.Now - realStart; + TimeSpan totalEstimated = TimeSpan.FromMilliseconds(totalElapsed.TotalMilliseconds / Math.Max(1, (e.PassStart + (processed + e.Pass * (e.PassEnd - e.PassStart)) / 16)) * audioSource.TOC.AudioLength); + + // if ((elapsed - lastPrint).TotalMilliseconds > 60) ; + Console.Write("\r{9} : {0:00}%; {1:00.00}x; {2} ({10:0.00}%) errors; {3:d2}:{4:d2}:{5:d2}/{6:d2}:{7:d2}:{8:d2} ", + 100.0 * e.Position / audioSource.TOC.AudioLength, + speed, + e.ErrorsCount, + totalElapsed.Hours, totalElapsed.Minutes, totalElapsed.Seconds, + totalEstimated.Hours, totalEstimated.Minutes, totalEstimated.Seconds, + e.Pass < 1 ? "Progress " : string.Format("Retry {0:00} ", e.Pass), + processed > 0 ? 100.0 * e.ErrorsCount / processed / (4 * 588) : 0 + ); + //lastPrint = elapsed; + } + } + class Program { + static void Usage() + { + string drives = ""; + char[] drivesAvailable = CDDriveReader.DrivesAvailable(); + for (int i = 0; i < drivesAvailable.Length; i++) + drives += string.Format("{0}: ", drivesAvailable[i]); + Console.WriteLine("Usage : CUERipper.exe "); + Console.WriteLine(); + //Console.WriteLine("-S, --secure secure mode, read each block twice;"); + Console.WriteLine("-P, --paranoid maximum level of error correction;"); + Console.WriteLine("-D, --drive use a specific CD drive, e.g. {0};", drives); + Console.WriteLine("-O, --offset use specific drive read offset;"); + } + static void Main(string[] args) { string programVersion = "CUERipper v1.9.3 Copyright (C) 2008 Gregory S. Chudov"; @@ -42,25 +89,65 @@ namespace CUERipper Console.WriteLine("This is free software under the GNU GPLv3+ license; There is NO WARRANTY, to"); Console.WriteLine("the extent permitted by law. for details."); - char[] drives = CDDriveReader.DrivesAvailable(); - if (drives.Length < 1) + int correctionQuality = 1; + string driveLetter = null; + int driveOffset = 0; + for (int arg = 0; arg < args.Length; arg++) { - Console.WriteLine("No CD drives found."); - return; + bool ok = true; + if (args[arg] == "-P" || args[arg] == "--paranoid") + correctionQuality = 4; + //else if (args[arg] == "-B" || args[arg] == "--burst") + // correctionQuality = 1; + else if ((args[arg] == "-D" || args[arg] == "--drive") && ++arg < args.Length) + driveLetter = args[arg]; + else if ((args[arg] == "-O" || args[arg] == "--offset") && ++arg < args.Length) + ok = int.TryParse(args[arg], out driveOffset); + else + ok = false; + if (!ok) + { + Usage(); + return; + } } - char driveLetter = drives[0]; + + char[] drives; + if (driveLetter == null || driveLetter.Length < 1) + { + drives = CDDriveReader.DrivesAvailable(); + if (drives.Length < 1) + { + Console.WriteLine("No CD drives found."); + return; + } + } + else + { + drives = new char[1]; + drives[0] = driveLetter[0]; + } + #if !DEBUG try #endif { CDDriveReader audioSource = new CDDriveReader(); - audioSource.Open(driveLetter); - int driveOffset; - if (!AccurateRipVerify.FindDriveReadOffset(audioSource.ARName, out driveOffset)) - throw new Exception("Failed to find drive read offset for drive" + audioSource.ARName); + audioSource.Open(drives[0]); + + if (audioSource.TOC.AudioTracks < 1) + { + Console.WriteLine("{0}: CD does not contain any audio tracks.", audioSource.Path); + audioSource.Close(); + return; + } + if (driveOffset == 0) + if (!AccurateRipVerify.FindDriveReadOffset(audioSource.ARName, out driveOffset)) + Console.WriteLine("Unknown read offset for drive {0}!!!", audioSource.Path); + //throw new Exception("Failed to find drive read offset for drive" + audioSource.ARName); audioSource.DriveOffset = driveOffset; + audioSource.CorrectionQuality = correctionQuality; - //bool toStdout = false; AccurateRipVerify arVerify = new AccurateRipVerify(audioSource.TOC); int[,] buff = new int[audioSource.BestBlockSize, audioSource.ChannelCount]; string CDDBId = AccurateRipVerify.CalculateCDDBId(audioSource.TOC); @@ -81,7 +168,8 @@ namespace CUERipper release = null; } - string destFile = (release == null) ? "cdimage.flac" : release.GetArtist() + " - " + release.GetTitle() + ".flac"; + //string destFile = (release == null) ? "cdimage.flac" : release.GetArtist() + " - " + release.GetTitle() + ".flac"; + string destFile = (release == null) ? "cdimage.wav" : release.GetArtist() + " - " + release.GetTitle() + ".wav"; Console.WriteLine("Drive : {0}", audioSource.Path); Console.WriteLine("Read offset : {0}", audioSource.DriveOffset); @@ -90,48 +178,53 @@ namespace CUERipper Console.WriteLine("AccurateRip : {0}", arVerify.ARStatus == null ? "ok" : arVerify.ARStatus); Console.WriteLine("MusicBrainz : {0}", release == null ? "not found" : release.GetArtist() + " - " + release.GetTitle()); - IAudioDest audioDest = new FLACWriter(destFile, audioSource.BitsPerSample, audioSource.ChannelCount, audioSource.SampleRate); + //IAudioDest audioDest = new FLACWriter(destFile, audioSource.BitsPerSample, audioSource.ChannelCount, audioSource.SampleRate); + IAudioDest audioDest = new WAVWriter(destFile, audioSource.BitsPerSample, audioSource.ChannelCount, audioSource.SampleRate, null); audioDest.FinalSampleCount = (long)audioSource.Length; - - DateTime start = DateTime.Now; - TimeSpan lastPrint = TimeSpan.FromMilliseconds(0); + ProgressMeter meter = new ProgressMeter(); + audioSource.ReadProgress += new EventHandler(meter.ReadProgress); do { - uint samplesRead = audioSource.Read(buff, Math.Min((uint)buff.GetLength(0), (uint)audioSource.Remaining)); + uint toRead = Math.Min((uint)buff.GetLength(0), (uint)audioSource.Remaining); + uint samplesRead = audioSource.Read(buff, toRead); if (samplesRead == 0) break; + if (samplesRead != toRead) + throw new Exception("samples read != samples requested"); arVerify.Write(buff, samplesRead); audioDest.Write(buff, samplesRead); - TimeSpan elapsed = DateTime.Now - start; - if ((elapsed - lastPrint).TotalMilliseconds > 60) - { - Console.Write("\rProgress : {0:00}%; {1:0.00}x; {2}/{3}", - 100.0 * audioSource.Position / audioSource.Length, - audioSource.Position / elapsed.TotalSeconds / audioSource.SampleRate, - elapsed, - TimeSpan.FromMilliseconds(elapsed.TotalMilliseconds / audioSource.Position * audioSource.Length) - ); - lastPrint = elapsed; - } } while (true); - TimeSpan totalElapsed = DateTime.Now - start; - Console.Write("\r \r"); - Console.WriteLine("Results : {0:0.00}x; {1}", + TimeSpan totalElapsed = DateTime.Now - meter.realStart; + Console.Write("\r \r"); + Console.WriteLine("Results : {0:0.00}x; {1:d5} errors; {2:d2}:{3:d2}:{4:d2}", audioSource.Length / totalElapsed.TotalSeconds / audioSource.SampleRate, - totalElapsed + audioSource.ErrorsCount, + totalElapsed.Hours, totalElapsed.Minutes, totalElapsed.Seconds ); audioDest.Close(); StringWriter logWriter = new StringWriter(); logWriter.WriteLine("{0}", programVersion); - logWriter.WriteLine(); logWriter.WriteLine("Extraction logfile from {0}", DateTime.Now); - logWriter.WriteLine(); logWriter.WriteLine("Used drive : {0}", audioSource.Path); - logWriter.WriteLine(); logWriter.WriteLine("Read offset correction : {0}", audioSource.DriveOffset); + bool wereErrors = false; + for (int iTrack = 1; iTrack <= audioSource.TOC.AudioTracks; iTrack++) + for (uint iSector = audioSource.TOC[iTrack].Start; iSector <= audioSource.TOC[iTrack].End; iSector ++) + if (audioSource.Errors[(int)iSector]) + { + if (!wereErrors) + { + logWriter.WriteLine(); + logWriter.WriteLine("Errors detected"); + logWriter.WriteLine(); + } + wereErrors = true; + logWriter.WriteLine("Track {0} contains errors", iTrack); + break; + } logWriter.WriteLine(); logWriter.WriteLine("TOC of the extracted CD"); logWriter.WriteLine(); @@ -180,6 +273,8 @@ namespace CUERipper } if (audioSource.TOC[track].ISRC != null) cueWriter.WriteLine(" ISRC {0}", audioSource.TOC[track].ISRC); + if (audioSource.TOC[track].PreEmphasis) + cueWriter.WriteLine(" FLAGS PRE"); for (int index = audioSource.TOC[track].Pregap > 0 ? 0 : 1; index <= audioSource.TOC[track].LastIndex; index++) cueWriter.WriteLine(" INDEX {0:00} {1}", index, audioSource.TOC[track][index].MSF); } @@ -190,10 +285,10 @@ namespace CUERipper audioSource.Close(); - FLACReader tagger = new FLACReader(destFile, null); - tagger.Tags.Add("CUESHEET", cueWriter.ToString()); - tagger.Tags.Add("LOG", logWriter.ToString()); - tagger.UpdateTags(false); + //FLACReader tagger = new FLACReader(destFile, null); + //tagger.Tags.Add("CUESHEET", cueWriter.ToString()); + //tagger.Tags.Add("LOG", logWriter.ToString()); + //tagger.UpdateTags(false); } #if !DEBUG catch (Exception ex) @@ -204,5 +299,17 @@ namespace CUERipper } #endif } + + //private void MusicBrainz_LookupProgress(object sender, XmlRequestEventArgs e) + //{ + // if (this.CUEToolsProgress == null) + // return; + // _progress.percentDisk = (1.0 + _progress.percentDisk) / 2; + // _progress.percentTrack = 0; + // _progress.input = e.Uri.ToString(); + // _progress.output = null; + // _progress.status = "Looking up album via MusicBrainz"; + // this.CUEToolsProgress(this, _progress); + //} } } diff --git a/CUETools.Ripper.SCSI/CUETools.Ripper.SCSI.csproj b/CUETools.Ripper.SCSI/CUETools.Ripper.SCSI.csproj index 68e4a62..9607ba5 100644 --- a/CUETools.Ripper.SCSI/CUETools.Ripper.SCSI.csproj +++ b/CUETools.Ripper.SCSI/CUETools.Ripper.SCSI.csproj @@ -81,6 +81,10 @@ + + + + diff --git a/CUETools.Ripper.SCSI/SCSIDrive.cs b/CUETools.Ripper.SCSI/SCSIDrive.cs index db835d9..4676b11 100644 --- a/CUETools.Ripper.SCSI/SCSIDrive.cs +++ b/CUETools.Ripper.SCSI/SCSIDrive.cs @@ -21,6 +21,7 @@ using System; using System.Runtime.InteropServices; +using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Text; @@ -40,16 +41,29 @@ namespace CUETools.Ripper.SCSI byte[] cdtext = null; private Device m_device; int _sampleOffset = 0; - uint _samplesInBuffer = 0; - uint _samplesBufferOffset = 0; - uint _samplesBufferSector = 0; int _driveOffset = 0; - const int CB_AUDIO = 588 * 4 + 16; - const int NSECTORS = 32; + int _correctionQuality = 1; + int _currentStart = -1, _currentEnd = -1, _currentErrorsCount = 0; + const bool DoC2 = true; + const int CB_AUDIO = 588 * 4 + 16 + (DoC2 ? 294 : 0); + //const int REDUNDANCY = 8; + const int NSECTORS = 64; //255 - REDUNDANCY; + const int MSECTORS = 10000000 / CB_AUDIO; int _currentTrack = -1, _currentIndex = -1, _currentTrackActualStart = -1; Logger m_logger; CDImageLayout _toc; DeviceInfo m_info; + Crc16Ccitt _crc; + //RsEncode _rsEncoder; + //RsDecode _rsDecoder; + List _scanResults; + ScanResults _currentScan; + BitArray _errors; + int _errorsCount; + byte[] _currentData = new byte[MSECTORS * 4 * 588]; + int[] valueScore = new int[256]; + + public event EventHandler ReadProgress; public CDImageLayout TOC { @@ -59,9 +73,28 @@ namespace CUETools.Ripper.SCSI } } + public BitArray Errors + { + get + { + return _errors; + } + } + + public int ErrorsCount + { + get + { + return _errorsCount; + } + } + public CDDriveReader() { m_logger = new Logger(); + _crc = new Crc16Ccitt(InitialCrcValue.Zeros); + //_rsEncoder = new RsEncode(REDUNDANCY); + //_rsDecoder = new RsDecode(REDUNDANCY); } public bool Open(char Drive) @@ -97,19 +130,28 @@ namespace CUETools.Ripper.SCSI //ParseProfileList(f.Data); // } - SpeedDescriptorList speed_list; - st = m_device.GetSpeed(out speed_list); - if (st != Device.CommandStatus.Success) - throw new Exception("GetSpeed failed: SCSI error"); + //SpeedDescriptorList speed_list; + //st = m_device.GetSpeed(out speed_list); + //if (st != Device.CommandStatus.Success) + // throw new Exception("GetSpeed failed: SCSI error"); - st = m_device.SetCdSpeed(Device.RotationalControl.CLVandNonPureCav, 32767/*Device.OptimumSpeed*/, Device.OptimumSpeed); - if (st != Device.CommandStatus.Success) - throw new Exception("SetCdSpeed failed: SCSI error"); + //int bytesPerSec = 4 * 588 * 75 * (pass > 8 ? 4 : pass > 4 ? 8 : pass > 0 ? 16 : 32); + //Device.CommandStatus st = m_device.SetStreaming(Device.RotationalControl.CLVandNonPureCav, start, end, bytesPerSec, 1, bytesPerSec, 1); + //if (st != Device.CommandStatus.Success) + // System.Console.WriteLine("SetStreaming: ", (st == Device.CommandStatus.DeviceFailed ? Device.LookupSenseError(m_device.GetSenseAsc(), m_device.GetSenseAscq()) : st.ToString())); + //st = m_device.SetCdSpeed(Device.RotationalControl.CLVandNonPureCav, (ushort)(bytesPerSec / 1024), (ushort)(bytesPerSec / 1024)); + //if (st != Device.CommandStatus.Success) + // System.Console.WriteLine("SetCdSpeed: ", (st == Device.CommandStatus.DeviceFailed ? Device.LookupSenseError(m_device.GetSenseAsc(), m_device.GetSenseAscq()) : st.ToString())); + + + //st = m_device.SetCdSpeed(Device.RotationalControl.CLVandNonPureCav, 32767/*Device.OptimumSpeed*/, Device.OptimumSpeed); + //if (st != Device.CommandStatus.Success) + // throw new Exception("SetCdSpeed failed: SCSI error"); IList toc; st = m_device.ReadToc((byte)0, false, out toc); if (st != Device.CommandStatus.Success) - throw new Exception("ReadToc failed: SCSI error"); + throw new Exception("ReadTOC: " + (st == Device.CommandStatus.DeviceFailed ? Device.LookupSenseError(m_device.GetSenseAsc(), m_device.GetSenseAscq()) : st.ToString())); st = m_device.ReadCDText(out cdtext); // new CDTextEncoderDecoder @@ -119,10 +161,13 @@ namespace CUETools.Ripper.SCSI _toc.AddTrack(new CDTrack((uint)iTrack + 1, toc[iTrack].StartSector, toc[iTrack + 1].StartSector - toc[iTrack].StartSector - - ((toc[iTrack + 1].Control == 0 || iTrack + 1 == toc.Count - 1) ? 0U : 152U * 75U), - toc[iTrack].Control == 0)); + ((toc[iTrack + 1].Control < 4 || iTrack + 1 == toc.Count - 1) ? 0U : 152U * 75U), + toc[iTrack].Control < 4, + (toc[iTrack].Control & 1) == 1)); if (_toc[1].IsAudio) _toc[1][0].Start = 0; + if (_toc.AudioLength > 0) + _errors = new BitArray((int)_toc.AudioLength); // !!! return true; } @@ -137,32 +182,54 @@ namespace CUETools.Ripper.SCSI { get { - return Math.Min(m_device.MaximumTransferLength / CB_AUDIO, NSECTORS) * 588; + return Math.Min(m_device.MaximumTransferLength / CB_AUDIO - 1, NSECTORS) * 588; } } - private void ProcessSubchannel(int sector, int Sectors2Read) + private void ProcessSubchannel(int sector, int Sectors2Read, bool updateMap) { for (int iSector = 0; iSector < Sectors2Read; iSector++) { - int q_pos = (iSector + 1) * (588 * 4 + 16) - 16; - int ctl = _sectorBuffer[q_pos + 0] >> 4; - int adr = _sectorBuffer[q_pos + 0] & 7; - bool preemph = (ctl == 1); + int q_pos = (sector - _currentStart + iSector + 1) * CB_AUDIO - 16; + int ctl = _currentScan.Data[q_pos + 0] >> 4; + int adr = _currentScan.Data[q_pos + 0] & 7; + bool preemph = (ctl & 1) == 1; switch (adr) { case 1: // current position { - int iTrack = fromBCD(_sectorBuffer[q_pos + 1]); - int iIndex = fromBCD(_sectorBuffer[q_pos + 2]); + int iTrack = fromBCD(_currentScan.Data[q_pos + 1]); + int iIndex = fromBCD(_currentScan.Data[q_pos + 2]); + int mm = fromBCD(_currentScan.Data[q_pos + 7]); + int ss = fromBCD(_currentScan.Data[q_pos + 8]); + int ff = fromBCD(_currentScan.Data[q_pos + 9]); + int sec = ff + 75 * (ss + 60 * mm) - 150; // sector + iSector; + //if (sec != sector + iSector) + // System.Console.WriteLine("\rLost sync: {0} vs {1} ({2:X} vs {3:X})", CDImageLayout.TimeToString((uint)(sector + iSector)), CDImageLayout.TimeToString((uint)sec), sector + iSector, sec); + //ushort crc = _crc.ComputeChecksum(_currentScan.Data, q_pos, 10); + //crc ^= 0xffff; + //if (_currentScan.Data[q_pos + 10] != 0 && _currentScan.Data[q_pos + 11] != 0 && + // ((crc & 0xff) != _currentScan.Data[q_pos + 11] || + // (crc >> 8) != _currentScan.Data[q_pos + 10]) + // ) + //{ + // System.Console.WriteLine("CRC error at {0}", CDImageLayout.TimeToString((uint)(sector + iSector))); + //} if (iTrack == 110) - throw new Exception("lead out area encountred"); + { + if (sector + iSector + 75 < _toc.AudioLength) + throw new Exception("lead out area encountred"); + // make sure that data is zero? + return; + } if (iTrack == 0) throw new Exception("lead in area encountred"); + if (!updateMap) + break; if (iTrack != _currentTrack) { _currentTrack = iTrack; - _currentTrackActualStart = sector + iSector; + _currentTrackActualStart = sec; _currentIndex = iIndex; if (_currentIndex != 1 && _currentIndex != 0) throw new Exception("invalid index"); @@ -174,13 +241,15 @@ namespace CUETools.Ripper.SCSI _currentIndex = iIndex; if (_currentIndex == 1) { - uint pregap = (uint) (sector + iSector - _currentTrackActualStart); + uint pregap = (uint)(sec - _currentTrackActualStart); _toc[iTrack][0].Start = _toc[iTrack].Start - pregap; - _currentTrackActualStart = sector + iSector; + _currentTrackActualStart = sec; } else - _toc[iTrack].AddIndex(new CDTrackIndex((uint)iIndex, (uint)(_toc[iTrack].Start + sector + iSector - _currentTrackActualStart))); + _toc[iTrack].AddIndex(new CDTrackIndex((uint)iIndex, (uint)(_toc[iTrack].Start + sec - _currentTrackActualStart))); _currentIndex = iIndex; } + if (preemph) + _toc[iTrack].PreEmphasis = true; break; } case 2: // catalog @@ -188,7 +257,7 @@ namespace CUETools.Ripper.SCSI { StringBuilder catalog = new StringBuilder(); for (int i = 1; i < 8; i++) - catalog.AppendFormat("{0:x2}", _sectorBuffer[q_pos + i]); + catalog.AppendFormat("{0:x2}", _currentScan.Data[q_pos + i]); _toc.Catalog = catalog.ToString(0, 13); } break; @@ -197,16 +266,16 @@ namespace CUETools.Ripper.SCSI { StringBuilder isrc = new StringBuilder(); char[] ISRC6 = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '#', '#', '#', '#', '#', '#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; - isrc.Append(ISRC6[_sectorBuffer[q_pos + 1] >> 2]); - isrc.Append(ISRC6[((_sectorBuffer[q_pos + 1] & 0x3) << 4) + (_sectorBuffer[q_pos + 2] >> 4)]); - isrc.Append(ISRC6[((_sectorBuffer[q_pos + 2] & 0xf) << 2) + (_sectorBuffer[q_pos + 3] >> 6)]); - isrc.Append(ISRC6[(_sectorBuffer[q_pos + 3] & 0x3f)]); - isrc.Append(ISRC6[_sectorBuffer[q_pos + 4] >> 2]); - isrc.Append(ISRC6[((_sectorBuffer[q_pos + 4] & 0x3) << 4) + (_sectorBuffer[q_pos + 5] >> 4)]); - isrc.AppendFormat("{0:x}", _sectorBuffer[q_pos + 5] & 0xf); - isrc.AppendFormat("{0:x2}", _sectorBuffer[q_pos + 6]); - isrc.AppendFormat("{0:x2}", _sectorBuffer[q_pos + 7]); - isrc.AppendFormat("{0:x}", _sectorBuffer[q_pos + 8] >> 4); + isrc.Append(ISRC6[_currentScan.Data[q_pos + 1] >> 2]); + isrc.Append(ISRC6[((_currentScan.Data[q_pos + 1] & 0x3) << 4) + (_currentScan.Data[q_pos + 2] >> 4)]); + isrc.Append(ISRC6[((_currentScan.Data[q_pos + 2] & 0xf) << 2) + (_currentScan.Data[q_pos + 3] >> 6)]); + isrc.Append(ISRC6[(_currentScan.Data[q_pos + 3] & 0x3f)]); + isrc.Append(ISRC6[_currentScan.Data[q_pos + 4] >> 2]); + isrc.Append(ISRC6[((_currentScan.Data[q_pos + 4] & 0x3) << 4) + (_currentScan.Data[q_pos + 5] >> 4)]); + isrc.AppendFormat("{0:x}", _currentScan.Data[q_pos + 5] & 0xf); + isrc.AppendFormat("{0:x2}", _currentScan.Data[q_pos + 6]); + isrc.AppendFormat("{0:x2}", _currentScan.Data[q_pos + 7]); + isrc.AppendFormat("{0:x}", _currentScan.Data[q_pos + 8] >> 4); _toc[_currentTrack].ISRC = isrc.ToString(); } break; @@ -216,117 +285,294 @@ namespace CUETools.Ripper.SCSI private unsafe void FetchSectors(int sector, int Sectors2Read) { - fixed (byte* data = _sectorBuffer) + fixed (byte* data = _currentScan.Data) { - Device.CommandStatus st = m_device.ReadCDAndSubChannel(2, 1, true, (uint)sector, (uint)Sectors2Read, (IntPtr)((void*)data), Sectors2Read * (2352 + 16)); + //Device.CommandStatus st; + //using (Command cmd = new Command(ScsiCommandCode.Read12, 12, (IntPtr)((void*)data), Sectors2Read * 588 * 4, Command.CmdDirection.In, 5 * 60)) + //{ + // //cmd.SetCDB8(1, 4); // force from media + // cmd.SetCDB32(2, sector); + // cmd.SetCDB32(6, Sectors2Read); + // //cmd.SetCDB8(10, 0x80); // streaming + // st= m_device.SendCommand(cmd); + //} + Device.CommandStatus st = m_device.ReadCDAndSubChannel(2, DoC2, 1, false, (uint)sector, (uint)Sectors2Read, (IntPtr)((void*)(data + (sector - _currentStart) * CB_AUDIO)), Sectors2Read * CB_AUDIO); if (st != Device.CommandStatus.Success) - throw new Exception("ReadCDAndSubChannel failed: SCSI error"); + { + if (st == Device.CommandStatus.DeviceFailed && m_device.GetSenseAsc() == 0x64 && m_device.GetSenseAscq() == 0x00) + { + int iErrors = 0; + for (int iSector = 0; iSector < Sectors2Read; iSector++) + { + st = m_device.ReadCDAndSubChannel(2, DoC2, 1, false, (uint)(sector + iSector), 1U, (IntPtr)((void*)(data + (sector + iSector - _currentStart) * CB_AUDIO)), CB_AUDIO); + if (st != Device.CommandStatus.Success) + { + iErrors ++; + for (int iPos = 0; iPos < CB_AUDIO; iPos++) + data[(sector + iSector - _currentStart) * CB_AUDIO + iPos] = (DoC2 && iPos >= 4 * 588 && iPos < 4 * 588 + 294) ? (byte)255 : (byte)0; + } + } + if (iErrors < Sectors2Read) + return; + } + string status = (st == Device.CommandStatus.DeviceFailed ? Device.LookupSenseError(m_device.GetSenseAsc(), m_device.GetSenseAscq()) : st.ToString()); + st = m_device.ReadCDAndSubChannel(0, false, 1, false, (uint)sector, (uint)Sectors2Read, (IntPtr)((void*)(data + (sector - _currentStart) * 4 * 588)), Sectors2Read * 4 * 588); + if (st != Device.CommandStatus.Success) + status += "; ReadWithoutSubchannel: " + (st == Device.CommandStatus.DeviceFailed ? Device.LookupSenseError(m_device.GetSenseAsc(), m_device.GetSenseAscq()) : st.ToString()); + else + status += "; ReadWithoutSubchannel: might work"; + throw new Exception("ReadCD: " + status); + } + + //int localC2 = 0; + //if (DoC2) + // for (int iSector = 0; iSector < Sectors2Read; iSector++) + // { + // _currentScan.C2Errors[sector + iSector] = data[(iSector + 1) * CB_AUDIO - 18] != 0; + // localC2 += (data[(iSector + 1) * CB_AUDIO - 18] != 0) ? 1 : 0; + // } + //_currentScan.C2Count += localC2; } - ProcessSubchannel(sector, Sectors2Read); } - public unsafe uint Read(int[,] buff, uint sampleCount) + private unsafe void CorrectSectors(int sector, int Sectors2Read, bool findWorst, bool markErrors) + { + int c2Score = 10; + for (int iSector = 0; iSector < Sectors2Read; iSector++) + { + for (int iPar = 0; iPar < 4 * 588; iPar++) + { + int dataPos = (sector - _currentStart + iSector) * CB_AUDIO + iPar; + int c2Pos = (sector - _currentStart + iSector) * CB_AUDIO + 4 * 588 + iPar / 8; + int c2Bit = 0x80 >> (iPar % 8); + + Array.Clear(valueScore, 0, 256); + + byte bestValue = _currentScan.Data[dataPos]; + valueScore[bestValue] += 1 + (((_currentScan.Data[c2Pos] & c2Bit) == 0) ? c2Score : 0); + int totalScore = valueScore[bestValue]; + for (int result = 0; result < _scanResults.Count; result++) + { + byte value = _scanResults[result].Data[dataPos]; + valueScore[value] += 1 + (((_scanResults[result].Data[c2Pos] & c2Bit) == 0) ? c2Score : 0); + totalScore += 1 + (((_scanResults[result].Data[c2Pos] & c2Bit) == 0) ? c2Score : 0); + if (valueScore[value] > valueScore[bestValue]) + bestValue = value; + } + bool fError = valueScore[bestValue] * 2 <= c2Score + totalScore; + if (fError) + _currentErrorsCount++; + _currentData[(sector - _currentStart + iSector) * 4 * 588 + iPar] = bestValue; + if (findWorst) + { + for (int result = 0; result < _scanResults.Count; result++) + _scanResults[result].Quality += Math.Min(0, valueScore[_scanResults[result].Data[dataPos]] - c2Score - 1); + _currentScan.Quality += Math.Min(0, valueScore[_currentScan.Data[dataPos]] - c2Score - 1); + } + if (markErrors) + { + _errors[sector + iSector] = fError; + _errorsCount += fError ? 1 : 0; + } + } + } + } + + //private unsafe int CorrectSectorsTest(int start, int end, int c2Score, byte[] realData, int worstScan) + //{ + // int[] valueScore = new int[256]; + // int[] scoreErrors = new int[256]; + // int realErrors = 0; + // int bestScore = 0; + // int _errorsCaught = 0; + // int _falsePositives = 0; + // for (int iSector = 0; iSector < end - start; iSector++) + // { + // for (int iPar = 0; iPar < 4 * 588; iPar++) + // { + // int dataPos = iSector * CB_AUDIO + iPar; + // int c2Pos = iSector * CB_AUDIO + 4 * 588 + iPar / 8; + // int c2Bit = 0x80 >> (iPar % 8); + + // Array.Clear(valueScore, 0, 256); + + // byte bestValue = _currentScan.Data[dataPos]; + // valueScore[bestValue] += 1 + (((_currentScan.Data[c2Pos] & c2Bit) == 0) ? c2Score : 0); + // int totalScore = valueScore[bestValue]; + // for (int result = 0; result < _scanResults.Count; result++) + // { + // if (result == worstScan) + // continue; + // byte value = _scanResults[result].Data[dataPos]; + // valueScore[value] += 1 + (((_scanResults[result].Data[c2Pos] & c2Bit) == 0) ? c2Score : 0); + // totalScore += 1 + (((_scanResults[result].Data[c2Pos] & c2Bit) == 0) ? c2Score : 0); + // if (valueScore[value] > valueScore[bestValue]) + // bestValue = value; + // } + // if (valueScore[bestValue] < (1 + c2Score + totalScore) / 2) + // _currentErrorsCount++; + // //_currentData[iSector * 4 * 588 + iPar] = bestValue; + // if (realData[iSector * 4 * 588 + iPar] != bestValue) + // { + // if (valueScore[bestValue] > bestScore) + // scoreErrors[valueScore[bestValue]]++; + // realErrors++; + // if (valueScore[bestValue] * 2 <= c2Score + totalScore) + // _errorsCaught++; + // } else + // if (valueScore[bestValue] * 2 <= c2Score + totalScore) + // _falsePositives++; + // } + // } + // //string s = ""; + // //for (int i = 0; i < 256; i++) + // // if (scoreErrors[i] > 0) + // // s += string.Format("[{0}]={1};", i, scoreErrors[i]); + // //System.Console.WriteLine("RE{0:000} EC{1} FP{2}", realErrors, _errorsCaught, _falsePositives); + // return realErrors; + //} + + public void PrefetchSector(int iSector) + { + int nPasses = 16 + _correctionQuality * 2; + int nExtraPasses = 8 + _correctionQuality; + + if (_currentStart == MSECTORS * (iSector / MSECTORS)) + return; + + _currentStart = MSECTORS * (iSector / MSECTORS); + _currentEnd = Math.Min(_currentStart + MSECTORS, (int)_toc.AudioLength); + _scanResults = new List(); + + //FileStream correctFile = new FileStream("correct.wav", FileMode.Open); + //byte[] realData = new byte[MSECTORS * 4 * 588]; + //correctFile.Seek(0x2C + start * 588 * 4, SeekOrigin.Begin); + //if (correctFile.Read(realData, 0, MSECTORS * 4 * 588) != MSECTORS * 4 * 588) + // throw new Exception("read"); + //correctFile.Close(); + + for (int pass = 0; pass <= nPasses + nExtraPasses; pass++) + { + DateTime PassTime = DateTime.Now; + _currentScan = new ScanResults(MSECTORS, CB_AUDIO); + _currentErrorsCount = 0; + int nSectors = Math.Min(NSECTORS, m_device.MaximumTransferLength / CB_AUDIO - 1); + for (int sector = _currentStart; sector < _currentEnd; sector += nSectors) + { + int Sectors2Read = Math.Min(nSectors, _currentEnd - sector); + FetchSectors(sector, Sectors2Read); + CorrectSectors(sector, Sectors2Read, pass > nPasses, pass == nPasses + nExtraPasses); + ProcessSubchannel(sector, Sectors2Read, pass == 0); + if (ReadProgress != null) + ReadProgress(this, new ReadProgressArgs(sector + Sectors2Read, pass, _currentStart, _currentEnd, _currentErrorsCount, PassTime)); + } + //System.Console.WriteLine(); + //if (CorrectSectorsTest(start, _currentEnd, 10, realData) == 0) + // break; + if (pass == nPasses + nExtraPasses) + break; + if (pass > nPasses) + { + int worstPass = -1; + for (int result = 0; result < _scanResults.Count; result++) + if (_scanResults[result].Quality < (worstPass < 0 ? _currentScan.Quality : _scanResults[worstPass].Quality)) + worstPass = result; + //if (worstPass < 0) + // System.Console.WriteLine("bad scan"); + //else + // System.Console.WriteLine("{0}->{1}, {2}->{3}", _scanResults[worstPass].Quality, _currentScan.Quality, CorrectSectorsTest(_currentStart, _currentEnd, 10, realData, -1), CorrectSectorsTest(_currentStart, _currentEnd, 10, realData, worstPass)); + if (worstPass < 0) + _currentScan = null; + else + _scanResults[worstPass] = _currentScan; + for (int result = 0; result < _scanResults.Count; result++) + _scanResults[result].Quality = 0; + continue; + } + if (_currentErrorsCount == 0 && pass >= _correctionQuality) + { + bool syncOk = true; + //if (pass == 0) + //{ + // ScanResults saved = _currentScan; + // _currentScan = new ScanResults(_currentEnd - _currentStart, CB_AUDIO); + // for (int sector = _currentStart; sector < _currentEnd && syncOk; sector += 7) + // { + // FetchSectors(sector, 2); + // for (int i = 0; i < 2 * CB_AUDIO; i++) + // if (_currentScan.Data[(sector - _currentStart) * CB_AUDIO + i] != saved.Data[(sector - _currentStart) * CB_AUDIO + i]) + // { + // System.Console.WriteLine("Lost Sync"); + // syncOk = false; + // break; + // } + // } + // _currentScan = saved; + //} + if (syncOk) + break; + } + _scanResults.Add(_currentScan); + } + _currentScan = null; + _scanResults = null; + } + + public uint Read(int[,] buff, uint sampleCount) { if (_toc == null) throw new Exception("Read: invalid TOC"); if (_sampleOffset - _driveOffset >= (uint)Length) - return 0; + return 0; if (_sampleOffset > (uint)Length) { - int samplesRead = _sampleOffset - (int)Length; - for (int i = 0; i < samplesRead; i++) - for (int c = 0; c < ChannelCount; c++) - buff[i, c] = 0; - _sampleOffset += samplesRead; - return 0; + int samplesRead = _sampleOffset - (int)Length; + for (int i = 0; i < samplesRead; i++) + for (int c = 0; c < ChannelCount; c++) + buff[i, c] = 0; + _sampleOffset += samplesRead; + return 0; } if ((uint)(_sampleOffset - _driveOffset + sampleCount) > Length) - sampleCount = (uint)((int)Length + _driveOffset - _sampleOffset); + sampleCount = (uint)((int)Length + _driveOffset - _sampleOffset); int silenceCount = 0; if ((uint)(_sampleOffset + sampleCount) > Length) { - silenceCount = _sampleOffset + (int)sampleCount - (int)Length; - sampleCount -= (uint) silenceCount; + silenceCount = _sampleOffset + (int)sampleCount - (int)Length; + sampleCount -= (uint) silenceCount; } uint pos = 0; if (_sampleOffset < 0) { - uint nullSamplesRead = Math.Min((uint)-_sampleOffset, sampleCount); - for (int i = 0; i < nullSamplesRead; i++) - for (int c = 0; c < ChannelCount; c++) - buff[i, c] = 0; - pos += nullSamplesRead; - sampleCount -= nullSamplesRead; - _sampleOffset += (int)nullSamplesRead; - if (sampleCount == 0) - return pos; + uint nullSamplesRead = Math.Min((uint)-_sampleOffset, sampleCount); + for (int i = 0; i < nullSamplesRead; i++) + for (int c = 0; c < ChannelCount; c++) + buff[i, c] = 0; + pos += nullSamplesRead; + sampleCount -= nullSamplesRead; + _sampleOffset += (int)nullSamplesRead; + if (sampleCount == 0) + return pos; } - if (_samplesInBuffer > 0) - { - uint samplesRead = Math.Min(_samplesInBuffer, sampleCount); - AudioSamples.BytesToFLACSamples_16(_sectorBuffer, (int)(_samplesBufferSector * (588 * 4 + 16) + _samplesBufferOffset * 4), buff, (int)pos, samplesRead, 2); - pos += samplesRead; - sampleCount -= samplesRead; - _sampleOffset += (int) samplesRead; - if (sampleCount == 0) - { - _samplesInBuffer -= samplesRead; - _samplesBufferOffset += samplesRead; - if (silenceCount > 0) - { - uint nullSamplesRead = (uint) silenceCount; - for (int i = 0; i < nullSamplesRead; i++) - for (int c = 0; c < ChannelCount; c++) - buff[pos + i, c] = 0; - pos += nullSamplesRead; - _sampleOffset += (int)nullSamplesRead; - } - return pos; - } - _samplesInBuffer = 0; - _samplesBufferOffset = 0; - _samplesBufferSector = 0; - } - // if (_sampleOffset < PreGapLength && !_overreadIntoPreGap ... ? int firstSector = (int)_sampleOffset / 588; - int lastSector = (int)(_sampleOffset + sampleCount + 587) / 588; - for (int sector = firstSector; sector < lastSector; sector += NSECTORS) + int lastSector = Math.Min((int)(_sampleOffset + sampleCount + 587)/588, (int)_toc.AudioLength); + for (int sector = firstSector; sector < lastSector; sector ++) { - int Sectors2Read = ((sector + NSECTORS) < lastSector) ? NSECTORS : (lastSector - sector); - FetchSectors(sector, Sectors2Read); - for (int iSector = 0; iSector < Sectors2Read; iSector++) - { - uint samplesRead = (uint) (Math.Min((int)sampleCount, 588) - (_sampleOffset % 588)); - AudioSamples.BytesToFLACSamples_16(_sectorBuffer, iSector * (588 * 4 + 16) + ((int)_sampleOffset % 588) * 4, buff, (int)pos, samplesRead, 2); - pos += samplesRead; - sampleCount -= samplesRead; - _sampleOffset += (int) samplesRead; - if (sampleCount == 0) - { - _samplesBufferSector = (uint)iSector; - _samplesBufferOffset = samplesRead; - _samplesInBuffer = 588U - samplesRead; - if (silenceCount > 0) - { - uint nullSamplesRead = (uint)silenceCount; - for (int i = 0; i < nullSamplesRead; i++) - for (int c = 0; c < ChannelCount; c++) - buff[pos + i, c] = 0; - pos += nullSamplesRead; - _sampleOffset += (int)nullSamplesRead; - } - return pos; - } - } + PrefetchSector(sector); + uint samplesRead = (uint) (Math.Min((int)sampleCount, 588 - (_sampleOffset % 588))); + AudioSamples.BytesToFLACSamples_16(_currentData, (sector - _currentStart) * 4 * 588 + ((int)_sampleOffset % 588) * 4, buff, (int)pos, samplesRead, 2); + pos += samplesRead; + sampleCount -= samplesRead; + _sampleOffset += (int) samplesRead; } if (silenceCount > 0) { - uint nullSamplesRead = (uint)silenceCount; - for (int i = 0; i < nullSamplesRead; i++) - for (int c = 0; c < ChannelCount; c++) - buff[pos + i, c] = 0; - pos += nullSamplesRead; - _sampleOffset += (int)nullSamplesRead; + uint nullSamplesRead = (uint)silenceCount; + for (int i = 0; i < nullSamplesRead; i++) + for (int c = 0; c < ChannelCount; c++) + buff[pos + i, c] = 0; + pos += nullSamplesRead; + _sampleOffset += (int)nullSamplesRead; } return pos; } @@ -430,14 +676,24 @@ namespace CUETools.Ripper.SCSI } } + public int CorrectionQuality + { + get + { + return _correctionQuality; + } + set + { + _correctionQuality = value; + } + } + public static string RipperVersion() { return "CUERipper v1.9.3 Copyright (C) 2008 Gregory S. Chudov"; // ripper.GetName().Name + " " + ripper.GetName().Version; } - byte[] _sectorBuffer = new byte[CB_AUDIO * NSECTORS]; - private int fromBCD(byte hex) { return (hex >> 4) * 10 + (hex & 15); @@ -452,4 +708,35 @@ namespace CUETools.Ripper.SCSI return result.ToArray(); } } + + internal class ScanResults + { + public byte[] Data; + public long Quality; + + public ScanResults(int msector, int cbaudio) + { + Data = new byte[msector * cbaudio]; + Quality = 0; + } + } + + public sealed class ReadProgressArgs: EventArgs + { + public int Position; + public int Pass; + public int PassStart, PassEnd; + public int ErrorsCount; + public DateTime PassTime; + + public ReadProgressArgs(int position, int pass, int passStart, int passEnd, int errorsCount, DateTime passTime) + { + Position = position; + Pass = pass; + PassStart = passStart; + PassEnd = passEnd; + ErrorsCount = errorsCount; + PassTime = passTime; + } + } } diff --git a/CUEToolsLib/Main.cs b/CUEToolsLib/Main.cs index 1f3ddea..d88d8c9 100644 --- a/CUEToolsLib/Main.cs +++ b/CUEToolsLib/Main.cs @@ -20,6 +20,7 @@ // **************************************************************************** using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Text; @@ -436,6 +437,7 @@ namespace CUETools.Processor private bool _isCD; private string _driveName; private int _driveOffset; + private BitArray _cdErrors; private bool _isArchive; private List _archiveContents; private string _archiveCUEpath; @@ -696,7 +698,7 @@ namespace CUETools.Processor } trackInfo = new TrackInfo(); _tracks.Add(trackInfo); - _toc.AddTrack(new CDTrack((uint)trackNumber, 0, 0, true)); + _toc.AddTrack(new CDTrack((uint)trackNumber, 0, 0, true, false)); } } else if (seenDataTrack) { @@ -892,7 +894,7 @@ namespace CUETools.Processor uint trNo, trStart, trEnd; if (n.Length == 5 && uint.TryParse(n[0], out trNo) && uint.TryParse(n[3], out trStart) && uint.TryParse(n[4], out trEnd)) tocFromLog.AddTrack(new CDTrack(trNo, trStart, trEnd + 1 - trStart, - tocFromLog.TrackCount < _toc.TrackCount || trStart != tocFromLog[tocFromLog.TrackCount].End + 1U + 152U * 75U)); + tocFromLog.TrackCount < _toc.TrackCount || trStart != tocFromLog[tocFromLog.TrackCount].End + 1U + 152U * 75U, false)); } else if (lineStr.StartsWith("TOC of the extracted CD") || lineStr.StartsWith("Exact Audio Copy") @@ -906,7 +908,7 @@ namespace CUETools.Processor if (_accurateRipId == null && _dataTrackLength != null) { CDImageLayout toc2 = new CDImageLayout(_toc); - toc2.AddTrack(new CDTrack((uint)_toc.TrackCount, _toc.Length + 152U * 75U, _dataTrackLength.Value, false)); + toc2.AddTrack(new CDTrack((uint)_toc.TrackCount, _toc.Length + 152U * 75U, _dataTrackLength.Value, false, false)); _accurateRipId = AccurateRipVerify.CalculateAccurateRipId(toc2); } @@ -1216,18 +1218,39 @@ namespace CUETools.Processor _toc[track].LengthMSF, _toc[track].Start, _toc[track].End); - logWriter.WriteLine(); - if (_accurateRipMode == AccurateRipMode.VerifyThenConvert) - for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) - if (_arVerify.BackupCRC(iTrack) != _arVerify.CRC(iTrack)) - logWriter.WriteLine("Track {0} CRC mismatch: test {1:X8} vs copy {2:X8}", iTrack + 1, _arVerify.BackupCRC(iTrack), _arVerify.CRC(iTrack)); + bool wereErrors = false; + for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) + { + int cdErrors = 0; + bool crcMismatch = _accurateRipMode == AccurateRipMode.VerifyThenConvert && + _arVerify.BackupCRC(iTrack) != _arVerify.CRC(iTrack); + for (uint iSector = _toc[iTrack + 1].Start; iSector <= _toc[iTrack + 1].End; iSector++) + if (_cdErrors[(int)iSector]) + cdErrors++; + if (crcMismatch || cdErrors != 0) + { + if (!wereErrors) + { + logWriter.WriteLine(); + logWriter.WriteLine("Errors detected"); + logWriter.WriteLine(); + } + wereErrors = true; + if (crcMismatch) + logWriter.WriteLine("Track {0} contains {1} errors, CRC mismatch: test {2:X8} vs copy {3:X8}", iTrack + 1, cdErrors, _arVerify.BackupCRC(iTrack), _arVerify.CRC(iTrack)); + else + logWriter.WriteLine("Track {0} contains {1} errors", iTrack + 1, cdErrors); + } + } if (_accurateRipMode != AccurateRipMode.None) { + logWriter.WriteLine(); logWriter.WriteLine("AccurateRip summary"); logWriter.WriteLine(); _arVerify.GenerateFullLog(logWriter, 0); logWriter.WriteLine(); } + logWriter.WriteLine(); logWriter.WriteLine("End of status report"); logWriter.Close(); return logWriter.ToString(); @@ -1494,7 +1517,7 @@ namespace CUETools.Processor { uint minDTL = _minDataTrackLength.Value; CDImageLayout toc2 = new CDImageLayout(_toc); - toc2.AddTrack(new CDTrack((uint)_toc.TrackCount, _toc.Length + 152 * 75, minDTL, false)); + toc2.AddTrack(new CDTrack((uint)_toc.TrackCount, _toc.Length + 152 * 75, minDTL, false, false)); for (uint dtl = minDTL; dtl < minDTL + 75; dtl++) { toc2[toc2.TrackCount].Length = dtl; @@ -1608,7 +1631,7 @@ namespace CUETools.Processor { WriteText(_cuePath, cueContents); #if !MONO - if (needNewCRCs) + if (needNewCRCs && style != CUEStyle.SingleFile) { for (int iTrack = 0; iTrack < TrackCount; iTrack++) { @@ -1972,7 +1995,10 @@ namespace CUETools.Processor { #if !MONO if (_isCD && audioSource != null && audioSource is CDDriveReader) + { updatedTOC = ((CDDriveReader)audioSource).TOC; + _cdErrors = ((CDDriveReader)audioSource).Errors; + } #endif if (audioSource != null) audioSource.Close(); audioSource = GetAudioSource(++iSource); @@ -2057,15 +2083,22 @@ namespace CUETools.Processor #if !MONO if (_isCD && audioSource != null && audioSource is CDDriveReader) + { updatedTOC = ((CDDriveReader)audioSource).TOC; + _cdErrors = ((CDDriveReader)audioSource).Errors; + } if (updatedTOC != null) { _toc = updatedTOC; if (_toc.Catalog != null) General.SetCUELine(_attributes, "CATALOG", _toc.Catalog, false); for (iTrack = 1; iTrack <= _toc.TrackCount; iTrack++) + { if (_toc[iTrack].IsAudio && _toc[iTrack].ISRC != null) General.SetCUELine(_tracks[iTrack - 1].Attributes, "ISRC", _toc[iTrack].ISRC, false); + if (_toc[iTrack].IsAudio && _toc[iTrack].PreEmphasis) + General.SetCUELine(_tracks[iTrack - 1].Attributes, "FLAGS", "PRE", false); + } } #endif @@ -2401,7 +2434,7 @@ namespace CUETools.Processor { _dataTrackLength = dtl; CDImageLayout toc2 = new CDImageLayout(_toc); - toc2.AddTrack(new CDTrack((uint)_toc.TrackCount, _toc.Length + 152 * 75, dtl, false)); + toc2.AddTrack(new CDTrack((uint)_toc.TrackCount, _toc.Length + 152 * 75, dtl, false, false)); _accurateRipIdActual = _accurateRipId = AccurateRipVerify.CalculateAccurateRipId(toc2); } } diff --git a/MAC_SDK/Source/MACLib/Assembly/Assembly.obj b/MAC_SDK/Source/MACLib/Assembly/Assembly.obj index e718d81..c1db5fd 100644 Binary files a/MAC_SDK/Source/MACLib/Assembly/Assembly.obj and b/MAC_SDK/Source/MACLib/Assembly/Assembly.obj differ