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