paulrosen / abcjs

javascript for rendering abc music notation
Other
1.89k stars 281 forks source link

Find the y of a note to draw a different symbol without messing with the abc notation #860

Closed leocaseiro closed 1 year ago

leocaseiro commented 1 year ago

Hi there,

I am trying to find the y of a specific note. I want to draw a different symbol, but I do not want to change the ABC notation.

I am building a "rhythm game" that will display a "red cross" when the player plays the wrong note in the wrong tempo.

If user plays for example, the note c, I would like to draw an X in svg but in the correct position for y. Is there a public API to get the position based on the input?

eg:

getY('c'); //

The furthest I've got it is to draw a symbol next to a different note. https://codesandbox.io/s/drum-abcjs-draw-circle-and-cross-otkn9p?file=/src/index.js

paulrosen commented 1 year ago

I didn't see anything in your codesandbox but here's the idea. I created some random music with the {add_classes:true" option and looked at the SVG for one of the notes. Here is what was created:

<g class="abcjs-note abcjs-d0-25 abcjs-p4 abcjs-l1 abcjs-m0 abcjs-mm4 abcjs-v0 abcjs-n4" 
  fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="25">
    <path data-name="G" d="M 162.73384874353874 245.2695c 0.36 -0.03 1.2 0 1.53 0.06c 1.17 0.24 1.89 0.84 2.16 1.83c 0.06 0.18 0.06 0.3 0.06 0.66c 0 0.45 0 0.63 -0.15 1.08c -0.66 2.04 -3.06 3.93 -5.52 4.38c -0.54 0.09 -1.44 0.09 -1.83 0.03c -1.23 -0.27 -1.98 -0.87 -2.25 -1.86c -0.06 -0.18 -0.06 -0.3 -0.06 -0.66c 0 -0.45 0 -0.63 0.15 -1.08c 0.24 -0.78 0.75 -1.53 1.44 -2.22c 1.2 -1.2 2.85 -2.01 4.47 -2.22z"></path>
    <path d="M 166.45 222.19L 166.45 248.03L 165.45 248.03L 165.45 222.19z" 
      class="abcjs-stem" data-name="stem">
    </path>
</g>

To find a particular note, for instance, this is measure 4, note 4 (counting from zero) you can target the CSS like this:

const note = document.querySelector(".abcjs-mm4.abcjs-n4")

Then to get just the note head look for data-name="x" where x is the note name. Notice that this example also has a stem. There could be other things like flags, accidentals decorations, etc.

leocaseiro commented 1 year ago

Thank you for your reply, but search for the css only works after drawing. I want to do the opposite, I want to draw myself depending on the note.

I want to know what is the correct x/y to draw a note, but I don't want to change the abc notation.

So if my notation is:

c c a c

I want to find the x,y of b, for example. This is going to be a kind of game/tutorial. So when the user play the note, I need to tell them the note is correct/wrong. And when it's wrong, I want to draw an x in the staff, where the note pressed would appear.

paulrosen commented 1 year ago

How does the user know what note they are supposed to play? Wouldn't it be drawn on the screen already?

In any case, there isn't a way to do what you are asking currently, but doing it after drawing would be pretty fast and I don't think would cause a visible flash.

You could even make the opacity 0 until you are finished to make sure.

I might not be understanding the effect you are after.

leocaseiro commented 1 year ago

Sorry, I am making an interactive learning tool that the user can input with midi.

So the user can connect an e-drum or a midi keyboard which inputs a note.

I want to show the feedback while the user inputs a note.

If the user inputs the correct note, I'll circle the note(easy to be done already).

Now, I want to show a cross when the note is incorrect. So if they user press b, instead of c, I want to draw a cross on the exactly place that the b would appear in the staff.

Here is an example attached of what I intend to achieve:

leocaseiro commented 1 year ago

Hi @paulrosen, I think I wasn't clear that the user will play the inputs after we already generated the sheet music.

I am making a small application inspired by a discontinued software by Roland called DT-1 Drum Tutor. You can see an example of exactly what I would like to display on the sheet music if you watch this demo starting at 3:06: https://youtu.be/YU_Y1m9YLXw?t=186

paulrosen commented 1 year ago

Cool! Then what I would do is get the note like I mentioned above. You can get the position of it with note.getBBox(). Then you can insert another element at the coordinates that you want. You can get the music with const music =document.querySelector("#paper svg"). Then create the element you want and domusic.appendChild(yourElement)`

leocaseiro commented 1 year ago

So, I can easily find the y when the note is displayed on the staff (using CSS, of getting the notePositions), thank you for that, but what if the note is not present in the SVG? Maybe I should ask, how do I set the Y, instead of get? Does that make more sense?

Consider the following markup:

let abcString = `
X:1
L: 1
%%stretchlast
A A
`;

var visualOptions = {
  add_classes: true
};

var visualObj = abcjs.renderAbc("app", abcString, visualOptions);

This will generate the following SVG:

<svg xmlns:xlink="http://www.w3.org/1999/xlink" role="img" fill="currentColor" stroke="currentColor" aria-label="Sheet Music" width="770" height="94.617">
  <style>
    .abcjs-dragging-in-progress text,
    .abcjs-dragging-in-progress tspan {
      user-select: none;
    }
  </style>
  <title>Sheet Music</title>
  <g>
    <g class="abcjs-staff abcjs-l0 abcjs-v0">
      <path d="M 15 36.64 L 755 36.64 L 755 37.34 L 15 37.34 z" stroke="none" fill="currentColor" class="abcjs-top-line"></path>
      <path d="M 15 44.39 L 755 44.39 L 755 45.09 L 15 45.09 z" stroke="none" fill="currentColor"></path>
      <path d="M 15 52.14 L 755 52.14 L 755 52.84 L 15 52.84 z" stroke="none" fill="currentColor"></path>
      <path d="M 15 59.89 L 755 59.89 L 755 60.59 L 15 60.59 z" stroke="none" fill="currentColor"></path>
      <path d="M 15 67.64 L 755 67.64 L 755 68.34 L 15 68.34 z" stroke="none" fill="currentColor"></path>
    </g>
    <g class="abcjs-staff-extra abcjs-clef abcjs-l0 abcjs-m0 abcjs-mm0 abcjs-v0" fill="currentColor" stroke="none" data-name="staff-extra clef">
      <path data-name="clefs.G" d="M 29.689999999999998 22.832000000000008c 0.09 -0.09 0.24 -0.06 0.36 0c 0.12 0.09 0.57 0.6 0.96 1.11c 1.77 2.34 3.21 5.85 3.57 8.73c 0.21 1.56 0.03 3.27 -0.45 4.86c -0.69 2.31 -1.92 4.47 -4.23 7.44c -0.3 0.39 -0.57 0.72 -0.6 0.75c -0.03 0.06 0 0.15 0.18 0.78c 0.54 1.68 1.38 4.44 1.68 5.49l 0.09 0.42l 0.39 0c 1.47 0.09 2.76 0.51 3.96 1.29c 1.83 1.23 3.06 3.21 3.39 5.52c 0.09 0.45 0.12 1.29 0.06 1.74c -0.09 1.02 -0.33 1.83 -0.75 2.73c -0.84 1.71 -2.28 3.06 -4.02 3.72l -0.33 0.12l 0.03 1.26c 0 1.74 -0.06 3.63 -0.21 4.62c -0.45 3.06 -2.19 5.49 -4.47 6.21c -0.57 0.18 -0.9 0.21 -1.59 0.21c -0.69 0 -1.02 -0.03 -1.65 -0.21c -1.14 -0.27 -2.13 -0.84 -2.94 -1.65c -0.99 -0.99 -1.56 -2.16 -1.71 -3.54c -0.09 -0.81 0.06 -1.53 0.45 -2.13c 0.63 -0.99 1.83 -1.56 3 -1.53c 1.5 0.09 2.64 1.32 2.73 2.94c 0.06 1.47 -0.93 2.7 -2.37 2.97c -0.45 0.06 -0.84 0.03 -1.29 -0.09l -0.21 -0.09l 0.09 0.12c 0.39 0.54 0.78 0.93 1.32 1.26c 1.35 0.87 3.06 1.02 4.35 0.36c 1.44 -0.72 2.52 -2.28 2.97 -4.35c 0.15 -0.66 0.24 -1.5 0.3 -3.03c 0.03 -0.84 0.03 -2.94 0 -3c -0.03 0 -0.18 0 -0.36 0.03c -0.66 0.12 -0.99 0.12 -1.83 0.12c -1.05 0 -1.71 -0.06 -2.61 -0.3c -4.02 -0.99 -7.11 -4.35 -7.8 -8.46c -0.12 -0.66 -0.12 -0.99 -0.12 -1.83c 0 -0.84 0 -1.14 0.15 -1.92c 0.36 -2.28 1.41 -4.62 3.3 -7.29l 2.79 -3.6c 0.54 -0.66 0.96 -1.2 0.96 -1.23c 0 -0.03 -0.09 -0.33 -0.18 -0.69c -0.96 -3.21 -1.41 -5.28 -1.59 -7.68c -0.12 -1.38 -0.15 -3.09 -0.06 -3.96c 0.33 -2.67 1.38 -5.07 3.12 -7.08c 0.36 -0.42 0.99 -1.05 1.17 -1.14zm 2.01 4.71c -0.15 -0.3 -0.3 -0.54 -0.3 -0.54c -0.03 0 -0.18 0.09 -0.3 0.21c -2.4 1.74 -3.87 4.2 -4.26 7.11c -0.06 0.54 -0.06 1.41 -0.03 1.89c 0.09 1.29 0.48 3.12 1.08 5.22c 0.15 0.42 0.24 0.78 0.24 0.81c 0 0.03 0.84 -1.11 1.23 -1.68c 1.89 -2.73 2.88 -5.07 3.15 -7.53c 0.09 -0.57 0.12 -1.74 0.06 -2.37c -0.09 -1.23 -0.27 -1.92 -0.87 -3.12zm -2.94 20.7c -0.21 -0.72 -0.39 -1.32 -0.42 -1.32c 0 0 -1.2 1.47 -1.86 2.37c -2.79 3.63 -4.02 6.3 -4.35 9.3c -0.03 0.21 -0.03 0.69 -0.03 1.08c 0 0.69 0 0.75 0.06 1.11c 0.12 0.54 0.27 0.99 0.51 1.47c 0.69 1.38 1.83 2.55 3.42 3.42c 0.96 0.54 2.07 0.9 3.21 1.08c 0.78 0.12 2.04 0.12 2.94 -0.03c 0.51 -0.06 0.45 -0.03 0.42 -0.3c -0.24 -3.33 -0.72 -6.33 -1.62 -10.08c -0.09 -0.39 -0.18 -0.75 -0.18 -0.78c -0.03 -0.03 -0.42 0 -0.81 0.09c -0.9 0.18 -1.65 0.57 -2.22 1.14c -0.72 0.72 -1.08 1.65 -1.05 2.64c 0.06 0.96 0.48 1.83 1.23 2.58c 0.36 0.36 0.72 0.63 1.17 0.9c 0.33 0.18 0.36 0.21 0.42 0.33c 0.18 0.42 -0.18 0.9 -0.6 0.87c -0.18 -0.03 -0.84 -0.36 -1.26 -0.63c -0.78 -0.51 -1.38 -1.11 -1.86 -1.83c -1.77 -2.7 -0.99 -6.42 1.71 -8.19c 0.3 -0.21 0.81 -0.48 1.17 -0.63c 0.3 -0.09 1.02 -0.3 1.14 -0.3c 0.06 0 0.09 0 0.09 -0.03c 0.03 -0.03 -0.51 -1.92 -1.23 -4.26zm 3.78 7.41c -0.18 -0.03 -0.36 -0.06 -0.39 -0.06c -0.03 0 0 0.21 0.18 1.02c 0.75 3.18 1.26 6.3 1.5 9.09c 0.06 0.72 0 0.69 0.51 0.42c 0.78 -0.36 1.44 -0.96 1.98 -1.77c 1.08 -1.62 1.2 -3.69 0.3 -5.55c -0.81 -1.62 -2.31 -2.79 -4.08 -3.15z"></path>
    </g>
    <g class="abcjs-note abcjs-d1 abcjs-p5 abcjs-l0 abcjs-m0 abcjs-mm0 abcjs-v0 abcjs-n0" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="0">
      <path data-name="A" d="M 55.561 52.31700000000001c 0.51 -0.03 2.01 0 2.52 0.03c 1.41 0.18 2.64 0.51 3.72 1.08c 1.2 0.63 1.95 1.41 2.19 2.31c 0.09 0.33 0.09 0.9 0 1.23c -0.24 0.9 -0.99 1.68 -2.19 2.31c -1.08 0.57 -2.28 0.9 -3.75 1.08c -0.66 0.06 -2.31 0.06 -2.97 0c -1.47 -0.18 -2.67 -0.51 -3.75 -1.08c -1.2 -0.63 -1.95 -1.41 -2.19 -2.31c -0.09 -0.33 -0.09 -0.9 0 -1.23c 0.24 -0.9 0.99 -1.68 2.19 -2.31c 1.2 -0.63 2.61 -0.99 4.23 -1.11zm 0.57 0.66c -0.87 -0.15 -1.53 0 -2.04 0.51c -0.15 0.15 -0.24 0.27 -0.33 0.48c -0.24 0.51 -0.36 1.08 -0.33 1.77c 0.03 0.69 0.18 1.26 0.42 1.77c 0.6 1.17 1.74 1.98 3.18 2.22c 1.11 0.21 1.95 -0.15 2.34 -0.99c 0.24 -0.51 0.36 -1.08 0.33 -1.8c -0.06 -1.11 -0.45 -2.04 -1.17 -2.76c -0.63 -0.63 -1.47 -1.05 -2.4 -1.2z"></path>
    </g>
    <g class="abcjs-note abcjs-d1 abcjs-p5 abcjs-l0 abcjs-m0 abcjs-mm0 abcjs-v0 abcjs-n1" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="1">
      <path data-name="A" d="M 408.5355 52.31700000000001c 0.51 -0.03 2.01 0 2.52 0.03c 1.41 0.18 2.64 0.51 3.72 1.08c 1.2 0.63 1.95 1.41 2.19 2.31c 0.09 0.33 0.09 0.9 0 1.23c -0.24 0.9 -0.99 1.68 -2.19 2.31c -1.08 0.57 -2.28 0.9 -3.75 1.08c -0.66 0.06 -2.31 0.06 -2.97 0c -1.47 -0.18 -2.67 -0.51 -3.75 -1.08c -1.2 -0.63 -1.95 -1.41 -2.19 -2.31c -0.09 -0.33 -0.09 -0.9 0 -1.23c 0.24 -0.9 0.99 -1.68 2.19 -2.31c 1.2 -0.63 2.61 -0.99 4.23 -1.11zm 0.57 0.66c -0.87 -0.15 -1.53 0 -2.04 0.51c -0.15 0.15 -0.24 0.27 -0.33 0.48c -0.24 0.51 -0.36 1.08 -0.33 1.77c 0.03 0.69 0.18 1.26 0.42 1.77c 0.6 1.17 1.74 1.98 3.18 2.22c 1.11 0.21 1.95 -0.15 2.34 -0.99c 0.24 -0.51 0.36 -1.08 0.33 -1.8c -0.06 -1.11 -0.45 -2.04 -1.17 -2.76c -0.63 -0.63 -1.47 -1.05 -2.4 -1.2z"></path>
    </g>
  </g>
</svg>

Note, that the first and second notes are the same: A. So they were defined by abcjs as notePositions which are drawn as follows:

<path data-name="A" d="M 55.561 52.31700000000001c ....>

and

<path data-name="A" d="M 408.5355 52.31700000000001c ...>

Which means, the position y, of the note A is 52.31700000000001

Now, I want to draw a different SVG. So I need to know the correct Y for a specific note.

Imagine that I want to draw on the position of the note F;

So I would like to call a function to be able to add the correct Y for F.

Eg:

calculateNoteY('F'); // 60.06700000000001

So I can draw my path with:

<path data-name="F--cross" d="M 408.5355 60.06700000000001c ...>

What I am trying to understand, is how abcjs calculates the correct Y of a terminated note, to add into its <path>?

Reading the code, I was able to find the https://github.com/paulrosen/abcjs/blob/2616d88ddf0222e255c508f944df3089960c13dc/src/write/draw/absolute.js#L57-L66 which defines the notePositions. Is there a public API for that? If not, do you know how can I calculate the appropriated y for each note. I'm happy to have an object, like:

const allNotePosition = { A: { y: 52.31700000000001 }, F: { y: 60.06700000000001 } };

With that, I can draw the circles and crosses as the user plays the instrument in real-time:

Eg: in the image example, the user played F A, instead of A A, so the result is:

Screenshot 2022-12-19 at 8 28 21 am
samuelbradshaw commented 1 year ago

How important is it to show the note they played? Could you put an X on top of the correct note, instead of where the incorrect note would be? Either way it would signal to the user that they missed the note.

leocaseiro commented 1 year ago

Thank you for your suggestion, but the user not playing a note, or playing the wrong note are 2 different things that I'm very interested in educate.

So I would say that is very important.

Is anyone able to explain me how abcjs gets from parsing the notation, until writing isolated notes and adding into the staff?

I've been trying to reverse-engineering, but I'm still very far from understanding that part of the code.

paulrosen commented 1 year ago

So, you have everything except the y position? The y position is a constant (although not publicly available - but easy enough to figure out.) It is 3.875. Now, that could change sometime in the future, but there aren't any current plans to change it.

What if the person sings Ab instead of A? How is that marked? What if they sing G? Will the X be clear enough?

leocaseiro commented 1 year ago

Hi @paulrosen, thank you for clarifying that. Do you mind sharing what constant is that? I couldn't find it myself.

As my app is focusing on drums, it wouldn't matter in very specific cases, but it is an interesting topic if I ever enable for other instruments. Either way, I think the X can help them to understand that that they played the wrong note, even in the correct position.

I could add as text what note they played. Good catch.

paulrosen commented 1 year ago

It is calculated in abc_spacing.js from hardcoded values.

paulrosen commented 1 year ago

I guess this is resolved. Let me know if there are any other questions.