4bbfdcceff
audio-surface.el / image-surface.el: own-core additive-synthesis WAV and
raster-PNG renderers (integer-only DSP, since EL has no floats), rendered
from learned engram signatures via a pluggable surface-profile
abstraction (surface-profile.el). audio-demo.el / image-demo.el are
drivers. NOTE: demo files hardcode absolute paths to this worktree's own
directory — will need a path fixup before landing.
elp/projector/ is a Python package the author's own README marks as
"STAGING/PROOF-OF-SHAPE — not the deliverable", superseded by the native
.el surface-profile work above; kept as a validated architecture proof.
Generated output (elp/faculty/{out,sig}, elp/projector/out,
__pycache__) intentionally excluded.
134 lines
5.8 KiB
Python
134 lines
5.8 KiB
Python
"""midi.py — the MUSIC surface projector: geometry -> symbolic music (MIDI).
|
|
|
|
The first NON-TEXT surface, and the proof of the general shape. "Music is
|
|
language and it is math" (Will): symbolic music is tractable and geometry-native,
|
|
so it is the natural efferent twin to try first after text.
|
|
|
|
CRUCIALLY this projector does NOT read the realized sentences. It reads the IR's
|
|
GEOMETRY facet — ``block.provenance`` — and DECODES each edge onto a musical
|
|
surface. That is the whole thesis of the multimodal projector: the same
|
|
geometry-carrying IR drives text AND music; a text projector reads the words, a
|
|
music projector reads the meaning-geometry. The mapping is deterministic and
|
|
faithful to the geometry's structure:
|
|
|
|
relation lemma -> scale degree (same relation -> same pitch class;
|
|
meaning has a consistent sonic form)
|
|
polarity -> mode (aff = major third above; neg = minor
|
|
third / lowered — SACRED polarity is
|
|
audible, a negated edge sounds negated)
|
|
confidence -> note duration (stronger grounding rings longer)
|
|
importance -> velocity (more important source = louder)
|
|
section -> phrase + register shift (structure becomes musical form)
|
|
|
|
Own-the-core: a Standard MIDI File is a header chunk + a track chunk of
|
|
delta-timed events. We emit the raw bytes with ``struct`` — no external MIDI
|
|
library. Format 0, one track.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import os
|
|
import struct
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
from document_ir import DocumentIR, Provenance # noqa: E402
|
|
from projectors.base import TwoStageProjector, register # noqa: E402
|
|
|
|
_TICKS = 480 # ticks per quarter note
|
|
_C_MAJOR = [0, 2, 4, 5, 7, 9, 11] # semitone offsets of a diatonic scale
|
|
|
|
|
|
def _vlq(n: int) -> bytes:
|
|
"""MIDI variable-length quantity encoding of a delta time."""
|
|
if n == 0:
|
|
return b"\x00"
|
|
out = bytearray()
|
|
out.append(n & 0x7F)
|
|
n >>= 7
|
|
while n:
|
|
out.insert(0, (n & 0x7F) | 0x80)
|
|
n >>= 7
|
|
return bytes(out)
|
|
|
|
|
|
def _degree_for(relation: str) -> int:
|
|
"""Stable scale degree for a relation lemma (same relation -> same pitch)."""
|
|
if not relation:
|
|
return 0
|
|
return sum(ord(c) for c in relation.lower()) % len(_C_MAJOR)
|
|
|
|
|
|
def _note_for(p: Provenance, base: int) -> tuple[int, int, int]:
|
|
"""(pitch, velocity, duration_ticks) for one geometry edge."""
|
|
root = base + _C_MAJOR[_degree_for(p.relation)]
|
|
# polarity -> mode: affirmed edges take the bright major third, negated edges
|
|
# take the darker minor third. The negation is AUDIBLE and never dropped.
|
|
third = 4 if p.polarity == "aff" else 3
|
|
pitch = max(24, min(96, root + (third if p.confidence >= 0.5 else 0)))
|
|
velocity = int(56 + 60 * min(1.0, max(0.0, p.importance)))
|
|
velocity = max(40, min(120, velocity))
|
|
# confidence -> duration: quarter .. dotted-half
|
|
dur = int(_TICKS * (0.5 + 1.5 * min(1.0, max(0.0, p.confidence))))
|
|
return pitch, velocity, dur
|
|
|
|
|
|
# a mode-profile: the pluggable musical knob (the peer's mode_profile). Scale +
|
|
# tempo. Swapping this profile re-voices the SAME geometry — surface as parameter.
|
|
_DEFAULT_PROFILE = {"scale": _C_MAJOR, "tempo_us": 500000,
|
|
"registers": [60, 55, 64, 50, 67, 48], "program": 0}
|
|
|
|
|
|
class MidiProjector(TwoStageProjector):
|
|
"""geometry -> symbolic music, in the shared two-stage shape.
|
|
|
|
``plan(frame)`` -> a music_spec: an ordered list of note dicts derived
|
|
deterministically from the frame's provenance geometry
|
|
(the peer's ``plan(frame, profile) -> spec``).
|
|
``realize(spec)`` -> Standard MIDI File bytes (the peer's
|
|
``realize(spec, profile) -> surface``; here the surface
|
|
is symbolic MIDI, the minimal audio proof — a richer
|
|
additive-synth audio projector conforms identically).
|
|
"""
|
|
|
|
surface = "midi"
|
|
media_type = "audio/midi"
|
|
ext = "mid"
|
|
modality = "audio"
|
|
|
|
def __init__(self, profile: dict | None = None):
|
|
self.profile = profile or _DEFAULT_PROFILE
|
|
|
|
# -- stage 1: meaning-geometry -> music_spec (reads the GEOMETRY facet) -- #
|
|
def plan(self, doc: DocumentIR) -> list[dict]:
|
|
registers = self.profile["registers"]
|
|
spec: list[dict] = []
|
|
for si, sec in enumerate(doc.sections):
|
|
base = registers[si % len(registers)]
|
|
provs = [p for p in sec.all_provenance()
|
|
if p.kind in ("fact", "interpretation")]
|
|
for i, p in enumerate(provs):
|
|
pitch, vel, dur = _note_for(p, base)
|
|
spec.append({"pitch": pitch, "velocity": vel, "dur": dur,
|
|
"rest_before": (_TICKS // 2) if (si > 0 and i == 0) else 0,
|
|
"relation": p.relation, "polarity": p.polarity})
|
|
return spec
|
|
|
|
# -- stage 2: music_spec -> MIDI bytes (own-core, no library) ------------ #
|
|
def realize(self, spec: list[dict]) -> bytes:
|
|
ev = bytearray()
|
|
ev += _vlq(0) + b"\xFF\x51\x03" + struct.pack(">I", self.profile["tempo_us"])[1:]
|
|
ev += _vlq(0) + bytes([0xC0, self.profile["program"] & 0x7F])
|
|
for note in spec:
|
|
ev += _vlq(note["rest_before"]) + bytes([0x90, note["pitch"], note["velocity"]])
|
|
ev += _vlq(note["dur"]) + bytes([0x80, note["pitch"], 0])
|
|
ev += _vlq(0) + b"\xFF\x2F\x00"
|
|
track = bytes(ev)
|
|
buf = io.BytesIO()
|
|
buf.write(b"MThd" + struct.pack(">IHHH", 6, 0, 1, _TICKS))
|
|
buf.write(b"MTrk" + struct.pack(">I", len(track)) + track)
|
|
return buf.getvalue()
|
|
|
|
|
|
register(MidiProjector())
|