summaryrefslogtreecommitdiffstats
path: root/src/commands/notation/KeyInsertionCommand.cpp
blob: fc2bb848f640cb904f1051d0b044fd3096f814ec (plain)
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
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */

/*
    Rosegarden
    A MIDI and audio sequencer and musical notation editor.
 
    This program is Copyright 2000-2008
        Guillaume Laurent   <glaurent@telegraph-road.org>,
        Chris Cannam        <cannam@all-day-breakfast.com>,
        Richard Bown        <richard.bown@ferventsoftware.com>
 
    The moral rights of Guillaume Laurent, Chris Cannam, and Richard
    Bown to claim authorship of this work have been asserted.
 
    Other copyrights also apply to some parts of this work.  Please
    see the AUTHORS file and individual file headers for details.
 
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/


#include "KeyInsertionCommand.h"

#include "misc/Debug.h"
#include "base/Event.h"
#include "base/NotationTypes.h"
#include "base/Segment.h"
#include "base/SegmentNotationHelper.h"
#include "base/Studio.h"
#include "document/BasicCommand.h"
#include "base/BaseProperties.h"
#include <tqstring.h>


namespace Rosegarden
{

using namespace BaseProperties;


KeyInsertionCommand::KeyInsertionCommand(Segment &segment, timeT time,
        Key key,
        bool convert,
        bool transpose,
        bool transposeKey,
	bool ignorePercussion) :
        BasicCommand(getGlobalName(&key), segment, time, segment.getEndTime()),
        m_key(key),
        m_lastInsertedEvent(0),
        m_convert(convert),
        m_transpose(transpose),
        m_transposeKey(transposeKey),
	m_ignorePercussion(ignorePercussion)

{
    // nothing
}

KeyInsertionCommand::~KeyInsertionCommand()
{
    // nothing
}

void
KeyInsertionCommand::modifySegment()
{
    SegmentNotationHelper helper(getSegment());
    Key oldKey;

    if (m_convert || m_transpose) {
        oldKey = getSegment().getKeyAtTime(getStartTime());
    }

    Segment::iterator i = getSegment().findTime(getStartTime());
    while (getSegment().isBeforeEndMarker(i)) {
        if ((*i)->getAbsoluteTime() > getStartTime()) {
            break;
        }
        if ((*i)->isa(Key::EventType)) {
            getSegment().erase(i);
            break;
        }
        ++i;
    }

    // transpose if desired, according to new dialog option
    if (m_transposeKey) {
        // we don't really care about major/minor for this, so pass it through
        // from the original key
        bool keyIsMinor = m_key.isMinor();

        // get whether the original key is flat or sharp, so we know what to
        // prefer for the new key
        bool keyIsSharp = m_key.isSharp();

        // get the tonic pitch of the user-specified key, reported as a 0-11 int, then
        // add an extra octave to it to avoid winding up with negative numbers
        // (the octave will be stripped back off)
        int specifiedKeyTonic = m_key.getTonicPitch() + 12;

        // get the transpose factor for the segment we're working on
        int segTranspose = getSegment().getTranspose();

        // subtract the transpose factor from the tonic pitch of the
        // user-specified key, because we want to move in the opposite
        // direction for notation (eg. notation is in C major concert, at Bb
        // transposition, we have -2 from the segment, and want to go +2 for
        // the key, from tonic pitch 0 (C) to tonic pitch 2 (D) for the key as
        // written for a Bb instrument
        //
        // sanity check: 0 == C; 0 + 12 == 12; (12 - -2) % 12 == 2; 2 == D
        int transposedKeyTonic = (specifiedKeyTonic - segTranspose) % 12;

        // create a new key with the new tonic pitch, and major/minor from the
        // original key
        std::string newKeyName = "";

        switch (transposedKeyTonic) {
            // 0 C | 1 C# | 2 D | 3 D# | 4 E | 5 F | 6 F# | 7 G | 8 G# | 9 A | 10 A# | 11 B
        case 0 :  // C
            newKeyName = "C";
            break;
        case 2 :  // D
            newKeyName = "D";
            break;
        case 4 :  // E
            newKeyName = "E";
            break;
        case 5 :  // F
            newKeyName = "F";
            break;
        case 7 :  // G
            newKeyName = "G";
            break;
        case 9 :  // A
            newKeyName = "A";
            break;
        case 11:  // B
            newKeyName = "B";
            break;
            // the glorious, glorious black keys need special treatment
            // again, so we pick flat or sharp spellings based on the
            // condition of the original, user-specified key we're
            // transposing
        case 1 :  // C#/Db
            newKeyName = (keyIsSharp ? "C#" : "Db");
            break;
        case 3 :  // D#/Eb
            newKeyName = (keyIsSharp ? "D#" : "Eb");
            break;
        case 6 :  // F#/Gb
            newKeyName = (keyIsSharp ? "F#" : "Gb");
            break;
        case 8 :  // G#/Ab
            newKeyName = (keyIsSharp ? "G#" : "Ab");
            break;
        case 10:   // A#/Bb
            newKeyName = (keyIsSharp ? "A#" : "Bb");
            break;
        default:
            // if this fails, we won't have a valid key name, and
            // there will be some crashing exception I don't know how
            // to intercept and avoid, so I'm doing this lame failsafe
            // instead, which should never, ever actually run under
            // any conceivable cirumstance anyway
            RG_DEBUG << "KeyInsertionCommand: by the pricking of my thumbs, something wicked this way comes.  :("
            << endl;
            return ;
        }

        newKeyName += (keyIsMinor ? " minor" : " major");

        //for f in C# D# E# F# G# A# B# Cb Db Eb Fb Gb Ab Bb;do grep "$f
        //major" NotationTypes.C > /dev/null||echo "invalid key: $f
        //major";grep "$f minor" NotationTypes.C > /dev/null||echo "invalid
        //key: $f minor";done|sort
        //invalid key: A# major
        //invalid key: B# major
        //invalid key: B# minor
        //invalid key: Cb minor
        //invalid key: Db minor
        //invalid key: D# major
        //invalid key: E# major
        //invalid key: E# minor
        //invalid key: Fb major
        //invalid key: Fb minor
        //invalid key: Gb minor
        //invalid key: G# major

        // some kludgery to avoid creating invalid key names with some if/then
        // swapping to manually respell things generated incorrectly by the
        // above, rather than adding all kinds of nonsense to avoid this
        // necessity
        if (newKeyName == "A# major")
            newKeyName = "Bb major";
        else if (newKeyName == "B# major")
            newKeyName = "C major";
        else if (newKeyName == "Cb minor")
            newKeyName = "B minor";
        else if (newKeyName == "Db minor")
            newKeyName = "C# minor";
        else if (newKeyName == "D# major")
            newKeyName = "Eb major";
        else if (newKeyName == "E# major")
            newKeyName = "F major";
        else if (newKeyName == "E# minor")
            newKeyName = "F minor";
        else if (newKeyName == "Fb major")
            newKeyName = "E major";
        else if (newKeyName == "Fb minor")
            newKeyName = "E minor";
        else if (newKeyName == "Gb minor")
            newKeyName = "F# minor";
        else if (newKeyName == "G# major")
            newKeyName = "Ab major";

        // create a new key with the newly derived name, and swap it for the
        // user-specified version
        Key k(newKeyName);
        RG_DEBUG << "KeyInsertCommand: inserting transposed key" << endl
        << "        user key was: " << m_key.getName() << endl
        << "    tranposed key is: " << k.getName() << endl;
        m_key = k;
    } // if (m_transposeKey)

    i = helper.insertKey(getStartTime(), m_key);

    if (i != helper.segment().end()) {

        m_lastInsertedEvent = *i;
        if (!m_convert && !m_transpose)
            return ;

        while (++i != helper.segment().end()) {

            //!!! what if we get two keys at the same time...?
            if ((*i)->isa(Key::EventType))
                break;

            if ((*i)->isa(Note::EventType) &&
                    (*i)->has(PITCH)) {

                long pitch = (*i)->get
                             <Int>(PITCH);

                if (m_convert) {
                    (*i)->set
                    <Int>(PITCH, m_key.convertFrom(pitch, oldKey));
                } else {
                    (*i)->set
                    <Int>(PITCH, m_key.transposeFrom(pitch, oldKey));
                }

                (*i)->unset(ACCIDENTAL);
            }
        }
    }
}

}