-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy pathGRSlur.cpp
417 lines (338 loc) · 14.2 KB
/
GRSlur.cpp
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
/*
GUIDO Library
Copyright (C) 2002 Holger Hoos, Juergen Kilian, Kai Renz
Copyright (C) 2004 Grame
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
Grame Research Laboratory, 11, cours de Verdun Gensoul 69002 Lyon - France
*/
#ifdef WIN32
#define _USE_MATH_DEFINES
#endif
#include <cmath> // for abs
#include <algorithm>
#include "GRGlobalStem.h"
#include "GRSingleNote.h"
#include "GRSlur.h"
#include "GRStaff.h"
#include "GRStem.h"
#include "GRStdNoteHead.h"
using namespace std;
// -----------------------------------------------------------------------------
void GRSlur::accept (GRVisitor& visitor)
{
visitor.visitStart (this);
visitor.visitEnd (this);
}
// -----------------------------------------------------------------------------
void GRSlur::automaticCurveDirection( GRBowingContext * context, const ARBowing * arBow, GRSystemStartEndStruct * sse )
{
// -- Long phrase positionning rule: choose upward if there is a lot of notes.
// in the range.
// We consider that this rule has the higer priority, so we return as soon as
// it applies.
const int aLotOfEvents = 16;
const int evCount = mAssociated->GetCount();
if( evCount > aLotOfEvents )
{
context->curveDir = 1; // upward
return;
}
// -- Equal pitch for start and end: place on the stems.
// this rule avoids confusion with ties.
// TODO: take care of accidentals.
GRNote * firstNote = dynamic_cast<GRNote *>(sse->startElement);
GRNote * lastNote = dynamic_cast<GRNote *>(sse->endElement);
if( firstNote && lastNote )
{
int octave1, octave2;
int pitch1, pitch2;
int acc1, acc2;
firstNote->getPitchAndOctave( &pitch1, &octave1, &acc1 );
lastNote->getPitchAndOctave( &pitch2, &octave2, &acc2 );
if(( pitch1 == pitch2 ) && (octave1 == octave2 ) && (acc1 == acc2))
{
context->curveDir = firstNote->getThroatDirection();
return;
}
}
// - Todo: the two voices rule (upward for upper voice, downward for lower voice)
// - Todo: direction conservation if the slur covers many systems.
// - Todo: "opposite to the tie" rule.
// -- Call inherited rules.
GRBowing::automaticCurveDirection( context, arBow, sse );
}
// -----------------------------------------------------------------------------
float GRSlur::getEltOffset (const GRNotationElement* el ) const
{
const GRSingleNote* note = el->isSingleNote();
if (note)
return note->getNoteHead()->getOffset().x;
return 0;
}
// -----------------------------------------------------------------------------
NVRect GRSlur::getElementBox (const GRBowingContext * context, const GRNotationElement* el ) const
{
NVRect r = el->getBoundingBox() + el->getPosition();
float leftlength = 0;
float leftoffset = 0;
float lefty = 0;
if (context->leftChordStem) {
leftlength = context->leftChordStem->getStemLength();
lefty = context->leftChordStem->getGRStem()->getPosition().y;
leftoffset = lefty - el->getPosition().y;
}
float rightlength = 0;
float rightoffset = 0;
float righty = 0;
if (context->rightChordStem) {
rightlength = context->rightChordStem->getStemLength();
righty = context->rightChordStem->getGRStem()->getPosition().y;
rightoffset = righty - el->getPosition().y;
}
if (context->stemDirLeft == 1) {
if (el == context->topLeftHead)
r.top -= leftlength - leftoffset;
else if (el == context->topRightHead)
r.top -= rightlength - rightoffset;
}
else {
if (el == context->bottomLeftHead)
r.bottom += leftlength + leftoffset;
else if (el == context->bottomRightHead)
r.bottom += rightlength + rightoffset;
}
return r;
}
// -----------------------------------------------------------------------------
void GRSlur::automaticAnchorPoints( const GRBowingContext * context, const ARBowing * arBow, GRSystemStartEndStruct * sse )
{
const bool upward = (context->curveDir == 1);
// Careful, we have to deal with chords! what about getStemStartPosition()?
NVPoint posLeft;
NVPoint posRight;
const GRNotationElement * startElement = sse->startElement;
const GRNotationElement * endElement = sse->endElement;
GRBowingSaveStruct * bowInfos = (GRBowingSaveStruct *)sse->p;
const GRStaff * startStaff = startElement->getGRStaff();
const GRStaff * endStaff = endElement->getGRStaff();
bool spanStaves = (startStaff != endStaff);
// We try to fix the following problem here: with chord, the start and end
// elements are GREmpty objects, with a zero bounding box. So we substitute
// them by adequate noteheads.
if( context->topLeftHead != context->bottomLeftHead ) // test if chord
startElement = upward ? context->topLeftHead : context->bottomLeftHead;
if( (context->topRightHead != context->bottomRightHead) || endElement->isEmpty()) // test if chord
endElement = upward ? context->topRightHead : context->bottomRightHead;
// -- Get the bounding box of the left and right elements.
const NVRect leftBox = getElementBox ( context, startElement );
const NVRect rightBox = getElementBox ( context, endElement );
const float yMargin = float(0.75) * LSPACE;
// -- Calculates the start and end coordinates of the slur.
if( upward ) {
// Left x-pos
posLeft.x = context->stemDirLeft == 1 ? leftBox.right : (leftBox.left + leftBox.right) * float(0.5);
// Right x-pos
posRight.x = context->stemDirRight == 1 ? rightBox.right : (rightBox.left + rightBox.right) * float(0.5);
// y-pos
posLeft.y = leftBox.top - yMargin;
posRight.y = rightBox.top - yMargin;
}
else // downward
{
// Left x-pos
posLeft.x = context->stemDirLeft == 1 ? (leftBox.left + leftBox.right) * float(0.5) : leftBox.left;
// Right x-pos
posRight.x = context->stemDirRight == 1 ? (rightBox.left + rightBox.right) * float(0.5) : rightBox.left;
// y-pos
posLeft.y = leftBox.bottom + yMargin;
posRight.y = rightBox.bottom + yMargin;
}
if (spanStaves) posRight.y += endStaff->getPosition().y - startStaff->getPosition().y;
// - Handle broken slurs and minimal width
const float minWidth = float(1.5) * LSPACE; // arbitrary miminal width of a tie
const float openYOffset = upward ? - LSPACE : LSPACE;
if (context->openLeft) {
posLeft.y = posRight.y + openYOffset;
if( posLeft.x > (posRight.x - minWidth))
posLeft.x = (posRight.x - minWidth);
}
if (context->openRight) {
posRight.y = posLeft.y + openYOffset;
if( posRight.x < (posLeft.x + minWidth))
posRight.x = (posLeft.x + minWidth);
}
posLeft.x += getEltOffset(startElement);
posRight.x += getEltOffset(endElement);
// - Store results.
bowInfos->position = posLeft;
bowInfos->offsets[2] = posRight - posLeft; // control points are stored as offsets to the position.
// arBow->setCurve( context->curveDir, posLeft, posRight ); // (JB) useless ?
}
// -----------------------------------------------------------------------------
/** \brief Modifies the shape of the curve to avoid collisions.
At this point, the curve direction (up or down) and the anchor points
positions have already been decided.
Here, we're only trying to choose a good position for the control points.
(We could also decide to adjust anchor points, according to the music
notation rules)
*/
void GRSlur::automaticControlPoints( const GRBowingContext * context, const ARBowing * arBow, GRSystemStartEndStruct * sse )
{
if (mAssociated == 0 ) return;
//cerr << "GRSlur::automaticControlPoints " << endl;
//if (1) {
// GuidoPos pos = mAssociated->GetHeadPosition();
// while( pos) {
// GRNotationElement * el = mAssociated->GetNext(pos);
//cerr << " assoc: " << el << endl;
// }
//}
GRBowingSaveStruct * bowInfos = (GRBowingSaveStruct *)sse->p;
const bool upward = (context->curveDir == 1);
// We don't need to adjust the curve if there is only two elements
// (That is: only the first and the last event).
// -- Calculates a bounding triangle that contains all objects between the
// futur bow and the line joining the first and the last element.
// Important: note that the screen y-axis is inverted.
const float startX = bowInfos->position.x + bowInfos->offsets[0].x;
const float startY = bowInfos->position.y + bowInfos->offsets[0].y;
const float endX = bowInfos->position.x + bowInfos->offsets[2].x;
const float endY = bowInfos->position.y + bowInfos->offsets[2].y;
// Minimum angles
const float minBaseAngle = float(12.f * M_PI/180.f);
const float minHorizontalAngle = float(-8.f * M_PI / 180.f);
// Slope of the base segment of the triangle
float baseSlope = (endY-startY) / (endX-startX);
const float baseAngle = atan(baseSlope);
// Initial slopes of the left and right segments of the triangle.
// (segment equation: y = Ax + B )
//const float minSlope = float(0.03); // force slurs to start with an angle.
// float startA = (upward ? -minSlope : minSlope)+baseSlope;
// float endA = (upward ? minSlope : -minSlope)+baseSlope;
float startA = upward ? tan(-minBaseAngle + baseAngle) : tan(minBaseAngle + baseAngle);
float endA = upward ? tan( minBaseAngle + float(M_PI) + baseAngle) : tan( - minBaseAngle + float(M_PI) + baseAngle);
if(upward){
startA = min(startA, -tan(minHorizontalAngle));
endA = max(endA, +tan(minHorizontalAngle));
}else{
startA = max(startA, +tan(minHorizontalAngle));
endA = min(endA, -tan(minHorizontalAngle));
}
float x1, x2, y, a;
float extremeY;
if( upward ) extremeY = (startY < endY ) ? startY : endY;
else extremeY = (startY > endY ) ? startY : endY;
float refyStaff = context->staff->getPosition().y;
// - Find the best slopes for the left and right segments of the triangle, such
// that no object is above them (or below, if the bow goes downward).
GuidoPos pos = mAssociated->GetHeadPosition();
int size = mAssociated->size() - 2;
mAssociated->GetNext(pos); // skip the first element
while( pos && size--) // and the last element
{
GREvent * el = mAssociated->GetNext(pos)->isGREvent();
if( el && !el->isEmpty()) {
// - Get this element box
NVRect elBox ( el->getBoundingBox());
if( elBox.Width() == 0 && elBox.Height() == 0 ) continue; // useless ?
NVPoint elPos = el->getPosition();
elBox += elPos;
// - Get the two corners we must deal with (depends of curve direction up/down)
x1 = elBox.left;
x2 = elBox.right;
y = upward ? elBox.top : elBox.bottom;
const GRStaff* staff = el->getGRStaff();
float offset = staff ? staff->getPosition().y : refyStaff;
y += offset - refyStaff;
// - Ignore it, if it's outside the start/end range.
// this also avoid divisions by zero.
if(( x1 <= startX ) || ( x2 >= endX )) continue;
// - Catch the biggest encoutered Y, we'll need it later.
if(( upward && ( y < extremeY )) || ( !upward && ( y > extremeY )))
extremeY = y;
// - Check for the left max slope.
a = (y - startY) / (x1 - startX); // slope = (yb - ya) / (xb - xa)
if(( upward && ( a < startA )) || ( !upward && ( a > startA )))
startA = a;
// - Check for the right max slope.
a = (endY - y ) / (endX - x2);
if(( upward && ( a > endA )) || ( !upward && ( a < endA )))
endA = a;
}
}
// -- Tune the slopes so the triangle is isocele
float startAngle = atan(startA) - baseAngle;
float endAngle = atan(endA) - baseAngle;
if(abs(startAngle) < abs(endAngle)) startAngle = -endAngle ;
else endAngle = -startAngle;
startA = tan(startAngle + baseAngle);
endA = tan(endAngle + baseAngle);
// -- Now, find the intersection point of the left and right
// segments of the bounding triangle.
float crossX = 0; // the intersection point.
float crossY = 0;
const float b1 = startY - (startA * startX); // calculate b from: y = ax + b
const float b2 = endY - (endA * endX); // <=> b = y - (ax)
// -- Force lines to intersect within the [startX, endX] range.
// This is done by recalculating slopes that are too small.
const float yForEndX = startA * endX + b1;
if( upward && ( yForEndX > ( endY - 20 )))
startA = ((endY - 20) - b1) / endX;
else if( !upward && ( yForEndX < ( endY + 20 )))
startA = ((endY + 20) - b1) / endX;
const float yForStartX = endA * startX + b2;
if( upward && ( yForStartX > ( startY - 20 ))) endA = ((startY - 20) - b2) / startX;
else if( !upward && ( yForStartX < ( startY + 20 ))) endA = ((startY + 20) - b2) / startX;
// -- Avoid too high curves.
const float maxSlope = float(1.2);
if(( abs(startA) > maxSlope ) && ( abs(endA) > maxSlope ))
{
startA = 0; // abandon previous slopes, we'll use the 'middle' point.
endA = 0;
}
// -- Calculate the intersection point by resolving the equation:
// a1x + b1 = a2x + b2 <=> x = (b2 - b1) / (a1 - a2)
if( startA != endA )
{
crossX = (b2 - b1) / (startA - endA);
crossY = (startA * crossX) + b1;
}
// If we failed to find a good intersection point, just pick the middle point.
// this should never happen.
if( crossX <= (startX + 5) || crossX >= (endX - 5))
{
crossX = (startX + endX) * float(0.5);
crossY = extremeY;
}
// -- Now, we have an intersection point. We're going to tune
// it to get a nice curve.
const float minDelta = 10; // arbitrary value.
const float maxDelta = 160;
const float factor = float(0.35); // arbitrary value. old
const float deltaY = abs(crossY - extremeY);
float newDeltaY = factor * deltaY;
if( newDeltaY < minDelta ) newDeltaY = minDelta;
else if( newDeltaY > maxDelta ) newDeltaY = maxDelta; // avoid too high curves.
crossY = upward ? (crossY - newDeltaY ) : (crossY + newDeltaY);
// -- Here we change the sharpness of the curve inflexion.
const float maxInflexion = 7; // Max inflexion (for small arc)
const float power = 600; // Arbitrary, scale the arc size
const float arcWidth = (float)sqrt(pow(startX-endX,2)+ pow(startY-endY,2));
bowInfos->inflexion = (maxInflexion-2) * exp( - arcWidth/power) + 2;
// -- Apply the new control point.
bowInfos->offsets[1].y = crossY - bowInfos->position.y;
bowInfos->offsets[1].x = crossX - bowInfos->position.x;
{
float limit = 250;
float h = bowInfos->offsets[1].y;
float y1 = bowInfos->offsets[0].y;
float y2 = bowInfos->offsets[2].y;
if ((h >= 0) && (h - y1 > limit) && (h - y2 > limit))
bowInfos->offsets[1].y = std::max(y1, y2) + limit;
else if ((h < 0) && (h - y1 < -limit) && (h - y2 < -limit))
bowInfos->offsets[1].y = std::min(y1, y2) - limit;
}
}