mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 23:00:20 +00:00
Add dedicated Rhodes electric piano synth
Tine + tonebar + electromagnetic pickup model with bell-like harmonics, metallic attack transient, and pickup nonlinearity. electric_piano instrument preset now uses rhodes_synth instead of FM. FM section updated to show bells. Audio demos for both. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
+18
-1
@@ -465,7 +465,23 @@ def gen_synth_pwm_fast():
|
||||
_synth_demo("pwm_fast", "pwm_fast")
|
||||
|
||||
def gen_synth_fm():
|
||||
_synth_demo("fm", "fm", envelope="piano")
|
||||
score = Score("4/4", bpm=100)
|
||||
p = score.part("demo", synth="fm", envelope="bell", volume=0.5,
|
||||
fm_ratio=3.0, fm_index=5.0, reverb=0.3)
|
||||
for n in ["C5", "E5", "G5", "C6", "G5", "E5", "C5", "E5"]:
|
||||
p.add(n, Duration.QUARTER, velocity=80)
|
||||
render("synth_fm", score)
|
||||
|
||||
def gen_synth_rhodes():
|
||||
score = Score("4/4", bpm=80)
|
||||
p = score.part("demo", instrument="electric_piano", volume=0.5, reverb=0.3)
|
||||
# Jazz chords with hold
|
||||
p.hold("C3", Duration.WHOLE * 2, velocity=60)
|
||||
p.hold("E3", Duration.WHOLE * 2, velocity=55)
|
||||
p.hold("Bb3", Duration.WHOLE * 2, velocity=55)
|
||||
for n in ["G4", "Bb4", "C5", "Bb4", "G4", "F4", "E4", "G4"]:
|
||||
p.add(n, Duration.QUARTER, velocity=75)
|
||||
render("synth_rhodes", score)
|
||||
|
||||
def gen_synth_supersaw():
|
||||
_synth_demo("supersaw", "supersaw", envelope="pad")
|
||||
@@ -823,6 +839,7 @@ GENERATORS = [
|
||||
gen_synth_pwm_slow,
|
||||
gen_synth_pwm_fast,
|
||||
gen_synth_fm,
|
||||
gen_synth_rhodes,
|
||||
gen_synth_supersaw,
|
||||
gen_synth_piano,
|
||||
gen_synth_bass_guitar,
|
||||
|
||||
+24
-4
@@ -129,14 +129,16 @@ the electric piano in every Whitney Houston ballad, the bass in every
|
||||
Depeche Mode track, the bells in a thousand TV jingles. If you heard
|
||||
pop music in the 80s, you heard FM synthesis.
|
||||
|
||||
**Use for:** electric piano (rhodes), bells, metallic leads, jazz chords.
|
||||
**Use for:** bells, metallic leads, glassy pads, DX7-style sounds.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
rhodes = score.part(
|
||||
"rhodes",
|
||||
bells = score.part(
|
||||
"bells",
|
||||
synth="fm",
|
||||
envelope="piano",
|
||||
envelope="bell",
|
||||
fm_ratio=3.0,
|
||||
fm_index=5.0,
|
||||
volume=0.3,
|
||||
reverb=0.4,
|
||||
)
|
||||
@@ -459,6 +461,24 @@ soundboard.
|
||||
|
||||
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_piano.wav" type="audio/wav"></audio>
|
||||
|
||||
Rhodes Electric Piano
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Fender Rhodes — a rubber-tipped hammer strikes a steel tine
|
||||
next to a tonebar, picked up by an electromagnetic pickup. Warm,
|
||||
bell-like, with a bright metallic attack that mellows into a
|
||||
singing sustain. The sound of jazz clubs, soul, and neo-soul.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
rhodes = score.part("rhodes", synth="rhodes_synth")
|
||||
# Or use the instrument preset (adds tremolo + chorus)
|
||||
rhodes = score.part("rhodes", instrument="electric_piano")
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_rhodes.wav" type="audio/wav"></audio>
|
||||
|
||||
Bass Guitar Synth
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
+63
-1
@@ -416,6 +416,68 @@ def piano_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
return (peak * wave).astype(numpy.int16)
|
||||
|
||||
|
||||
def rhodes_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Rhodes electric piano — tine struck by hammer, electromagnetic pickup.
|
||||
|
||||
The Rhodes sound comes from a rubber-tipped hammer hitting a thin
|
||||
steel tine next to a tonebar. The tine vibrates near an electromagnetic
|
||||
pickup (like a guitar pickup), producing a warm, bell-like tone with:
|
||||
1. Strong fundamental + 2nd harmonic (tine character)
|
||||
2. Bright metallic attack that mellows quickly (hammer on tine)
|
||||
3. Bell-like inharmonic partials on soft hits, bark on hard hits
|
||||
4. Asymmetric waveform from the pickup's nonlinear response
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float64) / SAMPLE_RATE
|
||||
rng = numpy.random.default_rng(int(hz * 100) % 2**31)
|
||||
|
||||
# Two-stage decay: quick initial drop, then long sustain
|
||||
decay = numpy.where(t < 0.15,
|
||||
numpy.exp(-4.0 * t),
|
||||
numpy.exp(-4.0 * 0.15) * numpy.exp(-1.2 * (t - 0.15)))
|
||||
|
||||
wave = numpy.zeros(n_samples, dtype=numpy.float64)
|
||||
|
||||
# Brightness scales with pitch
|
||||
brightness = numpy.clip((hz - 65) / 800, 0.0, 1.0)
|
||||
|
||||
# Tine harmonics — strong fundamental, prominent 2nd, bell-like upper
|
||||
tine_harmonics = [
|
||||
(1, 1.0), # fundamental
|
||||
(2, 0.6 + 0.15 * brightness), # 2nd — the Rhodes character
|
||||
(3, 0.15 + 0.1 * brightness), # 3rd — adds warmth
|
||||
(4, 0.08 + 0.08 * brightness), # 4th — bell quality
|
||||
(5, 0.04), # 5th — subtle shimmer
|
||||
(6, 0.02), # 6th
|
||||
]
|
||||
|
||||
for n, amp in tine_harmonics:
|
||||
f_n = hz * n
|
||||
if f_n >= SAMPLE_RATE / 2:
|
||||
break
|
||||
# Higher harmonics decay faster
|
||||
h_decay = decay * numpy.exp(-(0.8 + 0.3 * brightness) * (n - 1) * t)
|
||||
phase = rng.uniform(0, 2 * numpy.pi)
|
||||
wave += amp * numpy.sin(2 * numpy.pi * f_n * t + phase) * h_decay
|
||||
|
||||
# Hammer-on-tine transient — bright metallic click
|
||||
click_len = min(int(SAMPLE_RATE * 0.008), n_samples)
|
||||
click_t = numpy.arange(click_len, dtype=numpy.float64) / SAMPLE_RATE
|
||||
# Inharmonic tine ring at attack (bell partials)
|
||||
click = (numpy.sin(2 * numpy.pi * hz * 5.3 * click_t) * 0.15 +
|
||||
numpy.sin(2 * numpy.pi * hz * 7.1 * click_t) * 0.08)
|
||||
click *= numpy.exp(-numpy.linspace(0, 20, click_len))
|
||||
wave[:click_len] += click
|
||||
|
||||
# Subtle asymmetry from pickup nonlinearity (soft saturation)
|
||||
wave = numpy.tanh(wave * 1.2) / 1.2
|
||||
|
||||
mx = numpy.abs(wave).max()
|
||||
if mx > 0:
|
||||
wave /= mx
|
||||
|
||||
return (peak * wave).astype(numpy.int16)
|
||||
|
||||
|
||||
def bass_guitar_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Bass guitar — plucked thick string with magnetic pickup.
|
||||
|
||||
@@ -1872,7 +1934,7 @@ _SYNTH_FUNCTIONS = {
|
||||
"noise": noise_wave, "supersaw": supersaw_wave,
|
||||
"pwm_slow": pwm_slow_wave, "pwm_fast": pwm_fast_wave,
|
||||
"pluck_synth": pluck_wave, "organ_synth": organ_wave,
|
||||
"strings_synth": strings_wave, "piano_synth": piano_wave,
|
||||
"strings_synth": strings_wave, "piano_synth": piano_wave, "rhodes_synth": rhodes_wave,
|
||||
"bass_guitar_synth": bass_guitar_wave, "flute_synth": flute_wave,
|
||||
"trumpet_synth": trumpet_wave, "clarinet_synth": clarinet_wave,
|
||||
"marimba_synth": marimba_wave, "oboe_synth": oboe_wave,
|
||||
|
||||
+5
-7
@@ -17,13 +17,11 @@ INSTRUMENTS = {
|
||||
"synth": "piano_synth", "envelope": "none",
|
||||
"vel_to_filter": 3000,
|
||||
},
|
||||
"electric_piano": { # Rhodes/Wurlitzer
|
||||
"synth": "fm", "envelope": "piano",
|
||||
"fm_ratio": 1.0, "fm_index": 2.0,
|
||||
"detune": 6, "chorus": 0.2, "chorus_rate": 1.0,
|
||||
"lowpass": 4000, "saturation": 0.15,
|
||||
"tremolo_depth": 0.15, "tremolo_rate": 4.5,
|
||||
"analog": 0.2,
|
||||
"electric_piano": { # Rhodes
|
||||
"synth": "rhodes_synth", "envelope": "none",
|
||||
"chorus": 0.15, "chorus_rate": 1.0,
|
||||
"tremolo_depth": 0.12, "tremolo_rate": 4.5,
|
||||
"analog": 0.15,
|
||||
},
|
||||
"organ": {
|
||||
"synth": "organ_synth", "envelope": "organ",
|
||||
|
||||
Reference in New Issue
Block a user