boopadoop 0.0.0.1 → 0.0.0.2
raw patch · 5 files changed
+358/−32 lines, 5 filesdep +bytestringdep +containersdep +vectornew-component:exe:perfprofPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: bytestring, containers, vector
API changes (from Hackage documentation)
- Boopadoop: modulateMuting :: Num a => Waveform t Bool -> Waveform t a -> Waveform t a
- Boopadoop.Diagram: Factors :: [Integer] -> PitchFactorDiagram
- Boopadoop.Diagram: [getFactors] :: PitchFactorDiagram -> [Integer]
- Boopadoop.Diagram: addPFD :: PitchFactorDiagram -> PitchFactorDiagram -> PitchFactorDiagram
- Boopadoop.Diagram: allSemis :: Floating a => [a]
- Boopadoop.Diagram: countPFD :: Rational -> PitchFactorDiagram
- Boopadoop.Diagram: countPFDFuzzy :: Double -> PitchFactorDiagram
- Boopadoop.Diagram: diagramToFloatyRatio :: PitchFactorDiagram -> Rational
- Boopadoop.Diagram: diagramToRatio :: Fractional a => PitchFactorDiagram -> a
- Boopadoop.Diagram: diagramToSemi :: Floating a => PitchFactorDiagram -> a
- Boopadoop.Diagram: instance GHC.Base.Monoid Boopadoop.Diagram.PitchFactorDiagram
- Boopadoop.Diagram: instance GHC.Base.Semigroup Boopadoop.Diagram.PitchFactorDiagram
- Boopadoop.Diagram: instance GHC.Show.Show Boopadoop.Diagram.PitchFactorDiagram
- Boopadoop.Diagram: intervalOf :: PitchFactorDiagram -> Double -> Double
- Boopadoop.Diagram: invertPFD :: PitchFactorDiagram -> PitchFactorDiagram
- Boopadoop.Diagram: newtype PitchFactorDiagram
- Boopadoop.Diagram: normalizePFD :: PitchFactorDiagram -> PitchFactorDiagram
- Boopadoop.Diagram: printTheSequence :: Int -> IO ()
- Boopadoop.Diagram: scalePFD :: Integer -> PitchFactorDiagram -> PitchFactorDiagram
- Boopadoop.Diagram: semi :: Floating a => a
- Boopadoop.Diagram: takeFinAlignments :: Floating a => Int -> [[a]]
- Boopadoop.Interval: counterExample :: PitchFactorDiagram
- Boopadoop.Interval: majorSecond :: PitchFactorDiagram
- Boopadoop.Interval: majorThird :: PitchFactorDiagram
- Boopadoop.Interval: mystery25 :: PitchFactorDiagram
- Boopadoop.Interval: mysterySeven :: PitchFactorDiagram
- Boopadoop.Interval: octave :: PitchFactorDiagram
- Boopadoop.Interval: perfectFifth :: PitchFactorDiagram
- Boopadoop.Rhythm: Beat :: a -> Beat a
- Boopadoop.Rhythm: Kick :: DrumRack
- Boopadoop.Rhythm: Rest :: Beat a
- Boopadoop.Rhythm: RoseBeat :: [Beat a] -> Beat a
- Boopadoop.Rhythm: Snare :: DrumRack
- Boopadoop.Rhythm: class SummaryChar a
- Boopadoop.Rhythm: data Beat a
- Boopadoop.Rhythm: data DrumRack
- Boopadoop.Rhythm: instance Boopadoop.Rhythm.SummaryChar Boopadoop.Rhythm.DrumRack
- Boopadoop.Rhythm: instance Boopadoop.Rhythm.SummaryChar a => GHC.Show.Show (Boopadoop.Rhythm.Beat a)
- Boopadoop.Rhythm: primeBeat :: Beat a -> Beat a
- Boopadoop.Rhythm: rockBeat :: Beat DrumRack
- Boopadoop.Rhythm: sumUp :: SummaryChar a => a -> Char
+ Boopadoop: Beat :: a -> Beat a
+ Boopadoop: CompactWavetable :: Vector Int32 -> CompactWavetable
+ Boopadoop: Discrete :: Int32 -> Discrete
+ Boopadoop: Factors :: [Integer] -> PitchFactorDiagram
+ Boopadoop: Kick :: DrumRack
+ Boopadoop: Rest :: Beat a
+ Boopadoop: RoseBeat :: [Beat a] -> Beat a
+ Boopadoop: Snare :: DrumRack
+ Boopadoop: Tick :: Int32 -> Tick
+ Boopadoop: [getFactors] :: PitchFactorDiagram -> [Integer]
+ Boopadoop: [getWavetable] :: CompactWavetable -> Vector Int32
+ Boopadoop: [unDiscrete] :: Discrete -> Int32
+ Boopadoop: [unTick] :: Tick -> Int32
+ Boopadoop: addPFD :: PitchFactorDiagram -> PitchFactorDiagram -> PitchFactorDiagram
+ Boopadoop: allSemis :: Floating a => [a]
+ Boopadoop: bandpassFilter :: Fractional a => Double -> Double -> Waveform Double a
+ Boopadoop: class SummaryChar a
+ Boopadoop: countPFD :: Rational -> PitchFactorDiagram
+ Boopadoop: countPFDFuzzy :: Double -> PitchFactorDiagram
+ Boopadoop: counterExample :: PitchFactorDiagram
+ Boopadoop: data Beat a
+ Boopadoop: data CompactWavetable
+ Boopadoop: data DrumRack
+ Boopadoop: diagramToFloatyRatio :: PitchFactorDiagram -> Rational
+ Boopadoop: diagramToRatio :: Fractional a => PitchFactorDiagram -> a
+ Boopadoop: diagramToSemi :: Floating a => PitchFactorDiagram -> a
+ Boopadoop: discFactor :: Num a => a
+ Boopadoop: discreteConvolve :: (Num a, Num t) => Waveform t [(t, a)] -> Waveform t a -> Waveform t a
+ Boopadoop: discreteToDouble :: Discrete -> Double
+ Boopadoop: discretize :: Waveform t Double -> Waveform t Discrete
+ Boopadoop: disguise :: (Double -> Double) -> Discrete -> Discrete
+ Boopadoop: doubleToDiscrete :: Double -> Discrete
+ Boopadoop: exploitPeriodicity :: Tick -> Wavetable -> Wavetable
+ Boopadoop: fastSin :: Double -> Double -> Wavetable
+ Boopadoop: fft :: [Complex Double] -> [Complex Double]
+ Boopadoop: fourierTransform :: Tick -> Double -> Waveform Tick (Complex Double) -> Waveform Double (Complex Double)
+ Boopadoop: instance GHC.Base.Functor (Boopadoop.Waveform t)
+ Boopadoop: instance GHC.Show.Show (Boopadoop.Waveform Boopadoop.Discrete.Tick Boopadoop.Discrete.Discrete)
+ Boopadoop: instance GHC.Show.Show (Boopadoop.Waveform GHC.Types.Double Boopadoop.Discrete.Discrete)
+ Boopadoop: intervalOf :: PitchFactorDiagram -> Double -> Double
+ Boopadoop: invertPFD :: PitchFactorDiagram -> PitchFactorDiagram
+ Boopadoop: majorSecond :: PitchFactorDiagram
+ Boopadoop: majorThird :: PitchFactorDiagram
+ Boopadoop: multiplyDiscrete :: Discrete -> Discrete -> Discrete
+ Boopadoop: muting :: Num a => Bool -> a -> a
+ Boopadoop: mystery25 :: PitchFactorDiagram
+ Boopadoop: mysterySeven :: PitchFactorDiagram
+ Boopadoop: newtype Discrete
+ Boopadoop: newtype PitchFactorDiagram
+ Boopadoop: newtype Tick
+ Boopadoop: normalizePFD :: PitchFactorDiagram -> PitchFactorDiagram
+ Boopadoop: octave :: PitchFactorDiagram
+ Boopadoop: optimizeFilter :: Tick -> Wavetable -> Wavetable
+ Boopadoop: perfectFifth :: PitchFactorDiagram
+ Boopadoop: primeBeat :: Beat a -> Beat a
+ Boopadoop: printTheSequence :: Int -> IO ()
+ Boopadoop: properFloor :: RealFrac a => a -> Int32
+ Boopadoop: quotRoundUp :: Int -> Int -> Int
+ Boopadoop: realDFT :: Tick -> Double -> Wavetable -> Wavetable
+ Boopadoop: rockBeat :: Beat DrumRack
+ Boopadoop: sampledConvolution :: (RealFrac t, Fractional a) => t -> t -> Waveform t (Waveform t a) -> Waveform t a -> Waveform t a
+ Boopadoop: scalePFD :: Integer -> PitchFactorDiagram -> PitchFactorDiagram
+ Boopadoop: seekTo :: Num t => t -> Waveform t a -> Waveform t a
+ Boopadoop: semi :: Floating a => a
+ Boopadoop: skipTicks :: Tick -> Waveform Tick a -> Waveform Tick a
+ Boopadoop: solidSlice :: Tick -> Tick -> Wavetable -> Wavetable
+ Boopadoop: stdtr :: Num a => a
+ Boopadoop: sumUp :: SummaryChar a => a -> Char
+ Boopadoop: takeFinAlignments :: Floating a => Int -> [[a]]
+ Boopadoop: tickConvolution :: Fractional a => Tick -> Tick -> Waveform Tick (Waveform Tick a) -> Waveform Tick a -> Waveform Tick a
+ Boopadoop: tickTable :: Double -> Waveform Double a -> Waveform Tick a
+ Boopadoop: tickTableMemo :: Double -> Waveform Double a -> Waveform Tick a
+ Boopadoop: type Wavetable = Waveform Tick Discrete
+ Boopadoop: usingFFT :: Tick -> Wavetable -> Wavetable
+ Boopadoop: wackyNotConvolution :: (a -> b -> c) -> Waveform t (Waveform t a) -> Waveform t b -> Waveform t c
+ Boopadoop.Example: bandFilterIt :: DWave -> DWave
+ Boopadoop.Example: fWave :: Wavetable
+ Boopadoop.Example: filteredCont :: DWave
+ Boopadoop.Example: filteredDisc :: Waveform Double Discrete
+ Boopadoop.Example: filteredTicks :: Wavetable
+ Boopadoop.Example: outputGoal :: Waveform Double Discrete
+ Boopadoop.Example: outputTicks :: Wavetable
+ Boopadoop.Example: testFourier :: Wavetable
+ Boopadoop.Example: unfiltered :: Waveform Double Discrete
+ Boopadoop.Example: unfilteredCont :: Waveform Double Double
+ Boopadoop.Example: unfilteredTicks :: Wavetable
- Boopadoop: buildChord :: [Double] -> Double -> DWave
+ Boopadoop: buildChord :: (Num a, RealFrac a) => [a] -> a -> Waveform a a
- Boopadoop: mergeWaves :: Fractional a => [Waveform t a] -> Waveform t a
+ Boopadoop: mergeWaves :: Num a => [Waveform t a] -> Waveform t a
- Boopadoop: sinWave :: Double -> DWave
+ Boopadoop: sinWave :: Floating a => a -> Waveform a a
- Boopadoop: testWave :: DWave -> IO ()
+ Boopadoop: testWave :: String -> Wavetable -> IO ()
- Boopadoop: waveformToWAVE :: Double -> DWave -> WAVE
+ Boopadoop: waveformToWAVE :: Tick -> Int -> Wavetable -> WAVE
Files
- boopadoop.cabal +29/−5
- perfprof/Main.hs +6/−0
- src/Boopadoop.hs +217/−27
- src/Boopadoop/Discrete.hs +67/−0
- src/Boopadoop/Example.hs +39/−0
boopadoop.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 425f991fc503fc954825df752ab44d8049b166438f4928aab27991d5435aea18+-- hash: b398caf8159a6e48aaad2517ea385a910d1a2f67c86642e5c8d3d7d904c47136 name: boopadoop-version: 0.0.0.1+version: 0.0.0.2 synopsis: Mathematically sound sound synthesis description: Please see the README on GitHub at <https://github.com/Lazersmoke/boopadoop#readme> category: Music@@ -29,20 +29,41 @@ library exposed-modules: Boopadoop- Boopadoop.Diagram Boopadoop.Example- Boopadoop.Interval+ other-modules:+ Boopadoop.Diagram Boopadoop.Rhythm+ Boopadoop.Interval+ Boopadoop.Discrete+ hs-source-dirs:+ src+ build-depends:+ WAVE+ , base >=4.7 && <5+ , bytestring+ , containers+ , primes+ , semialign+ , split+ , vector+ default-language: Haskell2010++executable perfprof+ main-is: Main.hs other-modules: Paths_boopadoop hs-source-dirs:- src+ perfprof build-depends: WAVE , base >=4.7 && <5+ , boopadoop+ , bytestring+ , containers , primes , semialign , split+ , vector default-language: Haskell2010 test-suite boopadoop-test@@ -57,7 +78,10 @@ WAVE , base >=4.7 && <5 , boopadoop+ , bytestring+ , containers , primes , semialign , split+ , vector default-language: Haskell2010
+ perfprof/Main.hs view
@@ -0,0 +1,6 @@+module Main where + +import Boopadoop +import Boopadoop.Example + +main = testWave "filteredTicks" filteredTicks
src/Boopadoop.hs view
@@ -1,10 +1,13 @@ {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE BangPatterns #-} -- | A music theory library for just intonation and other mathematically pure ideas. module Boopadoop (module Boopadoop ,module Boopadoop.Diagram ,module Boopadoop.Rhythm ,module Boopadoop.Interval+ ,module Boopadoop.Discrete ) where import Data.WAVE as WAVE@@ -12,13 +15,26 @@ import Boopadoop.Diagram import Boopadoop.Rhythm import Boopadoop.Interval+import Boopadoop.Discrete import Data.List+import Data.Bits+import Data.Int+import Data.Complex+import Data.Foldable+import qualified Data.IntMap.Lazy as IntMap+import qualified Data.Vector.Unboxed as Vector+import qualified Data.ByteString.Lazy as BS+import qualified Data.ByteString.Builder as BSB+import Debug.Trace -- | A 'Waveform' is a function (of time) that we can later sample. newtype Waveform t a = Waveform {sample :: t -> a -- ^ 'sample' the 'Waveform' at a specified time } +instance Functor (Waveform t) where+ fmap f w = sampleFrom $ f . sample w+ -- | A 'Double' valued wave with time also in terms of 'Double'. -- This models a real-valued waveform which typically has values in @[-1,1]@ and -- is typically supported on either the entire real line ('sinWave') or on a compact subset ('compactWave')@@ -28,30 +44,59 @@ instance Show (Waveform Double Double) where show w = intercalate "\n" . transpose $ map sampleToString waveSamples where- sampleToString k = replicate (quantLevel - k) '.' ++ "x" ++ replicate (quantLevel + k) '.'+ sampleToString k = if k <= quantLevel && k >= -quantLevel+ then replicate (quantLevel - k) '.' ++ "x" ++ replicate (quantLevel + k) '.'+ else let m = "k = " ++ show k in m ++ replicate (quantLevel * 2 + 1 - length m) ' ' waveSamples = map (floor . (* realToFrac quantLevel) . sample w . (/sampleRate)) [0 .. 115] quantLevel = 15 :: Int- sampleRate = 16000+ sampleRate = 6400 +instance Show (Waveform Double Discrete) where+ show w = intercalate "\n" . transpose $ map sampleToString waveSamples+ where+ sampleToString k = if k <= quantLevel && k >= -quantLevel+ then replicate (quantLevel - k) '.' ++ "x" ++ replicate (quantLevel + k) '.'+ else let m = "k = " ++ show k in m ++ replicate (quantLevel * 2 + 1 - length m) ' '+ waveSamples = map ((`quotRoundUp` (1 + (discFactor `quot` quantLevel))) . fromIntegral . unDiscrete . sample w . (/sampleRate)) [0 .. 115]+ quantLevel = 15 :: Int+ sampleRate = 6400++instance Show (Waveform Tick Discrete) where+ show w = intercalate "\n" . transpose $ map sampleToString waveSamples+ where+ sampleToString k = if k <= quantLevel && k >= -quantLevel+ then replicate (quantLevel - k) '.' ++ "x" ++ replicate (quantLevel + k) '.'+ else let m = "k = " ++ show k in m ++ replicate (quantLevel * 2 + 1 - length m) ' '+ waveSamples = map ((`quotRoundUp` (1 + (discFactor `quot` quantLevel))) . fromIntegral . unDiscrete . sample (skipTicks 1 w)) [0 .. 115]+ quantLevel = 15 :: Int++-- | A version of @'quot'@ that rounds away from zero instead of towards it.+quotRoundUp :: Int -> Int -> Int+quotRoundUp a b = if a `mod` b == 0 then a `quot` b else (signum a * signum b) + (a `quot` b)+ -- | Build a 'Waveform' by sampling the given function. sampleFrom :: (t -> a) -> Waveform t a-sampleFrom = Waveform+sampleFrom f = Waveform $ \t -> t `seq` f t -- | Sample a 'Waveform' at specified time. @'sampleAt' = 'flip' 'sample'@ sampleAt :: t -> Waveform t a -> a sampleAt = flip sample -- | Pure sine wave of the given frequency-sinWave :: Double -> DWave-sinWave f = sampleFrom $ \t -> sin (2 * pi * f * t)+sinWave :: Floating a => a -> Waveform a a+sinWave f = sampleFrom $ \t -> let !freq = 2 * pi * f in sin (freq * t) --- | @'compactWave' (l,h)@ is a wave which is @1@ on @[l,h)@ and @0@ elsewhere+-- | Sine wave that is optimized to store only a small @'CompactWavetable'@. Frequency given in +fastSin :: Double -> Double -> Wavetable+fastSin f sampleRate = exploitPeriodicity (floor $ sampleRate / f) $ tickTable sampleRate $ discretize $ sinWave f++-- | @'compactWave' (l,h)@ is a wave which is @'True'@ on @[l,h)@ and @'False'@ elsewhere compactWave :: (Ord t,Num t) => (t,t) -> Waveform t Bool compactWave (low,high) = sampleFrom $ \t -> t >= low && t < high --- | Modulate the muting or non-muting of another wave with a @'Bool'@ value wave, such as @'compactWave'@.-modulateMuting :: Num a => Waveform t Bool -> Waveform t a -> Waveform t a-modulateMuting = modulate (\b s -> if b then s else 0)+-- | @'muting' 'True'@ is @'id'@ while @'muting' 'False'@ is @'const' 0@.+muting :: Num a => Bool -> a -> a+muting b s = if b then s else 0 -- | Modulate one wave with another according to the given function pointwise. -- This means you can't implement 'phaseModulate' using only this combinator because phase modulation@@ -89,33 +134,27 @@ -- | Play several waves on top of each other, normalizing so that e.g. playing three notes together doesn't triple the volume. balanceChord :: Fractional a => [Waveform t a] -> Waveform t a-balanceChord notes = sampleFrom $ \t -> sum . map ((/ fromIntegral chordSize) . sampleAt t) $ notes- where- chordSize = length notes+balanceChord notes = sampleFrom $ \t -> sum . map ((* (realToFrac . recip . fromIntegral . length $ notes)) . sampleAt t) $ notes -- | Play several waves on top of each other, without worrying about the volume. See 'balanceChord' for -- a normalized version.-mergeWaves :: Fractional a => [Waveform t a] -> Waveform t a+mergeWaves :: Num a => [Waveform t a] -> Waveform t a mergeWaves notes = sampleFrom $ \t -> sum (map (sampleAt t) notes) -- Average Frequency --,frequency = fmap (/(fromIntegral $ length notes)) . foldl (liftA2 (+)) (Just 0) . map frequency $ notes -- | @'waveformToWAVE' outputLength@ gives a @'WAVE'@ file object by sampling the given @'DWave'@ at @44100Hz@. -- May disbehave or clip based on behavior of @'doubleToSample'@ if the DWave takes values outside of @[-1,1]@.-waveformToWAVE :: Double -> DWave -> WAVE-waveformToWAVE outTime w = WAVE+waveformToWAVE :: Tick -> Int -> Wavetable -> WAVE+waveformToWAVE outTicks sampleRate w = WAVE {waveHeader = WAVEHeader {waveNumChannels = 1 ,waveFrameRate = sampleRate ,waveBitsPerSample = 32- ,waveFrames = Just $ numFrames+ ,waveFrames = Just $ fromIntegral outTicks }- ,waveSamples = [map (doubleToSample . sample w . (/sampleRate)) [0 .. fromIntegral (numFrames - 1)]]+ ,waveSamples = [map (unDiscrete . sample w) [0 .. outTicks - 1]] }- where- sampleRate :: Num a => a- sampleRate = 44100- numFrames = ceiling $ outTime * sampleRate -- | Triangle wave of the given frequency triWave :: (Ord a,RealFrac a) => a -> Waveform a a@@ -125,15 +164,19 @@ then 2 - (4 * r) else -4 + (4 * r) +-- | Arbitrarily chosen standard tick rate, used in @'testWave'@+stdtr :: Num a => a+stdtr = 32000+ -- | Output the first ten seconds of the given @'DWave'@ to the file @test.wav@ for testing. -- The volume is also attenuated by 50% to not blow out your eardrums. -- Also pretty prints the wave.-testWave :: DWave -> IO ()-testWave w = print w >> pure w >>= putWAVEFile "test.wav" . waveformToWAVE 10 . amplitudeModulate (sampleFrom $ const 0.5)+testWave :: String -> Wavetable -> IO ()+testWave fp w = print w >> pure w >>= putWAVEFile (fp ++ ".wav") . waveformToWAVE (2*stdtr) stdtr . amplitudeModulate (sampleFrom $ const 0.5) -- | Outputs a sound test of the given @'PitchFactorDiagram'@ as an interval above @'concertA'@ as a @'sinWave'@ to the file @diag.wav@ for testing. testDiagram :: PitchFactorDiagram -> IO ()-testDiagram = putWAVEFile "diag.wav" . waveformToWAVE 3 . buildTestTrack . realToFrac . diagramToRatio . normalizePFD+testDiagram = putWAVEFile "diag.wav" . waveformToWAVE (3*32000) 32000 . tickTable 32000 . fmap doubleToDiscrete . buildTestTrack . realToFrac . diagramToRatio . normalizePFD where buildTestTrack p = sequenceNotes [((0,1),sinWave concertA),((1,2),sinWave (concertA * p)),((2,3), buildChord [1,p] concertA)] @@ -141,17 +184,17 @@ sequenceToBeat :: Double -> Double -> Beat DWave -> DWave sequenceToBeat startAt totalLength (RoseBeat bs) = let dt = totalLength / genericLength bs in fst $ foldl (\(w,i) b -> (mergeWaves . (:[w]) . sequenceToBeat (i * dt) dt $ b,i+1)) (sampleFrom $ const 0,0) bs sequenceToBeat startAt totalLength Rest = sampleFrom $ const 0-sequenceToBeat startAt totalLength (Beat w) = modulateMuting (compactWave (startAt,startAt + totalLength)) $ timeShift startAt w+sequenceToBeat startAt totalLength (Beat w) = modulate muting (compactWave (startAt,startAt + totalLength)) $ timeShift startAt w -- | Sequences some waves to play on the given time intervals. sequenceNotes :: (Ord t,Fractional t,Fractional a) => [((t,t),Waveform t a)] -> Waveform t a-sequenceNotes = mergeWaves . map (\(t,w) -> modulateMuting (compactWave t) $ timeShift (fst t) w)+sequenceNotes = mergeWaves . map (\(t,w) -> modulate muting (compactWave t) $ timeShift (fst t) w) -- | Builds a chord out of the given ratios relative to the root pitch -- @ -- buildChord ratios root -- @-buildChord :: [Double] -> Double -> DWave+buildChord :: (Num a,RealFrac a) => [a] -> a -> Waveform a a buildChord relPitches root = balanceChord $ map (triWave . (root *)) relPitches -- | Builds a chord out of the given ratios relative to the root pitch, without normalizing the volume.@@ -197,6 +240,11 @@ timeShift :: Num t => t -> Waveform t a -> Waveform t a timeShift dt = sampleFrom . (. subtract dt) . sample +-- | Shift a wave in time such that the new zero is at the specified position+seekTo :: Num t => t -> Waveform t a -> Waveform t a+seekTo dt = sampleFrom . (. (+dt)) . sample++ -- | Play several waves in a row with eqqual time each, using @'sequenceNotes'@. equalTime :: Double -> [DWave] -> DWave equalTime dt = sequenceNotes . foldl go []@@ -211,3 +259,145 @@ -- | The empty wave that is always zero when sampled emptyWave :: Num a => Waveform t a emptyWave = sampleFrom $ const 0++-- | Convolve with explicit discrete filter kernel weights.+discreteConvolve :: (Num a, Num t) => Waveform t [(t,a)] -> Waveform t a -> Waveform t a+discreteConvolve profile w = sampleFrom $ \t -> sum . map (\(dt,amp) -> amp * sample w (t + dt)) $ sample profile t++-- | This operation is not convolution, but something kind of like it. Use for creative purposes? Should be fast!+-- @wackyNotConvolution modf profile w = sampleFrom $ \t -> sample (modulate modf (sample profile t) w) t@+wackyNotConvolution :: (a -> b -> c) -> Waveform t (Waveform t a) -> Waveform t b -> Waveform t c+wackyNotConvolution modf profile w = sampleFrom $ \t -> sample (modulate modf (sample profile t) w) t++-- | Perform a discrete convolution. The output waveform is @f(t) = \int_{t-tickRadius}^{t+tickRadius} (kernel(t))(x) * w(t+x) dx@+-- but is discretized such that @x@ is always a multiple of @skipRate@.+tickConvolution :: Fractional a + => Tick -- ^ @tickRadius@+ -> Tick -- ^ @skipRate@+ -> Waveform Tick (Waveform Tick a) -- ^ The kernel of the convolution at each @'Tick'@+ -> Waveform Tick a -- ^ w(t)+ -> Waveform Tick a+tickConvolution tickRadius skipRate profile w = sampleFrom $ \t -> let !kern = sample profile t in sum . map (\dt -> (*stepModifier) . (*sample w (t + dt)) . sample kern $ dt) $ sampleDeltas+ where+ sampleDeltas = map (*skipRate) [-stepsPerSide.. stepsPerSide]+ stepsPerSide = tickRadius `div` skipRate+ !stepModifier = realToFrac . recip . fromIntegral $ stepsPerSide++-- | Same as @'tickConvolution'@ but for arbitarily valued waveforms. Works on @'DWave'@ for example.+sampledConvolution :: (RealFrac t, Fractional a) + => t -- ^ @convolutionSampleRate@, controls sampling for @x@+ -> t -- ^ @convolutionRadius@, continuous analogue of @tickRadius@+ -> Waveform t (Waveform t a) -- ^ Kernel of convolution for each time+ -> Waveform t a -> Waveform t a+sampledConvolution convolutionSampleRate convolutionRadius profile w = sampleFrom $ \t -> sum . map (\dt -> (*(realToFrac . recip $ convolutionSampleRate * convolutionRadius)) . (* sample w (t + dt)) . sample (sample profile t) $ dt) $ sampleDeltas+ where+ sampleDeltas = map ((/convolutionSampleRate) . realToFrac) [-samplesPerSide .. samplesPerSide]+ samplesPerSide = floor (convolutionRadius * convolutionSampleRate)+ sampleCount = 2 * samplesPerSide + 1+++-- | Makes a filter which selects frequencies near @bandCenter@ with tuning parameter @bandSize@.+-- Try: @'optimizeFilter' 200 . 'tickTable' 'stdtr' $ 'bandpassFilter' 'concertA' 100@+bandpassFilter :: Fractional a + => Double -- ^ @bandCenter@+ -> Double -- ^ @bandSize@+ -> Waveform Double a+bandpassFilter bandCenter bandSize = sampleFrom $ \t -> if t == 0 then 1 else realToFrac $ (sin (bandFreq * t)) / (bandFreq * t) * (cos (centerFreq * t))+ where+ !bandFreq = 2 * pi * bandSize+ !centerFreq = 2 * pi * bandCenter++{-+sampledConvolve modf profile w = sampleFrom $ \p -> modf (sample (sample profile p) p) (sample w p)++takeSamples :: +takeSamples sampleRate w = map (sample w . (/sampleRate)) [0 .. 115]+ ,waveSamples = [map (doubleToSample . sample w . (/sampleRate)) [0 .. fromIntegral (numFrames - 1)]]+-}+++-- | Discretize the output of a @'Double'@ producing waveform+discretize :: Waveform t Double -> Waveform t Discrete+discretize = fmap (Discrete . properFloor . (*discFactor))++-- | Discretize the input to a @'Double'@ consuming waveform+tickTable :: Double -- ^ Sample rate. Each tick is @1/sampleRate@ seconds+ -> Waveform Double a -> Waveform Tick a+tickTable tickrate w = sampleFrom $ \t -> sample w (fromIntegral t/tickrate)++-- | Tries and fails to optimize a @'Waveform'@ through memoization but actually hangs and eats all your memory.+tickTableMemo :: Double -> Waveform Double a -> Waveform Tick a+tickTableMemo tickrate w = sampleFrom $ \t -> if t < 0 then sample w (fromIntegral t/tickrate) else tab IntMap.! (fromIntegral t)+ where+ tab = IntMap.fromAscList . map (\k -> (fromIntegral k, sample w (fromIntegral k/tickrate))) $ [0..]++-- | A domain- and codomain-discretized @'Waveform'@ suitable for writing to a WAVE file.+-- See @'waveformToWAVE'@.+type Wavetable = Waveform Tick Discrete++-- | A data structure for storing the results of a @'Wavetable'@ on some subset of its domain.+-- Used internally.+data CompactWavetable = CompactWavetable {getWavetable :: Vector.Vector Int32}++-- | Optimize a @'Wavetable'@ by storing its values in a particular range.+-- Uses @(tickEnd - tickStart + 1) * sizeOf (_ :: 'Discrete')@ bytes of memory to do this.+solidSlice :: Tick -> Tick -> Wavetable -> Wavetable+solidSlice tickStart tickEnd w = sampleFrom $ \t -> case getWavetable cwt Vector.!? (fromIntegral (t-tickStart)) of+ Just d -> Discrete d+ Nothing -> sample w t+ where+ cwt = CompactWavetable {getWavetable = Vector.generate (fromIntegral $ tickEnd - tickStart + 1) (unDiscrete . sample w . (+tickStart) . fromIntegral)}++-- | Optimize a filter by doing @'solidSlice'@ around @t=0@ since those values are sampled repeatedly in a filter+optimizeFilter :: Tick -> Wavetable -> Wavetable+optimizeFilter tickRadius = solidSlice (-tickRadius) tickRadius++-- | Take the Fourier Transform of a complex valued @'Tick'@ sampled waveform+fourierTransform :: Tick -> Double -> Waveform Tick (Complex Double) -> Waveform Double (Complex Double)+fourierTransform tickRadius fTickRate x = sampleFrom $ \f -> sum . map (\n -> sample x n / (fromIntegral tickRadius) * cis (2 * pi * f * (fromIntegral n / fTickRate))) $ [-tickRadius .. tickRadius]++-- | Take the Fourier Transform of a @'Wavetable'@+realDFT :: Tick -- ^ Radius of Fourier Transform window in @'Tick'@s. Try 200+ -> Double -- ^ Sampling rate to use for the Fourier transform. Try the sample sample rate as the @'Wavetable'@+ -> Wavetable -> Wavetable+realDFT tickRadius fTickRate x = discretize $ tickTable 1 $ fmap ((min 1) . magnitude) $ fourierTransform tickRadius fTickRate ((\x -> discreteToDouble x :+ 0) <$> solidSlice (-tickRadius) tickRadius x)++-- | Skip every @n@ ticks in the in the given @'Waveform'@.+-- @'sample' ('skipTicks' n w) k = 'sample' w (n*k)@+skipTicks :: Tick -- ^ @n@+ -> Waveform Tick a -> Waveform Tick a+skipTicks skipRate w = sampleFrom $ \t -> sample w (skipRate * t)++-- | Optimize a @'Wavetable'@ that we know to be periodic by storing it's values on one period.+-- Takes @period * sizeOf (_ :: 'Discrete')@ bytes of memory to do this.+exploitPeriodicity :: Tick -- ^ Period in @'Tick'@s of the @'Wavetable'@.+ -> Wavetable -> Wavetable+exploitPeriodicity period x = sampleFrom $ \t -> case getWavetable cwt Vector.!? (fromIntegral (t `mod` period)) of+ Just d -> Discrete d+ Nothing -> sample x t+ where+ cwt = CompactWavetable {getWavetable = Vector.generate (fromIntegral period) (unDiscrete . sample x . fromIntegral)}++-- | Attempts to do a fast fourier transform, but the units of the domain of the output are highly suspect.+-- May be unreliable, use with caution.+usingFFT :: Tick -> Wavetable -> Wavetable+usingFFT tickRadius w = sampleFrom $ \t -> if t < (fromIntegral $ length l)+ then(!! fromIntegral t) . fmap (doubleToDiscrete . magnitude) $ l+ else 0+ where+ l = fft (map ((\x -> discreteToDouble x :+ 0) . sample w) [-tickRadius .. tickRadius])++-- | Cooley-Tukey fft+fft :: [Complex Double] -> [Complex Double]+fft [] = []+fft [x] = [x]+fft xs = zipWith (+) ys ts ++ zipWith (-) ys ts+ where n = length xs+ ys = fft evens+ zs = fft odds + (evens, odds) = split xs+ split [] = ([], [])+ split [x] = ([x], [])+ split (x:y:xs) = (x:xt, y:yt) where (xt, yt) = split xs+ ts = zipWith (\z k -> exp' k n * z) zs [0..]+ exp' k n = cis $ -2 * pi * (fromIntegral k) / (fromIntegral n)
+ src/Boopadoop/Discrete.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-} +module Boopadoop.Discrete where + +import Data.Int +import Data.Bits + +-- | @'Discrete' x@ represents @x/'discFactor'@ as a floating point number in [-1,1]. +newtype Discrete = Discrete {unDiscrete :: Int32} deriving (Eq,Ord) + +instance Show Discrete where + show (Discrete x) = "Discrete {unDiscrete = " ++ show x ++ ", value = " ++ show (fromIntegral x / discFactor :: Double) ++ "}" + +-- | Breaks when the double is not in [-1,1] +doubleToDiscrete :: Double -> Discrete +doubleToDiscrete x = Discrete . properFloor $ x * discFactor + +-- | Convert @'Discrete'@ to the @'Double'@ it represents. +discreteToDouble :: Discrete -> Double +discreteToDouble (Discrete x) = fromIntegral x / discFactor + +-- | This is the conversion factor between the internal value of a @'Discrete'@ and the value it represents. +discFactor :: Num a => a +discFactor = fromIntegral $ (maxBound :: Int32) + +-- | Round toward zero +properFloor :: RealFrac a => a -> Int32 +properFloor x = if x >= 0 then floor x else ceiling x +--properFloor = floor + +instance Num Discrete where + (Discrete a) + (Discrete b) = Discrete $ let s = a + b in if signum a == signum b && signum a /= signum s then error ("Discrete overflow! " ++ show (Discrete a) ++ " + " ++ show (Discrete b) ++ " /= " ++ show (Discrete s)) else s + a - b = a + negate b + (*) = multiplyDiscrete --(Discrete a) * (Discrete b) = Discrete . properFloor $ ((fromIntegral a / discFactor :: Double) * (fromIntegral b :: Double)) + negate (Discrete a) = Discrete (negate a) + abs (Discrete a) = Discrete (abs a) + signum (Discrete a) = Discrete (signum a) + fromInteger i = if i `elem` [-1,0,1] + then Discrete $ discFactor * (fromInteger i :: Int32) + else error $ "(fromInteger " ++ show i ++ " :: Discrete)" + +-- | Perform fast @'Discrete'@ multiplication. +multiplyDiscrete :: Discrete -> Discrete -> Discrete +multiplyDiscrete (Discrete a) (Discrete b) = let m = Discrete . fromIntegral $ ((fromIntegral a :: Int64) * (fromIntegral b :: Int64)) `div` (discFactor + 1) in if signum m /= 0 && signum a * signum b /= signum (unDiscrete m) then error ("Discrete multiply overflow!! " ++ show (Discrete a) ++ " * " ++ show (Discrete b) ++ " /= " ++ show m) else m + +instance Fractional Discrete where + (Discrete a) / (Discrete b) = let d = Discrete . fromIntegral $ ((fromIntegral a :: Int64) * (discFactor + 1)) `div` fromIntegral b in if signum d /= 0 && signum a * signum b /= signum (unDiscrete d) then error ("Discrete division overflow!! " ++ show (Discrete a) ++ " / " ++ show (Discrete b) ++ " /= " ++ show d) else d + fromRational r = if r <= 1 && r >= -1 + then Discrete . properFloor $ discFactor * r + else error $ "(fromRational " ++ show r ++ " :: Discrete)" + +instance Bounded Discrete where + minBound = -1 + maxBound = 1 + +-- | Make a function of doubles a function of discretes +disguise :: (Double -> Double) -> Discrete -> Discrete +disguise f (Discrete x) = Discrete . properFloor $ f (fromIntegral x / discFactor :: Double) * discFactor + +-- | A discrete representation of time. See @'Boopadoop.tickTable'@ for the sampling rate. +newtype Tick = Tick {unTick :: Int32} deriving (Enum,Num,Ord,Eq,Real,Integral) + +instance Show Tick where + show (Tick a) = "Tick[" ++ show a ++ "]" + +--cisDiscrete :: Double -> Complex Discrete +--cisDiscrete t = let (a :+ b) = cis t in doubleToDiscrete a :+ doubleToDiscrete b +
src/Boopadoop/Example.hs view
@@ -1,6 +1,8 @@+{-# LANGUAGE BangPatterns #-} module Boopadoop.Example where import Boopadoop +import Data.Complex testProg :: DWave testProg = sequenceNotes @@ -43,4 +45,41 @@ downBeat :: Beat DWave downBeat = RoseBeat [Beat (sinWave concertA),Rest,Beat (sinWave $ intervalOf perfectFifth concertA)] + +filteredCont :: DWave +filteredCont = sampledConvolution 1600 0.02 (sampleFrom $ const $ bandpassFilter concertA 1) $ unfilteredCont + +filteredDisc :: Waveform Double Discrete +filteredDisc = sampledConvolution 1600 0.02 (sampleFrom $ const $ bandpassFilter concertA 1) $ unfiltered + +bandFilterIt :: DWave -> DWave +bandFilterIt = sampledConvolution 1600 0.05 (sampleFrom $ const $ bandpassFilter concertA 100) + +unfilteredCont :: Waveform Double Double +unfilteredCont = sampleFrom $ \t -> let !w = (sample (sinWave concertA) t + sample (sinWave (18 / 13 * concertA)) t)* 0.5 in w + +unfiltered :: Waveform Double Discrete +unfiltered = sampleFrom $ \t -> let !w = (sample (discretize $ sinWave concertA) t * 0.5 + sample (discretize $ sinWave (18 / 13 * concertA)) t * 0.5) in w + +filteredTicks :: Wavetable +filteredTicks = tickConvolution (160) 10 (sampleFrom $ const theFilter) $ unfilteredTicks + where + !theFilter = optimizeFilter (160) $ tickTable stdtr $ bandpassFilter concertA 100 + +unfilteredTicks :: Wavetable +unfilteredTicks = solidSlice (0-160) (2*stdtr + 160) $ modulate (+) (setVolume 0.5 $ fastSin concertA stdtr) (setVolume 0.5 $ fastSin (18 / 13 * concertA) stdtr) + +testFourier :: Wavetable +testFourier = realDFT 100 44000 $ fWave + +fWave :: Wavetable +fWave = solidSlice (-2000) (2000) . discretize . tickTable 44000 . sinWave $ concertA + +outputTicks :: Wavetable +outputTicks = tickTable stdtr $ balanceChord [discretize $ sinWave concertA,discretize $ sampleFrom $ const 0] + +outputGoal :: Waveform Double Discrete +outputGoal = balanceChord [discretize $ sinWave concertA,discretize $ sampleFrom $ const 0] + +