ffmpeg-light-0.14.0: demo/AudioSin.hs
{-# LANGUAGE MultiWayIf #-}
module Main where
import Codec.FFmpeg
import Codec.FFmpeg.AudioStream
import Codec.FFmpeg.Common
import Codec.FFmpeg.Encode
import Codec.FFmpeg.Enums
import Codec.FFmpeg.Juicy
import Codec.FFmpeg.Types
import Codec.Picture
import Control.Monad.Except
import Data.IORef
import Foreign.C.Types
import Foreign.Marshal.Array
import Foreign.Ptr
import Foreign.Storable
import System.Environment
-- Simple Music DSL
type Sound = Float -> Float
volume :: Float -> Sound -> Sound
volume v s t = v * (s t)
mkNote :: Float -> Sound
mkNote f = \t -> sin (f * 2 * pi * t)
a4 :: Sound
a4 = mkNote 440
c4 :: Sound
c4 = mkNote 261.6256
d4 :: Sound
d4 = mkNote 293.6648
e4 :: Sound
e4 = mkNote 329.6276
f4 :: Sound
f4 = mkNote 349.2282
g4 :: Sound
g4 = mkNote 391.9954
b4 :: Sound
b4 = mkNote 493.8833
c5 :: Sound
c5 = mkNote 523.2511
e5 :: Sound
e5 = mkNote 659.2551
combineSounds :: [Sound] -> Sound
combineSounds snds t = sum $ map (\s -> s t) snds
-- Take sounds and their duration and put them in order
sequenceSounds :: [(Float, Sound)] -> Sound
sequenceSounds snds tin = go tin snds
where
go t [(_,snd)] = snd t
go t ((dur,snd):rest)
| t <= dur = snd t
| otherwise = go (t-dur) rest
go _ _ = 0
cMaj7 = combineSounds [ volume 0.23 c4, volume 0.23 e4, volume 0.23 g4, volume 0.23 b4 ]
dMin7 = combineSounds [ volume 0.23 d4, volume 0.23 f4, volume 0.23 a4, volume 0.23 c5 ]
g7 = combineSounds [ volume 0.23 g4, volume 0.23 a4, volume 0.23 c5, volume 0.23 e5 ]
-- ii - V7 - I jazz chord progression
twoFiveOne = sequenceSounds [(1, dMin7), (1, g7), (1, cMaj7)]
mkImage :: Int -> Int -> PixelRGB8 -> Image PixelRGB8
mkImage w h color =
generateImage (\_ _ -> color) w h
main :: IO ()
main = do
initFFmpeg
let w = 1080
h = 720
encParams = AVEncodingParams
{ avepWidth = w
, avepHeight = h
, avepFps = 30
, avepCodec = Nothing
, avepPixelFormat = Nothing
, avepChannelLayout = avChLayoutMono
, avepSampleRate = 44100
, avepSampleFormat = avSampleFmtFltp
, avepPreset = ""
, avepFormatName = Nothing
}
writerContext <- audioVideoWriter encParams "sinusoidal.mp4"
let mCtx = avwAudioCodecContext writerContext
videoWriter = avwVideoWriter writerContext
audioWriter = avwAudioWriter writerContext
case mCtx of
Nothing -> error "Could not get audio ctx"
Just ctx -> do
frame <- frame_alloc_check
setNumSamples frame =<< getFrameSize ctx
setFormat frame . getSampleFormatInt =<< getSampleFormat ctx
setChannelLayout frame =<< getChannelLayout ctx
setSampleRate frame =<< getSampleRate ctx
ch <- getChannelLayout ctx
numChannels <- getChannels ctx
print ("Channel Layout", ch)
print ("Channels", numChannels)
runWithError "Alloc buffers" (av_frame_get_buffer frame 0)
let sampleRate = avepSampleRate encParams
print ("sample rate", sampleRate)
vidFrameRef <- newIORef 0 :: IO (IORef Int)
forM_ [0..120] $ \i -> do
av_frame_make_writable frame
dataPtr <- castPtr <$> getData frame :: IO (Ptr CFloat)
nbSamples <- getNumSamples frame
forM_ [0..nbSamples-1] $ \j -> do
let idx = fromIntegral i * fromIntegral nbSamples + fromIntegral j :: Integer
t = fromIntegral idx / fromIntegral sampleRate
v = twoFiveOne t
poke (advancePtr dataPtr (fromIntegral j)) (realToFrac v)
vidFrame <- readIORef vidFrameRef
when (t * 30 >= fromIntegral vidFrame) $ do
-- TODO: I'm not sure why t seems to be half the actual value but I need to do
-- 0.5 and 1 to make the chord changes match up with the color changes
modifyIORef vidFrameRef (+1)
let color = if | t <= 1 -> PixelRGB8 255 0 0
| t <= 2 -> PixelRGB8 0 255 0
| otherwise -> PixelRGB8 0 0 255
img = mkImage (fromIntegral w) (fromIntegral h) color
videoWriter (Just (fromJuciy img))
audioWriter (Just frame)
videoWriter Nothing
audioWriter Nothing