Code generator based music generator

    Hi, Habrauser! 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 a compiler of this language in wave files and get a rather nice electronic composition.

    Welcome to cat.

    Who can’t wait to listen to the musician now, then online: clickable (the first couple of seconds are unsuccessful, then it’s quite normal).

    Instead of an introduction,

    I read here on a hub article about music generation ( google.ru> music generation site: habrahabr.ru ), I liked it. And then I came across thrash genes (junk code generators). All this time I listened to music and drew attention to the fact that in each composition there are repeating notes.

    For instance:

    tynt tynt, pam pam, param pam pam tynt tynt, pam pam, param pam pamynt tynt, pam pam, param pam pam

    And I had the idea to represent all these sequences of sounds as cycles in the body of which these sounds are encoded. This is somewhat similar to the “school data compression” algorithms, when we write the number of repetitions before the repeated data.

    So, we have a task:

    1) Design a rhythm description language
    2) Write a compiler in bytecode (a sequence of sounds)
    3) Digitize and write to a wave file

    Let's get started.

    Rhythm Description Language


    After much research into the theory of compilers, writing lexers and breaking into tokens, I got tired of this thing. It was decided to use, attention, the syntax of the Python language. Yes yes exactly. This language supports expressions of the form yield statement.

    The yield topic is quite extensive and if you are not familiar with it and want to get acquainted, then I am sending you to the article “How yield works” .

    We will continue. So, let's agree.

    To represent some sound signal (hereinafter referred to as the frame) we will use a function of the form n (diap [0], diap [1]), where n is the numerical number of this function. Where diap is a list or tuple of the initial value and the final range of the generated frequencies.

    We will use an expression of the form to encode its calls:

    yield "n(diap[0], diap[1])"
    


    To give clarity, here is an example from the generator 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 text there is yield “0”, which means that in this place there will be a zero sequence of bytes, to give pauses between frames (so that the music does not come out in a solid sound).

    This means (from the torn out context) the following sequence: Now we will consider what a frame function is. When n (diap [0], diap [1]) is called, the n key with a random value r is added to the associative array, where diap [0] <= r <= diap [1] This is required for the virtual machine to execute our byte code for the frames.

    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







    Compile frames into bytecode and build 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 value from the range. You can do this when parsing the code, or you can directly during generation. I have exactly the second option.

    Our rhythm description code (hereinafter referred to as COR) can be represented as:
    code = """
    def temp():
      тут наш код
    """
    

    Note: the code uses three times the double -quote "

    Now we keep our code as a string in Python is a function of the exec, which allows to execute code Let's use it..:

    def my_code(cd):
        namespace  = {}
        exec(cd,namespace)
        return namespace["temp"]()
    


    When calling my_code and passing it a string with the code as a parameter, we get a list generator that generates a sequence of bytecode, 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 KOR.

    That is, as the same example, we already have an associative array (I generated it during code generation), we just have to go through lst, tear out the numbers of functions (keys for the dictionary) from there, getting their values ​​by accessing our dictionary. Here comes this process and digitization with recording in wave:

    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





    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  # Hz
            samples = duration * samplerate
            frequency = frame #Hz
            period = samplerate / float(frequency)  # in sample points
            omega = N.pi * 2 / period
            xaxis = N.arange(int(period), dtype=N.float) * omega
            ydata = 16384 * N.sin(xaxis)
            signal = N.resize(ydata, samples) # 2-й параметр - скорость
            ssignal = b''
            for i in range(len(signal)):
                ssignal += wave.struct.pack('h', int(signal[i]))  # transform to binary
            music.writeframes(signal)
        music.close()
    



    All code is available on the github ( note : there is shit code in the generator, since I rewrote this code about 20 times and distorted the syntax of the language until I came to the perfect consensus. No refactoring ).

    PS Run the Main.py module, saving the result of the generator in out.py (because of crutches it only accepts this name).

    Only registered users can participate in the survey. Please come in.

    Need a sequel?

    • 65.7% Yes 46
    • 7.1% No 5
    • 27.1% And this article was superfluous 19

    Also popular now: