From 1fc62e7fc1e9bad80dfe32ab997a081f74523c26 Mon Sep 17 00:00:00 2001 From: Tristan Cacqueray Date: Sun, 21 Feb 2021 22:55:19 +0000 Subject: [PATCH] Add demo --- .gitignore | 1 + Main.hs | 45 +++++++++++++++++++++++++++++++++++++++++---- README.md | 22 ++++++++++++++++++++++ cabal.project | 2 +- dear-implot.cabal | 27 ++++++++++++++++++--------- demo.png | Bin 0 -> 18685 bytes 6 files changed, 83 insertions(+), 14 deletions(-) create mode 100644 .gitignore create mode 100644 demo.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..16566ff --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/imgui.ini diff --git a/Main.hs b/Main.hs index b88d162..6fc732d 100644 --- a/Main.hs +++ b/Main.hs @@ -1,18 +1,28 @@ {-# LANGUAGE BlockArguments #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} module Main (main) where import Control.Exception import Control.Monad.IO.Class import Control.Monad.Managed +import Data.Binary.Get (getInt16le, isEmpty, runGet) +import qualified Data.ByteString as BS +import Data.ByteString.Lazy (fromStrict) +import Data.List (iterate') import DearImGui import DearImGui.OpenGL2 import qualified DearImGui.Plot as ImPlot import DearImGui.SDL import DearImGui.SDL.OpenGL +import GHC.Float (int2Float) +import GHC.Int (Int16) import Graphics.GL +import Pipes +import Pipes.PulseSimple +import Pipes.Safe (runSafeT) import SDL main :: IO () @@ -42,21 +52,43 @@ main = do -- Initialize ImGui's OpenGL backend _ <- managed_ $ bracket_ openGL2Init openGL2Shutdown - liftIO $ mainLoop win + liftIO $ runSafeT (runEffect (readPulse "dear-pulse" Nothing 25 >-> mainLoop win)) -mainLoop :: Window -> IO () +-- | Binary decoder +decodeSampleList :: BS.ByteString -> [Int16] +decodeSampleList = runGet get . fromStrict + where + get = do + empty <- isEmpty + if empty + then return [] + else do + sample <- getInt16le + rest <- get + return (sample : rest) + +mainLoop :: MonadIO m => Window -> Consumer' BS.ByteString m () mainLoop win = do -- Process the event loop untilNothingM pollEventWithImGui + -- Get audio buffer + buf <- await + let maxInt16 :: Int16 + maxInt16 = maxBound + maxInt16f = int2Float $ fromIntegral maxInt16 + samples :: [Float] + samples = map (\x' -> int2Float (fromIntegral x') / maxInt16f) $ decodeSampleList buf + -- Tell ImGui we're starting a new frame openGL2NewFrame sdl2NewFrame win newFrame -- Build the GUI - bracket_ (ImPlot.beginPlot "Hello, ImPlot!") ImPlot.endPlot do - ImPlot.plotLine "test" [0.0, 0.1, 0.2, 0.3, 0.4] [0.1, 0.2, 0.3, 0.1, 0.5] + ImPlot.setNextPlotLimits (0, 1) (-1, 1) + liftIO $ bracket_ (ImPlot.beginPlot "Audio") ImPlot.endPlot do + ImPlot.plotLine "pulse-input" xs samples -- Render glClear GL_COLOR_BUFFER_BIT @@ -69,3 +101,8 @@ mainLoop win = do mainLoop win where untilNothingM m = m >>= maybe (return ()) (\_ -> untilNothingM m) + xs = range + range :: [Float] + range = take 1764 $ iterate' (+ step) 0.0 + step :: Float + step = 1 / 1764 diff --git a/README.md b/README.md index ad5b98f..245686d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ # implot binding +This project contains Haskell bindings to the +[dear-imgui](https://github.com/ocornut/imgui) +[implot](https://github.com/epezent/implot) project. + +The [demo](./Main.hs) shows a LinePlot of a pulseaudio input: + +![](./demo.png) + +## Contribute + +To build the project and the demo, make sure these projects are cloned: + +- ./github.com/haskell-game/dear-imgui.hs/ +- ./github.com/TristanCacqueray/pipes-pulse-simple/ +- ./github.com/TristanCacqueray/dear-implot.hs/ + +Then run: + +```ShellSession +$ cabal build +$ cabal run test +``` diff --git a/cabal.project b/cabal.project index a8c2ee9..e9879f8 100644 --- a/cabal.project +++ b/cabal.project @@ -1,2 +1,2 @@ -packages: . ../../haskell-game/dear-imgui.hs/ +packages: . ../../haskell-game/dear-imgui.hs/ ../pipes-pulse-simple/ flags: +sdl +opengl2 -vulkan diff --git a/dear-implot.cabal b/dear-implot.cabal index 5973069..12fc48e 100644 --- a/dear-implot.cabal +++ b/dear-implot.cabal @@ -22,17 +22,26 @@ library include-dirs: implot imgui - build-depends: - base - , containers - , managed - , inline-c - , inline-c-cpp - , StateVar - , dear-imgui + build-depends: base + , StateVar + , containers + , dear-imgui + , inline-c + , inline-c-cpp + , managed executable test main-is: Main.hs default-language: Haskell2010 - build-depends: base, sdl2, gl, dear-imgui, dear-implot, managed + build-depends: base + , binary + , bytestring + , dear-imgui + , dear-implot + , gl + , managed + , pipes + , pipes-safe + , sdl2 + , pipes-pulse-simple ghc-options: -Wall diff --git a/demo.png b/demo.png new file mode 100644 index 0000000000000000000000000000000000000000..3ae7a4d497bcde7c56875ae66f9fa514df1d4418 GIT binary patch literal 18685 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 ZNUd