GithubHelp home page GithubHelp logo

leipzig's Introduction

Build Status

A composition library for Clojure and Clojurescript by @ctford.

Use

Include it as a dependency in your project.clj, along with Overtone:

[overtone "0.9.1"]
[leipzig "0.10.0"]

Leiningen template

There is a Leiningen template that creates a simple Leipzig project, ready to run.

Get started

Leipzig models music as a sequence of notes, each of which is a map. They are ordered by :time:

[{:time 0
  :pitch 67
  :duration 2
  :part :melody}
 {:time 2
  :pitch 71
  :duration 2
  :part :melody}]

When using Leipzig, it's helpful to keep this model of notes in mind. Leipzig's functions are convenient ways of manipulating this basic structure, but there's nothing to stop you from building your own.

You can create a melody with the phrase function. Here's a simple melody:

(require '[leipzig.melody :refer [all bpm is phrase tempo then times where with]])

(def melody
         ; Row,  row,  row   your  boat
  (phrase [3/3   3/3   2/3   1/3   3/3]
          [  0     0     0     1     2]))

The first argument to phrase is a sequence of durations. The second is a sequence of pitches. phrase builds a sequence of notes which we can transform with sequence functions, either from Leipzig or ones from Clojure's core libraries.

To play a melody, first define an arrangement. play-note is a multimethod that dispatches on the :part key of each note, so you can easily define an instrument responsible for playing notes of each part. Then, put the sequence of notes into a particular key and tempo and pass them along to play:

(require '[overtone.live :as overtone]
         '[leipzig.live :as live]
         '[leipzig.scale :as scale])

(overtone/definst beep [freq 440 dur 1.0]
  (-> freq
      overtone/saw
      (* (overtone/env-gen (overtone/perc 0.05 dur) :action overtone/FREE))))

(defmethod live/play-note :default [{midi :pitch seconds :duration}]
  (-> midi overtone/midi->hz (beep seconds)))

(->>
  melody
  (tempo (bpm 90))
  (where :pitch (comp scale/C scale/major))
  live/play)

There's nothing magic about where. It just applies a function to a particular key of each note, like update-in for sequences.

Let's define two other parts to go with the original melody:

(def reply "The second bar of the melody."
         ; Gent -ly  down the stream
  (phrase [2/3  1/3  2/3  1/3  6/3]
          [  2    1    2    3    4]))

(def bass "A bass part to accompany the melody."
  (->> (phrase [1  1 2]
               [0 -3 0])
       (all :part :bass)))

(defmethod live/play-note :bass [{midi :pitch}]
  ; Halving the frequency drops the note an octave.
  (-> midi overtone/midi->hz (/ 2) (beep 0.5)))

You can then put multiple series of notes together:

(->>
  bass
  (then (with bass melody))
  (then (with bass melody reply))
  (then (times 2 bass))
  (tempo (bpm 90))
  (where :pitch (comp scale/C scale/major))
  live/play)

Namespaces

Leipzig features a number of namespaces, each containing functions pertaining to a particular area of composition.

leipzig.melody

This namespace contains the core functions for creating and manipulating melodies. In particular:

  • phrase creates a melody from a sequence of durations and a sequence of pitches.
  • where applies a function to a specified key of each note in a melody.

For example:

(->> (phrase [3/3 3/3 2/3 1/3 3/3] [0 0 0 1 2])
     (where :time inc))

leipzig.live

Here are functions to send your melodies to Overtone:

  • play-note is a multimethod that dispatches on the :part a note has.
  • play plays the notes.
  • jam loops the notes, reloading the var each time.
  • stop stops all running melodies.

For example:

(defmethod live/play-note :melody [{midi :pitch}]
  (some-> midi overtone/midi->hz beep))

(def boring-scale
  (->> (phrase (repeat 1) (range 8))
       (all :part :melody)
       (where :pitch (comp C major))))

(jam (var boring-scale))
; Edits to boring-scale will be played each time we go back round the loop.

leipzig.scale

This namespace contains functions for placing melodies within musical scales. In particular:

  • major and minor are functions that place a pitch within a relative scale.
  • A, B, C etc are functions that take a relative pitch, and place it in a specific absolute key.

For example:

(->> (phrase (repeat 1) (range 8))
     (where :pitch (comp C major)))

leipzig.chord

The phrase function accepts chords as well as simple pitches. This namespace provides simple ways to manipulate them:

  • triad is the tonic, which can be manipulated to form other chords.
  • root scales the chord up to the specified root.
  • inversion inverts the chord, leaving the root where it is.

For example, a fourth chord, then the second inversion of the fifth:

(phrase
  [4 4]
  [(-> triad (root 3))
   (-> triad (inversion 2) (root 4))])

leipzig.temperament

This namespace translates midi pitches into frequencies. Overtone's midi->hz will usually do just fine, but if you want to experiment with more exotic temperaments, there are plenty here.

In particular:

  • equal is equivalent to midi->hz and translates frequencies into pitches like a piano is tuned.
  • just uses pure ratios, and more closely models how singers interpret intervals into frequencies.

For example:

(->> (phrase (repeat 1) (range 8))
     (where :pitch (comp just C major)))

Advanced use

In addition to simple pitches, phrase can take maps representing chords or nils:

(require '[leipzig.chord :as chord])

(def chords "Off-beat chords."
  (->> (phrase (repeat 1/2)
               [nil chord/triad
                nil (-> chord/seventh (chord/root 4) (chord/inversion 1) (dissoc :v))
                nil chord/triad
                nil chord/triad])
       (all :part :chords)))

The maps generate a note for each value in the map - the keys are used only to enable chord-transforming functions such as root and inversion.

The nils generate notes without pitches, representing rests. This is convenient, because it allows melodies to have a duration extending beyond their last audible note. However, the play-note implementations and where invocations must be prepared to handle this, e.g. by using when and where's variation wherever:

(require '[leipzig.melody :refer [wherever]]
         '[leipzig.scale :refer [lower]])

(defmethod live/play-note :chords [{midi :pitch}]
  (when midi (-> midi overtone/midi->hz beep)))

(->>
  (times 2 chords)
  (wherever :pitch, :pitch lower)
  (with (->> melody (then reply)))
  (tempo (bpm 90))
  (where :pitch (comp scale/C scale/major))
  live/play)

Clojurescript

Leipzig supports Clojurescript for all of its namespaces save leipzig.live. The live namespace depends directly on Overtone, so it cannot be used in the browser. However the rest of Leipzig can be used so long as an alternative synthesis engine is present like the Web Audio API. Klangmeister is a good example of this.

Examples

See Row, row, row your boat or whelmed for examples.

In Leipzig from scratch, I demonstrate how to create a piece from lein new onwards.

Leipzig came out of a talk I gave called Functional Composition, where I explain basic music theory using Overtone and Clojure.

API

API documentation, generated by Codox.

Design

Leipzig is designed to play nicely with Clojure's standard sequence functions. Therefore, Leipzig's functions for transforming notes all take the sequence as a final argument so that they can be threaded with the ->> macro:

(->>
  (phrase (repeat 1) (cycle [0 2 4]))
  (take 24)
  (filter #(-> % :time even?)))

These sequence functions all exhibit "closure" i.e. their result is the same shape as their input. That allows them to be used and combined very flexibly. where for example, can raise the pitch, set the part or put the notes into a particular tempo:

(->> notes (where :pitch inc))
(->> notes (where :time (bpm 90)))

Leipzig aims to be a library rather than a framework or environment. It uses simple Clojure datastructures and strives to be as open as possible. A new timing scheme, tuning or tempo can be mixed with Leipzig's other functions just as easily as the ones that come with the library.

Testing

To run the unit tests without having to start Overtone's Supercollider server:

lein midje leipzig.test.*

Issues

As pointed out by @clojens, leipzig.live imports overtone.live, which implicitly boots an internal Supercollider server and can cause problems for folks using 64 bit Windows.

leipzig's People

Contributors

ctford avatar dfannius avatar ethancrawford avatar namin avatar neatonk avatar philandstuff avatar samaaron avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

leipzig's Issues

how to implement retrograde?

I'm not sure if this is an issue or just my misunderstanding. I'm trying to implement a "retrograde" function that will reverse a melody. I think I should be able to do something along the lines of (->> the-phrase crab (simple the-phrase-length)), but that doesn't quite work. I hope you can set me straight here.

Consider a simple example of a whole note followed by two half notes:

> ((lc/simple (- 2 0.5)) (lc/crab (lm/phrase [1 0.5 0.5] [4 5 6])))
({:pitch 4, :duration 1, :time 1.5} 
 {:pitch 5, :duration 0.5, :time 0.5} 
 {:pitch 6, :duration 0.5, :time 0.0})

That almost works. Note 6 & 5 start at 0 and 0.5, but Note 4 is at time 1.5? Shouldn't it be at time 1?

I drew this out and it finally made sense to me. I think there is a missing step

;;  -2  -1   0   1   2
;; --|-+-|-+-|-+-|-+-|
;;           AaaaBbCc = start with whole note + 2 half-notes
;;    cCbBaaaA        = crab?  no...we don't play notes backwards
;;     CcBb  Aaaa     = actual crab result when notes actual durations
;;   CcBbAaaa         = a missing step: offset-each-by-duration
;;           CcBbAaaa = (simple 2) puts melody in proper location

Do you agree there's an issue? Or is this just a misunderstanding on my part?

Thanks in advance...

--Roger

[question] Stringed instruments?

Hi there, I am really digging this library but I am having a bit of a hard time using it with stringed instruments. I tried to write an example based on the overtone stringed synth examples here but it is not clear to me how I could express the chords in a more elegant way. Any ideas or suggestions?

Is clojure / clojurescript dep really needed?

When using Leipzig, I'm noticing that deps are rather old and include both Clojure and Clojurescript. Maybe they both could be a :provided dependency, so you don't get them when consuming the library?

[Feature Request] Velocity support

Velocity is also an important part of music. It would be great if notes were represented like this:

{:time 0
:pitch 67
:velocity 127
:duration 2
:part :melody}

Windows 64bit and overtone.live

Hi,

Just to inform you, something I faced a bit earlier when trying to setup Overtone in LightTable on Windows (8) 64bit. Normally I run a ArchLinux setup in virtualbox guest on this machine but due to a cracking sound, I had to switch back to the host Win. The problem I faced was java.lang.NoClassDefFoundError: Could not initialize class com.sun.jna.NativeLong exceptions thrown and the solution was found in https://groups.google.com/forum/#!topic/overtone/orbm9ZEtZaE group.

Basically what it boils down to, from what I understand, is that overtone.live can't be used/called in Windows 64bit. It is not just limited to LightTable, lein repl throws the same. Falling back to overtone.core and then using (boot-external-server) everything run smoothly though and, from what I read, seems to be the prefered method of dealing with this issue.

Perhaps something to take into account for leipzig as I noticed you made use of it in leipzig.live and mention it in the how-to of the README file.

Support CLJS

Leipzig's dependence on Overtone is very slim - at and now. If we provide equivalents for CLJS, then Leipzig should be able to support music in the browser, using web audio instead of Overtone synths.

All tracks return future pending map

I'm seeing the identical problem reported in this google group chat:

https://groups.google.com/g/overtone/c/jF_9U-MkEBI

Everything on the overtone and leipzig side is working with no errors (all functions return a value). However, the (live/play) call returns a Future which includes a map that claims the track is :pending. This happens both in the repl and when C-c inside of a cider buffer. The example i'm using is the leipzig from scratch file, so this should work. Thanks so much and I'm looking forward to using Leipzig.

insane-noises.leiptest> (live/play track)
#<Future@4dd38521: :pending>

`leipzig.melody/then` argument order

as in below example

(->>
  (phrase [3/3 3/3 2/3 1/3 3/3] ; Can you recognise the tune yet?
          [ 72  72  72  74  76])
  (then
    (phrase [2/3 1/3 2/3 1/3 3/3]
            [ 76  74  76  77  79])))

it becomes below with out the ->> macro

(then 
  (phrase [2/3 1/3 2/3 1/3 3/3]
            [ 76  74  76  77  79])
  (phrase [3/3 3/3 2/3 1/3 3/3] ; Can you recognise the tune yet?
          [ 72  72  72  74  76]))

I find it pretty weird. why the phrase played later comes first?

if we reverse the order of argument then can take phrase in order without using ->>
or if using threading macro is desired, then can use -> macro.

I wonder why it is made like this currently and if you want to change it? I could try to pull off pull request if you want to change it.

Thanks.

Duration messes up when the last note finishes before another

melody/duration measures the duration of a melody.

This goes wrong if e.g. the second-last note is long enough that it lasts beyond the last note e.g. [{:time 0 :duration 4 :pitch 0} {:time 1 :duration 1 :pitch 2}]. We need to use a max strategy rather than just looking at the final note (which we can only rely on starting last).

Full stop

Was playing around with leipzig again this evening and found myself wanting a "full stop" helper.

I noticed I can capture the play future and stop it like this:

(def player (->> drums (times 4) play))
(future-cancel player)

But I get the feeling something like

(leipzig.live/stop)

that canceled all play futures could be handy if you happen to kick off a somewhat long sequence.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.