Hommage Song 120 steps to a first simple song. The result can be found below. This is a documentation of playing around with the Hommage library. The focus is on short code that shows what can be done within a few lines. Note: The filepaths are links to the audio files. The input file is always the same file, it's Philip Wadler saying "Haskell Programming Language" (taken from the talk "Faith, Evolution, and Programming Languages"). Let's start with a small task: Reading a wav-file and saving it in mono-format. import Sound.Hommage import Data.List import Data.Ratio main1 = writeWavMono "out1.wav" $ signalToMono $ openWavSignal "in.wav" Since the audio data is represented as a list of Doubles, any list-based function can work on it. In main2, splitWaves splits the audio signal into segments (or "parts"), where each segment starts when the signal switches from a negative number to a non-negative number. sortWaves sorts these segments by value that is calculated from the segment by a given function. In this case, this function is length. main2 = writeWavMono "out2.wav" $ concat $ sortWaves length $ splitWaves $ signalToMono $ openWavSignal "in.wav" sortWaves :: Ord a => ([Mono] -> a) -> [[Mono]] -> [[Mono]] sortWaves f = map fst . sortBy (\a b -> compare (snd a) (snd b)) . map (\x -> (x, f x)) The next two main-functions use a more complex function to determine the ordering. They don't make much sense, but they demontrate the use of sortWaves. main3 = writeWavMono "out3.wav" $ concat $ sortWaves (\x -> sum (map abs x) / fromIntegral (length x + 1)) $ splitWaves $ signalToMono $ openWavSignal "in.wav" main4 = writeWavMono "out4.wav" $ concat $ sortWaves (\x -> negate $ sum (map abs x) / fromIntegral (length x + 1)) $ splitWaves $ signalToMono $ openWavSignal "in.wav" Now, instead of sorting the segments of the audio signal, a function is applied to each segment. In main5, in each segment each value is duplicated. (It does not really make sense to do it this way, since a splitting of the wav-signal is not necessary for it.) main5 = writeWavMono "out5.wav" $ concat $ map (\x -> x >>= \x -> [x,x]) $ splitWaves $ signalToMono $ openWavSignal "in.wav" Here the segments itself are duplicated: main6 = writeWavMono "out6.wav" $ concat $ map (\x -> x ++ x) $ splitWaves $ signalToMono $ openWavSignal "in.wav" Sorting the values in each segment: main7 = writeWavMono "out7.wav" $ concat $ map sort $ splitWaves $ signalToMono $ openWavSignal "in.wav" Reversing each segment: main8 = writeWavMono "out8.wav" $ concat $ map reverse $ splitWaves $ signalToMono $ openWavSignal "in.wav" The next three definitions perform a kind of stretching of the signal. main9 = writeWavMono "out9.wav" $ concat $ let { loop [] = [] ; loop xs = (take 10 xs) ++ loop (tail xs) } in loop $ splitWaves $ signalToMono $ openWavSignal "in.wav" main10 = writeWavMono "out10.wav" $ concat $ let { loop [] = [] ; loop xs = reverse (take 10 xs) ++ loop (tail xs) } in loop $ splitWaves $ signalToMono $ openWavSignal "in.wav" main11 = writeWavMono "out11.wav" $ concat $ let { loop [] = [] ; loop xs = sort (take 10 xs) ++ loop (tail xs) } in loop $ splitWaves $ signalToMono $ openWavSignal "in.wav" The function osc takes two lists as argument: A signal to play and a signal that represents the pitch of the oscillator. In main12, the list of wav-segments is mapped to a sequence of sounds that use the segment for the oscillator. The sounds have a duration of 1/16 and the sequence is played with 110 beats per minute (based on 4/4 counting). The pitch of the sounds is not changed, it's 1. main12 = writeWavMono "out12.wav" $ runSong 110 $ return $ notationMono $ stretch (1%16) $ line $ map (\x -> note ( osc (cycle x) (repeat 1) ==> Amplifier (Envelope FitADR CosLike (1,3,0.5,2)))) $ splitWaves $ signalToMono $ openWavSignal "in.wav" Faster, and a pitch of 0.25: main13 = writeWavMono "out13.wav" $ runSong 140 $ return $ notationMono $ stretch (1%16) $ line $ map (\x -> note ( osc (cycle x) (repeat 0.25) ==> Amplifier (Envelope FitADR CosLike (1,3,0.5,2)))) $ splitWaves $ signalToMono $ openWavSignal "in.wav" Now, the pitch of each sound is adjusted individually, so all of them have the same frequency: main14 = writeWavMono "out14.wav" $ runSong 120 $ return $ notationMono $ stretch (1%16) $ line $ map (\(x, len) -> note ( osc (cycle x) (repeat (adjustFrequency (fromIntegral len) 120 1)) ==> Amplifier (Envelope FitADR CosLike (1,3,0.5,2)) )) $ filter (\(x, len) -> len > 0) $ map (\x -> (x, length x)) $ splitWaves $ signalToMono $ openWavSignal "in.wav" Normalizing the sounds, so the all have the same volume: main15 = writeWavMono "out15.wav" $ runSong 120 $ return $ notationMono $ stretch (1%16) $ line $ map (\(x, len) -> note ( osc (cycle x) (repeat (adjustFrequency (fromIntegral len) 120 1)) ==> Amplifier (Envelope FitADR CosLike (1,3,0.5,2)) )) $ filter (\(x, len) -> len > 0) $ map (\x -> (normalize x, length x)) $ splitWaves $ signalToMono $ openWavSignal "in.wav" normalize xs = map (/v) xs where v = maximum $ map abs xs The sounds get filtered by a lowpass filter: main16 = writeWavMono "out16.wav" $ runSong 120 $ return $ notationMono $ stretch (1%16) $ line $ map (\(x, len) -> note ( osc (cycle x) (repeat (adjustFrequency (fromIntegral len) 120 1)) ==> Filter (Lowpass (0.6::Double) (0.35::Double) ) ==> Amplifier (Envelope FitADR CosLike (1,3,0.5,2)) )) $ filter (\(x, len) -> len > 0) $ map (\x -> (normalize x, length x)) $ splitWaves $ signalToMono $ openWavSignal "in.wav" An auxilary signal is used to control the cutoff value of the filter: main17 = writeWavMono "out17.wav" $ runSong 120 $ do cutoff <- track $ play $ map ((+0.2).(*0.28).(+1)) $ sinus $ repeat 0.003 return $ notationMono $ stretch (1%16) $ line $ map (\(x, len) -> note ( osc (cycle x) (repeat (adjustFrequency (fromIntegral len) 120 1)) ==> Filter (Lowpass (0.8::Double) cutoff) ==> Amplifier (Envelope FitADR CosLike (1,3,0.5,2)) )) $ filter (\(x, len) -> len > 0) $ map (\x -> (normalize x, length x)) $ splitWaves $ signalToMono $ openWavSignal "in.wav" Adding some beats: main18 = writeWavMono "out18.wav" $ map (*0.8) $ runSong 120 $ do cutoff <- track $ play $ map ((+0.2).(*0.28).(+1)) $ sinus $ repeat 0.003 return $ notationMono $ chord [ line $ replicate 64 $ Note (1%4) (PlayWav "bassdrum.wav" ==> Amplifier (0.7::Double)) , stretch (1%16) $ line $ take 256 $ map (\(x, len) -> note ( osc (cycle x) (repeat (adjustFrequency (fromIntegral len) 120 1)) ==> Filter (Lowpass (0.8::Double) cutoff) ==> Amplifier (Envelope FitADR CosLike (1,3,0.5,2)) )) $ filter (\(x, len) -> len > 0) $ map (\x -> (normalize x, length x)) $ splitWaves $ signalToMono $ openWavSignal "in.wav" ] The following two songs use some of the samples created earlier: main19 = writeWavMono "out19.wav" $ map (*0.65) $ runSong 120 $ do cutoff <- track $ play $ map ((+0.2).(*0.28).(+1)) $ sinus $ repeat 0.003 sample1 <- track $ play $ cycle $ signalToMono $ openWavSignal "out6.wav" return $ notationMono $ chord [ line $ replicate 128 $ Note (1%4) ( PlayWav "bassdrum.wav" ==> Amplifier (0.7::Double)) , line $ replicate 256 (Note (1%16) ( sample1 ==> Amplifier ( Envelope FitADR Linear (1,2,0.5,1) ==> Amplifier (0.8::Double))) :+: Rest (1%16)) , stretch (1%16) $ line $ take 512 $ map (\(x, len) -> note ( osc (cycle x) (repeat (adjustFrequency (fromIntegral len) 120 1)) ==> Filter (Lowpass (0.8::Double) cutoff) ==> Amplifier (Envelope FitADR CosLike (1,3,0.5,2)) )) $ filter (\(x, len) -> len > 0) $ map (\x -> (normalize x, length x)) $ splitWaves $ signalToMono $ openWavSignal "in.wav" ] Ok, this is the final song: main20 = writeWavMono "out20.wav" $ map (*0.6) $ runSong 120 $ do cutoff <- track $ play $ map ((+0.2).(*0.28).(+1)) $ sinus $ repeat 0.001 sample1 <- track $ play $ cycle $ signalToMono $ openWavSignal "out6.wav" sample2 <- track $ play $ cycle $ signalToMono $ openWavSignal "out11.wav" volume <- track $ notationMono $ line [ Rest 4 , Note 12 $ play $ Interpolate CosLike (0,1::Double) , Note 48 $ play (1::Double)] return $ notationMono $ chord [ line $ map beats ([1,0,1,2,3,3,2,1] >>= replicate 8) , line $ replicate 512 (Note (1%16) ( sample1 ==> Amplifier (Envelope FitADR Linear (1,2,0.5,1) ==> Amplifier (volume <*> (0.8::Double)))) :+: Rest (1%16)) , line $ map samples [0,0,0,3,1,0,2,3,4,3,1,0,2,0,1,3,1] , fmap (sample2player sample2) $ line (Rest 32 : (replicate 128 $ line [ Rest (1%8) , Note (1%8) (0.5::Double) ])) , stretch (1%16) $ line $ take 1024 $ map (\(x, len) -> note ( osc (cycle x) (repeat (adjustFrequency (fromIntegral len) 120 1)) ==> Filter (Lowpass (0.6::Double) cutoff) ==> Amplifier (Envelope FitADR CosLike (1,3,0.5,2)) )) $ filter (\(x, len) -> len > 0) $ map (\x -> (normalize x, length x)) $ splitWaves $ signalToMono $ openWavSignal "in.wav" ] beats 0 = rest beats 1 = line' $ replicate 4 $ note ( PlayWav "bassdrum.wav" ==> Amplifier (0.7::Double)) beats 2 = chord [ beats 1 , line' $ replicate 4 $ line' [rest, note ( PlayWav "hihat.wav" ==> Amplifier (0.4::Double))] ] beats 3 = chord [ beats 2 , line' $ replicate 2 $ line' [rest, note ( PlayWav "snare.wav" ==> Amplifier (0.36::Double))] ] samples 0 = stretch 4 rest samples 1 = stretch 4 $ note $ play $ PlayWav "out4.wav" samples 2 = stretch 4 $ note (PlayWav "out2.wav" ==> Amplifier (0.8::Double)) samples 3 = line [Rest 3, Rest (1%3), Note (2%3) $ play $ PlayWav "out3.wav"] samples 4 = Note 4 $ play $ PlayWav "out5.wav" sample2player sample vol = sample ==> Amplifier (Envelope FitADR Linear (1,2,0.5,1)) ==> Amplifier vol Create all Samples and Songs at once: main = do main1 main2 main3 main4 main5 main6 main7 main8 main9 main10 main11 main12 main13 main14 main15 main16 main17 main18 main19 main20
Last update: 2009-06-30 |