From 1ee5d770e5a91ac0bef5508ff283de67b93f5b1b Mon Sep 17 00:00:00 2001 From: XiNNiW Date: Fri, 6 Jan 2023 18:12:32 -0500 Subject: [PATCH] named the project... continued to work toward a timespan --- Abstract.md | 15 ++++ README.md | 9 ++- src/{fractional.lua => fraction.lua} | 16 +++- src/time_span.lua | 51 +++++++----- test/all_tests.lua | 2 +- ...{fractional_test.lua => fraction_test.lua} | 78 +++++++++++++++---- test/time_span_test.lua | 59 +++++++------- 7 files changed, 167 insertions(+), 63 deletions(-) create mode 100644 Abstract.md rename src/{fractional.lua => fraction.lua} (94%) rename test/{fractional_test.lua => fraction_test.lua} (55%) diff --git a/Abstract.md b/Abstract.md new file mode 100644 index 0000000..ccd84b5 --- /dev/null +++ b/Abstract.md @@ -0,0 +1,15 @@ +# Abstract + +Tidalcycles is a popular language for improvising live-coded music. Tidalcycles allows artists to manipulate musical pattern using a Domain Specific Language (DSL) written in the Haskell programming language. +Alex McLean, who is the primary author of Tidalcycles has recently begun several related projects that involve re-writing Tidalcycles from scratch as a form of design inquiry. +In addition to a "clean-room" re-write of Tidalcycles in Haskell, McLean and the larger community have undertaken the project of translating Tidalcycles into other programming languages. +Tidalcycles has so far been translated into Python, Javascript, and Kotlin. These projects, codenamed vortex, strudel, and kidal respectively, _______ + +This project aims to continue exploring the design space afforded by the process of translation/re-implimentation. +Individually, I hope to learn about the constrution of livecoding languages by "hand-building" one - much as an apprentice luthier might build a guitar in order to learn how to build guitars. +Additionally, I hope to begin answering some larger questions about livecoding languages as musical tools: +- To what extent can the pattern language of a livecoding language be seperated from its formal language? +- What can we learn about the nature of the pattern language by attempting to seperate it from its host programming language? +- How to the affordances of a particular formal language affect the implementation and end user experience of a livecoding system? +- What new design possiblities does the process of translation reveal? How will they affect the development of Tidalcycles? How will they affect the development of new livecoding systems? +- diff --git a/README.md b/README.md index a24705d..a8ef513 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ -# NAME +# Tranquility + +Tranquility is an experimental port of the livecoding music language [Tidalcycles](http://tidalcycles.org/) to the lua programming language. +This project follows in the footsteps of [vortex](https://github.com/tidalcycles/vortex) and [strudel](https://strudel.tidalcycles.org). +For me the main purpose of this project is to learn about how to implement a livecoding language. + +## Motivation -NAME is an experimental port of the livecoding music language [Tidalcycles](http://tidalcycles.org/) to the lua programming language. This project follows in the footsteps of [vortex](https://github.com/tidalcycles/vortex) and [strudel](https://strudel.tidalcycles.org). For me the main purpose of this project is to learn about how to implement a livecoding language. It is not likely to be directly useful as a tool or instrument. diff --git a/src/fractional.lua b/src/fraction.lua similarity index 94% rename from src/fractional.lua rename to src/fraction.lua index f7c18ab..1078535 100644 --- a/src/fractional.lua +++ b/src/fraction.lua @@ -1,4 +1,5 @@ require('math') +-- require('src/time_span') -- this is a quick and dirty port of python's Fraction library pulling in only the things i need to get a first version working -- this should probably all be C calls instead @@ -230,7 +231,7 @@ function Fraction:__pow (f2) return Fraction:new(self:numerator()^power, self:denominator()^power, false) elseif self:numerator() >= 0 then return Fraction:new(self:denominator()^-power, self:numerator()^-power, false) - else + else return Fraction:new((-self:numerator())^-power, (-self:denominator())^-power, false) end else @@ -252,6 +253,19 @@ function Fraction:__unm () return Fraction:new(-self:numerator(), self:denominator(), false) end +function Fraction:__eq (rhs) + return (self:numerator()/self:denominator())==(rhs:numerator()/rhs:denominator()) +end + +function Fraction:__lt (rhs) + return (self:numerator()/self:denominator())<(rhs:numerator()/rhs:denominator()) +end + + +function Fraction:__lte (rhs) + return (self:numerator()/self:denominator())<=(rhs.numerator()/rhs.denominator()) +end + function Fraction:floor () return self:numerator()//self:denominator() end diff --git a/src/time_span.lua b/src/time_span.lua index 71dd764..bddd845 100644 --- a/src/time_span.lua +++ b/src/time_span.lua @@ -1,5 +1,6 @@ require("math") -require('src/fractional') +require("table") +require('src/fraction') -- """Returns the start of the cycle.""" -- Fraction.sam = lambda self: Fraction(math.floor(self)) @@ -20,6 +21,10 @@ function TimeSpan:nextSam(frac) return Fraction:new(frac:floor()+1) end +function TimeSpan:wholeCycle(frac) + return TimeSpan:new(TimeSpan:sam(frac), TimeSpan:nextSam(frac)) +end + function TimeSpan:create (o) o = o or {} setmetatable(o, self) @@ -27,28 +32,38 @@ function TimeSpan:create (o) return o end -function TimeSpan:wholeCycle() - return TimeSpan:new(TimeSpan:sam(self:beginTime()), TimeSpan:nextSam(self:endTime())) +function TimeSpan:new(_begin, _end) + return TimeSpan:create{_begin=_begin, _end=_end} end -function TimeSpan:beginTime () +function TimeSpan:beginTime() return self._begin end -function TimeSpan:endTime () +function TimeSpan:endTime() return self._end end ---function TimeSpan.span_cycles() --- local spans = {} --- local _begin = self._begin --- local _end = self._end --- local end_sam = _end.sam() --- --- while _end > _begin do --- if begin.sam() == end_sam then --- spans. --- end --- end --- return spans ---end +function TimeSpan:spanCycles() + local spans = {} + local _begin = self._begin + local _end = self._end + local end_sam = TimeSpan:sam(_end) + + if _begin == _end then + return {TimeSpan:new(_begin, _end)} + end + + while _end > _begin do + if TimeSpan:sam(_begin) == end_sam then + table.insert(spans, TimeSpan:new(_begin, self._end)) + break + end + + local next_begin = TimeSpan:nextSam(_begin) + table.insert(spans, TimeSpan:new(_begin, next_begin)) + + _begin = next_begin + end + return spans +end diff --git a/test/all_tests.lua b/test/all_tests.lua index a3b006a..717d204 100644 --- a/test/all_tests.lua +++ b/test/all_tests.lua @@ -1,6 +1,6 @@ ---@diagnostic disable: different-requires local lu = require('test/luaunit/luaunit') -require('test/fractional_test') +require('test/fraction_test') require('test/pattern_test') require('test/time_span_test') diff --git a/test/fractional_test.lua b/test/fraction_test.lua similarity index 55% rename from test/fractional_test.lua rename to test/fraction_test.lua index 54b0508..1ac073a 100644 --- a/test/fractional_test.lua +++ b/test/fraction_test.lua @@ -1,8 +1,8 @@ ---@diagnostic disable: different-requires local lu = require('test/luaunit/luaunit') -require('src/fractional') +require('src/fraction') -function TestFractional__create() +function TestFraction__create() local f = Fraction:create() lu.assertEquals(f:numerator(), 0) lu.assertEquals(f:denominator(), 1) @@ -13,7 +13,7 @@ end function CreateFraction(n,d) return Fraction:new(n,d) end -function TestFractional__new() +function TestFraction__new() local f = Fraction:new() lu.assertEquals(f:numerator(), 0) lu.assertEquals(f:denominator(), 1) @@ -32,10 +32,21 @@ function TestFractional__new() f = Fraction:new(-4,-8) lu.assertEquals(f:numerator(), 1) lu.assertEquals(f:denominator(), 2) + -- Does Fraction need to reduce decimal numbers to closest approximation? + --f = Fraction:new(1.5, 7.6) + -- lu.assertEquals(f:numerator(), 1.5) + -- lu.assertEquals(f:denominator(), 7.6) lu.assertError(CreateFraction, 1, 0) end -function TestFractional__add() +-- Does Fraction need to infer fraction from string representation? +--function TestFractional__new__fromString() +-- local f = Fraction:new("1/2") +-- lu.assertEquals(f:numerator(), 1) +-- lu.assertEquals(f:denominator(), 2) +--end + +function TestFraction__add() local f1 = Fraction:new(1,2) local f2 = Fraction:new(1,2) lu.assertEquals(f1+f2, Fraction:new(1)) @@ -49,7 +60,7 @@ function TestFractional__add() lu.assertEquals(f1+f2, Fraction:new(1,6)) end -function TestFractional__sub() +function TestFraction__sub() local f1 = Fraction:new(1,2) local f2 = Fraction:new(1,2) lu.assertEquals(f1-f2, Fraction:new(0)) @@ -63,7 +74,7 @@ function TestFractional__sub() lu.assertEquals(f1-f2, Fraction:new(5,6)) end -function TestFractional__mult() +function TestFraction__mult() local f1 = Fraction:new(1,2) local f2 = Fraction:new(1,2) lu.assertEquals(f1*f2, Fraction:new(1,4)) @@ -75,7 +86,7 @@ function TestFractional__mult() lu.assertEquals(f1*f2, Fraction:new(-1,6)) end -function TestFractional__div() +function TestFraction__div() local f1 = Fraction:new(1,2) local f2 = Fraction:new(1,2) lu.assertEquals(f1/f2, Fraction:new(1)) @@ -88,7 +99,7 @@ function TestFractional__div() end -function TestFractional__mod() +function TestFraction__mod() local f1 = Fraction:new(1,2) local f2 = Fraction:new(2,3) lu.assertEquals(f1%f2, Fraction:new(1,2)) @@ -96,24 +107,23 @@ function TestFractional__mod() f2 = Fraction:new(2,3) -- 9/12 % 8/12 = 1/12 lu.assertEquals(f1%f2, Fraction:new(1,12)) - end -function TestFractional__pow() +function TestFraction__pow() local f1 = Fraction:new(1,4) local f2 = Fraction:new(1,2) lu.assertEquals(f1^f2, 0.5) - local f1 = Fraction:new(1,4) - local f2 = Fraction:new(2,1) + f1 = Fraction:new(1,4) + f2 = Fraction:new(2,1) lu.assertEquals(f1^f2, Fraction:new(1,16)) end -function TestFractional__neg() +function TestFraction__neg() local f1 = Fraction:new(1,4) lu.assertEquals(-f1, Fraction:new(-1,4)) end -function TestFractional__floor() +function TestFraction__floor() local f1 = Fraction:new(1,4) lu.assertEquals(f1:floor(), 0) f1 = Fraction:new(5,4) @@ -122,5 +132,45 @@ function TestFractional__floor() lu.assertEquals(f1:floor(), 2) end +function TestFraction__gt() + lu.assertTrue(Fraction:new(3,4) > Fraction:new(1,3)) + lu.assertTrue(Fraction:new(5,4) > Fraction:new(1,1)) + lu.assertFalse(Fraction:new(1,3)> Fraction:new(1,2)) + lu.assertFalse(Fraction:new(5,4)> Fraction:new(7,4)) +end + +function TestFraction__lt() + lu.assertTrue(Fraction:new(1,4) < Fraction:new(1,3)) + lu.assertTrue(Fraction:new(1,4) < Fraction:new(1,3)) + lu.assertTrue(Fraction:new(5,4) < Fraction:new(7,3)) + lu.assertFalse(Fraction:new(2,3)< Fraction:new(1,2)) + lu.assertFalse(Fraction:new(9,1)< Fraction:new(7,4)) +end + +function TestFraction__gte() + lu.assertTrue(Fraction:new(3,4) >= Fraction:new(1,3)) + lu.assertTrue(Fraction:new(1,3) >= Fraction:new(1,3)) + lu.assertTrue(Fraction:new(-1,3) >= Fraction:new(-7,3)) + lu.assertTrue(Fraction:new(5,4) >= Fraction:new(5,4)) + lu.assertFalse(Fraction:new(1,3)>= Fraction:new(1,2)) + lu.assertFalse(Fraction:new(5,4)>= Fraction:new(7,4)) +end + +function TestFraction__lte() + lu.assertTrue(Fraction:new(1,4) <= Fraction:new(1,3)) + lu.assertTrue(Fraction:new(1,4) <= Fraction:new(1,4)) + lu.assertTrue(Fraction:new(5,4) <= Fraction:new(7,3)) + lu.assertTrue(Fraction:new(-5,4) <= Fraction:new(7,3)) + lu.assertFalse(Fraction:new(2,3)<= Fraction:new(1,2)) + lu.assertFalse(Fraction:new(9,1)<= Fraction:new(7,4)) +end + +function TestFraction__eq() + lu.assertTrue(Fraction:new(1,4) == Fraction:new(1,4)) + lu.assertTrue(Fraction:new(5,4) == Fraction:new(10,8)) + lu.assertTrue(Fraction:new(-2,3)== Fraction:new(8,-12)) + lu.assertTrue(Fraction:new(-1,3)== Fraction:new(-3,9)) + lu.assertFalse(Fraction:new(254,255) == Fraction:new(255,256)) +end --os.exit( lu.LuaUnit.run() ) diff --git a/test/time_span_test.lua b/test/time_span_test.lua index 00bd02b..6a8be53 100644 --- a/test/time_span_test.lua +++ b/test/time_span_test.lua @@ -3,52 +3,57 @@ local lu = require('test/luaunit/luaunit') require('src/time_span') function TestTimeSpan_sam() - local f = Fraction:new(3,4) + local f = Fraction:new(3, 4) local sam = TimeSpan:sam(f) - lu.assertEquals(sam,Fraction:new(0,1)) - f = Fraction:new(5,4) + lu.assertEquals(sam, Fraction:new(0, 1)) + f = Fraction:new(5, 4) sam = TimeSpan:sam(f) - lu.assertEquals(sam,Fraction:new(1,1)) + lu.assertEquals(sam, Fraction:new(1, 1)) end function TestTimeSpan_nextSam() - local f = Fraction:new(3,4) + local f = Fraction:new(3, 4) local sam = TimeSpan:nextSam(f) - lu.assertEquals(sam,Fraction:new(1,1)) - f = Fraction:new(5,4) + lu.assertEquals(sam, Fraction:new(1, 1)) + f = Fraction:new(5, 4) sam = TimeSpan:nextSam(f) - lu.assertEquals(sam,Fraction:new(2,1)) + lu.assertEquals(sam, Fraction:new(2, 1)) end -function TestTimeSpan_wholeCycle() - local ts = TimeSpan:new(Fraction:new(3,4), Fraction:new(4,4)) - local cycle = ts:wholeCycle() - lu.assertEquals(cycle:beginTime(), Fraction:new(0)) - lu.assertEquals(cycle:endTime(), Fraction:new(1)) +function TestFractional__wholeCycle() + local f1 = Fraction:new(1, 2) + lu.assertEquals(TimeSpan:new(Fraction:new(0, 1), Fraction:new(1, 1)), TimeSpan:wholeCycle(f1)) + f1 = Fraction:new(3, 2) + lu.assertEquals(TimeSpan:new(Fraction:new(1, 1), Fraction:new(2, 1)), TimeSpan:wholeCycle(f1)) end function TestTimeSpan__create() local ts = TimeSpan:create() - lu.assertEquals(Fraction:new(1), ts:beginTime()) - lu.assertEquals(Fraction:new(1), ts:endTime()) + lu.assertEquals(ts:beginTime(), Fraction:new(1)) + lu.assertEquals(ts:endTime() , Fraction:new(1)) ts = TimeSpan:create{} - lu.assertEquals(Fraction:new(1), ts:beginTime()) - lu.assertEquals(Fraction:new(1), ts:endTime()) + lu.assertEquals(ts:beginTime(), Fraction:new(1)) + lu.assertEquals(ts:endTime() , Fraction:new(1)) end function TestTimeSpan__new() local ts = TimeSpan:new(Fraction:new(3), Fraction:new(4)) - lu.assertEquals(Fraction:new(3), ts:beginTime()) - lu.assertEquals(Fraction:new(4), ts:endTime()) + lu.assertEquals(ts:beginTime(), Fraction:new(3)) + lu.assertEquals(ts:endTime() , Fraction:new(4)) end - --- function TestTimeSpan__spanCycles() --- local ts = TimeSpan:new(Fraction:new(3), Fraction:new(4)) --- local spans = ts:spanCycles() --- lu.assertEquals(Fraction:new(1), ts:beginTime()) --- lu.assertEquals(Fraction:new(1), ts:endTime()) - --- end +function TestTimeSpan__spanCycles() + local ts = TimeSpan:new(Fraction:new(3, 4), Fraction:new(7, 2)) + local spans = ts:spanCycles() + lu.assertEquals(4, #(spans)) + lu.assertEquals(spans[1]:beginTime(), Fraction:new(3, 4)) + lu.assertEquals(spans[1]:endTime() , Fraction:new(1, 1)) + lu.assertEquals(spans[2]:beginTime(), Fraction:new(1, 1)) + lu.assertEquals(spans[2]:endTime() , Fraction:new(2, 1)) + lu.assertEquals(spans[3]:beginTime(), Fraction:new(2, 1)) + lu.assertEquals(spans[3]:endTime() , Fraction:new(3, 1)) + lu.assertEquals(spans[4]:beginTime(), Fraction:new(3, 1)) + lu.assertEquals(spans[4]:endTime() , Fraction:new(7, 2)) +end --os.exit( lu.LuaUnit.run() )