HOMMAGE

De Wikihaskell
Saltar a: navegación, buscar
Sonido
Biblioteca de sonido y audio para la generación de música
Lenguaje Haskell
Biblioteca HOMMAGE
Autores Antonio Martín
Jose Miguel Perez
Daniel Salazar

HOMMAGE (Haskell Offline Music Manipulation And Generation EDSL) es el sucesor de la biblioteca de Sonido Haskell y ha sido desarrollada durante los años 2005 y 2006. Esta biblioteca puede ser ejecutada desde la versión actual de GHC.

La utilidad de la biblioteca es combinar los dos niveles de producción de la música en un solo sistema, lo que permite definir las estructuras musicales así como algunos instrumentos.

Por lo menos tres tipos de tareas se pueden hacer con esta biblioteca:

  • Se puede utilizar para definir las funciones de procesamiento de señales, tales como los efectos, sintetizadores y otros. Los datos producidos por las funciones que se pueden escribir en archivos WAV.
  • Es posible definir las pistas de música o canciones (como en Haskore) que se pueden escribir en archivos MIDI.
  • Una canción, representada por notación musical, también pueden renderizada por sintetizadores, por lo que directamente puede ser escrito en un archivo WAV.

Contenido

Módulos

A continuación vamos a analizar algunos de los módulos que contiene la librería HOMMAGE.

DFTFilter

Este módulo importa funciones de C para la transformada de Fourier y las encapsula en funciones de listas.

Con las funciones de este módulo podemos filtrar datos de audio y construir filtros de audio.

Las funciones y tipos que contiene este módulo son los siguientes:

 dftanalyse :: [Double] -> [Complex Double]

Realiza la transformada de Fourier (rápida)

 dftsynthese :: [Complex Double] -> [Double] 

Realiza la transformada de Fourier inversa (rápida)

 dftfilter :: [Double] -> [Double] -> [Double] 

Transformada de Fourier y transformada Inversa con un argumento adicional con sublistas de factores que hacen de pesos de los coeficientes.

 dftfilterBy :: IO (CoeffMap ()) -> [Double] -> [Double] 

Transformada de Fourier y transformada Inversa con un buffer para filtrado.

 dftfilterBy' :: s -> IO (s -> CoeffMap s) -> [Double] -> [Double] 

Transformada de Fourier y transformada Inversa con un buffer para filtrado.

 type CoeffArr = StorableArray Int Double 

Representa un espectro de frecuencias. El rango es de 0 a 1023.

 readCoeffArr :: CoeffArr -> Int -> IO (Complex Double) 

Lee un valor complejo de un CoeffArr

 writeCoeffArr :: CoeffArr -> Int -> Complex Double -> IO () 

Escribe en la salida un valor complejo de un CoeffArr

 type CoeffMap a = CoeffArr -> CoeffArr -> IO a 

Una acción que pasa los coeficientes del primer array al segundo, modificando los datos mediante filtros.

 mkCoeffMap :: [Double] -> IO (CoeffMap ()) 

Crea un CoeffMap

 coeffmap :: StorableArray Int Double -> CoeffMap () 

Toma un array con 512 valores y los usa para añadir pesos a los coeficientes de Fourier.

 mkAnalyse :: IO (Maybe Double -> IO (Maybe (Complex Double))) 

Construye una acción que pasa de datos de onda a datos de tipo coeficiente.

 mkSynthese :: IO (Maybe (Complex Double) -> IO (Maybe Double)) 

Construye una acción que pasa de datos de tipo coeficiente a datos de onda.

 mkFilterBuffered :: IO (CoeffMap ()) -> IO (Maybe Double -> IO (Maybe Double)) 

Construye una acción que transforma datos de onda via FFT, filtrados por el CoeffMap dado.

Midi

 data MidiFile  
 = MidiSync Ticks [MidiTrack] 
 | MidiSingle Ticks MidiTrack 
 | MidiAsync Ticks [MidiTrack] 



 writeMidiFile :: FilePath -> MidiFile -> IO () 

Escribe un MidiFile en un fichero del sistema.


 data MidiNote  
 = MidiNote Chan MidiValue MidiValue 
 | MidiCtrl Chan MidiValue MidiValue 



 type Chan = Word8 

Canal Midi (0-15)

 type MidiValue = Word8 

Valor Midi (0-127)

 type MidiMusic = [(Delta, Maybe MidiEvent)] 

Una clase de MidiTrack extendido.

 noteMidiMusic :: Int -> MidiNote -> MidiMusic 

Crea una nota o controlador de eventos con la longitud dada.

 restMidiMusic :: Int -> MidiMusic 

Crea una pausa con la longitud dada.

 appendMidiMusic :: MidiMusic -> MidiMusic -> MidiMusic 

Crea una composición secuencial de MidiMusic

 mergeMidiMusic :: MidiMusic -> MidiMusic -> MidiMusic 

Crea una composición paralela de MidiMusic.

 runMidiMusic :: MidiMusic -> MidiTrack 

Convierte un MidiMusic en un MidiTrack

FFT

Este módulo implementa la trasformada de Fourier rápida (fft) puramente en Haskell

 fft :: Int -> [Double] -> Complex Double 

Transformada de Fourier rápida.

 fft' :: Int -> [Complex Double] -> [Double] 

Transformada de Fourier rápida inversa

 fftc :: Int -> [Complex Double] -> Complex Double 

Transformada de Fourier rápida con parámetros complejos.

 fftc' :: Int -> [Complex Double] -> Complex Double 

Transformada de Fourier rápida inversa con parámetros complejos.

 i :: Complex Double 

El valor complejo i

 w :: Int -> Complex Double 

La enésima raiz de 1

 ws :: Bool -> Int -> [Complex Double] 

La 2-enésima raiz con exponentes 0, 1, ..., n. False indica inversa, es decir, con exponentes negativos.

 overlap_curve :: Int -> [Double] 

Crea un apagado (fade) de longitud N.

 mix_overlap :: Int -> Double -> [Double]

Solapa una secuencia de partes de longitud N.

Instalación en Linux

Pues bien, si seguimos la instalación en Linux de Biblioteca de empaquetamiento Cabal, podemos encontrarnos con una serie de problemas al instalar esta librería.

El problema viene derivado de que el paquete ghc6, no contiene una dependencia necesaria que es base-4.0.0.0 o superior.

Si usamos cabal install hommage recibiríamos un error que nos dice que tenemos que tener insalado el paquete anterior.

Para solucionar esto, lo que hay que hacer es instalar todas las librerias de ghc6 que hay en los repositorios con el siguiente comando, ya que alguna de esta contiene la dependencia que necesitamos.Si intentamos instalar el paquete con cabal install base-4.0.0.0 nos dá otro error.

  sudo apt-get install libghc6-*


A continuación, nos instalará multitud de bibliotecas. Cuando finalice, ejecutamos el comando cabal install hommage y vemos como se instala correctamente.

  Resolving dependencies...
  Downloading hommage-0.0.5...
  Configuring hommage-0.0.5...
  Preprocessing library hommage-0.0.5...
  Building hommage-0.0.5...
  [ 1 of 18] Compiling Sound.Hommage.Parameter ( Sound/Hommage/Parameter.hs, dist/build/Sound/Hommage/Parameter.o )
  [ 2 of 18] Compiling Sound.Hommage.Filter ( Sound/Hommage/Filter.hs, dist/build/Sound/Hommage/Filter.o )

Ya podemos disfrutar de la biblioteca Hommage en Linux.

Instalación en Windows

Para realizar la instalación vamos a utilizar la Biblioteca de empaquetamiento Cabal.

Desde la línea de comandos es preciso realizar una actualización de los paquetes de los que disponemos con el comando cabal update. Tras esperar un corto periodo de tiempo volveremos a tener disponible el cursor.

El siguiente paso es la instalación propiamente dicha, para ello deberemos utilizar el comando:

cabal install hommage

Que nos devolverá el siguiente resultado:

Resolving dependencies...

Downloading hommage-0.0.5...

Configuring hommage-0.0.5...

Preprocessing library hommage-0.0.5...

Building hommage-0.0.5...

[ 1 of 18] Compiling Sound.Hommage.Parameter ( Sound\Hommage\Parameter.hs, dist\
build\Sound\Hommage\Parameter.o )

[ 2 of 18] Compiling Sound.Hommage.Filter ( Sound\Hommage\Filter.hs, dist\build\
Sound\Hommage\Filter.o )

[ 3 of 18] Compiling Sound.Hommage.Envelope ( Sound\Hommage\Envelope.hs, dist\bu
ild\Sound\Hommage\Envelope.o )

[ 4 of 18] Compiling Sound.Hommage.WavFile ( Sound\Hommage\WavFile.hs, dist\buil
d\Sound\Hommage\WavFile.o )

[ 5 of 18] Compiling Sound.Hommage.Midi ( Sound\Hommage\Midi.hs, dist\build\Soun
d\Hommage\Midi.o )

[ 6 of 18] Compiling Sound.Hommage.Notation ( Sound\Hommage\Notation.hs, dist\bu
ild\Sound\Hommage\Notation.o )

[ 7 of 18] Compiling Sound.Hommage.Misc ( Sound\Hommage\Misc.hs, dist\build\Soun
d\Hommage\Misc.o )

[ 8 of 18] Compiling Sound.Hommage.DFTFilter ( Sound\Hommage\DFTFilter.hs, dist\
build\Sound\Hommage\DFTFilter.o )

[ 9 of 18] Compiling Sound.Hommage.Signal ( Sound\Hommage\Signal.hs, dist\build\
Sound\Hommage\Signal.o )

[10 of 18] Compiling Sound.Hommage.Sample ( Sound\Hommage\Sample.hs, dist\build\
Sound\Hommage\Sample.o )

[11 of 18] Compiling Sound.Hommage.Seq ( Sound\Hommage\Seq.hs, dist\build\Sound\
Hommage\Seq.o )

[12 of 18] Compiling Sound.Hommage.Osc ( Sound\Hommage\Osc.hs, dist\build\Sound\
Hommage\Osc.o )

[13 of 18] Compiling Sound.Hommage.Play ( Sound\Hommage\Play.hs, dist\build\Soun
d\Hommage\Play.o )

[14 of 18] Compiling Sound.Hommage.FFT ( Sound\Hommage\FFT.hs, dist\build\Sound\
Hommage\FFT.o )

[15 of 18] Compiling Sound.Hommage.Sound ( Sound\Hommage\Sound.hs, dist\build\So
und\Hommage\Sound.o )

[16 of 18] Compiling Sound.Hommage.Rand ( Sound\Hommage\Rand.hs, dist\build\Soun
d\Hommage\Rand.o )

[17 of 18] Compiling Sound.Hommage.Tools ( Sound\Hommage\Tools.hs, dist\build\So
und\Hommage\Tools.o )

[18 of 18] Compiling Sound.Hommage    ( Sound\Hommage.hs, dist\build\Sound\Homma
ge.o )

C:\ghc\ghc-6.10.1\bin\ar.exe: creating dist\build\libHShommage-0.0.5.a

Installing library in C:\Archivos de programa\Haskell\hommage-0.0.5\ghc-6.10.1

Registering hommage-0.0.5...

Reading package info from "dist\\installed-pkg-config" ... done.

Writing new package config file... done.

Tras estos pasos ya podemos empezar a componer música.

Ejemplos

Una vez que hemos instalado la biblioteca Hommage correctamente en nuestro PC, mostraremos unos ejemplos para que entendáis un poco el funcionamiento de esta.

Cuando creamos un fichero .wav, debemos distinguir entre Mono o Estéreo. Esta propiedad, solo puede detectarse en un fichero WAV dinámicamente. Una función que vaya a trabajar con ficheros WAV, puede recibir o bien un archivo en mono o en estéreo. Por este motivo, se ha creado el tipo de dato Signal y Estéreo. El tipo Signal es una lista de valores Mono, o bien una lista de valores Estéreo. También se ha creado un sinónimo de tipo Mono para que se entienda mejor.

 type Mono = Double
 
 data Stereo = Double :><: Double
 
 writeWavMono   :: FilePath -> [Mono] -> IO ()
 writeWavStereo :: FilePath -> [Stereo] -> IO ()
 
 data Signal = Mono [Mono]
             | Stereo [Stereo]
 
 readWavSignal :: FilePath -> IO Signal
 openWavSignal :: FilePath -> Signal
 

Ejemplo 1

 sound1 :: [Mono]
 sound1 = take 44100 $ map sin $ iterate (+0.03) 0.0

 main1 = writeWavMono "ejemplo.wav" $ map (*0.9) sound1

Este ejemplo, nos crea un archivo ejemplo1.wav , que contiene un leve sonido, con una duración de 1 segundo a 44.1kHz. La amplitud que tiene el archivo debería de estar comprendida entre -1 y 1, si no puede producirse algún ruido no deseado. Para asegurarnos que no hay un desbordamiento de buffer, se ha multiplicado la amplitud por 0.9.


Ejemplo 2

Ahora mostraremos un ejemplo donde crearemos un sintetizador. Se ha creado una función llamada 'sintetizador' que recibe dos parámetros, el tono de la señal (o número de nota) y la señal de inicio. El oscilador genera una onda la cuál, su frecuencia depende de la señal. Esta onda es filtrada y amplificada. El límite de la frecuencia del filtro, es modulada por un LFO.

sintetizador :: Double -> Int -> [Mono]
sintetizador notenr len = zipWith (*) env $ dftfilter (lowpass (repeat 0.5) lfo) osc
 where
  lfo = map ((+0.4) . (*0.25)) $ cosinus $ repeat 0.09
  osc = saw $ repeat $ noteToFrequency 12.0 notenr
  env = playADSR FitS Linear (500,1500,0.4,2500) len 
main2 = writeWavMono "ejemplo2.wav" $ map (*0.9) $ sintetizador 12.0 11025

Ejemplo 3

A continuación vamos a mostrar un ejemplo completo que partiendo de 5 archivos de sonido (bassdrum.wav, crash.wav, hihat.wav, in.wav, snare.wav) vamos a obtener uno en el que todos se integran.

Importamos las bibliotecas necesarias:

import Sound.Hommage
import Data.List
import Data.Ratio

Leemos el archivo in.wav y lo almacenamos en formato mono (out1.wav)

main1 = writeWavMono "out1.wav"
      $ signalToMono
      $ openWavSignal "in.wav"

A continuación generamos un nuevo archivo, también mono (out2.wav)

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))


Las siguientes funciones son un poco más complejas para determinar el orden:

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"


Ahora, en lugar de clasificar los segmentos de la señal de audio, una función se aplica a cada segmento. En main5, en cada segmento de cada valor se duplica.

main5 = writeWavMono "out5.wav"
      $ concat
      $ map (\x -> x >>= \x -> [x,x])
      $ splitWaves
      $ signalToMono
      $ openWavSignal "in.wav"

Aqui los segmentos son duplicados:

main6 = writeWavMono "out6.wav"
      $ concat
      $ map (\x -> x ++ x)
      $ splitWaves
      $ signalToMono
      $ openWavSignal "in.wav"

Se ordenan los valores de cada segmento:

main7 = writeWavMono "out7.wav"
      $ concat
      $ map sort
      $ splitWaves
      $ signalToMono
      $ openWavSignal "in.wav"

En el siguiente invertimos cada segmento:

main8 = writeWavMono "out8.wav"

     $ concat
     $ map reverse
     $ splitWaves
     $ signalToMono
     $ openWavSignal "in.wav"


En las siguientes tres funciones se va a producir una especie de estiramiento de la señal:

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"

La función osc toma dos listas como argumentos, una señal para reproducir y una señal que respresenta el tono del oscilador. En main12 la lista de segmentos wav es mapeado a una secuencia de sonidos que usan el oscilador. Los sonidos tienen una duración de 1/16 y la secuencia se reproduce con 110 latidos por minuto.

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"

Cambiamos la velocidad y el tono:

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"

Cómo con cada función se van generando nuevos archivos se puede ir comprobando el funcionamiento de cada una de estas funciones:

Ahora, el tono de cada sonido se ajusta individualmente, de modo que todos ellos tienen la misma frecuencia.

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"


Normalizamos el sonido para que todos tengan la misma frecuencia:

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


A continuación, aplicamos un filtro a los sonidos:

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"

Una señal auxiliar se usa para controlar el valor de corte:

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"

Añadimos algunos golpes:

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"
         ]


Las siguientes dos canciones usan las ejemplos creados anteriormente:

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"
         ]

Y la canción final:

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

Por último podemos crear todos los ejemplos y las canciones a la vez:

main = do
 main1
 main2
 main3
 main4
 main5
 main6
 main7
 main8
 main9
 main10
 main11
 main12
 main13
 main14
 main15
 main16
 main17
 main18
 main19
 main20

Bibliografía

Herramientas personales