CD parity information database

This commit is contained in:
chudov
2010-02-23 15:15:08 +00:00
parent b613227ada
commit 5b9b266de5
45 changed files with 2017 additions and 793 deletions

View File

@@ -61,7 +61,7 @@ namespace CUETools.Processor
return new LossyWAVReader(lossySource, lwcdfSource);
}
public static IAudioDest GetAudioDest(AudioEncoderType audioEncoderType, string path, AudioPCMConfig pcm, long finalSampleCount, int padding, string extension, CUEConfig config)
public static IAudioDest GetAudioDest(AudioEncoderType audioEncoderType, string path, AudioPCMConfig pcm, long finalSampleCount, int padding, string extension, CUEConfig config)
{
IAudioDest dest;
if (audioEncoderType == AudioEncoderType.NoAudio || extension == ".dummy")
@@ -73,7 +73,7 @@ namespace CUETools.Processor
CUEToolsFormat fmt;
if (!extension.StartsWith(".") || !config.formats.TryGetValue(extension.Substring(1), out fmt))
throw new Exception("Unsupported audio type: " + path);
CUEToolsUDC encoder = audioEncoderType == AudioEncoderType.Lossless ? fmt.encoderLossless :
CUEToolsUDC encoder = audioEncoderType == AudioEncoderType.Lossless ? fmt.encoderLossless :
audioEncoderType == AudioEncoderType.Lossy ? fmt.encoderLossy :
null;
if (encoder == null)
@@ -86,37 +86,39 @@ namespace CUETools.Processor
if (o == null || !(o is IAudioDest))
throw new Exception("Unsupported audio type: " + path + ": " + encoder.type.FullName);
dest = o as IAudioDest;
} else
}
else
throw new Exception("Unsupported audio type: " + path);
dest.CompressionLevel = encoder.DefaultModeIndex;
dest.FinalSampleCount = finalSampleCount;
switch (encoder.type.FullName)
{
case "CUETools.Codecs.ALAC.ALACWriter":
dest.Options = string.Format("--padding-length {0}", padding);
break;
case "CUETools.Codecs.FLAKE.FlakeWriter":
dest.Options = string.Format("--padding-length {0}", padding);
break;
case "CUETools.Codecs.FlaCuda.FlaCudaWriter":
dest.Options = string.Format("{0}{1}--padding-length {2} --cpu-threads {3}",
config.flaCudaVerify ? "--verify " : "",
config.flaCudaGPUOnly ? "--gpu-only " : "",
padding,
config.FlaCudaThreads ? 1 : 0);
break;
case "CUETools.Codecs.FLAC.FLACWriter":
dest.Options = string.Format("{0}{1}--padding-length {2}",
config.disableAsm ? "--disable-asm " : "",
config.flacVerify ? "--verify " : "",
padding);
break;
case "CUETools.Codecs.WavPack.WavPackWriter":
dest.Options = string.Format("{0}--extra-mode {1}",
config.wvStoreMD5 ? "--md5 " : "",
config.wvExtraMode);
break;
}
if (encoder.type != null)
switch (encoder.type.FullName)
{
case "CUETools.Codecs.ALAC.ALACWriter":
dest.Options = string.Format("--padding-length {0}", padding);
break;
case "CUETools.Codecs.FLAKE.FlakeWriter":
dest.Options = string.Format("--padding-length {0}", padding);
break;
case "CUETools.Codecs.FlaCuda.FlaCudaWriter":
dest.Options = string.Format("{0}{1}--padding-length {2} --cpu-threads {3}",
config.flaCudaVerify ? "--verify " : "",
config.flaCudaGPUOnly ? "--gpu-only " : "",
padding,
config.FlaCudaThreads ? 1 : 0);
break;
case "CUETools.Codecs.FLAC.FLACWriter":
dest.Options = string.Format("{0}{1}--padding-length {2}",
config.disableAsm ? "--disable-asm " : "",
config.flacVerify ? "--verify " : "",
padding);
break;
case "CUETools.Codecs.WavPack.WavPackWriter":
dest.Options = string.Format("{0}--extra-mode {1}",
config.wvStoreMD5 ? "--md5 " : "",
config.wvExtraMode);
break;
}
return dest;
}

View File

@@ -34,7 +34,7 @@
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\bin\win32\Debug\</OutputPath>
<OutputPath>..\bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
@@ -74,6 +74,10 @@
<Compile Include="UserDefined.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CUETools.CDRepair\CUETools.CDRepair.csproj">
<Project>{C4869B37-EBB1-47BB-9406-B1209BEAB84B}</Project>
<Name>CUETools.CDRepair</Name>
</ProjectReference>
<ProjectReference Include="..\CUETools.Codecs\CUETools.Codecs.csproj">
<Project>{6458A13A-30EF-45A9-9D58-E5031B17BEE2}</Project>
<Name>CUETools.Codecs</Name>
@@ -90,6 +94,10 @@
<Project>{14EE067E-C218-4625-9540-2361AB27C4A6}</Project>
<Name>CUETools.Compression</Name>
</ProjectReference>
<ProjectReference Include="..\CUETools.CTDB\CUETools.CTDB.csproj">
<Project>{AA2A9A7E-45FB-4632-AD85-85B0E556F818}</Project>
<Name>CUETools.CTDB</Name>
</ProjectReference>
<ProjectReference Include="..\CUETools.Ripper\CUETools.Ripper.csproj">
<Project>{D2700165-3E77-4B28-928D-551F5FC11954}</Project>
<Name>CUETools.Ripper</Name>

View File

@@ -41,6 +41,8 @@ using System.Runtime.InteropServices;
using CUETools.Codecs;
using CUETools.CDImage;
using CUETools.AccurateRip;
//using CUETools.CDRepair;
using CUETools.CTDB;
using CUETools.Ripper;
using CUETools.Compression;
using MusicBrainz;
@@ -928,6 +930,8 @@ namespace CUETools.Processor
public int maxAlbumArtSize;
public string arLogFilenameFormat, alArtFilenameFormat;
public CUEStyle gapsHandling;
public bool separateDecodingThread;
public bool useSystemProxySettings;
public bool CopyAlbumArt { get { return copyAlbumArt; } set { copyAlbumArt = value; } }
public bool FlaCudaThreads { get { return flaCudaThreads; } set { flaCudaThreads = value; } }
@@ -1004,6 +1008,9 @@ namespace CUETools.Processor
arLogFilenameFormat = "%filename%.accurip";
alArtFilenameFormat = "folder.jpg";
separateDecodingThread = true;
useSystemProxySettings = true;
gapsHandling = CUEStyle.GapsAppended;
language = Thread.CurrentThread.CurrentUICulture.Name;
@@ -1090,6 +1097,29 @@ if (tracksMatch * 100 < processor.Config.encodeWhenPercent * processor.TrackCoun
processor.Action = CUEAction.Encode;
return processor.Go();
"));
scripts.Add("repair", new CUEToolsScript("repair", true,
new CUEAction[] { CUEAction.Encode },
@"
processor.UseCUEToolsDB();
processor.Action = CUEAction.Verify;
if (processor.CTDB.DBStatus != null)
return CTDB.DBStatus;
processor.Go();
processor.CTDB.DoVerify();
if (!processor.CTDB.Verify.HasErrors)
return ""nothing to fix"";
if (!processor.CTDB.Verify.CanRecover)
return ""cannot fix"";
processor._useCUEToolsDBFix = true;
processor.Action = CUEAction.Encode;
return processor.Go();
"));
scripts.Add("submit", new CUEToolsScript("submit", true,
new CUEAction[] { CUEAction.Verify },
@"
string status = processor.Go();
"));
defaultVerifyScript = "default";
defaultEncodeScript = "default";
}
@@ -1145,6 +1175,9 @@ return processor.Go();
sw.Save("CheckForUpdates", checkForUpdates);
sw.Save("Language", language);
sw.Save("SeparateDecodingThread", separateDecodingThread);
sw.Save("UseSystemProxySettings", useSystemProxySettings);
sw.Save("WriteBasicTagsFromCUEData", writeBasicTagsFromCUEData);
sw.Save("CopyBasicTags", copyBasicTags);
sw.Save("CopyUnknownTags", copyUnknownTags);
@@ -1289,6 +1322,9 @@ return processor.Go();
arLogFilenameFormat = sr.Load("ArLogFilenameFormat") ?? arLogFilenameFormat;
alArtFilenameFormat = sr.Load("AlArtFilenameFormat") ?? alArtFilenameFormat;
separateDecodingThread = sr.LoadBoolean("SeparateDecodingThread") ?? separateDecodingThread;
useSystemProxySettings = sr.LoadBoolean("UseSystemProxySettings") ?? useSystemProxySettings;
int totalEncoders = sr.LoadInt32("ExternalEncoders", 0, null) ?? 0;
for (int nEncoders = 0; nEncoders < totalEncoders; nEncoders++)
{
@@ -1477,6 +1513,7 @@ return processor.Go();
_useFreeDb = sr.LoadBoolean("FreedbLookup") ?? _useFreeDb;
_useMusicBrainz = sr.LoadBoolean("MusicBrainzLookup") ?? _useMusicBrainz;
_useAccurateRip = sr.LoadBoolean("AccurateRipLookup") ?? _useAccurateRip;
_useCUEToolsDB = sr.LoadBoolean("CUEToolsDBLookup") ?? _useCUEToolsDB;
_outputAudioType = (AudioEncoderType?)sr.LoadInt32("OutputAudioType", null, null) ?? _outputAudioType;
_outputAudioFormat = sr.Load("OutputAudioFmt") ?? _outputAudioFormat;
_action = (CUEAction?)sr.LoadInt32("AccurateRipMode", (int)CUEAction.Encode, (int)CUEAction.CorrectFilenames) ?? _action;
@@ -1493,6 +1530,7 @@ return processor.Go();
sw.Save("FreedbLookup", _useFreeDb);
sw.Save("MusicBrainzLookup", _useMusicBrainz);
sw.Save("AccurateRipLookup", _useAccurateRip);
sw.Save("CUEToolsDBLookup", _useCUEToolsDB);
sw.Save("OutputAudioType", (int)_outputAudioType);
sw.Save("OutputAudioFmt", _outputAudioFormat);
sw.Save("AccurateRipMode", (int)_action);
@@ -1508,7 +1546,7 @@ return processor.Go();
public CUEAction _action = CUEAction.Encode;
public CUEStyle _CUEStyle = CUEStyle.SingleFileWithCUE;
public int _writeOffset = 0;
public bool _useFreeDb = true, _useMusicBrainz = true, _useAccurateRip = true;
public bool _useFreeDb = true, _useMusicBrainz = true, _useAccurateRip = true, _useCUEToolsDB = true;
public string _name;
}
@@ -1533,9 +1571,9 @@ return processor.Go();
{
public string path;
public string contents;
public bool isEAC;
public object data;
public CUEToolsSourceFile(string _path, StreamReader reader)
public CUEToolsSourceFile(string _path, TextReader reader)
{
path = _path;
contents = reader.ReadToEnd();
@@ -1556,6 +1594,10 @@ return processor.Go();
private int _writeOffset;
private CUEAction _action;
private bool _useAccurateRip = false;
private bool _useCUEToolsDB = false;
private bool _useCUEToolsDBFix = false;
private bool _useCUEToolsDBSibmit = false;
private bool _processed = false;
private uint? _minDataTrackLength;
private string _accurateRipId;
private string _eacLog;
@@ -1581,10 +1623,12 @@ return processor.Go();
private string _archivePassword;
private CUEToolsProgressEventArgs _progress;
private AccurateRipVerify _arVerify;
private CUEToolsDB _CUEToolsDB;
private CDImageLayout _toc;
private string _arLogFileName, _alArtFileName;
private TagLib.IPicture[] _albumArt;
private int _padding = 8192;
private IWebProxy proxy;
public event EventHandler<CompressionPasswordRequiredEventArgs> PasswordRequired;
public event EventHandler<CUEToolsProgressEventArgs> CUEToolsProgress;
@@ -1614,6 +1658,7 @@ return processor.Go();
_isArchive = false;
_isCD = false;
_albumArt = null;
proxy = _config.useSystemProxySettings ? WebRequest.GetSystemWebProxy() : null;
}
public void OpenCD(ICDRipper ripper)
@@ -1625,7 +1670,9 @@ return processor.Go();
_trackFilenames.Add(string.Format("{0:00}.wav", iTrack + 1));
_tracks.Add(new TrackInfo());
}
_arVerify = new AccurateRipVerify(_toc);
_arVerify = new AccurateRipVerify(_toc, proxy);
_CUEToolsDB = new CUEToolsDB(_toc, proxy);
_CUEToolsDB.UploadHelper.onProgress += new EventHandler<Krystalware.UploadHelper.UploadProgressEventArgs>(UploadProgress);
_isCD = true;
SourceInfo cdInfo;
cdInfo.Path = _ripper.ARName;
@@ -1653,6 +1700,14 @@ return processor.Go();
}
}
public CUEToolsDB CTDB
{
get
{
return _CUEToolsDB;
}
}
public ICDRipper CDRipper
{
get
@@ -2533,7 +2588,9 @@ return processor.Go();
}
}
_arVerify = new AccurateRipVerify(_toc);
_arVerify = new AccurateRipVerify(_toc, proxy);
_CUEToolsDB = new CUEToolsDB(_toc, proxy);
_CUEToolsDB.UploadHelper.onProgress += new EventHandler<Krystalware.UploadHelper.UploadProgressEventArgs>(UploadProgress);
if (_eacLog != null)
{
@@ -2578,6 +2635,16 @@ return processor.Go();
_padding += _eacLog.Length;
}
public void UseCUEToolsDB()
{
ShowProgress((string)"Contacting CUETools database...", 0, 0, null, null);
_CUEToolsDB.ContactDB(_accurateRipId ?? AccurateRipVerify.CalculateAccurateRipId(_toc));
ShowProgress("", 0.0, 0.0, null, null);
_useCUEToolsDB = true;
}
public void UseAccurateRip()
{
ShowProgress((string)"Contacting AccurateRip database...", 0, 0, null, null);
@@ -2685,6 +2752,18 @@ return processor.Go();
this.CUEToolsProgress(this, _progress);
}
private void UploadProgress(object sender, Krystalware.UploadHelper.UploadProgressEventArgs e)
{
CheckStop();
if (this.CUEToolsProgress == null)
return;
_progress.percentDisk = 1.0;
_progress.percentTrck = e.percent;
_progress.offset = 0;
_progress.status = e.uri;
this.CUEToolsProgress(this, _progress);
}
private void CDReadProgress(object sender, ReadProgressArgs e)
{
CheckStop();
@@ -3016,7 +3095,8 @@ return processor.Go();
if (Path.GetExtension(path) == ".dummy" || Path.GetExtension(path) == ".bin")
{
fileInfo = null;
} else
}
else
{
TagLib.UserDefined.AdditionalFileTypes.Config = _config;
TagLib.File.IFileAbstraction file = _isArchive
@@ -3026,15 +3106,18 @@ return processor.Go();
}
IAudioSource audioSource = AudioReadWrite.GetAudioSource(path, _isArchive ? OpenArchive(path, true) : null, _config);
if (!audioSource.PCM.IsRedBook ||
audioSource.Length <= 0 ||
audioSource.Length >= Int32.MaxValue)
try
{
if (!audioSource.PCM.IsRedBook ||
audioSource.Length <= 0 ||
audioSource.Length >= Int32.MaxValue)
throw new Exception("Audio format is invalid.");
return (int)audioSource.Length;
}
finally
{
audioSource.Close();
throw new Exception("Audio format is invalid.");
}
audioSource.Close();
return (int)audioSource.Length;
}
public static void WriteText(string path, string text, Encoding encoding)
@@ -3122,6 +3205,48 @@ return processor.Go();
logWriter.WriteLine();
logWriter.WriteLine(" Pre-gap length 0:{0}.{1:00}", CDImageLayout.TimeToString("{0:00}:{1:00}", _toc[track + _toc.FirstAudio].Pregap + (track + _toc.FirstAudio == 1 ? 150U : 0U)), (_toc[track + _toc.FirstAudio].Pregap % 75) * 100 / 75);
}
int errCount = 0;
uint tr_start = _toc[track + _toc.FirstAudio].Start;
uint tr_end = (_toc[track + _toc.FirstAudio].Length + 74) / 75;
for (uint iSecond = 0; iSecond < tr_end; iSecond ++)
{
uint sec_start = tr_start + iSecond * 75;
uint sec_end = Math.Min(sec_start + 74, _toc[track + _toc.FirstAudio].End);
bool fError = false;
for (uint iSector = sec_start; iSector <= sec_end; iSector++)
if (_ripper.Errors[(int)iSector])
fError = true;
if (fError)
{
uint end = iSecond;
for (uint jSecond = iSecond + 1; jSecond < tr_end; jSecond++)
{
uint jsec_start = tr_start + jSecond * 75;
uint jsec_end = Math.Min(jsec_start + 74, _toc[track + _toc.FirstAudio].End);
bool jfError = false;
for (uint jSector = jsec_start; jSector <= jsec_end; jSector++)
if (_ripper.Errors[(int)jSector])
jfError = true;
if (jfError)
end = jSecond;
}
if (errCount == 0)
logWriter.WriteLine();
if (errCount++ > 20)
break;
//"Suspicious position 0:02:20"
//" Suspicious position 0:02:23 - 0:02:24"
string s1 = CDImageLayout.TimeToString("0:{0:00}:{1:00}", iSecond * 75);
string s2 = CDImageLayout.TimeToString("0:{0:00}:{1:00}", end * 75);
if (iSecond == end)
logWriter.WriteLine(" Suspicious position {0}", s1);
else
logWriter.WriteLine(" Suspicious position {0} - {1}", s1, s2);
iSecond = end;
}
}
logWriter.WriteLine();
logWriter.WriteLine(" Peak level {0:F1} %", (Tracks[track].PeakLevel * 1000 / 32768) * 0.1);
logWriter.WriteLine(" Track quality 100.0 %");
@@ -3391,6 +3516,8 @@ return processor.Go();
public void GenerateAccurateRipLog(TextWriter sw)
{
if (!_processed)
throw new Exception("not processed");
sw.WriteLine("[Verification date: {0}]", DateTime.Now);
sw.WriteLine("[Disc ID: {0}]", _accurateRipId ?? AccurateRipVerify.CalculateAccurateRipId(_toc));
if (PreGapLength != 0)
@@ -3414,6 +3541,10 @@ return processor.Go();
sw.WriteLine("HDCD: {0:f}", hdcdDecoder);
if (0 != _writeOffset)
sw.WriteLine("Offset applied: {0}", _writeOffset);
if (_useCUEToolsDBFix)// && _CUEToolsDB.SelectedEntry != null)
sw.WriteLine("CUETools DB: corrected {0} errors.", _CUEToolsDB.SelectedEntry.repair.CorrectableErrors);
else if (_useCUEToolsDB)
sw.WriteLine("CUETools DB: {0}.", _CUEToolsDB.Status);
_arVerify.GenerateFullLog(sw, _config.arLogVerbose);
}
@@ -3550,6 +3681,14 @@ return processor.Go();
if (_audioEncoderType != AudioEncoderType.NoAudio || _action == CUEAction.Verify)
WriteAudioFilesPass(OutputDir, OutputStyle, destLengths, htoaToFile, _action == CUEAction.Verify);
if (_useCUEToolsDB && CTDB.AccResult == HttpStatusCode.OK)
{
if (!_useCUEToolsDBFix)
CTDB.DoVerify();
}
_processed = true;
CreateRipperLOG();
if (_action == CUEAction.Encode)
@@ -4097,6 +4236,8 @@ return processor.Go();
if (_useAccurateRip)
_arVerify.Init();
if (_useCUEToolsDB && !_useCUEToolsDBFix)
_CUEToolsDB.Init(_useCUEToolsDBSibmit);
ShowProgress(String.Format("{2} track {0:00} ({1:00}%)...", 0, 0, noOutput ? "Verifying" : "Writing"), 0, 0.0, null, null);
@@ -4179,6 +4320,13 @@ return processor.Go();
}
copyCount = audioSource.Read(sampleBuffer, copyCount);
if (_useCUEToolsDB)
{
if (_useCUEToolsDBFix)
_CUEToolsDB.SelectedEntry.repair.Write(sampleBuffer);
else
_CUEToolsDB.Verify.Write(sampleBuffer);
}
if (!discardOutput)
{
if (!_config.detectHDCD || !_config.decodeHDCD)
@@ -4208,7 +4356,7 @@ return processor.Go();
{
_arVerify.Write(sampleBuffer);
if (iTrack > 0 || iIndex > 0)
Tracks[iTrack + (iIndex == 0 ? -1 : 0)].MeasurePeakLevel(sampleBuffer.Samples, copyCount);
Tracks[iTrack + (iIndex == 0 ? -1 : 0)].MeasurePeakLevel(sampleBuffer, copyCount);
}
currentOffset += copyCount;
@@ -4550,7 +4698,9 @@ return processor.Go();
if (sourceInfo.Offset != 0)
audioSource.Position = sourceInfo.Offset;
//audioSource = new AudioPipe(audioSource, 0x10000);
//if (!(audioSource is AudioPipe) && !(audioSource is UserDefinedReader) && _config.separateDecodingThread)
if (!(audioSource is AudioPipe) && _config.separateDecodingThread)
audioSource = new AudioPipe(audioSource, 0x10000);
return audioSource;
}
@@ -4902,6 +5052,14 @@ return processor.Go();
}
}
public bool Processed
{
get
{
return _processed;
}
}
public bool IsCD
{
get
@@ -5039,6 +5197,56 @@ return processor.Go();
return Go();
case "only if found":
return ArVerify.AccResult != HttpStatusCode.OK ? WriteReport() : Go();
case "submit":
{
if (!_useCUEToolsDB)
return "CUETools DB not enabled";
if (ArVerify.ARStatus != null)
return "AccurateRip: " + ArVerify.ARStatus;
if (ArVerify.WorstTotal() < 3)
return "AccurateRip: confidence too low";
//if (CTDB.AccResult == HttpStatusCode.OK)
//return "CUEToolsDB: disc already present in database";
if (CTDB.AccResult != HttpStatusCode.NotFound && CTDB.AccResult != HttpStatusCode.OK)
return "CUEToolsDB: " + CTDB.DBStatus;
_useCUEToolsDBSibmit = true;
string status = Go();
if (CTDB.AccResult == HttpStatusCode.OK)
foreach (DBEntry entry in CTDB.Entries)
if (!entry.hasErrors)
return "CUEToolsDB: " + CTDB.Status;
if (ArVerify.WorstConfidence() < 3)
return status + ": confidence too low";
return CTDB.Submit((int)ArVerify.WorstConfidence(), (int)ArVerify.WorstTotal());
}
case "repair":
{
UseCUEToolsDB();
Action = CUEAction.Verify;
if (CTDB.DBStatus != null)
return CTDB.DBStatus;
bool useAR = _useAccurateRip;
_useAccurateRip = false;
Go();
_useAccurateRip = useAR;
List<CUEToolsSourceFile> choices = new List<CUEToolsSourceFile>();
foreach (DBEntry entry in CTDB.Entries)
if (!entry.hasErrors || entry.canRecover)
{
CUEToolsSourceFile choice = new CUEToolsSourceFile(entry.Status, new StringReader(""));
choice.data = entry;
choices.Add(choice);
}
CUEToolsSourceFile selectedEntry = ChooseFile(choices, null, true);
if (selectedEntry == null)
return CTDB.Status;
CTDB.SelectedEntry = (DBEntry)selectedEntry.data;
if (!CTDB.SelectedEntry.hasErrors)
return CTDB.Status;
_useCUEToolsDBFix = true;
Action = CUEAction.Encode;
return Go();
}
case "fix offset":
{
if (ArVerify.AccResult != HttpStatusCode.OK)
@@ -5276,12 +5484,15 @@ return processor.Go();
_peakLevel = 0;
}
public unsafe void MeasurePeakLevel(int[,] samplesBuffer, int sampleCount)
public unsafe void MeasurePeakLevel(AudioBuffer buff, int sampleCount)
{
fixed (int* s = samplesBuffer)
if (!buff.PCM.IsRedBook)
throw new Exception();
fixed (byte* bb = buff.Bytes)
{
short* ss = (short*)bb;
for (int i = 0; i < sampleCount * 2; i++)
_peakLevel = Math.Max(_peakLevel, Math.Abs(s[i]));
_peakLevel = Math.Max(_peakLevel, Math.Abs((int)ss[i]));
}
//for (uint i = 0; i < sampleCount; i++)
// for (uint j = 0; j < 2; j++)