]> git.cworth.org Git - scherzo/blob - scherzo-key.c
Fix high octave numbers (8+) to not be interpreted as 0.
[scherzo] / scherzo-key.c
1 /* scherzo - Music notation training
2  *
3  *      key.c - Common structures and functions for keys
4  *
5  * Copyright © 2013 Carl Worth
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see http://www.gnu.org/licenses/ .
19  */
20
21 #include <stdbool.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include "scherzo-key.h"
26
27 #define ARRAY_SIZE(arr) ((int) (sizeof(arr) / sizeof(arr[0])))
28
29 void
30 scherzo_key_init (scherzo_key_t *key, pitch_t pitch)
31 {
32     int i;
33
34     pitch_t flat_keys[] = {
35         PITCH_CLASS_LITERAL (C, NATURAL),
36         PITCH_CLASS_LITERAL (F, NATURAL),
37         PITCH_CLASS_LITERAL (B, FLAT),
38         PITCH_CLASS_LITERAL (E, FLAT),
39         PITCH_CLASS_LITERAL (A, FLAT),
40         PITCH_CLASS_LITERAL (D, FLAT),
41         PITCH_CLASS_LITERAL (G, FLAT),
42         PITCH_CLASS_LITERAL (C, FLAT)
43     };
44
45     pitch_t sharp_keys[] = {
46         PITCH_CLASS_LITERAL (C, NATURAL),
47         PITCH_CLASS_LITERAL (G, NATURAL),
48         PITCH_CLASS_LITERAL (D, NATURAL),
49         PITCH_CLASS_LITERAL (A, NATURAL),
50         PITCH_CLASS_LITERAL (E, NATURAL),
51         PITCH_CLASS_LITERAL (B, NATURAL),
52         PITCH_CLASS_LITERAL (F, SHARP),
53         PITCH_CLASS_LITERAL (C, SHARP),
54     };
55
56     /* Remove octave from 'pitch' */
57     pitch = PITCH_CLASS (pitch);
58
59     key->num_sharps = 0;
60     key->num_flats = 0;
61
62     /* First, look for a key that exactly matches the specified pitch. */
63     for (i = 0; i < ARRAY_SIZE (flat_keys); i++) {
64         if (flat_keys[i] == pitch) {
65             key->pitch = flat_keys[i];
66             key->num_flats = i;
67             return;
68         }
69     }
70
71     for (i = 0; i < ARRAY_SIZE (sharp_keys); i++) {
72         if (sharp_keys[i] == pitch) {
73             key->pitch = sharp_keys[i];
74             key->num_sharps = i;
75             return;
76         }
77     }
78
79     /* Second, if we haven't found a key, look for something enharmonic. */
80     for (i = 0; i < ARRAY_SIZE (flat_keys); i++) {
81         if (pitch_enharmonic_to (flat_keys[i], pitch)) {
82             key->pitch = flat_keys[i];
83             key->num_flats = i;
84             return;
85         }
86     }
87
88     for (i = 0; i < ARRAY_SIZE (sharp_keys); i++) {
89         if (pitch_enharmonic_to (sharp_keys[i], pitch)) {
90             key->pitch = sharp_keys[i];
91             key->num_sharps = i;
92             return;
93         }
94     }
95
96     /* The pitch_enharmonic_to function won't catch this wraparound. */
97     if (pitch == PITCH_CLASS_LITERAL (B, SHARP)) {
98         key->pitch = PITCH_CLASS_LITERAL (C, NATURAL);
99         return;
100     }
101
102     fprintf (stderr, "Interal error: Failed to find a key for %s.\n",
103              pitch_string (pitch));
104     exit (1);
105 }
106
107 bool
108 scherzo_key_contains_pitch (scherzo_key_t *key, pitch_t pitch)
109 {
110     pitch_accidental_t accidental = PITCH_ACCIDENTAL (pitch);
111     int i;
112
113     pitch_name_t sharps_order[] = {
114         PITCH_NAME_F,
115         PITCH_NAME_C,
116         PITCH_NAME_G,
117         PITCH_NAME_D,
118         PITCH_NAME_A,
119         PITCH_NAME_E,
120         PITCH_NAME_B
121     };
122
123     pitch_name_t flats_order[] = {
124         PITCH_NAME_B,
125         PITCH_NAME_E,
126         PITCH_NAME_A,
127         PITCH_NAME_D,
128         PITCH_NAME_G,
129         PITCH_NAME_C,
130         PITCH_NAME_F
131     };
132
133     if (accidental == PITCH_ACCIDENTAL_DOUBLE_SHARP ||
134         accidental == PITCH_ACCIDENTAL_DOUBLE_FLAT)
135     {
136         return false;
137     }
138
139     if (key->num_sharps) {
140         if (accidental == PITCH_ACCIDENTAL_FLAT)
141             return false;
142         for (i = 0; i < key->num_sharps; i++) {
143             if (sharps_order[i] == PITCH_NAME (pitch)) {
144                 if (accidental == PITCH_ACCIDENTAL_SHARP)
145                     return true;
146                 else
147                     return false;
148             }
149         }
150         if (accidental == PITCH_ACCIDENTAL_SHARP)
151             return false;
152         else
153             return true;
154     }
155
156     if (key->num_flats) {
157         if (accidental == PITCH_ACCIDENTAL_SHARP)
158             return false;
159         for (i = 0; i < key->num_flats; i++) {
160             if (flats_order[i] == PITCH_NAME (pitch)) {
161                 if (accidental == PITCH_ACCIDENTAL_FLAT)
162                     return true;
163                 else
164                     return false;
165             }
166         }
167         if (accidental == PITCH_ACCIDENTAL_FLAT)
168             return false;
169         else
170             return true;
171     }
172
173     if (accidental == PITCH_ACCIDENTAL_NATURAL)
174         return true;
175     else
176         return false;
177 }
178
179 pitch_t
180 scherzo_key_spell_pitch (scherzo_key_t *key, pitch_t pitch)
181 {
182     pitch_t root = key->pitch;
183     int half_steps = pitch_from_root_in_half_steps (pitch, root);
184     pitch_name_t new_pitch_name;
185     int i;
186     int half_steps_per_degree[] = {
187         0, 2, 4, 5, 7, 9, 11
188     };
189
190     for (i = 0; i < ARRAY_SIZE (half_steps_per_degree); i++)
191         if (half_steps_per_degree[i] == half_steps)
192             goto WITHIN_SCALE;
193
194     return pitch;
195
196 WITHIN_SCALE:
197     new_pitch_name = (PITCH_NAME (root) + i) % 7;
198
199     /* Original pitch is spelled correctly. Return it as is. */
200     if (new_pitch_name == PITCH_NAME (pitch))
201         return pitch;
202
203     return pitch_spell_as_degree (pitch, root, i + 1);
204 }