genny
Offline sound and music synthesis in Python: physical models of instruments, objects, liquids, fire, voices
and rooms, rendered to WAV from JSON specs, a command line or Python. Built to be driven by AI agents
(see AGENTS.md), and deterministic: the same spec always gives the same file.
Version 1.0.1: 129 instruments, 48 drums, 234 sound effects, 96 effects, 13 generative and vocal layer types, plus a speech synthesiser (English, Spanish). Python only (numpy, scipy, numba).
On PyPI: pypi.org/project/genny. pip install genny gives the library and
the genny command.
python install.py --tool # installs the `genny` command (needs uv) and the Claude Code skill
uv tool install --editable . # or: just the command
genny sfx impact -p material=glass -p size=0.2 -o glass_tap.wav
genny sfx pour -p dur=4 -o pour.wav
genny seq guitar "E3:1 G3:1 B3:1 E4:2" --bpm 100 --fx room:preset=hall,mix=0.2 -o tune.wav
genny seq cristal "A4:2 C5:2 E5:4" --bpm 60 -o cristal.wav
genny say "level up!" -p voice=female -o level_up.wav
genny render examples/library/instrument_tunes.json # a short tune for each of the 129 instruments
genny render examples/library/style_pieces.json # a generated piece for each of the 35 styles
genny render examples/library/cyber.json # 47 machines, vehicles, interface cues and damaged voices (new in 1.0.1)
genny list # every name and parameter (--json for agents)
genny info tune.wav # loudness, sharpness, energy above 5 kHz
In a program, a sound is one call that returns a numpy array (pip install genny):
import genny
from genny.sfx import render_sfx
from genny.core import write_wav
tap = render_sfx("impact", 44100, material="glass", size=0.2) # float array, -1..1
ok = render_sfx("cyber_ui", 44100, kind="confirm", seed=3) # another seed, another cue
engine = render_sfx("pipe_engine", 44100, rpm=3200.0, cylinders=6, dur=2.0)
write_wav("engine.wav", engine, 44100)
examples/in_your_code.py shows the patterns a project needs: a cache keyed by the arguments, an event
table for an interface, variations by seed, an engine bank by rpm, sounds driven by program state, effects
on an array, a scene built from a dict, and WAV bytes in memory.
| Read | For |
|---|---|
AGENTS.md |
the spec format, note and step notation, tempo and loops, recipes |
docs/README.md |
the documentation index, the Python quick start and how to add a sound |
docs/catalog.md |
every instrument, drum, sound effect, effect and layer type with its parameters |
docs/physics.md |
which physical model is behind each sound, its source, and the known limits |
docs/api.md |
the Python API |
examples/in_your_code.py |
genny called from a program: caching, variations, parameter banks, scenes |
examples/library/*.json |
demo batches; renders go to sounds/ |
What is modelled: plucked, hammered, bowed and prepared strings (waveguide and finite difference); bars,
membranes, linear and nonlinear plates (cymbals, gongs); reeds, lips, jets and tubes; the singing voice and
choirs with lyrics; impacts, bouncing, rolling, scraping, breaking and crumpling of solids by material and
size; stick-slip friction and the Baschet sound sculptures; bubbles, pouring, steam, flames, sparks and ice;
animals, insects, birds, footsteps and crowds; engines, machines, weapons and signals; rooms, distance,
Doppler and occlusion; spectral processing, convolution, codecs; sfxr / ZzFX retro engines; generated music
in 35 styles. docs/physics.md has the full table.
Checks: uv run --with pytest pytest -q (the tests compare each model with its own physics: mode
frequencies, decay times, tuning, event rates); uv run python -m genny.klang --qa renders every catalog
entry and flags level, DC and clicks. Nothing was validated by listening tests: audition
sounds/instrument_tunes/, sounds/style_pieces/ and sounds/choir_audition/ and tune to taste.
Licensing: parts of genny/physical/, genny/klang.py and a few functions elsewhere are adapted from the
Klang procedural library, Copyright (c) 2025 Chris Nash, Klang Open License 1.0 (Apache 2.0 plus an
attribution requirement for interactive products). See THIRD_PARTY_NOTICES.md before shipping a game.
The sections below document the Python physical API as it grew, version by version.
Physical synthesis API (v0.3)
Genny now includes a physically informed engine alongside the original synth/drum/SFX API. Existing calls remain available; new code can compose reusable physical models:
import genny
# Extended Karplus-Strong + commuted body
string = genny.StringInstrument(body="guitar", position=0.18, brightness=0.6)
audio = string.pluck(220.0, 2.0, velocity=0.8)
# Nonlinear felt hammer + stiff, dispersive piano strings
piano = genny.Piano(felt=0.2, inharmonicity=4e-4)
audio = piano.note(261.63, 3.0, velocity=0.75)
# PhISEM / stochastic particles
shaker = genny.ParticleMaterial("Maraca")
audio = shaker.shake(1.5, energy=0.8)
# Minnaert bubble population
water = genny.BubblePopulation(rate=80)
audio = water.render(3.0)
# Articulatory voice and avian syrinx
voice = genny.VocalTract(tract_scale=1.1)
audio = voice.phonate(120.0, 1.0)
bird = genny.BirdSyrinx(pressure_pa=3000)
audio = bird.call((650, 657), 1.0)
# Environment and physical room/propagation
rain = genny.Environment(seed=7).rain(5.0, intensity=0.8)
room = genny.Room(volume=120, surfaces={"plaster": 80, "wood": 35, "glass": 15})
rain_in_room = room.reverberate(rain, mix=0.25)
Low-level research engines are exposed under genny.physical:
modal: modal banks, strike-position coupling, micro-collisions, fracture, aeroacoustic swooshes;particles: PhISEM shakers, granular footsteps, Minnaert bubble fields and drips;waveguides: plucked/bowed strings, mandolin, brass, clarinet, recorder and felt piano;voice: LF glottis, Pink Trombone tract, formant/FOF tools and syrinx;space: Zita/Puckette/Farnell reverbs, Sabine T60, distance and air absorption;analysis: LPC/lattice analysis-resynthesis, PSOLA/granular tools and modal fitting;ambience: wind, fire, water, rain, thunder, surf, creaks and object-detail textures;footsteps: physically/statistically informed surface interactions.
The physical kernels run internally at 48 kHz and the high-level API resamples to the requested Genny rate, preserving the original API default of 44.1 kHz.
Licensing note
Parts of genny/physical are adapted from the supplied Klang/procedural source. See
THIRD_PARTY_NOTICES.md and THIRD_PARTY_KLANG_LICENSE.txt. The Klang license contains an additional
attribution requirement for interactive audio-visual products.
Physical engine v0.4
Version 0.4 extends the physically informed API introduced in v0.3 with additional causal models derived from the supplied research corpus and the adapted Klang kernels:
import genny
# Geometry + material -> bending modes
bar = genny.BarBody(genny.MATERIALS["steel"], length_m=0.35, radius_m=0.006)
y = bar.strike(duration=1.5, velocity=1.2)
# Bidirectional bowed-string waveguide
violin = genny.BowedString(body="violin", bow_pressure=0.9)
y = violin.note(440.0, 2.0)
# Jet-driven flute
flute = genny.Flute(noise=0.18)
y = flute.note(523.25, 1.5)
# GRF + modal/PhISEM footstep
step = genny.Footstep("wood")
y = step.render(seed=12)
# Reynolds/Strouhal aeroacoustics for a swept object
y = genny.AeroacousticSwing("SWD1").render(seed=3)
# Moving propagation: Doppler emerges from time-varying delay
prop = genny.MovingPropagation()
distance = np.linspace(20.0, 2.0, len(y))
y_moving = prop.apply_trajectory(y[:, 0] if y.ndim == 2 else y, distance, sr=44100)
The legacy genny.instruments, genny.acoustic, genny.sfx, genny.vehicle, etc.
remain available; the physical API is additive so existing code does not need to migrate all at once.
v0.5 unified legacy names
Genny 0.5 begins routing familiar high-level names through the new physical engine. Existing calls such as render_note("piano", ...), render_note("violin", ...), render_note("clarinet", ...), and render_sfx("footstep", ...) keep their API but now use physical waveguide/modal/contact models underneath. See PHYSICAL_ENGINE_CHANGELOG_V05.md for the migrated list and compatibility notes.
0.6 physical catalogue migration
Genny 0.6 moves more of the historical public catalogue onto the common physical engines. New reusable facades include:
import genny
# Modal / struck bodies
bar = genny.TunedPercussion("marimba", hardness=0.7)
y = bar.note(440.0, 1.0)
# Physical/commuted plucked strings
mandolin = genny.PluckedString("mandolin", position=0.35)
y = mandolin.note(330.0, 1.0)
# Four-stroke engine; firing rate = cylinders * RPM / 120
engine = genny.CombustionEngine(cylinders=4, rpm=1800, load=0.7)
y = engine.render(2.0)
# Pouring combines stream texture, bubble resonances and a rising air-cavity mode
y = genny.Environment(seed=4).pour(2.0, flow=0.8, fill=0.1)
The legacy xylophone, glockenspiel, tubular_bell, mandolin, steel_guitar, banjo, shaker, tambourine, woodblock, gong, rain, fire, stream, pour, engine and swoosh catalogue names now route through these physical/statistical engines.
Genny 0.7 physical families
Version 0.7 adds shared membrane, plate, surface-contact and rotor/aeroacoustic engines. The important point is that these are reusable physical models rather than one generator per named sound.
import genny
# Circular membrane / hand drum physics
mem = genny.MembraneDrum(frequency=180, radius_m=0.16, air_coupling=0.4)
y = mem.strike(0.8, velocity=0.9, position=0.2, stroke="tone")
# Nonlinear cymbal / thin plate
y = genny.CymbalPlate(profile="crash").strike(2.0, velocity=0.9)
# A surface can be scraped or rolled over using the same spatial roughness model
surface = genny.SurfaceContact(roughness=0.7, characteristic_mm=1.2)
scrape = surface.scrape(1.0, speed=0.3)
roll = surface.roll(1.0, speed=0.5, radius_m=0.03)
# Rotor formulae are physical controls, not post pitch shifting
fan = genny.Rotor(rpm=1800, blades=5, radius_m=0.15)
y = fan.render(2.0)
print(fan.blade_passing_hz) # 150 Hz
jet = genny.JetEngine(rpm=9000, throttle=0.8).render(3.0)
heli = genny.Helicopter(main_rpm=320, main_blades=4, load=0.7).render(3.0)
Existing names such as conga, tabla, timpani, crash, ride, hihat, koto, sitar, fan, propeller, and helicopter now map to these shared engines while retaining their public parameters.
Genny 0.8: physical ports, machine sounds and physical kick/snare
Genny 0.8 introduces typed physical ports so models can exchange conjugate SI variables rather than only normalized audio arrays:
import numpy as np
import genny
force = np.ones(256)
velocity = np.ones(256) * 0.1
port = genny.PhysicalPort.mechanical(force, velocity, name="hammer")
print(port.effort_unit, port.flow_unit) # N, m/s
The new physical drum models are directly available:
kick = genny.KickDrum(frequency=52, beater_hardness=0.75)
a = kick.strike(0.6, velocity=0.9, sr=48000)
snare = genny.SnareDrum(frequency=185, snare_tension=0.8, wire_count=24)
b = snare.strike(0.45, velocity=0.85, snappy=0.8, sr=48000)
Machine models derive their important frequencies from geometry and speed:
motor = genny.ElectricMotor(rpm=6000, commutations_per_rev=12)
print(motor.rotation_hz, motor.commutation_hz)
y = motor.render(2.0)
gears = genny.GearTrain(rpm=1800, driver_teeth=20, driven_teeth=40)
print(gears.mesh_hz, gears.output_rpm)
z = gears.render(2.0)
AdvancedRollingContact closes a weak contact↔resonator feedback loop rather
than treating texture and body resonance as unrelated layers. Explosion
combines a reduced N-wave shock, low-frequency expansion, debris and distance
propagation.
Unified physical interactions (0.9)
Genny 0.9 standardizes instrument excitation/resonator interfaces using typed physical ports while retaining the proven nonlinear waveguide kernels as the internal high-rate solvers.
import genny
interaction = genny.HammerStringInteraction(
felt=0.18,
brightness=0.65,
)
result = interaction.solve(440.0, 1.0, velocity=0.8)
# Audio renderer output
samples = result.audio
# Mechanical SI-domain interface traces
force = result.drive_port.effort # newtons
velocity = result.drive_port.flow # metres / second
power = result.drive_port.power # watts
Acoustic interactions use pressure and volume flow:
reed = genny.ReedBoreInteraction(breath=0.7)
result = reed.solve(220.0, 1.0)
pressure = result.drive_port.effort # pascals
flow = result.drive_port.flow # m^3 / s
The high-level models expose the same layer:
piano = genny.Piano()
physical = piano.interaction()
audio = piano.note(440.0, 1.0)
violin = genny.BowedString()
physical = violin.interaction()
clarinet = genny.Clarinet()
physical = clarinet.interaction()
This is intentionally a staged refactor: in 0.9 the shared ports, units, power accounting and public interaction contracts are unified, while the specialized nonlinear waveguide loops remain their existing calibrated solvers. Future versions can move the inner scattering loops onto the same physical graph without another public-API change.
Common physical interaction solver (0.10)
Genny now contains a shared sample-by-sample interaction core in
genny.graphsolver. It is the first step from model-specific waveguide loops to
one common causal physical graph.
import genny
solver = genny.CommonPhysicalSolver
piano = solver.hammer_string(440.0, 1.0, velocity=0.8, sr=48000)
violin = solver.bow_string(220.0, 1.0, pressure=0.85, sr=48000)
clarinet = solver.reed_bore(220.0, 1.0, breath=0.7, sr=48000)
# SI-domain interface traces
force = piano.drive.effort # N
hammer_velocity = piano.drive.flow # m/s
pressure = clarinet.drive.effort # Pa
volume_flow = clarinet.drive.flow # m^3/s
The public HammerStringInteraction, BowStringInteraction,
ReedBoreInteraction, LipBoreInteraction, and SyrinxTracheaInteraction
currently use a hybrid renderer: the shared solver supplies the causal physical
interaction/ports while the older calibrated renderer remains mixed in for
sound quality during migration.
v0.11 shared waveguide core
Genny 0.11 adds the next layer of the common physical solver. Fractional delays can now use linear or cubic Lagrange interpolation, while ThiranDelay supplies a magnitude-preserving allpass option for feedback loops. The common solver also exposes frequency-dependent passive losses, stiff-string dispersion, stateful stick/slip friction, multi-port scattering, tone holes, bell radiation and hydrodynamic jet delay.
import genny
# Higher-accuracy fractional delay
line = genny.FractionalDelay(37.4, 256, method="lagrange3")
# Magnitude-preserving fractional delay for waveguide loops
thiran = genny.ThiranDelay(37.4, 256)
# Physical flute/edge-tone model now participates in the shared graph
flute = genny.Flute(pressure=0.8, jet_ratio=0.32)
audio = flute.note(440.0, 1.0, sr=48000)
The v0.11 common reed/bore and brass/bore solvers now include reduced tone-hole/bell termination behavior, and bowed strings use a stateful stick/slip model rather than a memoryless friction curve.
0.12 — passive multiport waveguide networks
Genny 0.12 adds WaveguideBranch and WaveguideNetwork. A network joins any number of delayed acoustic branches through the common lossless impedance-weighted scattering junction. This is the foundation for multi-hole bores, branched pipes, coupled strings and cavity networks.
import genny
branches = [
genny.WaveguideBranch(1.0, 17.3, 128),
genny.WaveguideBranch(1.5, 23.7, 128),
genny.WaveguideBranch(2.0, 31.2, 128),
]
network = genny.WaveguideNetwork(branches)
ir = network.render_impulse(4096)
The legacy timpani, upright_bass and finger_bass wrappers were also corrected for tuning/very-short-note compatibility while keeping their physical renderers underneath.
Genny 0.13: multi-hole wind networks and coupled resonators
Genny 0.13 adds reduced real-time acoustic networks built from the passive waveguide primitives introduced in 0.11 and 0.12.
Dynamic tone holes
import numpy as np
import genny
clarinet = genny.MultiHoleClarinet(220.0, sr=48000)
openings = np.zeros((48000, len(clarinet.bore.holes)))
openings[24000:, -2:] = 1.0 # open two holes during the note
audio, reed_port, bore_port = clarinet.note(1.0, fingering=openings)
A tone hole may be anywhere from closed (0) to open (1). Internally each hole is a third acoustic branch attached to the two neighbouring bore segments through an impedance-weighted scattering junction.
Multi-hole flute
flute = genny.MultiHoleFlute(440.0, jet_velocity=18.0, sr=48000)
fingering = np.zeros(len(flute.bore.holes))
fingering[-1] = 0.65
audio, jet_port, bore_port = flute.note(1.0, fingering=fingering)
The excitation retains a hydrodynamic jet delay while the resonator is now a chain of bore segments, side holes and bell radiation rather than one monolithic delay.
Coupled strings
strings = genny.CoupledStringBank(
frequencies=(440.0, 440.7, 439.3),
coupling=0.08,
sr=48000,
)
audio = strings.render(2.0, velocity=0.8)
This is intended for piano unisons, mandolin courses and sympathetic-string experiments. The strings exchange energy through a shared bridge state instead of being mixed only after synthesis.
Multi-port acoustic cavity
cavity = genny.AcousticCavityNetwork(
volume_m3=0.025,
port_radii_m=(0.01, 0.007),
port_lengths_m=(0.03, 0.02),
sr=48000,
)
impulse_response = cavity.render_impulse(4096)
The cavity is a reduced compliance connected to inertive neck/port branches. It is suitable for bottles, enclosures, coupled drum cavities and other low-order resonant systems.
These models are reduced real-time approximations. They are designed to preserve topology, coupling and causal interaction at low cost; they are not substitutes for full FEM/BEM simulation when exact geometry-dependent radiation is required.
Genny 0.14: shared piano bridge, soundboard and sympathetic strings
Genny 0.14 adds a reduced real-time piano network in which unison and unstruck strings share one mechanical bridge and one modal soundboard. Energy can therefore move from a struck course into sympathetic strings instead of those strings being rendered independently.
import genny
piano = genny.SympatheticPiano(
struck_frequency=220.0,
coupling=0.07,
sr=48000,
)
audio, stems = piano.render(
2.0,
velocity=0.9,
return_stems=True,
)
The existing high-level piano exposes the same network without changing the legacy-calibrated note() method:
piano = genny.Piano()
audio = piano.sympathetic_note(
220.0,
2.0,
coupling=0.07,
)
The shared bridge is a reduced mass-spring-damper system and keeps mechanical force/velocity state. The soundboard is a single damped modal bank driven by bridge force. This is a reduced-order real-time model, not a full finite-element piano simulation.
v0.15 — Stateful polyphonic piano
Genny now includes a continuous shared-state piano engine. Unlike rendering
separate notes and mixing them, PolyphonicPiano keeps one bridge, one modal
soundboard, per-note string courses, dampers, pedal state, a keyboard-wide
sympathetic bank, and passive aliquot modes alive across render calls.
import genny
p = genny.Piano().polyphonic(sr=48000)
p.note_on(60, velocity=0.85)
p.note_on(64, velocity=0.75)
p.note_on(67, velocity=0.80)
attack = p.render(0.5)
p.sustain_pedal(True)
p.note_off(60)
p.note_off(64)
p.note_off(67)
tail = p.render(2.0)
For sample-accurate offline sequencing:
p = genny.PolyphonicPiano(sr=48000)
audio = p.render_events([
{"time": 0.00, "type": "note_on", "note": 60, "velocity": 0.85},
{"time": 0.00, "type": "note_on", "note": 64, "velocity": 0.75},
{"time": 0.30, "type": "sustain", "down": True},
{"time": 0.50, "type": "note_off", "note": 60},
{"time": 0.50, "type": "note_off", "note": 64},
{"time": 1.40, "type": "sustain", "down": False},
], dur=2.0)
Piano.note() remains unchanged for backward compatibility, while
Piano.sympathetic_note() remains the lightweight single-note shared-network
renderer introduced in v0.14.
v0.16 - Piano pedals, dampers and action geometry
The stateful PolyphonicPiano now models the three grand-piano pedal behaviours separately:
p = genny.Piano().polyphonic(sr=48000)
p.note_on(60, 0.85)
p.sostenuto_pedal(True) # captures only keys already held
p.note_off(60)
p.una_corda(True) # subsequent strikes use fewer strings/softer action
p.set_damper(72, 0.45) # partial per-note damper contact, 0=open, 1=closed
audio = p.render(2.0)
render_events() also accepts sostenuto, una_corda/soft, and damper events. Una corda changes excitation before the string network: multi-string courses strike one fewer string and use a softer hammer impulse. Sostenuto captures only the notes held at pedal-down, unlike sustain which opens all dampers. Per-note damper contact continuously changes string loss instead of gating the final signal.
Hammer strike position now varies across the keyboard and is represented by a weak delayed counter-wave in the excitation. Treble duplex/aliquot resonators receive a small note-dependent stretch rather than sitting on perfectly coincident integer harmonics. These are reduced-order realtime models, not a geometric FEM action.
Genny 0.17 — piano polarization, longitudinal coupling, and hammer rebound
The stateful piano now models two transverse polarization planes per string, a weak longitudinal mode, note-position-dependent bridge mobility, and a nonlinear felt hammer contact with rebound diagnostics.
import genny
p = genny.Piano().polyphonic(sr=48000)
p.note_on(60, 0.85)
audio = p.render(1.0)
course = p.courses[60]
print(course.polarization_energy())
print(course.longitudinal_energy())
print(course.hammer_action.last_contact_time_s)
print(course.hammer_action.last_rebound_velocity_m_s)
The hammer contact follows a reduced nonlinear felt law:
F = K * x**p
while each course contains a primary and orthogonal transverse delay loop with a small frequency split. A weak longitudinal resonance exchanges energy with the transverse motion at the bridge. FrequencyDependentBridge.step_note_forces() keeps one shared bridge state while returning slightly different local mobilities for different keyboard regions.
These are realtime reduced-order models: they preserve the coupling topology and physically meaningful state, but they are not substitutes for full 3D finite-element analysis.
v0.18 — shared physical string core
Genny now has one explicit reduced-order string model that can be reused by piano, guitar, harp and related instruments.
import genny
props = genny.StringPhysicalProperties(
length_m=0.65,
radius_m=0.00035,
material="steel",
frequency_hz=440.0,
)
print(props.tension_n)
print(props.characteristic_impedance_n_s_m)
print(props.flexural_rigidity_n_m2)
print(props.inharmonicity_B)
string = genny.DispersiveString(props, sr=48000)
audio = string.render_pluck(2.0, velocity=0.8, position=0.22)
The ideal fundamental is
f0 = (1/(2L))*sqrt(T/mu)
and the string-side characteristic impedance is
Z = sqrt(T*mu).
For a circular string, I = pi*r^4/4 and flexural rigidity is E*I. Stiff-string
partials use the reduced approximation
f_n ~= n*f0*sqrt(1 + B*n^2)
with B = pi^2*E*I/(T*L^2).
The longitudinal-wave estimate is c_L=sqrt(E/rho) and
f_L=c_L/(2L).
Existing plucked-string facades remain compatible, while the common core is available explicitly:
inst = genny.PluckedString(kind="guitar")
audio = inst.physical_note(196.0, 1.5, sr=48000)
PianoStringCourse now also creates one StringPhysicalProperties object for every
unison string and derives dispersion from its physical inharmonicity. The model is a
realtime reduced-order waveguide, not a full 3-D FEM string/bridge solver.
v0.19 — shared string migration and wound strings
Genny now uses the same reduced-order physical string core for the main plucked and bowed families. WoundStringPhysicalProperties represents a load-bearing core plus a helical winding and exposes its mass per unit length, tension, characteristic impedance, effective bending rigidity and inharmonicity.
import genny
bass_string = genny.WoundStringPhysicalProperties.from_frequency(
55.0,
length_m=0.864,
core_material="steel",
winding_material="nickel",
)
audio = genny.DispersiveString(bass_string, sr=48000).render_pluck(
2.0, velocity=0.8, position=0.28
)
The winding mass follows the helical path rather than treating the string as a single solid cylinder. Bending stiffness is a reduced partially-coupled core/winding approximation; this is not a contact-resolved helical FEM model.
DispersiveString also supports bowed excitation with stateful stick/slip hysteresis:
string = genny.DispersiveString(
genny.StringPhysicalProperties.from_frequency(440.0, material="steel"),
sr=48000,
)
violin_like = string.render_bow(1.5, bow_velocity=0.18, bow_pressure=0.75)
The public guitar, harp, mandolin, steel guitar, koto, sitar, bass, violin and cello wrappers source their string motion from this shared core. Instrument bodies, pickups and jawari layers remain specialised radiation/post-processing stages.
Genny 0.20 — local string interactions
The shared physical string core now models common local playing interactions rather than treating them as unrelated effects.
import genny
props = genny.WoundStringPhysicalProperties.from_frequency(
82.41,
length_m=0.648,
)
string = genny.DispersiveString(props, sr=48000)
# Pick/finger/nail hardness changes physical contact duration.
y = string.render_exciter(
1.0,
genny.StringExciter("pick", hardness=0.9, position=0.18),
velocity=0.8,
)
Fretting preserves construction/tension and shortens the vibrating length according to equal temperament:
fb = genny.FretboardGeometry(scale_length_m=0.648, action_m=0.0018)
y = genny.DispersiveString(props, sr=48000).render_fret_buzz(
5, 1.0, fretboard=fb, velocity=0.9, buzz=0.75
)
A slide changes the waveguide delay continuously:
y = genny.DispersiveString(props, sr=48000).render_glissando(
1.2, 82.41, 123.47
)
Additional shared interactions include render_natural_harmonic(), render_slap(), bridge-local palm_mute, PluckedString.local_note(), PluckedString.glissando(), and BowedString.glissando().
These are realtime reduced-order contact models. They preserve causal local excitation/contact behaviour but are not a full geometric string/fret FEM solver.
v0.21 — expressive physical string gestures
The shared string core now supports expressive techniques inside the stateful waveguide rather than as post-render effects.
import genny
guitar = genny.PluckedString("guitar", position=0.18)
bend = guitar.expressive_note(196, 1.0, technique="bend", cents=200)
vibrato = guitar.expressive_note(196, 1.0, technique="vibrato",
depth_cents=20, rate_hz=5.2)
slide = guitar.expressive_note(196, 1.2, technique="slide",
end_frequency=293.66,
roughness=0.5, pressure=0.6)
buzz = guitar.expressive_note(196, 1.0,
technique="fret_buzz_feedback", fret=3)
tap = guitar.expressive_note(196, 0.8, technique="tapping", fret=12)
pinch = guitar.expressive_note(196, 1.0,
technique="pinch_harmonic", harmonic=4)
For a bend, fixed vibrating length and linear density imply
T / T0 = (f / f0)^2.
render_bend() and render_vibrato() therefore change the effective waveguide delay continuously. render_fret_buzz_feedback() detects a reduced string/fret penetration state and reinjects the collision impulse into the travelling wave, rather than adding fret noise only after the string has been rendered.
These remain realtime reduced-order interaction models, not contact FEM.
v0.22 — continuous combined string performance
Genny 0.22 adds a stateful performance layer that combines expressive controls in one physical string loop instead of rendering one technique per call.
import genny
guitar = genny.PluckedString("guitar")
audio = guitar.performance(
196.0,
1.5,
sr=48000,
gestures=[
genny.StringGesture("bend", 0.1, 0.8, 0, 180),
genny.StringGesture("vibrato", 0.35, 1.2, 12, rate_hz=5.4),
genny.StringGesture("slide", 0.7, 1.15, 0, 3),
genny.StringGesture("fret_contact", 0.5, 1.3, 0.3, 0.9),
genny.StringGesture("palm_mute", 1.05, 1.5, 0, 0.8),
],
)
The same sample loop now combines effective vibrating length, tension, vibrato, loop damping, and nonlinear fret contact. HertzFretContact uses a reduced F = k delta^(3/2) law. StringPerformance.last_diagnostics reports the resulting frequency, length, tension ratio, inharmonicity and contact-force extrema.
This is still a realtime reduced-order model rather than a contact-resolved FEM string/fret simulation.
Metadata
Release files for genny 1.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| genny-1.0.1.tar.gz | 1.4 MB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| genny-1.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.2 MB
Release files / genny-1.0.1.tar.gz
| Download URL | genny-1.0.1.tar.gz |
|---|---|
| Size | 1.4 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
341506e181afe76d9fab2ae1fbb3081c2dbbfebb2f121492bf852377bf6b749e
|
|
BLAKE2b-256 checksum How to use checksums |
ee6880082801205c9d03b0d74aeed95371c3d207ff563d10214b41289c979621
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Release files / genny-1.0.1-py3-none-any.whl
| Download URL | genny-1.0.1-py3-none-any.whl |
|---|---|
| Size | 790.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7ec31cf14b5d49a2d23af39a0b6fd5547f081393054bbdb6dcb0e28b386d39ec
|
|
BLAKE2b-256 checksum How to use checksums |
34508e340af446162774857add0628281da9b16c3c5fe4fcdbde088fa3a57946
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Security for genny 1.0.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.