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