From 0fb87d18189c4fbb794a4c012c6dfcd49419cce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Doar=C3=A9?= Date: Tue, 4 Mar 2025 15:02:39 +0100 Subject: [PATCH 1/6] Scale:circle_of_5ths_rotate --- scale.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scale.lua b/scale.lua index adc55b1..640d23d 100644 --- a/scale.lua +++ b/scale.lua @@ -106,6 +106,22 @@ function Scale:to_the_right_on_circle_of_5ths(iterations) :to_the_right_on_circle_of_5ths(iterations -1) end +--[[ + Warning: only works for ionian scales +]] +function Scale:circle_of_5ths_rotate(iterations) + if(iterations == 0) then + return self + elseif(iterations < 0) then + return self:to_the_left_on_circle_of_5ths(-iterations) + elseif(iterations > 0) then + return self:to_the_right_on_circle_of_5ths(iterations) + else + return "Error" + end +end + + --[[ Returns a new scale based on self, rotated from note_offset note_offset: From d12c6540b9f0b351c31f37dd417e0871dd563414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Doar=C3=A9?= Date: Tue, 25 Mar 2025 22:11:04 +0100 Subject: [PATCH 2/6] ChordBuilder:extend --- chord.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/chord.lua b/chord.lua index 34e5518..345c2c8 100644 --- a/chord.lua +++ b/chord.lua @@ -336,6 +336,20 @@ function ChordBuilder:clone() return b end +-- extensions is a table of 4 elements, false or true +-- the first element is the seventh, +-- the second is the ninth, +-- the third is the eleventh, +-- the fourth is the thirteenth +function ChordBuilder:extend(extensions) + local b = self:clone() + b.seventh = extensions[1] + b.ninth = extensions[2] + b.eleventh = extensions[3] + b.thirteenth = extensions[4] + return b +end + function ChordBuilder:withSeventh() local b = self:clone() b.seventh = true From b9426c39ec8ed098c6db4d4cfc6fe8431e9c650c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Doar=C3=A9?= Date: Wed, 26 Mar 2025 21:45:12 +0100 Subject: [PATCH 3/6] Array:sum() --- array.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/array.lua b/array.lua index afdc0b1..ab29ea6 100644 --- a/array.lua +++ b/array.lua @@ -153,4 +153,12 @@ function Array:equals(array) return getmetatable(array) == Array and #self == #array and equal_values +end + +function Array:sum() + local total = 0 + for _, value in ipairs(self) do + total = total + value + end + return total end \ No newline at end of file From 9f2916415e86a643acb278abf2660aa191d59f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Doar=C3=A9?= Date: Wed, 26 Mar 2025 21:46:34 +0100 Subject: [PATCH 4/6] Fix Circle of 5ths functions --- scale.lua | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/scale.lua b/scale.lua index 640d23d..d33a9fe 100644 --- a/scale.lua +++ b/scale.lua @@ -82,6 +82,10 @@ function Scale.c_major() }) end +function Scale:count_alterations() + return self.notes:map(function(n) return n.alteration end):sum() +end + --[[ Warning: only works for ionian scales ]] @@ -89,9 +93,14 @@ function Scale:to_the_left_on_circle_of_5ths(iterations) if iterations == 0 then return self end - return self:rotate(4) - :add_alteration(4, -1) - :to_the_left_on_circle_of_5ths(iterations -1) + out = self:clone() + for i = 1, iterations do + out = out:rotate(4):add_alteration(4, -1) + end + if out:count_alterations() < -7 then + out = out:to_the_right_on_circle_of_5ths(12) + end + return out end --[[ @@ -101,9 +110,14 @@ function Scale:to_the_right_on_circle_of_5ths(iterations) if(iterations == 0) then return self end - return self:rotate(5) - :add_alteration(7, 1) - :to_the_right_on_circle_of_5ths(iterations -1) + out = self:clone() + for i = 1, iterations do + out = out:rotate(5):add_alteration(7, 1) + end + if out:count_alterations() > 7 then + out = out:to_the_left_on_circle_of_5ths(12) + end + return out end --[[ From cdeb56effe38e23e90ef357db32b0aa9a01cce40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Doar=C3=A9?= Date: Wed, 26 Mar 2025 21:46:49 +0100 Subject: [PATCH 5/6] Test for circle of 5ths functions --- test/test_scale.lua | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/test/test_scale.lua b/test/test_scale.lua index 3a2e9c6..9f2ee41 100644 --- a/test/test_scale.lua +++ b/test/test_scale.lua @@ -359,4 +359,26 @@ function TestScale:testGetDistance() luaunit.assertEquals(f_sharp_maj:get_distance(g_flat_min), 2) luaunit.assertEquals(f_sharp_maj:get_distance(d_sharp_min), 0) luaunit.assertEquals(f_sharp_maj:get_distance(d_sharp_min_harm), 1) -end \ No newline at end of file +end + +function TestScale:test_c5ths_totheleft() + local c_major = Scale.c_major() + local next = c_major:to_the_left_on_circle_of_5ths(1) + -- print(next:tostring()) + for i=1,11 do + next = next:to_the_left_on_circle_of_5ths(1) + -- print(next:tostring()) + end + luaunit.assertEquals(next, Scale.c_major()) +end + +function TestScale:test_c5ths_totheright() + local c_major = Scale.c_major() + local next = c_major:to_the_right_on_circle_of_5ths(1) + -- print(next:tostring()) + for i=1,11 do + next = next:to_the_right_on_circle_of_5ths(1) + -- print(next:tostring()) + end + luaunit.assertEquals(next, Scale.c_major()) +end From 6c2d509af112aa4ba0dcc6bb9d2a962367fa7548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Doar=C3=A9?= Date: Sat, 29 Mar 2025 16:33:59 +0100 Subject: [PATCH 6/6] Rework circle of 5ths functions --- scale.lua | 34 +++++++++++++--------------------- test/test_scale.lua | 25 ++++++------------------- 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/scale.lua b/scale.lua index d33a9fe..057e98e 100644 --- a/scale.lua +++ b/scale.lua @@ -93,14 +93,9 @@ function Scale:to_the_left_on_circle_of_5ths(iterations) if iterations == 0 then return self end - out = self:clone() - for i = 1, iterations do - out = out:rotate(4):add_alteration(4, -1) - end - if out:count_alterations() < -7 then - out = out:to_the_right_on_circle_of_5ths(12) - end - return out + return self:rotate(4) + :add_alteration(4, -1) + :to_the_left_on_circle_of_5ths(iterations -1) end --[[ @@ -110,26 +105,23 @@ function Scale:to_the_right_on_circle_of_5ths(iterations) if(iterations == 0) then return self end - out = self:clone() - for i = 1, iterations do - out = out:rotate(5):add_alteration(7, 1) - end - if out:count_alterations() > 7 then - out = out:to_the_left_on_circle_of_5ths(12) - end - return out + return self:rotate(5) + :add_alteration(7, 1) + :to_the_right_on_circle_of_5ths(iterations -1) end --[[ Warning: only works for ionian scales ]] function Scale:circle_of_5ths_rotate(iterations) - if(iterations == 0) then + local total_alterations = self:count_alterations()+iterations + local rotation = ( (total_alterations + 5) % 12 ) - 5 + if(rotation == 0) then return self - elseif(iterations < 0) then - return self:to_the_left_on_circle_of_5ths(-iterations) - elseif(iterations > 0) then - return self:to_the_right_on_circle_of_5ths(iterations) + elseif(rotation < 0) then + return self:to_the_left_on_circle_of_5ths(-rotation) + elseif(rotation > 0) then + return self:to_the_right_on_circle_of_5ths(rotation) else return "Error" end diff --git a/test/test_scale.lua b/test/test_scale.lua index 9f2ee41..132c015 100644 --- a/test/test_scale.lua +++ b/test/test_scale.lua @@ -361,24 +361,11 @@ function TestScale:testGetDistance() luaunit.assertEquals(f_sharp_maj:get_distance(d_sharp_min_harm), 1) end -function TestScale:test_c5ths_totheleft() +function TestScale:test_c5ths_rotate() local c_major = Scale.c_major() - local next = c_major:to_the_left_on_circle_of_5ths(1) - -- print(next:tostring()) - for i=1,11 do - next = next:to_the_left_on_circle_of_5ths(1) - -- print(next:tostring()) - end - luaunit.assertEquals(next, Scale.c_major()) -end - -function TestScale:test_c5ths_totheright() - local c_major = Scale.c_major() - local next = c_major:to_the_right_on_circle_of_5ths(1) - -- print(next:tostring()) - for i=1,11 do - next = next:to_the_right_on_circle_of_5ths(1) - -- print(next:tostring()) - end - luaunit.assertEquals(next, Scale.c_major()) + luaunit.assertEquals(c_major:circle_of_5ths_rotate(12), c_major:circle_of_5ths_rotate(0)) + luaunit.assertEquals(c_major:circle_of_5ths_rotate(8), c_major:circle_of_5ths_rotate(-4)) + luaunit.assertEquals(c_major:circle_of_5ths_rotate(-8), c_major:circle_of_5ths_rotate(4)) + luaunit.assertEquals(c_major:circle_of_5ths_rotate(123), c_major:circle_of_5ths_rotate(3)) + luaunit.assertEquals(c_major:circle_of_5ths_rotate(-123), c_major:circle_of_5ths_rotate(-3)) end