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 "IntervalDialog.h"
20 #include <QLayout>
21
22 #include <iostream>
23 #include "misc/Strings.h"
24 #include "base/MidiDevice.h"
25 #include "base/NotationRules.h"
26 #include <QComboBox>
27 #include <QDialog>
28 #include <QDialogButtonBox>
29 #include <QFrame>
30 #include <QGroupBox>
31 #include <QCheckBox>
32 #include <QLabel>
33 #include <QRadioButton>
34 #include <QSizePolicy>
35 #include <QString>
36 #include <QWidget>
37 #include <QVBoxLayout>
38
39
40 namespace Rosegarden
41 {
42
IntervalDialog(QWidget * parent,bool askChangeKey,bool askTransposeSegmentBack)43 IntervalDialog::IntervalDialog(QWidget *parent, bool askChangeKey, bool askTransposeSegmentBack) :
44 QDialog(parent)
45 {
46 setModal(true);
47 setWindowTitle(tr("Specify Interval"));
48
49 QGridLayout *metagrid = new QGridLayout;
50 setLayout(metagrid);
51
52 QWidget *vBox = new QWidget;
53 QVBoxLayout *vBoxLayout = new QVBoxLayout;
54 metagrid->addWidget(vBox, 0, 0);
55
56 QWidget *hBox = new QWidget;
57 vBoxLayout->addWidget(hBox);
58 QHBoxLayout *hBoxLayout = new QHBoxLayout;
59
60 m_referencenote = new DiatonicPitchChooser(tr("Reference note:"), hBox );
61 hBoxLayout->addWidget(m_referencenote);
62 m_targetnote = new DiatonicPitchChooser(tr("Target note:"), hBox );
63 hBoxLayout->addWidget(m_targetnote);
64 hBox->setLayout(hBoxLayout);
65
66 intervalChromatic = 0;
67 intervalDiatonic = 0;
68
69 m_intervalLabel = new QLabel(tr("a perfect unison"), vBox );
70 vBoxLayout->addWidget(m_intervalLabel);
71 m_intervalLabel->setAlignment(Qt::AlignCenter);
72 QFont font(m_intervalLabel->font());
73 font.setItalic(true);
74 m_intervalLabel->setFont(font);
75
76 if (askChangeKey) {
77 QGroupBox *affectKeyGroup = new QGroupBox( tr("Effect on Key"), vBox );
78 QVBoxLayout *affectKeyGroupLayout = new QVBoxLayout;
79 vBoxLayout->addWidget(affectKeyGroup);
80 m_transposeWithinKey = new QRadioButton(tr("Transpose within key"));
81 affectKeyGroupLayout->addWidget(m_transposeWithinKey);
82 m_transposeWithinKey->setChecked(true);
83 m_transposeChangingKey = new QRadioButton(tr("Change key for selection"));
84 affectKeyGroupLayout->addWidget(m_transposeChangingKey);
85 affectKeyGroup->setLayout(affectKeyGroupLayout);
86 } else {
87 m_transposeChangingKey = nullptr;
88 m_transposeWithinKey = nullptr;
89 }
90
91 if (askTransposeSegmentBack) {
92 m_transposeSegmentBack = new QCheckBox(tr("Adjust segment transposition in opposite direction (maintain audible pitch)"), vBox );
93 vBoxLayout->addWidget(m_transposeSegmentBack);
94 m_transposeSegmentBack->setTristate(false);
95 m_transposeSegmentBack->setChecked(false);
96 } else {
97 m_transposeSegmentBack = nullptr;
98 }
99
100 vBox->setLayout(vBoxLayout);
101
102 connect(m_referencenote, &DiatonicPitchChooser::noteChanged,
103 this, &IntervalDialog::slotSetReferenceNote);
104
105 connect(m_targetnote, &DiatonicPitchChooser::noteChanged,
106 this, &IntervalDialog::slotSetTargetNote);
107 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
108 metagrid->addWidget(buttonBox, 1, 0);
109 metagrid->setRowStretch(0, 10);
110 connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
111 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
112 }
113
114 // number of octaves the notes are apart
115 int
getOctaveDistance()116 IntervalDialog::getOctaveDistance()
117 {
118 return m_targetnote->getOctave() - m_referencenote->getOctave();
119 }
120
121 // chromatic distance between the steps, not taking account octaves or
122 // accidentals
123 int
getStepDistanceChromatic()124 IntervalDialog::getStepDistanceChromatic()
125 {
126 return scale_Cmajor[m_targetnote->getStep()] - scale_Cmajor[m_referencenote->getStep()];
127 // - getChromaticStepValue(m_referencestep->currentIndex());
128 //return m_targetnote->getPitch() - m_referencenote->getPitch();
129 }
130
131 // correction due to accidentals
132 int
getAccidentalCorrectionChromatic()133 IntervalDialog::getAccidentalCorrectionChromatic()
134 {
135 return m_targetnote->getAccidental() - m_referencenote->getAccidental();
136 }
137
138 int
getDiatonicDistance()139 IntervalDialog::getDiatonicDistance()
140 {
141 return getOctaveDistance() * 7 + m_targetnote->getStep() - m_referencenote->getStep();
142 }
143
144 int
getChromaticDistance()145 IntervalDialog::getChromaticDistance()
146 {
147 return getOctaveDistance() * 12 + getStepDistanceChromatic() + getAccidentalCorrectionChromatic();
148 }
149
150 QString
getIntervalName(int intervalDiatonic,int intervalChromatic)151 IntervalDialog::getIntervalName(int intervalDiatonic, int intervalChromatic)
152 {
153 // displayInterval: an intervalDiatonic of -3 will yield a displayInterval of 3 and
154 // set the boolean 'down' to true.
155 int displayIntervalDiatonic = intervalDiatonic;
156 int displayIntervalChromatic = intervalChromatic;
157 bool down = (intervalDiatonic < 0 ||
158 (intervalDiatonic == 0 &&
159 intervalChromatic < 0));
160 if (down) {
161 displayIntervalDiatonic = -displayIntervalDiatonic;
162 displayIntervalChromatic = -displayIntervalChromatic;
163 }
164
165 int octaves = displayIntervalDiatonic / 7;
166 int deviation = displayIntervalChromatic % 12 - scale_Cmajor[displayIntervalDiatonic % 7];
167 // Note (hjj):
168 // "1 octave and a diminished octave" is better than
169 // "2 octaves and a diminished unison"
170 if (displayIntervalDiatonic % 7 == 0) {
171 if (octaves > 0) {
172 deviation = (deviation < 5 ? deviation : deviation - 12);
173 } else if (octaves < 0) {
174 deviation = (deviation < 5 ? -deviation : 12 - deviation);
175 }
176 } else if (down) {
177 // Note (hjj):
178 // an augmented prime down, NOT a diminished prime down
179 deviation = -deviation;
180 }
181
182 // show the step for an unison only if the octave doesn't change, any other interval
183 // always, and augmented/dimnished unisons (modulo octaves) always.
184 bool showStep = displayIntervalDiatonic == 0 ||
185 displayIntervalDiatonic % 7 != 0 || deviation != 0;
186
187 QString textInterval = "";
188 QString textIntervalDeviated = "";
189 if (showStep) {
190 switch (displayIntervalDiatonic % 7) {
191 // First the diminished/perfect/augmented:
192 case 0: // unison or octaves
193 case 3: // fourth
194 case 4: // fifth
195 if (deviation == -1)
196 textIntervalDeviated += tr("a diminished");
197 else if (deviation == 1)
198 textIntervalDeviated += tr("an augmented");
199 else if (deviation == -2)
200 textIntervalDeviated += tr("a doubly diminished");
201 else if (deviation == 2)
202 textIntervalDeviated += tr("a doubly augmented");
203 else if (deviation == -3)
204 textIntervalDeviated += tr("a triply diminished");
205 else if (deviation == 3)
206 textIntervalDeviated += tr("a triply augmented");
207 else if (deviation == -4)
208 textIntervalDeviated += tr("a quadruply diminished");
209 else if (deviation == 4)
210 textIntervalDeviated += tr("a quadruply augmented");
211 else if (deviation == 0)
212 textIntervalDeviated += tr("a perfect");
213 else
214 textIntervalDeviated += tr("an (unknown, %1)").arg(deviation);
215 break;
216 // Then the major/minor:
217 case 1: // second
218 case 2: // third
219 case 5: // sixth
220 case 6: // seventh
221 if (deviation == -1)
222 textIntervalDeviated += tr("a minor");
223 else if (deviation == 0)
224 textIntervalDeviated += tr("a major");
225 else if (deviation == -2)
226 textIntervalDeviated += tr("a diminished");
227 else if (deviation == 1)
228 textIntervalDeviated += tr("an augmented");
229 else if (deviation == -3)
230 textIntervalDeviated += tr("a doubly diminished");
231 else if (deviation == 2)
232 textIntervalDeviated += tr("a doubly augmented");
233 else if (deviation == -4)
234 textIntervalDeviated += tr("a triply diminished");
235 else if (deviation == 3)
236 textIntervalDeviated += tr("a triply augmented");
237 else if (deviation == 4)
238 textIntervalDeviated += tr("a quadruply augmented");
239 else
240 textIntervalDeviated += tr("an (unknown, %1)").arg(deviation);
241 break;
242 default:
243 textIntervalDeviated += tr("an (unknown)");
244 }
245 switch (displayIntervalDiatonic % 7) {
246 case 0:
247 // Note (hjj):
248 // "1 octave and a diminished octave" is better than
249 // "2 octaves and a diminished unison"
250 if (octaves > 0) {
251 textInterval += tr("%1 octave").arg(textIntervalDeviated);
252 octaves--;
253 } else if (octaves < 0) {
254 textInterval += tr("%1 octave").arg(textIntervalDeviated);
255 octaves++;
256 } else {
257 textInterval += tr("%1 unison").arg(textIntervalDeviated);
258 }
259 break;
260 case 1:
261 textInterval += tr("%1 second").arg(textIntervalDeviated);
262 break;
263 case 2:
264 textInterval += tr("%1 third").arg(textIntervalDeviated);
265 break;
266 case 3:
267 textInterval += tr("%1 fourth").arg(textIntervalDeviated);
268 break;
269 case 4:
270 textInterval += tr("%1 fifth").arg(textIntervalDeviated);
271 break;
272 case 5:
273 textInterval += tr("%1 sixth").arg(textIntervalDeviated);
274 break;
275 case 6:
276 textInterval += tr("%1 seventh").arg(textIntervalDeviated);
277 break;
278 default:
279 textInterval += tr("%1").arg(textIntervalDeviated);
280 }
281 }
282
283 if (displayIntervalChromatic != 0 || displayIntervalDiatonic != 0) {
284 if (!down) {
285 if (octaves != 0) {
286 if (showStep) {
287 return tr("up %n octave(s) and %1", "", octaves)
288 .arg(textInterval);
289 } else {
290 return tr("up %n octave(s)", "", octaves);
291 }
292 } else {
293 return tr("up %1").arg(textInterval);
294 }
295 } else {
296 if (octaves != 0) {
297 if (showStep) {
298 return tr("down %n octave(s) and %1", "", octaves)
299 .arg(textInterval);
300 } else {
301 return tr("down %n octave(s)", "", octaves);
302 }
303 } else {
304 return tr("down %1").arg(textInterval);
305 }
306 }
307 } else {
308 return tr("a perfect unison");
309 }
310 }
311
312 void
slotSetTargetNote(int pitch,int octave,int step)313 IntervalDialog::slotSetTargetNote(int pitch, int octave, int step)
314 {
315 intervalChromatic = pitch - m_referencenote->getPitch();
316 intervalDiatonic = (octave * 7 + step) - (m_referencenote->getOctave() * 7 + m_referencenote->getStep());
317
318 m_intervalLabel->setText( getIntervalName( intervalDiatonic, intervalChromatic ) );
319 }
320
321 void
slotSetReferenceNote(int pitch,int octave,int step)322 IntervalDialog::slotSetReferenceNote(int pitch, int octave, int step)
323 {
324 // recalculate target note based on reference note and current interval
325 int pitch_new = pitch + intervalChromatic;
326 int diatonic_new = (octave * 7 + step) + intervalDiatonic;
327 int octave_new = diatonic_new / 7;
328 int step_new = diatonic_new % 7;
329
330 m_targetnote->slotSetNote( pitch_new, octave_new, step_new );
331 }
332
333 bool
getChangeKey()334 IntervalDialog::getChangeKey()
335 {
336 if (m_transposeChangingKey == nullptr) {
337 return false;
338 } else {
339 return m_transposeChangingKey->isChecked();
340 }
341 }
342
343 bool
getTransposeSegmentBack()344 IntervalDialog::getTransposeSegmentBack()
345 {
346 if (m_transposeSegmentBack == nullptr) {
347 return false;
348 } else {
349 return m_transposeSegmentBack->isChecked();
350 }
351 }
352
353 }
354