Featured
- Get link
- X
- Other Apps
Python Programming 5 Rhythm (length of notes) created by Markov transition matrix
In the melody made by Fourier series, we got rhythms (duration of notes), when Fourier series function value changes.
Now, I added rhythm algorithm based on Markov chain with transition matrix.
It is quite simple:
if your first beat is [1/4 length note], then with some probabilities next beat is selected, e.g. [1/4] (probability 50%) , [1/8, 1/8] (30%) or [1/8, 1/16, 1/16] (10%). These probabilities for all cases are shown in a transition matrix.
Under 1/16 note length basis, we write [1/4] as [1,0,0,0]. [1/8, 1/8] as [1,0,1,0] or [1/8, 1/16, 1/16] as [1,0,1,1]. Note on timing is 1 and Note continue is 0. (For illustration, Rest is not considered.)
Below table is a Markov transition matrix (count basis (you can divided by sum of row to get probabilities)) from Bach music. I used Bach music because I love it and also it uses very simple rhythms (mostly 1/16 note length basis).
Just a sample. https://www.youtube.com/watch?v=1X4RwFw0hOY
You can make more with below.
---------------------------------------------------------------------------------------------
# Blog 5 import mido import time from mido import Message, MidiFile, MidiTrack, MetaMessage from random import random import numpy as np import matplotlib.pyplot as plt from blog_4_matplotlib_graph import * # please import play_plot_MIDI # port open ports = mido.get_output_names() port = mido.open_output(ports[0]) ######################################################################## # function for melody pattern def fx1(x, a, cycle): x1 = 2*np.pi/cycle*x y = a[0] * np.sin(x1) + a[1] * np.sin(2*x1) + a[2] * np.sin(4*x1) + a[3] * np.sin(8*x1) + a[4] * np.sin(16*x1) + a[5] * np.sin(32*x1) return y # find nearlest pitch on scale def nearest_on_scale(p,scale): if abs(p-scale[sum([p>x for x in scale])-1]) <= abs(p-scale[sum([p>x for x in scale])]): pOnS = scale[sum([p>x for x in scale])-1] else: pOnS = scale[sum([p>x for x in scale])] return pOnS # add chord notes to track def addChord(track,channel,pitch_list, duration, velocity): for i in range(len(pitch_list)): track.append(Message('note_on', channel = channel, note=pitch_list[i], velocity=velocity, time=0)) track.append(Message('note_off', channel = channel, note=pitch_list[0], velocity=0, time=beat_to_tick(duration))) for i in range(1,len(pitch_list)): track.append(Message('note_off', channel = channel, note=pitch_list[i], velocity=0, time=0)) # convert beat to MIDI time scale (1 beat to 480 ticks) def beat_to_tick(dur): return int(dur * 480 +0.5) # rythm transition matrix from Bach Concerto in A minor BWV1041 1st movement # with some adjustments rhythm_patterns = [[0, 0, 0, 0], [1, 0, 0, 0], [0, 1, 0, 0], [1, 1, 0, 0], [0, 0, 1, 0], [1, 0, 1, 0], [0, 1, 1, 0], [1, 1, 1, 0], [0, 0, 0, 1], [1, 0, 0, 1], [0, 1, 0, 1], [1, 1, 0, 1], [0, 0, 1, 1], [1, 0, 1, 1], [0, 1, 1, 1], [1, 1, 1, 1]] transition = np.array([[ 0, 9, 0, 0,17, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1], [ 26,34, 0, 0,26, 4, 0, 0, 0, 0, 0, 0,11, 2,12, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0,28, 0, 0, 2,28, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1], [ 5,26, 0, 0,15,106, 0, 3, 0, 0, 0, 0, 7, 7, 8, 9], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,10, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 6], [ 0, 3, 0, 0, 0,13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3], [ 0, 3, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [ 0, 5, 0, 0, 0, 8, 0, 7, 0, 0, 0, 1, 0, 0, 0,11], [ 0, 7, 0, 0, 0,20, 0, 0, 0, 0, 0, 5, 0, 0, 0,20]], dtype = 'float') # select next rhythm by Markov transition matrix def markov_transition(r, transition): return np.random.choice(np.arange(0,16), p=transition[r]/sum(transition[r])) ######################################################################## # create_melody def create_melody(length, scale, chord_pitch_list): x = np.linspace(0, length, 481) # Fourier series cycle = 32 beats cycle = 32 # Fourier series coefficient random creation a = [1/((i+1)**0.5)*(random()-0.5) for i in range(6) ] # create waves y = [72 + 12 * fx1(xi, a, cycle) for xi in x] # pitch center = C5, pitch range [1 octave x max/min(fx1)] # Melody: pitch = Fourier, rhythm = Markov pitch_list = [] duration_list = [] rhythm_no = 1 x1 = 0 while sum(duration_list) < x[-1]: # base duration of a note is 1/4 duration = 0.25 # selected rhythm pattern selected_rhythm = rhythm_patterns[rhythm_no] # next rhythm number based on Markov transition rhythm_no = markov_transition(rhythm_no, transition) for i in range(4): # pitch on y at time x1 p0 = 72 + 12 * fx1(x1,a,cycle) # find nearest pitch on scale pitch = nearest_on_scale(p0, C_major_scale) x1 += duration # if rhyth is off or too many repeats of same pitch, make duration longer if selected_rhythm[i] == 0 or (len(pitch_list) > 2 and (pitch_list[-1] == pitch_list[-2] and pitch_list[-1] == pitch)): duration_list[-1] += duration # otherwise add new note else: pitch_list.append(pitch) duration_list.append(duration) # aligned to chord pitches for i in range(len(pitch_list)): if duration_list[i] >= 1.5: ch_i = int(sum(duration_list[0:i])/4) p1 = pitch_list[i] chord_pitches = [p + 12*oct for p in chord_pitch_list[ch_i] for oct in [-2,-1,0,1,2]] chord_pitches.sort() pitch_list[i] = nearest_on_scale(pitch_list[i], chord_pitches) return pitch_list, duration_list, x, y ######################################################################## # create new MIDI file score = MidiFile() track0 = MidiTrack() # tempo track1 = MidiTrack() # instrument 1 track2 = MidiTrack() # instrument 1 score.tracks.append(track0) # tempo score.tracks.append(track1) # melody score.tracks.append(track2) # chord track1.append(Message('program_change', channel = 0, program=40,time=0)) # melody program:40 = Violin (General MIDI) track2.append(Message('program_change', channel = 1, program=0,time=0)) # chord program:0 = Piano # set tempo bpm = 120 # beat per minutes track0.append(MetaMessage('set_tempo', tempo=int(1000000*60/bpm), time=0)) # C major scale C_major_scale = [p + octave * 12 for octave in [0,1,2,3,4,5,6,7,8,9] for p in [0, 2, 4, 5, 7, 9, 11]] # chord progression on C_major chord_list = ["C", "Em", "G", "CM7onE","C", "Em", "G", "CM7onE"] chord_pitch_list = [[60, 64, 67], [64, 67, 71], [67, 71, 74], [72, 64, 67, 71],[60, 64, 67], [64, 67, 71], [67, 71, 74], [72, 64, 67, 71]] # from blog 2 chord progression # add melody length = 32 pitch_list, duration_list, x, y = create_melody(length, C_major_scale, chord_pitch_list) velocity = 70 for i in range(len(pitch_list)): track1.append(Message('note_on', channel = 0, note=pitch_list[i], velocity=velocity, time=0)) track1.append(Message('note_off', channel = 0, note=pitch_list[i], velocity=0, time=beat_to_tick(duration_list[i]))) # add chords for i in range(len(chord_pitch_list)): channel = 1 addChord(track2, channel, chord_pitch_list[i], 4, 70) ######################################################################## # play MIDI file play_plot_MIDI(port, score)
- Get link
- X
- Other Apps
Popular Posts
Python programming 1 Basics of Mido for MIDI handling
- Get link
- X
- Other Apps
Python Programming 4 Piano Roll Animation of MIDI file by Matplotlib
- Get link
- X
- Other Apps
Comments
Post a Comment