diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2016-09-22 04:13:21 +0200 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2016-09-25 03:52:16 +0200 |
commit | 503acf7dfe5fa747dbbd2576d0b78b8c54ac9e4c (patch) | |
tree | 75994115663633e2307bf8d6d91faf9eea552731 | |
parent | d5ae3fdde5b22fd0e66f6e9c27eb08c23f0274ba (diff) | |
download | applause2-503acf7dfe5fa747dbbd2576d0b78b8c54ac9e4c.tar.gz |
changed semantics of ZipStream (ie. multiply, add operators): the left stream determines the length
* this makes them compatible with the scalar operations like Stream:mul(), Stream:add() etc
when the right stream is a scalar turned into an infinite stream.
* Consequently, both operations could be merged: Stream:mul() and the __mul operator
are now synonymous.
The methods are kept since they are sometimes handy to avoid braces when writing
from left to right.
Since the old way of mapping a stream for scalars is still a bit faster compared to using
a ZipStream, this method is still applied for scalars as an optimization.
Both the methods and the operators will now work with scalars and arbitrary streams, though.
* This means that many primitives based on scalar operations previously will now
work with streams as well. This applies to Stream:scale(), Stream:ccscale(), Stream:mix(),
Stream:pan() and even line().
* Added Stream:min() and Stream:max() as shortcuts for binary operations from the
math package.
* All the binary operations from the math and bit packages will work with streams now
as well (without performance penalties).
* Stream:clip() has been revised and works with stream arguments now as well.
* Optimized ConcatStream, MapStream, ScanStream, FoldStream and esp. ZipStream.
* The new ZipStream semantics allow for new useful idioms.
For instance, to add an envelope to an infinite stream (stream*env), a SubStream
was often necessary to restrict the resulting stream to the length of the envelope
as in (stream*env):sub(1,env:len()).
This can now be written more elegantly as (env*stream).
To extend a stream to infinite length, you may still write 0+stream or
stream..0
-rw-r--r-- | applause.lua | 259 |
1 files changed, 149 insertions, 110 deletions
diff --git a/applause.lua b/applause.lua index a52b617..1b143b8 100644 --- a/applause.lua +++ b/applause.lua @@ -233,6 +233,17 @@ for _, fnc in pairs{"abs", "acos", "asin", "atan", end end +-- Some binary functions from the math package +for _, name in pairs{"min", "max"} do + local fnc = math[name] + + Stream[name] = function(self, v) + return type(v) == "number" and + self:map(function(x) return fnc(x, v) end) or + self:zip(fnc, v) + end +end + function Stream:bnot() return self:map(bit.bnot) end @@ -244,52 +255,17 @@ for _, name in pairs{"bor", "band", "bxor", local fnc = bit[name] Stream[name] = function(self, v) - return self:map(function(x) return fnc(x, v) end) + return type(v) == "number" and + self:map(function(x) return fnc(x, v) end) or + self:zip(fnc, v) end end --- Scalar operations --- In contrast to stream operations (based on ZipStream), --- these work only with scalars and do not --- extend the stream length -function Stream:add(n) - return self:map(function(x) return x+n end) -end - -function Stream:minus(n) - return self:map(function(x) return x-n end) -end - -function Stream:mul(n) - return self:map(function(x) return x*n end) -end -Stream.gain = Stream.mul -Stream["\u{00D7}"] = Stream.mul -- APL Multiply/Signum - -function Stream:div(n) - return self:map(function(x) return x/n end) -end -Stream["\u{00F7}"] = Stream.div -- APL Divide - -function Stream:mod(n) - return self:map(function(x) return x%n end) -end - -function Stream:pow(n) - return self:map(function(x) return x^n end) -end -Stream["\u{22C6}"] = Stream.pow -- APL Exponentiation - function Stream:clip(min, max) min = min or -1 max = max or 1 - local math_min = math.min - local math_max = math.max - - return self:map(function(x) - return math_min(math_max(x, min), max) - end) + return self:max(min):min(max) end -- Scale [-1,+1] signal to [lower,upper] @@ -298,9 +274,13 @@ function Stream:scale(v1, v2) local lower = v2 and v1 or 0 local upper = v2 or v1 - return self:map(function(x) - return (x + 1)*(upper - lower)/2 + lower - end) + if type(lower) == "number" and type(upper) == "number" then + return self:map(function(x) + return (x + 1)*(upper - lower)/2 + lower + end) + else + return (self + 1)*((upper - lower)/2) + lower + end end -- same as Stream:scale() but for values between [0, 127] @@ -309,9 +289,13 @@ function Stream:ccscale(v1, v2) local lower = v2 and v1 or 0 local upper = v2 or v1 - return self:map(function(x) - return (x/127)*(upper - lower) + lower - end) + if type(lower) == "number" and type(upper) == "number" then + return self:map(function(x) + return (x/127)*(upper - lower) + lower + end) + else + return self*((upper - lower)/127) + lower + end end function Stream:scan(fnc) @@ -336,14 +320,22 @@ end function Stream:mix(other, wetness) wetness = wetness or 0.5 - return self:mul(1 - wetness) + other:mul(wetness) + return self*(1 - wetness) + other*wetness end function Stream:pan(location) location = location or 0 local cached = self:cache() - return MuxStream:new(cached:mul(1-math.max(location, 0)), - cached:mul(1+math.min(location, 0))) + + if type(location) == "number" then + return MuxStream:new(cached * (1-math.max(location, 0)), + cached * (1+math.min(location, 0))) + else + local location_cached = tostream(location):cache() + + return MuxStream:new(cached * (1-location_cached:max(0)), + cached * (1+location_cached:min(0))) + end end function Stream:delay(length) @@ -389,7 +381,7 @@ function Stream.SawOsc(freq, phase) return ScanStream:new(freq, function(accu, f) return ((accu or phase) + 2*f/samplerate) % 2 - end):minus(1) + end) - 1 end function Stream.SinOsc(freq, phase) @@ -719,41 +711,86 @@ function Stream:__tostring() return "{"..table.concat(t, ", ").."}" end +-- NOTE: These operators work with scalars and streams. +-- The semantics of e.g. adding Stream(x) is compatible +-- with a map that adds x. Maps are preferred since +-- they are (slightly). -- NOTE: Named addOp() and similar functions below -- are necessary instead of lambdas so consecutive -- operations can be collapsed by ZipStream (which -- tests for function equivalence) -local function addOp(x1, x2) return x1+x2 end -function Stream.__add(op1, op2) - return ZipStream:new(addOp, op1, op2) + +do + local function addOp(x1, x2) return x1+x2 end + + function Stream.add(v1, v2) + return type(v2) == "number" and + MapStream:new(v1, function(x) return x+v2 end) or + ZipStream:new(addOp, v1, v2) + end + Stream.__add = Stream.add end -local function subOp(x1, x2) return x1-x2 end -function Stream.__sub(op1, op2) - return ZipStream:new(subOp, op1, op2) +do + local function subOp(x1, x2) return x1-x2 end + + function Stream.minus(v1, v2) + return type(v2) == "number" and + MapStream:new(v1, function(x) return x-v2 end) or + ZipStream:new(subOp, v1, v2) + end + Stream.__sub = Stream.minus end -local function mulOp(x1, x2) return x1*x2 end -function Stream.__mul(op1, op2) - return ZipStream:new(mulOp, op1, op2) +do + local function mulOp(x1, x2) return x1*x2 end + + function Stream.mul(v1, v2) + return type(v2) == "number" and + MapStream:new(v1, function(x) return x*v2 end) or + ZipStream:new(mulOp, v1, v2) + end + Stream.gain = Stream.mul + Stream["\u{00D7}"] = Stream.mul -- APL Multiply/Signum + Stream.__mul = Stream.mul end -local function divOp(x1, x2) return x1/x2 end -function Stream.__div(op1, op2) - return ZipStream:new(divOp, op1, op2) +do + local function divOp(x1, x2) return x1/x2 end + + function Stream.div(v1, v2) + return type(v2) == "number" and + MapStream:new(v1, function(x) return x/v2 end) or + ZipStream:new(divOp, v1, v2) + end + Stream["\u{00F7}"] = Stream.div -- APL Divide + Stream.__div = Stream.div end -local function modOp(x1, x2) return x1%x2 end -function Stream.__mod(op1, op2) - return ZipStream:new(modOp, op1, op2) +do + local function modOp(x1, x2) return x1%x2 end + + function Stream.mod(v1, v2) + return type(v2) == "number" and + MapStream:new(v1, function(x) return x%v2 end) or + ZipStream:new(modOp, v1, v2) + end + Stream.__mod = Stream.mod end -local function powOp(x1, x2) return x1^x2 end -function Stream.__pow(op1, op2) - return ZipStream:new(powOp, op1, op2) +do + local function powOp(x1, x2) return x1^x2 end + + function Stream.pow(v1, v2) + return type(v2) == "number" and + MapStream:new(v1, function(x) return x^v2 end) or + ZipStream:new(powOp, v1, v2) + end + Stream["\u{22C6}"] = Stream.pow -- APL Exponentiation + Stream.__pow = Stream.pow end -function Stream:__unm() return self:mul(-1) end +function Stream:__unm() return self * -1 end function Stream.__concat(op1, op2) return ConcatStream:new(op1, op2) @@ -1069,14 +1106,19 @@ function ConcatStream:gtick() ticks[k] = self.streams[k]:gtick() end - return function() - while i <= #ticks do - local sample = ticks[i]() + -- NOTE: Binding each tick function to a variable + -- is faster since it allows the JIT compiler + -- to inline functions. + local tick = ticks[1] + return function() + while tick do + local sample = tick() if sample then return sample end -- try next stream i = i + 1 + tick = ticks[i] end end end @@ -1323,10 +1365,11 @@ end function MapStream:gtick() local tick = self.stream:gtick() + local fnc = self.fnc return function() local sample = tick() - return sample and self.fnc(sample) + return sample and fnc(sample) end end @@ -1346,13 +1389,14 @@ end function ScanStream:gtick() local tick = self.stream:gtick() + local fnc = self.fnc local last_sample = nil return function() local sample = tick() if not sample then return end - last_sample = self.fnc(last_sample, sample) + last_sample = fnc(last_sample, sample) return last_sample end end @@ -1373,6 +1417,7 @@ end function FoldStream:gtick() local tick = self.stream:gtick() + local fnc = self.fnc return function() local l, r @@ -1381,7 +1426,7 @@ function FoldStream:gtick() r = tick() if not r then break end - l = l and self.fnc(l, r) or r + l = l and fnc(l, r) or r end return l @@ -1418,62 +1463,56 @@ function ZipStream:muxableCtor(fnc, ...) end function ZipStream:gtick() - local running = true - local ticks = {} - - for i = 1, #self.streams do - ticks[i] = self.streams[i]:gtick() - end + local fnc = self.fnc - if #ticks == 2 then + if #self.streams == 2 then -- 2 streams are common, so use an unrolled -- version here - return function() - if not running then return end + -- + -- NOTE: Unrolling the ticks array here + -- almost halves the overhead when calculating + -- something like Stream(0)+Stream(1), making + -- it almost as fast as + -- Stream(0):map(function(x) return x+1 end) + local tick1 = self.streams[1]:gtick() + local tick2 = self.streams[2]:gtick() - local sample1, sample2 = ticks[1](), ticks[2]() + return function() + local sample1 = tick1() + if not sample1 then return end - if not sample1 then - running = sample2 - return sample2 - elseif not sample2 then - -- have sample1, keep running - return sample1 - end + local sample2 = tick2() + if not sample2 then return sample1 end - return self.fnc(sample1, sample2) + return fnc(sample1, sample2) end else - return function() - if not running then return end + -- NOTE: Unfortunately, functions in the + -- ticks array cannot be inlined + local ticks = {} + for i = 1, #self.streams do + ticks[i] = self.streams[i]:gtick() + end - local result = nil + return function() + local result = ticks[1]() + if not result then return end - for i = 1, #ticks do + for i = 2, #ticks do local sample = ticks[i]() if sample then - result = result and self.fnc(result, sample) - or sample + result = fnc(result, sample) end end - -- if all streams have ended, `result` will be nil - running = result - return result end end end function ZipStream:len() - local max = 0 - - for _, stream in pairs(self.streams) do - max = math.max(max, stream:len()) - end - - return max + return self.streams[1]:len() end NoiseStream = DeriveClass(Stream) @@ -1492,7 +1531,7 @@ function BrownNoise() return NoiseStream():scan(function(brown, white) brown = (brown or 0) + white return (brown < -8 or brown > 8) and brown - white or brown - end):mul(0.0625) + end) * 0.0625 end PinkNoiseStream = DeriveClass(Stream) @@ -1851,7 +1890,7 @@ function iota(...) return IotaStream:new(...) end _G["\u{2373}"] = iota -- APL Iota function line(v1, t, v2) - return iota(t):mul((v2-v1)/t):add(v1) + return iota(t) * ((v2-v1)/t) + v1 end -- Derived from RTcmix' "curve" table |