diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-03-18 17:50:33 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-03-18 18:03:04 +0000 |
| commit | a49382c7b46c7bdf1c105c0783c7bdcd0bb70e4a (patch) | |
| tree | c1f800736e1905514aa913630ee31a720e770c9e | |
| parent | docs/internal: add builtins and invoke modules (diff) | |
| download | alive-a49382c7b46c7bdf1c105c0783c7bdcd0bb70e4a.tar.gz alive-a49382c7b46c7bdf1c105c0783c7bdcd0bb70e4a.zip | |
fork ops before eval cycle
| -rw-r--r-- | core/base/op.moon | 12 | ||||
| -rw-r--r-- | core/invoke.moon | 4 | ||||
| -rw-r--r-- | core/value.moon | 9 | ||||
| -rw-r--r-- | lib/midi/core.moon | 2 | ||||
| -rw-r--r-- | lib/midi/launchctl.moon | 44 | ||||
| -rw-r--r-- | lib/random.moon | 10 | ||||
| -rw-r--r-- | lib/time.moon | 43 | ||||
| -rw-r--r-- | lib/util.moon | 33 | ||||
| -rw-r--r-- | spec/core/value_spec.moon | 120 |
9 files changed, 164 insertions, 113 deletions
diff --git a/core/base/op.moon b/core/base/op.moon index f338673..08c47ab 100644 --- a/core/base/op.moon +++ b/core/base/op.moon @@ -19,6 +19,17 @@ class Op -- @treturn iterator iterator over `inputs` all_inputs: => coroutine.wrap -> do_yield @inputs + --- create a mutable copy of this Op. + -- + -- Used to wrap insulate eval-cycles from each other. The copy does not have + -- `inputs` set, since it is expected that this is (re)set in `setup`. + -- + -- @treturn Value + fork: => + with setmetatable {}, getmetatable @ + .state = {k,v for k,v in pairs @state} if @state + .out = @out\fork! if @out + --- `Value` instance representing this Op's computed output value. -- -- Must be set to a `Value` instance once `setup` finishes. Must not change @@ -92,6 +103,7 @@ class Op -- @tparam[opt] string type the type-name for `out` -- @tparam[optchain] any init the initial value for `out` new: (type, init) => + @state = {} if type @out = Value type, init diff --git a/core/invoke.moon b/core/invoke.moon index 8d63962..23207b7 100644 --- a/core/invoke.moon +++ b/core/invoke.moon @@ -13,7 +13,9 @@ import Scope from require 'core.scope' class op_invoke extends Action --- `Action:patch` implementation. patch: (head) => - return true if head == @head + if head == @head + @op = @op\fork! + return true @op\destroy! if @op diff --git a/core/value.moon b/core/value.moon index 0f40ef3..bda6127 100644 --- a/core/value.moon +++ b/core/value.moon @@ -36,6 +36,15 @@ class Value assert type == @type, msg or "#{@} is not a #{type}" if type @value + --- create a mutable copy of this Value. + -- + -- Used to wrap insulate eval-cycles from each other. + -- + -- @treturn Value + fork: => + with Value @type, @value, @raw + .updated = @updated + --- alias for `unwrap`. __call: (...) => @unwrap ... diff --git a/lib/midi/core.moon b/lib/midi/core.moon index d5259ad..8378b1d 100644 --- a/lib/midi/core.moon +++ b/lib/midi/core.moon @@ -101,7 +101,7 @@ apply_range = (range, val) -> when 'rad' then val / 64 * math.pi when 'deg' then val / 128 * 360 else - error "unknown range #{@range}" + error "unknown range #{range}" elseif range.type == 'num' val / 128 * range\unwrap! else diff --git a/lib/midi/launchctl.moon b/lib/midi/launchctl.moon index 6408528..9f29922 100644 --- a/lib/midi/launchctl.moon +++ b/lib/midi/launchctl.moon @@ -19,9 +19,7 @@ range can be one of: - 'deg' [ 0 - 360[ - (num) [ 0 - num[" - new: => - super 'num' - @steps = {} + new: => super 'num' setup: (inputs) => { port, i, start, @@ -42,23 +40,23 @@ range can be one of: { :port, :i, :start, :chan, :steps, :range } = @inputs if steps\dirty! - while steps! > #@steps - table.insert @steps, 0 - while steps! < #@steps - table.remove @steps + while steps! > #@state + table.insert @state, 0 + while steps! < #@state + table.remove @state - curr_i = i! % #@steps + curr_i = i! % #@state if port\dirty! changed = false for msg in port!\receive! if msg.status == 'control-change' and msg.chan == chan! rel_i = msg.a - start! - if rel_i >= 0 and rel_i < #@steps - @steps[rel_i+1] = msg.b + if rel_i >= 0 and rel_i < #@state + @state[rel_i+1] = msg.b changed = rel_i == curr_i - @out\set apply_range range, @steps[curr_i+1] if changed + @out\set apply_range range, @state[curr_i+1] if changed else - @out\set apply_range range, @steps[curr_i+1] + @out\set apply_range range, @state[curr_i+1] class gate_seq extends Op @doc: "(launctl/gate-seq port i start chan [steps]) - Gate-Sequencer @@ -68,7 +66,7 @@ steps defaults to 8." new: => super 'bool', false - @steps = {} + @state = {} setup: (inputs) => { port, i, start, chan, steps } = match 'midi/port num num num num?', inputs @@ -91,34 +89,34 @@ steps defaults to 8." display: (i, active) => { :port, :start, :chan } = @unwrap_all! - port\send 'note-on', chan, (start + i), light @steps[i+1], active + port\send 'note-on', chan, (start + i), light @state[i+1], active tick: => { :port, :i, :start, :chan, :steps } = @inputs if steps\dirty! - while steps! > #@steps - table.insert @steps, false - while steps! < #@steps - table.remove @steps + while steps! > #@state + table.insert @state, false + while steps! < #@state + table.remove @state - curr_i = i! % #@steps + curr_i = i! % #@state if port\dirty! for msg in port!\receive! if msg.status == 'note-on' and msg.chan == chan! rel_i = msg.a - start! - if rel_i >= 0 and rel_i < #@steps - @steps[rel_i+1] = not @steps[rel_i+1] + if rel_i >= 0 and rel_i < #@state + @state[rel_i+1] = not @state[rel_i+1] @display rel_i, rel_i == curr_i if i\dirty! - prev_i = (curr_i - 1) % #@steps + prev_i = (curr_i - 1) % #@state @display curr_i, true @display prev_i, false - @out\set @steps[curr_i+1] + @out\set @state[curr_i+1] { 'gate-seq': gate_seq diff --git a/lib/random.moon b/lib/random.moon index 7cabdf0..b97671d 100644 --- a/lib/random.moon +++ b/lib/random.moon @@ -8,7 +8,7 @@ apply_range = (range, val) -> when 'rad' then val * 2 * math.pi when 'deg' then val * 360 else - error "unknown range #{@range}" + error "unknown range #{range}" elseif range.type == 'num' val * range\unwrap! else @@ -31,7 +31,7 @@ generates a random value in range on create and trigger. super 'num' @gen! - gen: => @val = math.random! + gen: => @state = { math.random! } setup: (inputs) => { trig, range } = match 'bang? any?', inputs @@ -41,7 +41,7 @@ generates a random value in range on create and trigger. tick: => @gen! if @inputs.trig and @inputs.trig\dirty! - @out\set apply_range @inputs.range, @val + @out\set apply_range @inputs.range, @state[1] vec_ = (n) -> class vec extends Op @@ -54,7 +54,7 @@ each component is in range. super "vec#{n}" @gen! - gen: => @val = for i=1,n do math.random! + gen: => @state = for i=1,n do math.random! setup: (inputs) => { trig, range } = match 'bang? any?', inputs @@ -64,7 +64,7 @@ each component is in range. tick: => @gen! if @inputs.trig and @inputs.trig\dirty! - @out\set [apply_range @inputs.range, v for v in *@val] + @out\set [apply_range @inputs.range, v for v in *@state] vec.__name = "vec#{n}" vec diff --git a/lib/time.moon b/lib/time.moon index 6e70355..8e9aded 100644 --- a/lib/time.moon +++ b/lib/time.moon @@ -47,7 +47,7 @@ wave selects the wave shape from the following: new: => super 'num' - @phase = 0 + @state.phase or= 0 default_wave = Value.str 'sin' setup: (inputs, scope) => @@ -62,11 +62,11 @@ wave selects the wave shape from the following: if @inputs.clock\dirty! { :clock, :freq, :wave } = @unwrap_all! - @phase += clock.dt * freq + @state.phase += clock.dt * freq @out\set switch wave - when 'sin' then .5 + .5 * math.cos @phase * tau - when 'saw' then @phase % 1 - when 'tri' then math.abs (2*@phase % 2) - 1 + when 'sin' then .5 + .5 * math.cos @state.phase * tau + when 'saw' then @state.phase % 1 + when 'tri' then math.abs (2*@state.phase % 2) - 1 else error "unknown wave type" class ramp extends Op @@ -76,7 +76,7 @@ ramps from 0 to max (default same as ramp) once every period seconds." new: => super 'num' - @phase = 0 + @state.phase or= 0 setup: (inputs, scope) => { clock, period, max } = match 'clock? num num?', inputs @@ -90,21 +90,22 @@ ramps from 0 to max (default same as ramp) once every period seconds." if clock_dirty { :clock, :period, :max } = @unwrap_all! max or= period - @phase += clock.dt / period + @state.phase += clock.dt / period - while @phase >= 1 - @phase -= 1 + while @state.phase >= 1 + @state.phase -= 1 if clock_dirty or (@inputs.max and @inputs.max\dirty!) - @out\set @phase * max + @out\set @state.phase * max class tick extends Op @doc: "(tick [clock] period) - count ticks counts upwards by one every period seconds and returns the number of completed ticks." new: => - @phase, @count = 0, 0 - super 'num', @count + super 'num', 0 + @state.phase or= 0 + @state.count or= 0 setup: (inputs, scope) => { clock, period } = match 'clock? num', inputs @@ -115,12 +116,12 @@ counts upwards by one every period seconds and returns the number of completed t tick: => if @inputs.clock\dirty! { :clock, :period, :max } = @unwrap_all! - @phase += clock.dt / period + @state.phase += clock.dt / period - while @phase >= 1 - @phase -= 1 - @count += 1 - @out\set @count + while @state.phase >= 1 + @state.phase -= 1 + @state.count += 1 + @out\set @state.count class every extends Op @doc: "(every [clock] period) - trigger every period seconds @@ -128,7 +129,7 @@ class every extends Op returns true once every period seconds." new: => super 'bang' - @phase = 0 + @state.phase or= 0 setup: (inputs, scope) => { clock, period } = match 'clock? num', inputs @@ -139,10 +140,10 @@ returns true once every period seconds." tick: => if @inputs.clock\dirty! { :clock, :period, :max } = @unwrap_all! - @phase += clock.dt / period + @state.phase += clock.dt / period - while @phase >= 1 - @phase -= 1 + while @state.phase >= 1 + @state.phase -= 1 @out\set true { diff --git a/lib/util.moon b/lib/util.moon index 79ee721..1fddca4 100644 --- a/lib/util.moon +++ b/lib/util.moon @@ -71,35 +71,6 @@ when i is a num, it is (floor)ed and the matching argument (starting from 0) is if active and active\dirty! @out\set active! ---class switch_pause extends Op --- @doc: "(switch- i v0 [v1 v2...]) - switch and pause multiple inputs --- ---like (switch ...) except that the unused inputs are paused." --- --- setup: (@i, ...) => --- @choices = { ... } --- --- typ = @choices[1].type --- for inp in *@choices[2,] --- assert inp.type == typ, "not all values have the same type: #{typ} != #{inp.type}" --- --- @out = Stream typ --- @out --- --- tick: => --- i = @i\unwrap! --- active = switch i --- when true --- @choices[1] --- when false --- @choices[2] --- else --- i = 1 + (math.floor i) % #@choices --- @choices[i] --- --- @out\set if active --- active\unwrap! - class edge extends Op @doc: "(edge bool) - convert rising edges to bangs" new: => super 'bang' @@ -110,9 +81,9 @@ class edge extends Op tick: => now = @inputs.value! - if now and not @last + if now and not @state.last @out\set true - @last = now + @state.last = now class default extends Op @doc: "(default stream default) - provide a default value for an event stream diff --git a/spec/core/value_spec.moon b/spec/core/value_spec.moon index 2436371..eeb3339 100644 --- a/spec/core/value_spec.moon +++ b/spec/core/value_spec.moon @@ -1,4 +1,4 @@ -import Value, Result, Scope from require 'core' +import Value, Result, Scope, SimpleRegistry from require 'core' import Op, Action from require 'core.base' import Logger from require 'logger' Logger.init 'silent' @@ -9,57 +9,61 @@ class TestOp extends Op class TestAction extends Action new: (...) => +reg = SimpleRegistry! +setup -> reg\grab! +teardown -> reg\release! + describe 'Value', -> - describe 'wraps', -> - test 'numbers', -> + describe '.wrap', -> + it 'wraps numbers', -> got = Value.wrap 3 assert.is.equal 'num', got.type assert.is.equal 3, got.value - test 'strings', -> + it 'wraps strings', -> got = Value.wrap "im a happy string" assert.is.equal 'str', got.type assert.is.equal "im a happy string", got.value - test 'Values', -> + it 'wraps Values', -> pi = Value 'num', 3.14 got = Value.wrap pi assert.is.equal pi, got - test 'Opdefs', -> + it 'wraps Opdefs', -> got = Value.wrap TestOp assert.is.equal 'opdef', got.type assert.is.equal TestOp, got.value - test 'Bultins', -> + it 'wraps Bultins', -> got = Value.wrap TestAction assert.is.equal 'builtin', got.type assert.is.equal TestAction, got.value - test 'Scopes', -> + it 'wraps Scopes', -> sub = Scope! got = Value.wrap sub assert.is.equal 'scope', got.type assert.is.equal sub, got.value - test 'tables', -> + it 'wraps tables', -> pi = Value 'num', 3.14 got = Value.wrap { :pi } assert.is.equal 'scope', got.type assert.is.equal pi, (got.value\get 'pi')\const! - describe 'unwraps', -> - test 'unwrap!', -> + describe ':unwrap', -> + it 'returns the raw value!', -> assert.is.equal 3.14, (Value.num 3.14)\unwrap! assert.is.equal 'hi', (Value.str 'hi')\unwrap! assert.is.equal 'hi', (Value.sym 'hi')\unwrap! - test 'with type assert', -> + test 'can assert the type', -> assert.is.equal 3.14, (Value.num 3.14)\unwrap 'num' assert.is.equal 'hi', (Value.str 'hi')\unwrap 'str' assert.is.equal 'hi', (Value.sym 'hi')\unwrap 'sym' @@ -67,7 +71,7 @@ describe 'Value', -> assert.has_error -> (Value.str 'hi')\unwrap 'num' assert.has_error -> (Value.sym 'hi')\unwrap 'str' - test 'with __call shorthand', -> + test 'has __call shorthand', -> assert.is.equal 3.14, (Value.num 3.14)! assert.is.equal 'hi', (Value.str 'hi')! assert.is.equal 'hi', (Value.sym 'hi')! @@ -78,8 +82,8 @@ describe 'Value', -> assert.has_error -> (Value.str 'hi') 'num' assert.has_error -> (Value.sym 'hi') 'str' - describe 'checks equality', -> - test 'using the type', -> + describe 'overrides __eq', -> + it 'compares the type', -> val = Value 'num', 3 assert.is.equal (Value.num 3), val assert.not.equal (Value.str '3'), val @@ -88,20 +92,36 @@ describe 'Value', -> assert.is.equal (Value.str 'hello'), val assert.not.equal (Value.sym 'hello'), val - test 'using the value', -> + it 'compares the value', -> val = Value 'num', 3 assert.is.equal (Value.num 3), val assert.not.equal (Value.num 4), val - describe 'evaluates literal', -> - test 'numbers to consts', -> + describe ':set', -> + it 'sets the value', -> + val = Value 'num', 3 + assert.is.equal (Value.num 3), val + + val\set 4 + assert.is.equal (Value.num 4), val + assert.not.equal (Value.num 3), val + + it 'marks the value dirty', -> + val = Value 'num', 3 + assert.is.false val\dirty! + + val\set 4 + assert.is.true val\dirty! + + describe ':eval', -> + it 'turns numbers into consts', -> assert_noop = (val) -> assert.is.equal val, val\eval!\const! assert_noop Value.num 2 assert_noop Value.str 'hello' - test 'symbols in the scope', -> + it 'looks up symbols in the scope', -> scope = with Scope! \set 'number', Result value: Value.num 3 \set 'hello', Result value: Value.str "world" @@ -115,18 +135,56 @@ describe 'Value', -> assert_eval 'hello', Value.str "world" assert_eval 'goodbye', Value.sym "again" - describe 'quotes literals', -> - test 'as themselves', -> - assert_noop = (val) -> assert.is.equal val, val\quote! + it ':quote s literals as themselves', -> + assert_noop = (val) -> assert.is.equal val, val\quote! - assert_noop Value.num 2 - assert_noop Value.str 'hello' - assert_noop Value.sym 'world' + assert_noop Value.num 2 + assert_noop Value.str 'hello' + assert_noop Value.sym 'world' - describe 'clones literals', -> - test 'as themselves', -> - assert_noop = (val) -> assert.is.equal val, val\clone! + it ':clone sliterals as themselves', -> + assert_noop = (val) -> assert.is.equal val, val\clone! - assert_noop Value.num 2 - assert_noop Value.str 'hello' - assert_noop Value.sym 'world' + assert_noop Value.num 2 + assert_noop Value.str 'hello' + assert_noop Value.sym 'world' + + describe ':fork', -> + it 'is equal to the original', -> + a = Value.num 2 + b = Value.str 'asdf' + c = with Value 'weird', {}, '(raw)' + \set {} + + aa, bb, cc = a\fork!, b\fork!, c\fork! + assert.is.equal a, aa + assert.is.equal b, bb + assert.is.equal c, cc + + assert.is.false aa\dirty! + assert.is.false bb\dirty! + assert.is.true cc\dirty! + + assert.is.equal c.raw, cc.raw + + it 'isolates the original from the fork', -> + a = Value.num 3 + b = with Value 'weird', {}, '(raw)' + \set {} + + aa, bb = a\fork!, b\fork! + + bb\set {false} + + assert.is.same {}, b! + assert.is.same {false}, bb! + assert.is.true b\dirty! + assert.is.true bb\dirty! + + reg\next_tick! + aa\set 4 + + assert.is.equal 3, a! + assert.is.equal 4, aa! + assert.is.false a\dirty! + assert.is.true aa\dirty! |
