Code Monkey home page Code Monkey logo

miti's Introduction

Version

miti is a musical instrument textual interface. Basically, its MIDI, but textual.

miti provides a program and musical notation that you can use to easily connect and control sounds in a very human way. It provides surprisingly simple sequencing for synthesizers or other instruments, namely control from your favorite text editor.

Features

  • Control one/many external/virtual MIDI devices simultaneously
  • Sequence single notes or chords, at any subdivision
  • Low latency (~2 ms) and low jitter (~2 ms, with rare spikes of 10-15 ms)
  • Real-time editing of sequences using any text editor
  • Sequences specified using human-readable text
  • Compatible with Windows, macOS, Linux, Raspberry Pis

Demos

Demo of playing

Demo of playing

Install

The easiest way to install is to download the latest release.

Its very easy to install from the source code too. To install from the source code, first install portmidi, following these directions.

Next install Go and then in a terminal:

> go install github.com/schollz/miti

Optional: If you want to input chord names then you need to also download and instal LilyPond on your system. (Here are instructions for installing on a Raspberry Pi).

That's it! miti is now available from the command-line.

Documentation

Quickstart

You don't need to be familiar with command-lines to get started. Simply plug in your instruments (or virtual instruments), and double-click on the miti program to get started. It will load up the default .miti file in the default text editor and start playing!

Getting started playing music

To get started, first plugin your instruments to your computer. Open a command prompt and type miti --list to see which instruments are available to you.

> miti --list
Available MIDI devices:
- midi through port-0
- nts-1 digital kit midi 1

You can then use these instruments to make a simple sequence. Make a new file called first.miti with the following:

pattern 1

instruments nts-1
C D E F G A B C

Make sure you replace nts-1 with the name of your MIDI device!

Also, note that I did not write out the full MIDI device for the instrument. miti will accept any part of the device name and map it to the correct device. So in that example it will accept nts-1 in place of writing nts-1 digital kit midi 1.

Now to play this sequence you can just do:

> miti --play first.miti
[info]  2020/07/17 08:18:12 playing

And you'll hear some music!

Getting started making sequences

You can make a sequence using any text editor you want. To get started quickly, though, you can record a sequence using your MIDI keyboard. Just plug in a keyboard and type:

> midi --record song2.miti 
Use MIDI keyboard to enter notes
Press . to enter rests
Press p to make new pattern
Press m to make new measure
Press backspace to delete last
Press Ctl+C to quit

Then you can just play chords and notes on your MIDI keyboard and it will generate the sequence. When you are done with a measure, just press m to start a new one. When you are done with a pattern, just press p to start a new one. If you are finished, press Ctl+C to finish and write the file to disk.

Once the sequence is written, you can play it and edit it as much as you want.

Basic pattern

miti reads a .miti file, which is a high-level musical notation developed for miti. The musical notation is simple and powerful, allowing you to create patterns of notes that can be played on many instruments simultaneously.

The basic unit is the pattern. A pattern contains a collection of instruments. Each instrument contains a collection of notes. For example, here is a simple pattern that plays Cmaj followed by Fmaj and then repeats.

pattern 1
instruments <instrument1>
CEG
FAC

The pattern 1 designates the pattern name, "1". This pattern has a single instrument, <instrument1>. The instrument name must be contained in the official MIDI instrument name, case insensitive. For example, "nts-1" is a viable name if the MIDI instrument is "NTS-1 digital kit 1 SOUND."

Each line under instruments designates a different measure. This is where you put notes. Notes without spaces are considered a chord and will be played simultaneously. The first measure plays C, E, and G (C major) and the second measure plays F, A and C (F major). This pattern will repeat indefinitely when played.

To add more patterns, simply add a line with pattern X and again add instruments and their notes. The patterns will be played in order.

Add instruments and subdivisions

You can easily add a second instrument to this section by adding another line with the instrument name:

pattern 1 
instruments <instrument1>
CEG 
FAC
instruments <instrument2>
A F E C A F E C

This second instrument will play arpeggios. It consists of a single repeated measure which eight notes. Since each note is separated by a space, they are not played together as a chord (unlike in instrument1) and are automatically subdivided according to the number of notes in that measure. In this case they are subdivided into 1/8th notes since there are eight of them in that measure. Since there is only one measure for the instrument2, it will repeat over every chord of instrument1.

You can add rests in using . to create specific subdivisions. For example, we can change instrument1 to play chords on the off-beat of beat 1 and beat 2:

instruments <instrument1>
. CEG . . . CEG . . 
. FAC . . . FAC . . 

Adding comments

You can add in comments into the .miti file by putting a # in the beginning of the line:

# this is a comment
pattern 1 

Chain patterns

If you have multiple patterns you can chain them together in any order using chain. The order will repeat once it gets to the end. For example, this repeats the first pattern followed by 5 of the second pattern:

chain a b b b b b

pattern a
CEG

pattern b 
DFA

Specifying octave of note

By default, the note played will be the note closest to the previous. If you want to specify the exact note you can add a suffix to include the octave. For example, instead of writing CEG you could instead write C3E4G5 which will span the chord over three octaves.

Setting the tempo

You can add a line to change the tempo, anywhere in the file.

tempo <10-300>

Changing the legato

The legato specifies how much to hold each note until releasing it. Full legato (100) holds to the very end, while the shortest legato (1) will release immediately after playing.

legato <1-100>

Sustain

For a pedal note (sustain) add a * to the end of the note. For example, the following will sustain a C major chord for two measures:

CEG- 
CEG

This next example shows how to hold out a C major chord for three beats and resting on the fourth beat:

CEG- CEG- CEG .

Multiple instruments

You can assign multiple instruments to a pattern by separating each instrument by a comma. For example:

instruments <instrument1>, <instrumnet2>
C E G

will play the C, E, G arpeggio on both instruments 1 and 2.

Chord names

Note: Inputting chord names directly requires first downloading and installing LilyPond.

To directly use chords, you can use the semicolon operator flanking the chord name. For instance, here are two C major chords followed by two A minor chords:

:C :C :Am :Am

If you want to alter the chord octave or add sustain, you do same as before but add another semicolon operator on the right side. In this example, the C major chord is played on the 3rd octave and held out for two beats using a sustain (- suffix):

:C:3- :C:3 :Am :Am

Chords can get pretty complex, and they should be understood. For example, you can add chord adjusters:

:Cm7/G

Click track

It's useful to get a click track going to be used to sync audio equip. miti will output a click track on the default audio using the --click track and can be lagged (if needed) by setting --clicklag.

Other similar work

  • textbeat is a text-based musical notation to do complex sequences using a columnated workflow.
  • helio-workstation is a simplified GUI based sequencer.
  • lilypond is a GUI based sequencer and musical notation software.
  • foxdot is a Python + SuperCollider music environment.
  • Sonic Pi is a SuperCollider live coding environment.
  • Pure Data is a GUI program that enables music synthesis.
  • Chuck is a music programming language.
  • melrose is a melody programming language.

To Do

  • Add legato control legato: 90
  • Hot-reload file
  • in midi, create a channel for each instrument
  • in midi, each instrument keeps track of which notes are off
  • in midi, accept -1 to turn off all notes
  • in midi, accept -2 to turn off all notes and shut down
  • Add - suffix for adding sustain
  • Easily identify instruments with partial matches (if contains)
  • Allow chaining patterns in different ways chain: a a b b a a
  • allow comments
  • Find source of spurious jitter
  • use portmidi scheduling to further eliminate jitter

License

MIT

miti's People

Contributors

schollz avatar xmikus01 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

miti's Issues

Building on OS X

It took me a minute to get this working on OS X / M1 / Golang 1.19.

Apply this diff fixes #18

diff --git a/src/record/record.go b/src/record/record.go
index 86c31e0..40ab77e 100644
--- a/src/record/record.go
+++ b/src/record/record.go
@@ -33,7 +33,7 @@ func Record(fname string) (err error) {
        patterns := 0
        currentPattern := ""
        currentState := ""
-       previousNote := music.NewNote("C", 4)
+       // previousNote := music.NewNote("C", 4)
        go func() {
                ticker := time.NewTicker(400 * time.Millisecond)
                notes := []midi.Event{}
@@ -69,7 +69,7 @@ func Record(fname string) (err error) {
                                        // } else {
                                        currentState += fmt.Sprintf("%s%d", note.Name, note.Octave)
                                        // }
-                                       previousNote = note
+                                       // previousNote = note
                                }
                                currentState += " "
                                notes = []midi.Event{}
$ go version
go version go1.19.3 darwin/arm64

# upgrade sys fixes compile error
$ go get -u golang.org/x/sys

# install portmidi and export homebrew paths
$ brew install portmidi
$ export CPATH="$HOMEBREW_PREFIX/include:$CPATH"
$ export LIBRARY_PATH="$HOMEBREW_PREFIX/lib:$LIBRARY_PATH"

# it works!
$ go run main.go

[Question]: Working with virtual instruments?

Hi,

Thanks for creating this! I checked out your demo and this is really cool. I like the general idea of a simple language / DSL to create music.

Can this be made with virtual MIDI instruments though? I notice that this seems to only work with external MIDI devices which I don't have readily available so I can't play with this at the moment.

Thanks!

declared but not used error in miti/src/record/record.go

Incidentally, just tried to compile current master using latest golang 1.19.3 and I get 'declared but not used error' on line 36 of miti/src/record/record.go:

patterns := 0
currentPattern := ""
currentState := ""
previousNote := music.NewNote("C", 4) // <--- here
go func() {

[Question] How much jitter is there?

Testing using 1/8th notes at 110 bpm on 3 instruments (nts-1, op-1, sh-01a). Two samples taken while running, for 20 seconds each. The samples were processed, aligned and differed.

Takeaways:

  • little difference between portmidi and rtmidi libraries
  • instrument makes a difference, nts-1 jitters between 0-20 ms, while op-1 jitters between 0-2 ms
  • little difference between one instrument and three simultaneous instruments

Some representative examples:

Measured from the nts-1, one instrument:

image

Measured from the nts-1, three instruments:

image

Measured from the op-1:

image

Measured from the op-1, 1/8th notes at 220 bpm:

image

Measured from the op-1, using rtmidi instead of portmidi:

image

Measured using a song I wrote at 110 bpm:

image

pattern a 

tempo 110
instruments nts-1
legato 1
C1 Eb G C Eb G C Eb 

instruments op-1
legato 50
Bb3D4  G3 Eb- Eb . . . . Bb3Eb4 G3 Eb- Eb . . . .
Bb3Eb4 G3 Eb- Eb . . . . Bb3Eb4 G3 Eb- Eb . . . .
G3C4  Eb3 C- C . . . . G3C4  Eb3 C- C . . . . 
G3C4  Eb3 C- C . . . . G3C4  Eb3 C- C . . . . 

instruments sh-01a
legato 90
Eb3GBb 
D3FBb
C3EbG- 
C3EbG

midi record doesn't work for me

On the latest release, I can't seem to get input from my Arturia Keystep:

λ miti  --record song2.miti
miti v0.6.0-5001293 - musical instrument textual interface
submit bugs to https://github.com/schollz/miti/issues

Use MIDI keyboard to enter notes
Press . to enter rests
Press p to make new pattern
Press m to make new measure
Press backspace to delete last
Press Ctl+C to quit
---------------------------
. . . . . . . . . 


     [ this is me playing notes on my keystep, but no output, hitting `.` and `p` does work]


. 
---------------------------

wrote to 'song2.miti'
λ miti --list
miti v0.6.0-5001293 - musical instrument textual interface
submit bugs to https://github.com/schollz/miti/issues

Available MIDI devices:
- midi through:midi through port-0 14:0
- arturia keystep 32:arturia keystep 32 midi 1 28:0

Specify MIDI channel

I LOVE miti! For me, it's missing one small thing: I'd love to specify the midi channel we're sending to, as an optional extra when selecting an instrument.

pattern 1

instruments op-z
C D E F G A B C

For example, this plays a scale on my OP-Z, but it's sent on one specific midi channel. (I don't know which one. What is the default?)

However, I want to be able to control the different instruments on the OP-Z, eg bass drum is channel 1, snare is channel 2, bass is channel 5, and so on.

pattern 1

instruments op-z 1 # new channel numbering proposal
C C C C # kick drum

instrumens op-z 5 # here too

C G C G # bass line

Is this a possible feature? It'd really open up the project for me personally. I've recently started to learn go, I think I could help if you point me in the right direction!

Panic and segfault on macOS ctrl-C

This is a really cool project! I love the low effort needed to get everything in my studio to make noise!

When I try the demo on macOS, I get a panic and the below stack trace when I ctrl-C to quit. If any notes are playing, they're stuck on:

^C[debug]	07:32:00 play.go:123: interrupt
[info]	2020/08/25 07:32:00 shutting down
[debug]	07:32:00 metronome.go:61: ..ticker stopped!
fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x1 addr=0x320 pc=0x42a4905]

runtime stack:
runtime.throw(0x41221a9, 0x2a)
	/usr/local/go/src/runtime/panic.go:1116 +0x72
runtime.sigpanic()
	/usr/local/go/src/runtime/signal_unix.go:704 +0x48c

goroutine 38 [syscall]:
runtime.cgocall(0x40e8140, 0xc000093950, 0xc000186900)
	/usr/local/go/src/runtime/cgocall.go:133 +0x5b fp=0xc000093920 sp=0xc0000938e8 pc=0x40051db
github.com/schollz/miti/src/portmidi._Cfunc_Pm_Write(0x4402b20, 0xc000158148, 0x1, 0x0)
	_cgo_gotypes.go:297 +0x4d fp=0xc000093950 sp=0xc000093920 pc=0x40dce0d
github.com/schollz/miti/src/portmidi.(*Stream).Write.func1(0xc00009c000, 0xc000158148, 0x1, 0x1, 0x1, 0xc0000939c8)
	/Users/mkb/go/src/github.com/schollz/miti/src/portmidi/stream.go:120 +0x73 fp=0xc000093988 sp=0xc000093950 pc=0x40dded3
github.com/schollz/miti/src/portmidi.(*Stream).Write(0xc00009c000, 0xc0000939f8, 0x1, 0x1, 0x0, 0x40fca20)
	/Users/mkb/go/src/github.com/schollz/miti/src/portmidi/stream.go:120 +0xca fp=0xc0000939c8 sp=0xc000093988 pc=0x40dd74a
github.com/schollz/miti/src/portmidi.(*Stream).WriteShort(0xc00009c000, 0x80, 0x40, 0x0, 0x0, 0x0)
	/Users/mkb/go/src/github.com/schollz/miti/src/portmidi/stream.go:131 +0x85 fp=0xc000093a28 sp=0xc0000939c8 pc=0x40dd845
github.com/schollz/miti/src/midi.Init.func2(0xc0001ae1a8, 0x4, 0xc, 0x3)
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:77 +0x25d fp=0xc000093fc0 sp=0xc000093a28 pc=0x40df5bd
runtime.goexit()
	/usr/local/go/src/runtime/asm_amd64.s:1374 +0x1 fp=0xc000093fc8 sp=0xc000093fc0 pc=0x4068841
created by github.com/schollz/miti/src/midi.Init
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:50 +0x41b

goroutine 1 [chan receive]:
github.com/schollz/miti/src/play.Play(0xc0000a8000, 0x24, 0x4123500, 0x66, 0xc00003ef58)
	/Users/mkb/go/src/github.com/schollz/miti/src/play/play.go:151 +0x5c5
main.main()
	/Users/mkb/go/src/github.com/schollz/miti/main.go:55 +0x218

goroutine 35 [semacquire]:
sync.runtime_SemacquireMutex(0x421a6d4, 0x400c700, 0x1)
	/usr/local/go/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0x421a6d0)
	/usr/local/go/src/sync/mutex.go:138 +0x105
sync.(*Mutex).Lock(...)
	/usr/local/go/src/sync/mutex.go:81
github.com/schollz/miti/src/midi.Init.func2(0xc0001ae160, 0x10, 0x9, 0x0)
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:74 +0xafe
created by github.com/schollz/miti/src/midi.Init
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:50 +0x41b

goroutine 36 [semacquire]:
sync.runtime_SemacquireMutex(0x421a6d4, 0x400c700, 0x1)
	/usr/local/go/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0x421a6d0)
	/usr/local/go/src/sync/mutex.go:138 +0x105
sync.(*Mutex).Lock(...)
	/usr/local/go/src/sync/mutex.go:81
github.com/schollz/miti/src/midi.Init.func2(0xc0001ae178, 0x4, 0xa, 0x1)
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:74 +0xafe
created by github.com/schollz/miti/src/midi.Init
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:50 +0x41b

goroutine 37 [semacquire]:
sync.runtime_SemacquireMutex(0x421a6d4, 0x400c700, 0x1)
	/usr/local/go/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0x421a6d0)
	/usr/local/go/src/sync/mutex.go:138 +0x105
sync.(*Mutex).Lock(...)
	/usr/local/go/src/sync/mutex.go:81
github.com/schollz/miti/src/midi.Init.func2(0xc0001ae190, 0x4, 0xb, 0x2)
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:74 +0xafe
created by github.com/schollz/miti/src/midi.Init
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:50 +0x41b

goroutine 39 [semacquire]:
sync.runtime_SemacquireMutex(0x421a6d4, 0x400c700, 0x1)
	/usr/local/go/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0x421a6d0)
	/usr/local/go/src/sync/mutex.go:138 +0x105
sync.(*Mutex).Lock(...)
	/usr/local/go/src/sync/mutex.go:81
github.com/schollz/miti/src/midi.Init.func2(0xc0001ae1c0, 0x6, 0xd, 0x4)
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:74 +0xafe
created by github.com/schollz/miti/src/midi.Init
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:50 +0x41b

goroutine 40 [semacquire]:
sync.runtime_SemacquireMutex(0x421a6d4, 0x400c700, 0x1)
	/usr/local/go/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0x421a6d0)
	/usr/local/go/src/sync/mutex.go:138 +0x105
sync.(*Mutex).Lock(...)
	/usr/local/go/src/sync/mutex.go:81
github.com/schollz/miti/src/midi.Init.func2(0xc0001ae1d8, 0x2, 0xe, 0x5)
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:74 +0xafe
created by github.com/schollz/miti/src/midi.Init
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:50 +0x41b

goroutine 41 [semacquire]:
sync.runtime_SemacquireMutex(0x421a6d4, 0x400c700, 0x1)
	/usr/local/go/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0x421a6d0)
	/usr/local/go/src/sync/mutex.go:138 +0x105
sync.(*Mutex).Lock(...)
	/usr/local/go/src/sync/mutex.go:81
github.com/schollz/miti/src/midi.Init.func2(0xc0001b0100, 0x12, 0xf, 0x6)
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:74 +0xafe
created by github.com/schollz/miti/src/midi.Init
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:50 +0x41b

goroutine 42 [semacquire]:
sync.runtime_SemacquireMutex(0x421a6d4, 0x400c700, 0x1)
	/usr/local/go/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0x421a6d0)
	/usr/local/go/src/sync/mutex.go:138 +0x105
sync.(*Mutex).Lock(...)
	/usr/local/go/src/sync/mutex.go:81
github.com/schollz/miti/src/midi.Init.func2(0xc0001ae200, 0xf, 0x10, 0x7)
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:74 +0xafe
created by github.com/schollz/miti/src/midi.Init
	/Users/mkb/go/src/github.com/schollz/miti/src/midi/midi.go:50 +0x41b

goroutine 51 [syscall]:
os/signal.signal_recv(0x4143ae0)
	/usr/local/go/src/runtime/sigqueue.go:144 +0x9d
os/signal.loop()
	/usr/local/go/src/os/signal/signal_unix.go:23 +0x25
created by os/signal.Notify.func1.1
	/usr/local/go/src/os/signal/signal.go:150 +0x45

goroutine 52 [sleep]:
time.Sleep(0x2faf080)
	/usr/local/go/src/runtime/time.go:188 +0xbf
github.com/schollz/miti/src/play.Play.func2(0xc0000a2180, 0xc000098048, 0xc0000c0000, 0xc0000be060, 0xc0000be000)
	/Users/mkb/go/src/github.com/schollz/miti/src/play/play.go:128 +0xd9
created by github.com/schollz/miti/src/play.Play
	/Users/mkb/go/src/github.com/schollz/miti/src/play/play.go:120 +0x486

goroutine 53 [select]:
github.com/schollz/miti/src/play.hotReloadFile(0xc0000c0000, 0xc0000a8000, 0x24, 0xc0000be060, 0x0, 0x0)
	/Users/mkb/go/src/github.com/schollz/miti/src/play/play.go:159 +0x125
github.com/schollz/miti/src/play.Play.func3(0xc0000c0000, 0xc0000a8000, 0x24, 0xc0000be060, 0xc000192340)
	/Users/mkb/go/src/github.com/schollz/miti/src/play/play.go:143 +0x4d
created by github.com/schollz/miti/src/play.Play
	/Users/mkb/go/src/github.com/schollz/miti/src/play/play.go:142 +0x54d

I can try to dig more, but my Go and PortMIDI skills are a little rusty and time is short.

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.