Сигурни сме, че в курс с ~70 човека има поне 4-5 от вас, които притежават китари, седящи тихичко в някой ъгъл или шкаф и бавно събиращи прах. Може би на тях е свирено, а може би са купени или получени като подарък, но така и не сте прогресирали с онзи плейлист с туториъли na JustinGuitar и все още единственото, което умеете, са онези 3 акорда, с които можете да изсвирите Wonderwall. Today is gonna be the day… Спирам.
Преди да продължим с домашното, ето ви нещо за настроение:
Е, няма да ви караме да вадите китарата и да започвате пак да се учите, макар че ако се надъхате - ще сме доволни, че сме ви вдъхновили и с удоволствие бихме си поговорили с вас по темата. Вместо това, ще ви научим на малко…
Основен градивен елемент на всяко едно музикално произведение и негова най-атомарна част са, you guessed it, тоновете. Тоновете са най-просто казано вибрацията на дадено тяло, които трептят с определена честота. Отвъд това няма да си говорим за физичните свойства на тоновете, макар че и там има какво интересно да се каже.
В западната музика най-често използваме 12 такива предефинирани честоти, определени от различните височини (i.e. скоростта на трептенето, честотата) на тоновете. Тоновете назоваваме и записваме с ноти, които си имат определени имена. Много от вас вероятно ще се досетят веднага за До-Ре-Ми-Фа-Сол-Ла-Си (за разлика от в училище, последното "До" няма да го броим два пъти), но това са само 8 ноти / тона, а не 12?!
Преди визуализацията на въпросната концепция с клавиши на пиано, искаме да направим едно уточнение - за целите на домашното ще работим със западните имена на нотите, а не както по-горе. Това по-горе беше, за да ви припомним часовете по музика, но занапред ще искаме да работим с латинските букви, обозначаващи въпросните тонове:
Та, защо 12, а не 8?! Ако си представите как изглеждат клавишите на пианото, вероятно ще се сетите, че има бели клавиши и между някои от тях - по-малки, черни клавиши. Белите клавиши отговарят на 8-те тона, които изброихме по-горе. Черните са "междинни" тонове между някои от нотите. Тези тонове, между познатите ни ноти, отбелязваме с # или "диез" (да, знаем, че има и алтернатива, но няма да я споменаваме, защото на тези от вас, които нямат грам понятие от тези концепции, ще им дойде в повече).
Ето визуален пример за това как изглеждат всичките 12 тона / ноти спрямо клавишите на пиано:
Тези 12 ноти дефинират една "октава". Причината в училище (а и на горната картинка) да ни учеха да повтаряме "До" накрая е, че след като "приключат" тоновете от тази октава - все пак има по-високи и следващата октава отново работи със същите 12 имена на тоновете. Следователно след C, D, E, F, G, A, B… Имаме пак C… И после D, и така нататък. За целите на днешното домашно няма да взимаме предвид концепциите за октави, но е важно да възприемете тоновете като последователни и циклични, а не сякаш приключват със B. Ето ви картинка за по-лесно илюстиране:
На този етап вероятно се чудите какво определя наличието на тези 12 тона и защо има "междинни" такива. Тук трябва да си поговорим за…
Интервалите, най-просто казано, са разстоянията между височините на два тона. Между всеки два от дванайсетте тона има интервал от точно един полутон. С други думи, ако започнем от C, всяка стъпка по клавишите на пианото, включвайки и белите и черните клавиши, е през интервал от един полутон. C->C# - един полутон, C#->D - един полутон, E->F - един полутон и така нататък.
Следователно ако скочим с два клавиша, получаваме интервал от 2 стъпки, 2 полутона или можем да го наречем "цял тон". Разстоянието, интервалът между C и D - два полутона / един тон, между C# и D# - същото. Съответно имаме интервали от по 3, 4, 5 и така до 12 скока разстояние, където вече се "връщаме" в оригиналния тон, от който сме започнали да броим.
Всеки от тези интервали си има определено име, ние ще използваме имената на английски:
На български има по-готини имена като "мажорна терца", "квинта", "септима" и прочие, но за домашното ще се придържаме към английските такива.
Остана ни едно последно нещо, преди да можем да пристъпим към условието на домашното.
Основата на акордите всъщност е доста проста - комбинацията от два или повече (различни) тона образува акорд. С други думи ако изсвирим заедно C и G - вече имаме акорд (по-конкретно C5). Ако към този акорд добавим още един тон - получаваме нов акорд. Ако направим комбинация от C, E и G - получаваме акорд дефиниран от тези 3 тона (в този случай - C). Ще забележите, че и двата акорда са с имена, производни на нотата C. Тази нота наричаме "основен тон" или "root", а акордът назоваваме на базата на производния тон.
Добре де, но в единия случай имаме C5, в другия C, а ако вземем да направим акорд от C, D# и G, ще получим още по-интересното Cm (C minor). Оттук идва и популярната фраза "Fingering A minor" (ако root ни беше A, разбира се). Защо?
Тук идва и идеята за "качествата" на акорда. Не, не колко добре сме го изсвирили, това е друга тема. Няма да задълбаваме безкрайно в това, тъй като е обект на поне двусеместриален курс, част от бакалавърска степен в Музикалната академия, но това, което искаме да знаем, е, че "качествата" на акорда са директно последствие от взаимоотношенията между отделните тонове в акорда. Това, върху което ще се фокусираме, е наличието или липсата на minor или major 3rd, но за това - малко по-натам в условието.
Tone
Напишете клас Tone
, който да описва понятието за музикален тон. Искаме да може да се инстанцира по следния начин:
c_sharp = Tone("C#") # At least not Java
Искаме да имаме стрингова репрезентация на тоновете, работеща по следния начин:
print(str(c_sharp))
# "C#"
Уговорка: Както споменахме по-горе - за целите на домашното няма да се занимаваме с октавите на дадените тонове, приемаме, че всички тонове C са едно и също.
Interval
Напишете клас Interval
, който да описва понятието за интервал. Искаме да може да се инстанцира по следния начин:
number_of_semitones = 3 # Където 3 е брой полутонове разстояние или "стъпки" според горните обяснения
minor_third = Interval(number_of_semitones)
Искаме да имаме стрингова репрезентация на интервалите, работеща по следния начин:
print(str(minor_third))
# "minor 3rd"
# С малки букви
Уговорка: Интервалите могат да приемат произволни естествени числа, като Interval(13)
е същото като Interval(1)
и неговата репрезентация би била minor 2nd
. С други думи отново се абстрахираме от октавите.
Chord
Напишете клас Chord
, който да описва понятието за акорд. Искаме да може да се инстанцира по следния начин:
c, d_sharp, g = Tone("C"), Tone("D#"), Tone("G")
c_minor_chord = Chord(c, d_sharp, g)
Имаме следните уговорки:
TypeError
с текст "Cannot have a chord made of only 1 unique tone"
.Още един пример:
c, another_c = Tone("C"), Tone("C")
sissy_chord = Chord(c, another_c)
Traceback (most recent call last):
File ...
...
TypeError: Cannot have a chord made of only 1 unique tone
Искаме да имаме стрингова репрезентация на акордите, работеща по следния начин:
print(str(c_minor_chord))
# "C-D#-G"
# Описание на всички съдържащи се тонове, започващи от "root"-а,
# подредени по редът си в хроматичната скала (C, C#, D, D#... B), разделени от тире
# Както казахме по-горе - повторения няма
c, another_c, f = Tone("C"), Tone("C"), Tone("F")
csus4_chord = Chord(c, f, another_c) # Спокойно, не е важно какъв точно е този акорд
print(str(csus4_chord))
# "C-F"
f, c, d, a, g = Tone("F"), Tone("C"), Tone("D"), Tone("A"), Tone("G")
f_sixth_ninth_chord = Chord(f, c, d, a, g)
print(str(f_sixth_ninth_chord))
# "F-G-A-C-D"
# Root-ът е водещ, оттам насетне всички са подредени спрямо редът си в скалата
# релативно спрямо основния тон
За да не навлизаме в дълбините на "качествата" на акордите, ще се ограничим до следните 3 качества - "минорен", "мажорен" и "power" (ако някой знае как е на български - да каже) акорди, определени от следните методи:
Chord.is_minor
Ако акордът има в себе си тон, който заедно с основният (root) образува "minor 3rd" - той е минорен и функцията трябва да ни върне True
. В противен случай - False
:
c_minor_chord = Chord(Tone("C"), Tone("D#"), Tone("G"))
print(c_minor_chord.is_minor())
# True
# Тонът D# и root-ът C са на разстояние един от друг, образуващо интервал "minor 3rd"
c_not_minor_chord = Chord(Tone("C"), Tone("D"), Tone("G"))
print(c_not_minor_chord.is_minor())
# False
# Няма тон, който заедно с root-а да образува "minor 3rd", така че приемаме, че акордът не е минорен
Chord.is_major
Ако акордът има в себе си тон, който заедно с основният (root) образува "major 3rd" - той е мажорен и функцията трябва да ни върне True
. В противен случай - False
:
c_major_chord = Chord(Tone("C"), Tone("E"), Tone("G"))
print(c_major_chord.is_major())
# True
# Тонът E и root-ът C са на разстояние един от друг, образуващо интервал "major 3rd"
c_not_major_chord = Chord(Tone("C"), Tone("D"), Tone("G"))
print(c_not_major_chord.is_major())
# False
# Няма тон, който заедно с root-а да образува "major 3rd", така че приемаме, че акордът не е мажорен
Chord.is_power_chord
Ако акордът няма в себе си тон, който заедно с основният (root) образува "minor 3rd", нито пък "major 3rd" - той е power акорд и функцията трябва да ни върне True
. В противен случай - False
:
c_power_chord = Chord(Tone("C"), Tone("F"), Tone("G"))
print(c_power_chord.is_power_chord())
# True
# Нямаме нито "minor 3rd", нито "major 3rd" интервал спрямо основния тон
# И преди някой, който е бил в дебрите на музикалната теория да ни направи забележка,
# че въпросният акорд всъщност не е power chord, а суспендиран (или там както се води
# на български) - искате ли да ви караме да отбелязвате и суспендираните акорди? :D
c_not_power_chord = Chord(Tone("C"), Tone("E"), Tone("G"))
print(c_not_power_chord.is_power_chord())
# False
# Тонът E и root-ът C са на разстояние един от друг, образуващо интервал "major 3rd", следователно нямаме power chord
Искаме да имаме възможност да извършваме следните операции с обектите от горните класове:
Искаме да можем да събираме обекти от клас Tone
и резултат от операцията да бъде обект от клас Chord
:
c, g = Tone("C"), Tone("G")
result_chord = c + g # result_chord е обект от тип Chord
print(result_chord)
# "C-G"
Искаме да можем да изваждаме обекти от клас Tone
и резултат от операцията да бъде обект от клас Interval
:
c, g = Tone("C"), Tone("G")
result_interval = g - c # result_interval е обект от тип Interval
print(result_interval)
# "perfect 5th"
Искаме да можем да събираме тонове с интервали и резултат от операцията да бъде нов тон с променена височина:
c = Tone("C")
perfect_fifth = Interval(7)
result_tone = c + perfect_fifth
print(result_tone)
# "G"
c = Tone("C")
result_tone = c + Interval(12)
print(result_tone)
# "C"
# Тук забелязваме "цикличността" на 12-те тона - тонът е отново C, тъй като не се
# ангажираме с концепцията за октави
g = Tone("G")
perfect_fifth = Interval(7)
result_tone = g + perfect_fifth
print(result_tone)
# "D"
Забележка: Искаме това да работи само когато тонът е отляво на знакът за събиране. Когато тонът е отдясно, го приемаме за недефинирано поведение и искаме да се възбуди TypeError
с текст "Invalid operation"
. Семпло, но няма да ви вгорчаваме живота… Повече.
Искаме да можем да изваждаме интервал от тон и резултат от операцията да бъде нов тон с променена височина, но този път в обратна посока, т.е. "надолу":
c = Tone("C")
perfect_fifth = Interval(7)
result_tone = c - perfect_fifth
print(result_tone)
# "F"
# Тук отново забелязваме "цикличността" на 12-те тона
Забележка: Искаме това да работи само когато тонът е отляво на знакът за изваждане. Когато тонът е отдясно, го приемаме за недефинирано поведение и искаме да се възбуди TypeError
с текст "Invalid operation"
. Ако искате може и друг… Просто ще ви фейлнат тестовете.
Искаме да можем да събираме обекти от клас Interval
и резултат от операцията да бъде нов обект от клас Interval
:
perfect_fifth = Interval(7)
minor_third = Interval(3)
result_interval = perfect_fifth + minor_third
print(result_interval)
# "minor 7th"
Искаме да можем да събираме акорди с тонове и резултат от операцията да бъде нов акорд:
c5_chord = Chord(Tone("C"), Tone("G"))
result_chord = c5_chord + Tone("E")
print(result_chord)
# C-Е-G
Искаме да можем да изваждаме тон от акорд. Ако акордът съдържа три или повече, тона ще се върне нов акорд, в противен случай ще го третираме като недефинирано поведение и искаме да се възбуди TypeError
с текст "Cannot have a chord made of only 1 unique tone"
.
c_major_chord = Chord(Tone("C"), Tone("E"), Tone("G"))
result_chord = c_major_chord - Tone("E")
print(result_chord)
# C-G
c_power_chord = Chord(Tone("C"), Tone("G"))
result_chord = c_power_chord - Tone("G")
Traceback (most recent call last):
File ...
...
TypeError: Cannot have a chord made of only 1 unique tone
Изваждането на несъществуващ тон също е недефинирано поведение, отново искаме TypeError
, но този път искаме текстът да бъде "Cannot remove tone <Tone> from chord <Chord>"
, където <Tone>
и <Chord>
са съотвените стрингови репрезентации на тона и акорда:
c_power_chord = Chord(Tone("C"), Tone("G"))
result_chord = c_power_chord - Tone("E")
Traceback (most recent call last):
File ...
...
TypeError: Cannot remove tone E from chord C-G
Искаме да можем да събираме обекти от клас Chord
и резултат от операцията да бъде нов обект от клас Chord
:
c5_chord = Chord(Tone("C"), Tone("G"))
this_other_chord = Chord(Tone("A"), Tone("B"))
result_chord = c5_chord + this_other_chord
print(result_chord)
# "C-G-A-B"
Chord.transposed
Умишлено оставихме този метод за накрая, тъй като горните операции описват поведението на тоновете когато ги "изместваме" нагоре или надолу, което е важно за финалната ни функция.
Последен термин, обещавам… Искаме да можем да "транспонираме" даден акорд, т.е. да изместим всеки един от тоновете му с един и същ интервал. Интервалът в нашият случай ще бъде обект от тип Interval
, а промяната на тоновете следва да може да се случва както нагоре, така и надолу, определено от знака пред интервала. Тъй като методът е transposed - искаме като резултат да ни връща нов обект от тип Chord
.
Ето и пример:
print(str(c_minor_chord))
# "C-D#-G"
d_minor_chord = c_minor_chord.transposed(Interval(2))
print(str(d_minor_chord))
# "D-F-A"
a_sharp_minor_chord = d_minor_chord.transposed(-Interval(4))
print(str(a_sharp_minor_chord))
# "A#-C#-F"
Утре (31.10) ще говорим за това. И все пак, за хората, които много бързат, в Python грешки се хвърлят по следния начин:
raise TypeError("текст")
That's pretty much it, поне за целите на домашното.
Утре - по-интересни неща по темата.
1import unittest
2
3import solution
4
5
6class TestSanity(unittest.TestCase):
7 """Check if all of the classes are present."""
8
9 def test_tone(self):
10 self.assertIn('Tone', dir(solution), 'Убеди се, че класът "Tone" е наличен с точно това име.')
11 self.assertTrue(isinstance(solution.Tone, type), 'Убеди се, че "Tone" е клас.')
12
13 def test_interval(self):
14 self.assertIn('Interval', dir(solution), 'Убеди се, че класът "Interval" е наличен с точно това име.')
15 self.assertTrue(isinstance(solution.Interval, type), 'Убеди се, че "Interval" е клас.')
16
17 def test_chord(self):
18 self.assertIn('Chord', dir(solution), 'Убеди се, че класът "Chord" е наличен с точно това име.')
19 self.assertTrue(isinstance(solution.Chord, type), 'Убеди се, че "Chord" е клас.')
20
21
22if __name__ == '__main__':
23 unittest.main()
1import unittest
2
3from solution import *
4
5
6CANNOT_CHORD_THIS_CHORD = "Cannot have a chord made of only 1 unique tone"
7CANNOT_REMOVE_TONE = "Cannot remove tone {0} from chord {1}"
8INVALID_OPERATION = "Invalid operation"
9
10
11class TestBasicToneFunctionality(unittest.TestCase):
12 """Test the basic functionality of the Tone class (operations excluded)."""
13 TONES = "CDEFGAB"
14 SHARPS = (f"{tone}#" for tone in "CDFGA")
15
16 def test_tone_str(self):
17 for tone in self.TONES:
18 self.assertEqual(str(Tone(tone)), tone)
19 for sharp_letter in self.SHARPS:
20 self.assertEqual(str(Tone(sharp_letter)), sharp_letter)
21
22
23class TestBasicIntervalFunctionality(unittest.TestCase):
24 """Test the basic functionality of the Interval class (operations excluded)."""
25 INTERVALS = (
26 "unison",
27 "minor 2nd",
28 "major 2nd",
29 "minor 3rd",
30 "major 3rd",
31 "perfect 4th",
32 "diminished 5th",
33 "perfect 5th",
34 "minor 6th",
35 "major 6th",
36 "minor 7th",
37 "major 7th"
38 )
39
40 def test_interval_str(self):
41 for index, interval in enumerate(self.INTERVALS):
42 self.assertEqual(str(Interval(index)), interval)
43
44 def test_interval_overflow(self):
45 major_7th = Interval(11 + 12**2)
46 self.assertEqual(str(major_7th), "major 7th")
47
48 def test_interval_negative(self):
49 major_7th = Interval(11)
50 minor_2nd = -major_7th
51 self.assertEqual(str(minor_2nd), "minor 2nd")
52
53
54class TestBasicChordFunctionality(unittest.TestCase):
55 """Test the basic functionality of the Chord class (operations excluded)."""
56
57 def test_chord_str(self):
58 a_major_tones = (Tone(tone) for tone in ["A", "C#", "E"])
59 a_major_chord = Chord(*a_major_tones)
60 self.assertEqual(str(a_major_chord), "A-C#-E")
61
62 def test_chord_ordering(self):
63 f_major_seventh_tones = (Tone(tone) for tone in "FCEA")
64 f_sixth_ninth_chord = Chord(*f_major_seventh_tones)
65 self.assertEqual(str(f_sixth_ninth_chord), "F-A-C-E")
66
67 f_sixth_ninth_tones = (Tone(tone) for tone in "FDAG")
68 f_sixth_ninth_chord = Chord(*f_sixth_ninth_tones)
69 self.assertEqual(str(f_sixth_ninth_chord), "F-G-A-D")
70
71 def test_chord_tone_repetition(self):
72 a_minor_tones = (Tone(tone) for tone in "AAAAAAACCCCCCCCCCCCCCEEEEEEEEEEEEEEEEEE")
73 a_minor_chord = Chord(*a_minor_tones)
74 self.assertEqual(str(a_minor_chord), "A-C-E")
75
76 def test_chord_not_enough_tones(self):
77 a_sharp = Tone("A#")
78 with self.assertRaises(TypeError) as err:
79 Chord(a_sharp)
80 self.assertEqual(str(err.exception), CANNOT_CHORD_THIS_CHORD)
81
82 tones = (Tone(tone) for tone in ["A#", "A#", "A#"])
83 with self.assertRaises(TypeError) as err:
84 Chord(*tones)
85 self.assertEqual(str(err.exception), CANNOT_CHORD_THIS_CHORD)
86
87 def test_is_minor(self):
88 a_minor_tones = (Tone(tone) for tone in "AEC")
89 a_minor_chord = Chord(*a_minor_tones)
90 self.assertTrue(a_minor_chord.is_minor())
91
92 a_major_tones = (Tone(tone) for tone in ["A", "C#", "E"])
93 a_major_chord = Chord(*a_major_tones)
94 self.assertFalse(a_major_chord.is_minor())
95
96 a_kind_of_power_chord_tones = (Tone(tone) for tone in ["A", "B", "D", "E"])
97 a_kind_of_power_chord = Chord(*a_kind_of_power_chord_tones)
98 self.assertFalse(a_kind_of_power_chord.is_minor())
99
100 def test_is_major(self):
101 a_minor_tones = (Tone(tone) for tone in "AEC")
102 a_minor_chord = Chord(*a_minor_tones)
103 self.assertFalse(a_minor_chord.is_major())
104
105 a_major_tones = (Tone(tone) for tone in ["A", "C#", "E"])
106 a_major_chord = Chord(*a_major_tones)
107 self.assertTrue(a_major_chord.is_major())
108
109 a_kind_of_power_chord_tones = (Tone(tone) for tone in ["A", "B", "D", "E"])
110 a_kind_of_power_chord = Chord(*a_kind_of_power_chord_tones)
111 self.assertFalse(a_kind_of_power_chord.is_major())
112
113 def test_is_power_chord(self):
114 a_minor_tones = (Tone(tone) for tone in "AEC")
115 a_minor_chord = Chord(*a_minor_tones)
116 self.assertFalse(a_minor_chord.is_power_chord())
117
118 a_major_tones = (Tone(tone) for tone in ["A", "C#", "E"])
119 a_major_chord = Chord(*a_major_tones)
120 self.assertFalse(a_major_chord.is_power_chord())
121
122 a_kind_of_power_chord_tones = (Tone(tone) for tone in ["A", "B", "D", "E"])
123 a_kind_of_power_chord = Chord(*a_kind_of_power_chord_tones)
124 self.assertTrue(a_kind_of_power_chord.is_power_chord())
125
126 def test_transposed(self):
127 d_major_tones = (Tone(tone) for tone in ["D", "F#", "A"])
128 d_major_chord = Chord(*d_major_tones)
129 result_chord = d_major_chord.transposed(Interval(2))
130 self.assertIsInstance(result_chord, Chord)
131 self.assertEqual(str(result_chord), "E-G#-B")
132
133 def test_transposed_negative(self):
134 e_minor_tones = (Tone(tone) for tone in "EGB")
135 e_minor_chord = Chord(*e_minor_tones)
136 result_chord = e_minor_chord.transposed(-Interval(2))
137 self.assertIsInstance(result_chord, Chord)
138 self.assertEqual(str(result_chord), "D-F-A")
139
140 def test_transposed_overflow(self):
141 d_major_tones = (Tone(tone) for tone in ["D", "F#", "A"])
142 d_major_chord = Chord(*d_major_tones)
143 result_chord = d_major_chord.transposed(Interval(8))
144 self.assertIsInstance(result_chord, Chord)
145 self.assertEqual(str(result_chord), "A#-D-F")
146
147 def test_transposed_negative_overflow(self):
148 e_minor_tones = (Tone(tone) for tone in "EGB")
149 e_minor_chord = Chord(*e_minor_tones)
150 result_chord = e_minor_chord.transposed(-Interval(8))
151 self.assertIsInstance(result_chord, Chord)
152 self.assertEqual(str(result_chord), "G#-B-D#")
153
154
155class TestOperations(unittest.TestCase):
156 """Test the operations between the different musical primitives."""
157
158 def test_tone_addition_different_tones(self):
159 g, f = Tone("F"), Tone("G")
160 result_chord = g + f
161 self.assertIsInstance(result_chord, Chord)
162 self.assertEqual(str(result_chord), "F-G")
163
164 def test_tone_addition_same_tone(self):
165 c, another_c = Tone("C"), Tone("C")
166 with self.assertRaises(TypeError) as err:
167 c + another_c
168 self.assertEqual(str(err.exception), CANNOT_CHORD_THIS_CHORD)
169
170 def test_tone_subtraction(self):
171 g, c = Tone("G"), Tone("C")
172 perfect_5th = g - c
173 self.assertEqual(str(perfect_5th), "perfect 5th")
174
175 def test_tone_subtraction_inverse(self):
176 c, g = Tone("C"), Tone("G")
177 perfect_4th = c - g
178 self.assertEqual(str(perfect_4th), "perfect 4th")
179
180 def test_tone_subtraction_same_tone(self):
181 c, another_c = Tone("C"), Tone("C")
182 unison = c - another_c
183 self.assertEqual(str(unison), "unison")
184
185 def test_add_interval_to_tone(self):
186 g = Tone("G")
187 g_sharp = g + Interval(1)
188 self.assertIsInstance(g_sharp, Tone)
189 self.assertEqual(str(g_sharp), "G#")
190
191 def test_add_interval_to_tone_same_tone(self):
192 g = Tone("G")
193 still_g = g + Interval(0)
194 self.assertIsInstance(still_g, Tone)
195 self.assertEqual(str(still_g), "G")
196
197 def test_add_interval_to_tone_overflow(self):
198 g = Tone("G")
199 a = g + Interval(2)
200 self.assertIsInstance(a, Tone)
201 self.assertEqual(str(a), "A")
202
203 a = g + Interval(2 + 12**2)
204 self.assertIsInstance(a, Tone)
205 self.assertEqual(str(a), "A")
206
207 def test_add_interval_to_tone_left_side_error(self):
208 g = Tone("G")
209 with self.assertRaises(TypeError) as err:
210 Interval(2) + g
211 self.assertEqual(str(err.exception), INVALID_OPERATION)
212
213 def test_subtract_interval_from_tone(self):
214 g = Tone("G")
215 f_sharp = g - Interval(1)
216 self.assertIsInstance(f_sharp, Tone)
217 self.assertEqual(str(f_sharp), "F#")
218
219 def test_subtract_interval_from_tone_same_tone(self):
220 g = Tone("G")
221 still_g = g - Interval(0)
222 self.assertIsInstance(still_g, Tone)
223 self.assertEqual(str(still_g), "G")
224
225 def test_subtract_interval_from_tone_oveflow(self):
226 c = Tone("C")
227 g = c - Interval(5)
228 self.assertIsInstance(g, Tone)
229 self.assertEqual(str(g), "G")
230
231 def test_subtract_interval_from_tone_left_side_error(self):
232 g = Tone("G")
233 with self.assertRaises(TypeError) as err:
234 Interval(2) - g
235 self.assertEqual(str(err.exception), INVALID_OPERATION)
236
237 def test_interval_addition(self):
238 minor_2nd, minor_3rd = Interval(1), Interval(3)
239 major_3rd = minor_2nd + minor_3rd
240 self.assertIsInstance(major_3rd, Interval)
241 self.assertEqual(str(major_3rd), "major 3rd")
242
243 def test_interval_addition_overflow(self):
244 perfect_4th, perfect_5th = Interval(5), Interval(7)
245 unison = perfect_4th + perfect_5th
246 self.assertIsInstance(unison, Interval)
247 self.assertEqual(str(unison), "unison")
248
249 def test_add_tone_to_chord(self):
250 f_major_tones = (Tone(tone) for tone in "FAC")
251 f_major_chord = Chord(*f_major_tones)
252 result_chord = f_major_chord + Tone("D")
253 self.assertIsInstance(result_chord, Chord)
254 self.assertEqual(str(result_chord), "F-A-C-D")
255
256 def test_add_tone_to_chord_order(self):
257 f_major_tones = (Tone(tone) for tone in "FAC")
258 f_major_chord = Chord(*f_major_tones)
259 result_chord = f_major_chord + Tone("G")
260 self.assertIsInstance(result_chord, Chord)
261 self.assertEqual(str(result_chord), "F-G-A-C")
262
263 def test_add_tone_to_chord_existing_tone(self):
264 f_minor_tones = (Tone(tone) for tone in ["F", "G#", "C"])
265 f_minor_chord = Chord(*f_minor_tones)
266 result_chord = f_minor_chord + Tone("F")
267 result_chord = result_chord + Tone("G#")
268 result_chord = result_chord + Tone("C")
269 self.assertIsInstance(result_chord, Chord)
270 self.assertEqual(str(result_chord), "F-G#-C")
271
272 def test_subtract_tone_from_chord(self):
273 f_minor_tones = (Tone(tone) for tone in ["F", "G#", "C"])
274 f_minor_chord = Chord(*f_minor_tones)
275 result_chord = f_minor_chord - Tone("C")
276 self.assertIsInstance(result_chord, Chord)
277 self.assertEqual(str(result_chord), "F-G#")
278
279 def test_subtract_tone_from_chord_error(self):
280 c5_chord_tones = Tone("C"), Tone("G")
281 c5_chord = Chord(*c5_chord_tones)
282 with self.assertRaises(TypeError) as err:
283 c5_chord - Tone("G")
284 self.assertEqual(str(err.exception), CANNOT_CHORD_THIS_CHORD)
285
286 def test_subtract_tone_from_chord_error(self):
287 c5_chord_tones = Tone("C"), Tone("G")
288 c5_chord = Chord(*c5_chord_tones)
289 a = Tone("A")
290 expected_message = CANNOT_REMOVE_TONE.format(str(a), str(c5_chord))
291 with self.assertRaises(TypeError) as err:
292 c5_chord - a
293 self.assertEqual(str(err.exception), expected_message)
294
295 def test_add_chords(self):
296 c5_chord = Chord(Tone("C"), Tone("G"))
297 this_other_chord = Chord(Tone("A"), Tone("B"))
298 result_chord = c5_chord + this_other_chord
299 self.assertEqual(str(result_chord), "C-G-A-B")
300
301 def test_add_chords_repeating_notes(self):
302 c5_chord = Chord(Tone("C"), Tone("G"))
303 inverted_c5_chord = Chord(Tone("G"), Tone("C"))
304 result_chord = c5_chord + inverted_c5_chord
305 self.assertEqual(str(result_chord), "C-G")
306
307
308if __name__ == "__main__":
309 unittest.main()
|
![]()
Георги Кунчев
01.11.2024 20:28@Даниел_Стефанов, главният тон на първия акорд е водещ, т.е. този, който стои отляво на '+'. |
![]()
Даниел Стефанов
01.11.2024 20:16При събиране на акорди как се определя кой е главния тон на получения акорд. Взимаме главния тон на първия операнд или взимаме този който е по-напред от двата главни тона като поредност? |
![]()
Георги Кунчев
01.11.2024 15:06@Ивайло_Кънчев, тъй като местата са разменени, посоката е различна. Разстоянието при C - G e 5, т.е "perfect 4th". |
![]()
Ивайло Кънчев
01.11.2024 13:58При изваждането на тонове, ако от C извадим G, какъв трябва да е резултатът? Отново ли ще е Perfect 5? |
![]()
Георги Кунчев
31.10.2024 17:18@Даниел_Стефанов, да. Все още нямаме амбиция да позволим множество файлове. Много работа по сайта ще е. В повечето случаи това е достатъчно. Не пишем Java и е ок да имаме много класове в един файл. |
|
![]()
Виктор Бечев
31.10.2024 10:49
|
![]()
Павел Петков
31.10.2024 02:11Имам няколко неясни неща по домашното:
|