paces 0.0.18__tar.gz → 0.0.20__tar.gz
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.
- {paces-0.0.18 → paces-0.0.20}/PKG-INFO +1 -1
- {paces-0.0.18 → paces-0.0.20}/paces/ask.py +1 -1
- {paces-0.0.18 → paces-0.0.20}/paces/corrections.py +1 -1
- {paces-0.0.18 → paces-0.0.20}/paces/evidence.py +2 -2
- {paces-0.0.18 → paces-0.0.20}/paces/measure.py +2 -2
- {paces-0.0.18 → paces-0.0.20}/paces/projection.py +1 -1
- paces-0.0.20/paces/segmentation.py +74 -0
- {paces-0.0.18 → paces-0.0.20}/paces/segmenters.py +62 -66
- {paces-0.0.18 → paces-0.0.20}/pyproject.toml +1 -1
- {paces-0.0.18 → paces-0.0.20}/tests/test_segment.py +29 -0
- {paces-0.0.18 → paces-0.0.20}/.gitignore +0 -0
- {paces-0.0.18 → paces-0.0.20}/LICENSE +0 -0
- {paces-0.0.18 → paces-0.0.20}/README.md +0 -0
- {paces-0.0.18 → paces-0.0.20}/docs/README.md +0 -0
- {paces-0.0.18 → paces-0.0.20}/docs/alignment/README.md +0 -0
- {paces-0.0.18 → paces-0.0.20}/docs/poc-reference/README.md +0 -0
- {paces-0.0.18 → paces-0.0.20}/docs/poc-reference/artifacts/clips.json +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/__init__.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/__main__.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/bodies/__init__.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/bodies/_base.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/bodies/derivation.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/bodies/document.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/bodies/signal.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/bodies/step.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/cues.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/data/ask/ask.css +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/data/ask/ask.html +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/data/ask/ask.js +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/derivation.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/edits.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/excerpts.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/model.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/pose.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/render.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/timed_text.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/paces/tools.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/audio_synth.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/data/body_schemas.json +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/data/routine.json +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/poc_fixture.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_ask.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_chapters.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_cli_surface.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_corrections.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_cues.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_derivation.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_edit_protection_residuals.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_edits.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_evidence.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_evidence_schemas.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_excerpts.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_measure.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_pose.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_render_media.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_roundtrip_poc.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_smoke.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/test_vertical_slice.py +0 -0
- {paces-0.0.18 → paces-0.0.20}/tests/video_synth.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: paces
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.20
|
|
4
4
|
Summary: Turn instructional media into structured, interactive learning material
|
|
5
5
|
Project-URL: Homepage, https://github.com/thorwhalen/paces
|
|
6
6
|
Project-URL: Repository, https://github.com/thorwhalen/paces
|
|
@@ -49,7 +49,7 @@ from paces.corrections import (
|
|
|
49
49
|
time_str,
|
|
50
50
|
)
|
|
51
51
|
from paces.model import seconds_per_unit
|
|
52
|
-
from paces.
|
|
52
|
+
from paces.segmentation import Segmentation
|
|
53
53
|
|
|
54
54
|
PROPOSAL_KIND = "PacesAskProposal"
|
|
55
55
|
ANSWERS_KIND = "PacesAskAnswers"
|
|
@@ -144,7 +144,7 @@ from paces.model import (
|
|
|
144
144
|
StepDocument,
|
|
145
145
|
)
|
|
146
146
|
from paces.projection import to_document
|
|
147
|
-
from paces.
|
|
147
|
+
from paces.segmentation import Segmentation
|
|
148
148
|
|
|
149
149
|
#: The namespace every paces annotation id is derived under. Derived from the
|
|
150
150
|
#: repository URL rather than typed as a literal, so it is reproducible from
|
|
@@ -454,7 +454,7 @@ def to_store(
|
|
|
454
454
|
*seg* is the analysis result; the projection arguments (*doc_id*,
|
|
455
455
|
*title*, *source*, *domain*, *lang*) are :func:`paces.to_document`'s,
|
|
456
456
|
unchanged. Everything else is evidence a segmenter does not carry on
|
|
457
|
-
:class:`~paces.
|
|
457
|
+
:class:`~paces.segmentation.Segmentation` but a pipeline may have:
|
|
458
458
|
|
|
459
459
|
Args:
|
|
460
460
|
store: Any ``lacing.IntervalAnnotationStore`` — ``MemoryStore()`` for
|
|
@@ -45,7 +45,7 @@ from paces.model import MetricGrid, decimal_str, seconds_per_unit
|
|
|
45
45
|
from paces.segmenters import (
|
|
46
46
|
Capability,
|
|
47
47
|
Segmentation,
|
|
48
|
-
|
|
48
|
+
place_on_grid,
|
|
49
49
|
register,
|
|
50
50
|
)
|
|
51
51
|
|
|
@@ -318,7 +318,7 @@ def _segment_grid_measured(inputs: Mapping[str, Any]) -> Segmentation:
|
|
|
318
318
|
grid = measurement.grid
|
|
319
319
|
if grid.tempo_bpm is None or grid.origin is None:
|
|
320
320
|
return Segmentation(grid=grid, confidence=measurement.confidence, flags=flags)
|
|
321
|
-
placed =
|
|
321
|
+
placed = place_on_grid(inputs["steps"], grid)
|
|
322
322
|
return Segmentation(
|
|
323
323
|
steps=placed.steps,
|
|
324
324
|
boundaries=placed.boundaries,
|
|
@@ -21,7 +21,7 @@ from paces.model import (
|
|
|
21
21
|
StepDocument,
|
|
22
22
|
seconds_per_unit,
|
|
23
23
|
)
|
|
24
|
-
from paces.
|
|
24
|
+
from paces.segmentation import SegStep, Segmentation
|
|
25
25
|
|
|
26
26
|
#: Decimal places kept when converting computed seconds to wire decimals —
|
|
27
27
|
#: matches the POC's 0.1 s working resolution with headroom.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""What a segmenter returns: :class:`Segmentation`, its :class:`SegStep` records and
|
|
2
|
+
the ``Span`` they cover — the analysis-result types, with nothing else.
|
|
3
|
+
|
|
4
|
+
They live apart from :mod:`paces.segmenters` (the registry and the
|
|
5
|
+
``segment()`` facade) so a module that only consumes or reshapes a result —
|
|
6
|
+
:mod:`paces.corrections`, :mod:`paces.projection`, :mod:`paces.ask`, the
|
|
7
|
+
evidence layer — depends on the types and not on the facade, and the facade
|
|
8
|
+
can import those modules without a cycle. :mod:`paces.segmenters` re-exports
|
|
9
|
+
every name here, so ``from paces.segmenters import Segmentation`` keeps
|
|
10
|
+
working.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Mapping
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from paces.model import MetricGrid
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"DFLT_CHAPTERS_CONFIDENCE",
|
|
23
|
+
"DFLT_EXPLICIT_CONFIDENCE",
|
|
24
|
+
"DFLT_GRID_CONFIDENCE",
|
|
25
|
+
"SegStep",
|
|
26
|
+
"Segmentation",
|
|
27
|
+
"Span",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
Span = tuple[float, float] # seconds, half-open
|
|
31
|
+
|
|
32
|
+
# Confidence constants: placement from user-supplied inputs is arithmetic, so
|
|
33
|
+
# what these grade is trust in the inputs, not in any inference.
|
|
34
|
+
DFLT_EXPLICIT_CONFIDENCE = 0.95 # a human said so
|
|
35
|
+
DFLT_GRID_CONFIDENCE = 0.9 # exact given the grid; the grid itself may drift
|
|
36
|
+
DFLT_CHAPTERS_CONFIDENCE = 0.9 # the author said so, in metadata
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
40
|
+
class SegStep:
|
|
41
|
+
"""One named piece of the media, as an analysis result.
|
|
42
|
+
|
|
43
|
+
``name=''`` means "found, not named" — a VALID state, not an error.
|
|
44
|
+
``spans`` is a tuple because a step legitimately appears in several places
|
|
45
|
+
(run-through AND breakdown).
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
id: str
|
|
49
|
+
name: str = ""
|
|
50
|
+
spans: tuple[Span, ...] = ()
|
|
51
|
+
confidence: float = 0.0
|
|
52
|
+
children: tuple["SegStep", ...] = ()
|
|
53
|
+
source: str = "" # which capability produced it
|
|
54
|
+
evidence: Mapping[str, Any] = field(default_factory=dict)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
58
|
+
class Segmentation:
|
|
59
|
+
"""What every segmenter returns. Reviewable, re-runnable, arguable.
|
|
60
|
+
|
|
61
|
+
``boundaries`` is separate from ``steps`` and populated even when naming
|
|
62
|
+
failed: nine excellent unnamed cuts are a useful, honest result. ``steps``
|
|
63
|
+
may be empty while ``boundaries`` is not.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
steps: tuple[SegStep, ...] = () # ordered; MAY be empty while boundaries is not
|
|
67
|
+
boundaries: tuple[float, ...] = () # the raw cut set, always
|
|
68
|
+
unit: str = "seconds" # "eight" | "rep" | "seconds"
|
|
69
|
+
grid: MetricGrid | None = None
|
|
70
|
+
confidence: float = 0.0
|
|
71
|
+
method: str = "" # which capability ran (the ADR's "reported default")
|
|
72
|
+
flags: tuple[str, ...] = () # 'naming-abstained', 'no-signal', 'try: ...'
|
|
73
|
+
elapsed_s: float = 0.0
|
|
74
|
+
spent_usd: float = 0.0
|
|
@@ -44,55 +44,18 @@ from dataclasses import dataclass, field
|
|
|
44
44
|
from pathlib import Path
|
|
45
45
|
from typing import Any, Callable
|
|
46
46
|
|
|
47
|
+
from paces import corrections as corrections_module
|
|
47
48
|
from paces.model import MetricGrid, seconds_per_unit
|
|
49
|
+
from paces.segmentation import ( # noqa: F401 — re-exported; their home is there
|
|
50
|
+
DFLT_CHAPTERS_CONFIDENCE,
|
|
51
|
+
DFLT_EXPLICIT_CONFIDENCE,
|
|
52
|
+
DFLT_GRID_CONFIDENCE,
|
|
53
|
+
SegStep,
|
|
54
|
+
Segmentation,
|
|
55
|
+
Span,
|
|
56
|
+
)
|
|
48
57
|
from paces.timed_text import normalize_timed_text
|
|
49
58
|
|
|
50
|
-
Span = tuple[float, float] # seconds, half-open
|
|
51
|
-
|
|
52
|
-
# Confidence constants: placement from user-supplied inputs is arithmetic, so
|
|
53
|
-
# what these grade is trust in the inputs, not in any inference.
|
|
54
|
-
DFLT_EXPLICIT_CONFIDENCE = 0.95 # a human said so
|
|
55
|
-
DFLT_GRID_CONFIDENCE = 0.9 # exact given the grid; the grid itself may drift
|
|
56
|
-
DFLT_CHAPTERS_CONFIDENCE = 0.9 # the author said so, in metadata
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
60
|
-
class SegStep:
|
|
61
|
-
"""One named piece of the media, as an analysis result.
|
|
62
|
-
|
|
63
|
-
``name=''`` means "found, not named" — a VALID state, not an error.
|
|
64
|
-
``spans`` is a tuple because a step legitimately appears in several places
|
|
65
|
-
(run-through AND breakdown).
|
|
66
|
-
"""
|
|
67
|
-
|
|
68
|
-
id: str
|
|
69
|
-
name: str = ""
|
|
70
|
-
spans: tuple[Span, ...] = ()
|
|
71
|
-
confidence: float = 0.0
|
|
72
|
-
children: tuple["SegStep", ...] = ()
|
|
73
|
-
source: str = "" # which capability produced it
|
|
74
|
-
evidence: Mapping[str, Any] = field(default_factory=dict)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
78
|
-
class Segmentation:
|
|
79
|
-
"""What every segmenter returns. Reviewable, re-runnable, arguable.
|
|
80
|
-
|
|
81
|
-
``boundaries`` is separate from ``steps`` and populated even when naming
|
|
82
|
-
failed: nine excellent unnamed cuts are a useful, honest result. ``steps``
|
|
83
|
-
may be empty while ``boundaries`` is not.
|
|
84
|
-
"""
|
|
85
|
-
|
|
86
|
-
steps: tuple[SegStep, ...] = () # ordered; MAY be empty while boundaries is not
|
|
87
|
-
boundaries: tuple[float, ...] = () # the raw cut set, always
|
|
88
|
-
unit: str = "seconds" # "eight" | "rep" | "seconds"
|
|
89
|
-
grid: MetricGrid | None = None
|
|
90
|
-
confidence: float = 0.0
|
|
91
|
-
method: str = "" # which capability ran (the ADR's "reported default")
|
|
92
|
-
flags: tuple[str, ...] = () # 'naming-abstained', 'no-signal', 'try: ...'
|
|
93
|
-
elapsed_s: float = 0.0
|
|
94
|
-
spent_usd: float = 0.0
|
|
95
|
-
|
|
96
59
|
|
|
97
60
|
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
98
61
|
class Capability:
|
|
@@ -354,13 +317,6 @@ def _facts(
|
|
|
354
317
|
return frozenset(facts)
|
|
355
318
|
|
|
356
319
|
|
|
357
|
-
def _corrections_api():
|
|
358
|
-
""":mod:`paces.corrections`, imported lazily: it imports this module."""
|
|
359
|
-
from paces import corrections
|
|
360
|
-
|
|
361
|
-
return corrections
|
|
362
|
-
|
|
363
|
-
|
|
364
320
|
def _apply_corrections(
|
|
365
321
|
result: Segmentation, log: tuple, capability: Capability | None
|
|
366
322
|
) -> Segmentation:
|
|
@@ -370,7 +326,7 @@ def _apply_corrections(
|
|
|
370
326
|
unmatched — rather than dropped."""
|
|
371
327
|
if not log or (capability is not None and "corrections" in capability.reads):
|
|
372
328
|
return result
|
|
373
|
-
return
|
|
329
|
+
return corrections_module.replay(result, log)
|
|
374
330
|
|
|
375
331
|
|
|
376
332
|
# ── the facade ──────────────────────────────────────────────────────────────
|
|
@@ -418,7 +374,7 @@ def segment(
|
|
|
418
374
|
transcript_cues = normalize_timed_text(transcript)
|
|
419
375
|
# fail fast: a malformed log raises before any segmenter runs
|
|
420
376
|
correction_log = (
|
|
421
|
-
|
|
377
|
+
corrections_module.normalize_corrections(corrections)
|
|
422
378
|
if corrections is not None
|
|
423
379
|
else ()
|
|
424
380
|
)
|
|
@@ -543,42 +499,77 @@ def segment(
|
|
|
543
499
|
# ── the two v1 segmenters ───────────────────────────────────────────────────
|
|
544
500
|
|
|
545
501
|
|
|
546
|
-
def
|
|
502
|
+
def place_on_grid(steps: Sequence, grid: MetricGrid | Mapping) -> Segmentation:
|
|
547
503
|
"""Place an ordered step list on the timeline using a metric grid.
|
|
548
504
|
|
|
549
505
|
The que-calor shape: cumulative domain durations from the grid's origin,
|
|
550
506
|
``span = origin + cumulative_units * seconds_per_unit``. Children subdivide
|
|
551
|
-
their parent's span the same way.
|
|
507
|
+
their parent's span the same way. ``steps`` takes any form
|
|
508
|
+
:func:`segment` takes (``(name, duration)`` pairs, mappings with
|
|
509
|
+
``children``); every step needs a positive duration, and the grid a tempo
|
|
510
|
+
and an origin — anything missing or invalid is a ``ValueError`` naming it.
|
|
511
|
+
|
|
512
|
+
>>> seg = place_on_grid([("a", 1), ("b", 2)],
|
|
513
|
+
... {"unit": "eight", "subdivisions": 8,
|
|
514
|
+
... "tempoBpm": "120", "origin": "1"})
|
|
515
|
+
>>> [s.spans for s in seg.steps], seg.boundaries
|
|
516
|
+
([((1.0, 5.0),), ((5.0, 13.0),)], (1.0, 5.0, 13.0))
|
|
552
517
|
"""
|
|
553
|
-
|
|
518
|
+
|
|
519
|
+
def _all_rows(rows: list[dict]):
|
|
520
|
+
for row in rows:
|
|
521
|
+
yield row
|
|
522
|
+
yield from _all_rows(row["children"])
|
|
523
|
+
|
|
524
|
+
grid = _normalize_grid(grid)
|
|
525
|
+
rows = _normalize_steps(steps)
|
|
526
|
+
if grid is None:
|
|
527
|
+
raise ValueError("place_on_grid needs a grid; got None")
|
|
528
|
+
missing = [
|
|
529
|
+
name
|
|
530
|
+
for name, value in (("tempoBpm", grid.tempo_bpm), ("origin", grid.origin))
|
|
531
|
+
if value is None
|
|
532
|
+
]
|
|
533
|
+
if missing:
|
|
534
|
+
raise ValueError(f"place_on_grid needs a grid with {' and '.join(missing)}")
|
|
535
|
+
if not rows:
|
|
536
|
+
raise ValueError("place_on_grid needs at least one step")
|
|
537
|
+
undated = [row["id"] for row in _all_rows(rows) if row["duration"] is None]
|
|
538
|
+
if undated:
|
|
539
|
+
raise ValueError(f"place_on_grid needs a duration for every step: {undated}")
|
|
540
|
+
bad = [
|
|
541
|
+
row["id"]
|
|
542
|
+
for row in _all_rows(rows)
|
|
543
|
+
if not (math.isfinite(row["duration"]) and row["duration"] > 0)
|
|
544
|
+
]
|
|
545
|
+
if bad:
|
|
546
|
+
raise ValueError(f"place_on_grid needs positive, finite durations: {bad}")
|
|
554
547
|
spu = seconds_per_unit(grid)
|
|
555
|
-
origin = float(grid.origin)
|
|
548
|
+
origin = float(grid.origin)
|
|
556
549
|
|
|
557
|
-
def _place(rows: list[dict], at_units: float) ->
|
|
550
|
+
def _place(rows: list[dict], at_units: float) -> list[SegStep]:
|
|
558
551
|
placed = []
|
|
559
552
|
for row in rows:
|
|
560
553
|
duration = row["duration"]
|
|
561
554
|
start = origin + at_units * spu
|
|
562
555
|
end = origin + (at_units + duration) * spu
|
|
563
|
-
children, _ = _place(row["children"], at_units)
|
|
564
556
|
placed.append(
|
|
565
557
|
SegStep(
|
|
566
558
|
id=row["id"],
|
|
567
559
|
name=row["name"],
|
|
568
560
|
spans=((start, end),),
|
|
569
561
|
confidence=DFLT_GRID_CONFIDENCE,
|
|
570
|
-
children=tuple(children),
|
|
562
|
+
children=tuple(_place(row["children"], at_units)),
|
|
571
563
|
source="grid-placed",
|
|
572
564
|
evidence={"duration_units": duration, "offset_units": at_units},
|
|
573
565
|
)
|
|
574
566
|
)
|
|
575
567
|
at_units += duration
|
|
576
|
-
return placed
|
|
568
|
+
return placed
|
|
577
569
|
|
|
578
|
-
|
|
579
|
-
boundaries = tuple(origin + units * spu for units in _cumulative(inputs["steps"]))
|
|
570
|
+
boundaries = tuple(origin + units * spu for units in _cumulative(rows))
|
|
580
571
|
return Segmentation(
|
|
581
|
-
steps=tuple(
|
|
572
|
+
steps=tuple(_place(rows, 0.0)),
|
|
582
573
|
boundaries=boundaries,
|
|
583
574
|
unit=grid.unit,
|
|
584
575
|
grid=grid,
|
|
@@ -586,6 +577,11 @@ def _segment_grid_placed(inputs: Mapping[str, Any]) -> Segmentation:
|
|
|
586
577
|
)
|
|
587
578
|
|
|
588
579
|
|
|
580
|
+
def _segment_grid_placed(inputs: Mapping[str, Any]) -> Segmentation:
|
|
581
|
+
"""The ``grid-placed`` capability: :func:`place_on_grid` over the facade's inputs."""
|
|
582
|
+
return place_on_grid(inputs["steps"], inputs["grid"])
|
|
583
|
+
|
|
584
|
+
|
|
589
585
|
def _cumulative(rows: list[dict]) -> list[float]:
|
|
590
586
|
offsets, at = [0.0], 0.0
|
|
591
587
|
for row in rows:
|
|
@@ -170,3 +170,32 @@ def test_decimal_str_refuses_non_finite_values(value):
|
|
|
170
170
|
|
|
171
171
|
with pytest.raises(ValueError, match="non-finite"):
|
|
172
172
|
decimal_str(value)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_place_on_grid_is_the_public_placement():
|
|
176
|
+
from paces.segmenters import place_on_grid
|
|
177
|
+
|
|
178
|
+
seg = place_on_grid(ROUTINE_STEPS, GRID)
|
|
179
|
+
assert [s.spans for s in seg.steps] == [
|
|
180
|
+
s.spans for s in segment(steps=ROUTINE_STEPS, grid=GRID).steps
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@pytest.mark.parametrize(
|
|
185
|
+
"steps, grid, match",
|
|
186
|
+
[
|
|
187
|
+
(ROUTINE_STEPS, {"unit": "eight", "subdivisions": 8}, "tempoBpm and origin"),
|
|
188
|
+
(ROUTINE_STEPS, {**GRID, "origin": None}, "with origin$"),
|
|
189
|
+
([], GRID, "at least one step"),
|
|
190
|
+
([("back", -1)], GRID, r"positive, finite durations: \['back'\]"),
|
|
191
|
+
([("nan", float("nan"))], GRID, "positive, finite"),
|
|
192
|
+
([("zero", 0)], GRID, "positive, finite"),
|
|
193
|
+
(["no duration"], GRID, "duration for every step"),
|
|
194
|
+
([{"name": "p", "duration": 2, "children": ["c"]}], GRID, r"\['c'\]"),
|
|
195
|
+
],
|
|
196
|
+
)
|
|
197
|
+
def test_place_on_grid_names_what_is_missing(steps, grid, match):
|
|
198
|
+
from paces.segmenters import place_on_grid
|
|
199
|
+
|
|
200
|
+
with pytest.raises(ValueError, match=match):
|
|
201
|
+
place_on_grid(steps, grid)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|