Hi, Habrayuzer! In this topic I will talk about my idea of generating musical compositions. Let's create a language for describing the rhythm of music based on python, write the compiler of this language in the wave files and get a pretty nehilno electronic composition.
Welcome under cat.
Who can not wait to listen to muzonchik now, here's online:
clickable (the first couple of seconds are unsuccessful, the next is quite normal).
Instead of intro')
I read here on Habré article about the generation of music (
google.ru> generation of music site: habrahabr.ru ), I liked it. And then I came across thrashgens (garbage code generators). All this time I was listening to music and noticed that in each composition there are repetitive notes.
For example:
tynts tynts, pam pam, couples pam pam tynts tynts, pam pam, couples pam pam tynts tynts, pam pam, couples pam pamAnd I got the idea to represent all these sequences of sounds, like cycles, in the body of which these sounds are encoded. This is somewhat similar to the "school data compression" algorithms, when before repeating data we write the number of their repetitions.
So, we have a task:
1) Design a rhythm description language
2) Write a compiler in the byte code (sequence of sounds)
3) Digitize and write to wave file
Let's get started
Rhythm description language
After much research into compiler theory, writing lexers and breaking into tokens, I got tired of this. It was decided to use the attention syntax of the Python language. Yes yes exactly. This language supports expressions such as the yield statement.
The topic of yield is quite extensive and if you are not familiar with it and wish to familiarize yourself, then I send you to the article
“How yield works” .
We will continue. So let's agree.
To represent a sound signal (hereinafter - the frame) we will use a function of the form n (diap [0], diap [1]), where n is the numeric number of this function. Where diap is a list or tuple of the initial value and the final range of generated frequencies.
We will use the following expression to encode its calls:
yield "n(diap[0], diap[1])"
To give clarity, here is an example from the generator's exhaust somewhere in the center of the code:
yield "19(400,800)" for _ in range(7): yield "0" yield "20(400,800)" yield "0" yield "21(400,800)" yield "22(400,800)" yield "0"
In this source code there is a yield of "0", meaning that in this place there will be a zero byte sequence to give pauses between frames (so that the music does not come out as a solid sound).
This means (from the ripped out context) the following sequence:
19(400, 800); 0; 20(400, 800); 0; 20(400, 800); 0; 20(400, 800); 0; 20(400, 800); 0; 20(400, 800); 0; 20(400, 800); 0; 20(400, 800); 0; 21(400,800); 22(400,800); 0
Now consider
what the frame function is.
When calling n (diap [0], diap [1]), the key n is added to the associative array with a random value r, where diap [0] <= r <= diap [1]
This will be required for the virtual machine to execute our frame byte code.
Compiling bytecode frames and building in .wav
So, it is time to compile.
How are we going to do this?
First we need to go through our generated code and compile a dictionary in which the keys are the function number and the value is a random variable from the range. You can do this when parsing the code, but you can right during the generation. I have exactly the second option.
Our code for the description of the rhythm (hereinafter KOR) we can present in the form:
code = """ def temp(): """
Warning: the code uses
three double quotes "
Now we store our code as a string. Python has an exec function that allows you to execute code. Let's see its application:
def my_code(cd): namespace = {} exec(cd,namespace) return namespace["temp"]()
When you call my_code and pass it as a parameter string with the code, we get a list generator that generates a bytecode sequence, that is:
print("Compiling...") lst = list(my_code(out.code)) print("Compiled!")
In lst there will be a list of consecutive calls to the frame functions of our COR.
That is, as the same example,
19(400, 800); 0; 20(400, 800); 0; 20(400, 800); 0; 20(400, 800); 0; 20(400, 800); 0; 20(400, 800); 0; 20(400, 800); 0; 20(400, 800); 0; 21(400,800); 22(400,800); 0
We already have an associative array (I generated it during code generation), we just have to go through lst, tear out the function numbers (keys for the dictionary), and get their values by referring to our dictionary.
Here there is this process and digitization with a record in the wave:
Link to wave file music = wave.open('out.wav', 'w') music.setparams((2, 1, freq, 0, 'NONE', 'not compressed')) for i in lst: if (i == "0"): packed_value = wave.struct.pack('h', 0) for _ in range(100): music.writeframes(packed_value) continue key = i[0:i.find("(")] frame = Syntax.struc.num[int(key)] duration = 0.05 samplerate = freq
All code is available on the
githab (
attention : there is a govnod in the generator, since I rewrote this code 20 times and juggled the syntax of the language until I came to an ideal consensus.
There was no refactoring ).
PS Run the Main.py module, saving the generator result in out.py (because of the crutches, only this name is accepted).