1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4     Rosegarden
5     A MIDI and audio sequencer and musical notation editor.
6     Copyright 2000-2021 the Rosegarden development team.
7 
8     Other copyrights also apply to some parts of this work.  Please
9     see the AUTHORS file and individual file headers for details.
10 
11     This program is free software; you can redistribute it and/or
12     modify it under the terms of the GNU General Public License as
13     published by the Free Software Foundation; either version 2 of the
14     License, or (at your option) any later version.  See the file
15     COPYING included with this distribution for more information.
16 */
17 
18 
19 #include "NotationChord.h"
20 
21 #include "base/Sets.h"
22 #include "base/Event.h"
23 #include "base/NotationRules.h"
24 #include "base/NotationTypes.h"
25 #include "base/Quantizer.h"
26 #include "misc/Debug.h"
27 #include "NotationProperties.h"
28 #include "NoteStyleFactory.h"
29 
30 namespace Rosegarden
31 {
32 
33 template <>
34 Event *
getAsEvent(const NotationElementList::iterator & i)35 AbstractSet<NotationElement, NotationElementList>::getAsEvent(const NotationElementList::iterator &i)
36 {
37     return (*i)->event();
38 }
39 
NotationChord(NotationElementList & c,NotationElementList::iterator i,const Quantizer * quantizer,const NotationProperties & properties,const Clef & clef,const::Rosegarden::Key & key)40 NotationChord::NotationChord(NotationElementList &c,
41                              NotationElementList::iterator i,
42                              const Quantizer *quantizer,
43                              const NotationProperties &properties,
44                              const Clef &clef,
45                              const ::Rosegarden::Key &key) :
46         GenericChord < NotationElement,
47         NotationElementList, true > (c, i, quantizer,
48                                      NotationProperties::STEM_UP),
49         m_properties(properties),
50         m_clef(clef),
51         m_key(key)
52 {
53     initialise();
54 }
55 
56 int
getHeight(const Iterator & i) const57 NotationChord::getHeight(const Iterator &i) const
58 {
59     //!!! We use HEIGHT_ON_STAFF in preference to the passed clef/key,
60     //but what if the clef/key changed since HEIGHT_ON_STAFF was
61     //written?  Who updates the properties then?  Check this.
62 
63     long h = 0;
64     if (getAsEvent(i)->get
65             <Int>(NotationProperties::HEIGHT_ON_STAFF, h)) {
66         return h;
67     }
68 
69     try {
70         Pitch pitch(*getAsEvent(i));
71         h = pitch.getHeightOnStaff(m_clef, m_key);
72     } catch (...) {
73         // no pitch!
74     }
75 
76     // set non-persistent, not setMaybe, as we know the property is absent:
77     getAsEvent(i)->set
78     <Int>(NotationProperties::HEIGHT_ON_STAFF, h, false);
79     return h;
80 }
81 
82 bool
hasStem() const83 NotationChord::hasStem() const
84 {
85     // true if any of the notes is stemmed
86 
87     Iterator i(getInitialNote());
88     for (;;) {
89         long note;
90         if (!getAsEvent(i)->get
91                 <Int>(BaseProperties::NOTE_TYPE, note)) return true;
92         if (NoteStyleFactory::getStyleForEvent(getAsEvent(i))->hasStem(note))
93             return true;
94         if (i == getFinalNote())
95             return false;
96         ++i;
97     }
98     return false;
99 }
100 
101 bool
hasStemUp() const102 NotationChord::hasStemUp() const
103 {
104     NotationRules rules;
105 
106     // believe anything found in any of the notes, if in a persistent
107     // property or a property apparently set by the beaming algorithm
108 
109     Iterator i(getInitialNote());
110 
111     for (;;) {
112         Event *e = getAsEvent(i);
113         /*!!!
114         	if (e->has(m_properties.VIEW_LOCAL_STEM_UP)) {
115         	    return e->get<Bool>(m_properties.VIEW_LOCAL_STEM_UP);
116         	}
117         */
118         if (e->has(NotationProperties::STEM_UP)) {
119             return e->get
120                    <Bool>(NotationProperties::STEM_UP);
121         }
122 
123         if (e->has(NotationProperties::BEAM_ABOVE)) {
124             if (e->has(NotationProperties::BEAMED) &&
125                     e->get
126                     <Bool>(NotationProperties::BEAMED)) {
127                 return e->get
128                        <Bool>(NotationProperties::BEAM_ABOVE);
129             }
130             else {
131                 return !e->get
132                        <Bool>(NotationProperties::BEAM_ABOVE);
133             }
134         }
135 
136         if (i == getFinalNote())
137             break;
138         ++i;
139     }
140 
141     return rules.isStemUp(getHighestNoteHeight(),getLowestNoteHeight());
142 }
143 
144 bool
hasNoteHeadShifted() const145 NotationChord::hasNoteHeadShifted() const
146 {
147     int ph = 10000;
148 
149     for (unsigned int i = 0; i < size(); ++i) {
150         int h = getHeight((*this)[i]);
151         if (h == ph + 1)
152             return true;
153         ph = h;
154     }
155 
156     return false;
157 }
158 
159 bool
isNoteHeadShifted(const Iterator & itr) const160 NotationChord::isNoteHeadShifted(const Iterator &itr) const
161 {
162     unsigned int i;
163     for (i = 0; i < size(); ++i) {
164         if ((*this)[i] == itr)
165             break;
166     }
167 
168     if (i == size()) {
169         RG_WARNING << "NotationChord::isNoteHeadShifted: Warning: Unable to find note head " << getAsEvent(itr);
170         return false;
171     }
172 
173     int h = getHeight((*this)[i]);
174 
175     if (hasStemUp()) {
176         if ((i > 0) && (h == getHeight((*this)[i - 1]) + 1)) {
177             return (!isNoteHeadShifted((*this)[i - 1]));
178         }
179     } else {
180         if ((i < size() - 1) && (h == getHeight((*this)[i + 1]) - 1)) {
181             return (!isNoteHeadShifted((*this)[i + 1]));
182         }
183     }
184 
185     return false;
186 }
187 
188 void
applyAccidentalShiftProperties()189 NotationChord::applyAccidentalShiftProperties()
190 {
191     // Some rules:
192     //
193     // The top accidental always gets the minimum shift (i.e. normally
194     // right next to the note head or stem).
195     //
196     // The bottom accidental gets the next least: the same, if the
197     // interval is more than a sixth, or the next shift out otherwise.
198     //
199     // We then progress up from the bottom accidental upwards.
200     //
201     // These rules aren't really enough, but they might do for now!
202 
203     //!!! Uh-oh... we have a catch-22 here.  We can't determine the
204     // proper minimum shift until we know which way the stem goes,
205     // because if we have a shifted note head and the stem goes down,
206     // we need to shift one place further than otherwise.  But we
207     // don't know for sure which way the stem goes until we've
208     // calculated the beam, and we don't do that until after we've
209     // worked out the x-coordinates based on (among other things) the
210     // accidental shift.
211 
212     int minShift = 0;
213     bool extra = false;
214 
215     if (!hasStemUp() && hasNoteHeadShifted()) {
216         minShift = 1; // lazy
217         extra = true;
218     }
219 
220     int lastShift = minShift;
221     int lastHeight = 0, maxHeight = 999;
222     int lastWidth = 1;
223 
224     for (iterator i = end(); i != begin(); ) {
225 
226         --i;
227         Event *e = getAsEvent(*i);
228 
229         Accidental acc;
230         if (e->get
231                 <String>(m_properties.DISPLAY_ACCIDENTAL, acc) &&
232                 acc != Accidentals::NoAccidental) {
233             e->setMaybe<Int>(m_properties.ACCIDENTAL_SHIFT, minShift);
234             e->setMaybe<Bool>(m_properties.ACCIDENTAL_EXTRA_SHIFT, extra);
235             maxHeight = lastHeight = getHeight(*i);
236             break;
237         }
238     }
239 
240     if (maxHeight == 999) {
241         return ;
242     }
243 
244     for (iterator i = begin(); i != end(); ++i) {
245 
246         Event *e = getAsEvent(*i);
247         int height = getHeight(*i);
248 
249         if (height == maxHeight && e->has(m_properties.ACCIDENTAL_SHIFT)) {
250             // finished -- we've come around to the highest one again
251             break;
252         }
253 
254         Accidental acc;
255 
256         if (e->get
257                 <String>(m_properties.DISPLAY_ACCIDENTAL, acc) &&
258                 acc != Accidentals::NoAccidental) {
259 
260             int shift = lastShift;
261 
262             if (height < lastHeight) { // lastHeight was the first, up top
263                 if (lastHeight - height < 6) {
264                     shift = lastShift + lastWidth;
265                 }
266             } else {
267                 if (height - lastHeight >= 6) {
268                     if (maxHeight - height >= 6) {
269                         shift = minShift;
270                     } else {
271                         shift = minShift + 1;
272                     }
273                 } else {
274                     shift = lastShift + lastWidth;
275                 }
276             }
277 
278             e->setMaybe<Int>(m_properties.ACCIDENTAL_SHIFT, shift);
279 
280             lastHeight = height;
281             lastShift = shift;
282 
283             lastWidth = 1;
284             bool c = false;
285             if (e->get
286                     <Bool>(m_properties.DISPLAY_ACCIDENTAL_IS_CAUTIONARY, c)
287                     && c) {
288                 lastWidth = 2;
289             }
290         }
291     }
292 }
293 
294 int
getMaxAccidentalShift(bool & extra) const295 NotationChord::getMaxAccidentalShift(bool &extra) const
296 {
297     int maxShift = 0;
298 
299     for (const_iterator i = begin(); i != end(); ++i) {
300         Event *e = getAsEvent(*i);
301         if (e->has(m_properties.ACCIDENTAL_SHIFT)) {
302             int shift = e->get
303                         <Int>(m_properties.ACCIDENTAL_SHIFT);
304             if (shift > maxShift) {
305                 maxShift = shift;
306                 e->get
307                 <Bool>(m_properties.ACCIDENTAL_EXTRA_SHIFT, extra);
308             }
309         }
310     }
311 
312     return maxShift;
313 }
314 
315 int
getAccidentalShift(const Iterator & i,bool & extra) const316 NotationChord::getAccidentalShift(const Iterator &i, bool &extra) const
317 {
318     if (getAsEvent(i)->has(m_properties.ACCIDENTAL_SHIFT)) {
319         int shift = getAsEvent(i)->get
320                     <Int>(m_properties.ACCIDENTAL_SHIFT);
321         getAsEvent(i)->get
322         <Bool>(m_properties.ACCIDENTAL_EXTRA_SHIFT, extra);
323         return shift;
324     } else {
325         return 0;
326     }
327 }
328 
329 }
330