klank.dev - audio sandbox in the browser
Tags: functional, javascript, webdev
Published: 2020-10-04 14:49:20

---
title: klank.dev - audio sandbox in the browser
published: true
description: A new way to make music, sound, and/or noise using FRP and PureScript
tags: [functional,javascript,webdev]
cover_image: https://physicsworld.com/wp-content/uploads/2018/09/PWSep18Ball-noise_HERO-1024x576.jpg
---

tl;dr https://klank.dev && https://discourse.klank.dev

I just launched [klank.dev](https://klank.dev)!  It's a browser-based audio sandbox that uses [PureScript](https://www.purescript.org/) as its input language.

Below is a small tutorial using my favorite coding technique: copy and paste. For each example below:

- Copy and paste the entire snippet into the [klank.dev](https://klank.dev) editor after deleting whatever gunk was there before.
- Press `k` then `ENTER` to compile.
- Press `p` then `ENTER` to play.
- Press `s` then `ENTER` to stop.

> Use Firefox! 🦊 + :headphones: = :muscle:

## Hello klank

A simple sine wave.

[Try me on klank.dev.](https://klank.dev/?k&url=https://klank-share.s3.amazonaws.com/K16089024678018947.purs)

```haskell
module Klank.Dev where

import Prelude
import Data.Typelevel.Num (D1)
import FRP.Behavior (Behavior)
import FRP.Behavior.Audio (AudioUnit, gain', runInBrowser, sinOsc, speaker')
import Type.Klank.Dev (Klank, klank)

scene :: Number -> Behavior (AudioUnit D1)
scene = const $ pure (speaker' (gain' 0.2 $ sinOsc 220.0))

main :: Klank
main = klank { run = runInBrowser scene }
```

## Two's company

Double the sine waves, double the fun.

[Try me on klank.dev.](https://klank.dev/?k&url=https://klank-share.s3.eu-west-1.amazonaws.com/K16089025361408726.purs)
```haskell
module Klank.Dev where

import Prelude
import Data.Typelevel.Num (D1)
import Data.List ((:), List(..))
import Data.NonEmpty ((:|))
import FRP.Behavior (Behavior)
import FRP.Behavior.Audio (AudioUnit, gain', runInBrowser, sinOsc, speaker)
import Type.Klank.Dev (Klank, klank)

scene :: Number -> Behavior (AudioUnit D1)
scene = const $ pure (speaker 
  ((gain' 0.2 $ sinOsc 220.0) 
  :| (gain' 0.1 $ sinOsc 330.0)
  : Nil))

main :: Klank
main = klank { run = runInBrowser scene }
```

## Sampler
Atari speech :robot:

[Try me on klank.dev](https://klank.dev/?k&url=https://klank-share.s3.eu-west-1.amazonaws.com/K16089025802557395.purs)
```haskell
module Klank.Dev where

import Prelude
import Effect.Class(liftEffect)
import Effect.Aff(Aff, launchAff_)
import Control.Promise(toAffE)
import Data.List ((:), List(..))
import Data.NonEmpty ((:|))
import Effect.Class(liftEffect)
import Foreign.Object as O
import Data.Traversable(sequence)
import Data.Typelevel.Num (D1)
import FRP.Behavior (Behavior)
import Data.Vec((+>), empty)
import Type.Klank.Dev(Klank, klank, affable)
import FRP.Behavior.Audio (AudioUnit,
 decodeAudioDataFromUri,
 BrowserAudioTrack, gain', gain, makeAudioTrack, play, loopBuf, 
 runInBrowser, makePeriodicWave, sinOsc, speaker, BrowserAudioBuffer, CanvasInfo)

import Math (pi, sin)

vol = 1.4

scene :: Number -> Behavior (AudioUnit D1)
scene time = let
      rad = pi * time
    in
      pure $ speaker  ((gain' (0.3 * vol) (loopBuf "atar" (1.0 + 0.1 * sin rad) 0.0 0.0) )
     :| (gain' (0.15 * vol)
          (loopBuf "atar" 
             (1.5 + 0.1 * sin (2.0 * rad))
             (0.1 + 0.1 * sin rad)
             (0.5 + 0.25 * sin (2.0 * rad))))
     : (gain' (0.3 * vol) (loopBuf "atar" 0.25 0.0 0.0) )
     : Nil)

buffers ctx _ = affable $ sequence (
    O.singleton "atar"
        $ toAffE (decodeAudioDataFromUri ctx "https://freesound.org/data/previews/100/100981_1234256-lq.mp3"))

main :: Klank
main = klank {
    run = runInBrowser scene,
    buffers = buffers
}
```

## Fake cricket

Click the 🖱️ while this is playing 😁

[Try me on klank.dev.](https://klank.dev/?k&url=https://klank-share.s3.eu-west-1.amazonaws.com/K16089026260487021.purs)
```haskell
module Klank.Dev where

import Prelude
import Data.List ((:), List(..))
import Data.NonEmpty ((:|))
import Data.Typelevel.Num (D1, D2)
import Foreign.Object as O
import Data.Vec((+>), empty)
import Effect.Class(liftEffect)
import Data.Set(isEmpty)
import FRP.Behavior (Behavior)
import FRP.Behavior.Audio (AudioUnit, gain', 
  runInBrowser_, dup1, panner, merger,
  sinOsc, play, speaker,
  makeAudioTrack)
import Math (pi, sin)
import Type.Klank.Dev(Klank, klank, affable)
import FRP.Behavior.Mouse (buttons)
import FRP.Event.Mouse (Mouse, getMouse)

scene :: Mouse -> Number -> Behavior (AudioUnit D2)
scene mouse time = f time <$> click
  where
  f s cl =
    let
      rad = pi * s
    in
      dup1
        ( (gain' (if cl then 0.01 else 0.0) $ sinOsc (1830.0 + (20.0 * sin (0.2 * rad))))
            + (gain' (if cl then 0.01 else 0.0) $ sinOsc (1840.0 + (if cl then 50.0 else 0.0)))
        ) \mono ->
        speaker
          $ ( (panner rad (merger (mono +> mono +> empty)))
                :| (gain' 0.5 $ (play "forest"))
                : Nil
            )

  click :: Behavior Boolean
  click = map (not <<< isEmpty) $ buttons mouse

tracks _ = affable (do
  forest <- liftEffect $ makeAudioTrack "https://freesound.org/data/previews/530/530415_1648170-lq.mp3"
  pure $ O.singleton "forest" forest)

enableMicrophone = true

main :: Klank
main = klank {
    run = runInBrowser_ do
      mouse <- getMouse
      pure $ scene mouse
  , tracks = tracks
}
```

## Modulating voice

This one uses your microphone, so please only run it with headphones!

[Try me on klank.dev.](https://klank.dev/?k&url=https://klank-share.s3.eu-west-1.amazonaws.com/K16089026660107556.purs)
```haskell
module Klank.Dev where

import Prelude
import Data.Symbol (SProxy(..))
import Data.Tuple (Tuple(..))
import Data.Typelevel.Num (D1)
import FRP.Behavior (Behavior)
import FRP.Behavior.Audio (AudioUnit, g'add, g'bandpass, g'delay, g'gain, graph, microphone, runInBrowser, speaker')
import Math (pi, sin)
import Record.Extra (SLProxy(..), SNil)
import Type.Data.Graph (type (:/))
import Type.Klank.Dev (Klank, klank)

scene :: Number -> Behavior (AudioUnit D1)
scene time =
  pure
    $ speaker'
        ( graph
            { aggregators:
                { out: Tuple g'add (SLProxy :: SLProxy ("combine" :/ SNil))
                , combine: Tuple g'add (SLProxy :: SLProxy ("gain" :/ "mic" :/ SNil))
                , gain: Tuple (g'gain 0.9) (SLProxy :: SLProxy ("del" :/ SNil))
                }
            , processors:
                { del: Tuple (g'delay (0.7 + 0.45 * sin (0.25 * time * pi))) (SProxy :: SProxy "filt")
                , filt: Tuple (g'bandpass 440.0 1.0) (SProxy :: SProxy "combine")
                }
            , generators:
                { mic: microphone
                }
            }
        )

main :: Klank
main =
  klank
    { enableMicrophone = true
    , run = runInBrowser scene
    }
```

## For more info...

I'm slowly but surely adding examples and documentation to [discourse.klank.dev](https://discourse.klank.dev). A good place to start is [here](https://discourse.klank.dev/t/readme-first-getting-started/15). If anything's broken, it's my fault.  Please file a bug report on the discourse using the _Site feedback_ tag.

**THANK YOU** for checking out [klank.dev](https://klank.dev), I hope you have fun, and please share what you make!