« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/composite/things/track
diff options
context:
space:
mode:
Diffstat (limited to 'src/data/composite/things/track')
-rw-r--r--src/data/composite/things/track/index.js12
-rw-r--r--src/data/composite/things/track/inheritContributionListFromMainRelease.js (renamed from src/data/composite/things/track/inheritContributionListFromOriginalRelease.js)20
-rw-r--r--src/data/composite/things/track/inheritFromMainRelease.js (renamed from src/data/composite/things/track/inheritFromOriginalRelease.js)18
-rw-r--r--src/data/composite/things/track/trackAdditionalNameList.js38
-rw-r--r--src/data/composite/things/track/withAlbum.js22
-rw-r--r--src/data/composite/things/track/withAllReleases.js46
-rw-r--r--src/data/composite/things/track/withAlwaysReferenceByDirectory.js43
-rw-r--r--src/data/composite/things/track/withCoverArtistContribs.js73
-rw-r--r--src/data/composite/things/track/withHasUniqueCoverArt.js76
-rw-r--r--src/data/composite/things/track/withMainRelease.js (renamed from src/data/composite/things/track/withOriginalRelease.js)28
-rw-r--r--src/data/composite/things/track/withOtherReleases.js30
-rw-r--r--src/data/composite/things/track/withPropertyFromAlbum.js17
-rw-r--r--src/data/composite/things/track/withPropertyFromMainRelease.js (renamed from src/data/composite/things/track/withPropertyFromOriginalRelease.js)36
-rw-r--r--src/data/composite/things/track/withTrackArtDate.js24
-rw-r--r--src/data/composite/things/track/withTrackNumber.js50
15 files changed, 327 insertions, 206 deletions
diff --git a/src/data/composite/things/track/index.js b/src/data/composite/things/track/index.js
index 32c72f78..e789e736 100644
--- a/src/data/composite/things/track/index.js
+++ b/src/data/composite/things/track/index.js
@@ -1,15 +1,17 @@
 export {default as exitWithoutUniqueCoverArt} from './exitWithoutUniqueCoverArt.js';
-export {default as inheritContributionListFromOriginalRelease} from './inheritContributionListFromOriginalRelease.js';
-export {default as inheritFromOriginalRelease} from './inheritFromOriginalRelease.js';
-export {default as withAlbum} from './withAlbum.js';
+export {default as inheritContributionListFromMainRelease} from './inheritContributionListFromMainRelease.js';
+export {default as inheritFromMainRelease} from './inheritFromMainRelease.js';
+export {default as withAllReleases} from './withAllReleases.js';
 export {default as withAlwaysReferenceByDirectory} from './withAlwaysReferenceByDirectory.js';
 export {default as withContainingTrackSection} from './withContainingTrackSection.js';
+export {default as withCoverArtistContribs} from './withCoverArtistContribs.js';
 export {default as withDate} from './withDate.js';
 export {default as withDirectorySuffix} from './withDirectorySuffix.js';
 export {default as withHasUniqueCoverArt} from './withHasUniqueCoverArt.js';
-export {default as withOriginalRelease} from './withOriginalRelease.js';
+export {default as withMainRelease} from './withMainRelease.js';
 export {default as withOtherReleases} from './withOtherReleases.js';
 export {default as withPropertyFromAlbum} from './withPropertyFromAlbum.js';
-export {default as withPropertyFromOriginalRelease} from './withPropertyFromOriginalRelease.js';
+export {default as withPropertyFromMainRelease} from './withPropertyFromMainRelease.js';
 export {default as withSuffixDirectoryFromAlbum} from './withSuffixDirectoryFromAlbum.js';
 export {default as withTrackArtDate} from './withTrackArtDate.js';
+export {default as withTrackNumber} from './withTrackNumber.js';
diff --git a/src/data/composite/things/track/inheritContributionListFromOriginalRelease.js b/src/data/composite/things/track/inheritContributionListFromMainRelease.js
index f4ae3ddb..89252feb 100644
--- a/src/data/composite/things/track/inheritContributionListFromOriginalRelease.js
+++ b/src/data/composite/things/track/inheritContributionListFromMainRelease.js
@@ -1,5 +1,5 @@
-// Like inheritFromOriginalRelease, but tuned for contributions.
-// Recontextualized contributions for this track.
+// Like inheritFromMainRelease, but tuned for contributions.
+// Recontextualizes contributions for this track.
 
 import {input, templateCompositeFrom} from '#composite';
 
@@ -9,36 +9,36 @@ import {withRecontextualizedContributionList, withRedatedContributionList}
   from '#composite/wiki-data';
 
 import withDate from './withDate.js';
-import withPropertyFromOriginalRelease
-  from './withPropertyFromOriginalRelease.js';
+import withPropertyFromMainRelease
+  from './withPropertyFromMainRelease.js';
 
 export default templateCompositeFrom({
-  annotation: `inheritContributionListFromOriginalRelease`,
+  annotation: `inheritContributionListFromMainRelease`,
 
   steps: () => [
-    withPropertyFromOriginalRelease({
+    withPropertyFromMainRelease({
       property: input.thisProperty(),
       notFoundValue: input.value([]),
     }),
 
     raiseOutputWithoutDependency({
-      dependency: '#isRerelease',
+      dependency: '#isSecondaryRelease',
       mode: input.value('falsy'),
     }),
 
     withRecontextualizedContributionList({
-      list: '#originalValue',
+      list: '#mainReleaseValue',
     }),
 
     withDate(),
 
     withRedatedContributionList({
-      list: '#originalValue',
+      list: '#mainReleaseValue',
       date: '#date',
     }),
 
     exposeDependency({
-      dependency: '#originalValue',
+      dependency: '#mainReleaseValue',
     }),
   ],
 });
diff --git a/src/data/composite/things/track/inheritFromOriginalRelease.js b/src/data/composite/things/track/inheritFromMainRelease.js
index 38ab06be..b1cbb65e 100644
--- a/src/data/composite/things/track/inheritFromOriginalRelease.js
+++ b/src/data/composite/things/track/inheritFromMainRelease.js
@@ -1,9 +1,9 @@
 // Early exits with the value for the same property as specified on the
-// original release, if this track is a rerelease, and otherwise continues
+// main release, if this track is a secondary release, and otherwise continues
 // without providing any further dependencies.
 //
-// Like withOriginalRelease, this will early exit (with notFoundValue) if the
-// original release is specified by reference and that reference doesn't
+// Like withMainRelease, this will early exit (with notFoundValue) if the
+// main release is specified by reference and that reference doesn't
 // resolve to anything.
 
 import {input, templateCompositeFrom} from '#composite';
@@ -11,11 +11,11 @@ import {input, templateCompositeFrom} from '#composite';
 import {exposeDependency, raiseOutputWithoutDependency}
   from '#composite/control-flow';
 
-import withPropertyFromOriginalRelease
-  from './withPropertyFromOriginalRelease.js';
+import withPropertyFromMainRelease
+  from './withPropertyFromMainRelease.js';
 
 export default templateCompositeFrom({
-  annotation: `inheritFromOriginalRelease`,
+  annotation: `inheritFromMainRelease`,
 
   inputs: {
     notFoundValue: input({
@@ -24,18 +24,18 @@ export default templateCompositeFrom({
   },
 
   steps: () => [
-    withPropertyFromOriginalRelease({
+    withPropertyFromMainRelease({
       property: input.thisProperty(),
       notFoundValue: input('notFoundValue'),
     }),
 
     raiseOutputWithoutDependency({
-      dependency: '#isRerelease',
+      dependency: '#isSecondaryRelease',
       mode: input.value('falsy'),
     }),
 
     exposeDependency({
-      dependency: '#originalValue',
+      dependency: '#mainReleaseValue',
     }),
   ],
 });
diff --git a/src/data/composite/things/track/trackAdditionalNameList.js b/src/data/composite/things/track/trackAdditionalNameList.js
deleted file mode 100644
index 65a2263d..00000000
--- a/src/data/composite/things/track/trackAdditionalNameList.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// Compiles additional names from various sources.
-
-import {input, templateCompositeFrom} from '#composite';
-import {isAdditionalNameList} from '#validators';
-
-import withInferredAdditionalNames from './withInferredAdditionalNames.js';
-import withSharedAdditionalNames from './withSharedAdditionalNames.js';
-
-export default templateCompositeFrom({
-  annotation: `trackAdditionalNameList`,
-
-  compose: false,
-
-  update: {validate: isAdditionalNameList},
-
-  steps: () => [
-    withInferredAdditionalNames(),
-    withSharedAdditionalNames(),
-
-    {
-      dependencies: [
-        '#inferredAdditionalNames',
-        '#sharedAdditionalNames',
-        input.updateValue(),
-      ],
-
-      compute: ({
-        ['#inferredAdditionalNames']: inferredAdditionalNames,
-        ['#sharedAdditionalNames']: sharedAdditionalNames,
-        [input.updateValue()]: providedAdditionalNames,
-      }) => [
-        ...providedAdditionalNames ?? [],
-        ...sharedAdditionalNames,
-        ...inferredAdditionalNames,
-      ],
-    },
-  ],
-});
diff --git a/src/data/composite/things/track/withAlbum.js b/src/data/composite/things/track/withAlbum.js
deleted file mode 100644
index 4c55e1f4..00000000
--- a/src/data/composite/things/track/withAlbum.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// Gets the track's album. This will early exit if albumData is missing.
-// If there's no album whose list of tracks includes this track, the output
-// dependency will be null.
-
-import {templateCompositeFrom} from '#composite';
-
-import {withUniqueReferencingThing} from '#composite/wiki-data';
-import {soupyReverse} from '#composite/wiki-properties';
-
-export default templateCompositeFrom({
-  annotation: `withAlbum`,
-
-  outputs: ['#album'],
-
-  steps: () => [
-    withUniqueReferencingThing({
-      reverse: soupyReverse.input('albumsWhoseTracksInclude'),
-    }).outputs({
-      ['#uniqueReferencingThing']: '#album',
-    }),
-  ],
-});
diff --git a/src/data/composite/things/track/withAllReleases.js b/src/data/composite/things/track/withAllReleases.js
new file mode 100644
index 00000000..891db102
--- /dev/null
+++ b/src/data/composite/things/track/withAllReleases.js
@@ -0,0 +1,46 @@
+// Gets all releases of the current track. All items of the outputs are
+// distinct Track objects; one track is the main release; all else are
+// secondary releases of that main release; and one item, which may be
+// the main release or one of the secondary releases, is the current
+// track. The results are sorted by date, and it is possible that the
+// main release is not actually the earliest/first.
+
+import {input, templateCompositeFrom} from '#composite';
+import {sortByDate} from '#sort';
+
+import {withPropertyFromObject} from '#composite/data';
+
+import withMainRelease from './withMainRelease.js';
+
+export default templateCompositeFrom({
+  annotation: `withAllReleases`,
+
+  outputs: ['#allReleases'],
+
+  steps: () => [
+    withMainRelease({
+      selfIfMain: input.value(true),
+      notFoundValue: input.value([]),
+    }),
+
+    // We don't talk about bruno no no
+    // Yes, this can perform a normal access equivalent to
+    // `this.secondaryReleases` from within a data composition.
+    // Oooooooooooooooooooooooooooooooooooooooooooooooo
+    withPropertyFromObject({
+      object: '#mainRelease',
+      property: input.value('secondaryReleases'),
+    }),
+
+    {
+      dependencies: ['#mainRelease', '#mainRelease.secondaryReleases'],
+      compute: (continuation, {
+        ['#mainRelease']: mainRelease,
+        ['#mainRelease.secondaryReleases']: secondaryReleases,
+      }) => continuation({
+        ['#allReleases']:
+          sortByDate([mainRelease, ...secondaryReleases]),
+      }),
+    },
+  ],
+});
diff --git a/src/data/composite/things/track/withAlwaysReferenceByDirectory.js b/src/data/composite/things/track/withAlwaysReferenceByDirectory.js
index 26c5ba97..87edf21e 100644
--- a/src/data/composite/things/track/withAlwaysReferenceByDirectory.js
+++ b/src/data/composite/things/track/withAlwaysReferenceByDirectory.js
@@ -9,7 +9,6 @@ import {isBoolean} from '#validators';
 
 import {withPropertyFromObject} from '#composite/data';
 import {withResolvedReference} from '#composite/wiki-data';
-import {soupyFind} from '#composite/wiki-properties';
 
 import {
   exitWithoutDependency,
@@ -17,6 +16,8 @@ import {
   exposeUpdateValueOrContinue,
 } from '#composite/control-flow';
 
+import withPropertyFromAlbum from './withPropertyFromAlbum.js';
+
 export default templateCompositeFrom({
   annotation: `withAlwaysReferenceByDirectory`,
 
@@ -27,18 +28,7 @@ export default templateCompositeFrom({
       validate: input.value(isBoolean),
     }),
 
-    // withAlwaysReferenceByDirectory is sort of a fragile area - we can't
-    // find the track's album the normal way because albums' track lists
-    // recurse back into alwaysReferenceByDirectory!
-    withResolvedReference({
-      ref: 'dataSourceAlbum',
-      find: soupyFind.input('album'),
-    }).outputs({
-      '#resolvedReference': '#album',
-    }),
-
-    withPropertyFromObject({
-      object: '#album',
+    withPropertyFromAlbum({
       property: input.value('alwaysReferenceTracksByDirectory'),
     }),
 
@@ -51,7 +41,7 @@ export default templateCompositeFrom({
 
     // Remaining code is for defaulting to true if this track is a rerelease of
     // another with the same name, so everything further depends on access to
-    // trackData as well as originalReleaseTrack.
+    // trackData as well as mainReleaseTrack.
 
     exitWithoutDependency({
       dependency: 'trackData',
@@ -60,45 +50,46 @@ export default templateCompositeFrom({
     }),
 
     exitWithoutDependency({
-      dependency: 'originalReleaseTrack',
+      dependency: 'mainReleaseTrack',
       value: input.value(false),
     }),
 
-    // It's necessary to use the custom trackOriginalReleasesOnly find function
+    // It's necessary to use the custom trackMainReleasesOnly find function
     // here, so as to avoid recursion issues - the find.track() function depends
     // on accessing each track's alwaysReferenceByDirectory, which means it'll
     // hit *this track* - and thus this step - and end up recursing infinitely.
-    // By definition, find.trackOriginalReleasesOnly excludes tracks which have
-    // an originalReleaseTrack update value set, which means even though it does
+    // By definition, find.trackMainReleasesOnly excludes tracks which have
+    // an mainReleaseTrack update value set, which means even though it does
     // still access each of tracks' `alwaysReferenceByDirectory` property, it
     // won't access that of *this* track - it will never proceed past the
     // `exitWithoutDependency` step directly above, so there's no opportunity
     // for recursion.
     withResolvedReference({
-      ref: 'originalReleaseTrack',
+      ref: 'mainReleaseTrack',
       data: 'trackData',
-      find: input.value(find.trackOriginalReleasesOnly),
+      find: input.value(find.trackMainReleasesOnly),
     }).outputs({
-      '#resolvedReference': '#originalRelease',
+      '#resolvedReference': '#mainRelease',
     }),
 
     exitWithoutDependency({
-      dependency: '#originalRelease',
+      dependency: '#mainRelease',
       value: input.value(false),
     }),
 
     withPropertyFromObject({
-      object: '#originalRelease',
+      object: '#mainRelease',
       property: input.value('name'),
     }),
 
     {
-      dependencies: ['name', '#originalRelease.name'],
+      dependencies: ['name', '#mainRelease.name'],
       compute: (continuation, {
         name,
-        ['#originalRelease.name']: originalName,
+        ['#mainRelease.name']: mainReleaseName,
       }) => continuation({
-        ['#alwaysReferenceByDirectory']: name === originalName,
+        ['#alwaysReferenceByDirectory']:
+          name === mainReleaseName,
       }),
     },
   ],
diff --git a/src/data/composite/things/track/withCoverArtistContribs.js b/src/data/composite/things/track/withCoverArtistContribs.js
new file mode 100644
index 00000000..9057cfeb
--- /dev/null
+++ b/src/data/composite/things/track/withCoverArtistContribs.js
@@ -0,0 +1,73 @@
+import {input, templateCompositeFrom} from '#composite';
+import {isContributionList} from '#validators';
+
+import {exposeDependencyOrContinue} from '#composite/control-flow';
+
+import {
+  withRecontextualizedContributionList,
+  withRedatedContributionList,
+  withResolvedContribs,
+} from '#composite/wiki-data';
+
+import exitWithoutUniqueCoverArt from './exitWithoutUniqueCoverArt.js';
+import withPropertyFromAlbum from './withPropertyFromAlbum.js';
+import withTrackArtDate from './withTrackArtDate.js';
+
+export default templateCompositeFrom({
+  annotation: `withCoverArtistContribs`,
+
+  inputs: {
+    from: input({
+      defaultDependency: 'coverArtistContribs',
+      validate: isContributionList,
+      acceptsNull: true,
+    }),
+  },
+
+  outputs: ['#coverArtistContribs'],
+
+  steps: () => [
+    exitWithoutUniqueCoverArt({
+      value: input.value([]),
+    }),
+
+    withTrackArtDate(),
+
+    withResolvedContribs({
+      from: input('from'),
+      thingProperty: input.value('coverArtistContribs'),
+      artistProperty: input.value('trackCoverArtistContributions'),
+      date: '#trackArtDate',
+    }).outputs({
+      '#resolvedContribs': '#coverArtistContribs',
+    }),
+
+    exposeDependencyOrContinue({
+      dependency: '#coverArtistContribs',
+      mode: input.value('empty'),
+    }),
+
+    withPropertyFromAlbum({
+      property: input.value('trackCoverArtistContribs'),
+    }),
+
+    withRecontextualizedContributionList({
+      list: '#album.trackCoverArtistContribs',
+      artistProperty: input.value('trackCoverArtistContributions'),
+    }),
+
+    withRedatedContributionList({
+      list: '#album.trackCoverArtistContribs',
+      date: '#trackArtDate',
+    }),
+
+    {
+      dependencies: ['#album.trackCoverArtistContribs'],
+      compute: (continuation, {
+        ['#album.trackCoverArtistContribs']: coverArtistContribs,
+      }) => continuation({
+        ['#coverArtistContribs']: coverArtistContribs,
+      }),
+    },
+  ],
+});
diff --git a/src/data/composite/things/track/withHasUniqueCoverArt.js b/src/data/composite/things/track/withHasUniqueCoverArt.js
index f7e65f25..85d3b92a 100644
--- a/src/data/composite/things/track/withHasUniqueCoverArt.js
+++ b/src/data/composite/things/track/withHasUniqueCoverArt.js
@@ -5,11 +5,18 @@
 // or a placeholder. (This property is named hasUniqueCoverArt instead of
 // the usual hasCoverArt to emphasize that it does not inherit from the
 // album.)
+//
+// withHasUniqueCoverArt is based only around the presence of *specified*
+// cover artist contributions, not whether the references to artists on those
+// contributions actually resolve to anything. It completely evades interacting
+// with find/replace.
 
 import {input, templateCompositeFrom} from '#composite';
-import {empty} from '#sugar';
 
-import {withResolvedContribs} from '#composite/wiki-data';
+import {raiseOutputWithoutDependency, withResultOfAvailabilityCheck}
+  from '#composite/control-flow';
+import {fillMissingListItems, withFlattenedList, withPropertyFromList}
+  from '#composite/data';
 
 import withPropertyFromAlbum from './withPropertyFromAlbum.js';
 
@@ -29,36 +36,73 @@ export default templateCompositeFrom({
           : continuation()),
     },
 
-    withResolvedContribs({
+    withResultOfAvailabilityCheck({
       from: 'coverArtistContribs',
-      date: input.value(null),
+      mode: input.value('empty'),
     }),
 
     {
-      dependencies: ['#resolvedContribs'],
+      dependencies: ['#availability'],
       compute: (continuation, {
-        ['#resolvedContribs']: contribsFromTrack,
+        ['#availability']: availability,
       }) =>
-        (empty(contribsFromTrack)
-          ? continuation()
-          : continuation.raiseOutput({
+        (availability
+          ? continuation.raiseOutput({
               ['#hasUniqueCoverArt']: true,
-            })),
+            })
+          : continuation()),
     },
 
     withPropertyFromAlbum({
       property: input.value('trackCoverArtistContribs'),
+      internal: input.value(true),
+    }),
+
+    withResultOfAvailabilityCheck({
+      from: '#album.trackCoverArtistContribs',
+      mode: input.value('empty'),
     }),
 
     {
-      dependencies: ['#album.trackCoverArtistContribs'],
+      dependencies: ['#availability'],
       compute: (continuation, {
-        ['#album.trackCoverArtistContribs']: contribsFromAlbum,
+        ['#availability']: availability,
       }) =>
-        continuation.raiseOutput({
-          ['#hasUniqueCoverArt']:
-            !empty(contribsFromAlbum),
-        }),
+        (availability
+          ? continuation.raiseOutput({
+              ['#hasUniqueCoverArt']: true,
+            })
+          : continuation()),
     },
+
+    raiseOutputWithoutDependency({
+      dependency: 'trackArtworks',
+      mode: input.value('empty'),
+      output: input.value({'#hasUniqueCoverArt': false}),
+    }),
+
+    withPropertyFromList({
+      list: 'trackArtworks',
+      property: input.value('artistContribs'),
+      internal: input.value(true),
+    }),
+
+    // Since we're getting the update value for each artwork's artistContribs,
+    // it may not be set at all, and in that case won't be exposing as [].
+    fillMissingListItems({
+      list: '#trackArtworks.artistContribs',
+      fill: input.value([]),
+    }),
+
+    withFlattenedList({
+      list: '#trackArtworks.artistContribs',
+    }),
+
+    withResultOfAvailabilityCheck({
+      from: '#flattenedList',
+      mode: input.value('empty'),
+    }).outputs({
+      '#availability': '#hasUniqueCoverArt',
+    }),
   ],
 });
diff --git a/src/data/composite/things/track/withOriginalRelease.js b/src/data/composite/things/track/withMainRelease.js
index 7aefc64a..3a91edae 100644
--- a/src/data/composite/things/track/withOriginalRelease.js
+++ b/src/data/composite/things/track/withMainRelease.js
@@ -1,7 +1,7 @@
-// Just includes the original release of this track as a dependency.
-// If this track isn't a rerelease, then it'll provide null, unless the
-// {selfIfOriginal} option is set, in which case it'll provide this track
-// itself. This will early exit (with notFoundValue) if the original release
+// Just includes the main release of this track as a dependency.
+// If this track isn't a secondary release, then it'll provide null, unless
+// the {selfIfMain} option is set, in which case it'll provide this track
+// itself. This will early exit (with notFoundValue) if the main release
 // is specified by reference and that reference doesn't resolve to anything.
 
 import {input, templateCompositeFrom} from '#composite';
@@ -12,42 +12,42 @@ import {withResolvedReference} from '#composite/wiki-data';
 import {soupyFind} from '#composite/wiki-properties';
 
 export default templateCompositeFrom({
-  annotation: `withOriginalRelease`,
+  annotation: `withMainRelease`,
 
   inputs: {
-    selfIfOriginal: input({type: 'boolean', defaultValue: false}),
+    selfIfMain: input({type: 'boolean', defaultValue: false}),
     notFoundValue: input({defaultValue: null}),
   },
 
-  outputs: ['#originalRelease'],
+  outputs: ['#mainRelease'],
 
   steps: () => [
     withResultOfAvailabilityCheck({
-      from: 'originalReleaseTrack',
+      from: 'mainReleaseTrack',
     }),
 
     {
       dependencies: [
         input.myself(),
-        input('selfIfOriginal'),
+        input('selfIfMain'),
         '#availability',
       ],
 
       compute: (continuation, {
         [input.myself()]: track,
-        [input('selfIfOriginal')]: selfIfOriginal,
+        [input('selfIfMain')]: selfIfMain,
         '#availability': availability,
       }) =>
         (availability
           ? continuation()
           : continuation.raiseOutput({
-              ['#originalRelease']:
-                (selfIfOriginal ? track : null),
+              ['#mainRelease']:
+                (selfIfMain ? track : null),
             })),
     },
 
     withResolvedReference({
-      ref: 'originalReleaseTrack',
+      ref: 'mainReleaseTrack',
       find: soupyFind.input('track'),
     }),
 
@@ -63,7 +63,7 @@ export default templateCompositeFrom({
         ['#resolvedReference']: resolvedReference,
       }) =>
         continuation({
-          ['#originalRelease']: resolvedReference,
+          ['#mainRelease']: resolvedReference,
         }),
     },
   ],
diff --git a/src/data/composite/things/track/withOtherReleases.js b/src/data/composite/things/track/withOtherReleases.js
index f8c1c3f0..bb3e8983 100644
--- a/src/data/composite/things/track/withOtherReleases.js
+++ b/src/data/composite/things/track/withOtherReleases.js
@@ -1,8 +1,9 @@
-import {input, templateCompositeFrom} from '#composite';
+// Gets all releases of the current track *except* this track itself;
+// in other words, all other releases of the current track.
 
-import {exitWithoutDependency} from '#composite/control-flow';
+import {input, templateCompositeFrom} from '#composite';
 
-import withOriginalRelease from './withOriginalRelease.js';
+import withAllReleases from './withAllReleases.js';
 
 export default templateCompositeFrom({
   annotation: `withOtherReleases`,
@@ -10,31 +11,16 @@ export default templateCompositeFrom({
   outputs: ['#otherReleases'],
 
   steps: () => [
-    exitWithoutDependency({
-      dependency: 'trackData',
-      mode: input.value('empty'),
-    }),
-
-    withOriginalRelease({
-      selfIfOriginal: input.value(true),
-      notFoundValue: input.value([]),
-    }),
+    withAllReleases(),
 
     {
-      dependencies: [input.myself(), '#originalRelease', 'trackData'],
+      dependencies: [input.myself(), '#allReleases'],
       compute: (continuation, {
         [input.myself()]: thisTrack,
-        ['#originalRelease']: originalRelease,
-        trackData,
+        ['#allReleases']: allReleases,
       }) => continuation({
         ['#otherReleases']:
-          (originalRelease === thisTrack
-            ? []
-            : [originalRelease])
-            .concat(trackData.filter(track =>
-              track !== originalRelease &&
-              track !== thisTrack &&
-              track.originalReleaseTrack === originalRelease)),
+          allReleases.filter(track => track !== thisTrack),
       }),
     },
   ],
diff --git a/src/data/composite/things/track/withPropertyFromAlbum.js b/src/data/composite/things/track/withPropertyFromAlbum.js
index e9c5b56e..a203c2e7 100644
--- a/src/data/composite/things/track/withPropertyFromAlbum.js
+++ b/src/data/composite/things/track/withPropertyFromAlbum.js
@@ -5,13 +5,12 @@ import {input, templateCompositeFrom} from '#composite';
 
 import {withPropertyFromObject} from '#composite/data';
 
-import withAlbum from './withAlbum.js';
-
 export default templateCompositeFrom({
   annotation: `withPropertyFromAlbum`,
 
   inputs: {
     property: input.staticValue({type: 'string'}),
+    internal: input({type: 'boolean', defaultValue: false}),
   },
 
   outputs: ({
@@ -19,11 +18,21 @@ export default templateCompositeFrom({
   }) => ['#album.' + property],
 
   steps: () => [
-    withAlbum(),
+    // XXX: This is a ridiculous hack considering `defaultValue` above.
+    // If we were certain what was up, we'd just get around to fixing it LOL
+    {
+      dependencies: [input('internal')],
+      compute: (continuation, {
+        [input('internal')]: internal,
+      }) => continuation({
+        ['#internal']: internal ?? false,
+      }),
+    },
 
     withPropertyFromObject({
-      object: '#album',
+      object: 'album',
       property: input('property'),
+      internal: '#internal',
     }),
 
     {
diff --git a/src/data/composite/things/track/withPropertyFromOriginalRelease.js b/src/data/composite/things/track/withPropertyFromMainRelease.js
index fd37f6de..393a4c63 100644
--- a/src/data/composite/things/track/withPropertyFromOriginalRelease.js
+++ b/src/data/composite/things/track/withPropertyFromMainRelease.js
@@ -1,8 +1,8 @@
-// Provides a value inherited from the original release, if applicable, and a
-// flag indicating if this track is a rerelase or not.
+// Provides a value inherited from the main release, if applicable, and a
+// flag indicating if this track is a secondary release or not.
 //
-// Like withOriginalRelease, this will early exit (with notFoundValue) if the
-// original release is specified by reference and that reference doesn't
+// Like withMainRelease, this will early exit (with notFoundValue) if the
+// main release is specified by reference and that reference doesn't
 // resolve to anything.
 
 import {input, templateCompositeFrom} from '#composite';
@@ -10,10 +10,10 @@ import {input, templateCompositeFrom} from '#composite';
 import {withResultOfAvailabilityCheck} from '#composite/control-flow';
 import {withPropertyFromObject} from '#composite/data';
 
-import withOriginalRelease from './withOriginalRelease.js';
+import withMainRelease from './withMainRelease.js';
 
 export default templateCompositeFrom({
-  annotation: `inheritFromOriginalRelease`,
+  annotation: `inheritFromMainRelease`,
 
   inputs: {
     property: input({type: 'string'}),
@@ -26,18 +26,18 @@ export default templateCompositeFrom({
   outputs: ({
     [input.staticValue('property')]: property,
   }) =>
-    ['#isRerelease'].concat(
+    ['#isSecondaryRelease'].concat(
       (property
-        ? ['#original.' + property]
-        : ['#originalValue'])),
+        ? ['#mainRelease.' + property]
+        : ['#mainReleaseValue'])),
 
   steps: () => [
-    withOriginalRelease({
+    withMainRelease({
       notFoundValue: input('notFoundValue'),
     }),
 
     withResultOfAvailabilityCheck({
-      from: '#originalRelease',
+      from: '#mainRelease',
     }),
 
     {
@@ -54,14 +54,14 @@ export default templateCompositeFrom({
           ? continuation()
           : continuation.raiseOutput(
               Object.assign(
-                {'#isRerelease': false},
+                {'#isSecondaryRelease': false},
                 (property
-                  ? {['#original.' + property]: null}
-                  : {'#originalValue': null})))),
+                  ? {['#mainRelease.' + property]: null}
+                  : {'#mainReleaseValue': null})))),
     },
 
     withPropertyFromObject({
-      object: '#originalRelease',
+      object: '#mainRelease',
       property: input('property'),
     }),
 
@@ -77,10 +77,10 @@ export default templateCompositeFrom({
       }) =>
         continuation.raiseOutput(
           Object.assign(
-            {'#isRerelease': true},
+            {'#isSecondaryRelease': true},
             (property
-              ? {['#original.' + property]: value}
-              : {'#originalValue': value}))),
+              ? {['#mainRelease.' + property]: value}
+              : {'#mainReleaseValue': value}))),
     },
   ],
 });
diff --git a/src/data/composite/things/track/withTrackArtDate.js b/src/data/composite/things/track/withTrackArtDate.js
index e2c4d8bc..9b7b61c7 100644
--- a/src/data/composite/things/track/withTrackArtDate.js
+++ b/src/data/composite/things/track/withTrackArtDate.js
@@ -1,11 +1,3 @@
-// Gets the date of cover art release. This represents only the track's own
-// unique cover artwork, if any.
-//
-// If the 'fallback' option is false (the default), this will only output
-// the track's own coverArtDate or its album's trackArtDate. If 'fallback'
-// is set, and neither of these is available, it'll output the track's own
-// date instead.
-
 import {input, templateCompositeFrom} from '#composite';
 import {isDate} from '#validators';
 
@@ -24,11 +16,6 @@ export default templateCompositeFrom({
       defaultDependency: 'coverArtDate',
       acceptsNull: true,
     }),
-
-    fallback: input({
-      type: 'boolean',
-      defaultValue: false,
-    }),
   },
 
   outputs: ['#trackArtDate'],
@@ -57,20 +44,13 @@ export default templateCompositeFrom({
     }),
 
     {
-      dependencies: [
-        '#album.trackArtDate',
-        input('fallback'),
-      ],
-
+      dependencies: ['#album.trackArtDate'],
       compute: (continuation, {
         ['#album.trackArtDate']: albumTrackArtDate,
-        [input('fallback')]: fallback,
       }) =>
         (albumTrackArtDate
           ? continuation.raiseOutput({'#trackArtDate': albumTrackArtDate})
-       : fallback
-          ? continuation()
-          : continuation.raiseOutput({'#trackArtDate': null})),
+          : continuation()),
     },
 
     withDate().outputs({
diff --git a/src/data/composite/things/track/withTrackNumber.js b/src/data/composite/things/track/withTrackNumber.js
new file mode 100644
index 00000000..61428e8c
--- /dev/null
+++ b/src/data/composite/things/track/withTrackNumber.js
@@ -0,0 +1,50 @@
+import {input, templateCompositeFrom} from '#composite';
+
+import {raiseOutputWithoutDependency} from '#composite/control-flow';
+import {withIndexInList, withPropertiesFromObject} from '#composite/data';
+
+import withContainingTrackSection from './withContainingTrackSection.js';
+
+export default templateCompositeFrom({
+  annotation: `withTrackNumber`,
+
+  outputs: ['#trackNumber'],
+
+  steps: () => [
+    withContainingTrackSection(),
+
+    // Zero is the fallback, not one, but in most albums the first track
+    // (and its intended output by this composition) will be one.
+    raiseOutputWithoutDependency({
+      dependency: '#trackSection',
+      output: input.value({'#trackNumber': 0}),
+    }),
+
+    withPropertiesFromObject({
+      object: '#trackSection',
+      properties: input.value(['tracks', 'startCountingFrom']),
+    }),
+
+    withIndexInList({
+      list: '#trackSection.tracks',
+      item: input.myself(),
+    }),
+
+    raiseOutputWithoutDependency({
+      dependency: '#index',
+      output: input.value({'#trackNumber': 0}),
+    }),
+
+    {
+      dependencies: ['#trackSection.startCountingFrom', '#index'],
+      compute: (continuation, {
+        ['#trackSection.startCountingFrom']: startCountingFrom,
+        ['#index']: index,
+      }) => continuation({
+        ['#trackNumber']:
+          startCountingFrom +
+          index,
+      }),
+    },
+  ],
+});