Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additions to the music api (WIP) #16

Draft
wants to merge 38 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ec872ee
Add the NoteF class.
ezio-melotti May 6, 2020
354ad14
Make the Note() == string work.
ezio-melotti May 6, 2020
b163fd2
Remove unused code from the Scale class.
ezio-melotti May 6, 2020
599d2d1
Add a range() method to Scale().
ezio-melotti May 6, 2020
b08dff3
Add constants and symbols for the notes.
ezio-melotti May 6, 2020
ebfc17e
Add the Phrase and Rhythm classes.
ezio-melotti May 6, 2020
a34948e
Add a banjo-sounding gen file.
ezio-melotti May 6, 2020
3c431b4
Add an increasily delirious piece.
ezio-melotti May 7, 2020
d47e6b4
Make the args of the NoteF constructor keyword-only.
ezio-melotti May 7, 2020
8cd48df
Add the NoteFV class.
ezio-melotti May 7, 2020
011fc18
Add the Notes class.
ezio-melotti May 7, 2020
82b5b26
Rename Phrase to Melody and integrate it with Notes.
ezio-melotti May 7, 2020
54efbd6
Update gen files.
ezio-melotti May 7, 2020
f065d78
Add a Pachelbel-canon-sounding gen file.
ezio-melotti May 7, 2020
10c8797
Merge branch 'master' into music-api-2
ezio-melotti May 9, 2020
a0fdaad
Merge branch 'master' into music-api-2
ezio-melotti May 9, 2020
aae54b4
Add find_next_note() and use strings in Scale.notes.
ezio-melotti May 11, 2020
6b2ff84
Reimplement the tuning systems handling.
ezio-melotti May 11, 2020
572cb52
Unify the Note classes.
ezio-melotti May 11, 2020
a7975fd
Fix Melody.from_rhythm_and_notes().
ezio-melotti May 11, 2020
8a0dea8
Fix Scale.range().
ezio-melotti May 11, 2020
7d81746
Fix the scores.
ezio-melotti May 11, 2020
5bfd1d2
Add Melody.from_rtttl() and the rtttl0 score.
ezio-melotti May 11, 2020
8fd2077
Reorder the classes in music.py.
ezio-melotti May 11, 2020
6e496bc
Fix scales tests.
ezio-melotti May 21, 2020
89faeee
Improve scales tests and implementation.
ezio-melotti May 21, 2020
327caf8
Add tests for the Note class.
ezio-melotti May 22, 2020
36f7551
Fix a bug in Melody.from_list().
ezio-melotti Jun 5, 2020
0ac103e
Fix a warning in the Melody class.
ezio-melotti Jun 5, 2020
caa2817
Add the pure_sin instrument.
ezio-melotti Jun 5, 2020
cfd9a65
add sounddevice support
EdmundOgban Oct 26, 2021
64f5e6f
Refactor SoundcardOutput and SounddeviceOutput
EdmundOgban Oct 27, 2021
561e297
Rename terminate to terminated
EdmundOgban Oct 27, 2021
d61eb20
Merge pull request #1 from EdmundOgban/sounddevice-port
ezio-melotti Oct 31, 2021
15b8269
Add the mode name in repr(scale).
ezio-melotti Aug 2, 2021
b2b382d
Add support for the major pentatonic scale.
ezio-melotti Nov 7, 2021
83d2709
Add an improvisation score on a pentatonic major scale.
ezio-melotti Nov 7, 2021
af17851
Add support for more complex rhythms.
ezio-melotti Nov 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions ezio4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import random
from fractions import Fraction
from itertools import cycle

from music import tone, play_sequence, play_drumbase, Scale, Melody, Rhythm, Note
from music import NB, N1, N2, N4, N8, N16, N32, N64, N128
from instruments import kick, snare, hh, bass, violin, banjo


def drill_pattern(seq, pattern):
return ((freq*p, dur) for p, (freq,dur) in zip(cycle(pattern), seq))

def drumify(rhythm):
beats = []
for d in rhythm:
beats.append(1)
beats.extend([0]*(d-1))
return beats

def duration(note, tempo):
return note / (tempo / 240.)


def make_music(synth):
TIMESIG = Fraction(4, 4)
MEASURES = 4
scale = Scale('C', 'melodic minor')
H, M, L = 10, 3, 1
weights = [
[H,L,H,L,H,L,M,H], # Cm
[H,L,M,H,L,H,L,H], # Fmaj
[L,H,L,M,H,L,H,L], # Gmaj
[H,L,H,L,H,L,M,H], # Cm
[H,L,H,L,H,L,H,H], # Cm7
[M,H,L,H,L,H,L,H], # Dm7
[L,H,L,H,H,L,H,L], # Gmaj
[H,L,H,L,H,L,M,H], # Cm
]
scale1 = scale.range('C5', 'D6')
scale2 = scale.range('C4', 'D5')
bass_scale1 = scale.range('C2', 'D3')
bass_scale2 = scale.range('C3', 'D4')
rest = Note.rest()
dominants = [0, 3, 4, 0, 0, 1, 4, 0]
for dom, weight in zip((dominants), (weights)):
# main melody
r1 = Rhythm(TIMESIG, MEASURES//2).generate()
r2 = Rhythm(TIMESIG, MEASURES//2).generate()
rhythm = r1 + r2
notes = [*random.choices(scale1, weight, k=len(r1)),
*random.choices(scale1, weight, k=len(r2))]
melody = rhythm.add_notes(notes)

# melody
rhythm = Rhythm(TIMESIG, MEASURES).generate(N16, N16)
notes = random.choices(scale1, weight, k=len(rhythm))
melody = rhythm.add_notes(notes)

# melody2
rhythm = Rhythm(TIMESIG, MEASURES).generate(N16, N16)
notes = [scale1[dom], rest, scale1[dom+3], rest] * MEASURES * 4
melody2 = rhythm.add_notes(notes)

# bass line 1
rhythm = Rhythm(TIMESIG, MEASURES).generate(N8, N2)
notes2a = random.choices(scale1, weight, k=len(rhythm))*(MEASURES//2)
notes2b = random.choices(scale1, weight, k=len(rhythm))*(MEASURES//2)
bass_line1 = rhythm.add_notes(notes2a + notes2b)

# bass line 2 (slower)
rhythm = Rhythm(TIMESIG, MEASURES).generate(N4, NB, prob=0.7, decay=0)
weight2 = [w if w == H else 0 for w in weight]
notes2 = random.choices(scale2, weight2, k=len(rhythm))
#bass_line2 = Melody.from_list(TIMESIG, zip(notes2, rhythm4))
bass_line2 = rhythm.add_notes(notes2)

# fillers
filler1 = Melody.from_pattern(TIMESIG, MEASURES, [(bass_scale1[dom], N2)])
filler2 = Melody.from_pattern(TIMESIG, MEASURES, [(bass_scale2[dom], N1)])

BPM = random.choice([140, 150])
pattern = random.choice([[1,0,0,0], [1,0,1,0], [0,1,0,1]])
drums = [
#play_drumbase(drumify(rhythm), duration(N8, BPM), snare),
play_drumbase([0,1]*4*MEASURES, duration(N8, BPM), hh),
play_drumbase([1,0]*4*MEASURES, duration(N8, BPM), kick),
play_drumbase(pattern*MEASURES, duration(N4, BPM), snare),
]
synth.play_mix([
*drums,
#play_sequence(melody.to_sequence(BPM), banjo),
play_sequence(drill_pattern(melody.to_sequence(BPM), [0,1]), banjo),
play_sequence(melody2.to_sequence(BPM), banjo),
play_sequence(bass_line1.to_sequence(BPM), bass),
play_sequence(bass_line2.to_sequence(BPM), bass),
play_sequence(filler1.to_sequence(BPM), bass),
play_sequence(filler2.to_sequence(BPM), bass),
])
113 changes: 113 additions & 0 deletions ezio5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import random

from itertools import chain
from fractions import Fraction

from music import play_sequence, play_drumbase, Scale, Melody, Rhythm, Note
from music import NB, N1, N2, N4, N8, N16, N32, N64, N128
from instruments import kick, snare, hh, bass, violin, banjo, metallic_ufo

def duration(note, tempo):
return note / (tempo / 240.)

def untune(seq, bpm, amount):
return ((freq+random.choice([-1,+1])*amount, duration)
for freq, duration in seq.to_sequence(bpm))

def gen_notes(rhythm, scale):
scale_len = len(scale)
index = scale_len // 2
interval = random.choice([1,2,3])
direction = +1
melody_notes = []
while len(melody_notes) < len(rhythm):
if random.random() < .3:
direction *= -1
#choices = []
#if index > interval:
#choices.append(-interval)
#if index < scale_len-interval-1:
#choices.append(+interval)
#index += random.choice(choices)
index += direction * interval
if not (0 <= index < scale_len):
index += -direction * (interval*2)
melody_notes.append(scale[index])
return melody_notes

def make_music(synth):
TIMESIG = TS = Fraction(4, 4)
MEASURES = MS = 4
BPM = 110
distortion = 0
for k, fun in enumerate('CCEEGG'*4):
scale = Scale(fun, 'melodic minor')
melody_scale = scale.range(fun+'4', 'G6')
melody2_scale = scale.range(fun+'3', 'C5')
melody_rhythm1 = Rhythm(TS, MS//4).generate(N16, N4) * 2
melody_rhythm2 = Rhythm(TS, MS//4).generate(N16, N4) * 2
melody_rhythm = melody_rhythm1 + melody_rhythm2
melody = melody_rhythm.add_notes(gen_notes(melody_rhythm, melody_scale))


bass_rhythm = Rhythm(TIMESIG, MEASURES).generate(N8, N2)
bass_line1 = bass_rhythm.add_notes(gen_notes(bass_rhythm, melody_scale))

bass_rhythm = Rhythm(TIMESIG, MEASURES).generate(N4, N2)
bass_line2 = bass_rhythm.add_notes(gen_notes(bass_rhythm, melody2_scale))

filler1 = Melody.from_pattern(TIMESIG, MEASURES, [(Note(fun+'5'), N2)])

if k < 6:
distortion = 0
patterns = [[1,0,0,0,0,0,0,0], [1,0,1,0,1,0,1,0]]
elif k < 12:
patterns = [[0,1,0,1,0,1,0,1], [1,1,0,0,1,1,0,0]]
elif k < 18:
patterns = [[0,1,0,1,1,0,0,1], [1,1,0,0,0,1,1,0], [0,1,1,1,1,0,0,1]]
pattern = chain.from_iterable(random.choice(patterns) for n in range(MEASURES))
if k >= 18:
BPM += 5
distortion += 5
pattern = random.choices([0,1], k=MEASURES*8)

mix = [
#play_drumbase(drumify(rhythm), duration(N8, BPM), snare),
play_drumbase([0,1]*4*MEASURES, duration(N8, BPM), hh),
play_drumbase([1,0]*4*MEASURES, duration(N8, BPM), kick),
play_drumbase(pattern, duration(N8, BPM), snare),
play_sequence(untune(melody, BPM, distortion), banjo),
play_sequence(bass_line1.to_sequence(BPM), bass),
play_sequence(untune(bass_line2, BPM, distortion), violin),
play_sequence(filler1.to_sequence(BPM), bass),
]
if k >= 14:
mix.append(play_sequence(untune(melody, BPM, distortion*1.1), banjo))
if k >= 16:
mix.append(play_sequence(untune(bass_line2, BPM, distortion*1.5), metallic_ufo))
if k >= 18:
mix.append(play_sequence(untune(melody, BPM, distortion*1.3), banjo))
mix.append(play_sequence(untune(bass_line2, BPM, distortion*2), metallic_ufo))
synth.play_mix(mix)
BPM += 7
distortion += 2

synth.play_mix([
play_sequence(untune(melody, BPM, distortion*1.2), bass),
play_sequence(untune(melody, BPM, distortion*1.3), violin),
#play_sequence(untune(melody, BPM, distortion*1.2), metallic_ufo),
play_sequence(untune(melody, BPM, distortion*1.5), bass),
play_sequence(untune(melody, BPM, distortion*1.7), violin),
#play_sequence(untune(bass_line2, BPM, distortion*1.5), metallic_ufo),
])


v = Melody.from_pattern(TIMESIG, MEASURES*2, [(Note(fun+'4'), NB*4)])
synth.play_mix([
play_sequence(untune(v, BPM, distortion*1.2), bass),
play_sequence(untune(v, BPM, distortion*1.3), violin),
play_sequence(untune(v, BPM, distortion*1.2), metallic_ufo),
play_sequence(untune(v, BPM, distortion*1.5), bass),
play_sequence(untune(v, BPM, distortion*1.7), violin),
play_sequence(untune(v, BPM, distortion*1.5), metallic_ufo),
])
101 changes: 101 additions & 0 deletions ezio6.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import random
from fractions import Fraction
from itertools import cycle

from music import tone, play_sequence, play_drumbase, Scale, Melody, Rhythm, Note
from music import NB, N1, N2, N4, N8, N16, N32, N64, N128
from instruments import kick_hard, kick, snare, hh, bass, violin, banjo


def drill_pattern(seq, pattern):
return ((freq*p, dur) for p, (freq,dur) in zip(cycle(pattern), seq))

def drumify(rhythm):
beats = []
for d in rhythm:
beats.append(1)
beats.extend([0]*(d-1))
return beats

def duration(note, tempo):
return note / (tempo / 240.)

def gen_notes(rhythm, scale, index=None):
scale_len = len(scale)
#index = index or scale_len // 2
interval = 1
direction = +1
melody_notes = []
while len(melody_notes) < len(rhythm):
if random.random() < .3:
direction *= -1
#choices = []
#if index > interval:
#choices.append(-interval)
#if index < scale_len-interval-1:
#choices.append(+interval)
#index += random.choice(choices)
index += direction * interval
if not (0 <= index < scale_len):
index += -direction * (interval*2)
melody_notes.append(scale[index])
return melody_notes, index

def make_music(synth):
TIMESIG = Fraction(4, 4)
MEASURES = 1
scale = Scale('C', 'major')
H, M, L = 10, 6, 6
#I-V-vi-iii-IV-I-IV-V
#c g a e f c f g
chords = [1, 5, 6, 3, 4, 1, 4, 5]
indexes = [c-1 for c in chords] # 0-based
weights = [[H,L,H,L,H,L,L,H,L,H,L,H,L,L][i:i+7] for i in indexes]
scale0 = scale.range('C6', 'C7')
scale1 = scale.range('C5', 'C6')
scale2 = scale.range('C4', 'C5')
bass_scale1 = scale.range('C2', 'C3')
bass_scale2 = scale.range('C3', 'C4')
rest = Note.rest()
last_index = 0
for dom, weight in zip((indexes*4), (weights*4)):
# main melody
rhythm = Rhythm(TIMESIG, MEASURES).generate(N4, N4)
notes, last_index = gen_notes(rhythm, scale1, last_index)
melody = rhythm.add_notes(notes)

# bass line 1
rhythm = Rhythm(TIMESIG, MEASURES).generate(N8, N2)
notes = random.choices(scale2, weight, k=len(rhythm))*MEASURES
bass_line1 = rhythm.add_notes(notes)

# bass line 2 (slower)
rhythm = Rhythm(TIMESIG, MEASURES).generate(N4, NB, prob=0.7, decay=0)
weight2 = [w if w == H else 0 for w in weight]
notes2 = random.choices(scale2, weight2, k=len(rhythm))
#bass_line2 = Melody.from_list(TIMESIG, zip(notes2, rhythm4))
bass_line2 = rhythm.add_notes(notes2)

# fillers
filler1 = Melody.from_pattern(TIMESIG, MEASURES, [(scale1[dom], N2)])
filler2 = Melody.from_pattern(TIMESIG, MEASURES, [(scale2[dom], N1)])

BPM = 130
pattern = random.choice([[1,0,0,0], [1,0,1,0], [0,1,0,1]])
drums = [
#play_drumbase(drumify(rhythm), duration(N8, BPM), snare),
play_drumbase([0,1]*4*MEASURES, duration(N8, BPM), hh),
play_drumbase([1,0]*8*MEASURES, duration(N16, BPM), kick_hard),
play_drumbase([0,1]*8*MEASURES, duration(N16, BPM), kick),
play_drumbase(pattern*MEASURES, duration(N4, BPM), snare),
]
synth.play_mix([
*drums,
play_sequence(melody.to_sequence(BPM), banjo),
#play_sequence(drill_pattern(melody.to_sequence(BPM), [0,1]), banjo),
#play_sequence(melody2.to_sequence(BPM), banjo),
play_sequence(bass_line1.to_sequence(BPM), bass),
#play_sequence(bass_line2.to_sequence(BPM), bass),
play_sequence(filler1.to_sequence(BPM), bass),
play_sequence(filler2.to_sequence(BPM), violin),
])
11 changes: 11 additions & 0 deletions instruments.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ def default_tone(freq, duration, samplerate=SAMPLERATE):
return wave * envelope_ms(atk, dcy, sus, rel, len(wave))


@lru_cache()
def pure_sin(freq, duration, samplerate=SAMPLERATE):
ampl = 0.5
wave = sine_wave(duration, freq, ampl, samplerate)
atk = 15
dcy = 20
sus = 0.666
rel = release_time(atk, dcy, len(wave))
return wave * envelope_ms(atk, dcy, sus, rel, len(wave))


@lru_cache()
def bass(freq, duration, samplerate=SAMPLERATE):
ampl = 0.5
Expand Down
Loading