-
Notifications
You must be signed in to change notification settings - Fork 19
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
Feature: add a clef_map
#381
Comments
Hi! Good idea! I don't know whether it fits the |
It's not yet tested though. |
Currently, the map API has the following:
As there should be as many clefs as there are staffs, a Returning a dictionary mapping directly |
That's a good idea! The actual clef encoding might require some more details because even though there are only few standard clefs, they can have varying properties like the line they are placed on or octave changes. Something like |
Yes, adding those other properties make sense. However, why this shape? The example you gave can't be directly converted as a numpy array because of shapes: [
[staff_number1, clef_sign1, clef_line1, clef_octave1],
[staff_number2, clef_sign2, clef_line2, clef_octave2],
] I haven't digged down to how staff numbers are managed in partitura, but if you always number them sequentially, the [
[clef_sign1, clef_line1, clef_octave1],
[clef_sign2, clef_line2, clef_octave2],
] with the first line representing implicitly the first staff, and so on.
Is it a problem? For a 2 staves part, I would expect a shape |
yes that shape is better! Variable output size not a breaking problem, it's something to be aware of and adapt one's code to, especially in the case of bulk processing. |
By the way, I have a more general question about those map functions. Why did you choose to return numpy arrays rather than the most recent TimeSignature/KeySignature object? This would allow to directly use and understand the returned object, rather that having to understand what each dimension represents. I feel like something similar to this might be easier to understand for the user: def time_signature_map(self):
[......]
interpolator = interp1d(
tss[:, 0],
tss[:, 1:],
axis=0,
kind="previous",
bounds_error=False,
fill_value="extrapolate",
)
def map_function(time: int) -> TimeSignature:
ts = iterpolator(time)
return TimeSignature(beats=ts[0], beat_type=ts[1])
return map_function and same for def key_signature_map(self):
[......]
interpolator = interp1d(
kss[:, 0],
kss[:, 1:],
axis=0,
kind="previous",
bounds_error=False,
fill_value="extrapolate",
)
def map_function(time: int) -> KeySignature:
ks = iterpolator(time)
return KeySignature(fifths=ts[0], mode=pt.utils.music.key_int_to_mode[1])
return map_function |
Here is a simple implementation of what available_clefs = ["G", "F", "C", "percussion", "TAB", "jianpu", "none"]
def clef_name_to_int(clef: str) -> int:
return available_clefs.index(clef)
def clef_int_to_name(clef: int) -> str:
return available_clefs[clef]
def clef_map(part: pt.score.Part) -> Callable[[int], pt.score.Clef]:
"""A function mapping timeline times to the clefs at that time. The function can take
scalar values or lists/arrays of values.
Returns
-------
function
The mapping function
"""
clefs_objects = np.array(
[
(c.start.t, c.staff, clef_name_to_int(c.sign), c.line, c.octave_change if c.octave_change is not None else 0)
for c in part.iter_all(pt.score.Clef)
]
)
# clefs: (time, staff_id, clef, line, octave_change)
interpolators = []
for s in range(1, part.number_of_staves + 1):
staff_clefs = clefs_objects[clefs_objects[:, 1] == s]
if len(staff_clefs) == 0:
# default treble clef
staff, clef, line, octave_change = s, clef_name_to_int("G"), 2, 0
warnings.warn(
"No clefs found on staff {}, assuming {} clef.".format(s, clef)
)
if part.first_point is None:
t0, tN = 0, 0
else:
t0 = part.first_point.t
tN = part.last_point.t
staff_clefs = np.array(
[
(t0, staff, clef, line, octave_change),
(tN, staff, clef, line, octave_change),
]
)
elif len(staff_clefs) == 1:
# If there is only a single clef
staff_clefs = np.array([staff_clefs[0, :], staff_clefs[0, :]])
elif staff_clefs[0, 0] > part.first_point.t:
staff_clefs = np.vstack(
((part.first_point.t, *staff_clefs[0, 1:]), staff_clefs)
)
interpolators.append(
interp1d(
staff_clefs[:, 0],
staff_clefs[:, 1:],
axis=0,
kind="previous",
bounds_error=False,
fill_value="extrapolate",
)
)
def collator(time: int) -> pt.score.Clef:
return np.array([interpolator(time) for interpolator in interpolators])
return collator This will return things like: clef_map_function = clef_map(part)
print("Before clef change on staff 2:")
print(clef_map_function(1919))
print("After clef change on staff 2:")
print(clef_map_function(1920))
# Before clef change on staff 2:
# [[1. 0. 2. 0.]
# [2. 0. 2. 0.]]
# After clef change on staff 2:
# [[1. 0. 2. 0.]
# [2. 1. 4. 0.]] (Not the easiest to read, but might be easily adapted like my previous message to return a list of If you're interested in this implementation, I can open a PR to add it. |
Very nice addition! Yes, please open a PR so we can straighten out some details. |
I think this is historically motivated, we initially had only time mapping between different (musical) units, then some additions were added for often used score attributes (meter, key) but with the use case being to retrieve values which can be packed into numerical arrays. The note features are in some ways the generalization of that idea. When handling scores itself and their objects rather than using partitura as a feature extractor towards numerical representations, we use the internal iterators, e.g. if you had an individual note or timepoint and want to retrieve its encompassing measure object:
but of course that's a bit cumbersome and not easily accessible for library users. |
Done! See #384
Correct me if I'm wrong, but that would iterate over objects until the previous |
part.key_signature_map
andpart.time_signature_map
are very useful, as they allow retrieving the musical context in any part of the score.However, I feel that a
part.clef_map
is missing, that would get the current clef in each staff so far. I'm not sure what the best interface would be for that, as multiple values could be returned in case of multi-staff scores (oppositely as with key/time signatures).Like this, it would allow for example to know that at time 280, there is a treble key on first staff, and a bass key on second staff. It would be very helpful in contexts of retrieving the full context at each point of the score (e.g. see #380).
The text was updated successfully, but these errors were encountered: