2.03 Melody by Intervals

In the previous example the melody was completely random and did not sound like a melody for us. In composed music, the melody often only makes small steps of seconds or thirds. So in this Version the melody is created by intervals. Note duration and rests are set by controlled random.

from pyknon.genmidi import Midi
from pyknon.music import Rest, Note, NoteSeq
import numpy as np

Instruments: Available are at lest the 128 General-Midi (GM) Instruments. Depending on the sound-fonts there is a bigger choice. A list of the GM instruments can be found here. https://jazz-soft.net/demo/GeneralMidi.html

major = np.array([-12, -10, -8, -7, -5, -3, -1, 0, 2, 4, 5, 7, 9, 11, 12, 14, 16])
minor = np.array([-12, -10, -9, -7, -5, -4, -2, 0, 2, 3, 5, 7, 8, 10, 12, 14, 15])
var   = np.array([1,2,-1])
var2  = np.array([0,2,-1])
var3  = np.array([0,-1,2])

def fade(start,end,steps):
    fade = np.around( np.linspace(start,end,num=steps))
    fade = fade.astype(int)
    return fade

def ran_duration(duration, prob_duration, melody_len):
    duration= np.asarray(duration)                  # this are the allowed durations of the notes
    prob_duration = np.asarray(prob_duration)       # this are the probabilities how often each will occure
    prob_duration = prob_duration/np.sum(prob_duration)
    rythem = np.r_[np.random.choice(duration, size=melody_len, p=prob_duration)]
    return rythem

def ran_volume(volume, prob_volume, melody_len):
    volume = np.asarray(volume, dtype=int)         # this are the allowed volumes of thenotes
    prob_volume = np.asarray(prob_volume)          # this are the probabilities how often each volume will occure
    prob_volume = prob_volume/np.sum(prob_volume)
    volumes = np.r_[np.random.choice(volume, size=melody_len, p=prob_volume)]
    return volumes

tune_L: In this example we are not going to create the melody directly. Instead we control the interval of the next note of the melody. Starting with a second and a third up and downwards and the Perfect unison. The note durations are quarters for the moment.

The size of each interval is created by controlled random. Every interval has an individual probability.

def tune_L():
    tune_name = 'tune_L'
    np.random.seed(5)
    melody_len = 32
    scale = major
    i_tone_zero = np.argwhere(scale==0)[0]

    interval = np.asarray([-2,-1, 0, 1, 2])            # Possible interval
    prob_interval = np.asarray([2, 4, 1, 4, 2])         # Probability of each interval
    prob_interval = prob_interval/np.sum(prob_interval)
    intervals = np.r_[np.random.choice(interval, size=melody_len, p=prob_interval)]

    imelody1 = i_tone_zero + np.cumsum(intervals)
    melody1 = scale[imelody1]

    rythem1 = ran_duration([1/8, 1/4,1/2], [0,2,0], melody_len)
    volumes1 = ran_volume([0,100], [0,4], melody_len )

    notes1 = NoteSeq( [Note(no+4,octave=5, dur=du, volume=vo) for no,du,vo in zip(melody1,rythem1,volumes1)] )
    instruments = [69]
    notes = [notes1]
    return notes, instruments,tune_name

tune_L
tune_L


tune_M: Same as tune_L but with two voices and creating the int_vlt function

def intvl_melody(intvl, prob_intvl, melody_len):  #Interval Melody
    intvl = np.asarray(intvl)            # Possible interval
    prob_intvl = np.asarray(prob_intvl)         # Probability of each interval
    prob_intvl = prob_intvl/np.sum(prob_intvl)
    intervals = np.r_[np.random.choice(intvl, size=melody_len, p=prob_intvl)]
    imelody = np.cumsum(intervals)
    return imelody


def tune_M():
    tune_name = 'tune_M'
    np.random.seed(10)
    melody_len = 40
    scale = major
    i_tone_zero = np.argwhere(scale==0)[0]

    melody1 = scale[ i_tone_zero + intvl_melody([-2,-1, 0, 1, 2],[2, 4, 1, 4, 2], melody_len)]
    rythem1 = ran_duration([1/8, 1/4,1/2], [1,2,1], melody_len)
    volumes1 = ran_volume([0,120], [0,4], melody_len )

    melody2 = scale[ i_tone_zero + intvl_melody([-2,-1, 0, 1, 2],[2, 4, 1, 4, 2], melody_len)]
    rythem2 = ran_duration([1/8, 1/4,1/2], [1,2,1], melody_len)
    volumes2 = ran_volume([0,100], [0,4], melody_len )

    notes1 = NoteSeq( [Note(no,octave=6, dur=du, volume=vo) for no,du,vo in zip(melody1,rythem1,volumes1)] )
    notes2 = NoteSeq( [Note(no,octave=5, dur=du, volume=vo) for no,du,vo in zip(melody2,rythem2,volumes2)] )
    instruments = [110,70]
    notes = [notes1,notes2]
    return notes, instruments,tune_name

tune_M
tune_M


def gen_midi():
#     squezze into a MIDI framework
    notes, instruments, tune_name = tune_M() #  <--- select a tune  <<--     <<<<<<<<<--- select a tune -----
    nTracks = len(notes)

    m = Midi(number_tracks=nTracks, tempo=120, instrument=instruments)
    for iTrack in range(nTracks):
        m.seq_notes(notes[iTrack], track=iTrack)

    #--- write the MIDI file -----
    midi_file_name = tune_name +'.mid'   # set the name of the file
    m.write(midi_file_name)
    return midi_file_name

Midi: Play and Generate audio-file

External players offered a better sound quality in comparison with python libraries. We use VLC and Musescore

import subprocess
default_soundfont = '/usr/share/sounds/sf3/MuseScore_General.sf3'

def midi_play(midi_in, soundfont= default_soundfont):
    subprocess.call(['cvlc', midi_in , 'vlc://quit'])   # cvlc = vlc without gui

def midi_audio(midi_in, name_out = 'none', soundfont= default_soundfont):
    if name_out == 'none' :
        name_out = midi_in.replace('.mid', '.flac')
    else:
        name_out = name_out + '.flac'
    subprocess.call(['mscore', '-o', name_out, midi_in]) # -o = export as

def midi_png(midi_in, name_out = 'none'):
    if name_out == 'none' :
        name_out = midi_in.replace('.mid', '.png')
    else:
        name_out = name_out + '.png'
    subprocess.call(['mscore', '-o', name_out, '-T', '2', midi_in]) # -o = export as , -T 0 = cut page with 0 pixel
######---  Main  ---######
midi_file_name = gen_midi()

midi_play(midi_file_name)
midi_audio(midi_file_name)
midi_png(midi_file_name)