top of page

What does coding music look like?

  • amoghdwivedi
  • Oct 22, 2024
  • 5 min read

Updated: Nov 12, 2024


my face when I'm coding music

People are usually intrigued when I say I code music, and they don’t really know what I am talking about. Neither do I, but it feels nice to brag about it anyway. This blog post is my attempt to show some basic techniques I use when I make generative music.


Step 1: Music is all about the rhythm man

 
 

English (😎): Let’s imagine a well-paced pulse, and for now, let’s imagine the pulse being represented with a click.


Max/MSP lingo (🤓): Let’s initialize a metro object that triggers a bang every 1500 milliseconds and trigger a sped-up kick sample in a playlist~ object.



Step 2: Where's the melody?

 
 

😎: I would like to hear a lengthy sine wave every 4 clicks, on the note A.


🤓: Let’s initialize a counter that counts from 1 to 4. Let’s target the 1st count using the select object, and trigger an envelope using the function object that modulates the amplitude of a cycle~ object at 440 hz. Let’s set the domain of the envelope to last around 4000 seconds.


Step 3: More notes please

 
 

😎: Let’s say the note moves up to E on every 2nd count.


🤓: Let’s isolate the 2nd count of every using the sel object (i.e. sel 2). We can “flip a coin” using the decide object. If the condition is “true”, let’s set the frequency of the cycle~ object to E. We can use an equivalent MIDI note number to get the value in hz. Note that we have to reset the frequency back to 440 at the 1stcount.


Step 4: I just want to play a string instrument

 
 

😎: Hmm… it’s sounds predictable. What if we said there was a possibility of the note changing to E on the 2nd count? And let’s slide (i.e. gliss) up to it instead of jumping to it instantly.


🤓: Let’s use a decide object to generate a random Boolean value. Let’s change the note to E only if this condition is true. To simulate a gliss, let’s use the line object. To simplify the data passing through the first inlet of the cycle~ object, let’s have all pitch data go directly to the line object. We can reroute the 440 message into the line object with a ramp time of 0.

 

Step 5: Butter notes

 
 

😎: Let’s get rid of the click! I now want to hear some harmony. Let’s set up a second voice, identical to the first. But this one always starts on E and either slides down to C# or sliding up to G on the 2nd count. With the first voice oscillating between A and E, and the second voice between E, C#, and G, we are outlining an A7 chord.


🤓: Delete the playlist~ object! Copy and paste the basic module, and set the initial pitch to E. Using the same decide-line object combo, let’s slide down to either C# or G. Let’s also change the ramp time of the second voice slightly (300 ms to 350 ms) so that we can accentuate the second voice’s independence. Our patch is prone to getting too loud- let’s be cautious and use a *~ object, and be precautious and multiply the output by .25.


Step 6: Syncopation

 
 

😎: Sounds predictable and robotic. What if we were to potentially rhythmically offset the second voice by one beat? So instead of the voice changing solely on beat 1 and 2, what if we allowed it to start and slide on beat 2 and 3?


🤓: To achieve the rhythmic offsets, all we have to do is change the arguments of the sel object- we can do this by triggering a different pair of numbers into the 2nd and 3rd inlet of the sel object. When should we change these settings? To be safe and to avoid any possible pops or clicks, let’s only change these settings once the function graph is done generating a ramp. We can discern that through the second outlet of the curve~ object. In order to combat the mechanical feeling, lets add a pipe object with a modest randomized delay time range (0-300 ms) before the triggering of the envelopes. This is akin to simulating rubato and “feel”. 

 

Step 7: Imagine conducting a tempo change

 
 

😎: Okay, nice. Let’s speed up the underlying pulse every now and then. It would be nice if the basic pulse of the piece ebbed and flowed across time.


🤓: We can setup a function object to take care of the tempo changes, setting its maximum range value to 1500 (our current pulse in ms). Let’s draw a curve to simulate a deliberately shaped change in tempo. Note that the function values will decrease if you want the metro object to speed up. We can trigger the function graph itself every 8-12 loops. To keep track of the number of loops, let’s setup a counter which initially begins at 1 and terminates at 8. After the first loop, we can randomize the counter’s value to somewhere between 8-12. Note that our counter’s minimum value is fixed, i.e. always 1, and our maximum value is dynamic, i.e. 8-12, the logic we would use to detect the minimum and maximum values is different.

To avoid any future complaints about predictability, let’s also randomize the time domain of the function graph across a broad and generous range of values. We can change the domain of the curve while preserving the shape of the curve by using the “setdomain $1” message instead of the “domain $1” message. The former scales the original curve exactly and proportionally to the change in time domain.


Step 8: No effects?!

 
 

😎: It’s taking shape. Let’s just add some effects and call it a day. Maybe we can search for a balance between more harsh sounds and more delicate sounds!


🤓: Let’s use the rnbo.plateverb~ object, alongside overdrive~ as our two effects. We can use the change in tempo as a way to modulate our humble effects chain, so that we can add some compositional depth to our patch. One possible solution is this: as the tempo quickens, let’s gradually increase the distortion level alongside setting the reverb mix level to wet. This will allow our distorted frequencies to resonate in the reverb, adding a contrasting sound profile to our otherwise benign soundscape.

We already have a function object marking the change in tempo. We can use the scale~ object to easily reuse and reinterpret the gesture for the modulations in our effects chain. In order to make the patch more generative, let’s also randomize the maximum distortion and maximum mix levels using some random objects.  Additionally, in order to add some stereo depth, let’s have separate overdrive objects for each L and R channel. Our patch is getting loud, let’s *~0.5 everything at the end.


Hope that was helpful

And you could keep developing this for months! In my experience with generative music, I always like to start off with a fresh abstract idea like the one you saw above. That helps me exploit the openness of an environment like Max, and forces me to find some kind of a creative solution every step along the way.

Max is a bit unique in that everyone's solution to the same problems can look different. Maybe you prefer to organize your cables a certain way, or wish to use coll objects instead of sel, etc. I hope that you will find your own solutions to your problems. It's a very rewarding feeling!


Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating

© 2025 Amogh Dwivedi. All Rights Reserved.

bottom of page