Skip to content

Commit a0813d3

Browse files
SchlenkRSchlenkR
authored andcommitted
Merge branch 'master' of https://github.com/fsprojects/LocSta
# Conflicts: # src/LocSta2/Core.fs
2 parents ce46507 + 2a3afb2 commit a0813d3

33 files changed

Lines changed: 372 additions & 209 deletions

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/LocSta2/Arithmetic/Rescale.fs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ open LocSta.Core
44

55
/// Linearly maps the input from [inMin, inMax] to [outMin, outMax].
66
let rescale (inMin: float) inMax outMin outMax =
7+
let invRange = 1.0 / (inMax - inMin)
8+
let outRange = outMax - outMin
79
stream {
810
let! ctx = getCtx()
9-
let normalized = (ctx - inMin) / (inMax - inMin)
10-
return outMin + normalized * (outMax - outMin)
11+
return outMin + (ctx - inMin) * invRange * outRange
1112
}

src/LocSta2/Core.fs

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,29 @@ type SigStreamBuilder() =
2525
| ValueNone -> StateController(ValueNone), StateController(ValueNone)
2626
| ValueSome (ms,fs) -> ms, fs
2727
let mvs,ms = m ms ctx
28-
let results = ResizeArray()
29-
let mutable currentFs = fs
30-
for mv in mvs do
31-
let fStream = f mv
32-
let fvs, newFs = fStream currentFs ctx
33-
for fv in fvs do results.Add(fv)
34-
currentFs <- newFs
35-
do s.Set(ms, currentFs)
36-
results :> seq<_>, s
28+
// Fast path: most streams emit exactly one value via [| x |], so skip ResizeArray
29+
match mvs with
30+
| :? ('a array) as arr when arr.Length = 1 ->
31+
let fStream = f arr[0]
32+
let fvs, newFs = fStream fs ctx
33+
do s.Set(ms, newFs)
34+
fvs, s
35+
| _ ->
36+
let results = ResizeArray()
37+
let mutable currentFs = fs
38+
for mv in mvs do
39+
let fStream = f mv
40+
let fvs, newFs = fStream currentFs ctx
41+
for fv in fvs do results.Add(fv)
42+
currentFs <- newFs
43+
do s.Set(ms, currentFs)
44+
results :> seq<_>, s
3745

3846
member inline _.Return(x) : SigStream<_,_,unit> =
39-
fun s _ -> Seq.singleton x, s
47+
fun s _ -> [| x |] :> seq<_>, s
4048

4149
member inline _.Yield(x) : SigStream<_,_,unit> =
42-
fun s _ -> Seq.singleton x, s
50+
fun s _ -> [| x |] :> seq<_>, s
4351

4452
member inline _.ReturnFrom(v) = v
4553

@@ -71,7 +79,8 @@ let ofSeq (sequence: seq<_>) : SigStream<_,_,_> =
7179
| ValueSome e -> e
7280
match enumerator.MoveNext() with
7381
| true ->
74-
Seq.singleton enumerator.Current, s
82+
s.Set(enumerator)
83+
[| enumerator.Current |] :> seq<_>, s
7584
| false ->
7685
// failwith "Sequence contains no more elements"
7786
Seq.empty, s
@@ -85,12 +94,12 @@ let inline map ([<InlineIfLambda>] proj) ([<InlineIfLambda>] s1) =
8594

8695
/// Get the context value
8796
let inline getCtx<'c> () : SigStream<'c,'c,unit> =
88-
fun s ctx -> Seq.singleton ctx, s
97+
fun s ctx -> [| ctx |] :> seq<_>, s
8998

9099
/// Get the state controller of this block
91100
let inline getState<'c,'s> () : SigStream<StateController<'s>,'c,'s> =
92101
fun state ctx ->
93-
Seq.singleton state, state
102+
[| state |] :> seq<_>, state
94103

95104
/// Use a memoized value (lazy initialization)
96105
let inline useMemoWith ([<InlineIfLambda>] initializer) : SigStream<'a,'c,'a> =
@@ -100,7 +109,7 @@ let inline useMemoWith ([<InlineIfLambda>] initializer) : SigStream<'a,'c,'a> =
100109
| ValueNone -> initializer()
101110
| ValueSome v -> v
102111
state.Set(value)
103-
Seq.singleton value, state
112+
[| value |] :> seq<_>, state
104113

105114
let useMemo (value: 'a) : SigStream<'a,'c,'a> =
106115
fun (state: StateController<'a>) ctx ->
@@ -109,7 +118,7 @@ let useMemo (value: 'a) : SigStream<'a,'c,'a> =
109118
| ValueNone -> value
110119
| ValueSome v -> v
111120
state.Set(v)
112-
Seq.singleton v, state
121+
[| v |] :> seq<_>, state
113122

114123
/// Mutable value for local state within streams
115124
type MutableValue<'s>(initValue: 's) =
@@ -159,14 +168,23 @@ module Eval =
159168
toSeqWith 1000 getCtx stream
160169

161170
/// Evaluate n samples
162-
let inline run n getCtx stream =
163-
toSeq getCtx stream |> Seq.take n |> Seq.toList
171+
let inline run (n: int) getCtx stream =
172+
let state = StateController(ValueNone)
173+
let results = ResizeArray<_>(n)
174+
let mutable i = 0
175+
while results.Count < n do
176+
let ctx = getCtx i
177+
i <- i + 1
178+
let vs, _ = stream state ctx
179+
for v in vs do
180+
if results.Count < n then results.Add(v)
181+
Seq.toList results
164182

165183
/// Evaluate with input sequence
166184
let inline runWith (inputs: seq<_>) stream =
167185
let state = StateController(ValueNone)
168-
inputs
169-
|> Seq.collect (fun ctx ->
186+
let results = ResizeArray<_>()
187+
for ctx in inputs do
170188
let vs, _ = stream state ctx
171-
vs)
172-
|> Seq.toList
189+
for v in vs do results.Add(v)
190+
Seq.toList results

src/LocSta2/Counting/CountIn.fs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,17 @@ open LocSta.Core
66
let countIn windowSize =
77
stream {
88
let! ctx = getCtx()
9-
let! window = useState []
10-
let newWindow = (ctx :: window.Value) |> List.truncate windowSize
11-
window.Value <- newWindow
12-
return newWindow |> List.filter id |> List.length
9+
let! state = useStateWith (fun () ->
10+
let arr = Array.create windowSize false
11+
MutableValue(arr, 0, 0, 0))
12+
let (arr, idx, count, trueCount) = state.Value.Value
13+
let newCount = min (count + 1) windowSize
14+
// Subtract old value being overwritten (only if buffer is full)
15+
let removed = if count >= windowSize && arr[idx] then 1 else 0
16+
let added = if ctx then 1 else 0
17+
let newTrueCount = trueCount - removed + added
18+
arr[idx] <- ctx
19+
let nextIdx = (idx + 1) % windowSize
20+
state.Value.Value <- (arr, nextIdx, newCount, newTrueCount)
21+
return newTrueCount
1322
}

src/LocSta2/Counting/Rate.fs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,17 @@ open LocSta.Core
66
let rate windowSize =
77
stream {
88
let! ctx = getCtx()
9-
let! window = useState []
10-
let newWindow = (ctx :: window.Value) |> List.truncate windowSize
11-
window.Value <- newWindow
12-
let count = newWindow |> List.filter id |> List.length
13-
return float count / float newWindow.Length
9+
let! state = useStateWith (fun () ->
10+
let arr = Array.create windowSize false
11+
MutableValue(arr, 0, 0, 0))
12+
let (arr, idx, count, trueCount) = state.Value.Value
13+
let newCount = min (count + 1) windowSize
14+
// Subtract old value being overwritten (only if buffer is full)
15+
let removed = if count >= windowSize && arr[idx] then 1 else 0
16+
let added = if ctx then 1 else 0
17+
let newTrueCount = trueCount - removed + added
18+
arr[idx] <- ctx
19+
let nextIdx = (idx + 1) % windowSize
20+
state.Value.Value <- (arr, nextIdx, newCount, newTrueCount)
21+
return float newTrueCount / float newCount
1422
}

src/LocSta2/Delay/DelayByN.fs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ open LocSta.Core
66
let delayByN n defaultValue =
77
stream {
88
let! ctx = getCtx()
9-
let! buffer = useStateWith (fun () -> List.replicate n defaultValue)
9+
let! state = useStateWith (fun () ->
10+
let arr = Array.create (max 1 n) defaultValue
11+
MutableValue(arr, 0))
12+
let (arr, idx) = state.Value.Value
1013
let output =
11-
match buffer.Value with
12-
| [] -> ctx
13-
| head :: _ -> head
14-
if n > 0 then
15-
buffer.Value <-
16-
match buffer.Value with
17-
| [] -> [ ctx ]
18-
| _ :: tail -> tail @ [ ctx ]
14+
if n > 0 then arr[idx]
15+
else ctx
16+
arr[idx] <- ctx
17+
let nextIdx = (idx + 1) % (max 1 n)
18+
state.Value.Value <- (arr, nextIdx)
1919
return output
2020
}

src/LocSta2/Detection/Crossover.fs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@ let crossover s1 s2 =
77
stream {
88
let! v1 = s1
99
let! v2 = s2
10-
let! prevDiff = useState ValueNone
10+
let! st = useMemoWith (fun () -> MutableValue(0.0, false))
11+
let (prevDiff, hasPrev) = st.Value
1112
let currDiff = v1 - v2
1213
let output =
13-
match prevDiff.Value with
14-
| ValueNone -> 0
15-
| ValueSome pd ->
16-
if pd <= 0.0 && currDiff > 0.0 then 1
17-
elif pd >= 0.0 && currDiff < 0.0 then -1
18-
else 0
19-
prevDiff.Value <- ValueSome currDiff
14+
if not hasPrev then 0
15+
elif prevDiff <= 0.0 && currDiff > 0.0 then 1
16+
elif prevDiff >= 0.0 && currDiff < 0.0 then -1
17+
else 0
18+
st.Value <- (currDiff, true)
2019
return output
2120
}

src/LocSta2/Detection/Threshold.fs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@ open LocSta.Core
66
let inline threshold level =
77
stream {
88
let! ctx = getCtx()
9-
let! prev = useState ValueNone
9+
let! st = useMemoWith (fun () -> MutableValue(ctx, false))
10+
let (p, hasPrev) = st.Value
1011
let output =
11-
match prev.Value with
12-
| ValueNone -> 0
13-
| ValueSome p ->
14-
if p < level && ctx >= level then 1
15-
elif p >= level && ctx < level then -1
16-
else 0
17-
prev.Value <- ValueSome ctx
12+
if not hasPrev then 0
13+
elif p < level && ctx >= level then 1
14+
elif p >= level && ctx < level then -1
15+
else 0
16+
st.Value <- (ctx, true)
1817
return output
1918
}

src/LocSta2/Dsp/Analysis/Rms.fs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,24 @@ module LocSta.Blocks.Dsp.Analysis.Rms
22

33
open LocSta.Core
44

5+
/// Computes RMS over the first 'count' elements of 'arr'.
6+
let private computeRms (arr: float array) (count: int) =
7+
let mutable sumSq = 0.0
8+
for i = 0 to count - 1 do
9+
sumSq <- sumSq + arr[i] * arr[i]
10+
sqrt (sumSq / float count)
11+
512
/// RMS (Root Mean Square) level over a window of 'windowSize' samples.
613
let rms windowSize =
714
stream {
815
let! signal = getCtx()
9-
let! window = useState []
10-
let newWindow = (signal :: window.Value) |> List.truncate windowSize
11-
window.Value <- newWindow
12-
let sumOfSquares = newWindow |> List.sumBy (fun x -> x * x)
13-
return sqrt (sumOfSquares / float newWindow.Length)
16+
let! state = useStateWith (fun () ->
17+
let arr = Array.zeroCreate<float> windowSize
18+
MutableValue(arr, 0, 0))
19+
let (arr, idx, count) = state.Value.Value
20+
let newCount = min (count + 1) windowSize
21+
arr[idx] <- signal
22+
let nextIdx = (idx + 1) % windowSize
23+
state.Value.Value <- (arr, nextIdx, newCount)
24+
return computeRms arr newCount
1425
}

src/LocSta2/Dsp/Analysis/ZeroCrossRate.fs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,30 @@ module LocSta.Blocks.Dsp.Analysis.ZeroCrossRate
22

33
open LocSta.Core
44

5+
/// Computes zero-crossing rate over 'count' elements in circular buffer starting at 'startIdx'.
6+
let private computeZeroCrossings (arr: float array) (count: int) (startIdx: int) (windowSize: int) =
7+
if count < 2 then 0.0
8+
else
9+
let mutable crossings = 0
10+
for k = 0 to count - 2 do
11+
let i = (startIdx + k) % windowSize
12+
let j = (startIdx + k + 1) % windowSize
13+
if (arr[i] >= 0.0 && arr[j] < 0.0) || (arr[i] < 0.0 && arr[j] >= 0.0) then
14+
crossings <- crossings + 1
15+
float crossings / float (count - 1)
16+
517
/// Zero crossing rate over a window of 'windowSize' samples.
618
let zeroCrossRate windowSize =
719
stream {
820
let! signal = getCtx()
9-
let! window = useState []
10-
let newWindow = (signal :: window.Value) |> List.truncate windowSize
11-
window.Value <- newWindow
12-
let crossings =
13-
newWindow
14-
|> List.pairwise
15-
|> List.sumBy (fun (a, b) -> if (a >= 0.0 && b < 0.0) || (a < 0.0 && b >= 0.0) then 1 else 0)
16-
return float crossings / float (max 1 (newWindow.Length - 1))
21+
let! state = useStateWith (fun () ->
22+
let arr = Array.zeroCreate<float> windowSize
23+
MutableValue(arr, 0, 0))
24+
let (arr, idx, count) = state.Value.Value
25+
let newCount = min (count + 1) windowSize
26+
arr[idx] <- signal
27+
let nextIdx = (idx + 1) % windowSize
28+
state.Value.Value <- (arr, nextIdx, newCount)
29+
let startIdx = if newCount < windowSize then 0 else nextIdx
30+
return computeZeroCrossings arr newCount startIdx windowSize
1731
}

0 commit comments

Comments
 (0)