-
-
Notifications
You must be signed in to change notification settings - Fork 263
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
82 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -162,4 +162,4 @@ run = | |
-- ), | ||
-- ); | ||
-- }); | ||
-- }); | ||
-- }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,90 +1,92 @@ | ||
{-# LANGUAGE OverloadedStrings #-} | ||
{-# LANGUAGE ForeignFunctionInterface #-} | ||
{-# LANGUAGE DeriveGeneric #-} | ||
{-# LANGUAGE ForeignFunctionInterface #-} | ||
{-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
{-# LANGUAGE OverloadedStrings #-} | ||
|
||
module Sound.Tidal.Parse.FFI where | ||
|
||
import Foreign.C.String (CString, peekCString, newCString) | ||
import Data.Aeson (ToJSON (..), object, (.=)) | ||
import qualified Data.Aeson as Aeson | ||
import Data.Aeson (ToJSON(..), object, (.=)) | ||
import qualified Data.ByteString.Lazy.Char8 as B | ||
import qualified Data.Map.Strict as Map | ||
import Data.Maybe (fromMaybe) | ||
import Text.Read (readMaybe) | ||
import Foreign.C.String (CString, newCString, peekCString) | ||
import GHC.Generics (Generic) | ||
|
||
import Sound.Tidal.Params () | ||
import Sound.Tidal.Parse (parseTidal) | ||
import Sound.Tidal.Pattern | ||
import Sound.Tidal.Params () | ||
import Sound.Tidal.Show () | ||
import Text.Read (readMaybe) | ||
|
||
-- Newtype wrappers to avoid orphan instances | ||
newtype JSONValue = JSONValue { unJSONValue :: Value } | ||
newtype JSONValue = JSONValue {unJSONValue :: Value} | ||
deriving (Generic) | ||
|
||
instance ToJSON JSONValue where | ||
toJSON (JSONValue (VS str)) = toJSON str | ||
toJSON (JSONValue (VI i)) = toJSON i | ||
toJSON (JSONValue (VF f)) = toJSON f | ||
toJSON (JSONValue (VN num)) = toJSON $ show num | ||
toJSON (JSONValue (VR r)) = toJSON $ show r | ||
toJSON (JSONValue (VB b)) = toJSON b | ||
toJSON (JSONValue (VX xs)) = toJSON xs | ||
toJSON (JSONValue (VPattern pat)) = toJSON $ show pat | ||
toJSON (JSONValue (VState f)) = toJSON $ show $ f Map.empty | ||
toJSON (JSONValue (VList vs)) = toJSON $ map JSONValue vs | ||
toJSON (JSONValue (VS str)) = toJSON str | ||
toJSON (JSONValue (VI i)) = toJSON i | ||
toJSON (JSONValue (VF f)) = toJSON f | ||
toJSON (JSONValue (VN num)) = toJSON $ show num | ||
toJSON (JSONValue (VR r)) = toJSON $ show r | ||
toJSON (JSONValue (VB b)) = toJSON b | ||
toJSON (JSONValue (VX xs)) = toJSON xs | ||
toJSON (JSONValue (VPattern pat)) = toJSON $ show pat | ||
toJSON (JSONValue (VState f)) = toJSON $ show $ f Map.empty | ||
toJSON (JSONValue (VList vs)) = toJSON $ map JSONValue vs | ||
|
||
newtype JSONArcF = JSONArcF (ArcF Rational) | ||
deriving (Generic) | ||
|
||
instance ToJSON JSONArcF where | ||
toJSON (JSONArcF (Arc arcStart arcStop)) = | ||
object ["start" .= (realToFrac arcStart :: Double), | ||
"stop" .= (realToFrac arcStop :: Double)] | ||
toJSON (JSONArcF (Arc arcStart arcStop)) = | ||
object | ||
[ "start" .= (realToFrac arcStart :: Double), | ||
"stop" .= (realToFrac arcStop :: Double) | ||
] | ||
|
||
newtype JSONEventF = JSONEventF (Event (Map.Map String Value)) | ||
deriving (Generic) | ||
|
||
instance ToJSON JSONEventF where | ||
toJSON (JSONEventF (Event _ctx evWhole evPart evValue)) = | ||
object [ "whole" .= fmap JSONArcF evWhole -- Handle Maybe | ||
, "part" .= JSONArcF evPart | ||
, "value" .= fmap JSONValue evValue ] | ||
|
||
|
||
toJSON (JSONEventF (Event _ctx evWhole evPart evValue)) = | ||
object | ||
[ "whole" .= fmap JSONArcF evWhole, -- Handle Maybe | ||
"part" .= JSONArcF evPart, | ||
"value" .= fmap JSONValue evValue | ||
] | ||
|
||
-- Foreign export wrapper function | ||
foreign export ccall eval_pattern_c :: CString -> CString -> IO CString | ||
|
||
eval_pattern_c :: CString -> CString -> IO CString | ||
eval_pattern_c cStr cArc = do | ||
hsStr <- peekCString cStr | ||
arcStr <- peekCString cArc | ||
let arcLength = fromMaybe 16 (readMaybe arcStr :: Maybe Double) | ||
result <- evalPattern hsStr arcLength | ||
newCString result | ||
hsStr <- peekCString cStr | ||
arcStr <- peekCString cArc | ||
let arcLength = fromMaybe 16 (readMaybe arcStr :: Maybe Double) | ||
result <- evalPattern hsStr arcLength | ||
newCString result | ||
|
||
-- Function to evaluate and return pattern events as a JSON string | ||
evalPattern :: String -> Double -> IO String | ||
evalPattern pat arcLen = do | ||
let parsedResult = parseAndQuery pat arcLen | ||
return $ B.unpack $ Aeson.encode (either encodeError (encodeSuccess arcLen) parsedResult) | ||
let parsedResult = parseAndQuery pat arcLen | ||
return $ B.unpack $ Aeson.encode (either encodeError (encodeSuccess arcLen) parsedResult) | ||
|
||
encodeError :: String -> Aeson.Value | ||
encodeError err = Aeson.object ["error" Aeson..= err] | ||
|
||
encodeSuccess :: Double -> [Event (Map.Map String Value)] -> Aeson.Value | ||
encodeSuccess arcLen events = | ||
Aeson.object ["arcLen" .= arcLen, "events" .= map JSONEventF events] | ||
encodeSuccess arcLen events = | ||
Aeson.object ["arcLen" .= arcLen, "events" .= map JSONEventF events] | ||
|
||
-- Helper functions to handle parsing and querying | ||
parseAndQuery :: String -> Double -> Either String [Event (Map.Map String Value)] | ||
parseAndQuery str arcLen = | ||
case parseTidal str of | ||
Left err -> Left (show err) | ||
Right parsed -> | ||
let arcTime = toRational arcLen | ||
in Right $ query (stripContext parsed) (State (Arc 0 arcTime) Map.empty) | ||
case parseTidal str of | ||
Left err -> Left (show err) | ||
Right parsed -> | ||
let arcTime = toRational arcLen | ||
in Right $ query (stripContext parsed) (State (Arc 0 arcTime) Map.empty) | ||
|
||
stripContext :: Pattern a -> Pattern a | ||
stripContext = setContext $ Context [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,47 @@ | ||
{-# LANGUAGE OverloadedStrings #-} | ||
{-# LANGUAGE ForeignFunctionInterface #-} | ||
{-# LANGUAGE OverloadedStrings #-} | ||
|
||
module Sound.Tidal.TidalParseFFITest where | ||
|
||
import Foreign.C.String (CString, peekCString, newCString) | ||
import Test.HUnit | ||
import Data.Aeson (Value, encode, object, (.=)) | ||
import qualified Data.ByteString.Lazy.Char8 as B | ||
import Foreign.C.String (CString, newCString, peekCString) | ||
import Test.HUnit | ||
|
||
-- Foreign function import | ||
foreign import ccall "eval_pattern_c" eval_pattern_c :: CString -> CString -> IO CString | ||
|
||
-- Utility function to run FFI test | ||
ffiTest :: String -> String -> IO Bool | ||
ffiTest input arcLen = do | ||
cInput <- newCString input | ||
cArcLen <- newCString arcLen | ||
resultPtr <- eval_pattern_c cInput cArcLen | ||
result <- peekCString resultPtr | ||
let expected = B.unpack $ encode mockJSON | ||
return (result == expected) | ||
cInput <- newCString input | ||
cArcLen <- newCString arcLen | ||
resultPtr <- eval_pattern_c cInput cArcLen | ||
result <- peekCString resultPtr | ||
let expected = B.unpack $ encode mockJSON | ||
return (result == expected) | ||
|
||
-- Mock the exact expected JSON output | ||
mockJSON :: Value | ||
mockJSON = object | ||
[ "arcLen" .= (1 :: Int) | ||
, "events" .= | ||
[ object [ | ||
"part" .= object ["start" .= (0 :: Double), "stop" .= (0.5 :: Double)] | ||
, "value" .= object ["s" .= ("bd" :: String)] | ||
, "whole" .= object ["start" .= (0 :: Double), "stop" .= (0.5 :: Double)] | ||
] | ||
, object [ | ||
"part" .= object ["start" .= (0.5 :: Double), "stop" .= (1 :: Double)] | ||
, "value" .= object ["s" .= ("cd" :: String)] | ||
, "whole" .= object ["start" .= (0.5 :: Double), "stop" .= (1 :: Double)] | ||
] | ||
] | ||
mockJSON = | ||
object | ||
[ "arcLen" .= (1 :: Int), | ||
"events" | ||
.= [ object | ||
[ "part" .= object ["start" .= (0 :: Double), "stop" .= (0.5 :: Double)], | ||
"value" .= object ["s" .= ("bd" :: String)], | ||
"whole" .= object ["start" .= (0 :: Double), "stop" .= (0.5 :: Double)] | ||
], | ||
object | ||
[ "part" .= object ["start" .= (0.5 :: Double), "stop" .= (1 :: Double)], | ||
"value" .= object ["s" .= ("cd" :: String)], | ||
"whole" .= object ["start" .= (0.5 :: Double), "stop" .= (1 :: Double)] | ||
] | ||
] | ||
] | ||
|
||
-- Test case with the mocked JSON output | ||
testFullPattern :: Test | ||
testFullPattern = TestCase $ do | ||
result <- ffiTest "s $ \"bd cd\"" "1" | ||
assertBool "Full pattern 's $ \"bd cd\"' should return the expected JSON" result | ||
result <- ffiTest "s $ \"bd cd\"" "1" | ||
assertBool "Full pattern 's $ \"bd cd\"' should return the expected JSON" result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
{-# LANGUAGE OverloadedStrings #-} | ||
|
||
import Test.HUnit | ||
import Sound.Tidal.TidalParseFFITest (testFullPattern) | ||
import Test.HUnit | ||
|
||
main :: IO Counts | ||
main = runTestTT $ TestList [testFullPattern] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters