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 #define RG_MODULE_STRING "[KeyInsertionCommand]"
19 
20 #include "KeyInsertionCommand.h"
21 
22 #include "misc/Debug.h"
23 #include "base/Event.h"
24 #include "base/NotationTypes.h"
25 #include "base/Segment.h"
26 #include "base/SegmentNotationHelper.h"
27 #include "base/Studio.h"
28 #include "document/BasicCommand.h"
29 #include "base/BaseProperties.h"
30 #include "base/Selection.h"
31 #include <QString>
32 
33 
34 namespace Rosegarden
35 {
36 
Q_DECLARE_TR_FUNCTIONS(Rosegarden::IncrementDisplacementsCommand)37 using namespace BaseProperties;
38 
39 
40 KeyInsertionCommand::KeyInsertionCommand(Segment &segment, timeT time,
41                                          Key key,
42                                          bool convert,
43                                          bool transpose,
44                                          bool transposeKey,
45                                          bool ignorePercussion) :
46     BasicCommand(getGlobalName(&key), segment, time, segment.getEndTime()),
IncrementDisplacementsCommand(EventSelection & selection,long dx,long dy)47     m_key(key),
48     m_lastInsertedEvent(nullptr),
49     m_convert(convert),
50     m_transpose(transpose),
51     m_transposeKey(transposeKey),
52     m_ignorePercussion(ignorePercussion)
53 {
54     // nothing
55 }
56 
57 KeyInsertionCommand::~KeyInsertionCommand()
58 {
59     // nothing
60 }
61 
62 EventSelection *
63 KeyInsertionCommand::getSubsequentSelection()
64 {
65     EventSelection *selection = new EventSelection(getSegment());
66     selection->addEvent(getLastInsertedEvent());
67     return selection;
68 }
69 
70 void
71 KeyInsertionCommand::modifySegment()
72 {
73     SegmentNotationHelper helper(getSegment());
74     Key oldKey;
75 
76     if (m_convert || m_transpose) {
77         oldKey = getSegment().getKeyAtTime(getStartTime());
78     }
79 
80     Segment::iterator i = getSegment().findTime(getStartTime());
81     while (getSegment().isBeforeEndMarker(i)) {
82         if ((*i)->getAbsoluteTime() > getStartTime()) {
83             break;
84         }
85         if ((*i)->isa(Key::EventType)) {
86             getSegment().erase(i);
87             break;
88         }
89         ++i;
90     }
91 
92     // transpose if desired, according to new dialog option
93     if (m_transposeKey) {
94         // we don't really care about major/minor for this, so pass it through
95         // from the original key
96         bool keyIsMinor = m_key.isMinor();
97 
98         // get whether the original key is flat or sharp, so we know what to
99         // prefer for the new key
100         bool keyIsSharp = m_key.isSharp();
101 
102         // get the tonic pitch of the user-specified key, reported as a 0-11 int, then
103         // add an extra octave to it to avoid winding up with negative numbers
104         // (the octave will be stripped back off)
105         int specifiedKeyTonic = m_key.getTonicPitch() + 12;
106 
107         // get the transpose factor for the segment we're working on
108         int segTranspose = getSegment().getTranspose();
109 
110         // subtract the transpose factor from the tonic pitch of the
111         // user-specified key, because we want to move in the opposite
112         // direction for notation (eg. notation is in C major concert, at Bb
113         // transposition, we have -2 from the segment, and want to go +2 for
114         // the key, from tonic pitch 0 (C) to tonic pitch 2 (D) for the key as
115         // written for a Bb instrument
116         //
117         // sanity check: 0 == C; 0 + 12 == 12; (12 - -2) % 12 == 2; 2 == D
118         int transposedKeyTonic = (specifiedKeyTonic - segTranspose) % 12;
119 
120         // create a new key with the new tonic pitch, and major/minor from the
121         // original key
122         std::string newKeyName = "";
123 
124         switch (transposedKeyTonic) {
125             // 0 C | 1 C# | 2 D | 3 D# | 4 E | 5 F | 6 F# | 7 G | 8 G# | 9 A | 10 A# | 11 B
126         case 0 :  // C
127             newKeyName = "C";
128             break;
129         case 2 :  // D
130             newKeyName = "D";
131             break;
132         case 4 :  // E
133             newKeyName = "E";
134             break;
135         case 5 :  // F
136             newKeyName = "F";
137             break;
138         case 7 :  // G
139             newKeyName = "G";
140             break;
141         case 9 :  // A
142             newKeyName = "A";
143             break;
144         case 11:  // B
145             newKeyName = "B";
146             break;
147             // the glorious, glorious black keys need special treatment
148             // again, so we pick flat or sharp spellings based on the
149             // condition of the original, user-specified key we're
150             // transposing
151         case 1 :  // C#/Db
152             newKeyName = (keyIsSharp ? "C#" : "Db");
153             break;
154         case 3 :  // D#/Eb
155             newKeyName = (keyIsSharp ? "D#" : "Eb");
156             break;
157         case 6 :  // F#/Gb
158             newKeyName = (keyIsSharp ? "F#" : "Gb");
159             break;
160         case 8 :  // G#/Ab
161             newKeyName = (keyIsSharp ? "G#" : "Ab");
162             break;
163         case 10:   // A#/Bb
164             newKeyName = (keyIsSharp ? "A#" : "Bb");
165             break;
166         default:
167             // if this fails, we won't have a valid key name, and
168             // there will be some crashing exception I don't know how
169             // to intercept and avoid, so I'm doing this lame failsafe
170             // instead, which should never, ever actually run under
171             // any conceivable cirumstance anyway
172             RG_DEBUG << "KeyInsertionCommand: by the pricking of my thumbs, something wicked this way comes.  :(";
173             return ;
174         }
175 
176         newKeyName += (keyIsMinor ? " minor" : " major");
177 
178         //for f in C# D# E# F# G# A# B# Cb Db Eb Fb Gb Ab Bb;do grep "$f
179         //major" NotationTypes.C > /dev/null||echo "invalid key: $f
180         //major";grep "$f minor" NotationTypes.C > /dev/null||echo "invalid
181         //key: $f minor";done|sort
182         //invalid key: A# major
183         //invalid key: B# major
184         //invalid key: B# minor
185         //invalid key: Cb minor
186         //invalid key: Db minor
187         //invalid key: D# major
188         //invalid key: E# major
189         //invalid key: E# minor
190         //invalid key: Fb major
191         //invalid key: Fb minor
192         //invalid key: Gb minor
193         //invalid key: G# major
194 
195         // some kludgery to avoid creating invalid key names with some if/then
196         // swapping to manually respell things generated incorrectly by the
197         // above, rather than adding all kinds of nonsense to avoid this
198         // necessity
199         if (newKeyName == "A# major")
200             newKeyName = "Bb major";
201         else if (newKeyName == "B# major")
202             newKeyName = "C major";
203         else if (newKeyName == "Cb minor")
204             newKeyName = "B minor";
205         else if (newKeyName == "Db minor")
206             newKeyName = "C# minor";
207         else if (newKeyName == "D# major")
208             newKeyName = "Eb major";
209         else if (newKeyName == "E# major")
210             newKeyName = "F major";
211         else if (newKeyName == "E# minor")
212             newKeyName = "F minor";
213         else if (newKeyName == "Fb major")
214             newKeyName = "E major";
215         else if (newKeyName == "Fb minor")
216             newKeyName = "E minor";
217         else if (newKeyName == "Gb minor")
218             newKeyName = "F# minor";
219         else if (newKeyName == "G# major")
220             newKeyName = "Ab major";
221 
222         // create a new key with the newly derived name, and swap it for the
223         // user-specified version
224         Key k(newKeyName);
225         RG_DEBUG << "KeyInsertCommand: inserting transposed key"
226         << "        user key was: " << m_key.getName() << "\n"
227         << "    tranposed key is: " << k.getName();
228         m_key = k;
229     } // if (m_transposeKey)
230 
231     i = helper.insertKey(getStartTime(), m_key);
232 
233     if (i != helper.segment().end()) {
234 
235         m_lastInsertedEvent = *i;
236         if (!m_convert && !m_transpose)
237             return ;
238 
239         while (++i != helper.segment().end()) {
240 
241             //!!! what if we get two keys at the same time...?
242             if ((*i)->isa(Key::EventType))
243                 break;
244 
245             if ((*i)->isa(Note::EventType) &&
246                     (*i)->has(PITCH)) {
247 
248                 long pitch = (*i)->get
249                              <Int>(PITCH);
250 
251                 if (m_convert) {
252                     (*i)->set
253                     <Int>(PITCH, m_key.convertFrom(pitch, oldKey));
254                 } else {
255                     (*i)->set
256                     <Int>(PITCH, m_key.transposeFrom(pitch, oldKey));
257                 }
258 
259                 (*i)->unset(ACCIDENTAL);
260             }
261         }
262     }
263 }
264 
265 }
266