synthesizer-llvm-0.9: src/Synthesizer/LLVM/Server/CausalPacked/SpeechExplore.hs
{-# LANGUAGE NoImplicitPrelude #-}
module Main where
import Synthesizer.LLVM.Server.Common (Real)
import qualified Synthesizer.LLVM.Server.SampledSound as Sample
import qualified Sound.Sox.Write as SoxWrite
import qualified Graphics.Gnuplot.Advanced as Plot
import qualified Graphics.Gnuplot.Terminal.WXT as WXT
import qualified Graphics.Gnuplot.Plot.TwoDimensional as Plot2D
import qualified Graphics.Gnuplot.Graph.TwoDimensional as Graph2D
import qualified Synthesizer.LLVM.CausalParameterized.Controlled as CtrlP
import qualified Synthesizer.LLVM.CausalParameterized.Process as CausalP
import qualified Synthesizer.LLVM.Parameterized.Signal as SigP
import qualified Synthesizer.LLVM.Filter.FirstOrder as Filt1
import qualified Synthesizer.LLVM.Filter.NonRecursive as FiltNR
import Synthesizer.LLVM.Causal.Process (($*), ($<), ($<#))
import qualified Synthesizer.Plain.Filter.Recursive.Universal as UniFilter
import qualified Synthesizer.Plain.Filter.Recursive.FirstOrder as FirstOrder
import Synthesizer.Plain.Filter.Recursive (Pole(Pole))
import qualified Synthesizer.Generic.Filter.NonRecursive as FiltNRG
import qualified Synthesizer.Generic.Fourier as Fourier
import qualified Synthesizer.Generic.Analysis as Analysis
import qualified Synthesizer.Generic.Signal as SigG
import qualified Synthesizer.Generic.Piece as Piece
import qualified Synthesizer.Causal.Filter.NonRecursive as FiltNRC
import qualified Synthesizer.Causal.Process as Causal
import qualified Synthesizer.State.Signal as SigS
import Synthesizer.Piecewise ((#|-), (-|#), (#|), (|#))
import qualified Data.StorableVector.Lazy as SVL
import qualified Data.StorableVector as SV
import Control.Arrow (arr, (<<<), (^<<))
import Control.Category ((.), id)
import Control.Applicative ((<$>))
import Control.Functor.HT (void)
import qualified Data.List.HT as ListHT
import qualified Data.List as List
import Data.Foldable (forM_)
import Data.Maybe.HT (toMaybe)
import Data.Maybe (catMaybes)
import Data.Tuple.HT (mapSnd)
import Data.Ord.HT (comparing)
import Data.Monoid (mempty, mappend)
import qualified System.Path.PartClass as PathClass
import qualified System.Path as Path
import System.Path ((</>), (<.>))
import qualified Number.Complex as Complex
import NumericPrelude.Numeric
import NumericPrelude.Base hiding (id, (.))
sampleRateInt :: Int
sampleRateInt = 44100
sampleRate :: Real
sampleRate = fromIntegral sampleRateInt
spectrum :: SVL.Vector Real -> SVL.Vector Real
spectrum xs =
SVL.map Complex.magnitude $
SVL.take (div (SVL.length xs) 2) $
Fourier.transformBackward $
SVL.map Complex.fromReal xs
timeDomain :: SVL.Vector Real -> SVL.Vector (Complex.T Real)
timeDomain xs =
Fourier.transformForward $
SVL.append
(SVL.map Complex.fromReal xs)
(SVL.replicate SVL.defaultChunkSize (SVL.length xs) 0)
chop :: Int -> SVL.Vector Real -> [SVL.Vector Real]
chop n =
map (SVL.take n) .
takeWhile (not . SVL.null) .
iterate (SVL.drop n)
spectrumPlot :: SVL.Vector Real -> Plot2D.T Real Real
spectrumPlot xs =
let k = sampleRate / fromIntegral (SVL.length xs)
step = 16
avg chunk = SVL.foldl (+) zero chunk / fromIntegral step
in Plot2D.list Graph2D.lines $
zip (iterate (fromIntegral step * k +) 0) $
map avg $ chop step $
spectrum xs
plotSpectrum :: SVL.Vector Real -> IO ()
plotSpectrum xs =
void $
Plot.plot WXT.cons $
spectrumPlot xs
saveSound :: (PathClass.AbsRel ar) => Path.File ar -> SVL.Vector Real -> IO ()
saveSound path xs =
void $ SoxWrite.simple SVL.hPut mempty (Path.toString path) sampleRateInt xs
tmpWave :: String -> Path.AbsFile
tmpWave name = Path.absDir "/tmp" </> Path.relFile name <.> "wav"
phonemeWave :: String -> Path.RelFile
phonemeWave name = Path.relDir "phoneme" </> Path.relFile name <.> "wav"
maskWave :: String -> Path.RelFile
maskWave name = Path.relDir "mask" </> Path.relFile name <.> "wav"
loadPhoneme :: String -> IO (SVL.Vector Real)
loadPhoneme name = do
putStrLn name
Sample.load $ phonemeWave name
-- * modelling formants using bandpass filters
type Formant a = (UniFilter.Result a -> a, Pole Real, Real)
formants_a_noise :: [Formant a]
formants_a_noise =
(UniFilter.bandpass, Pole 20 900, 1) :
(UniFilter.bandpass, Pole 20 1200, 0.4) :
(UniFilter.bandpass, Pole 10 2600, 0.07) :
[]
formants_f :: [Formant a]
formants_f =
(UniFilter.lowpass, Pole 2 4000, 0.6) :
(UniFilter.lowpass, Pole 2 11000, 0.3) :
[]
formants_sch :: [Formant a]
formants_sch =
(UniFilter.bandpass, Pole 5 1500, 1.3) :
(UniFilter.lowpass, Pole 2 3000, 0.6) :
[]
synthesis :: IO (SVL.ChunkSize -> () -> SVL.Vector Real)
synthesis =
SigP.runChunky $
(sum (map (\(typ, Pole q f, amp) ->
CausalP.amplify (return amp)
<<<
typ
^<<
CtrlP.process
$<
(SigP.constant $ return $
UniFilter.parameter $ Pole q $ f / sampleRate))
formants_sch)
$* SigP.noise 174373 0.02)
compareSpec ::IO ()
compareSpec = do
sampled <- Sample.load (phonemeWave "sch")
synthesized <- synthesis
void $ Plot.plot WXT.cons $
spectrumPlot sampled
`mappend`
spectrumPlot
(SVL.take (SVL.length sampled) $
synthesized (SVL.chunkSize 4096) ())
render ::IO ()
render = do
synthesized <- synthesis
saveSound (Path.relFile "sch-synth.wav") $
SVL.take sampleRateInt $
synthesized (SVL.chunkSize 4096) ()
-- * purification of sampled periods
-- ** using a comb filter
type Comb = (Real, Int) -> SVL.Vector Real -> SVL.Vector Real
makeComb :: IO Comb
makeComb =
CausalP.runStorableChunky $ CausalP.comb (arr fst) (arr snd)
makeHighComb :: IO Comb
makeHighComb =
CausalP.runStorableChunky $
CausalP.comb (arr fst) (arr snd)
.
(Filt1.highpassCausal $<# FirstOrder.parameter (1000/sampleRate))
scorePeriod :: Comb -> Real -> Int -> SVL.Vector Real -> (Real, SVL.Vector Real)
scorePeriod comb gain period sig =
let end = SVL.takeEnd (3*period) $ comb (gain, period) sig
in (Analysis.volumeEuclideanSqr end, end)
vowelNames :: [String]
vowelNames = ["a", "e", "i", "o", "on", "u", "un", "oe", "ue", "ae"]
tonalNames :: [String]
tonalNames = vowelNames ++ ["l", "m", "n", "ng", "r", "j"]
sibilantNames :: [String]
sibilantNames = ["f", "h", "w", "s", "sch", "th", "ich", "ach"]
stopConsonantNames :: [String]
stopConsonantNames = ["p", "k", "t", "b", "g", "d"]
scanPeriods ::IO ()
scanPeriods = do
comb <- makeComb
forM_ tonalNames $ \name -> do
sampled <- loadPhoneme name
let scores =
flip map [350 .. 400] $ \period ->
(period,
flip map [0.9, 0.99, 0.999] $ \gain ->
fst $ scorePeriod comb gain period sampled)
-- mapM_ print scores
putStrLn $
"maximum: " ++
show (List.maximumBy (comparing snd) $ map (mapSnd maximum) scores)
normalize :: SVL.Vector Real -> SVL.Vector Real
normalize =
FiltNRG.normalize ((4*) . Analysis.volumeEuclidean)
{-
We use the zero with the least derivative
in order to reduce jumps at the loop point.
In order to further reduce jumps, we cross-fade two adjacent periods.
It must be @length period3 = 3*len@.
@period3@ must contain a zero in the center chunk of size @len@.
-}
bestRotation :: Int -> SVL.Vector Real -> SVL.Vector Real
bestRotation len period3 =
let start =
fst $
List.minimumBy (comparing snd) $ catMaybes $
zipWith (fmap . (,)) [0..] $
ListHT.mapAdjacent
(\x y -> toMaybe (signum x /= signum y) (abs(x-y))) $
SVL.unpack $ SVL.take len $ SVL.drop (len-1) period3
in Causal.apply
(Causal.applyFst
(FiltNRC.crossfade len)
(SVL.drop (start+len) period3))
(SVL.drop start period3)
findPeriod :: Comb -> SVL.Vector Real -> SVL.Vector Real
findPeriod comb sampled =
normalize $
uncurry bestRotation $
mapSnd snd $
List.maximumBy (comparing (fst . snd)) $
flip map [350 .. 400] $ \period ->
(period, scorePeriod comb 0.99 period sampled)
extractPeriods ::IO ()
extractPeriods = do
comb <- makeHighComb
forM_ tonalNames $ \name ->
saveSound (maskWave name) . findPeriod comb =<< loadPhoneme name
-- ** using the frequency spectrum
makeFilter :: IO (SV.Vector Real -> SVL.Vector Real -> SVL.Vector Real)
makeFilter =
CausalP.runStorableChunky $ FiltNR.convolve id
normalizeMax :: SVL.Vector Real -> SVL.Vector Real
normalizeMax = FiltNRG.normalize Analysis.volumeMaximum
envelope :: Int -> SVL.Vector Real
envelope sizeInt =
let size = fromIntegral sizeInt
rampSize = size / 8
in Piece.run SigG.defaultLazySize $
0 |# (rampSize, Piece.cosine) #|-
1 -|# (size-2*rampSize, Piece.step) #|-
1 -|# (rampSize, Piece.cosine) #| (0::Float)
data Transfer =
Transfer {
transferSpectrum,
transferShrunkenSpectrum,
transferEnvelope,
transferWindow :: SVL.Vector Real
}
transfer :: SVL.Vector Real -> Transfer
transfer sampled =
let halfResponseSize = 256
responseSize = 2*halfResponseSize
halfShrink = div (SVL.length sampled) (2*responseSize)
shrink = 2*halfShrink
spec = spectrum $ SVL.take (shrink*responseSize) sampled
shrunkenSpec =
SigG.fromState SigG.defaultLazySize $
SigS.init $ SigS.cons 0 $ SigS.map SigG.sum $
SigG.sliceVertical shrink $ SVL.drop halfShrink spec
env = envelope responseSize
window =
FiltNRG.envelope env $
uncurry (flip SVL.append) $
SVL.splitAt halfResponseSize $
normalizeMax $ SVL.map Complex.real $
timeDomain shrunkenSpec
in Transfer {
transferSpectrum = spec,
transferShrunkenSpectrum = shrunkenSpec,
transferEnvelope = env,
transferWindow = window
}
testTransfer ::IO ()
testTransfer = do
trans <- transfer <$> Sample.load (phonemeWave "o-noise")
filt <- makeFilter
saveSound (tmpWave "spectrum") $
normalizeMax $ transferSpectrum trans
saveSound (tmpWave "shrunkenspectrum") $
normalizeMax $ transferShrunkenSpectrum trans
saveSound (tmpWave "envelope") $ transferEnvelope trans
saveSound (tmpWave "window") $ transferWindow trans
let window = SV.concat $ SVL.chunks $ transferWindow trans
saveSound (tmpWave "filtered") $
filt window $ SVL.concat $ replicate 100 $ SVL.cons 1 $
SVL.replicate SVL.defaultChunkSize 380 0
saveSound (tmpWave "chirp") $
filt window $ SVL.concat $
map (\n -> SVL.cons 1 $ SVL.replicate SVL.defaultChunkSize n 0) $
[350..450]
transferMasks ::IO ()
transferMasks = do
-- forM_ (map (++"-noise") vowelNames) $ \name -> do
forM_ (tonalNames++sibilantNames++stopConsonantNames) $ \name -> do
trans <- transfer <$> loadPhoneme name
saveSound (maskWave name) $ normalize $ transferWindow trans
let spectrumPath = tmpWave $ "spec-" ++ name
saveSound spectrumPath $ normalizeMax $ transferShrunkenSpectrum trans
main :: IO ()
main = transferMasks