3.01 First Meteorological Data

  • Starting with just implementing one voice. The meteorological data is first transformed before the differences are taken and so created the intervals.
  • Functions which are no longer part of this development step are exported to the music_generation.py file. The file is found at the end of the page.
from pyknon.genmidi import Midi
from pyknon.music import Rest, Note, NoteSeq
import music_generation
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as plticker
from datetime import date

Transform Meteorological Data

def scale(a):    return (a-a.min())/(a.max()-a.min())
h24 = 6*24
h72 = 3*h24

def read_meteo_data(fName):
    colNames = ['Stao','time', 'Flash', 'p_QNH', 'T_2m', 'Precip', 'H_rel', 'V_wind']
    df = pd.read_csv(fName,sep=';', skiprows=3, names=colNames, na_values='-')
    print(df.head())
    return df

fPath = '/mnt/daten/04_Schule/42_Kanti/Matrua/Music_generation/Organisation/MeteoSchweiz/Daten/'
fName = 'order_74678_data.txt'
dM = read_meteo_data(fPath+fName)

#---- Parameter bestimmen -----------
NT, MP = dM.shape
print('-----------------')
print('NT, MP', NT, MP)
  Stao          time  Flash  p_QNH  T_2m  Precip  H_rel  V_wind
0  KLO  201908280000      0  968.5  19.6     0.0   90.1     0.6
1  KLO  201908280010      0  968.5  19.3     0.0   93.0     0.6
2  KLO  201908280020      0  968.6  19.4     0.0   90.6     0.7
3  KLO  201908280030      0  968.7  19.6     0.0   90.3     0.7
4  KLO  201908280040      0  968.7  18.7     0.0   95.6     0.5
-----------------
NT, MP 2016 8

meteorological data has noise. Also are the value difference in the 10 min measurement interval not that big.

w defines the sampling rate. But k also defines the size of the moving average. With this method every value is used once.

As we want intervals, the difference is taken. But the intervals are first multiplied with a factor to have a suitable range of intervals.

k=4  #Column number
w = 6 # how many values are used for the mean
Yw  = np.array(dM[dM.columns[k]].rolling(window=w,center=True).mean())
Yw= Yw[0:1000:w]
trans = np.diff(Yw)[1:]
trans = trans*1.3
trans = np.round(trans)
#trans = np.nancumsum(trans)
trans = trans.astype(int)
print(trans)
#print(len(trans))

plt.plot(trans)
#t(trans,bins=50)
[ 1  1  0  0 -1  2  3  2  1  1  1  1  0 -1 -1 -1 -3 -2 -1  1 -1 -1  0 -1
 -1 -1  0 -1  1  2  1  0  2  0  0  2  1  0  0  0 -2 -4 -2  0 -2 -1 -1  0
 -1 -1  0 -1  3  2  3  2  2  2  1  2  0  0  0 -1 -3 -4 -3 -1 -1 -1  0 -1
 -1 -1  0  0  2  3  3  3  2  2  1  1  2 -1 -2 -1 -1 -3 -2 -1 -1 -2 -1  0
  0  0  2  0  1  1  0 -1  2  1  2  1  1  0 -2 -2 -1 -2 -2  0  0  0  0  0
  0  0  0  0  0  0  1  0  0 -1  1  0  0  0  0 -1  0 -1 -2 -1 -2  0 -1 -2
 -1  0  0  1  1  1  2  3  3  3  2  1  0  0  0 -1 -3 -5 -2 -1 -2]
[<matplotlib.lines.Line2D at 0x7efc246d1048>]
../../_images/output_5_2.png

Chords and scales

major = np.array([ 0, 2, 4, 5, 7, 9, 11])
minor = np.array([ 0, 2, 3, 5, 7, 8, 10])
C7 = np.array([ 0, 4, 7, 10])
CM7 = np.array([ 0, 4, 7, 11])
Cm7 = np.array([ 0, 3, 7, 10])
Cm  = np.array([ 0, 3, 7])
C   = np.array([ 0, 4, 7])
bass= np.array([ 0])

Tune T

The intervals of the meteorological data are scaled and played

def meteo_melody(met_intvl, pattern, start_note, a_range, notenr, rythem):
    melody = np.zeros(notenr, dtype=int)
    cum_rythem = np.cumsum(rythem) *4
    cum_rythem = np.concatenate(([0],cum_rythem))[:-1] # add 0 at beginig remove last element
    scale_change = pattern[:,0]
    scale_nr =0
    scale = pattern[scale_nr,1]
    melody[0] = scale[i_last_note(start_note,scale)]
    cummelody = i_last_note(start_note,scale)+np.nancumsum(met_intvl)
    #print(cummelody)

    for npn in range(1, notenr):  #npn: note per note (index)
        scale_nr = np.ravel(np.argwhere(scale_change <= cum_rythem[npn-1])) [-1]
        scale = pattern[scale_nr,1]
        inote_next = cummelody[npn-1]
        #print(inote_next,scale)
        melody[npn] = scale[inote_next]
    #print(melody)
    return melody
def tune_T():
    tune_name = 'tune_T'
    #np.random.seed(23)
    bar, bpb = 12, 4  # bar: Takt , bpb: beat per bar
    melody_len = bar * bpb


    #scales = [[1,CM7],[1,Cm7+9],[1,Cm7+2],[1,C7+7]] #rythem Change
    #scales = [[4,C7],[2,C7+5],[2,C7],[1,C7+7],[1,C7+5],[2,C7]] # Blues
    scales = [[8,major]]
    pattern = pattern_gen(scales, melody_len)

    range_1 = liniar_range(44,51,70,76)
    rythem1, notenr_1 = ran_duration([1/8, 1/4,1/2], [1,3,1], melody_len)
    melody1 = meteo_melody(trans,pattern, 60, range_1, notenr_1, rythem1)
    volumes1 = ran_volume([0,120], [1,8], notenr_1 )
    notes1 = NoteSeq( [Note(no,octave=0, dur=du, volume=vo) for no,du,vo in zip(melody1,rythem1,volumes1)] )


    #plot_range([range_1],['range_1'],tune_name)
    instruments = [51]
    notes = [notes1]
    return notes, instruments,tune_name

tune_T
tune_T


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

Generate Midi and Audio file

def gen_midi():
#     squezze into a MIDI framework
    notes, instruments, tune_name = tune_T() #  <--- 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
######---  Main  ---######
midi_file_name = gen_midi()

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

External Music_Generation library

This library changes from version to version. New or changed code is first explained above. This is a copy of music_generation.py

# [[[[[[[[[[[[[[[[[[[   -- Functions for Music Generation --    ]]]]]]]]]]]]]]]]]]]

def scale_create(tones):
    tones = np.asarray(tones)   # tones which form chord or scale in the first octave (0-11)
    if any(tones > 11):             # tones over one octave?
        tones = np.mod(tones,12)    # set the thones in one octave
        tones = np.sort(tones)      # sort the tones new
        tones = np.unique(tones)    # remove duplicate tones
    octave = np.repeat( np.linspace(0,108, num=10), len(tones))
    scale = np.add( octave, np.tile(tones, 10)) # add element wise octave and note
    return scale.astype(int)
    
def fade(start,end,steps): 
    fade = np.around( np.linspace(start,end,num=steps))
    fade = fade.astype(int)
    return fade

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

#   liniar_range: Generates an range in which the instrument can play. 
def liniar_range(r_start, r_top, r_edge, r_end): # acceptance range of the instrument 
    h = 100 # hight of acceptance function
    a_range = np.zeros(121, dtype=int)  # only to midi =120 as 127 is not a complete octave
    np.put(a_range, range(r_start,r_top),  np.linspace(0,h, num=(r_top -r_start)) )
    np.put(a_range, range(r_top, r_edge),  np.linspace(h,h, num=(r_edge-r_top  )) )
    np.put(a_range, range(r_edge, r_end),  np.linspace(h,0, num=(r_end -r_edge )) )
    return a_range
    
#   i_last_note: finds de i value of the last not in the actual scale.
def i_last_note(note, scale):
    i_note = (np.abs(scale - note)).argmin()
    return i_note

#   intvl_next is a modification of intvl_melody. But it does only creats one interval and not an array/melody in one time.
def intvl_next(intvl, prob_intvl):  #singel interval
    intvl = np.asarray(intvl)            # Possible interval
    prob_intvl = np.asarray(prob_intvl)         # Probability of each interval
    prob_intvl = prob_intvl/np.sum(prob_intvl)
    interval = np.random.choice(intvl, size=1, p=prob_intvl)
    return interval[0]

#   acceptance: accepts and refuses proposed nots with Metropolis-Hasting Algorythem.
#     x is the value in the aceptance range of the current note, while x_new is it from the proposoal note
def acceptance(x, x_new):
    if x_new < 1:
        if x < 1: print('start_note not in range') ; x = start_note_not_in_range
    quot = x_new/x
    if quot >= 1: return True
    if np.random.uniform(0,1)< quot: return True
    else: return False
    
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)
    cumsum, melody_len, rythem = 0, melody_len/4 , np.asarray([])  #melody_len/4 as note values are quarter
    while cumsum < melody_len:
        note_len = np.random.choice(duration, p=prob_duration)
        cumsum = cumsum + note_len
        rythem = np.append(rythem,note_len)
    return rythem , len(rythem)

#   pattern_gen takes the chord pattern (scales): it reapeats the pattern as long the melody is, and generates the beat number where the chords change.
def pattern_gen(scales,melody_len):
    scales = np.asarray(scales)
    bpb = 4  # beats per bar
    factor = int(np.trunc(melody_len/(np.sum(scales[:,0]) * bpb)) + 1) # factor rounded up: how many times is the pattern used
    change_times = np.cumsum(np.tile(scales[:,0],factor)) * bpb        # create change time list with factor
    change_times = np.concatenate((np.asarray([0]),change_times))[:-1] # add 0 at beginig remove last element
    
    for i in range(len(scales)):         # send scales to scale_create
        scales[i,1] = scale_create(scales[i,1])
    pattern = np.tile(scales,(factor,1))   # tile the scales as long the melody is
    pattern[:,0] = change_times    #insert change_times into scales
    pattern = np.delete(pattern, np.argwhere(pattern[:,0] >= melody_len) ,0) # remove unneeded scales
    return pattern

   
def acceptance_melody(intvl, prob_intvl, pattern, start_note, a_range, notenr, rythem):
    melody = np.zeros(notenr, dtype=int)
    cum_rythem = np.cumsum(rythem) *4
    cum_rythem = np.concatenate(([0],cum_rythem))[:-1] # add 0 at beginig remove last element
    scale_change = pattern[:,0]
    scale_nr =0
    scale = pattern[scale_nr,1]
    melody[0] = scale[i_last_note(start_note,scale)]
    
    for npn in range(1, notenr):  #npn: note per note (index)      
        scale_nr = np.ravel(np.argwhere(scale_change <= cum_rythem[npn-1])) [-1]     
        scale = pattern[scale_nr,1]

        accept = False    
        while not accept:       # aslong acept == False
            inote = i_last_note(melody[npn-1],scale)
            inote_next = inote + intvl_next(intvl, prob_intvl)         # add current not with Proposition
            accept_val = a_range[[melody[(npn-1)],scale[inote_next]]]  # get acceptance values
            accept = acceptance(accept_val[0],accept_val[1])
        melody[npn] = scale[inote_next]
    return melody

#   plot_range: plot all ranges together
def plot_range(ranges,labels,title):
    fig, ax = plt.subplots()
    plt.xlabel('Midi Note')
    plt.ylabel('Acceptance')
    plt.title(title)

    for a_range, lab in zip(ranges,labels):
        ax.plot(range(121), a_range,label= lab )
    ax.vlines(x=np.linspace(0,108, num=10), ymin=0, ymax=10, color='grey', label='Octaves',linewidth=1) # plot octaves
    plt.legend()
    plt.show()
    
    
    
    
# [[[[[[[[[[[[[[[[[[[   -- Functions for Meteo Transformation --    ]]]]]]]]]]]]]]]]]]]




# [[[[[[[[[[[[[[[[[[[   -- Functions for Sound generation --    ]]]]]]]]]]]]]]]]]]]

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', '--soundfont', '/home/viturin/-vitis/Documents/MuseScore2/Soundfonts/Compifont_13082016.sf2'])   # 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 2 = cut page with 2 pixel