Skip to content

Commit 8586965

Browse files
committed
Add Rhythm model
1 parent c65c211 commit 8586965

File tree

5 files changed

+165
-90
lines changed

5 files changed

+165
-90
lines changed

collections/yes/MWC18Finals.txt

Lines changed: 0 additions & 13 deletions
This file was deleted.

collections/yes/MWC21Finals.txt

Lines changed: 0 additions & 14 deletions
This file was deleted.

config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ class Config():
66
MODE_COLLECTIONS = 1
77

88
plots = True # Whether to plot stuff
9-
wcsv = True # Whether to write results into csv
10-
mode = MODE_RANKED
9+
wcsv = False # Whether to write results into csv
10+
mode = MODE_COLLECTIONS

modules/Rhythm.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import numpy as np
2+
import math
3+
4+
###############################################################################
5+
# Obtains the rhythm calculation for each note in the map by counting the
6+
# amount of notes around the note in a timing window of size bin_size
7+
###############################################################################
8+
bin_size = 1000
9+
10+
# TO-DO: Make the window non-rigid by weighting over a bell curve over (-2,2) instead
11+
12+
13+
def gaussian(x):
14+
return math.exp(-(x**2)/(2))
15+
16+
17+
def bind_500(x): return max(min(x, 500), -500)
18+
19+
20+
def chord_suppression(x):
21+
grace_threshold = 30
22+
slope = +0.2
23+
x = bind_500(x)
24+
return 1 / (1 + math.exp(grace_threshold*slope-slope*x))
25+
26+
27+
def weighted_avg_and_std(values, weights):
28+
"""
29+
Return the weighted average and standard deviation.
30+
values, weights -- Numpy ndarrays with the same shape.
31+
"""
32+
average = np.average(values, weights=weights)
33+
# Fast and numerically precise:
34+
variance = np.average((values-average)**2, weights=weights)
35+
return (average, math.sqrt(variance))
36+
37+
38+
def obtainRhythmCalculation(ho):
39+
rhythm = np.ones(len(ho))
40+
wl = 0 # Current index of the note that first enters in the window
41+
wr = 0 # Same but for last
42+
43+
for i in range(len(ho)):
44+
r = 0
45+
# Find the new first note that is inside the window
46+
while ho[wl].timestamp < (ho[i].timestamp-bin_size/2):
47+
wl += 1
48+
49+
# Fin the new last note that is inside the window
50+
while ho[wr].timestamp < (ho[i].timestamp+bin_size/2) and wr < len(ho)-1:
51+
wr += 1
52+
53+
if wr-wl > 0:
54+
55+
# Raw distances between consecutive notes
56+
distances = np.zeros(wr-wl)
57+
# Gaussian weights assigned depending on distance to i, the center of the window (basically to smooth the calculations like in Density and Manip models)
58+
weights = np.zeros(wr-wl)
59+
60+
# We always use the closest note to i as the note to calculate the weight with (gaussian(((ho[j+1]... vs. gaussian(((ho[j]...)
61+
for j in range(wl, i):
62+
distances[j-wl] = ho[j+1].timestamp-ho[j].timestamp
63+
weights[j-wl] = gaussian(((ho[j+1].timestamp-ho[i].timestamp)/(
64+
bin_size/2))*3)*chord_suppression(ho[j+1].timestamp-ho[j].timestamp)
65+
for j in range(i, wr):
66+
distances[j-wl] = ho[j+1].timestamp-ho[j].timestamp
67+
weights[j-wl] = gaussian(((ho[j].timestamp-ho[i].timestamp)/(
68+
bin_size/2))*3)*chord_suppression(ho[j+1].timestamp-ho[j].timestamp)
69+
70+
# Compute the weighted avg and std deviation, rhythm difficulty for that note is stdev/avg. Perhaps this metric is not correct and favors higher BPM /lower BPM/faster maps?
71+
norm_avg, norm_stdev = weighted_avg_and_std(distances, weights)
72+
rhythm[i] += norm_stdev/(1+norm_avg)
73+
74+
return rhythm

0 commit comments

Comments
 (0)