Menu

#629 Read offset is shared between drives when more than one optical drive is present

New
nobody
None
Medium
Defect
6 days ago
6 days ago
No

Version: XLD 20250302 (157.2), macOS on Apple Silicon
Reporter: Walter van der Heiden
Hardware: four USB optical drives on a powered hub —
TSSTcorp CDDVDW SE-218CN (+6), HL-DT-ST DVDRAM GP30NB40 (+6),
HL-DT-ST DVD+-RW GU40N (+103), HL-DT-ST BD-RE BP06LU10 (+667)

Summary

With several drives connected, "Set automatically if possible" does not produce a
per-drive read offset. All discs that are queued together are ripped with a single
offset value — whichever happens to be in the preferences field at that moment.

The resulting rips are complete and error-free, but the audio is shifted by up to
661 samples relative to the AccurateRip reference, and the log reports an offset
that was not the drive's own.

Two related symptoms share the same root cause (see below):

  • Opening a disc blocks every other drive. After inserting a disc you must wait
    for it to be read before you can close the next drive's tray.
  • makeDriveOffsetList only auto-fills the offset when exactly one drive is found.

Steps to reproduce

  1. Connect two or more optical drives with different read offsets.
  2. Preferences > CD Rip: tick "Set automatically if possible".
  3. Insert a disc in drive A, then a disc in drive B, so both are queued.
  4. Start extraction.
  5. Compare the "Read offset correction" line in both logs with the drives'
    values in offsetlist.plist.

Both logs show the same offset. Ripping the same two discs one at a time gives
the correct offset for each.

Reproducibility: always, whenever more than one disc is queued at a time.
Never when discs are ripped one by one.

Evidence

60 rip logs, four drives, over eight days. Every rip started on its own has the
correct offset. Every rip started as part of a batch of discs inserted within the
same minute shares one offset. Nine out of nine mismatches follow this rule.

Two batches, four drives each:

2026-09-12 10:24:44  TSST    667   (correct:   6)
2026-09-12 10:25:04  GP30    667   (correct:   6)
2026-09-12 10:25:17  GU40N   667   (correct: 103)
2026-09-12 10:25:42  BD-RE   667   (correct: 667)

2026-09-13 14:26:19  GP30      6   (correct:   6)
2026-09-13 14:26:36  GU40N     6   (correct: 103)
2026-09-13 14:26:49  TSST      6   (correct:   6)
2026-09-13 14:26:58  BD-RE     6   (correct: 667)

In every affected log, AccurateRip reports "Accurately ripped with different
offset", and used offset + reported delta equals exactly the drive's value in
offsetlist.plist. All four drives are present in that file with the correct
values, so the data is right — only the plumbing is not.

Cause

o_offsetCorrectionValue is a single text field for the whole application.

  • updateOffsetCorrectionValueWithDriveName: (XLDController.m:3467) writes the
    drive's offset into that one field each time a disc is opened, so the last disc
    opened wins.
  • The value is not read until extraction starts, at XLDController.m:938
    ([rippingSession setOffsetCorrectionValue:[o_offsetCorrectionValue intValue]])
    and :949 (the value written to the log). Every disc in the queue therefore gets
    the same number.
  • makeDriveOffsetList (XLDController.m:4137) ends with
    if(found == 1) [o_offsetCorrectionValue setIntValue:...] — there is a special
    case for a single drive and none for more than one.

driveIsBusy (XLDController.h:152) is a single BOOL for all drives. It gates both
automount (XLDController.m:1474) and the "Open Audio CD" menu item
(XLDController.m:5058), which is why reading one disc blocks the other drives.

Building trunk today: what it takes

Trunk (157.2) does not build with a current Xcode. In the hope it is useful, here
is everything that was needed on macOS 26 / Apple Silicon:

  1. cddb.png and image.png are referenced by the project but are in neither
    the repository nor the shipped app bundle. Substitutes had to be drawn.
  2. 77 .xib files still declare macOS 10.4 or 10.5. ibtool refuses these with
    "Compiling for earlier than macOS 10.6 is no longer supported." The version
    lives in three different places depending on the file's format:
    IBDocument.SystemTarget, <deployment version="..."/>, and
    IBDocument.PluginDeclaredDependencies.
  3. MetadataEditor.xib cannot be opened by the current ibtool at all, in all
    ten localisations:
    -[__NSCFNumber isEqualToString:]: unrecognized selector sent to instance.
    This one has no workaround short of re-saving the file in Interface Builder.
  4. XLDGainAnalyzer.c and ebur128.c are present in the source tree but are not
    members of the XLD target, so linking fails on their symbols.
  5. SCDynamicStoreCopyProxies needs -framework SystemConfiguration, which is
    not in the link list.
  6. The plug-in bundles and XLDID3.framework must already exist in the sibling
    */build/Release/ directories; they can be borrowed from an installed XLD.app.

With those addressed, and libsndfile, libsoxr, flac, wavpack and libcddb from
Homebrew, this works:

xcodebuild -project XLD_export.xcodeproj -target XLD -configuration Release \
  SDKROOT=macosx ARCHS=arm64 ONLY_ACTIVE_ARCH=YES \
  MACOSX_DEPLOYMENT_TARGET=12.0 \
  HEADER_SEARCH_PATHS='$(inherited) /opt/homebrew/include' \
  LIBRARY_SEARCH_PATHS='$(inherited) /opt/homebrew/lib' \
  OTHER_LDFLAGS='$(inherited) -framework SystemConfiguration'

The patch below was built and verified this way. With it applied, two discs
inserted fifteen seconds apart into two different drives now each receive their
own offset:

2026-09-14 15:17:18  GP30NB40   offset 6     All tracks accurately ripped
2026-09-14 15:17:33  BP06LU10   offset 667   All tracks accurately ripped

Neither log reports "with different offset" any more. The same two discs, in the
same two drives, previously both came out at offset 6.

Suggested fix

XLDCDDARippingSession is already per-device — sessions is keyed by device path,
and each session owns both its cdread descriptor and its offsetCorrectionValue.
Letting the session resolve its own offset fixes the problem by construction and
needs no UI change. Patch below (against 157.2).

driveIsBusy is a larger change and is left out deliberately: the natural fix is to
ask the sessions dictionary whether this device is busy, but that touches menu
validation and automount and is better done separately.

--- a/XLDCDDARippingSession.h
+++ b/XLDCDDARippingSession.h
@@ -34,6 +34,7 @@

 - (xld_cdread_t *)descriptor;
 - (int)offsetCorrectionValue;
 - (void)setOffsetCorrectionValue:(int)value;
+- (BOOL)resolveOffsetCorrectionValueFromDrive;
 - (int)retryCount;
 - (void)setRetryCount:(int)value;
 - (xldoffset_t)firstAudioFrame;
--- a/XLDCDDARippingSession.m
+++ b/XLDCDDARippingSession.m
@@ -93,6 +93,22 @@
     offsetCorrectionValue = value;
 }

+- (BOOL)resolveOffsetCorrectionValueFromDrive
+{

+   /* Resolve the read offset from this session's own drive, so that discs in
+      different drives no longer share a single offset value. */
+   if(!cdread.product) return NO;
+   NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:
+       [[NSBundle mainBundle] pathForResource:@"offsetlist" ofType:@"plist"]];
+   NSString *name = [NSString stringWithUTF8String:cdread.product];
+   id value = [dic objectForKey:name];
+   if(!value && [name length] > 1)
+       value = [dic objectForKey:[name substringToIndex:[name length]-1]];
+   if(!value) return NO;
+   offsetCorrectionValue = [value intValue];
+   return YES;
+}
+
 - (int)retryCount
 {
     return retryCount;
--- a/XLDController.m
+++ b/XLDController.m
@@ -936,6 +936,7 @@
        if(ripperMode != kRipperModeBurst && [o_useC2Pointer state] == NSOnState) ripperMode |= kRipperModeC2;
        [rippingSession setRipperMode:ripperMode];
        [rippingSession setOffsetCorrectionValue:[o_offsetCorrectionValue intValue]];
+       if([o_autoSetOffsetValue state] == NSOnState) [rippingSession resolveOffsetCorrectionValueFromDrive];
        [rippingSession setRetryCount:[o_maxRetryCount intValue]];
        [rippingSession setFirstAudioFrame:[cueParser firstAudioFrame]];
        [rippingSession setLastAudioFrame:[cueParser lastAudioFrame]];
@@ -946,7 +947,7 @@
        [resultObj setDeviceStr:[cueParser fileToDecode]];
        [resultObj setDriveAndMediaInfoFromDescriptor:[rippingSession descriptor]];
        [resultObj setRipperMode:ripperMode
-                offsetCorrention:[o_offsetCorrectionValue intValue]
+                offsetCorrention:[rippingSession offsetCorrectionValue]
                       retryCount:[o_maxRetryCount intValue]
                 useAccurateRipDB:(([o_queryAccurateRip state] == NSOnState) && ([discView extractionMode] != 1) && ([dbData state] != kXLDAccurateRipDBStateNetworkError))
               checkInconsistency:([o_verifySuspiciousSector state] == NSOnState)

Discussion


Log in to post a comment.