You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
8.6 KiB
263 lines
8.6 KiB
/*
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|