@bycrux/editor 0.11.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/src/ControlsInfoModal.tsx +251 -61
- package/src/__tests__/ControlsInfoModal.test.tsx +43 -0
- package/src/__tests__/adapter.test.ts +59 -1
- package/src/__tests__/schema-assignability.test.ts +93 -0
- package/src/__tests__/schema.test.ts +15 -0
- package/src/__tests__/video-adapter-contract.test.ts +128 -5
- package/src/carousel/AddElementMenu.tsx +10 -4
- package/src/carousel/CarouselEditor.tsx +46 -8
- package/src/carousel/CarouselRenderModal.tsx +15 -10
- package/src/carousel/OverlayPicker.tsx +10 -3
- package/src/carousel/SlidePropertyPanel.tsx +43 -21
- package/src/components/FilmstripScrubber.tsx +277 -0
- package/src/components/__tests__/FilmstripScrubber.test.tsx +216 -0
- package/src/engine/__tests__/audio-clock.test.ts +655 -0
- package/src/engine/__tests__/audio-worklet-source.test.ts +316 -0
- package/src/engine/__tests__/batch-planner.test.ts +298 -0
- package/src/engine/__tests__/decode-worker-source.test.ts +249 -0
- package/src/engine/__tests__/demux-ranged.test.ts +495 -0
- package/src/engine/__tests__/demux-truncated.test.ts +136 -0
- package/src/engine/__tests__/demux.test.ts +305 -0
- package/src/engine/__tests__/eligibility.test.ts +146 -0
- package/src/engine/__tests__/engine-recovery.test.ts +263 -0
- package/src/engine/__tests__/engine.test.ts +326 -0
- package/src/engine/__tests__/frame-server-ranged.test.ts +261 -0
- package/src/engine/__tests__/frame-server.test.ts +326 -0
- package/src/engine/__tests__/media-loader-ranged.test.ts +289 -0
- package/src/engine/__tests__/media-loader.test.ts +100 -0
- package/src/engine/__tests__/scheduler-crop.test.ts +353 -0
- package/src/engine/__tests__/scheduler.test.ts +1310 -0
- package/src/engine/__tests__/scrub-resolve.test.ts +96 -0
- package/src/engine/__tests__/scrub-source.test.ts +195 -0
- package/src/engine/__tests__/source-host.test.ts +455 -0
- package/src/engine/__tests__/time-stretch.test.ts +182 -0
- package/src/engine/audio-clock.ts +1179 -0
- package/src/engine/audio-worklet-source.ts +160 -0
- package/src/engine/batch-planner.ts +308 -0
- package/src/engine/debug-hud.tsx +54 -0
- package/src/engine/decode-worker-source.ts +142 -0
- package/src/engine/demux.ts +900 -0
- package/src/engine/eligibility.ts +140 -0
- package/src/engine/frame-server.ts +644 -0
- package/src/engine/index.ts +950 -0
- package/src/engine/media-loader.ts +380 -0
- package/src/engine/mp4box.d.ts +214 -0
- package/src/engine/scheduler.ts +1324 -0
- package/src/engine/scrub-resolve.ts +66 -0
- package/src/engine/scrub-source.ts +496 -0
- package/src/engine/time-stretch.ts +224 -0
- package/src/index.ts +83 -1
- package/src/preview/OverlayPreview.tsx +2 -24
- package/src/schema.ts +146 -3
- package/src/state/__tests__/use-project-sync.test.tsx +56 -0
- package/src/state/use-project-sync.ts +17 -0
- package/src/test-setup.ts +32 -0
- package/src/text/FontPicker.tsx +85 -51
- package/src/text/TextFormattingToolbar.tsx +6 -1
- package/src/text/__tests__/FontPicker.options.test.ts +43 -0
- package/src/text/__tests__/TextFormattingToolbar.test.tsx +4 -4
- package/src/theme.ts +108 -3
- package/src/types.ts +601 -22
- package/src/ui/Loader.tsx +64 -0
- package/src/ui/NumberField.tsx +254 -0
- package/src/ui/Slider.tsx +128 -0
- package/src/ui/Tooltip.tsx +118 -0
- package/src/ui/__tests__/NumberField.test.tsx +298 -0
- package/src/ui/__tests__/Slider.test.tsx +150 -0
- package/src/ui/__tests__/Tooltip.test.tsx +105 -0
- package/src/ui/__tests__/usePersistentState.test.tsx +112 -0
- package/src/ui/badge.tsx +12 -4
- package/src/ui/index.ts +6 -0
- package/src/ui/input.tsx +1 -1
- package/src/ui/select.tsx +1 -1
- package/src/ui/switch.tsx +12 -2
- package/src/ui/textarea.tsx +1 -1
- package/src/ui/usePersistentState.ts +63 -0
- package/src/video/AudioPolishModal.tsx +983 -0
- package/src/video/CaptionListPanel.test.tsx +944 -0
- package/src/video/CaptionListPanel.tsx +1106 -0
- package/src/video/CaptionRegenModal.tsx +37 -9
- package/src/video/CaptionSpecimen.tsx +160 -0
- package/src/video/CaptionStyleGallery.tsx +466 -0
- package/src/video/CommandPalette.tsx +165 -0
- package/src/video/ImageToneMenu.tsx +137 -0
- package/src/video/OverlayInspector.tsx +977 -0
- package/src/video/RenderModal.tsx +1035 -62
- package/src/video/VersionCompare.tsx +258 -0
- package/src/video/VersionPanel.tsx +117 -42
- package/src/video/VideoEditor.tsx +2229 -243
- package/src/video/__tests__/AudioPolishModal.test.tsx +825 -0
- package/src/video/__tests__/CaptionListPanel.font.test.tsx +397 -0
- package/src/video/__tests__/CaptionListPanel.generate.test.tsx +153 -0
- package/src/video/__tests__/CaptionRegenModal.test.tsx +29 -0
- package/src/video/__tests__/CaptionSpecimen.test.tsx +213 -0
- package/src/video/__tests__/CaptionStyleGallery.test.tsx +362 -0
- package/src/video/__tests__/CommandPalette.test.tsx +119 -0
- package/src/video/__tests__/OverlayInspector.test.tsx +1367 -0
- package/src/video/__tests__/RenderModal.exportControls.test.tsx +319 -0
- package/src/video/__tests__/RenderModal.options.test.tsx +562 -0
- package/src/video/__tests__/RenderModal.progress.test.ts +65 -0
- package/src/video/__tests__/VersionCompare.test.tsx +182 -0
- package/src/video/__tests__/VersionPanel.test.tsx +279 -0
- package/src/video/__tests__/VideoEditor.audioPolish.test.tsx +241 -0
- package/src/video/__tests__/VideoEditor.captionDelete.test.tsx +147 -0
- package/src/video/__tests__/VideoEditor.captionGesture.test.tsx +170 -0
- package/src/video/__tests__/VideoEditor.captionSeam.test.tsx +134 -0
- package/src/video/__tests__/VideoEditor.clipKeyframes.test.tsx +59 -0
- package/src/video/__tests__/VideoEditor.context.test.tsx +44 -0
- package/src/video/__tests__/VideoEditor.editFocus.test.tsx +55 -0
- package/src/video/__tests__/VideoEditor.keymap.test.tsx +725 -0
- package/src/video/__tests__/VideoEditor.layout.test.tsx +338 -0
- package/src/video/__tests__/VideoEditor.propertiesPanel.test.tsx +765 -0
- package/src/video/__tests__/VideoEditor.rippleDeleteCaptions.test.tsx +159 -0
- package/src/video/__tests__/VideoEditor.sourcePreview.test.tsx +122 -0
- package/src/video/__tests__/VideoEditor.test.tsx +403 -50
- package/src/video/__tests__/audioMagnet.test.ts +158 -0
- package/src/video/__tests__/audioPolish.test.ts +1202 -0
- package/src/video/__tests__/captionActiveWord.test.ts +107 -0
- package/src/video/__tests__/captionLanes.test.ts +262 -0
- package/src/video/__tests__/captionPositioning.test.tsx +8 -3
- package/src/video/__tests__/captionWordFloor.test.ts +228 -0
- package/src/video/__tests__/clipboard-ops.test.ts +430 -0
- package/src/video/__tests__/cuts.insert.test.ts +244 -0
- package/src/video/__tests__/cuts.test.ts +1213 -34
- package/src/video/__tests__/export-limits.test.ts +195 -0
- package/src/video/__tests__/hover-scrub.test.ts +163 -0
- package/src/video/__tests__/keyframeOps.canKeyframeProp.test.ts +61 -0
- package/src/video/__tests__/keyframeOps.test.ts +643 -0
- package/src/video/__tests__/keymap.test.tsx +171 -0
- package/src/video/__tests__/render-progress.test.tsx +20 -4
- package/src/video/__tests__/shuttle.test.ts +210 -0
- package/src/video/__tests__/source-preview.test.ts +60 -0
- package/src/video/__tests__/timecode.test.ts +77 -0
- package/src/video/__tests__/use-report-context.test.tsx +101 -0
- package/src/video/audioMagnet.ts +72 -0
- package/src/video/audioPolish.ts +774 -0
- package/src/video/captionActiveWord.ts +74 -0
- package/src/video/captionLanes.ts +202 -0
- package/src/video/captionRepair.ts +9 -5
- package/src/video/captionStyleDefaults.ts +100 -0
- package/src/video/captionWordFloor.ts +83 -0
- package/src/video/clipboard-ops.ts +377 -0
- package/src/video/cuts.ts +713 -37
- package/src/video/export-limits.ts +102 -0
- package/src/video/hover-scrub.ts +102 -0
- package/src/video/imageTone.ts +58 -0
- package/src/video/imageToneExamples.ts +10 -0
- package/src/video/keyframeOps.ts +384 -0
- package/src/video/keymap.ts +146 -0
- package/src/video/panels/ClipPropertiesPanel.tsx +706 -0
- package/src/video/panels/LeftPanelTabs.tsx +187 -0
- package/src/video/panels/OverlayContentPanel.tsx +351 -0
- package/src/video/panels/TabNav.tsx +60 -0
- package/src/video/panels/__tests__/ClipPropertiesPanel.test.tsx +645 -0
- package/src/video/panels/__tests__/LeftPanelTabs.test.tsx +189 -0
- package/src/video/panels/__tests__/OverlayContentPanel.test.tsx +222 -0
- package/src/video/panels/__tests__/TabNav.test.tsx +71 -0
- package/src/video/preview/CaptionPreview.tsx +57 -69
- package/src/video/preview/EngineSurface.tsx +103 -0
- package/src/video/preview/OverlayItemsLayer.tsx +315 -103
- package/src/video/preview/PreviewPlayer.tsx +337 -56
- package/src/video/preview/SocialPreviewMenu.tsx +214 -0
- package/src/video/preview/SocialSafeZoneOverlay.tsx +478 -0
- package/src/video/preview/__tests__/CaptionPreview.fonts.test.tsx +97 -0
- package/src/video/preview/__tests__/EngineSurface.test.tsx +114 -0
- package/src/video/preview/__tests__/OverlayItemsLayer.edit.test.tsx +4 -2
- package/src/video/preview/__tests__/OverlayItemsLayer.keyframes.test.tsx +363 -0
- package/src/video/preview/__tests__/OverlayItemsLayer.selection.test.tsx +329 -0
- package/src/video/preview/__tests__/PreviewPlayer.engine.test.tsx +139 -0
- package/src/video/preview/__tests__/SocialPreviewMenu.test.tsx +121 -0
- package/src/video/preview/__tests__/SocialSafeZoneOverlay.test.tsx +165 -0
- package/src/video/preview/__tests__/captionDragState.test.ts +120 -1
- package/src/video/preview/__tests__/latencyCompensation.test.tsx +451 -0
- package/src/video/preview/__tests__/proxySupport.test.ts +75 -0
- package/src/video/preview/__tests__/transformStyle.test.ts +29 -1
- package/src/video/preview/__tests__/useDragOverlay.perAxis.test.ts +197 -0
- package/src/video/preview/__tests__/useEnginePlayback.test.tsx +530 -0
- package/src/video/preview/__tests__/useVideoPlayback.corpus.test.ts +313 -0
- package/src/video/preview/__tests__/useVideoPlayback.test.ts +38 -5
- package/src/video/preview/__tests__/useVideoPlayback.trackAudio.test.ts +278 -0
- package/src/video/preview/audio-context.ts +111 -0
- package/src/video/preview/captionDragState.ts +91 -1
- package/src/video/preview/proxySupport.ts +86 -0
- package/src/video/preview/transformStyle.ts +22 -12
- package/src/video/preview/useDragOverlay.ts +92 -18
- package/src/video/preview/useEnginePlayback.ts +625 -0
- package/src/video/preview/useVideoPlayback.ts +211 -167
- package/src/video/sdrCurves.ts +56 -0
- package/src/video/shuttle.ts +159 -0
- package/src/video/source-preview.ts +66 -0
- package/src/video/timecode.ts +59 -0
- package/src/video/timeline/EditableSegment.tsx +1 -1
- package/src/video/timeline/Scrubber.tsx +46 -174
- package/src/video/timeline/SpeedControl.tsx +95 -0
- package/src/video/timeline/Timeline.tsx +1061 -328
- package/src/video/timeline/TimelineContext.ts +17 -10
- package/src/video/timeline/TrackGutter.tsx +560 -0
- package/src/video/timeline/TrackSettingsPopover.tsx +228 -0
- package/src/video/timeline/VolumeControl.tsx +113 -0
- package/src/video/timeline/__tests__/Timeline.backgroundClick.test.tsx +110 -0
- package/src/video/timeline/__tests__/Timeline.crossfade.test.tsx +104 -0
- package/src/video/timeline/__tests__/Timeline.fadeCurveMenu.test.tsx +174 -0
- package/src/video/timeline/__tests__/Timeline.keyframeDelete.test.tsx +273 -0
- package/src/video/timeline/__tests__/Timeline.keyframeFollow.test.tsx +215 -0
- package/src/video/timeline/__tests__/Timeline.keyframeMenu.test.tsx +253 -0
- package/src/video/timeline/__tests__/Timeline.keymap.test.tsx +372 -0
- package/src/video/timeline/__tests__/Timeline.subcutRegen.test.tsx +351 -0
- package/src/video/timeline/__tests__/TrackGutter.test.tsx +372 -0
- package/src/video/timeline/__tests__/_canvasSelect.test.tsx +273 -0
- package/src/video/timeline/__tests__/_canvasSelect.ts +414 -0
- package/src/video/timeline/__tests__/dragdrop-math.test.ts +135 -0
- package/src/video/timeline/__tests__/effectiveItemAudio.test.ts +50 -0
- package/src/video/timeline/__tests__/enabledTrackItems.test.ts +164 -0
- package/src/video/timeline/__tests__/moveItemAcrossTracks.test.ts +363 -0
- package/src/video/timeline/__tests__/multiSelectOps.test.ts +447 -0
- package/src/video/timeline/__tests__/placement.test.ts +278 -0
- package/src/video/timeline/__tests__/resizeWindowedItem.test.ts +140 -0
- package/src/video/timeline/__tests__/timeline-model.test.ts +576 -0
- package/src/video/timeline/__tests__/visualItemLabel.test.ts +69 -0
- package/src/video/timeline/canvas/TimelineCanvas.tsx +1447 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.drop.test.tsx +439 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.edgeScroll.test.tsx +346 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.panefill.test.tsx +122 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.pendingDrops.test.tsx +316 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.pointer.test.tsx +606 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.test.tsx +407 -0
- package/src/video/timeline/canvas/__tests__/clip-bands.test.ts +77 -0
- package/src/video/timeline/canvas/__tests__/draw.test.ts +2198 -0
- package/src/video/timeline/canvas/__tests__/fade-curve.test.ts +187 -0
- package/src/video/timeline/canvas/__tests__/filmstrips.test.ts +561 -0
- package/src/video/timeline/canvas/__tests__/hit-test.test.ts +818 -0
- package/src/video/timeline/canvas/__tests__/pending-drop.test.ts +210 -0
- package/src/video/timeline/canvas/__tests__/pointer-machine.test.ts +3358 -0
- package/src/video/timeline/canvas/__tests__/snap.test.ts +257 -0
- package/src/video/timeline/canvas/__tests__/viewport.test.ts +399 -0
- package/src/video/timeline/canvas/__tests__/waveforms.test.ts +946 -0
- package/src/video/timeline/canvas/clip-bands.ts +56 -0
- package/src/video/timeline/canvas/draw.ts +2187 -0
- package/src/video/timeline/canvas/fade-curve.ts +111 -0
- package/src/video/timeline/canvas/filmstrips.ts +418 -0
- package/src/video/timeline/canvas/hit-test.ts +501 -0
- package/src/video/timeline/canvas/keyframe-strip.ts +73 -0
- package/src/video/timeline/canvas/pointer-machine.ts +1828 -0
- package/src/video/timeline/canvas/snap.ts +232 -0
- package/src/video/timeline/canvas/viewport.ts +457 -0
- package/src/video/timeline/canvas/waveforms.ts +664 -0
- package/src/video/timeline/makeCaptionEdit.ts +5 -1
- package/src/video/timeline/multiSelectOps.ts +214 -55
- package/src/video/timeline/placement.ts +282 -0
- package/src/video/timeline/timeline-model.ts +919 -0
- package/src/video/timeline/useItemDragDrop.ts +151 -178
- package/src/video/timeline/useTimelineZoom.ts +25 -60
- package/src/video/timeline/utils.ts +0 -12
- package/src/video/use-report-context.ts +75 -0
- package/src/video/preview/OverlayPropsModal.tsx +0 -292
- package/src/video/preview/__tests__/OverlayPropsModal.test.tsx +0 -32
- package/src/video/timeline/AudioTrackRow.tsx +0 -404
- package/src/video/timeline/AudioWaveformLayer.tsx +0 -117
- package/src/video/timeline/CaptionTrackRow.tsx +0 -235
- package/src/video/timeline/PlayheadLine.tsx +0 -18
- package/src/video/timeline/TranscriptModal.tsx +0 -70
- package/src/video/timeline/TranscriptPanel.tsx +0 -273
- package/src/video/timeline/VisualTrackRow.tsx +0 -300
- package/src/video/timeline/__tests__/CaptionTrackRow.test.tsx +0 -241
- package/src/video/timeline/__tests__/PlayheadLine.test.tsx +0 -60
- package/src/video/timeline/__tests__/TranscriptModal.test.tsx +0 -41
- package/src/video/timeline/__tests__/TranscriptPanel.test.tsx +0 -184
- package/src/video/timeline/__tests__/useItemDragDrop.test.ts +0 -72
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fade-shape math (`fade-curve.ts`) — the one source of truth for what
|
|
3
|
+
* linear/log/exp look and sound like, shared by the envelope curve
|
|
4
|
+
* (draw.ts), the waveform's amplitude scaling (waveforms.ts), and — via a
|
|
5
|
+
* name mapping kept there — the rendered mix (mix-audio.js).
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect } from 'vitest'
|
|
8
|
+
import { DEFAULT_FADE_CURVE, fadeCurveIconPoints, fadeGain, makeFadeGainAt } from '../fade-curve'
|
|
9
|
+
|
|
10
|
+
describe('DEFAULT_FADE_CURVE', () => {
|
|
11
|
+
it('is exp — the shape every fade had before curves existed', () => {
|
|
12
|
+
expect(DEFAULT_FADE_CURVE).toBe('exp')
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe('fadeGain', () => {
|
|
17
|
+
it('is 0 at p=0 (the silent edge) and 1 at p=1 (the full-volume edge), for every shape', () => {
|
|
18
|
+
for (const curve of ['linear', 'log', 'exp'] as const) {
|
|
19
|
+
expect(fadeGain(0, curve)).toBe(0)
|
|
20
|
+
expect(fadeGain(1, curve)).toBe(1)
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('linear is a straight ramp: gain(p) === p', () => {
|
|
25
|
+
expect(fadeGain(0.25, 'linear')).toBeCloseTo(0.25)
|
|
26
|
+
expect(fadeGain(0.5, 'linear')).toBeCloseTo(0.5)
|
|
27
|
+
expect(fadeGain(0.75, 'linear')).toBeCloseTo(0.75)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('exp (t²) sits BELOW the linear ramp everywhere in (0,1) — slow start, fast finish', () => {
|
|
31
|
+
for (const p of [0.1, 0.25, 0.5, 0.75, 0.9]) {
|
|
32
|
+
expect(fadeGain(p, 'exp')).toBeLessThan(p)
|
|
33
|
+
}
|
|
34
|
+
expect(fadeGain(0.5, 'exp')).toBeCloseTo(0.25)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('log (t(2-t)) sits ABOVE the linear ramp everywhere in (0,1) — fast start, slow finish', () => {
|
|
38
|
+
for (const p of [0.1, 0.25, 0.5, 0.75, 0.9]) {
|
|
39
|
+
expect(fadeGain(p, 'log')).toBeGreaterThan(p)
|
|
40
|
+
}
|
|
41
|
+
expect(fadeGain(0.5, 'log')).toBeCloseTo(0.75)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('log is the mirror of exp: log(p) === 1 - exp(1 - p)', () => {
|
|
45
|
+
for (const p of [0.1, 0.3, 0.5, 0.7, 0.9]) {
|
|
46
|
+
expect(fadeGain(p, 'log')).toBeCloseTo(1 - fadeGain(1 - p, 'exp'), 10)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('clamps p outside [0, 1] rather than extrapolating', () => {
|
|
51
|
+
expect(fadeGain(-0.5, 'linear')).toBe(0)
|
|
52
|
+
expect(fadeGain(1.5, 'linear')).toBe(1)
|
|
53
|
+
expect(fadeGain(-1, 'exp')).toBe(0)
|
|
54
|
+
expect(fadeGain(2, 'log')).toBe(1)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('every shape is monotonically non-decreasing across [0, 1]', () => {
|
|
58
|
+
for (const curve of ['linear', 'log', 'exp'] as const) {
|
|
59
|
+
let prev = -Infinity
|
|
60
|
+
for (let p = 0; p <= 1; p += 0.05) {
|
|
61
|
+
const g = fadeGain(p, curve)
|
|
62
|
+
expect(g).toBeGreaterThanOrEqual(prev - 1e-9)
|
|
63
|
+
prev = g
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('makeFadeGainAt', () => {
|
|
70
|
+
it('is 1 (full gain) everywhere when neither fade is set', () => {
|
|
71
|
+
const gainAt = makeFadeGainAt(0, 100, 0, 0, 'exp', 'exp')
|
|
72
|
+
expect(gainAt(0)).toBe(1)
|
|
73
|
+
expect(gainAt(50)).toBe(1)
|
|
74
|
+
expect(gainAt(100)).toBe(1)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('fade-in only: 0 at the span start, 1 at (and past) the fade-in edge', () => {
|
|
78
|
+
const gainAt = makeFadeGainAt(0, 100, 20, 0, 'linear', 'exp')
|
|
79
|
+
expect(gainAt(0)).toBeCloseTo(0)
|
|
80
|
+
expect(gainAt(10)).toBeCloseTo(0.5) // linear, halfway through a 20px fade-in
|
|
81
|
+
expect(gainAt(20)).toBeCloseTo(1)
|
|
82
|
+
expect(gainAt(80)).toBeCloseTo(1) // well past the fade-in, unaffected
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('fade-out only: 1 up to the fade-out edge, 0 at the span end', () => {
|
|
86
|
+
const gainAt = makeFadeGainAt(0, 100, 0, 20, 'exp', 'linear')
|
|
87
|
+
expect(gainAt(0)).toBeCloseTo(1)
|
|
88
|
+
expect(gainAt(79)).toBeCloseTo(1, 0) // just before the fade-out region starts
|
|
89
|
+
expect(gainAt(90)).toBeCloseTo(0.5) // linear, halfway through a 20px fade-out
|
|
90
|
+
expect(gainAt(100)).toBeCloseTo(0)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('each side samples its OWN curve independently', () => {
|
|
94
|
+
const gainAt = makeFadeGainAt(0, 100, 20, 20, 'linear', 'exp')
|
|
95
|
+
// Midpoint of the fade-in (p=0.5): linear → 0.5.
|
|
96
|
+
expect(gainAt(10)).toBeCloseTo(0.5)
|
|
97
|
+
// Midpoint of the fade-out (p=0.5 from the silent end): exp → 0.25.
|
|
98
|
+
expect(gainAt(90)).toBeCloseTo(0.25)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('overlapping fades (a bar shorter than fadeIn + fadeOut): the lower gain wins', () => {
|
|
102
|
+
// A 30px-wide span with a 20px fade-in AND a 20px fade-out overlapping
|
|
103
|
+
// across its whole width — every point should take whichever fade pulls
|
|
104
|
+
// it lower, so gain never exceeds what either side alone would allow.
|
|
105
|
+
const gainAt = makeFadeGainAt(0, 30, 20, 20, 'linear', 'linear')
|
|
106
|
+
for (let x = 0; x <= 30; x += 3) {
|
|
107
|
+
const g = gainAt(x)
|
|
108
|
+
expect(g).toBeGreaterThanOrEqual(0)
|
|
109
|
+
expect(g).toBeLessThanOrEqual(1)
|
|
110
|
+
}
|
|
111
|
+
// The very center should be pulled down by BOTH fades, not sitting at 1.
|
|
112
|
+
expect(gainAt(15)).toBeLessThan(1)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('anchors to spanX, not x=0 — a scrolled/offset span still fades correctly', () => {
|
|
116
|
+
const gainAt = makeFadeGainAt(-100, 300, 40, 30, 'linear', 'linear')
|
|
117
|
+
expect(gainAt(-100)).toBeCloseTo(0) // span start: silent
|
|
118
|
+
expect(gainAt(-60)).toBeCloseTo(1) // fade-in's own inner edge
|
|
119
|
+
expect(gainAt(170)).toBeCloseTo(1) // fade-out's own inner edge
|
|
120
|
+
expect(gainAt(200)).toBeCloseTo(0) // span end: silent
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('clamps the result to [0, 1]', () => {
|
|
124
|
+
const gainAt = makeFadeGainAt(0, 100, 20, 20, 'linear', 'linear')
|
|
125
|
+
expect(gainAt(-50)).toBeGreaterThanOrEqual(0)
|
|
126
|
+
expect(gainAt(500)).toBeGreaterThanOrEqual(0)
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
describe('fadeCurveIconPoints', () => {
|
|
131
|
+
const W = 40
|
|
132
|
+
const H = 28
|
|
133
|
+
|
|
134
|
+
it('returns segments+1 points, defaulting to 24 segments', () => {
|
|
135
|
+
expect(fadeCurveIconPoints('linear', W, H)).toHaveLength(25)
|
|
136
|
+
expect(fadeCurveIconPoints('linear', W, H, 10)).toHaveLength(11)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('starts at the bottom-left (silent) and ends at the top-right (full volume), for every shape', () => {
|
|
140
|
+
for (const curve of ['linear', 'log', 'exp'] as const) {
|
|
141
|
+
const points = fadeCurveIconPoints(curve, W, H)
|
|
142
|
+
expect(points[0]).toEqual({ x: 0, y: H }) // p=0, gain=0 → bottom-left
|
|
143
|
+
expect(points[points.length - 1]).toEqual({ x: W, y: 0 }) // p=1, gain=1 → top-right
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('x runs left→right in equal steps, independent of the curve', () => {
|
|
148
|
+
const points = fadeCurveIconPoints('exp', W, H, 4)
|
|
149
|
+
expect(points.map(p => p.x)).toEqual([0, W / 4, W / 2, (3 * W) / 4, W])
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('linear is a straight diagonal: y is an exact linear function of x', () => {
|
|
153
|
+
const points = fadeCurveIconPoints('linear', W, H)
|
|
154
|
+
for (const { x, y } of points) {
|
|
155
|
+
expect(y).toBeCloseTo(H - (x / W) * H)
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('exp is concave-up: stays LOW (near the bottom) through the midpoint, then rises sharply', () => {
|
|
160
|
+
// Reuses `fadeGain` directly, so this is really just checking the icon
|
|
161
|
+
// wires the same math through — see fadeGain's own concave-up test for
|
|
162
|
+
// the underlying claim.
|
|
163
|
+
const points = fadeCurveIconPoints('exp', W, H)
|
|
164
|
+
const mid = points[Math.round(points.length / 2) - 1]
|
|
165
|
+
const linearMidY = H - 0.5 * H
|
|
166
|
+
// "Stays low" in a y-down SVG means a LARGER y (closer to the bottom)
|
|
167
|
+
// than the straight-line midpoint.
|
|
168
|
+
expect(mid.y).toBeGreaterThan(linearMidY)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('log is concave-down: rises fast, sitting HIGH (near the top) by the midpoint', () => {
|
|
172
|
+
const points = fadeCurveIconPoints('log', W, H)
|
|
173
|
+
const mid = points[Math.round(points.length / 2) - 1]
|
|
174
|
+
const linearMidY = H - 0.5 * H
|
|
175
|
+
// "Levels near the top" means a SMALLER y than the straight-line midpoint.
|
|
176
|
+
expect(mid.y).toBeLessThan(linearMidY)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('every shape is monotonically non-increasing in y as x increases (gain only ever rises)', () => {
|
|
180
|
+
for (const curve of ['linear', 'log', 'exp'] as const) {
|
|
181
|
+
const points = fadeCurveIconPoints(curve, W, H)
|
|
182
|
+
for (let i = 1; i < points.length; i++) {
|
|
183
|
+
expect(points[i].y).toBeLessThanOrEqual(points[i - 1].y + 1e-9)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
})
|