Today, I had a class with Dr. Boyle. He played György Ligeti’s Poème Symphonique for 100 Metronomes, and I thought it would be such a cool idea to write code for this piece! However, while the nature of the piece remained unchanged, I don’t think it would be the same, since the laptop would play the code indefinitely. So, I decided that all metronomes should stop playing once they reach 50 beats.

György Ligeti’s Poème Symphonique for 100 Metronomes

Exported audio from the code

Spectrogram of the audio file

/*

Program Note:
This piece is inspired by György Ligeti's Poème Symphonique for 100 Metronomes, a work composed in
 1962 that explores the concept of a musical labyrinth and the auditory perception of infinity. Drawing on
 Ligeti's idea of parallel mirrors reflecting endless images, this program replicates the hypnotic and 
mesmerizing effect of overlapping independent tempos created by multiple metronomes.

Using modern digital synthesis, the program simulates the gradual deceleration and silencing of 100 
independent metronomes distributed across a stereo sound field. Each metronome is set to a unique 
frequency (spanning from 440 Hz to 880 Hz), tempo (ranging from 80 to 240 BPM), and stereo position
 (panned across the left and right channels). Instead of physical metronomes placed on resonant 
surfaces like in Ligeti's original performance, the digital audio synthesis reproduces their ticking pulses 
to emulate the acoustic complexity and variation.

Similar to the original performance instructions:

- Each metronome operates at its own tempo, creating a complex interplay of rhythms.

- The performance begins with all metronomes ticking simultaneously, 
but over time, the staggered tempi cause individual ticks to dissolve into silence.

- The spatial panning enhances the sense of physicality and unpredictability inherent in the original piece.

*/

(
// Boot the server and define the SynthDef
s.waitForBoot {
    SynthDef(\metronome, { |freq = 440, amp = 0.01, decay = 0.02, pan = 0|
        var env = EnvGen.kr(Env.perc(0.001, decay), doneAction: 2); // Envelope for short tick
        var signal = SinOsc.ar(freq) * env * amp; // Sound generation
        Out.ar(0, Pan2.ar(signal, pan)); // Output with panning
    }).add;

    // Start playing the pattern
    s.sync;

    // Use a function block to ensure variables are scoped properly
    {
        // Declare and initialize the required variables
        var numMetronomes = 100; // Number of metronomes
        var bpmMin = 80; // Minimum BPM
        var bpmMax = 240; // Maximum BPM
        var freqMin = 440; // Minimum frequency
        var freqMax = 880; // Maximum frequency
        var beatsPerMetronome = 50; // Number of beats for each metronome to play

        // Linearly distributed parameters
        var tempos = Array.fill(numMetronomes, { |i| bpmMin + (bpmMax - bpmMin) * (i / (numMetronomes - 1)) });
        var frequencies = Array.fill(numMetronomes, { |i| freqMin + (freqMax - freqMin) * (i / (numMetronomes - 1)) });
        var pans = Array.fill(numMetronomes, { |i| -1 + 2 * (i / (numMetronomes - 1)) }); // Spread panning between -1 and 1

        // Combine parameters into Pbind and Pseq
        Ppar(
            tempos.collect { |tempo, index|
                Pbind(
                    \instrument, \metronome,                         // Use the \metronome SynthDef
                    \freq, frequencies[index],                     // Assign unique frequency
                    \pan, pans[index],                             // Assign unique stereo position
                    \amp, 0.05,                                    // Set amplitude
                    \dur, Pseq([(60 / tempo)], beatsPerMetronome)   // Duration (tempo) repeated
                )
            }
        ).play;
    }.value; // Call the function block
};
)