Releases: DMGiulioRomano/PythonGranularEngine
v4.0.0 — "Unit-Driven Pitch"
Aggiunto
-
Sistema pitch unit-driven (
PitchUnit): il bloccopitch(base e
voices.pitch) accetta sei unità di misura —semitones,cents,
quarter_tone,eighth_tone,edo(EDO arbitrario:edo: N+value: X
su base,unit: {edo: N}nelle voci) eratio— con un'unica interfaccia di
conversione a ratio. Famiglia EDO:
2^(valore / N);ratioè moltiplicatore diretto. Default invariato
(semitones, valore neutro → ratio 1.0).EdoUnit/RatioUnite factory
make_pitch_unitinsrc/parameters/pitch_unit.py; strategy unica
UnitPitchStrategy. PR #84. -
Validazione strict del blocco
pitch: una chiave sconosciuta — incluso un
refuso sull'unità (es.semitone:invece disemitones:) — solleva
InvalidFieldValueErrorche elenca le chiavi valide, invece di essere
ignorata silenziosamente con default a semitoni neutri (No Silent Failures).
Chiavi valide: le 6 unità piùrangeevalue(valuesolo conedo: N).
Inoltre un bloccopitchpresente ma vuoto (pitch:→None) o
non-mapping (lista/scalare, es.pitch: [[0, -1200], [1, 1200]]) solleva
InvalidFieldValueErrorcon hint, invece del precedenteTypeErrorgrezzo da
PitchController._select_unit: per nessuna trasposizione si omette del tutto
il blocco.pitch: {}e blocco assente restano default semitoni neutro
(indistinguibili a valle: Stream passa{}in entrambi i casi). Migrati i
config in-repoPGE_pino2.yml(rimosso ilpitch:vuoto) e
PGE_envelope_syntax_test.yml(envelope di pitch esplicitato comecents,
che è l'unità reale dei valori ±1200). PR #84. -
Flag
normalizednel bloccovoices.pointer(YAML): opt-in per interpretare
l'offset di pointer di voce come frazione disample_dur_secanziché in
secondi. Default invariato (normalized: false→ secondi), nessun breaking
change sugli YAML esistenti. Vale per le strategielinearestochastic;
lo scaling avviene inStream._create_grain, le strategy restano pure.
Il flag accetta solotrue/false: un valore non-bool solleva
InvalidFieldValueError(nessuna coercion silenziosa, coerente con
grain.reverse). Risolve l'ambiguità di unità documentata in issue #80. -
Flag
--format aiff|wav|flacinsrc/main.pye variabileFORMATnel
Makefile: seleziona il formato audio di output (defaultaiff). Il formato
viene propagato aNamingStrategy(estensione file),NumpyAudioRenderer
(parametrisf.write),StreamCacheManager(fingerprint cache e
garbage collection). Csound non richiede modifiche: rileva il formato
dall'estensione del flag-o. AggiuntoAudioFormatdataclass in
src/rendering/audio_format.py. Risolve issue #75. -
Target
make clean-rpp(make/clean.mk): rimuove i file.rppe.rpp-bak
in$(SFDIR)(defaultoutput/) e nella root del repo. Risolve la
pulizia esplicita dei progetti Reaper, prima orfana di target dedicato.
Issue #65. -
Flag
CLEAN_RPPnelMakefile(defaultfalse): controlla semake clean
rimuove anche i.rppinoutput/. Defaultfalseper preservare
eventuale lavoro REAPER manuale (FX chain, automation, mixer routing) che
non è rigenerabile da YAML.CLEAN_RPP=trueripristina il comportamento
pre-issue#65 (wipe totale$(SFDIR)/*). Issue #65. -
Flag
REAPER_REUSE_TABnelMakefile(defaultfalse): setruecon
REAPER=true, prima di aprire il.rppaggiornato lo script Lua
generated/open_reaper_tab.luascorre le tab REAPER aperte (EnumProjects)
e chiude solo quella con path assoluto matching (action40860"Close
current project tab"), poi apre nuova tab (action40859). Le altre tab
restano intatte. Alternativa meno distruttiva adAUTOKILL_REAPERper
rebuild ripetuti dello stesso YAML. Risolve issue #59. -
Refactor
make/build.mk: estratta macroemit_open_reaper_luacondivisa
daautopen_stemseautopen_singleper centralizzare la generazione
dello ReaScript Lua (branch condizionale suREAPER_REUSE_TAB). -
Supporto Fedora / RHEL / Rocky / AlmaLinux nel branch
dnfdi
make install-system-deps(issue #58). Installapython3+sox;
stampa istruzioni per Csound (non disponibile nei repo Fedora / RPM
Fusion — usareRENDERER=numpyo compilare dai sorgenti). -
README: sezione dedicata "Fedora / RHEL / Rocky / AlmaLinux" con
istruzioni install e nota Csound; righe Fedora/RHEL nella tabella
compatibilità Python; voce Fedora/RHEL nella tabella "Platform Support". -
Flag
AUTOKILL_REAPERnelMakefile(defaultfalse): setruecon
REAPER=true, chiude REAPER prima del build viaSIGKILL
(pkill -9 -x REAPERmacOS /pkill -9 -x reaperLinux), poi il.rpp
viene riscritto e REAPER riaperto. Kill immediato senza dialog di
salvataggio (modifiche manuali non salvate vengono perse — scelta
intenzionale per garantire automazione non bloccante). Risolve issue #17 —
REAPER non ricarica da disco le modifiche aonset/durationse il
progetto e' gia' aperto. -
Target
make reaper-stop: chiude REAPER se attivo (specchio dirx-stop). -
Multi-tab REAPER per YAML: se REAPER e' gia' in esecuzione, l'apertura del
.rpppost-build avviene via ReaScript Lua generato al volo in
generated/open_reaper_tab.lua(action40859"New project tab" +
Main_openProject), invocato conREAPER -nonewinst <script.lua>.
Build dello stesso YAML produce nuova tab con dati aggiornati; build di
YAML diverso produce tab indipendente. Comportamento deterministico, non
dipende da preferenze utente REAPER. Richiede REAPER >= 6.80. -
docs/reaper-workflow.md: workflow REAPER, requisiti, troubleshooting. -
tests/e2e/test_reaper_makefile_e2e.py: 6 scenari su targetreaper-stop,
wiringAUTOKILL_REAPER, defaultREAPER_PATH.
Modificato
- Pitch delle voci unit-agnostico: la geometria della distribuzione vive
ora nellaPitchUnitvia il nuovo metodomaterialize(position, amount)
(EDO additiva2^(position·amount/N),ratiogeometricaamount^position).
Le voice pitch strategy emettono un fattore di ratio (get_pitch_factor,
primaget_pitch_offsetin semitoni);VoiceConfig.pitch_offset→
pitch_factor(default1.0= identità) eStream._create_grainmoltiplica
direttamente, senza il guard!= 0.0. Conseguenze suvoices.pitchcon
unit: ratio:rangeestochasticdiventano validi (distribuzione
geometrica, nessun ratio negativo o sub-zero);steppassa dai·step
(lineare) astep^i(geometrico) — breaking sui valori delle voci ≥2 con
unit: ratio. I path EDO (semitones/cents/quarter_tone/eighth_tone/edo)
restano numericamente identici.chord/spectralrestano semitone-locked. - Default
REAPER_PATH: da$(FILE).rpp(root del repo) a$(SFDIR)/$(FILE).rpp
(defaultoutput/$(FILE).rpp). I progetti Reaper vivono ora accanto agli
.aifgenerati, co-location semantica tra progetto Reaper e audio referenziati.
Breaking change minore: script che cercanofoo.rppnella root vanno
aggiornati aoutput/foo.rpp.REAPER_PATH=custom/path.rppresta supportato
per override esplicito. Issue #65. make cleannon rimuove più$(SFDIR)/*conrm -rfper default. Usafind
con esclusione di*.rppper preservare progetti Reaper. Override via
CLEAN_RPP=true. Issue #65.
Modificato (breaking)
- Chiave YAML
voices.pitch.semitone_rangerinominata inpitch_range(strategie
rangeestochastic). Il valore è interpretato nell'unità attiva
(semitones/cents/edo/ratio), non in semitoni: il vecchio nome mentiva.
pitch_rangeè domain-based, coerente con i siblingpointer_range/max_offset.
Hard break: la vecchia chiavesemitone_rangesolleva
InvalidStrategyConfigErrorcon hint di migrazione (guard in
Stream._init_voice_manager). Migrati i config in-repo. - Default
REAPER_PATH: eraProject.rppfisso, ora$(FILE).rpp. Ogni YAML
produce un.rppcon lo stesso basename, abilitando il multi-tab. Override
esplicito viaREAPER_PATH=...sempre supportato. Aggiornato help
make helpdi conseguenza.
Corretto
-
Stream._create_grain(src/core/stream.py): l'offset di voce sul pointer
veniva sommato dopo il wrap base, lasciandograin.pointer_posoltre
sample_durper le voci con offset positivo. Ora la somma è re-wrappata
in[0, sample_dur)con% self.sample_dur_sec. L'audio era già corretto
(GrainRenderere Csound ri-wrappano la traiettoria di lettura), ma la
partitura (ScoreVisualizer) clippava le voci sopra il bordo del buffer,
facendole "ricomparire" tutte insieme al wrap della voce 0 invece che
sfasate. Oragrain.pointer_posè la posizione reale di lettura, condivisa
da audio e partitura. Risolve issue #79. -
Docstring delle voice strategy (
voice_pointer_strategy.py,
voice_onset_strategy.py,voice_pitch_strategy.py,voice_pan_strategy.py):
rimosso il claim falso «seed deterministico / riproducibile tra sessioni».
hash()su stringa è randomizzato per-processo (PYTHONHASHSEEDnon fissato),
quindi l'offset per voce è stabile solo entro un run, non fra processi. Le
docstring ora descrivono accuratamente il comportamento. Corretta anche la
frase del README sui due renderer: stesso comportamento musicale, non output
bit-identico (sequenzerandomindipendenti per i grani stocastici). Solo
documentazione, nessuna modifica al comportamento. Risolve issue #76. -
Macro
autopen_stemsinmake/build.mk: il glob*.aifhardcoded è stato
sostituito con*$(FORMAT_EXT), così conFORMAT=wavoFORMAT=flacil
comandoAUTOPEN=trueapre i file con l'estensione corretta invece di non
trovare nulla. Nessuna regressione:FORMAT_EXTdefaults a.aif. Risolve
issue #77. -
Naming dei file stem
.aifin STEMS mode: separatore tra basename del
progetto estream_idcambiato da_a__(issue #56), per
allinearsi al protocollo del se...
v3.9.0 — "Reaper Polish"
Highlights
Focus su workflow REAPER e installazione multi-distro.
Feat
.rppinoutput/(#65, #66): defaultREAPER_PATH = $(SFDIR)/$(FILE).rpp, co-location con.aif. Nuovo targetmake clean-rpp+ flagCLEAN_RPP(defaultfalse) per preservare lavoro REAPER manuale damake clean.REAPER_REUSE_TAB(#59, #62): reload single-tab via ReaScript Lua + action40860/40859. Alternativa non distruttiva adAUTOKILL_REAPER.- Supporto Fedora/RHEL (#58, #60): branch
dnfinmake install-system-deps.
Fix
- REAPER autokill multi-tab (#17, #61): SIGKILL + ReaScript per ricaricare
.rppquando REAPER aperto. - Stem naming (#56, #57): separator
_→__per evitare collisioni.
Docs
- INDEX Obsidian + wikilinks tra documenti (#63).
Breaking change minore
REAPER_PATH default: $(FILE).rpp (root) → $(SFDIR)/$(FILE).rpp. Script che cercano foo.rpp in root vanno aggiornati a output/foo.rpp. Override esplicito REAPER_PATH=... invariato.
Full changelog
v3.8.0 — Arch/Manjaro compat + Cartridge removal
Highlights
Arch / Manjaro compat (issue #51)
make setup ora funziona su Arch/Manjaro senza pyenv. Il Makefile rileva automaticamente python3.12..python3.16 versionati oppure python3 generico (se >= 3.12).
sudo pacman -Sy python sox csound
make setup # ora completa anche con Python 3.14 di sistemaCartridge removal (#40, breaking ma silent)
Rimossa la classe Cartridge (tape recorder head) e tutte le dipendenze. Feature non utilizzata da nessun YAML in configs/. La chiave cartridges: in YAML viene ignorata silenziosamente — zero impatto sui brani esistenti.
Changes
- fix(make): detection Python multi-versione (numpy/scipy/soundfile cp314 verificate)
- refactor: rimozione Cartridge + test correlati
- docs: tabella compatibilità OS, brief UI editor
- tests: nuovo
test_makefile_python_detection.py(5 scenari)
Test
- 4081 unit pass
- 51 e2e pass (csound)
Full changelog: CHANGELOG.md
v3.7.0 - EngineError extension: controllers + envelopes
Issue #46 chiusa (follow-up #38)
Convertiti gli ultimi 11 raise user-facing residui nei moduli controllers/ e envelopes/ alle sotto-classi EngineError esistenti, completando l'unificazione della Categoria A (config errors).
PR incluse
- #47 - controllers (window strategies, registry, pitch/density)
- #48 - envelopes (segment, time_distribution)
Modificato
Controllers:
_validate_curve_range->InvalidStrategyConfigError(strategy_kind="window")MultiStateWindowStrategy.__init__->InvalidStrategyConfigError(strategy_kind="window_multistate")WindowStrategyFactory.create->StrategyNotFoundError(eraKeyError)WindowRegistry.generate_ftable_statement->InvalidWindowErrorpitch_controller/density_controllerviolazione gruppo esclusivo ->InvalidFieldValueError
Envelopes:
Segment.__init__empty breakpoints ->InvalidFieldValueError_validate_inputs(n_reps, total_time) +ExponentialDistribution(rate) ->ParameterBoundError
Compatibilita'
Tutte le nuove sotto-classi ereditano ValueError -> pytest.raises(ValueError) e except ValueError pre-esistenti continuano a funzionare. Unica eccezione: WindowStrategyFactory.create cambia base da KeyError a StrategyNotFoundError(ValueError) (verificato no caller).
Test
- 4172 unit tests passing
- 51 e2e tests passing
Per dettagli vedi CHANGELOG.
v3.6.0 — EngineError hierarchy & user-facing errors
Issue #38 chiusa: estensione completa della gerarchia EngineError con messaggi user-facing strutturati per tutti gli errori di configurazione e rendering.
Highlights
- Gerarchia EngineError completa (
ConfigError+EngineRuntimeErrorsub-tree) user_message()contract: head[ERRORE] ...+ context indentato + Stream/Config + path engine log- Pattern context enrichment layered:
stream_idarricchito al chiamante (parser/strategy/controller),config_fileinGenerator.create_elements, handler unico polimorfico inmain.py - Backward-compat preservata:
ConfigError → ValueError,CsoundRenderError → RuntimeError - Documentazione: nuovo
docs/error-handling.md
Sotto-classi aggiunte (issue #38)
| PR | Classi |
|---|---|
| PR1 (#39) | ConfigError, MissingFieldError, InvalidFieldValueError |
| PR2 (#41) | InvalidParameterError, ParameterBoundError |
| PR3 (#42) | StrategyNotFoundError, InvalidStrategyConfigError |
| PR4 (#43) | EngineRuntimeError, InvalidRendererError, InvalidWindowError, FtableError, CsoundRenderError |
| PR5 (#44) | docs/error-handling.md |
Test
- 4161 unit tests passing
- 49 e2e tests passing (tutti gli errori coperti via subprocess su YAML inline)
Riferimenti
- Issue: #38
- Doc:
docs/error-handling.md
v3.5.0 — Strategy passThrough
Strategy passThrough
GrainClipStrategy come unica fonte di verità su quali grain esistono; numpy_audio_renderer passthrough puro senza opinioni proprie sui bounds.
Highlights
- Nuovi campi YAML:
clip_strategy(overflow_margindefault |passthrough),clip_margin(float, default0.0) - Strategy registrate:
OverflowMarginClipStrategy,PassthroughClipStrategy, registry + factory - Filtro grain in post-process dentro
Stream.generate_grains. Csound e NumPy ricevono ora la stessastream.voices→ parità garantita strutturalmente numpy_audio_renderer: buffer dimensionato sull'extent reale dei grain; rimossi clampend_sample > n_totaleonset_sample >= n_total; preservato clamponset < 0- Doc YAML aggiornata in
docs/yaml-reference.md
Bugfix
- Risolve #27 (divergenza renderer su grain con onset >
stream.duration) make: rileva package manager Linux a runtime (apt vs pacman) (#32)
Nota di compatibilità
Comportamento default più restrittivo per la coda: grain con grain.onset + grain.duration > stream_end vengono esclusi. Per ripristinare l'inclusione integrale dei grain (vecchio comportamento Csound), aggiungi al blocco stream:
clip_strategy: passthroughIn modalità passthrough il file .aif può superare stream.duration se i grain sforano.
Test
- 4076 unit test passing
- 39 e2e test passing
PR
v3.4.0 — "Temporal Voice"
Nuove funzionalità
Dynamic strategy parameters — ogni parametro delle voice strategy accetta ora float o Envelope. Il valore viene valutato al tempo reale di ogni grain, consentendo evoluzione temporale su tutte le dimensioni del sistema multi-voice.
Cosa cambia
resolve_param(param, time)— primitiva condivisa inparameters/parameter.py; risolveUnion[float, Envelope]afloat- Tutte le strategy ABC ricevono
time: float; implementazioni stochastiche separano direzione (cache fissa, seeded) da magnitudine (time-varying) VoiceManagerstateless:get_voice_config(voice_index, time)calcola on-the-fly per ogni grain- Parsing YAML:
_parse_strategy_kwargrileva list/dict → costruisceEnvelope; supportatime_mode: normalized generate_grainspassavoice_cursors[voice_index]— ogni voce valuta l'envelope al proprio tempo musicale reale
Parametri time-varying
| Strategy | Parametri |
|---|---|
step pitch |
step |
range pitch |
semitone_range |
stochastic pitch |
semitone_range |
linear onset |
step |
geometric onset |
step, base |
stochastic onset |
max_offset |
linear pointer |
step |
stochastic pointer |
pointer_range |
| tutte le pan | spread (via VoiceManager) |
Backward compatibility
Tutti i config YAML scalari esistenti rimangono validi senza modifiche.
File allegati
PGE_dynamic_strategy_params_test.yml — config di test empirico con 19 stream da 10s (~3.75 min). Copre ogni dimensione time-varying in isolamento e in combinazione. Da usare per verifica ascolto della feature.
v3.3.0 — Jazz Chords & Chord Inversions
Novità
Jazz Chords (5–7 voci)
11 nuovi accordi aggiunti a CHORD_INTERVALS:
- 5 voci:
dom9,maj9,min9,9sus4 - 6 voci:
dom9s11,maj9s11,min11 - 7 voci:
dom13,min13,maj13s11,altered
voices:
num_voices: 7
pitch:
strategy: chord
chord: dom13Inversioni accordo
ChordPitchStrategy ora accetta inversion: int = 0. Ruota gli intervalli in modo che il grado k diventi la voce più bassa, normalizzata a 0.
voices:
num_voices: 4
pitch:
strategy: chord
chord: dom7
inversion: 1 # [0,3,6,8] invece di [0,4,7,10]WindowStrategyFactory (refactoring interno)
select_window() è ora pura delegazione. Nuove strategy si aggiungono via registry senza toccare WindowController.
Test
3974 test, tutti verdi.
v3.2.0 — Window Transitions
Novità
Transizioni probabilistiche tra finestre di grano
Due nuove modalità YAML per l'envelope del grano:
Transition — morphing da una finestra a un'altra guidato da una curva temporale:
grain:
envelope:
from: hanning
to: expodec
curve: [[0, 0], [30, 1]]Multi-state — transizione attraverso N finestre con separazione tra spazio del valore e spazio del tempo:
grain:
envelope:
states:
- [0.0, hanning]
- [0.3, bartlett]
- [0.7, expodec]
- [1.0, gaussian]
curve: [[0, 0], [60, 1]]La selezione per ogni grano è stocastica — il timbro dell'involucro evolve in modo probabilistico, non a step.
WindowStrategyFactory
Architettura OCP allineata al pattern voice_pitch_strategy: registry + **kwargs, estendibile senza toccare WindowController.
Finestra gaussian nel renderer NumPy
gaussian era già disponibile nel path Csound, ora è supportata anche dal renderer NumPy.
Breaking changes
envelope_rangerimosso dal YAML (era ridondante — la variazione è implicita dalla struttura lista/stringa)
Fix
- Errore leggibile quando
sampleè mancante o null in uno stream
v3.1.0
What's Changed
Features
PointerController: quandoloop_startè definito mastartnon è esplicito nello YAML, il pointer parte direttamente daloop_start(t=0)invece che da0. Il valorestartesplicito non viene mai sovrascritto.
Fixes
- Loop bounds relativi al file audio:
loop_dur,loop_start,loop_endnon hanno più un upper bound statico arbitrario nel registry.max_val=Noneindica assenza di limite statico — il bound reale è sempresample_dur_sec, passato dinamicamente. Eliminati i fallback1000.0/100.0che non rispecchiavano la realtà.
Test suite
3802 test, 0 falliti.