├── .prettierrc.json
├── style
└── style.css
├── audio
├── tom-808.ogg
├── tom-fm.ogg
├── clap-808.ogg
├── clap-fat.ogg
├── clap-tape.ogg
├── crash-808.ogg
├── hihat-808.ogg
├── kick-808.ogg
├── kick-big.ogg
├── kick-deep.ogg
├── kick-dry.ogg
├── kick-tape.ogg
├── kick-tron.ogg
├── perc-808.ogg
├── snare-808.ogg
├── snare-big.ogg
├── tom-lofi.ogg
├── tom-short.ogg
├── clap-analog.ogg
├── clap-crushed.ogg
├── clap-slapper.ogg
├── cowbell-808.ogg
├── crash-noise.ogg
├── crash-tape.ogg
├── hihat-analog.ogg
├── hihat-dist01.ogg
├── hihat-dist02.ogg
├── hihat-plain.ogg
├── hihat-reso.ogg
├── hihat-ring.ogg
├── kick-classic.ogg
├── kick-floppy.ogg
├── kick-gritty.ogg
├── kick-heavy.ogg
├── kick-newwave.ogg
├── kick-plain.ogg
├── kick-softy.ogg
├── kick-stomp.ogg
├── kick-thump.ogg
├── kick-tight.ogg
├── kick-vinyl01.ogg
├── kick-vinyl02.ogg
├── kick-zapper.ogg
├── openhat-808.ogg
├── perc-chirpy.ogg
├── perc-hollow.ogg
├── perc-laser.ogg
├── perc-metal.ogg
├── perc-nasty.ogg
├── perc-short.ogg
├── perc-tambo.ogg
├── perc-tribal.ogg
├── perc-weirdo.ogg
├── snare-analog.ogg
├── snare-block.ogg
├── snare-brute.ogg
├── snare-dist01.ogg
├── snare-dist02.ogg
├── snare-dist03.ogg
├── snare-lofi01.ogg
├── snare-lofi02.ogg
├── snare-noise.ogg
├── snare-pinch.ogg
├── snare-punch.ogg
├── snare-sumo.ogg
├── snare-tape.ogg
├── tom-analog.ogg
├── tom-chiptune.ogg
├── tom-rototom.ogg
├── crash-acoustic.ogg
├── hihat-digital.ogg
├── hihat-electro.ogg
├── kick-electro01.ogg
├── kick-electro02.ogg
├── kick-oldschool.ogg
├── kick-slapback.ogg
├── openhat-analog.ogg
├── openhat-slick.ogg
├── openhat-tight.ogg
├── shaker-analog.ogg
├── shaker-shuffle.ogg
├── shaker-suckup.ogg
├── snare-electro.ogg
├── snare-modular.ogg
├── snare-smasher.ogg
├── snare-vinyl01.ogg
├── snare-vinyl02.ogg
├── tom-acoustic01.ogg
├── tom-acoustic02.ogg
├── hihat-acoustic01.ogg
├── hihat-acoustic02.ogg
├── kick-acoustic01.ogg
├── kick-acoustic02.ogg
├── kick-cultivator.ogg
├── ride-acoustic01.ogg
├── ride-acoustic02.ogg
├── snare-acoustic01.ogg
├── snare-acoustic02.ogg
└── openhat-acoustic01.ogg
├── .gitignore
├── README.md
├── index.html
├── elm
├── Ports.elm
├── Data
│ ├── Note.elm
│ ├── Instrument.elm
│ ├── Sequence.elm
│ ├── Track.elm
│ ├── Scale.elm
│ ├── Drum
│ │ └── Kit.elm
│ └── Drum.elm
└── Main.elm
├── elm.json
├── package.json
└── js
├── meter.js
├── index.js
└── instruments.js
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120
3 | }
4 |
--------------------------------------------------------------------------------
/style/style.css:
--------------------------------------------------------------------------------
1 | .vuMeter {
2 | width: 300px;
3 | height: 300px;
4 | }
5 |
--------------------------------------------------------------------------------
/audio/tom-808.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/tom-808.ogg
--------------------------------------------------------------------------------
/audio/tom-fm.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/tom-fm.ogg
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 | /dist
3 | /node_modules
4 | /elm-stuff
5 | /package-lock.json
6 |
--------------------------------------------------------------------------------
/audio/clap-808.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/clap-808.ogg
--------------------------------------------------------------------------------
/audio/clap-fat.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/clap-fat.ogg
--------------------------------------------------------------------------------
/audio/clap-tape.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/clap-tape.ogg
--------------------------------------------------------------------------------
/audio/crash-808.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/crash-808.ogg
--------------------------------------------------------------------------------
/audio/hihat-808.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/hihat-808.ogg
--------------------------------------------------------------------------------
/audio/kick-808.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-808.ogg
--------------------------------------------------------------------------------
/audio/kick-big.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-big.ogg
--------------------------------------------------------------------------------
/audio/kick-deep.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-deep.ogg
--------------------------------------------------------------------------------
/audio/kick-dry.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-dry.ogg
--------------------------------------------------------------------------------
/audio/kick-tape.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-tape.ogg
--------------------------------------------------------------------------------
/audio/kick-tron.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-tron.ogg
--------------------------------------------------------------------------------
/audio/perc-808.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/perc-808.ogg
--------------------------------------------------------------------------------
/audio/snare-808.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-808.ogg
--------------------------------------------------------------------------------
/audio/snare-big.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-big.ogg
--------------------------------------------------------------------------------
/audio/tom-lofi.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/tom-lofi.ogg
--------------------------------------------------------------------------------
/audio/tom-short.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/tom-short.ogg
--------------------------------------------------------------------------------
/audio/clap-analog.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/clap-analog.ogg
--------------------------------------------------------------------------------
/audio/clap-crushed.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/clap-crushed.ogg
--------------------------------------------------------------------------------
/audio/clap-slapper.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/clap-slapper.ogg
--------------------------------------------------------------------------------
/audio/cowbell-808.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/cowbell-808.ogg
--------------------------------------------------------------------------------
/audio/crash-noise.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/crash-noise.ogg
--------------------------------------------------------------------------------
/audio/crash-tape.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/crash-tape.ogg
--------------------------------------------------------------------------------
/audio/hihat-analog.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/hihat-analog.ogg
--------------------------------------------------------------------------------
/audio/hihat-dist01.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/hihat-dist01.ogg
--------------------------------------------------------------------------------
/audio/hihat-dist02.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/hihat-dist02.ogg
--------------------------------------------------------------------------------
/audio/hihat-plain.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/hihat-plain.ogg
--------------------------------------------------------------------------------
/audio/hihat-reso.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/hihat-reso.ogg
--------------------------------------------------------------------------------
/audio/hihat-ring.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/hihat-ring.ogg
--------------------------------------------------------------------------------
/audio/kick-classic.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-classic.ogg
--------------------------------------------------------------------------------
/audio/kick-floppy.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-floppy.ogg
--------------------------------------------------------------------------------
/audio/kick-gritty.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-gritty.ogg
--------------------------------------------------------------------------------
/audio/kick-heavy.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-heavy.ogg
--------------------------------------------------------------------------------
/audio/kick-newwave.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-newwave.ogg
--------------------------------------------------------------------------------
/audio/kick-plain.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-plain.ogg
--------------------------------------------------------------------------------
/audio/kick-softy.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-softy.ogg
--------------------------------------------------------------------------------
/audio/kick-stomp.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-stomp.ogg
--------------------------------------------------------------------------------
/audio/kick-thump.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-thump.ogg
--------------------------------------------------------------------------------
/audio/kick-tight.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-tight.ogg
--------------------------------------------------------------------------------
/audio/kick-vinyl01.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-vinyl01.ogg
--------------------------------------------------------------------------------
/audio/kick-vinyl02.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-vinyl02.ogg
--------------------------------------------------------------------------------
/audio/kick-zapper.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-zapper.ogg
--------------------------------------------------------------------------------
/audio/openhat-808.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/openhat-808.ogg
--------------------------------------------------------------------------------
/audio/perc-chirpy.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/perc-chirpy.ogg
--------------------------------------------------------------------------------
/audio/perc-hollow.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/perc-hollow.ogg
--------------------------------------------------------------------------------
/audio/perc-laser.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/perc-laser.ogg
--------------------------------------------------------------------------------
/audio/perc-metal.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/perc-metal.ogg
--------------------------------------------------------------------------------
/audio/perc-nasty.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/perc-nasty.ogg
--------------------------------------------------------------------------------
/audio/perc-short.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/perc-short.ogg
--------------------------------------------------------------------------------
/audio/perc-tambo.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/perc-tambo.ogg
--------------------------------------------------------------------------------
/audio/perc-tribal.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/perc-tribal.ogg
--------------------------------------------------------------------------------
/audio/perc-weirdo.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/perc-weirdo.ogg
--------------------------------------------------------------------------------
/audio/snare-analog.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-analog.ogg
--------------------------------------------------------------------------------
/audio/snare-block.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-block.ogg
--------------------------------------------------------------------------------
/audio/snare-brute.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-brute.ogg
--------------------------------------------------------------------------------
/audio/snare-dist01.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-dist01.ogg
--------------------------------------------------------------------------------
/audio/snare-dist02.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-dist02.ogg
--------------------------------------------------------------------------------
/audio/snare-dist03.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-dist03.ogg
--------------------------------------------------------------------------------
/audio/snare-lofi01.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-lofi01.ogg
--------------------------------------------------------------------------------
/audio/snare-lofi02.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-lofi02.ogg
--------------------------------------------------------------------------------
/audio/snare-noise.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-noise.ogg
--------------------------------------------------------------------------------
/audio/snare-pinch.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-pinch.ogg
--------------------------------------------------------------------------------
/audio/snare-punch.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-punch.ogg
--------------------------------------------------------------------------------
/audio/snare-sumo.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-sumo.ogg
--------------------------------------------------------------------------------
/audio/snare-tape.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-tape.ogg
--------------------------------------------------------------------------------
/audio/tom-analog.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/tom-analog.ogg
--------------------------------------------------------------------------------
/audio/tom-chiptune.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/tom-chiptune.ogg
--------------------------------------------------------------------------------
/audio/tom-rototom.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/tom-rototom.ogg
--------------------------------------------------------------------------------
/audio/crash-acoustic.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/crash-acoustic.ogg
--------------------------------------------------------------------------------
/audio/hihat-digital.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/hihat-digital.ogg
--------------------------------------------------------------------------------
/audio/hihat-electro.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/hihat-electro.ogg
--------------------------------------------------------------------------------
/audio/kick-electro01.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-electro01.ogg
--------------------------------------------------------------------------------
/audio/kick-electro02.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-electro02.ogg
--------------------------------------------------------------------------------
/audio/kick-oldschool.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-oldschool.ogg
--------------------------------------------------------------------------------
/audio/kick-slapback.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-slapback.ogg
--------------------------------------------------------------------------------
/audio/openhat-analog.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/openhat-analog.ogg
--------------------------------------------------------------------------------
/audio/openhat-slick.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/openhat-slick.ogg
--------------------------------------------------------------------------------
/audio/openhat-tight.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/openhat-tight.ogg
--------------------------------------------------------------------------------
/audio/shaker-analog.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/shaker-analog.ogg
--------------------------------------------------------------------------------
/audio/shaker-shuffle.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/shaker-shuffle.ogg
--------------------------------------------------------------------------------
/audio/shaker-suckup.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/shaker-suckup.ogg
--------------------------------------------------------------------------------
/audio/snare-electro.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-electro.ogg
--------------------------------------------------------------------------------
/audio/snare-modular.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-modular.ogg
--------------------------------------------------------------------------------
/audio/snare-smasher.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-smasher.ogg
--------------------------------------------------------------------------------
/audio/snare-vinyl01.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-vinyl01.ogg
--------------------------------------------------------------------------------
/audio/snare-vinyl02.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-vinyl02.ogg
--------------------------------------------------------------------------------
/audio/tom-acoustic01.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/tom-acoustic01.ogg
--------------------------------------------------------------------------------
/audio/tom-acoustic02.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/tom-acoustic02.ogg
--------------------------------------------------------------------------------
/audio/hihat-acoustic01.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/hihat-acoustic01.ogg
--------------------------------------------------------------------------------
/audio/hihat-acoustic02.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/hihat-acoustic02.ogg
--------------------------------------------------------------------------------
/audio/kick-acoustic01.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-acoustic01.ogg
--------------------------------------------------------------------------------
/audio/kick-acoustic02.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-acoustic02.ogg
--------------------------------------------------------------------------------
/audio/kick-cultivator.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/kick-cultivator.ogg
--------------------------------------------------------------------------------
/audio/ride-acoustic01.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/ride-acoustic01.ogg
--------------------------------------------------------------------------------
/audio/ride-acoustic02.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/ride-acoustic02.ogg
--------------------------------------------------------------------------------
/audio/snare-acoustic01.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-acoustic01.ogg
--------------------------------------------------------------------------------
/audio/snare-acoustic02.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/snare-acoustic02.ogg
--------------------------------------------------------------------------------
/audio/openhat-acoustic01.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n1k0/jujube/master/audio/openhat-acoustic01.ogg
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | jujube
2 | ======
3 |
4 | [Generative Music](https://teropa.info/loop/#/title) experiment written with [Elm](https://elm-lang.org/).
5 |
6 | ## Install
7 |
8 | npm i && npm start
9 | open http://localhost:3000
10 |
11 | ## Why this name?
12 |
13 | Why not?
14 |
15 | ## License
16 |
17 | WTFPL
18 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | jujube
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/elm/Ports.elm:
--------------------------------------------------------------------------------
1 | port module Ports exposing (bar, load, ready, setBpm, setDrumTracks, setTrack, start, stop)
2 |
3 | import Json.Encode as Encode
4 |
5 |
6 |
7 | --Commands
8 |
9 |
10 | port load : () -> Cmd msg
11 |
12 |
13 | port start : () -> Cmd msg
14 |
15 |
16 | port stop : () -> Cmd msg
17 |
18 |
19 | port setBpm : Int -> Cmd msg
20 |
21 |
22 | port setDrumTracks : Encode.Value -> Cmd msg
23 |
24 |
25 | port setTrack : Encode.Value -> Cmd msg
26 |
27 |
28 |
29 | -- Subscriptions
30 |
31 |
32 | port bar : (Float -> msg) -> Sub msg
33 |
34 |
35 | port ready : (Bool -> msg) -> Sub msg
36 |
--------------------------------------------------------------------------------
/elm.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "application",
3 | "source-directories": ["elm"],
4 | "elm-version": "0.19.0",
5 | "dependencies": {
6 | "direct": {
7 | "elm/browser": "1.0.1",
8 | "elm/core": "1.0.2",
9 | "elm/html": "1.0.0",
10 | "elm/json": "1.1.3",
11 | "elm/random": "1.0.0",
12 | "elm/time": "1.0.0",
13 | "elm-community/random-extra": "3.1.0"
14 | },
15 | "indirect": {
16 | "elm/url": "1.0.0",
17 | "elm/virtual-dom": "1.0.2",
18 | "owanturist/elm-union-find": "1.0.0"
19 | }
20 | },
21 | "test-dependencies": {
22 | "direct": {},
23 | "indirect": {}
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jujube",
3 | "version": "1.0.0",
4 | "description": "",
5 | "private": true,
6 | "main": "index.js",
7 | "scripts": {
8 | "start": "parcel serve -p 3000 index.html",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "author": "Nicolas Perriault ",
12 | "license": "ISC",
13 | "staticFiles": {
14 | "staticPath": "audio"
15 | },
16 | "browserslist": [
17 | "last 1 Chrome version"
18 | ],
19 | "devDependencies": {
20 | "elm": "0.19.0-no-deps",
21 | "elm-format": "0.8.1",
22 | "elm-hot": "^1.1.1",
23 | "node-elm-compiler": "^5.0.4",
24 | "parcel-bundler": "^1.12.3",
25 | "parcel-plugin-static-files-copy": "^2.2.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/elm/Data/Note.elm:
--------------------------------------------------------------------------------
1 | module Data.Note exposing (Note, encode, randomFromPitch)
2 |
3 | import Json.Encode as Encode
4 | import Random exposing (Generator)
5 | import Random.List as RandomList
6 |
7 |
8 | type alias Note =
9 | { pitch : String
10 | , dur : String
11 | , vel : Float
12 | }
13 |
14 |
15 | durations : List String
16 | durations =
17 | [ "8n", "12n", "16n", "32n" ]
18 |
19 |
20 | encode : Note -> Encode.Value
21 | encode v =
22 | Encode.object
23 | [ ( "dur", Encode.string v.dur )
24 | , ( "vel", Encode.float v.vel )
25 | , ( "pitch", Encode.string v.pitch )
26 | ]
27 |
28 |
29 | randomFromPitch : String -> Generator Note
30 | randomFromPitch pitch =
31 | Random.map2 (Note pitch)
32 | (RandomList.choose durations |> Random.andThen (Tuple.first >> Maybe.withDefault "16n" >> Random.constant))
33 | (Random.float 0.2 1)
34 |
--------------------------------------------------------------------------------
/elm/Data/Instrument.elm:
--------------------------------------------------------------------------------
1 | module Data.Instrument exposing (Instrument(..), encode, random, toString)
2 |
3 | import Json.Encode as Encode
4 | import Random exposing (Generator)
5 | import Random.List as RandomList
6 |
7 |
8 | type Instrument
9 | = Bass
10 | | Brass
11 | | Cello
12 | | Kalimba
13 | | Marimba
14 | | Piano
15 | | Steelpan
16 | | Wind
17 |
18 |
19 | encode : Instrument -> Encode.Value
20 | encode =
21 | toString >> Encode.string
22 |
23 |
24 | tonal : List Instrument
25 | tonal =
26 | [ Bass, Brass, Cello, Kalimba, Marimba, Piano, Steelpan, Wind ]
27 |
28 |
29 | random : Generator Instrument
30 | random =
31 | RandomList.choose tonal
32 | |> Random.andThen (Tuple.first >> Maybe.withDefault Piano >> Random.constant)
33 |
34 |
35 | toString : Instrument -> String
36 | toString instrument =
37 | case instrument of
38 | Bass ->
39 | "bass"
40 |
41 | Brass ->
42 | "brass"
43 |
44 | Cello ->
45 | "cello"
46 |
47 | Kalimba ->
48 | "kalimba"
49 |
50 | Marimba ->
51 | "marimba"
52 |
53 | Piano ->
54 | "piano"
55 |
56 | Steelpan ->
57 | "steelpan"
58 |
59 | Wind ->
60 | "wind"
61 |
--------------------------------------------------------------------------------
/js/meter.js:
--------------------------------------------------------------------------------
1 | function clamp(x, lower, upper) {
2 | if (Number.isNaN(x)) return NaN;
3 | if (Number.isNaN(lower)) return NaN;
4 | if (Number.isNaN(upper)) return NaN;
5 |
6 | const max = Math.max(x, lower);
7 | const min = Math.min(max, upper);
8 |
9 | return min;
10 | }
11 |
12 | function scale(x, inLow, inHigh, outLow, outHigh) {
13 | if (Number.isNaN(x)) return NaN;
14 | if (Number.isNaN(inLow)) return NaN;
15 | if (Number.isNaN(inHigh)) return NaN;
16 | if (Number.isNaN(outLow)) return NaN;
17 | if (Number.isNaN(outHigh)) return NaN;
18 |
19 | if (x === Infinity || x === -Infinity) return x;
20 |
21 | let result = x - inLow;
22 | result *= outHigh - outLow;
23 | result /= inHigh - inLow;
24 | result += outLow;
25 | return result;
26 | }
27 |
28 | export default function vuMeter(tone, canvas) {
29 | const split = new Tone.Split();
30 | tone.connect(split);
31 | const meters = [new Tone.Meter(0.9), new Tone.Meter(0.9)];
32 | split.left.connect(meters[0]);
33 | split.right.connect(meters[1]);
34 |
35 | const context = canvas.getContext("2d");
36 |
37 | loop();
38 |
39 | function loop() {
40 | requestAnimationFrame(loop);
41 | const { width, height } = canvas;
42 | const values = meters.map(m => m.getLevel());
43 | const barHeights = values.map(value => clamp(scale(value, -100, 6, 0, height), 4, height));
44 | context.clearRect(0, 0, width, height);
45 | const barWidth = width / meters.length;
46 | context.fillStyle = "#777";
47 | const margin = meters.length > 1 ? 5 : 0;
48 | barHeights.forEach((barHeight, i) => {
49 | context.fillRect(i * barWidth + margin * i, height - barHeight, barWidth - margin, barHeight);
50 | });
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/elm/Data/Sequence.elm:
--------------------------------------------------------------------------------
1 | module Data.Sequence exposing (Sequence(..), encode, random)
2 |
3 | import Array
4 | import Data.Note as Note exposing (Note)
5 | import Json.Encode as Encode
6 | import Random exposing (Generator)
7 | import Random.Array as RandomArray
8 | import Random.List as RandomList
9 |
10 |
11 | type Sequence
12 | = Silence
13 | | Single Note
14 | | Multiple (List Sequence)
15 |
16 |
17 | encode : Sequence -> Encode.Value
18 | encode seq =
19 | case seq of
20 | Silence ->
21 | Encode.null
22 |
23 | Single note ->
24 | Note.encode note
25 |
26 | Multiple subSeq ->
27 | Encode.list encode subSeq
28 |
29 |
30 | pickOne : List String -> Generator Sequence
31 | pickOne =
32 | RandomList.choose
33 | >> Random.andThen
34 | (Tuple.first
35 | >> Maybe.map (Note.randomFromPitch >> Random.andThen (Single >> Random.constant))
36 | >> Maybe.withDefault (Random.constant Silence)
37 | )
38 |
39 |
40 | pickMany : Int -> List String -> Generator Sequence
41 | pickMany length =
42 | pickOne
43 | >> RandomArray.array length
44 | >> Random.andThen (Array.toList >> Multiple >> Random.constant)
45 |
46 |
47 | decideWhat : List String -> Float -> Generator Sequence
48 | decideWhat notes prob =
49 | if prob > 0.97 then
50 | pickMany 3 notes
51 |
52 | else if prob > 0.8 then
53 | Random.constant Silence
54 |
55 | else if prob > 0.6 then
56 | pickOne notes
57 |
58 | else if prob > 0.2 then
59 | pickMany 2 notes
60 |
61 | else
62 | pickMany 4 notes
63 |
64 |
65 | random : Int -> List String -> Generator Sequence
66 | random bars notes =
67 | Random.float 0 1
68 | |> Random.andThen (decideWhat notes)
69 | |> RandomArray.rangeLengthArray bars bars
70 | |> Random.andThen (Array.toList >> Multiple >> Random.constant)
71 |
--------------------------------------------------------------------------------
/elm/Data/Track.elm:
--------------------------------------------------------------------------------
1 | module Data.Track exposing (Track, encode, random)
2 |
3 | import Data.Drum as Drum exposing (Drum)
4 | import Data.Instrument as Instrument exposing (Instrument)
5 | import Data.Scale as Scale
6 | import Data.Sequence as Sequence exposing (Sequence)
7 | import Json.Encode as Encode
8 | import Random exposing (Generator)
9 | import Random.List as RandomList
10 |
11 |
12 | type alias Track =
13 | { id : String
14 | , resolution : String
15 | , instrument : Instrument
16 | , pan : Float
17 | , volume : Float
18 | , probability : Float
19 | , sequence : Sequence
20 | }
21 |
22 |
23 | type alias Config =
24 | { id : String
25 | , bars : Maybe Int
26 | , instrument : Maybe Instrument
27 | , octaves : Maybe ( Int, Int )
28 | , pan : Maybe Float
29 | , scale : List String
30 | , probability : Maybe Float
31 | , volume : Maybe Float
32 | }
33 |
34 |
35 | encode : Track -> Encode.Value
36 | encode v =
37 | Encode.object
38 | [ ( "id", Encode.string v.id )
39 | , ( "resolution", Encode.string v.resolution )
40 | , ( "instrument", Instrument.encode v.instrument )
41 | , ( "pan", Encode.float v.pan )
42 | , ( "probability", Encode.float v.probability )
43 | , ( "volume", Encode.float v.volume )
44 | , ( "resolution", Encode.string v.resolution )
45 | , ( "sequence", Sequence.encode v.sequence )
46 | ]
47 |
48 |
49 | random : Config -> Generator Track
50 | random config =
51 | Random.map5 (Track config.id "4n")
52 | (config.instrument |> Maybe.map Random.constant >> Maybe.withDefault Instrument.random)
53 | (config.pan |> Maybe.map Random.constant |> Maybe.withDefault (Random.float -1 1))
54 | (config.volume |> Maybe.map Random.constant |> Maybe.withDefault (Random.float -20 -10))
55 | (config.probability |> Maybe.withDefault 1 >> Random.constant)
56 | (Sequence.random (config.bars |> Maybe.withDefault 4) (Scale.range (config.octaves |> Maybe.withDefault ( 1, 6 )) config.scale))
57 |
--------------------------------------------------------------------------------
/elm/Data/Scale.elm:
--------------------------------------------------------------------------------
1 | module Data.Scale exposing
2 | ( diat
3 | , fifth
4 | , first
5 | , fourth
6 | , penta
7 | , randomNext
8 | , range
9 | , second
10 | , seventh
11 | , sixth
12 | , third
13 | )
14 |
15 | import Random exposing (Generator)
16 | import Random.List as RandomList
17 |
18 |
19 | first : List String
20 | first =
21 | [ "C", "E", "G", "B" ]
22 |
23 |
24 | second : List String
25 | second =
26 | [ "D", "F", "A", "C" ]
27 |
28 |
29 | third : List String
30 | third =
31 | [ "E", "G", "B", "D" ]
32 |
33 |
34 | fourth : List String
35 | fourth =
36 | [ "F", "A", "C", "E" ]
37 |
38 |
39 | fifth : List String
40 | fifth =
41 | [ "G", "B", "D", "F" ]
42 |
43 |
44 | sixth : List String
45 | sixth =
46 | [ "A", "C", "E", "G" ]
47 |
48 |
49 | seventh : List String
50 | seventh =
51 | [ "B", "D", "F", "A" ]
52 |
53 |
54 | diat : List String
55 | diat =
56 | [ "C", "D", "E", "F", "G", "A", "B" ]
57 |
58 |
59 | penta : List String
60 | penta =
61 | [ "A", "C", "D", "E", "F", "G" ]
62 |
63 |
64 | range : ( Int, Int ) -> List String -> List String
65 | range ( min, max ) notes =
66 | List.range min max
67 | |> List.map (\oct -> notes |> List.map (\n -> n ++ String.fromInt oct))
68 | |> List.concat
69 |
70 |
71 | takeSeventh : List String -> Generator (List String)
72 | takeSeventh scale =
73 | Random.float 0 1
74 | |> Random.andThen
75 | (\prob ->
76 | if prob > 0.9 then
77 | Random.constant scale
78 |
79 | else
80 | Random.constant (List.take 3 scale)
81 | )
82 |
83 |
84 | pickOne : List (List String) -> Generator (List String)
85 | pickOne choices =
86 | RandomList.choose choices
87 | |> Random.andThen (Tuple.first >> Maybe.withDefault first >> Random.constant)
88 | |> Random.andThen takeSeventh
89 |
90 |
91 | randomNext : List String -> Generator (List String)
92 | randomNext scale =
93 | if scale == first then
94 | pickOne [ second, fourth, sixth ]
95 |
96 | else if scale == second then
97 | pickOne [ third, fourth, fifth, sixth ]
98 |
99 | else if scale == third then
100 | pickOne [ first, second, fourth, fifth, sixth ]
101 |
102 | else if scale == fourth then
103 | pickOne [ first, second, fifth, sixth ]
104 |
105 | else if scale == fifth then
106 | pickOne [ first, fourth ]
107 |
108 | else if scale == sixth then
109 | pickOne [ first, second, third, fourth, fifth ]
110 |
111 | else
112 | Random.constant first
113 |
--------------------------------------------------------------------------------
/js/index.js:
--------------------------------------------------------------------------------
1 | import { Elm } from "./../elm/Main.elm";
2 | import instruments from "./instruments.js";
3 | import vuMeter from "./meter.js";
4 |
5 | if (module.hot) {
6 | module.hot.dispose(() => {
7 | window.location.reload();
8 | });
9 | }
10 |
11 | const app = Elm.Main.init({ node: document.querySelector("main") });
12 |
13 | let drumKit;
14 | const tracks = {};
15 |
16 | function setTrack(id, sequence, handler) {
17 | if (tracks.hasOwnProperty(id)) {
18 | tracks[id].mute = true;
19 | tracks[id].probability = 0;
20 | tracks[id].loop = 0;
21 | tracks[id].callback = () => {};
22 | tracks[id]
23 | .cancel(0)
24 | .stop(0)
25 | .removeAll()
26 | .dispose();
27 | }
28 | delete tracks[id];
29 | tracks[id] = new Tone.Sequence(handler, sequence).start(0);
30 | return tracks[id];
31 | }
32 |
33 | function drumTrack(id, sequence) {
34 | setTrack(id, sequence, function(time, note) {
35 | try {
36 | drumKit.get(note.pitch).start(time);
37 | } catch (e) {
38 | console.warn("cannot get drum sample", note.pitch);
39 | }
40 | }).probability = 0.99;
41 | }
42 |
43 | async function setupDrumKit() {
44 | const reverb = new Tone.Reverb(1.1);
45 | reverb.wet.value = 0.2;
46 | await reverb.generate();
47 | const panVol = new Tone.PanVol(0, -6);
48 | const sounds = instruments.drumSamples.reduce((acc, sample) => ({ ...acc, [sample]: sample }), {});
49 | drumKit = await new Promise(resolve => {
50 | new Tone.Players(sounds, function(kit) {
51 | resolve(kit.chain(panVol, reverb, Tone.Master));
52 | });
53 | });
54 | }
55 |
56 | app.ports.load.subscribe(async function() {
57 | Tone.start();
58 | Tone.Master.chain(new Tone.Limiter(-6));
59 | vuMeter(Tone.Master, document.querySelector(".vuMeter"));
60 | await setupDrumKit();
61 | app.ports.ready.send(true);
62 | });
63 |
64 | app.ports.start.subscribe(function() {
65 | Tone.Transport.start();
66 | Tone.Transport.scheduleRepeat(app.ports.bar.send, "4m");
67 | });
68 |
69 | app.ports.stop.subscribe(function() {
70 | Tone.Transport.stop();
71 | Tone.Transport.position = 0;
72 | });
73 |
74 | app.ports.setBpm.subscribe(function(bpm) {
75 | Tone.Transport.bpm.value = bpm;
76 | });
77 |
78 | app.ports.setDrumTracks.subscribe(function(drums) {
79 | for (const key in drums) {
80 | drumTrack(key, drums[key].sequence);
81 | }
82 | });
83 |
84 | app.ports.setTrack.subscribe(function(track) {
85 | const panVol = new Tone.PanVol(track.pan, track.volume);
86 | const pingPong = new Tone.PingPongDelay("8n", 0);
87 | const instrument = new Tone.PolySynth(4, Tone.Synth, instruments[track.instrument]).chain(
88 | pingPong,
89 | panVol,
90 | Tone.Master
91 | );
92 |
93 | setTrack(track.id, track.sequence, function(time, note) {
94 | instrument.triggerAttackRelease(note.pitch, note.dur, time, note.vel);
95 | }).probability = track.probability;
96 | });
97 |
--------------------------------------------------------------------------------
/elm/Data/Drum/Kit.elm:
--------------------------------------------------------------------------------
1 | module Data.Drum.Kit exposing
2 | ( randomClap
3 | , randomCrash
4 | , randomHihat
5 | , randomKick
6 | , randomOpenhat
7 | , randomPerc
8 | , randomRide
9 | , randomShaker
10 | , randomSnare
11 | , randomTom
12 | )
13 |
14 | import Random exposing (Generator)
15 | import Random.List as RandomList
16 |
17 |
18 | pick : List String -> Generator String
19 | pick =
20 | RandomList.choose
21 | >> Random.andThen
22 | (Tuple.first
23 | >> Maybe.withDefault ""
24 | >> Random.constant
25 | )
26 |
27 |
28 | randomClap : Generator String
29 | randomClap =
30 | pick
31 | [ "clap-808.ogg"
32 | , "clap-analog.ogg"
33 | , "clap-crushed.ogg"
34 | , "clap-fat.ogg"
35 | , "clap-slapper.ogg"
36 | , "clap-tape.ogg"
37 | ]
38 |
39 |
40 | randomCrash : Generator String
41 | randomCrash =
42 | pick
43 | [ "crash-808.ogg"
44 | , "crash-acoustic.ogg"
45 | , "crash-noise.ogg"
46 | , "crash-tape.ogg"
47 | ]
48 |
49 |
50 | randomHihat : Generator String
51 | randomHihat =
52 | pick
53 | [ "hihat-808.ogg"
54 | , "hihat-digital.ogg"
55 | , "hihat-electro.ogg"
56 | , "hihat-plain.ogg"
57 | ]
58 |
59 |
60 | randomKick : Generator String
61 | randomKick =
62 | pick
63 | [ "kick-deep.ogg"
64 | , "kick-electro01.ogg"
65 | , "kick-floppy.ogg"
66 | , "kick-newwave.ogg"
67 | , "kick-oldschool.ogg"
68 | , "kick-tape.ogg"
69 | , "kick-tight.ogg"
70 | , "kick-tron.ogg"
71 | ]
72 |
73 |
74 | randomOpenhat : Generator String
75 | randomOpenhat =
76 | pick
77 | [ "openhat-808.ogg"
78 | , "openhat-acoustic01.ogg"
79 | , "openhat-analog.ogg"
80 | , "openhat-slick.ogg"
81 | , "openhat-tight.ogg"
82 | ]
83 |
84 |
85 | randomPerc : Generator String
86 | randomPerc =
87 | pick
88 | [ "perc-808.ogg"
89 | , "perc-chirpy.ogg"
90 | , "perc-hollow.ogg"
91 | , "perc-laser.ogg"
92 | , "perc-metal.ogg"
93 | , "perc-nasty.ogg"
94 | , "perc-short.ogg"
95 | , "perc-tambo.ogg"
96 | , "perc-tribal.ogg"
97 | , "perc-weirdo.ogg"
98 | ]
99 |
100 |
101 | randomRide : Generator String
102 | randomRide =
103 | pick
104 | [ "ride-acoustic01.ogg"
105 | , "ride-acoustic02.ogg"
106 | ]
107 |
108 |
109 | randomShaker : Generator String
110 | randomShaker =
111 | pick
112 | [ "shaker-analog.ogg"
113 | , "shaker-shuffle.ogg"
114 | , "shaker-suckup.ogg"
115 | ]
116 |
117 |
118 | randomSnare : Generator String
119 | randomSnare =
120 | pick
121 | [ "snare-electro.ogg"
122 | , "snare-lofi01.ogg"
123 | , "snare-noise.ogg"
124 | , "snare-pinch.ogg"
125 | ]
126 |
127 |
128 | randomTom : Generator String
129 | randomTom =
130 | pick
131 | [ "tom-808.ogg"
132 | , "tom-acoustic01.ogg"
133 | , "tom-acoustic02.ogg"
134 | , "tom-analog.ogg"
135 | , "tom-chiptune.ogg"
136 | , "tom-fm.ogg"
137 | , "tom-lofi.ogg"
138 | , "tom-rototom.ogg"
139 | , "tom-short.ogg"
140 | ]
141 |
--------------------------------------------------------------------------------
/elm/Data/Drum.elm:
--------------------------------------------------------------------------------
1 | module Data.Drum exposing (Drum, encode, random)
2 |
3 | import Data.Drum.Kit as DrumKit
4 | import Data.Note as Note exposing (Note)
5 | import Data.Sequence as Sequence exposing (Sequence(..))
6 | import Json.Encode as Encode
7 | import Random exposing (Generator)
8 | import Random.List as RandomList
9 |
10 |
11 | type alias Drum =
12 | { kick : Track
13 | , snare : Track
14 | , hihat : Track
15 | }
16 |
17 |
18 | type alias Track =
19 | { sample : String
20 | , sequence : Sequence
21 | }
22 |
23 |
24 | encode : Drum -> Encode.Value
25 | encode v =
26 | Encode.object
27 | [ ( "kick", encodeTrack v.kick )
28 | , ( "snare", encodeTrack v.snare )
29 | , ( "hihat", encodeTrack v.hihat )
30 | ]
31 |
32 |
33 | encodeTrack : Track -> Encode.Value
34 | encodeTrack v =
35 | Encode.object
36 | [ ( "sample", Encode.string v.sample )
37 | , ( "sequence", Sequence.encode v.sequence )
38 | ]
39 |
40 |
41 | random : Generator Drum
42 | random =
43 | Random.map3 Drum
44 | randomKick
45 | randomSnare
46 | randomHihat
47 |
48 |
49 | randomSeq : (Sequence -> Float -> Sequence) -> Generator String -> Generator Track
50 | randomSeq handler =
51 | Random.andThen
52 | (\note ->
53 | Random.map (Track note)
54 | (Random.float 0 1
55 | |> Random.andThen (handler (Single (Note note "16n" 1)) >> Random.constant)
56 | )
57 | )
58 |
59 |
60 | m : List Sequence -> Sequence
61 | m =
62 | Multiple
63 |
64 |
65 | x : Sequence
66 | x =
67 | Silence
68 |
69 |
70 | randomKick : Generator Track
71 | randomKick =
72 | DrumKit.randomKick
73 | |> randomSeq
74 | (\h prob ->
75 | if prob < 0.2 then
76 | m [ h, x ]
77 |
78 | else if prob < 0.4 then
79 | m
80 | [ m [ h, x, x, h ]
81 | , m [ x, x, h, x ]
82 | , m [ h, x, x, x ]
83 | , x
84 | ]
85 |
86 | else if prob < 0.6 then
87 | m
88 | [ m [ h, x, x, x ]
89 | , m [ x, x, x, h ]
90 | , m [ x, x, h, x ]
91 | , x
92 | ]
93 |
94 | else if prob < 0.8 then
95 | m
96 | [ m [ h, h, h ]
97 | , x
98 | , h
99 | , x
100 | ]
101 |
102 | else
103 | m [ h ]
104 | )
105 |
106 |
107 | randomSnare : Generator Track
108 | randomSnare =
109 | DrumKit.randomSnare
110 | |> randomSeq
111 | (\h prob ->
112 | if prob < 0.5 then
113 | m [ x, h ]
114 |
115 | else if prob < 0.75 then
116 | m [ x, h, x, h, x, h, x, m [ h, h ] ]
117 |
118 | else if prob < 0.75 then
119 | m [ x, h, x, h, x, h, x, m [ h, m [ h, h ] ] ]
120 |
121 | else
122 | m [ x, h, x, h, x, h, x, m [ h, m [ h, h, x, h ] ] ]
123 | )
124 |
125 |
126 | randomHihat : Generator Track
127 | randomHihat =
128 | DrumKit.randomHihat
129 | |> randomSeq
130 | (\h prob ->
131 | if prob > 0.7 then
132 | m [ h, h, x, h ]
133 |
134 | else if prob > 0.5 then
135 | m [ m [ h, h ] ]
136 |
137 | else
138 | m [ h ]
139 | )
140 |
--------------------------------------------------------------------------------
/js/instruments.js:
--------------------------------------------------------------------------------
1 | const bass = {
2 | envelope: {
3 | attack: 0.1,
4 | decay: 0.3,
5 | release: 2
6 | },
7 | filterEnvelope: {
8 | attack: 0.001,
9 | decay: 0.01,
10 | sustain: 0.5,
11 | octaves: 2.6
12 | }
13 | };
14 |
15 | const brass = {
16 | portamento: 0.01,
17 | oscillator: {
18 | type: "sawtooth"
19 | },
20 | filter: {
21 | Q: 2,
22 | type: "lowpass",
23 | rolloff: -24
24 | },
25 | envelope: {
26 | attack: 0.1,
27 | decay: 0.1,
28 | sustain: 0.6,
29 | release: 0.5
30 | },
31 | filterEnvelope: {
32 | attack: 0.05,
33 | decay: 0.8,
34 | sustain: 0.4,
35 | release: 1.5,
36 | baseFrequency: 2000,
37 | octaves: 1.5
38 | }
39 | };
40 |
41 | const cello = {
42 | harmonicity: 3.01,
43 | modulationIndex: 14,
44 | oscillator: {
45 | type: "triangle"
46 | },
47 | envelope: {
48 | attack: 0.2,
49 | decay: 0.3,
50 | sustain: 0.1,
51 | release: 1.2
52 | },
53 | modulation: {
54 | type: "square"
55 | },
56 | modulationEnvelope: {
57 | attack: 0.01,
58 | decay: 0.5,
59 | sustain: 0.2,
60 | release: 0.1
61 | }
62 | };
63 |
64 | const drumSamples = [
65 | "clap-808.ogg",
66 | "clap-analog.ogg",
67 | "clap-crushed.ogg",
68 | "clap-fat.ogg",
69 | "clap-slapper.ogg",
70 | "clap-tape.ogg",
71 | "cowbell-808.ogg",
72 | "crash-808.ogg",
73 | "crash-acoustic.ogg",
74 | "crash-noise.ogg",
75 | "crash-tape.ogg",
76 | "hihat-808.ogg",
77 | "hihat-acoustic01.ogg",
78 | "hihat-acoustic02.ogg",
79 | "hihat-analog.ogg",
80 | "hihat-digital.ogg",
81 | "hihat-dist01.ogg",
82 | "hihat-dist02.ogg",
83 | "hihat-electro.ogg",
84 | "hihat-plain.ogg",
85 | "hihat-reso.ogg",
86 | "hihat-ring.ogg",
87 | "kick-808.ogg",
88 | "kick-acoustic01.ogg",
89 | "kick-acoustic02.ogg",
90 | "kick-big.ogg",
91 | "kick-classic.ogg",
92 | "kick-cultivator.ogg",
93 | "kick-deep.ogg",
94 | "kick-dry.ogg",
95 | "kick-electro01.ogg",
96 | "kick-electro02.ogg",
97 | "kick-floppy.ogg",
98 | "kick-gritty.ogg",
99 | "kick-heavy.ogg",
100 | "kick-newwave.ogg",
101 | "kick-oldschool.ogg",
102 | "kick-plain.ogg",
103 | "kick-slapback.ogg",
104 | "kick-softy.ogg",
105 | "kick-stomp.ogg",
106 | "kick-tape.ogg",
107 | "kick-thump.ogg",
108 | "kick-tight.ogg",
109 | "kick-tron.ogg",
110 | "kick-vinyl01.ogg",
111 | "kick-vinyl02.ogg",
112 | "kick-zapper.ogg",
113 | "openhat-808.ogg",
114 | "openhat-acoustic01.ogg",
115 | "openhat-analog.ogg",
116 | "openhat-slick.ogg",
117 | "openhat-tight.ogg",
118 | "perc-808.ogg",
119 | "perc-chirpy.ogg",
120 | "perc-hollow.ogg",
121 | "perc-laser.ogg",
122 | "perc-metal.ogg",
123 | "perc-nasty.ogg",
124 | "perc-short.ogg",
125 | "perc-tambo.ogg",
126 | "perc-tribal.ogg",
127 | "perc-weirdo.ogg",
128 | "ride-acoustic01.ogg",
129 | "ride-acoustic02.ogg",
130 | "shaker-analog.ogg",
131 | "shaker-shuffle.ogg",
132 | "shaker-suckup.ogg",
133 | "snare-808.ogg",
134 | "snare-acoustic01.ogg",
135 | "snare-acoustic02.ogg",
136 | "snare-analog.ogg",
137 | "snare-big.ogg",
138 | "snare-block.ogg",
139 | "snare-brute.ogg",
140 | "snare-dist01.ogg",
141 | "snare-dist02.ogg",
142 | "snare-dist03.ogg",
143 | "snare-electro.ogg",
144 | "snare-lofi01.ogg",
145 | "snare-lofi02.ogg",
146 | "snare-modular.ogg",
147 | "snare-noise.ogg",
148 | "snare-pinch.ogg",
149 | "snare-punch.ogg",
150 | "snare-smasher.ogg",
151 | "snare-sumo.ogg",
152 | "snare-tape.ogg",
153 | "snare-vinyl01.ogg",
154 | "snare-vinyl02.ogg",
155 | "tom-808.ogg",
156 | "tom-acoustic01.ogg",
157 | "tom-acoustic02.ogg",
158 | "tom-analog.ogg",
159 | "tom-chiptune.ogg",
160 | "tom-fm.ogg",
161 | "tom-lofi.ogg",
162 | "tom-rototom.ogg",
163 | "tom-short.ogg"
164 | ];
165 |
166 | const kalimba = {
167 | harmonicity: 8,
168 | modulationIndex: 2,
169 | oscillator: {
170 | type: "sine"
171 | },
172 | envelope: {
173 | attack: 0.001,
174 | decay: 2,
175 | sustain: 0.1,
176 | release: 2
177 | },
178 | modulation: {
179 | type: "square"
180 | },
181 | modulationEnvelope: {
182 | attack: 0.002,
183 | decay: 0.2,
184 | sustain: 0,
185 | release: 0.2
186 | }
187 | };
188 |
189 | const marimba = {
190 | oscillator: {
191 | partials: [1, 0, 2, 0, 3]
192 | },
193 | envelope: {
194 | attack: 0.001,
195 | decay: 1.2,
196 | sustain: 0,
197 | release: 1.2
198 | }
199 | };
200 |
201 | const piano = {
202 | harmonicity: 2,
203 | oscillator: {
204 | type: "amsine2",
205 | modulationType: "sine",
206 | harmonicity: 1.01
207 | },
208 | envelope: {
209 | attack: 0.006,
210 | decay: 4,
211 | sustain: 0.04,
212 | release: 1.2
213 | },
214 | modulation: {
215 | volume: 13,
216 | type: "amsine2",
217 | modulationType: "sine",
218 | harmonicity: 12
219 | },
220 | modulationEnvelope: {
221 | attack: 0.006,
222 | decay: 0.2,
223 | sustain: 0.2,
224 | release: 0.4
225 | }
226 | };
227 |
228 | const steelpan = {
229 | oscillator: {
230 | type: "fatcustom",
231 | partials: [0.2, 1, 0, 0.5, 0.1],
232 | spread: 40,
233 | count: 3
234 | },
235 | envelope: {
236 | attack: 0.001,
237 | decay: 1.6,
238 | sustain: 0,
239 | release: 1.6
240 | }
241 | };
242 |
243 | const wind = {
244 | portamento: 0.0,
245 | oscillator: {
246 | type: "square4"
247 | },
248 | envelope: {
249 | attack: 2,
250 | decay: 1,
251 | sustain: 0.2,
252 | release: 2
253 | }
254 | };
255 |
256 | export default {
257 | bass,
258 | brass,
259 | cello,
260 | drumSamples,
261 | kalimba,
262 | marimba,
263 | steelpan,
264 | piano,
265 | wind
266 | };
267 |
--------------------------------------------------------------------------------
/elm/Main.elm:
--------------------------------------------------------------------------------
1 | module Main exposing (main)
2 |
3 | import Array exposing (Array)
4 | import Browser
5 | import Data.Drum as Drum exposing (Drum)
6 | import Data.Instrument as Instrument exposing (Instrument)
7 | import Data.Scale as Scale
8 | import Data.Sequence as Sequence exposing (Sequence(..))
9 | import Data.Track as Track exposing (Track)
10 | import Html exposing (..)
11 | import Html.Attributes as HA exposing (..)
12 | import Html.Events exposing (..)
13 | import Json.Encode as Encode
14 | import Ports
15 | import Random exposing (Generator)
16 | import Random.Array as RandomArray
17 | import Random.List as RandomList
18 | import Time exposing (Posix)
19 |
20 |
21 | type alias Model =
22 | { bpm : Int
23 | , status : Status
24 | , tracks : List Track
25 | , scale : List String
26 | }
27 |
28 |
29 | type Status
30 | = Idle
31 | | Loading
32 | | Ready
33 | | Playing
34 |
35 |
36 | type Msg
37 | = Bar Float
38 | | NewDrumTracks Drum
39 | | NewScale (List String)
40 | | NewTrack Track
41 | | Play
42 | | SetReady Bool
43 | | SetBpm Int
44 | | ShouldVary ( Bool, Bool )
45 | | Stop
46 | | VaryDrums
47 | | VaryScale
48 |
49 |
50 | init : () -> ( Model, Cmd Msg )
51 | init _ =
52 | ( { bpm = 120
53 | , status = Idle
54 | , tracks = []
55 | , scale = Scale.first
56 | }
57 | , Random.int 80 100 |> Random.generate SetBpm
58 | )
59 |
60 |
61 | generateDrums : Cmd Msg
62 | generateDrums =
63 | Random.generate NewDrumTracks Drum.random
64 |
65 |
66 | generateTonalInstruments : Model -> Cmd Msg
67 | generateTonalInstruments model =
68 | Cmd.batch
69 | [ Track.random
70 | { id = "low"
71 | , bars = Just 4
72 | , instrument = Just Instrument.Piano
73 | , octaves = Just ( 2, 2 )
74 | , pan = Just 0
75 | , probability = Just 0.95
76 | , scale = model.scale
77 | , volume = Just -8
78 | }
79 | |> Random.generate NewTrack
80 | , Track.random
81 | { id = "mid"
82 | , bars = Just 6
83 | , instrument = Just Instrument.Cello
84 | , octaves = Just ( 3, 3 )
85 | , pan = Just -0.2
86 | , probability = Just 0.9
87 | , scale = model.scale
88 | , volume = Just -14
89 | }
90 | |> Random.generate NewTrack
91 | , Track.random
92 | { id = "high"
93 | , bars = Just 8
94 | , instrument = Just Instrument.Marimba
95 | , octaves = Just ( 4, 4 )
96 | , pan = Just 0.2
97 | , probability = Just 0.8
98 | , scale = model.scale
99 | , volume = Just -12
100 | }
101 | |> Random.generate NewTrack
102 | , Track.random
103 | { id = "other"
104 | , bars = Just 12
105 | , instrument = Just Instrument.Kalimba
106 | , octaves = Just ( 5, 5 )
107 | , pan = Just 0.4
108 | , probability = Just 0.7
109 | , scale = Scale.penta
110 | , volume = Just -16
111 | }
112 | |> Random.generate NewTrack
113 | ]
114 |
115 |
116 | percentChance : Int -> Random.Generator Bool
117 | percentChance odds =
118 | Random.map (\n -> n < odds) (Random.int 1 100)
119 |
120 |
121 | update : Msg -> Model -> ( Model, Cmd Msg )
122 | update msg model =
123 | case msg of
124 | Bar _ ->
125 | ( model
126 | , Random.pair (percentChance 10) (percentChance 80)
127 | |> Random.generate ShouldVary
128 | )
129 |
130 | NewDrumTracks drumTracks ->
131 | ( model, drumTracks |> Drum.encode |> Ports.setDrumTracks )
132 |
133 | NewTrack track ->
134 | ( { model
135 | | tracks =
136 | model.tracks
137 | |> List.filter (.id >> (/=) track.id)
138 | |> (::) track
139 | }
140 | , track |> Track.encode |> Ports.setTrack
141 | )
142 |
143 | NewScale scale ->
144 | let
145 | newModel =
146 | { model | scale = scale }
147 | in
148 | ( newModel, generateTonalInstruments newModel )
149 |
150 | Play ->
151 | case model.status of
152 | Idle ->
153 | ( { model | status = Loading }, Ports.load () )
154 |
155 | Ready ->
156 | ( { model | status = Playing }, Ports.start () )
157 |
158 | _ ->
159 | ( model, Cmd.none )
160 |
161 | SetReady _ ->
162 | ( { model | status = Playing }
163 | , Cmd.batch
164 | [ generateDrums
165 | , generateTonalInstruments model
166 | , Ports.start ()
167 | ]
168 | )
169 |
170 | SetBpm bpm ->
171 | ( { model | bpm = bpm }, Ports.setBpm bpm )
172 |
173 | ShouldVary ( varyDrums, varyScale ) ->
174 | ( model
175 | , Cmd.batch
176 | [ if varyDrums then
177 | generateDrums
178 |
179 | else
180 | Cmd.none
181 | , if varyScale then
182 | Scale.randomNext model.scale
183 | |> Random.generate NewScale
184 |
185 | else
186 | Cmd.none
187 | ]
188 | )
189 |
190 | Stop ->
191 | ( { model | status = Ready }, Ports.stop () )
192 |
193 | VaryDrums ->
194 | ( model, generateDrums )
195 |
196 | VaryScale ->
197 | ( { model | tracks = [] }
198 | , Scale.randomNext model.scale
199 | |> Random.generate NewScale
200 | )
201 |
202 |
203 | viewTrack : Track -> Html Msg
204 | viewTrack track =
205 | let
206 | viewSequence seq =
207 | case seq of
208 | Silence ->
209 | li [] [ text "silence" ]
210 |
211 | Single { pitch } ->
212 | li [] [ text pitch ]
213 |
214 | Multiple subSeq ->
215 | subSeq |> List.map viewSequence |> ul []
216 | in
217 | div [ style "float" "left" ]
218 | [ div [] [ text track.id ]
219 | , div [] [ track.instrument |> Instrument.toString |> text ]
220 | , div [] [ text (String.fromFloat track.pan) ]
221 | , div [] [ text (String.fromFloat track.volume) ]
222 | , div [] [ viewSequence track.sequence ]
223 | ]
224 |
225 |
226 | view : Model -> Html Msg
227 | view model =
228 | div []
229 | [ div []
230 | [ label []
231 | [ text "BPM"
232 | , input
233 | [ type_ "range"
234 | , HA.min "70"
235 | , HA.max "160"
236 | , value <| String.fromInt model.bpm
237 | , onInput (String.toInt >> Maybe.withDefault 120 >> SetBpm)
238 | ]
239 | []
240 | , text (String.fromInt model.bpm)
241 | ]
242 | ]
243 | , div []
244 | [ case model.status of
245 | Idle ->
246 | button [ onClick Play ] [ text "▶" ]
247 |
248 | Loading ->
249 | button [ disabled True ] [ text "▶" ]
250 |
251 | Ready ->
252 | button [ onClick Play ] [ text "▶" ]
253 |
254 | Playing ->
255 | span []
256 | [ button [ onClick Stop ] [ text "◼" ]
257 | , button [ onClick VaryDrums ] [ text "Vary drums" ]
258 | , button [ onClick VaryScale ] [ text "Vary notes" ]
259 | ]
260 | ]
261 | , model.tracks
262 | |> List.map viewTrack
263 | |> div []
264 | ]
265 |
266 |
267 | subscriptions : Model -> Sub Msg
268 | subscriptions model =
269 | Sub.batch
270 | [ Ports.ready SetReady
271 | , Ports.bar Bar
272 | ]
273 |
274 |
275 | main : Program () Model Msg
276 | main =
277 | Browser.element
278 | { init = init
279 | , update = update
280 | , view = view
281 | , subscriptions = subscriptions
282 | }
283 |
--------------------------------------------------------------------------------