I want to write a Haskell function that receives a vector and returns another one with the same size but delayed by a certain number of samples d
(add zeroes at the start). Because the produced vector must have the same size, it must buffer the remaining samples so the next application of this function from C continues on with the previously buffered samples. To do so I implemented a ring-buffer concept and hooked it up to C through FFI.
delay :: Int -> V.Vector Float -> V.Vector Float
delay d inp = unsafePerformIO $ do
ini <- MV.replicate d 0.0
buf <- newIORef ini
inx <- newIORef 0
let f v = unsafePerformIO $ do
i <- readIORef inx
b <- readIORef buf
r <- MV.unsafeExchange b i v
writeIORef buf b
writeIORef inx ((i+1) `mod` d)
return r
return $ V.map f inp
Note here there are two types of vectors, Data.StorableVectoras V
(not to be confused with Data.Vector.Storable
), and Data.Vector.Storable.Mutable as MV
for the ring-buffer.
type Process = (V.Vector Float -> V.Vector Float)
foreign export ccall
startCtx :: IO(StablePtr Process)
startCtx = newStablePtr $ delay 2
foreign export ccall
freeCtx :: StablePtr Process -> IO()
freeCtx = freeStablePtr
foreign export ccall
hs_process :: StablePtr Process -> Int -> Ptr Float -> Ptr Float -> IO()
hs_process pf ns i o = do
f <- deRefStablePtr pf
iv <- V.peek ns i
V.poke o $ f iv
On the C side:
#include "Process_stub.h"
#include <vector>
using namespace std;
extern "C" {
void HsStart();
void HsEnd();
}
vector<float> input1 = {1.0, 2.0, 3.0, 4.0, 5.0},
input2 = {6.0, 7.0, 8.0, 9.0, 10.0},
output(input1.size(), 0.0);
int main(int argc, char *argv[])
{
HsStart();
auto pf = startCtx();
hs_process(pf, input1.size(), input1.data(), output.data());
for(int i = 0; i < input1.size(); i++)
printf("[%d] output = %fn", i, output[i]);
hs_process(pf, input2.size(), input2.data(), output.data());
for(int i = 0; i < input2.size(); i++)
printf("[%d] output = %fn", i, output[i]);
freeCtx(pf);
HsEnd();
return 0;
}
What I expect:
First call of hs_process:
[0] input = 1 | output = 0
[1] input = 2 | output = 0
[2] input = 3 | output = 1
[3] input = 4 | output = 2
[4] input = 5 | output = 3
Second call of hs_process:
[0] input = 6 | output = 4
[1] input = 7 | output = 5
[2] input = 8 | output = 6
[3] input = 9 | output = 7
[4] input = 10 | output = 8
But what I get instead:
First call of hs_process:
[0] input = 1 | output = 0
[1] input = 2 | output = 0
[2] input = 3 | output = 1
[3] input = 4 | output = 2
[4] input = 5 | output = 3
Second call of hs_process:
[0] input = 6 | output = 0
[1] input = 7 | output = 0
[2] input = 8 | output = 6
[3] input = 9 | output = 7
[4] input = 10 | output = 8
I can see what I’m doing wrong although I can’t explain properly. I’m just keeping the function, not the applicative per se. I would like to be able to keep every (possibly chained) delay call alive by some closure in the StablePtr.