-
Notifications
You must be signed in to change notification settings - Fork 0
/
gtpm.py
executable file
·172 lines (154 loc) · 5.43 KB
/
gtpm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) 2013 Danilo de Jesus da Silva Bellini
#
# This is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Created on Tue Sep 10 22:21:34 2013
# danilo [dot] bellini [at] gmail [dot] com
"""
Guitar Tab Problem Maker
"""
from __future__ import print_function
from audiolazy import (sHz, Streamix, adsr, z, gauss_noise, thub, chain, pi,
repeat, str2midi, midi2freq, karplus_strong, saw_table,
zeros, AudioIO, inf, atan, TableLookup, sin_table,
orange, xrange)
from random import shuffle
import pylab
from gtpm.guitar import Guitar
# Input parameters
tuning = "E5 B4 G4 D4 A3 E3"
first_fret = 1
last_fret = 7
fingers = orange(4) # list of used fingers ("imao" fingering from 0 to 3)
width = 79 # monospaced characters
beat = 60 # bpm
notes_per_beat = 4
starting_beats = 4
shuffle_fingers = True
shuffle_per_string = False # Ignored when shuffle_fingers is False
invert_when_backwards = True
# Create notes as pairs (string_index, fret)
guitar = Guitar(tuning)
if shuffle_fingers and not shuffle_per_string:
shuffle(fingers)
num_strings = len(guitar)
notes = []
inv_fingers = fingers[::-1]
for forefinger_fret in xrange(first_fret, last_fret + 1):
inverter = slice(None, None, (-1) ** forefinger_fret) # Alternates asc/desc
if invert_when_backwards:
finger_order = inv_fingers[inverter]
else:
finger_order = fingers
for idx in orange(num_strings)[inverter]:
frets = [x + forefinger_fret for x in finger_order]
if shuffle_fingers and shuffle_per_string:
shuffle(frets)
notes.extend((idx, fret) for fret in frets)
#
# Tablature display
#
# Create tablature columns
# A column is a list of strings for each row
tab_cols = [[gs.tune for gs in guitar], ["|-"] * num_strings]
for string_idx, fret in notes:
column = ["-"] * num_strings
column[string_idx] = "{}".format(fret)
tab_cols.append(column)
tab_cols.append(["-"] * num_strings)
tab_cols[-1] = ["-||"] * num_strings
# Ensure the width is always the same in each row
for col in tab_cols:
length = max(len(el) for el in col)
for idx, el in enumerate(col):
col[idx] = "{1:-<{0}}".format(length, el)
# Separates the tuning heading columns (which should be in every staff)
heading_cols, tab_cols = tab_cols[:2], tab_cols[2:]
heading_length = sum(len(col[0]) for col in heading_cols)
available_width = width - heading_length
# Breaks the columns in blocks (staves) by its length
tab = []
staff_row = []
total_length = 0
for col in tab_cols:
length = len(col[0])
if staff_row and total_length + length > available_width:
if total_length != available_width:
staff_row.append(["-" * (available_width - total_length)] * num_strings)
tab.append(staff_row)
staff_row = []
total_length = 0
staff_row.append(col)
total_length += length
if staff_row:
tab.append(staff_row)
# Prints the tablature
for staff in tab:
print("\n".join("".join(el) for el in zip(*(heading_cols + staff))))
print()
#
# Audio
#
# Useful values from AudioLazy
rate = 44100
s, Hz = sHz(rate)
ms = 1e-3 * s
kHz = 1e3 * Hz
beat_duration = 60. / beat * s # In samples
dur = beat_duration / notes_per_beat # Per note
smix = Streamix() # That's our sound mixture
env = adsr(dur, a=40*ms, d=35*ms, s=.6, r=70*ms).take(inf) # Envelope
# Effects used
def distortion(sig, multiplier=18):
return atan(multiplier * sig) * (2 / pi)
# Intro count synth
filt = (1 - z ** -2) * .5
if starting_beats > 0:
inoisy_stream = filt(gauss_noise()) * env
inoisy_thub = thub(inoisy_stream.append(0).limit(beat_duration),
starting_beats)
inoisy = chain.from_iterable(repeat(inoisy_thub).limit(starting_beats))
smix.add(.1 * s, inoisy)
smix.add(starting_beats * beat_duration - dur, []) # Event timing
# Wavetable lookup initialization
square_table = TableLookup([1] * 256 + [-1] * 256)
harmonics = dict(enumerate([1, 3, 2, 1, .3, .1, .7, .9, 1, 1, .5, .4, .2], 1))
table = sin_table.harmonize(harmonics).normalize()
mem_table = (3 * saw_table + (sin_table - saw_table) ** 3).normalize()
# Notes synth
midi_tuning = str2midi([gs.tune for gs in guitar])
midi_pitches = [midi_tuning[string_idx] + fret for string_idx, fret in notes]
for freq in midi2freq(midi_pitches):
ks_memory = .1 * gauss_noise() + .9 * mem_table(freq * Hz)
ks_snd = distortion(karplus_strong(freq * Hz,
tau=.2 * s,
memory=ks_memory))
tl_snd = .5 * table(freq * Hz) * env
smix.add(dur, .5 * ks_snd + .5 * tl_snd)
# Shows synthesis wavetables
pylab.subplot(2, 1, 1)
pylab.plot(table.table)
pylab.title("Table lookup waveform")
pylab.axis(xmax=len(table) - 1)
pylab.subplot(2, 1, 2)
pylab.plot(mem_table.table)
pylab.title("Karplus-strong memory (besides noise)")
pylab.axis(xmax=len(mem_table) - 1)
pylab.tight_layout()
pylab.show()
# Voila!
smix.add(dur, zeros(.2 * s)) # Avoid ending click
with AudioIO(True) as player:
player.play(smix, rate=rate)