A creative toolkit for exploring modular forms and elliptic curves through Sonic Pi.
This is a pre-alpha release of modular_forms
. At this stage, only a subset of core mathematical definitions and operations is implemented.
Future updates might include a DSL, depending on how the library is used and the interest from the community. A key challenge lies in creating musical mappings that stay true to the underlying mathematics while also sounding intentional, expressive, and naturally fitting within a musical structure.
- Accessible to both musicians and coders: No math expertise required. Create musical patterns, rhythms, timbres, and harmonies by experimenting with mathematical ideas and turning them into sound and effects intuitively.
- Interactive Educational Resource: Use Sonic Pi to discover introductory number theory in a hands-on, immersive way, gaining insights into abstract concepts through math in action.
Given the vastness of the field, this tool intentionally focuses on a limited subset of definitions, without covering all aspects of each.
- 🧩 List of implemented modules:
This library is designed for creative exploration rather than maximum computational efficiency. It is not intended to replace specialized mathematical software optimized for heavy or large-scale computations. Instead, it draws inspiration from tools like SageMath, Pari/GP, and the LMFDB database.
The goal is simple: to provide an accessible and creative starting point for those who wish to explore, learn, and uncover new ideas, regardless of their mathematical background.
You can install the modular_forms
gem directly from RubyGems or clone it from GitHub.
gem install modular_forms
If you are use Ruby, then import via
require 'modular_forms'
If you are using Sonic Pi, replace <PATH>
with the directory path to the modular_forms.rb
file inside the gem installation on your system. You can find the full path by running:
gem which modular_forms
Then require the file like this:
require "<PATH>/modular_forms.rb"
The Taniyama–Shimura Conjecture (now the Modularity Theorem), proven for semistable cases by Andrew Wiles, connects elliptic curves over the rationals to modular forms. This profound result was crucial in proving Fermat’s Last Theorem.
For example, we can explore the relationship between the elliptic curve 144.a3
and the newform orbit 144.2.a.a
through their L-function,
L(E, s) = L(f, s)
, in a musical sense.
require "<PATH>/modular_forms.rb"
# Set precision for the Fourier q-expansion
prec = 20
# Construct a weight 2 newform as an eta quotient
n = ModularForms.dedekind_eta_pow(12, prec, 12)
d1 = ModularForms.dedekind_eta_pow(4, prec, 6)
d2 = ModularForms.dedekind_eta_pow(4, prec, 24)
eta_prod = ModularForms.eta_product(d1, d2)
newform = ModularForms.eta_quotient(n, eta_prod, prec)
# Define the elliptic curve E over F_p
p = 13
ellc = ModularForms.elliptic_curve_fp(p, [0, -1]) # y^2 = x^3 - 1
points = ModularForms.cardinality_fp(ellc) # count points on E mod p
# Compute the modular coefficient a_p of L-function(E, s)
a_p = ModularForms.a_p(p, points)
# Sonify the modular relationship
live_loop :modularity_music do
play (chord (:a3 + a_p), :m11)[newform.ring.tick],
amp: a_p, release: 0.125
sleep 0.125
end
Unlike the structured Fermat example, this finite loop explores more abstract territory, using multiple concepts to create a sonic landscape that, while grounded in mathematical principles, goes beyond conventional musical forms.
require "<PATH>/modular_forms.rb"
p = 3
eisenstein_melody = ModularForms.eisenstein_series(8)
e8 = eisenstein_melody.take(58)
hecke_op = ModularForms.hecke_operator_prime_non_cusp(e8, p, 8, 20)
ellc = ModularForms.elliptic_curve_q([2, 5])
disc = ModularForms.discriminant_q(ellc)
j_func = ModularForms.j_function(40)
newform_ac = ModularForms.analytic_conductor(15, 2)
pol = ModularForms.def_pol_2deg(41)
x = 7
dirichlet_char_group = []
(1..x).each do |i|
dirichlet_char_group << ModularForms.conrey_p_pminus1(x, i)
end
(disc * -1).times do
synth :zawa,
note: ModularForms.zeta_coeffs_deg2(dirichlet_char_group, 30)
.tick(:zeta) + 72, amp: rrand(0.3, 0.6)
synth :subpulse, note: ModularForms.padic_valuation(eisenstein_melody.next, p) % 7 + 70,
release: 0.25 if (spread (disc % 6), 7).tick(:d)
synth :chiplead, note: hecke_op.tick(:tp) % 7 + 50,
release: newform_ac, attack: newform_ac
synth :fm, note: ModularForms.gauss_sum_triv(5, j_func.tick(:j)) + 51,
release: 0.25, attack: 0.012,
pan: ModularForms.analytic_conductor(j_func.look % 15, 4) * rrand_i(-1, 1) if
spread(ModularForms.gamma1_index(p), 11).tick(:g)
sample 90, release: 0.125 if pol.tick == 'x'
sample :ambi_drone, beat_stretch: 0.7 if pol.look == '*'
sample :bass_drop_c, release: 0.125 if pol.look == '6'
sleep ModularForms.padic_norm(p, p)
end
ModularForms.eisenstein_series(weight_k, galois_field = nil)
ModularForms.eisenstein_series_product(weight_k1, weight_k2, prec)
ModularForms.eisenstein_series_pow(weight_k, power, prec)
ModularForms.dedekind_eta_function(m_scale = 1, pentagonal_coefs = false)
ModularForms.dedekind_eta_pow(power, prec, m_scale = 1)
ModularForms.dedekind_sum(h, k)
ModularForms.eta_product(eta1, eta2, prec = nil)
ModularForms.eta_quotient(num_eta, den_eta, prec)
ModularForms.jacobi_theta_function(jacobi_index = 3, square_coefs = false)
ModularForms.jacobi_theta_function_pow(jacobi_index, power, prec)
ModularForms.ramanujan_tau_function
ModularForms.j_function(prec)
ModularForms.hecke_operator_prime_non_cusp(non_cusp_form_arr, prime, weight_k, prec)
ModularForms.hecke_operator_prime_cusp(cusp_form_arr, prime, weight_k, prec)
ModularForms.t_gen_matrix(n_power)
ModularForms.s_gen_matrix(n_power)
ModularForms.u_gen_matrix(mod_n)
ModularForms.st_gen_matrix(n_power)
ModularForms.product_gen_mats(gen_mat_a, gen_mat_b)
ModularForms.gamma0_index(n)
ModularForms.gamma1_index(n)
ModularForms.dirichlet_trivchar(modq, a)
ModularForms.conrey_p_pminus1(modp, a)
ModularForms.gauss_sum_triv(dirichlet_q, a)
ModularForms.gauss_sum_conrey_p_minus1(dirichlet_q, a, parity)
ModularForms.elliptic_curve_q(coefs)
ModularForms.discriminant_q(curve)
ModularForms.j_invariant_q(curve)
ModularForms.point_on_curve_q?(curve, point)
ModularForms.point_addition_q(curve, p, q)
ModularForms.scalar_mul_point_q(curve, n, point)
ModularForms.isogeny_2deg_q(curve, point_2tor)
ModularForms.isogeny_ndeg_q(curve, point_ntor, order)
ModularForms.weil_height(x_point)
ModularForms.canonical_height(curve, point, prec = 64)
ModularForms.elliptic_curve_fp(p, coefs)
ModularForms.point_on_curve_modp?(curve, point)
ModularForms.discriminant_modp(curve)
ModularForms.j_invariant_modp(curve)
ModularForms.point_addition_modp(curve, p_point, q_point)
ModularForms.scalar_mul_point_modp(curve, n, point)
ModularForms.points_fp(curve, point_at_infinity = false)
ModularForms.cardinality_fp(curve)
ModularForms.quadratic_twist_fp(curve)
ModularForms.analytic_conductor(level_n, weight_k)
ModularForms.a_p(p, cardinality)
ModularForms.padic_valuation(num_b10, p)
ModularForms.padic_norm(num_b10, p)
ModularForms.padic_expansion(num_b10, p, prec = 11, reverse_trim = false)
ModularForms.def_pol_2deg(p = 2, c = 0, num = 1)
ModularForms.zeta_coeffs_deg2(dirichlet_char_group, n)
Install dependencies first:
bundle install
This project features a comprehensive Minitest suite covering core functionality across modules. While not all edge cases are tested, the main mathematical functions are well validated to ensure correctness and stability.
Run tests with:
rake test