mididings-20120419~ds0/0000755000175000017500000000000011744114132014410 5ustar alessioalessiomididings-20120419~ds0/PKG-INFO0000644000175000017500000000036311744114132015507 0ustar alessioalessioMetadata-Version: 1.0 Name: mididings Version: 20120419 Summary: a MIDI router/processor Home-page: http://das.nasophon.de/mididings/ Author: Dominic Sacre Author-email: dominic.sacre@gmx.de License: GPL Description: UNKNOWN Platform: UNKNOWN mididings-20120419~ds0/doc/0000755000175000017500000000000011347254021015156 5ustar alessioalessiomididings-20120419~ds0/doc/livedings.html0000644000175000017500000000307011745112742020035 0ustar alessioalessio mididings - livedings

mididings - livedings

livedings

livedings is a graphical frontend for mididings that allows you to monitor and trigger scene changes. It runs as a separate application that uses OSC to communicate with mididings. To use it, enable the OSCInterface hook in your mididings script:

from mididings.extra.osc import OSCInterface

hook(OSCInterface())
...

Then run livedings:

$ livedings

By default, livedings uses the standard Tk theme. Specify the -T option to switch to a custom theme with higher contrast and larger fonts. See 'livedings --help' for more options.

The buttons at the bottom of the screen can be used to switch to the previous/next scene, previous/next subscene, and to send all-notes-off messages (panic) on all output ports. It's also possible to use the arrow keys to switch scenes (up/down) and subscenes (left/right).

mididings-20120419~ds0/doc/velocity.png0000644000175000017500000004075111330660034017526 0ustar alessioalessioPNG  IHDRi-sRGB pHYs : :dJtIME |"'tEXtComment̖ IDATxw\TgCt JDQ 5آX$1n6M$nԍX `AXcRRf 럄̙<:<c   A!   d#!&^.@9|t(..=0cF0D)-ҥkpww  H[- m sUU՘0!AA$(WHZVP@ Y41Q U ő@c  AVc.\+zLA$j,31?zLA$j/˗AAr ŋWI9BMT=}0(T0T!E( aȐ!())Q9>pqŋUEc;v'ŋ#**BX;X/^[bŊذaqw,_>丕TVV iii8wFR>89uLJΣ:tǏaffFA!Z$//(--۷FA!Z$&&FBQPTv c۶m_~)))$DFZZ ,,LeƢ`aٲeF~~>SPOPXXj,[-RQQ0@ZZSPH#)) cƌBBBpMcY Dc3...6ú֓ ##z3 ,7oD@@,,,퍢" ЮC,"""0k,cС*+5_sòe˰yf=z]Yxxx Bb|}}ܽ{AH.S<°i& @˼qĥKH1AA h{LA$j#={:Bc P3A?~@P 3[jbNp,s=hD"*+044{!U/o$=n;]Ǒ#Q`!++aիJٹذaKz2deeGj׸{ޔqr"cطDk֬3 I/{{{+eKaoԗ ܸW`ff> $SΨֽ;w~~1m l2'O  .] ** QRR;wݻwرKTө5V\(񒜿 Ϟ=&D"qbb.ZDՑ3,,:J7_ ɓ'DL`uXZZB,s7ff p{䍪5Dnnnܸ翆tuQPP vuG)%/_gcKo ggg<~{ nnn044͛7۽$k9* ypuʱtuuev7?֬\7H2eԕ=zȨ]sgyilڼQF92ܼy)x"``/`k׼Q{2QTg\ĕ+IDpt ̘,5QCnU# 5k1tPo˗q]kSe7˖-͛D*,Gs-WK%ə捇LMMOwDVy7oݻdHMMŋWq}"XZǧ/=!U!''w<w'K޴%gC5°i&jt8:n@$"""K䳛ȨћZ4 qP_D=737*P]]7Rp5dffC[[>>0tɛɁ1>B[1?>VVx)aeeݻOյL P$-ȱ,'&V=&9VmAD~~> °}v|GN0o<@bb"Μ9CA$Z$55їp#@^=?x+8 ss3xxR ՘RDG_7  ѿo "VpmW`2&dcczLrhhh>Æ mwww  } MsDFǟ>.F?hrlL2&SsGPP :uzaC97W∞=HIIebk֬+q)Swx"#/ɓ4`„  8`/ QY)ٳq8>UUtCPP :w=ԩW`„2Fd7H NP11ŋ$8p'On'|DΞ})j$%ɓ琗]d,89uΟOz>(dcKzLr~L:oc 8tN8px{{#** BChhhٙ3HJ CCL__WvjJ qf ̔`:toޔO^x->`5'o***۽VpV?8Eђ9sg\.f͚__VKܒWV p89urP _bHKˀ>`*ܜ$ʿoǎv$Ȳ={cc ##%0rЯ_o~0LLZO$o 1qH ȑ3z5 z:u6l؎;}ٳ's*pT $99|>>>>rv>Or,g0abcc^$QK0pe?~&&FXl.զkTKJpUxx+5JS"={ g\c1rD)㜜<\A~C9޽[r |2\]]QQQ#::Xf ߏիWE>}Jr bYގcX=&9&Zc΃wzvڼr g\Du+A4ӧϱw1 3g¢TTرhhjjrǡM777|~!++ \L$''cРA05?5rAr0ưxb!88uEG͑[#P\\ɓbԨaZr\T߿7tFT\i 9+W`ll ǀ}>H$³g$g^Ν=z\wZT9Ar%I"9///&χ><==!q}ŋ(// וcgggc`ccP9bcpbժELFԩXDbL@cEΕx ͛R :'j7ņr[cV)A~+G$cѣ1~xL>_O2[[>+'N@xx8ܹS$QcTV ~ ̀aAZ9Ƶk2 66Ԉ +WpH._NVZԪMcqYY훕cHIy L:U2Ń7ߠ< -[d~$NJIHH뽦HccP1`Ɲ jWr EBGG0W#55;wD~kzKSmj!EG {UB[+5tAnNr\(J dff_vϯ%y[#))q*LI-B'O &&Ɔyݻ?H+p| thi_O^*&)R=&9&!?֭ƪU8ʹ5r, qH,-1b`j'++;v˗偙3CڴIk丸QQOO7R *ɱ*8P=&9&!7qy4伭cx^c Q $G,8> ]]VPV9.V]-Č&>JW"TI c ǎE8逰9m^߸r\ZGT89ul=%ر?N>`*LM9VKZZoc0tdMV9'9&yWI h(ǻw+I0͛Yr 'OCEE%M FRP]B3&oPn('a``چ\7C1%QXݻ$ Œ,8\qjj:o`0tډJD8v, 2NJ 8i1\~ /^daƌ}PG0IyVI h(qׯˆC0mZBȱH$ž=GajjIPC)%=HK0sftuu*Ǖ=N1|@1GIvEcc ]xC1uxc8w2sd,?b}89o[?2,\8Rk޾ 1ѮȫzLrLDC9ޱn޼Qaʔqr~zSc)1رht+Vr6rE&Ύ ?rǡ$D!11A x۶}HNѣc #nj1{93KAkAܺu ߶ʱX,ƞ=Ǡ1ThAz5䘐ѐO*kQ*M33Sj,6#++AA l(N[bc>л,* #$DŽ\G DG_B\u >P两Gή Qc)ؼy7hLݐ."> '4BR!䘐7]=&9&{a?>}zqrCx̝;E!'Yc{ƆXb)Kq!o Db̞=k$DŽ|i11A saÅ L@c|9=”)Х-5aԩXDFG6&&Ɯ+9NJ{a„@X[[*|\J]KFxu&9&F{VI hضm-| +|~9ÏʂUXT^k[7'OQ=&9&1/֭{ @߾n 'ؾ}?LL0k$j09oCII.޽]8=X,L "8Xt6tuu*rL").FGG;:g$DŽ 11A۶탅EG,X0IV\19swpqqF/_[!3|BwUh9~ѹMF%r GG_BnH @cc 7X,Ʋes9r->ũSӧ,ϥjz뷁1`ժE /~ӕfՊ媭̱rBv  MK4,*nj1ܾqqװlJ7v|,Y%y;ӹ ֬YIh}N ;;K̂qaa1o ?ZæMC9LLLq9"/5֯ PUq),8!&Q\orr2~g᫯y B|駵?_}y$&eXn+__ްB=Njǽ{p^=VF9ӧc+"/-KCC=155 @R]\tW89 Eزe'M兰0l߾~!é9 +W.@n^ qi88t}2̝;HII pw… 9Թsg_.ծ@$ǒK7BM8((@1193C Ҷ--D>_acڵ&Mpr @zzBC7[tww[0abcc9*Q^^+(Ű H1u\\ {{{c}<ϟ?G=`i)-( U_,lWUdQ=V&9VU1&EE,c΃ XB#>Sך5kb j6RUU_݅b|Q(+@BHKLɁ,--QPPPJ1c ==֭Cppp!$$ǜʱ+5h#Ȣz,rLbL!:>}Y&yܱ,Νؿ$\\1q- IDATujPo8v믿1ŋm@(V!#&&=F__?WTT@__1駟 A&9?"-- GFhhh=ꫯ 6"211Aȏ! Q89~<۶탭.dd,YO U9ǘ4i{yqqq v<++ ̜qzO1:vxllYXX($ 1c !!!O' ''!&&#FhRKKI%걢11!rBr*+;}{ 'EEo믻>/D'N <<ވvI . 16zcP[# rfW|}4Trv9ҥЕǎ`ccq_AC9rU8>{[ h CCCʽ]H(u':1nnnCVV<'''\rHNNƠA`jjZO H%"11!rB:{Qt`Ҧ%e!bqbȁJlv_vSl9Cxat3gLdyVNtHOOGiii|bGaa!^xrOr,%\UMI ik9!='O /˗j3ưgQܸѣcXr`׮C{?wCd)5R%KfA[[[%FYrѣ1~xL>fٳg _r;ɱpU=V$9&1&ZDll y!㈈r% 9٬HIy FY)X,˗ *>j+Ȋ7|bxFoH[l9^h&ɱpQ=V9&1&󁜐H00魛t% 9ODzc msqP$c8c8t(STII[CF=pQ=V9&1&hg!++al,#YÑp ¬YH@u۶탎6BCs6)Or.\!^ UvR;AVH[O[ccBD+@dҪd%'NCLL.|$Ж걼Ę {޽G?>@ LZSt$rB$ac055Ɣ)B`ƝGj 2ɱxÆmu؝z)<Ę j!? ;VWD֭{̙!$r$&&YY9Xd'ZkvX˱jbXYU{ ӧJ-ǯ^Cj]XZ1NNN? 333|W077駟\:Aܹ8",l%B++ؼy7=zqF`đ4X~z+*.5r @xư5T0w\DFF"%% , . <M;w RXXKիTsDd!%%e嗝ƌ?H9 f D={&39غ5BwXTYA+J-TK2"..Æ =zj;N$ѣ(A9|8"X5e! }qLZZRR"8xTWR]$PwXTIAV9y;qzr㜜z7XZZ^XSScƌAzz:֭[zC.?|{366 ,8--[Fe˗C^=q#G`jj -5;,&&ȑ TvS9AVf9.+#9 iDzc.'룲犊 ;F[[? 44$!c8|7BnrpzC$[yg^`Μ)Vh9>|870lgl s" 6 mXr,U)۷!б^zz*A(׮BVVfϞ ]]vcHN!.:dlS(gakk+.]àA1sfMT%AV yo+qoYڀ4cYɱ,ksqq&O<|wJxyy!++ `aa] 8Z' Cxa˖HMMǰa>>}"-@<|ٹXtvka/'bȐ=W)An( nV9~-z不HS=:6l  ĠAPTThiiQr\pEEolk9~"7FYY9fϞ __ojXYz{\a|rjr痓scX| h CCCRRKӡe+Woq`ժEptQ0rrSL>U-k9 EعpX&H9D1rL;zuqwΜk`aĈ >YEMKpIܺuVXtRc +}(dDqKrLbLDkx9)jK+8> `>8q$,-)*_ޤr-nj1\cǢ! 1qHG3UdULcYӰzܜΜ}}= Zr\p_AEE%ݝ2]v FZ=Jѣg'={TJ&$DŽDԭ7%$Ax ƌ񃁁r\\\ҥk{nTJfrcǢXXt…˃oSeA'O^ϟFr,cVml,ߑcc ̙F`P833 HJH޽]0~ӘPun+9Et%ܸCCL=MYx{{{15Q|ɱ!nݺObL'!%I9DsO]ǣGϠ~ N)j k855Mrs>ƍ#}GVuAV 9&9n'jnnNC$O 1&S/ASS}87*ǯ_!!&\7oJall >&&@5DGGH-ǥ|$'E| de¢#OCAVAVf9y5^  QQ!I H7RЧ j帺Z۷#16b1c xzAKKhjjJ++HIy7RjG{,^< n: r*eʱJ%DQQD",,,.]{x 񷐓Gb dddSN$T<s\i)189BFr=U066 ֊RM.]N544%cP <~ǏSyBlm0aB  CKʀ4t]933$ HVVFuaΜ9 umA5 ql-[ƍamuB+`ccG#22ւ+77'hjR#/"""m۶Ya1q=9(((Bnn>^ʯYv@_xyy2fw}1X{&Y`oo333ڠ JK`nnV`QQQe8::°eXX@KK&&022m|ddd@CC˖-%HOOW(QbcǎXnΝ+kww<~:AOO00Уjܽ{??r&}}1^ih𠭭]]]BWWry%^zΝ;cƍ4iRǷk .DZZR&EP^^r ^'S3@޸"(h23`aёX4mǏS(AhiiACCC!fhiiCGGƆ$0 ț7orBB&? hh𠡡 mm-@WWəZϑ-ڵL(>CPP6lA԰yfݻ6mٳg=:vH!$GBB6nܨc FW_}%^dЀ!̋Hs>OdVPecI A9CR(#B022!p9۷k5ƵD"ddd浯]pS%Y?~\P(rssB!x<455+++A("++ ]tmӺ6s\i,/:tk׮΄/_ظuE"cK$Bl>.yX!뵫X,X,fĆXRׯ! f ̄td~geeu_k.(g}B~1W^:tұ-}Z">lٲuЁ`Yzz:߿kX@@@>}{}ʕ1ƶnj_ bo޼aW;겊 DL|rlmmO?RO999 ֭c`vvv cӧ @ ~T^=ѣölhy֬Y e1!CP޴# j߾} :u*c˗3lݺu'1x<͛7O+ʪ^0ƚ[jr{lܸq ۺu+;w.cedd0bQ(ߙϟ_O0221>/Mc[\u7n0sԩS?gYTTҥ ~k[laط~˞>}Μ9>̒c,99]`K.eǏg ۱c۽{u>ǎc1:v쨒 }lɒ% xlݺulҤI kob- rSyј `| ۻw/300`˗HWbb"Ocg)oڑƆX,X`/f<M01ƚO_0v1V;C]YU&99x>`gϮrqUڲcǎKl޽>|f̘47ims=oN[}1ƄB!e<3777Ծk111 С `~){ikΎ1Ƙ5PxU|A*\# *95ڛOT^4&5g)S0СCJ7a_~ȈR޴3 ڊ3WWׯ_O~GcRdU͛vO]s}KMn3~Ņ2ֻwof``[gQ(5>Wf1VVVV'TWWo <2z47ims=ov ֆ9s᧟~ˆ#jiff{{{899?ǂ @79uJ?U3>x=X\o vE^|Ǩš5kp-L6 FFF7 HYY%%%(,,G5՟;7-խ… _޽{:u*LMM.T9gjĮTTTnǎ?]]]|w2eJ<&ͱ~ ȑ#{ӧ_B W͛ҥKxFL8̓[LZ~=;Fw8x!G5{yoÙ3g7zԩSamm͛7I?ϝ;^ڵk!0c TUUa̘1M'{(*رc}vܹ/^o֪sBGG-fsP.ƌ ڵ ?#Ν[<=.$UN믿 &L?Xt)зo_テ7~wcȐ!puuEXXXիѱcG7VBEE͛֓慝0sLTWW/:u%K...M4!_֮]gbʕO7 99_|O> 4i*++b۷oBCC1b>|U}K݇`0!C@9F(z|WYPحB!222`dd$5g6OIDATD"dggC K.ӫLxSj%"H-yS7b1222Yo7.g􄎎ك9s`ذauN:r\dP $xzz޽{ԩݻעLAСC}}}SND߾})8$zTWW022СC%zρ@Q T/_͛(++-΄M    AAA AA 2AAA AAsIENDB`mididings-20120419~ds0/doc/gm.html0000644000175000017500000000667511745112742016472 0ustar alessioalessio mididings - General MIDI Definitions

mididings - General MIDI Definitions

General MIDI Definitions

The mididings.extra.gm module contains constants for program and controller numbers defined in the General MIDI standard.

Programs

ACOUSTIC_GRAND_PIANO
BRIGHT_ACOUSTIC_PIANO
ELECTRIC_GRAND_PIANO
HONKY_TONK_PIANO
ELECTRIC_PIANO_1
ELECTRIC_PIANO_2
HARPSICHORD
CLAVINET
CELESTA
GLOCKENSPIEL
MUSIC_BOX
VIBRAPHONE
MARIMBA
XYLOPHONE
TUBULAR_BELLS
DULCIMER
DRAWBAR_ORGAN
PERCUSSIVE_ORGAN
ROCK_ORGAN
CHURCH_ORGAN
REED_ORGAN
ACCORDION
HARMONICA
TANGO_ACCORDION
ACOUSTIC_GUITAR_NYLON
ACOUSTIC_GUITAR_STEEL
ELECTRIC_GUITAR_JAZZ
ELECTRIC_GUITAR_CLEAN
ELECTRIC_GUITAR_MUTED
OVERDRIVEN_GUITAR
DISTORTION_GUITAR
GUITAR_HARMONICS
ACOUSTIC_BASS
ELECTRIC_BASS_FINGER
ELECTRIC_BASS_PICK
FRETLESS_BASS
SLAP_BASS_1
SLAP_BASS_2
SYNTH_BASS_1
SYNTH_BASS_2
VIOLIN
VIOLA
CELLO
CONTRABASS
TREMOLO_STRINGS
PIZZICATO_STRINGS
ORCHESTRAL_HARP
TIMPANI
STRING_ENSEMBLE_1
STRING_ENSEMBLE_2
SYNTH_STRINGS_1
SYNTH_STRINGS_2
CHOIR_AAHS
VOICE_OOHS
SYNTH_VOICE
ORCHESTRA_HIT
TRUMPET
TROMBONE
TUBA
MUTED_TRUMPET
FRENCH_HORN
BRASS_SECTION
SYNTHBRASS_1
SYNTHBRASS_2
SOPRANO_SAX
ALTO_SAX
TENOR_SAX
BARITONE_SAX
OBOE
ENGLISH_HORN
BASSOON
CLARINET
PICCOLO
FLUTE
RECORDER
PAN_FLUTE
BLOWN_BOTTLE
SHAKUHACHI
WHISTLE
OCARINA
LEAD_1_SQUARE
LEAD_2_SAWTOOTH
LEAD_3_CALLIOPE
LEAD_4_CHIFF
LEAD_5_CHARANG
LEAD_6_VOICE
LEAD_7_FIFTHS
LEAD_8_BASS_LEAD
PAD_1_NEW_AGE
PAD_2_WARM
PAD_3_POLYSYNTH
PAD_4_CHOIR
PAD_5_BOWED
PAD_6_METALLIC
PAD_7_HALO
PAD_8_SWEEP
FX_1_RAIN
FX_2_SOUNDTRACK
FX_3_CRYSTAL
FX_4_ATMOSPHERE
FX_5_BRIGHTNESS
FX_6_GOBLINS
FX_7_ECHOES
FX_8_SCI_FI
SITAR
BANJO
SHAMISEN
KOTO
KALIMBA
BAGPIPE
FIDDLE
SHANAI
TINKLE_BELL
AGOGO
STEEL_DRUMS
WOODBLOCK
TAIKO_DRUM
MELODIC_TOM
SYNTH_DRUM
REVERSE_CYMBAL
GUITAR_FRET_NOISE
BREATH_NOISE
SEASHORE
BIRD_TWEET
TELEPHONE_RING
HELICOPTER
APPLAUSE
GUNSHOT

Controllers

CTRL_BANK_SELECT_MSB
CTRL_MODULATION
CTRL_DATA_ENTRY_MSB
CTRL_VOLUME
CTRL_PAN
CTRL_EXPRESSION
CTRL_BANK_SELECT_LSB
CTRL_DATA_ENTRY_LSB
CTRL_SUSTAIN
CTRL_PORTAMENTO
CTRL_SOSTENUTO
CTRL_SOFT_PEDAL
CTRL_LEGATO_PEDAL
CTRL_NRPN_LSB
CTRL_NRPN_MSB
CTRL_RPN_LSB
CTRL_RPN_MSB
CTRL_RESET_ALL_CONTROLLERS
CTRL_ALL_NOTES_OFF

mididings-20120419~ds0/doc/gettingstarted.html0000644000175000017500000001737111745112742021112 0ustar alessioalessio mididings - Getting Started

mididings - Getting Started

Anatomy of a mididings script

mididings configuration files are just Python scripts, although some of Python's features are used in ways for which they weren't intended ;)
Technically speaking, mididings is an embedded domain-specific language (DSL) based on Python.

The main difference between mididings and regular Python scripts is that mididings typically uses Python only at startup. mididings patches consist of Python objects and nested data structures, but not actual Python code. Internally, patches are converted to C++ objects, and the event processing is done entirely in C++. It is however possible to call back into Python, if necessary.

Although mididings is a Python module that can theoretically be imported in other Python applications, it's really not designed to be used that way. Think of mididings as a standalone application, that just happens to use Python as its "user interface".

Examples

Example 1

Let's start with something very simple:

from mididings import *

run(Transpose(3))

The import statement imports everything from the mididings Python module into the global namespace. The run() function is then used to run a simple patch, in this case consisting of just a single Transpose() unit.

You can start this script by saving it to a Python file and executing it, for example:

$ python transpose.py

What this script does is to create an ALSA MIDI client with one input and one output port. It will then start listening for events on the input port, transpose all incoming note-on and note-off events up by 3 semitones, and send all events to the output port. Pressing Ctrl+C terminates the script.

Example 2: connecting units

Ok, that was easy. Now let's try something slightly more complex:

from mididings import *

config(
    backend='jack-rt',
    client_name='example',
)

run(
    Velocity(curve=1.0) >> [
        Transpose(12),
        Filter(NOTE|CTRL) >> Channel(3),
    ]
)

The first thing that's new in this example is the config() function. This function is used to configure some global settings, and should usually be called only once, at the start of the script. Here, it is used to select the JACK backend instead of ALSA, and to change the JACK client name from 'mididings' to 'example'.

Now, let's look at the patch inside the run() function call. There are four mididings units used in this patch:

  • Velocity(curve=1.0): applies a velocity curve to all note-on events, increasing their velocity values.
  • Transpose(12): transposes all note events up by 12 semitones.
  • Filter(NOTE|CTRL): removes all events that are not note-on, note-off or CC events.
  • Channel(3): changes the MIDI channel of all events to channel 3.

Each of these units is quite simple on its own, but what's important is how the units are connected to each other:

  • A >> B connects two units in series, so every event output by unit A is then passed to unit B.
  • [A, B, ...] (which is just a list in Python) connects units in parallel, so each of these units gets its own copy of incoming events and processes them individually.

A graphical representation of the patch above would look something like this:
Example patch

For incoming note-on, note-off and CC events, this patch will output two events: one on the original MIDI channel, with notes being transposed by one octave, and one on channel 3, with no transposition. All other event types will result in only one event being sent to the output, because those event types are filtered out in the lower branch of the patch, and thus never even reach the Channel() unit.

Example 3: multiple scenes

Often it's desirable to run not just a single patch, but many different ones which can be switched using a MIDI controller. With mididings it's possible to switch between an arbitrary number of "scenes", each of which consists of one patch, and optionally an "init-patch" that will be triggered every time you switch to that scene.

Here's an example:

from mididings import *

run(
    scenes = {
        1:  Scene("channel 1 only",
                Channel(1)
            ),
        2:  Scene("channel 2, program 23",
                Channel(2),
                Program(23) >> Channel(2)
            ),
        3:  Scene("channel 2, program 42",
                Channel(2),
                Program(42) >> Channel(2)
            ),
        4:  Scene("channel 1/3 split",
                KeySplit('c3',
                    Channel(1),
                    Channel(3)
                )
            ),
    },
    control = Filter(PROGRAM) >> SceneSwitch(),
    pre = ~Filter(PROGRAM),
)

This script defines 4 scenes. In the first scene, all incoming events are simply routed to output channel 1. In scenes 2 and 3, events are routed to channel 2. Additionally, when switching to one of these scenes, MIDI program change messages will be sent on that channel first. The fourth scene splits the keyboard at C3 and routes both regions of the keyboard to different output channels.

The control patch is used to switch between these scenes. The filter lets only program changes pass, which then trigger the scene switches.
In this example we assume that incoming program changes are only used for switching scenes, and should not be processed by the currently active scene. Therefore the pre patch is used to filter out these events.

This is a complete overview of the event flow when using multiple scenes: Event flow

The control, pre and post patches as well as any of the init patches are optional and can be omitted. The control patch can be used for any event processing that does not depend on the currently active scene.

Note that the use of init patches can often be significantly simplified by embedding them into the regular patches, in particular using the Output() unit and the OutputTemplate class.

Command line usage

With the mididings command line application, simple patches can also be specified directly in your favorite shell, so sometimes there's no need to write full-fledged Python scripts:

$ mididings "Transpose(3) >> Channel(2)"

See 'mididings --help' for more options.

It's also worth mentioning that mididings can easily be used in an interactive Python session:

$ python -i -c "from mididings import *"
>>> run(Transpose(3) >> Channel(2))
mididings-20120419~ds0/doc/units.html0000644000175000017500000003351211743670720017222 0ustar alessioalessio mididings - Units Reference

mididings - Units Reference

Units are the basic building blocks from which you can build your patches.

  • Filters: These units filter by some property of the MIDI event. If the event matches, it's passed unmodified, otherwise it's discarded.
  • Splits: Basically just combinations of multiple filters of the same kind.
  • Modifiers: These units change some property of the MIDI event.
  • Generators: Converters from one event type to another.
  • Function Calls: These units allow calling back into Python code.
  • Miscellaneous: Anything that doesn't fit into any of the other categories :)

Filters

Filter(type, ...)

Filters by one or more event types.

Discard all events except note-on, note-off and control changes:
Filter(NOTE|CTRL)

PortFilter(port, ...)

Filters by port(s).

ChannelFilter(channel, ...)

Filters by channel(s).

KeyFilter(note_range)
KeyFilter(lower, upper)
KeyFilter(lower=...)
KeyFilter(upper=...)
KeyFilter(notes=[...])

Filters by note or note-range.

VelocityFilter(value)
VelocityFilter(lower=...)
VelocityFilter(upper=...)
VelocityFilter(lower, upper)

Filters by note-on velocity.

CtrlFilter(ctrl, ...)

Filters by CC number(s).

CtrlValueFilter(value)
CtrlValueFilter(lower=...)
CtrlValueFilter(upper=...)
CtrlValueFilter(lower, upper)

Filters by CC value.

ProgramFilter(program, ...)

Filters by PC number(s).

SysExFilter(sysex)

Filters by sysex data, specified as a string or as a sequence of integers. If sysex doesn't end with F7, partial matches that start with the given data bytes are accepted.

Filter out SysEx events for Roland devices with device ID 0x10:
SysExFilter('\xf0\x41\x10')

SysExFilter(manufacturer=...)

Filters by sysex manufacturer id, which can be a string or a sequence of integers, with a length of one or three bytes.

Filter out SysEx events for Yamaha devices:
SysExFilter(manufacturer=0x43)

Splits

PortSplit({port: patch, ...})
PortSplit({(port, ...): patch, ...})

Splits by port.

ChannelSplit({channel: patch, ...})
ChannelSplit({(channel, ...): patch, ...})

Splits by channel.

KeySplit(note, patch_lower, patch_upper)
KeySplit({(lower, upper): patch, ...})
KeySplit({note_range: patch, ...})

Splits by note. Non-note events are sent to all patches.

VelocitySplit(threshold, patch_lower, patch_upper)
VelocitySplit({(lower, upper): patch, ...})

Splits by velocity. Non-note events are sent to all patches.

CtrlSplit({ctrl: patch, ...})
CtrlSplit({(ctrl, ...): patch, ...})

Splits by controller number. Non-controller events are left unchanged, but not sent to any of the patches.

CtrlValueSplit(threshold, patch_lower, patch_upper)
CtrlValueSplit({value: patch, ...})
CtrlValueSplit({(lower, upper): patch, ...})

Splits by controller value. Non-controller events are left unchanged, but not sent to any of the patches.

ProgramSplit({program: patch, ...})
ProgramSplit({(program, ...): patch, ...})

Splits by program number. Non-program-change events are left unchanged, but not sent to any of the patches.

SysExSplit({sysex: patch, ...})
SysExSplit(manufacturers={manufacturer: patch, ...})

Splits by sysex data or manufacturer id.

Modifiers

Port(port)

Changes port.

Channel(channel)

Changes channel.

Transpose(offset)

Transposes all note events by the given number of semitones.

Key(note)

Changes note-events to a fixed note number.

Velocity(offset)
Velocity(multiply=...)
Velocity(fixed=...)
Velocity(curve=...)
Velocity(gamma=...)
Velocity(multiply, offset)

Changes velocity by adding an offset, multiplying with a factor, setting it to a fixed value, or applying a velocity-curve.
gamma uses a simple power function, where values greater than 1 increase velocity, while values between 0 and 1 decrease it. curve uses a somewhat smoother exponential function, where positive values increase velocity, while negative values decrease it.
Velocity() modes
Within mididings, velocity values may be (temporarily) greater than 127 or less than 1. When sending events through a MIDI output port, or by using the Sanitize() unit, velocities greater than 127 will automatically be reduced to 127, and events with a velocity less than 1 will be removed.

Increase velocity of note-on events, making the keyboard feel softer:
Velocity(curve=1.0)

VelocitySlope(notes, offset)
VelocitySlope(notes, multiply=...)
VelocitySlope(notes, fixed=...)
VelocitySlope(notes, curve=...)
VelocitySlope(notes, gamma=...)
VelocitySlope(notes, multiply, offset)

Changes velocity, applying a linear slope between different notes. This can be thought of as a Velocity() unit with different parameters for different notes, and is useful for example to fade-in a sound over a region of the keyboard.
Both parameters must be sequences of the same length, where one velocity value corresponds to each note.
VelocitySlope() example

Apply a velocity slope as seen in the graphic above:
VelocitySlope(notes=('b1','g2','g#3','d4'), offset=(-64, 32, 32, 0))

VelocityLimit(min, max)
VelocityLimit(min)
VelocityLimit(max=...)

Confines velocity to values between min and max.

CtrlMap(ctrl_in, ctrl_out)

Maps one controller to another (i.e. changes the CC number).

Convert sustain pedal to sostenuto:
CtrlMap(64, 66)

CtrlRange(ctrl, min, max, in_min=0, in_max=127)

Maps controller values from one range of values (default is 0-127) to another.
Any input value less than or equal to in_min results in an output value of min. Likewise, any value of in_max or greater results in an output value of max.

Invert CC #11 (expression):
CtrlRange(11, 127, 0)

CtrlCurve(ctrl, gamma)
CtrlCurve(ctrl, curve=...)
CtrlCurve(ctrl, offset=...)
CtrlCurve(ctrl, multiply=...)
CtrlCurve(ctrl, multiply, offset)

Changes controller values by applying a curve, offset or multiplication. See Velocity() for a description of the parameters.

PitchbendRange(min, max, in_min=-8192, in_max=8191)

Changes the pitchbend range to values between min and max.

PitchbendRange(down, up, range=...)

Changes the pitchbend range to the specified number of semitones.

Bend a full octave down, but only one whole step up, assuming the tone generator is set to a (symmetric) range of 12 semitones:
PitchbendRange(-12, 2, range=12)

Generators

NoteOn(note, velocity)
NoteOn(port, channel, note, velocity)
NoteOff(note, velocity=0)
NoteOff(port, channel, note, velocity=0)
Ctrl(ctrl, value)
Ctrl(port, channel, ctrl, value)
Program(program)
Program(port, channel, program)
Pitchbend(value)
Pitchbend(port, channel, value)
Aftertouch(value)
Aftertouch(port, channel, value)

Changes the type of the event. If port and channel are omitted, the values of the input event are used.
To "reuse" other values from the incoming event, one of the EVENT_* constants can be used in place of any parameter.

Convert aftertouch to CC #1 (modulation):
Filter(AFTERTOUCH) % Ctrl(1, EVENT_VALUE)

SysEx(sysex)
SysEx(port, sysex)

Creates a new system exclusive event. sysex can be a string or a sequence of integers, and must include the leading F0 and trailing F7 bytes.

Send a SysEx message read from a file:
SysEx(open('example.syx').read())

Generator(type, port, channel, data1=0, data2=0)

Generic generator. System common and system realtime events can only be generated this way.

Function Calls

Process(function)

Calls a Python function. This will stall any other MIDI processing until the function returns.

  • function: A function (or any other callable object) that will be called with MidiEvent objects as its only argument.
    Possible return values are:
    • A single MidiEvent object: output one event and continue processing it.
    • A list of MidiEvent objects: output multiple events.
    • An empty list or None: discard current event.
    Alternatively, function can be a generator that yields MidiEvent objects.

Call(function)

Schedules a Python function for execution, and continues MIDI processing immediately.

  • function: The function to be called. Unlike Process(), this will run the function in another thread. Its return value will be ignored.

Call(thread=...)

Like Call(), but runs the function in its own thread.

System(cmd)

Runs an arbitrary shell command, without waiting for the command to complete.

  • cmd: The command as a string, or a Python function taking a single MidiEvent parameter and returning a string.

Miscellaneous

Pass()

Does nothing.

Discard()

Discards the current event.

SceneSwitch()
SceneSwitch(number)
SceneSwitch(offset=...)

Switches to another scene. number should be a scene number, or one of the EVENT_* constants. offset can be positive or negative and will be added to the current scene number. Without parameters, the program number of the incoming event (should be a program change) will be used.

SubSceneSwitch()
SubSceneSwitch(number)
SubSceneSwitch(offset=..., wrap=True)

Switches between subscenes within a scene group.

Init(patch)

Executes patch when switching to the scene containing this unit (essentially adding it to the init-patch, see run() for more information).

Output(port, channel, program=None, volume=None, pan=None, expression=None, ctrls={})

Routes incoming events to the specified port/channel. Optionally, when switching to the scene containing this unit, a program change and/or arbitrary control changes can be sent. To send a bank select in addition to the program change, program can be a tuple with two elements, where the first element is the bank number, and the second is the program number.

Route all events to output 'synth', channel 1, and set the volume to 100:
Output('synth', 1, volume=100)

OutputTemplate(*args, **kwargs)

Creates an object that when called will behave like Output(), with args and kwargs replacing some of its arguments. This works just like functools.partial(Output, *args, **kwargs), but with the added benefit that an OutputTemplate object also supports operator >> like any unit.

Define an instrument by specifying its output port, channel, program number and transposition, then use the same instrument in two different patches at different volumes:
synth = Transpose(12) >> OutputTemplate('synth', 1, 42)

patch1 = synth(64)
patch2 = synth(127)

This is equivalent to:
patch1 = Transpose(12) >> Output('synth', 1, 42, 64)
patch2 = Transpose(12) >> Output('synth', 1, 42, 127)

Print(name=None, portnames=None)

Prints event data.

  • name: A string to be printed before each event.
  • portnames: Pass 'in' or 'out' to print input or output port names, rather than just numbers.
A simple MIDI event monitor:
$ mididings "Print()"

Print(string=...)

Prints a string. Instead of a fixed value, string can also be a Python function that takes a single MidiEvent parameter, and returns the string to be sent.

Print a graph of note-on velocities:
Filter(NOTEON) % Print(string=lambda ev: '#'*ev.velocity)

Sanitize()

Makes sure the event is a valid MIDI message. Events with invalid port, channel, controller, program or note number are discarded, note velocity and controller values are confined to the range 0-127.

mididings-20120419~ds0/doc/style.css0000644000175000017500000000441111744102603017027 0ustar alessioalessiobody { max-width: 60em; font-size: 85%; font-family: sans-serif; } h1 { font-size: 130%; color: #333; background: #eee; padding: 3px 0.7em; } h2 { font-size: 110%; color: #fff; background: #667; padding: 2px 0.7em; margin-top: 1.8em; } h3 { font-size: 100%; color: #333; background: #dddde5; background: -moz-linear-gradient(left, #dddde5 0%, #dddde5 15%, #fff 50%); background: -webkit-linear-gradient(left, #dddde5 0%, #dddde5 15%, #fff 50%); padding: 2px 0.7em; margin-top: 1.8em; margin-left: 0.6em; } h4 { font-size: 100%; color: #333; padding-left: 0.5em; margin-bottom: 0.2em; margin-left: 0.5em; } p, table { margin-left: 2em; margin-top: 0.4em; margin-bottom: 0.8em; } ul { margin-left: 0.5em; } b { color: #333; } pre { margin: 0em; } table { border: 1px solid #333; border-collapse: collapse; } th, td { border: 1px solid #333; padding: 0em 0.3em; } th { color: #333; text-align: left; background: #eee; } a { font-weight: bold; color: #237; } li { margin-top: 0.4em; margin-bottom: 0.2em; } li.toplevel { margin-bottom: 0.8em; padding-bottom: 0.5em; border-bottom-width: 4px; border-bottom-style: solid; border-bottom-color: #eee; } li.toplevel > a { font-size: 115%; } li.toplevel:last-child { border-bottom: none; } div.example { background-color: #ffd; margin: 0.6em 16em 1em 2.2em; padding: 3px; border: 1px dashed #edb; font-family: monospace; } li > div.example { margin-left: 0em; } div.example > div.header { font-style: italic; color: #556; margin-bottom: 0.3em; } div.nav { position: relative; text-align: center; padding-top: 8px; border-top-width: 4px; border-top-style: solid; border-top-color: #eee; } div.nav:last-child { margin-top: 2em; padding-bottom: 8px; border-bottom-width: 4px; border-bottom-style: solid; border-bottom-color: #eee; } div.nav > .navprev { position: absolute; left: 12px; } div.nav > .navnext { position: absolute; right: 12px; } div.nav > .navprev:before { content: "\2190\00a0"; } div.nav > .navnext:after { content: "\00a0\2192"; } mididings-20120419~ds0/doc/submodules.html0000644000175000017500000001065711745112742020244 0ustar alessioalessio mididings - Submodules Reference

mididings - Submodules Reference

Submodules

mididings.event

This module defines classes and functions for dealing with MIDI events in Python code, e.g. using Process() or Call().

MidiEvent(type, port, channel, data1, data2)

The class used as the MIDI event data type inside the Process() and Call() units.

A MidiEvent object has the following attributes:

  • type: The event type, one of the constants described below.
  • port: The event port.
  • channel: The event channel.
  • data1, data2: Data bytes, meaning depends on event type.

There are also aliases for these attributes, some of which are only valid for certain types of events:

  • note: Note number, alias for data1.
  • velocity: Note velocity, alias for data2.
  • ctrl: Controller number, alias for data1.
  • value: Controller parameter, alias for data2.
  • program: Program number, alias for data2. Unlike data2, this value is affected by the data_offset setting.

NoteOnEvent(port, channel, note, velocity)
NoteOffEvent(port, channel, note, velocity=0)
CtrlEvent(port, channel, ctrl, value)
ProgramEvent(port, channel, program)
PitchbendEvent(port, channel, value)
AftertouchEvent(port, channel, value)
SysExEvent(port, sysex)

Auxiliary functions to create new MidiEvent objects of a certain type.

mididings.engine

This module contains functions to query or control the state of the mididings event processing engine from Python code.

switch_scene(scene, subscene=None)
switch_subscene(subscene)

Switches the current scene and/or subscene.

current_scene()
current_subscene()

Returns the current scene or subscene number.

scenes()

Returns a dictionary of all scenes. Keys are scene numbers, values are tuples containing the scene name and a (possibly empty) list of subscene names.

output_event(ev)

Sends a MIDI event directly to an output port, completely bypassing any other event processing.

in_ports()
out_ports()

Returns a list of the configured input/output port names.

time()

Returns the time in seconds (floating point) since some unspecified starting point. Unlike Python's time.time(), this clock is guaranteed to be monotonic.

active()

Returns True if the mididings engine is active (the run() function is running).

restart()

Restarts the mididings script by terminating the current process, and then running the same Python interpreter with the same arguments again. This will not work properly if run() is not the last call in your script, or if you're running mididings in an interactive Python interpreter.

quit()

Terminates mididings event processing (by making the run() function return).

mididings.util

This module contains various utility functions.

note_number(note)

Converts a note name to a MIDI note number.

note_range(notes)

Converts a note range string to a tuple of MIDI note numbers.

note_name(note)

Returns the note name for the given MIDI note number.

controller_name(ctrl)

Returns the GM controller name for the given controller number.

port_number(port)

Returns the port number corresponding to the given port name. If input and output ports exist with the same name but different index, the number of the input port is returned.

mididings-20120419~ds0/doc/examples/0000755000175000017500000000000011471533065017002 5ustar alessioalessiomididings-20120419~ds0/doc/examples/output.py0000644000175000017500000000267411371361344020723 0ustar alessioalessio# # output.py - uses the Output() unit to simplify MIDI routing and sending # program changes # # This script assumes that the folling sound sources are connected to # mididings: # # - A sampler, connected to the first output port, listening on channel 1. # Program 1 is an acoustic piano sound, program 4 is a Rhodes piano. # # - A synthesizer, connected to the second output port. On channel 3, there's # an organ sound. On channel 4, there's a string sound. # # Program changes on channel 16 switch between scenes. # from mididings import * config( # create two output ports out_ports = ['sampler', 'synth'], ) piano = Output('sampler', 1, 1) rhodes = Output('sampler', 1, 4) organ = Output('synth', 3) strings = Output('synth', 4) run( scenes = { # scene 1: play piano. # this will switch the sampler to program 1, then route all events # to it 1: piano, # scene 2: play organ, transposed one octave down 2: Transpose(-12) >> organ, # scene 3: split keyboard at C3, the lower part plays rhodes, the # upper part plays strings 3: KeySplit('c3', rhodes, strings), }, # control patch: use program changes on channel 16 to switch between # scenes control = Filter(PROGRAM) >> ChannelFilter(16) >> SceneSwitch(), # preprocessing: filter out program changes, everything else is sent to # the current scene pre = ~Filter(PROGRAM), ) mididings-20120419~ds0/doc/examples/aeolus.py0000644000175000017500000000176511371361344020653 0ustar alessioalessio# # aeolus.py - Aeolus stop control using one controller per stop # # Aeolus (http://www.kokkinizita.net/linuxaudio/aeolus/index.html) # uses CC #98 to enable/disable stops. Changing a stop requires one message # to select the button group and action, and another to select the button. # # This script converts simple CC messages, one CC# per stop, to the format # expected by Aeolus. CCs 0-56 are mapped to the 57 buttons of the Aeolus # default instrument. Stops are enabled by controller values >= 64. # from mididings import * def aeolus_button(ctrl, group, button): return CtrlFilter(ctrl) >> CtrlValueSplit(64, [ Ctrl(98, 0x50 | group), Ctrl(98, button) ], [ Ctrl(98, 0x60 | group), Ctrl(98, button) ] ) run( Filter(CTRL) % ( [ aeolus_button( n, 0, n) for n in range(12) ] + [ aeolus_button(12 + n, 1, n) for n in range(13) ] + [ aeolus_button(25 + n, 2, n) for n in range(16) ] + [ aeolus_button(41 + n, 3, n) for n in range(16) ] ) ) mididings-20120419~ds0/doc/examples/process.py0000644000175000017500000000074311323173123021025 0ustar alessioalessio# # process.py - shows how to process MIDI events in Python # # This is a curious little script that inverts the velocity of note-on events. # The harder you press the keys, the quieter the sound will become :) # # Events on channels other than channel 1 are discarded. # from mididings import * def invert(ev): if ev.channel == 1: if ev.type == NOTEON: ev.velocity = 128 - ev.velocity return ev else: return None run(Process(invert)) mididings-20120419~ds0/doc/examples/router.py0000644000175000017500000000201611371361344020671 0ustar alessioalessio# # router.py - A simple OSC-controlled MIDI router that sends all incoming # events to the output port/channel determined by the current scene/subscene. # # To route the output to a different MIDI port/channel, send an OSC message # /mididings/switch_scene # to UDP port 56418. # # For example, using the send_osc command from pyliblo: # $ send_osc 56418 /mididings/switch_scene 13 1 # from mididings import * from mididings.extra.osc import OSCInterface NPORTS = 16 config( out_ports=NPORTS, ) hook( OSCInterface(56418, 56419), ) # return a list of 16 scenes, each routing to a different channel on the # specified port def routing_channels(port): return [ Scene("Channel %d" % c, Port(port) >> Channel(c)) for c in range(1, 17) ] # return a dict with one scene group per port, each containing 16 scenes def routing_ports(): return dict( (p, SceneGroup("Port %d" % p, routing_channels(p))) for p in range(1, NPORTS + 1) ) run( scenes=routing_ports() ) mididings-20120419~ds0/doc/examples/hooks.py0000644000175000017500000000167111323153315020474 0ustar alessioalessio# # hooks.py - demonstrates the use of hooks extending mididings' functionality. # from mididings import * from mididings.extra.osc import OSCInterface from mididings.extra.inotify import AutoRestart from mididings.extra import MemorizeScene config( out_ports = 2, ) hook( # OSC interface: sending the message /mididings/switch_scene with a scene # number as a parameter to port 5678 switches scenes OSCInterface(5678), # auto-restart: edit and save this file in a text editor while it's # running, and mididings will automatically restart itself (!) AutoRestart(), # memorize scene: every time this script is restarted, it automatically # comes back with the previously active scene MemorizeScene('scene.txt') ) run( scenes = { # scene 1: route everything to the first output port 1: Port(1), # scene 2: route everything to the second output port 2: Port(2), }, ) mididings-20120419~ds0/doc/examples/skeleton.py0000644000175000017500000000602311471533065021201 0ustar alessioalessio# # skeleton.py - An example showing the basic structure of a complex setup with # multiple scenes and various other features. # from mididings import * from mididings.extra import * # some classes are defined in separate modules because they depend on # additional Python packages to be installed. uncomment these imports if you # need them #from mididings.extra.osc import OSCInterface #from mididings.extra.inotify import AutoRestart config( # uncomment this to use the JACK MIDI backend instead of ALSA sequencer #backend='jack', # you can assign names to input/output ports... out_ports=['spam', 'ham', 'eggs'], # ...or just change the number of ports available #in_ports=2, # when using a patchbay like QjackCtl, a small delay allows ports to be # connected before any MIDI events are sent #start_delay=0.5, ) hook( # some functions (like scene switching) can be controlled via OSC. # this is needed for the livedings GUI, for example. # by default, UDP port 56418 is used to control mididings, and mididings # will send notification to port 56419. #OSCInterface(), # uncomment this if you want mididings to restore the previously active # scene when it is restarted #MemorizeScene('scene.txt'), # mididings can automatically watch this script (and modules it imports) # for changes, and restart itself automatically #AutoRestart(), ) # in this example, we use the control to switch scenes in response to program # change events control = Filter(PROGRAM) >> SceneSwitch() # use the pre and post patches to print all incoming and outgoing events. # pre also filters out program changes, as these are already handled by the # control patch and probably meaningless for individual scenes pre = Print('input', portnames='in') >> ~Filter(PROGRAM) post = Print('output', portnames='out') # define some sounds/outputs. # by using OutputTemplate() instead of Output(), we allow additional # parameters (like volume) to be specified later, when these sounds are used # in patches. spam1 = Output('spam', 1) # channel 1 on port 'spam' spam2 = Output('spam', 2) # channel 2 on port 'spam' ham1 = OutputTemplate('ham', 1, None) # channel 1 on port 'ham' eggs1 = OutputTemplate('eggs', 1, 23) # channel 1, program 23 on port 'eggs' eggs2 = OutputTemplate('eggs', 1, 42) # channel 1, program 42 on port 'eggs' ## now define some patches using the sounds defined above dummy_1 = KeySplit('c3', spam1, spam2) dummy_2a = ham1(64) // eggs1(127) dummy_2b = ham1(96) // (Transpose(12) >> eggs2(64)) # finally, assign scene names and program numbers to these patches... scenes = { 1: Scene("Dummy Scene", dummy_1), 2: SceneGroup("Dummy SceneGroup", [ Scene("Subscene A", dummy_2a), Scene("Subscene B", dummy_2b), ]), # ... } # ...and start the whole thing... run( control=control, pre=pre, post=post, scenes=scenes, ) mididings-20120419~ds0/doc/examples/klick.py0000644000175000017500000000160611371361344020452 0ustar alessioalessio# # klick.py - uses SendOSC() to start/stop klick, or change its tempo # # klick: http://das.nasophon.de/klick/ # # CC 13 runs/terminates the klick process (alternatively, # run "klick -P -o 1234"), # CC 14 starts/stops the metronome, and CC 15 changes tempo. # from mididings import * from mididings.extra.osc import SendOSC port = 1234 run( Filter(CTRL) >> CtrlSplit({ # CC 13: run/terminate klick process 13: CtrlValueSplit( 64, SendOSC(port, '/klick/quit'), System('klick -P -o %d' % port) ), # CC 14: start/stop playing 14: CtrlValueSplit( 64, SendOSC(port, '/klick/metro/stop'), SendOSC(port, '/klick/metro/start'), ), # CC 15: change tempo 15: SendOSC(port, '/klick/simple/set_tempo', lambda ev: 60 + ev.value) }) ) mididings-20120419~ds0/doc/velocityslope.png0000644000175000017500000001330611347254021020570 0ustar alessioalessioPNG  IHDRsRGBbKGDC pHYs : :dJtIME GFIDATxkPS!hDEp1V"jnE3"g܃g͌͞_fB+*JQU%T-BCd9\>E2+Z< WLh&T5 i45 4jP z5&\ٳv6668* VXDٴiSzz: @ӣ-!!JpFմٳe2YJJ  V@Mj5nJIIQnp Tʶ2ڵkXRMwtt455PeXJ$&&BnSU[[.\h'!ښBz͛7u:EQfff}k:!!ѣb,%%eeennn|>p[,+Yfq\FM9w_\^^'nذ$##˗4MѣSSSlbJoo/M olliZ$iZ)t>@ i޺.((;w.!$88x۶mVVV2k֬l6oCWW׳gϖ.]P(۷o9rDP4-BBB w E#֡ __{577B^joo?RV-[N<˭RT*uuuxV:(bcc%ŋ>}jHŵ֭svv&$&&⎎V[XXHٿǓ"P y0g|B¢===555\.W$s?pvÊ*@ ==Wr83 jz geB\aׄ3do3⺺\qqҥK.#F)**h>,(57vD"q1p5=Tꄄ3uu/BB6Σ(7}潞51qpXv횶<ٳSSoH$n^Y#"@MOY M_l\P( 37mZkss<[k4L5%%OOHaX_}ɴ^e2JmX\å{y`q)"+…sZDEYYY hYAA6+----ξog_z5רiz}j;w]\""vac ,/_|b:7/]xð^p>"BMd7?bfF!)y{V^fjNR'&&6l޼!((Lu^y~Sx܉'’4M;9IFMǃJNJJidF c!*E2ioP,'AYY!kժeX\3 uPVZjr:?<__T2o%"jA7&$Q5zyB 0~S*/7X\Qĉ PL?rs ~mo̜q̝ti__8$YNNѬY|_ @`bҊ&Y,3SIj0^°lP8Ϳhs^ao߲CsQZ655(FQTl6e􎅅+Z[ wuuEHƙ@0kr0jNpvuu>}ѣR/U i0B¶D/qcMOsju{BBrMMd`P 7:5, Ǐ(%5$99Νbi9':: sNf/_θu+n߾P\P ݭ;u14=1:샏?Pcv ujqOH155_pqq@ fR\3jz?>88`DbxrG 5=t'?zT"޽e'93P Cf gaa[Cf\{V j1jMbbrUU-:58 /Uݻz @M3HYYgMLL0i˓s̎ 0S\gg'EQjzri:-nvtݷ/Cϟ?(a/ʄBH$,;\^^.񡝨iƛtCZ(8.Ny^QQ9siϞ=ffffnxRFb{C}۠={rRo;υBa'pޙ3y,{o;kllh4ɜA1}v=pٳg*11qV#"";qF}[s򯦦B(v3w e^tic?t=GGG91HȜ|<Y_JJ !930>=:I'MMo vob7Ϡ;?%zdXE9~ 5Lfcc3ܭ!&ׯ_'\~}X[EDDL&&ՄamGJ:thޔ\ DDD BHqq133Xxo`i45 4d?JeKK 7/^P*MMMb<4Ӄ(27o޼2~VImmmuua(X,ڸqJB,cZ,ϛ7O"!sNb@,+**jǎ+V8z QQQ݋ݰaC``ׯϝ;te˖]|+}Gׯ~ᇴ4gg[n;W^r劫\.OLLD&x"EQm嗼oRUUu߸qѣaaaqqqEݹsݻ*YYY]]]򋃃CVV֍7f8-\033p[Pe81HOOɜ9s8pܻwoLLL|||GGTPP!>>>s%o۶l6;>>ǮgϞyzzFEE̚5 5=j4MLLv؁4ƁL&+// ٿ?kQQQ ,04x6?o>4MDCBBlmmy}zΝd===ބ;|>Ç...;;XDxO.~gJ@@X,jŵ֭svvFM5kִ_hɒ%1%֬YB-ZX7Gt(PdxF1܈ojj15;nZSSrE"!d۶mBbZ6H޾}ۄLocǎojjq_eLn'$$t:d>t|>翵XssF jz5SL"TnHdB:5 LAQ rx&=F_aj48p <<|T߄=p@II;<~XHavvvzӽnnnCJT666]vX܆VsrrVUUUR }+g^QQQUUݻwE"ђ%Ky捍%%%ĸe>uuuJR(}嗦#_GML7̪ixẃ&MxCss4j`jPS<X:IENDB`mididings-20120419~ds0/doc/eventflow.png0000644000175000017500000006630511332020423017676 0ustar alessioalessioPNG  IHDR0% sRGBbKGD pHYs  tIMEݶ; IDATxwxTE%IBoI'@@J 4(*/Ey^A@AQPҤH*  I]v7vs_W.S33yyFl6AAW4 AAWADpAAA\AAAAW 1'MAp(s)t:]˝P)rٷ9x\aaXСO>/ butszʔ)S5kݻwgԨQTX L&3goaȐ!<|h4._̤Io۷/ Nsjdee`ׯO6mh۶->=zgϞNa y,WAW|›`x uf0:t(7fҥ\p1cPfMf3= 4￧TRlݺ:u&Mdɒh4f3 R-rw߱aRSS{ҥ 6TEbӧ?>.\`ܹ :Tu+Ϛ5ƍc2߁ BRldffRN̙=EVV:5att=Ca2Tu6m0Lf"##yGQE rѰaC/^W^yEMSVV~GX,N<&֭ BYVRk6X,n-jѨۅUSGN qgM8ݹEhw$9^{ G0($w&vuWv+8^Ӏ1wֿ b a/w}9]-e݉uMgl҇+"hZBCC wkz}ݞ7P  +wɓ'Msrgq Vmv}WlA_~(3m4:DHHϟg {=.]j%%% 6pM, K,aɒ%(©S6mΝCq)̙Ò%KHII+$%%h7_~A׳c^y aܹ|wz *?EQT Q\-\X=QVdNޫXXp9222x)^8 V+ǎԩSL:hEQƇgܹ)SE~zV%,,~lfԩ9r.]7ߠhxtX,OΝ;ѣwfڴiiHVVYRT/e]"* BZTR2e0~xj׮Miܸ1'O$66%Kb0J6((}a0x1K~l%PYYYl6ʖ-KFF:{b2x7X,;w(V+:ubܹ_ƍ$N~Yc_6AW;x9twޡE1ŢNphf3QQQtIXFDDAAANVo6Y&m۶USlYF##F !!ݻw|rV\ɜ9s8典/[bi[AWraMfZ-qqqjՊ͛3|FAժUqhڵks!*WL%lXVL&mbnݺ\t[,F#VBll,u%11sQ5jZā"޽7q+~Y{RAwEoDGG̾}[.ueƍ|4lɄj}4oޜ;woҼysZ-ϟzkNOGL&D^x77ol6ҴiSUOZ̙3.]ʕ+cZ*zw_n7+ B k%jXm6*T )) .`6iԨM4nǎ޽{9<{j=z4ٳ(Br]6*UпC(QBdd$ǏQzuTBHH-[ٳ$''ɋ/Hɒ%h.na'0e'" 8Vaaanŋ}n+bŊѪU+ZjjRlYʖ-봽^zNVdd$;vt@h޼9͛7wɕoV7*Ʈn~m, BX79;o^vb}Ў7w,wqAWuN O"kc$bAW"-*s:} zOn +EĊf]kO\Xi)>Y,"], Ұ.M^e \AWrGN)쑼͕ $/ BZ_z>hS? C~l6fwk\AȦ/MO?qZ?l6ӱcGjԨ¾ OBDp S8HoeXxyʽk*!7rΝKFFF4XZj"p;k,\P^2{l.\# ]\AȅȺZw8AQVX~ +T-( ) Z+*SH^ Sݹ]e%_V;an( nܸ"p Fc/&s7\A׊T,ܽKۙ={6Ç^ҥԩ=.]ĉ ) B.*@WGs=~o?iӦ >/&))TZU\wwRJ|<>YyO!Wr(7oرc\|*UD*UjXVN:ٳgQbcc?~gϢV+W͛˗Wȑ#/_RL4hV`ذa.]ŋIMMGPF ݊ " }3f`ǎT^ *W̔)SO?eܹ^Z*| ,^z N`0X "PT|2 | d2qM/NJJ 'Nd޼y_lTD oy~ڶm ܹs?~ol6fbҥK fʔ)fV+t֍m۶;`X }Er9thdtUB7 "PTL2;qƑ@Rظq#ŋW^ 922իWj`Ŋq\Y&&&^-믿Xlk~#33VKtt4;vP;L&yBBBhݺ5dddPX1>UNVE׫ϙ[4 'Nɓ9>fa0ѣGZDplDDDd̙+رcزe _}&MR^Պb_ 0sa.\Ȃ Tw9sxINNfѢE̛7ÇBdIhߝoUVe׮],^5kpڵ<$  N/ˑaaa2tP^ʽ'|]t)/={DQzC=-["9yXր=i@rw_/"֘w D! ,d +p Ch{we?~Yq "Vk Fmw->Dp!-gWtE"/lf[AWEh]+lN… 9tP@FH2`ڵks \AJ[y裏$̇~/BNUpoX;N|YfaXҥ}v^y'V +Tަ" ]AWZOGDl_KI>DpzPDdw* 2_dgYܭP]E>7_jq͆jbLW|pO uZǏ'%%yieʔzRJ 8Cr@322سg'OdTT+.׆ k&G S \F "##}\>}P^="""ZEhtiiilذAJIfD6'fCLVV_,>p'[q@E… |TX~iӦ{ٳۗ|c]t:~Wƍȑ#s&.]orNT5z=<󌔎B܈5Ϟ=3f  Bk׮1|Gxx8?0=AAA\ JΝ2d||W3vXڴiYd #F`$%%1btdΝ;M6Ë/HŊZ*+V 99E+^͛/^wy"L44]VX1BBB| B\bڏZvo$''ꫯk1f&L@2e 92dV̾}ԩ%K䥗^>`„ DDDw^ZjEzxg/:u*7nܠC,]?/n::tV`˖-ЬY3F#ƍc׮]r!j֬V%,,d2);'e[Q*ZEQ +>QʂXގSӧsUՊwwaРAի?ŋӤI}.\`Μ9l޼iߡC?>ϟ|Ԯ]^znԩ$$$pi,YB||< ,ܹs\|#F( /+WƥN&999G4Çiڴ(Fy뭷(WuX)ᩌ IDAT1wE4hqqqhт}͛XmB ?j6m ))/۫ԫW?_~ {1 cǎytuC+==/HpV]3G~aO/ȑ#<+V~֭[ <6P^{5 ͛Y|9_|k֬bx4M&L4u̙3ٳjcF1qD])B%L&zŌ3 q' B^D%\P:=!!!~ߐ6lHFݻ7 \tx.]i֬ji4ڴiK/NS8}qoߞz{tگ]X`J(^<A|(+W3<` … ,X Ŷm(]4#0n8}Y9q{]v<#ٓ^x{GrUf̘6dd2wߑO>^gDEE1j(&MDzz:/_f+W~Xs"ֱ +wPyZB Jɒ%S;v ##ryթ9M6U'/]Km6 +VcǎTR9s8Y=:BY/'ߣ( nZ=><<%K2c ĺqG~ϙ9y<(…p Ɛ!C0[;1aÆ*=jh4 :AaZU1l+W &8=]lK,ɳ>s6iӤL ڒ"_UI&:̟ S8hh4'x_Fs:|]"jNKBwR6XB`۴4222=fɘ<)wԩS_nߩYu'[W.H>vw&̨QX`Om~:u?zBӢE ]1,^Yfq!aߟϬYp6mڤS{rA, C_W__IK=oip dD2ҥK9s&+WTKΝ;9]Ɔ 0͜++Kӷo_-[o}EӵtVȑ#"~HHH>;|x^_V\IRR{!$$ҺukGFt2, 7obвeKڶmKyg|27nܠ^z|lO<=p+:c}c4y&=z`ƌL8 8Mw dZf#""x@-kQ裏{Ӄ';BBbt:={dРAP|*?簎Nl`0c]67w;?u==8{'È#ضmժUzM{pǠ1W&Ŋl6kf r|H)GBѲfݭ n0,XV{W Z`41 ^^ElPTӗonOǐaaahZ@ _tUΝ;G͚58pDjjӲG.\ ""ɵ蒒B8̗s+x|"rԵ?7>ԨQ͛?}fNΝ;iذ!pkL=ָׯ_WԩSyZ|B+J"11˗/s1^{5&O @ll,gh4|G۷W ]:߭CǑOQk{PPamv>ZݻvZ-[FFFsʕ+VK֭7oOѣ+dee( :X;nʁh4+VcCLL ueܸqO?7ߨ.ҥKS\9yӧsr5_>%Je˖NXի*U)SЮ];v?̤IlDGG3|;6lȩS7njRJ̛7+W2f6l 76Pxk+VN߳{s\b֭Yhs{eݺuYEaȐ!jՊʣ>Jbb"ZFc=Fz8q"'OVы/f̘14nܘŋ|rYYY4jԈ>6mڨINNfĈ,Y$W(B;=Z:uj3fS囙I޽ILL$++xx222(Vm-^z#ԕwǭzGJě.cL_Qyw;:EQXlTR_k]FŝGFFrMn;?::~[vef3޽WȮ/Xͮ@ixK 敻nܸ45{?~UV~zMƸqhѢO5T/Guwk敶zb '6;h_q%+]w(QX"KA-oJbbO޼y'|`o|m\F"##YjUV-4m+ٶ,رc19rl۶۷K P4,E_hYdSV-m6;wdڵ5mڔ͛?J R[ nN9./B]`[lV~Opk2>FӾ}{̙3]v1|pwU.Lmq7ua,@( ˖-#&&F]3?;!'?Z$(}˂k06l\r꒐_"[oѽ{wW^DZ@ŢF0\\ ,x=[kܞ~9:AҥTREusW;η%\aoXVL&6ldɒO?4seѢEtUuEL&M$Kֵx c|b2kCo_n݊`Yf8q .pEud 3EQX,DEE?;o4^xBCC?~>si>c5]K\ҥ cǎe͚5$&&?r9HOOO>aڴicL&ZlɓO>oAZ f1qD6lfcڵjŋ3m43aիoS8ydlž={С=:NZ*"E^\b|CBBBرc}(4u\HKKcժUZm۶1g /fɒ%DDDo>^*VԩSo\j_}Ǐ'44'|? 6Š+x\G5L_l$$$py;x9;wsÇg]C Zm~c~ԩp?e֭\rŧ݅=8b׼1tPz۾F0`ӧ绎/^mYvPb[}mVU,\?5`ޑ_Ic0u$_B9p QX%22իWY$'dɒ[[RHyUBj*2#5A("bkٸr .]rN^ʑ#G|( Gڵky>;&EDpx V^C=Dn޽;رckŊErF׮]Yf pka+V䫵) +f%Zw/@٥ݾ޽<,[˗sutju+^}Q6;?f,]4G!_ny+--/ÇNΝi۶-ZYz5VvѵkWt:f;wߢ:t(uˬZzO?3h { V^ͦM'lٲl6u+wߥf͚޽-[/_ԭ[EQhѢNb1x`uth4vڥ SNNs=o8'A+ nY oSLuhy&Æ cTP5k֐(L6G}%Jb N:ԩS߿? ZjQ?y$O<O=Ν#==f͚}vV={ꫯǩSGu6>~8˻} 6OǏϨQ)Y$&L`ܸqzm۶a2r ;v<9Iްnjx !5 ̝;ҥKtiӥdUZ˂z*G嫯aÆNo,X5kh۶Ӿ3k,]FRHHHPCtҥj~Gnݚ{Ǐל0a'OfժUN\t'|'RR% 鰰0wbŨTK,QF 8w}ZSLzɦM3gNoN? n ́h޼WV,-of,yoڵkV7Yp.\ŋ`*U+#ڂnQ+WZjE˖-ӧC !,,;vPlYѢj 0@ ]ɍ7xjy9Yn]9Z#--.]`4h4?N!Eƍ׏Ν;3c Ed2kSԨQ͛7~k<[Q?sԸZN eOsx-]`Y&'Ol6=zлw fcR ~鞲 z[^yoy7Xx1֭4LDFFk9 DXXQQQ;wcd2ѤI^y'nDD,NDDSXv-&Ç3{-^A߾}a.)ӅW:u_Z+h !*s?2}+{LfBFhԨ>,ŋnݚd~\?SNDN233}Z60!!_&M873f III+V,ރNS y骵\`vk۱ ԧ2JYKmasELB)[,{jժ.](FɈ#۷/Ŋŋ?-[2qD:vȣ>JR8y$r[#`0رcٸq#qqqVѣGiҤ =?<_|[fԨQL&4 /۵Flv?Gnzz*-[/C !&&1cPJky\EhFW+K݀1115_WrӣGuzΫJΝٲe nݺbkѻwo֮]Kjj*-ZPWU};dRSS[K}l۶_VK^նO۶mZNe˺}|i@'|hw*U `4j-ZsNك^'<<@EVU;Pݭ믕fCO||[q SNNKڧѲel6ʔ)N'_nIڵ&;t{v]fMj֬ޫO>N.USz@תQ5jȗ`aDp!p+h_wN-,yzx "BP8 @xBNp+B!)_+ B>X4 BpW`ZټyC9~RDp!@j=˗Y)ПKQ:u4XDp!lf}Y2/JOϒ۴( _^Dt((j~ 9\#9?,p;+Ц"y sYXoJ#[\AAe@, -ޖ *\ACŵdױ(L6/ˊT +:0\fs6/ M :=:." B |ut:O΍7ĥ2%%Dwʞ>: bqڜ,6nXXd }Cyؼy3=P @_DpE֛h4ڱuVsGEQPpw"}^j4<4Woxl b BY!xN\Aa:E a +٤2-؆Njj*H,d7XFl2}ޞ͗}d*VHɒ%)]4*UbϞ=~#h" [VE!((/nY,IKKsHMM%%%Ej(YYY:(׮]#++Kٳvś IDAT]Ciӆ7oMLDp9{,#Gd͚5RVگJ=HLL[nL:UW2fuF^1bW\AQnܸȑ#֭;w楗^R{qիڵ+:ubʔ)N?ݻ7=z !!E;a @R(UӧOh4rADOHP( aaanL& [VʤI0`@r SLaذat҅,Ο?Zdɒ|DŽp1u:PZ5KF#z`00k,Q>ׯӾ}{j֬ɰaøvm۶婧bر8q={A߾},dEQj4iҤP-F(tR "[,^c_]2339z(Ce\xPқEzz:k&22 WÇٷo{!66+f֭[h4hZ}] «]g̘Aj߿?k׮eذa̚5e/I֭ygX`*kL2w}BĜ9srtfQFٳhU0ٿ?Ŋ'##CmLTXѩ.KV'xB xJ,tSO1jԨB lZP 6maXرc AW>}0sLG\\ 4`РAԯ_-[xZm6: &н{w=/ Aظq#  sbŊ( ǏWxs=:uJ;BCC)[,?<)))j>Νc<+:l6ӿ<@Ϟ=% +yhfV+e˖姟~~DDDX,R!+lFQfMj֬IfXntؑ˗/qFzdY׏'ңGud0`C !,,5j_ڴiyok(k44JFFf^/C&"##WE>M 11>}P|y[m6Eaԩt ɓ'3|puZQ>x(cAWT"p5kFf (4nܘƍ_V-ܶ/22Ru5ۏ[mBO||mw}9zE%oHxHAWo?TI_Nig,!iJ ԝ h4q JpPv +*UJ](D%_x7\!ncZ6; ٓb,*?E[-(իWyxk)7Faȑ+W.ׅhҼ\t:/T2~^租~E7rSv7o̺ujSvUƀ uQ\8|0/_~n\;w*&_xF?@FիWT\]z^~e5:Ԡ L&݆Dp ڴiëhU:ұUI|sz~;^_b0rl6F#=WX!fc^78p  PdR|-\kmiii<2PW*LuM\sRsZ8 R K~m p,Nh4HFFmeSI!dff؊ uٷ 1v~̉'$55{RNԩ^Gpq"##IKKc׮]tԉH\ݻh4nݚҥK{sw# /Jװ 01PT(_,ݺu#..^ zGyʕ+c4)[,'Ofذa̘1DbccIKKCq}?3c 4ibyTWǿ6h\#%+QQC4[lGG35'j\N58nH&&1nD,M_nz9vWWU}Ezz:n݊t:ryd2vbx{{;t i^>J!\GOa8B>[y&bbbwyqqq ک3w6l؀'NI& 1~xE裏{aɒ%Yp-oO>ũSrJlذ=͛T*1eL4n[l2o#HpkZ,\w QVVKbƍׯ P(bѶsf燧O"33M4O?_cƌAϞ=Ѹqcȑ# Dvv6ΝL6mڄ>/^D"q+q;`7oތӧO׸***]Dt\xb;˸çkJv/^\,jrܡbÇ{ԣwu]}|TwbժU ~W 88ʵg}C(--JÑׯ_!HPYYaÆATԩSsh4j{p-50P*V74 W.___n_ZP(P(R駟jKBw߹uͶǏɓ'0_&0 JJJx{-_;GLz֝xr(86lXbhZ߹r P(/EEE֭FvʾgPTZթRDUUf3F#J%Vr9R)4 qA̜9'ODtt4ҸP7˲XԻuתt:"""(ZY\]bΜ9UvfLSNaΜ9H$(,,ĭ[еkjsf]vEyy9yhwmC9[2|M\ubq*Ig ;0s0 7u6+H0x`$&&b„ Af=R/^`এΝ;nݺQFh޼9N:Fܸq޽;z=t={Ç'|MHR( nꬮu!HpyɊ+#1sG٭>W\  6 ** o t:"6 ߿?ƍX,XAAAurrrm6Ntkyb!v'D$|-_CѾ}{pܻ 4i&Ngʕ+زe $ O4xyyW_źuX\~۷oGdd$T*;ƭ ʕ+Xᠣ.ĖW.`w:G ۲kZ̛7ظq#:uD0L2e :v-r0ظq#[}7W yy?"77mםc2 y$/'۷ǡCWnA-g0g{nD2;A"ѨQ#:8_V5t0 uRUUFJ?yJ*9[TؘvOU@Uh-\}¤I0vX$%%a8~Caٶm"##N:a:v`pZ&> A.AwbWks\2 cǎEHHd2݋QFʕ+ܶKFm-s}GEff&xk\ %7E^B#..Æ ۹& ;woA/U۷cРA6l=]ý{h"aҤI4h_Ml?">>on޼ԩѯ_?DGGcʕDVVk{.sul2Xnxb<~8z>,ڇJNQTgn} |EE&O۷ocx7qh4fc2d&N{ݻiӦ?رc1p@$%%a۶m|^'OFTTz1cXn;w3f@pp0"""jIii)*++i&vҥK/[l pe`ǎV֯_WBT" >>>GVlM^YYKV[Epb6 r)YG"JJJ#|ׯ_5qӦM8s ^{5E\p۶mÓ'OФIVϞ=SN",]qqq7n`׮]HHH5kPVVW&Lٌ?ȀH$hɓq-\v | {b1ݻ믿lڈ#8L&̙3_[d2vލsYEdrlƵk0`jD .A#BD7oDL8 iӦUV8}4BBBХKjeffB$!99jQRRr޽{soA"pߗH$Xf Ν;ym۶vÀb41d=ZVWf0 ]-+8??\ .Ad=r|G1cN8;w8q'~&zdiӦ8pUCL`٭KA͛V-ݹ"ڵDDD`͚58p[ 6F 894{4 n߾MK%<UHsml-Z`ҤI믡PXXxGQQ޽P"$$K$6mjժN!>AKuJˆ#p%SNXp!gI.[ }/rtM6]?.\۷oM6ٳ'CسgUFTxݻw/{nT^зo_ĉ?Ν;d1`u:mڴܹݻw޷n{"Kر;wv͛7P(%TT!Hc˃\.wە  @lH )~~~())f{[ ٲh4P!8 xWTA.A2ː`׍YRR7n@#** 4GJd/PllXzlbo>T* :#GJ˹IA/2Ljܾ^O?,sUUPQQ`uh4V***`6sf30 j5˹mKNCYY*++֫W/磤O>Ŗ-[48!2 !Nf"ZH`HJJB~мys-btYlٲiiiƣGvZܾ}2 [Ƃ |[n݂lF޽1{lxyyX|9Ǝ{ѣG… !ɦ !IDAT/_ƍSf5 )))PN4 SNEaaF&^ TJ+ \Kbۍ77`߾}1}uuIQQ>C;jA$!%%3gr9ѣѺuk,ZFsAQQVZBoL&ôiӠ둔L2<Ñ3f7ng:t8p1`BTb3 A A4 g V`00 F,32K*2X,fdFDD0?CcϟgjǾ[F.[}ń1jLnn.w|ڴiLrr20 3|O>V]zfСLv///f 0lnzZjNﯽfȐ! 0`p'OP$p ! T*qqqHLL:maݰ9s&VXᑲj .ƌ 43f@dd$R^,ZF#lFPP˹֭[s4i?dgg~C=`2 h}_&!##pyDGG#<<Çoz2LׯRvlF$%%h4d-aЊl\ bHĭRӧ͛ܬ,l߾}Ř1c uD"̝;&Lٳgo>9OJFAUU|}}79J|||мysܹsAΝ~.dX,R{W^yNF"==um4ve\ .Ab`GnHH222___ j ֪t BRRbccǣĉz=1|dffbܱ{SSS|DFFr߿"وܹsEn~QQQ2dZ՛ӈ#tRt}ŀM6VX///DFF"<<{ƌ3_ϑscOXɅLxC<˃B$,XRa…XjC ב4Zqi#""}^AA?a0\Z#G@V#22zjܺu ;wѣGj!vq.\l( ǣE37+V@ii)V^u$ݹ?F&Mq ό7uO,C #G:f͚qcm)J$''W;WTK.VgZg-0eݣG`OLd=[AK;<0Ɓ_U|T1{]Iu %%W"m۶5Nҽm{MGߡES Dl6ɾDmo?-AD]?b1j5tuA.Z[aLKK àjV0?~=Bg-AC*C )) ;v2ٳ\&RJ(qmn{"O DM9P-q xّ_(bW"/JګGM#Hp eg* mididings - Table of Contents

mididings - Table of Contents

mididings-20120419~ds0/doc/core.html0000644000175000017500000001642411743667352017022 0ustar alessioalessio mididings - Core Module Reference

mididings - Core Module Reference

Functions

config(**kwargs)

Changes global mididings settings. This should be called only once, before constructing any processing units.
Possible keyword arguments are:

  • backend: MIDI backend to be used:
    • 'alsa': Use the ALSA sequencer (this is the default).
    • 'jack': Use JACK MIDI. All MIDI events are buffered and processed outside the JACK process callback, and will thus be delayed by (at least) one period.
    • 'jack-rt': MIDI events are processed directly in the JACK process callback. It's not safe to run Python code in a realtime context, so it's recommended to avoid Process(), which might cause xruns (or worse). All other units should be safe to use.
  • client_name: MIDI client name to be used.
  • in_ports / out_ports: Defines the number and names of input and output ports, and optionally external ports to connect them to. Possible values are:
    • Integers: Simply indicates the number of ports to create (named in_n and out_n, respectively).
      Create two input ports with default names (in_1 and in_2):
      config(in_ports=2)
    • Lists of ports, where each port is described by a string specifying its name, or a list/tuple containing the port name, followed by any number of regular expressions specifying ports to connect to. These regular expressions are matched against the full name (clientname:portname) of each external port. ALSA clients and ports can be specified using either their names or numbers.
      Create three output ports with custom names, and automatically connect two of those ports to other clients:
      config(out_ports=[
          'foo',
          ('bar', '24:0', 'yoshimi:midi in'),
          ('baz', 'LinuxSampler:.*'),
      ])
  • data_offset: 1 (default) or 0. Determines whether program, port and channel numbers will be in the range 1-128 or 0-127.
  • octave_offset: Offset in octaves from note number 0 to C0. Default is 2, meaning that note number 24 (that is, two octaves up from 0) is designated as C0, and "middle C" (note number 60) is C3.
  • initial_scene: The number of the first scene to be activated.
  • start_delay: The number of seconds before sending any MIDI events (i.e. switching to the first scene). A small value like 0.5 can be used to give tools like qjackctl's patchbay time to connect the ports. A value of 0 instructs mididings to wait for the user to press enter. Default is None.

hook(*args)

Registers "hook" objects, that can be used to extend the functionality of mididings.

run(patch)

Starts the MIDI processing. This is usually the last function called by a mididings script.

  • patch: A single patch.

run(scenes=..., control=None, pre=None, post=None)

Starts the MIDI processing, using multiple scenes. The SceneSwitch() unit can be used to switch between these scenes.

  • scenes: A dictionary with program numbers as keys, and Scene objects or plain patches as values. Values can also be tuples with two items, the first being an init-patch that's executed once every time the scenes is selected, and the second being the actual patch that processes incoming events.
  • control: The "control" patch, which is always active, and runs in parallel to the current scene.
  • pre / post: Allows processing to take place before/after every scene. Does not affect the control patch.

process_file(infile, outfile, patch)

Requires mididings to be compiled with support for libsmf.
Reads a standard MIDI file, processes it, then writes the result back to a file.

Classes

Scene(name, patch, init_patch=None)

Constructs a Scene object to be used with the run() function.

  • name: A string describing the scene.
  • patch: The patch defining the MIDI processing to take place for incoming events.
  • init_patch: An optional patch that will be triggered when switching to this scene.

SceneGroup(name, [subscene, ...])

Constructs a SceneGroup object. This can be used to group multiple scenes under a common name and program number. Each of the subscenes should be a Scene object.

Define one scene (song) with 3 subscenes (parts):
SceneGroup("Example Song", [
    Scene("Intro", intro_patch),
    Scene("Verse", verse_patch),
    Scene("Chorus", chorus_patch),
])

Constants

Event Types

Every event has one of these types:

  • NOTEON
  • NOTEOFF
  • CTRL
  • PROGRAM
  • PITCHBEND
  • AFTERTOUCH
  • POLY_AFTERTOUCH
  • SYSEX
  • SYSCM_QFRAME
  • SYSCM_SONGPOS
  • SYSCM_SONGSEL
  • SYSCM_TUNEREQ
  • SYSRT_CLOCK
  • SYSRT_START
  • SYSRT_CONTINUE
  • SYSRT_STOP
  • SYSRT_SENSING
  • SYSRT_RESET

For use in filters, the following constants are also defined:

  • NOTE = NOTEON | NOTEOFF
  • SYSCM = SYSCM_*
  • SYSRT = SYSRT_*
  • SYSTEM = SYSEX | SYSCM | SYSRT
  • NONE
  • ANY

Event types are bit masks, so when building filters, they can be combined using | (bitwise or) and ~ (bitwise negation).

Event Attributes

These constants are used by generator units and the SceneSwitch() unit to refer to an event's data attributes:

  • EVENT_PORT
  • EVENT_CHANNEL
  • EVENT_DATA1
  • EVENT_DATA2
  • EVENT_NOTE
  • EVENT_VELOCITY
  • EVENT_CTRL
  • EVENT_VALUE
  • EVENT_PROGRAM
mididings-20120419~ds0/doc/patch.png0000644000175000017500000003716511331672377017012 0ustar alessioalessioPNG  IHDRRW@<sRGBbKGD pHYs  tIMEސf IDATx}w\Guvv R,"*HPT,H,X5FcI_O4& ذD%cAiҮ_ٽ;_873;;s7|SXA p A@@@@@@ڃi= wNćXVVhT*D"+˅Si<"Je֭r9Ų gPVYBJQMכh4?c`` AiRtǎX,&V{즴e¦Te<X4Ҩ[EilK6Z!Y$6ZaR@ޅĿ-= f^m5Mv>̶G&ۚE昩 ,!&<Ȇ%5-UښT3lK9%"ToRaބ1|pc4q>AEQ^omm \Ի1yP+h2¸W5{eX B`0444466|WWW+** uwwdSNJ{&{'Qo$hfNjeeh~~`8* ȋ2(0LѰX,E0|>h4`>}z߾}:EѮ]FEE]xq޽TY3))`09rOt&MZlӧ###z=4i@|Gx'Nˣ |T?9 8 ŲϞ=ܹ;wbk;;;kkk\ eee}ŋe2D"s*J o߾]v]vUUUE(>/ g̘PrSPoٲQ$5k?/uM{{{xXXXv휝JK++;rOOݻ߽{|$ fE&-l6է|vJ{T?7DO ϧMI3SDV-Wwfkғ'O>}`xGGǓ'OΙ3#$$/ .TTT >K.cǎ :yV?qgX_~}PPЄ ())yiq_f͌3BaYY 0؁,Yq={5*77~~~<v'Gz?T*ՙ3g>sADEE;0Vb0 +--8!A@|5jԙ3gzGj999999VVVh(Λ7`08>dȐ .\~qX\RR ɓ}||<==ݻG^7C ill6O>*jӦM...ϟgXϟn֬… %K8p[lR4MVVVBBߋ/z葑qʕGnD1)QMi8Β%K6nܨ qYfzɓ'6SEyM6 n >N(7o&''6bŊLAPMOO4hPzz:&(t:@cIII@CQ\H$B0 b^jW1cF=nj3qDqmmmm>}L&djAɓ Μ9O></))i̙|XUU"##[lYYY) mVRRV EQQ`rEEEf744<}ŋzٳg5ׯ_?~ 'Oy%???TJgPah =Ai1VB:d2@ ˕d䧝;wvww6lhx,ۓbXoΚ5 EQ@T*k0FcǍJJJVZ Ț5k-Z$_~ETnذ@(jD2`5rG7ښffffdիWj>9YWWT*'M{]vyyy۷oǎsssU={ϟXtfH%qℽZ :~8H$6m*//gX1߹ܹ$cǎez_~y`nnn666A`֯_6mx{{ СCMM Mq|ԩUUUZ庹FLjժOT*.kmm +?~x zݹsgVzkk[jZ^+A4 &~B@ڃvвX,PIigg/<HlsA8::{2ЖjfNm4~Ύ&uFPؼysZ5o޼ɓ'WAQussy&9/PkNH{6Q#DDdƨ4&hFD۶mmllJW c.'aO^e=ðua6f(A@ڃd$L,TbvYN#KmFQә$Kޒ6-07?H{Qَ)!MuX̓G%32A30dbf)R mN{⭟/C$Pg1ZyLOd4󗎰酯!j'B{߿z.{aobb[p#!7399p/V'Nbx~&̚Lɉ#^~SMlJ^/--x+V8d@0|p{4={611\?`%%%-z봇 Haa޽{@ i) UeD682{C\@ ;wnxxx^v# .]RT*<466ڵcǎaTر#22%bڷo[N>!!!#FYǘlAZ dTjO9B^x.;tP8o" @ [Srr60^M*0iyoS$0̤闹rZpwf9bƤdfɸ,֧61WNZj9Mܤ79$rr؅v ;46gcF͕[ tokzO4v!cD">O-'B$ BZ}y*4d#k=znAl{d 2RJUÅYڄe 5=bXƚ/(>|B't҂(loBwΝ;'q^:kwT"sC[ ׮]K* '͘1###$EsرQFQ/Y` &,,<ҥKPE (WTƍuXڿf\}#GlcFKm|> tĢ^ӧ߽{eee&`y<^\\܃>׭[;wʕ+;25?MIc2#$..&5p/e't&2+4STTTRR"@;Xw>x*++ EÇoߞb啖Ď;BCC=<<>9sD͛Ri||H$ yTA  \x/0 BpƍnnnBعs;w$ɐ!C@:48œ;wfGGGGEEG at:E/]tٳgD]_)))[P(>ܧO[[}k0E333\n=zꕟ_CCBB/^&˃qgٿF9}cǴZmΝ X  ޾} 01r .uٳ/j={1c{AW^=|-ZX070lW\H$@n׮]VVV\xQDEE͝;Yzذa|>\.799C,o߾\.Weggwiڵ .cƌqqqyҞZ^`'O)((7nh@1c=zD>}l6… {yp`X iiiǏh۶-&vڏ?(l߾ի<ݻ&LhݺcAAu@^Rlٲ 80e__nݺmڴi8qCeggggg>|G9|B eDRIZP%B t;w$Ksf6ݦM+W,HbI~Jrҥݺu:thaa ӲbM{ _;w.c!''gxzzX,WWWoooHM6S" <A۷oo޼@tuX,777Ք;@CA444۷:!!ܹs999C A͛sݺu+Mp.]秤EQ޽{ݻw*PuaSN={vll,ha@6lX߾}ܹӲe˃Ս=:++ (lՓ'O,Yk.LTUUuUVEFF}+2338ЪU+R4VZjJ}嗍DDD?~<$$h4nݺ5**̠vڵ'N8;;3I.*ʥRquѣ+*****:t@̙3d6l0ww 6~?l6رc SAAQTTT888qqqh4 B" }Nr}H4zh(*k4*|4++֭[o1 UUU{ٳgOhhL&X,VeeƍϜ9ӥKAZmFFFTTLESu&''O0a-[DD[t~ʶlٲxbR@w…D[[[ ^o䷇xvǓJ@ J=zMH4UToN{d\.W.T*HDXFcTT322vZ\\ r (GR(AAAAӧS}i1|Y,/^T*]xbAjkk\.yA._!C mٲ%bٳnnn7n(++#QTjZ6e eѤO=HCJZJR , E~| ÌFgςpر3 apdFT~_|;iӦ~>0799q j4јػwoX,'A@ q\>~֖kԩ.] G$33>#EBФ[``s4&prrZlYbb3ͮbUUUyyy۷Gرc>>>666@R׃gϞ "0# A7nX,LV]] B ؁7/Di8ݻw_|qĉ<Aڶm[VV6cƌN:ٳ4yӽbbbg#( -8iӦ (j4ӥKS 3g͙3윜FpBZ=o޼ loo~Ϛ5jwr]vڴiÆ CwރvqqU*7nAP77tFSVV7tmŊR0F: ͚5ST\.wmڴ)))),,9sc-}%}ť  MII1 ͛7駟ƍעEe˖egg#^2z^AAArr2Ϭ7qEsB@iڴfIDATi~\A&%%.Zdٲe۶m 00pԩcƌٻw{"EQ\>cƌr8N__zرcgΜaX- [~}MM d2Yeeٳ===޲'RSSA~~~`"aXhhѣ,Yp~f͚Ѳe;vX&MԩHѮ];wfgg߻w[nz%17--h4Rbѣk֬@uqqqzϯO>QkGGǭ[˗,YS%п^/6lpypUFDVB~ BC]lً/lllv޽zj`q:x`+++믿.Zݻ ,x\.9 ;A̚5III@OKDbbÆ 81x`[[[s@iJl6O> 781a„6mFm۶ \R.߼y`0x3g8ޭ[7pLn:Al޼yɒ%K.=x`tt4ظŋ'''=֭[w]Je4###,Y<tlr|ΝK,~1cݻwݻGzPLb͚5MݻwуZ¬cd5 ]֮];̙3 ]SSSWWhjkk /^Pz^k4zRY7T*@k4`04M}}}]]^ohhnu:]}}}MM/YYY *744'$$;vAP1XP(5 (jz^* FBP(ʿA5_SaZVTjZ7h4WqVjM : <<<++ ܧ@vQQQ'Olʚd2 SBrƍ{=WVV[IcP@nnnNT*USxOTԿVzTooo{!NG"MHҚOJ%0 XRˁQՀh4wuHZ-Mej4ACZ&9׮]KMbŋE)8^71Sh:<vM>)\76'6d9.>b64 6m"l Pؔ]a B‰Knn>݊vES?M1<\ͥ9|cgggS&,M^ r,*ig0**A8bp19̕[uGi\#^Ɲʩ.f>3 >Ii px@Qwi.K4]v}dy9L"L~̕x[ 3k_A w*6eL۷333z}v7wٶm-e0oaM^ZdKGo {as_[n`f] XXPP.3B!zFMMMff6?:~ڵCkEϾ-Z8s ܃sS f͚UWWGy%ljWb80^/@4%6 4NNNdd%Ļp>|ǎ555F3YfuuĈ%%%H$ΝuvvF1MxՓ&Mt{pOԪU+zwϞ=Njhhpvv8p`=X,޽{ϟ?r\.zy󊊊Iy$qvvt˗gΜr233O:USS#HWXXfp &L8p\XXXAoڴ n޼x<޻pR L>IT|>lΝw1 nnn{ѣǏ> R\m2/hyEDD 20Xe˖@nԩS]&9rAL Yʫvѭ[71 ;zA(Ax}~ݢEw""gҤIQQQ=zٳ@ ذaéSN @ئM &h%{+W'))UVzx(N6 Z|عsgЛV?kjjr9 jn$$$"P/JҶmx<8啖%YǤ$+++N'0`ӧ6 A@~sLË۴i3nܸ@+++rfT* 88s%222ׯ_gϞ>}L<ƍ'O z}cccLP^^~խ[ .VqT(!!! #\.?uԈ#&;i}4Ϟ=C޲eKϞ=cccU*Byyc0+@aXDDĜ9sb1G@e-Zqƅ &''#F:uԩSAjͩSGGݻw(P(>KSR-[w7nԩ)>x`T*yfrr2Iy.{̙!CVbx˖-dMNj XP3( +'N(,,۹sĉ@Wbٳ@WdZs 333333###+**h@,iWM͕+WlvLLҥKm >-D"77'O׿6 ( T񉉉ٺuk۶mG`Ɩ h̨po߾w}&D TcH ay|رcG úvڢE B ]]] ڷopB( |~nn.=0B.㩩;w:1uG=zs wH{'8B06R4==}ƍ0 cX͛7d:ϑAocc#؛@yAƎ,+++RrT#LIIILL<۝UUU93M6$[S@ޥ\5HT*Ν;۵kW]] Dŋ3<z=LFir>|H)[WW^WWpW\ oLبVb1yPz7 BRM [N81pŋdPR{L|YNM J@h. Itcǒ;uڹ| u+E*ijU@ڃh{+++>K{```BB„ 0 )evP۩[ @׷lْfӧgϞFjMgee%+$$$<<|ҥ6jԨ)Sܺu+ 7/_~qLL inuUa})u \.766vܹiiig6V?.˩igϞ=pc8n4_xhMFbٮMߎE"QPPPhh7|_ߢE~aҥEϟl0Ȝ}4K=wUDҹsg &S&.Ց#GI]tR"wrr?~JJ gϞ]ly^ 0pcǎ-[H$z+[U2YNUH$SLꫯJe۶mJ҂t:X_oof͚:u*-- AUV<͛;wܼy3N}y*ӌ! A@4ICG.!!A* @ SRR\R\nbN3gNHHÇU*stt4p6lL&#ctaƍ*J((N!!!gϾ{\.lf͚'Ny򤵵uTTT||VjN:҆T*MIIqpp͍F5k6os1cTWWFA/ѓdFQьTtrܹ۷۷0\>sVZUTT*jpظ}vkkQFٵhRX7 #F6]t#G:ׯߥRiHH8ohx?z}O>sss;v|mۜvMx31 ra0RSS;vH*HOKzQҩu=(JjGƌCF #5WT 74%UE{tVVVNNO?,L*#R_)9!}ؠ߿Er|>_R {4iRbꋓ%t,ť$/"&3t%'||צMzwIB򌌌2,|}}GPڃxM)dbvOɝJż13!OgΜyUfTk)'"./k䰁i>35-(풉jTByѢE&cdf- -4yL!A@@e>?53רrs+<\nd&|T°Q̚d;㨣%ΤFmB[,yh-k & Ë/***hYMw6b.rꅙe2D q^e> ?NtdȌy6Wz Q*P i5{{۷=zԤSI{727w.o  |3NAZjcǎs_zx5ŪWaxX AR:::I&}`vvv0=8 A@@@@@@ڃi=VOIENDB`mididings-20120419~ds0/doc/extra.html0000644000175000017500000002214411745112742017177 0ustar alessioalessio mididings - Extra Module Reference

mididings - Extra Module Reference

Units

These units offer some more advanced/specific functionality than what's provided in the core mididings module. Unless otherwise noted, they are defined in the mididings.extra module.

Note that some of these units are implemented in Python using Process(), and are thus not safe to use with the jack-rt backend.

Harmonize(tonic, scale, interval, non_harmonic='below')

A diatonic harmonizer.

  • tonic: The tonic of the scale.
  • scale: One of:
    'major', 'minor', 'minor_harmonic', 'ionian', 'dorian', 'phrygian', 'lydian', 'mixolydian', 'aeolian', 'locrian'.
  • interval: The number of steps to transpose the notes by, or one of these interval names:
    'unison', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'octave', 'ninth', 'tenth', 'eleventh', 'twelfth', 'thirteenth'.
    It's also possible to pass a list of intervals, to create multiple harmonized voices.
  • non_harmonic: What to do with out-of-scale notes:
    • 'below'/'above': Transpose by the same interval as the next on-scale note below/above.
    • 'skip': Ignore note.
    • 'same': Output note as is, without transposing it.
Add a third above each note, based on the C# harmonic minor scale:
Harmonize('c#', 'minor_harmonic', ['unison', 'third'])

FloatingKeySplit(threshold_lower, threshold_upper, patch_lower, patch_upper, hold_time=1.0, margin_lower=12, margin_upper=12)

Creates a floating split point that moves dynamically depending on what you are playing, allowing a region of the keyboard to be shared between two split zones.

  • threshold_lower, threshold_upper: The lower and upper notes between which the split point is allowed to move.
  • patch_lower, patch_upper: Patches to which notes below/above the split point will be sent.
  • hold_time: Specifies how long released notes will still be taken into account when determining the split point.
  • margin_lower, margin_upper: Numbers of semitones specifying how close you must get to the split point before it starts getting pushed into the opposite direction.
Split the keyboard somewhere between C2 and C3:
FloatingKeySplit('c2', 'c3', Channel(1), Channel(2))

VoiceFilter(voice='highest', time=0.1, retrigger=False)

Filters individual voices from a chord.

voice must be 'highest', 'lowest', or an index (positive or negative, the same way Python lists are indexed, with 0 being the lowest voice).
When a key is already held, and then becomes active for this voice because another key was pressed/released, the note will only be played if retrigger is True, or if the time since the key-press is less than the number of seconds specified by the time parameter.

VoiceSplit([patch, ...], fallback='highest', time=0.1, retrigger=False)

Creates multiple VoiceFilter() units to route each voice to a different instrument.

Regardless of the number of voices specified, the lowest and highest note played will always be routed to the first and last patch in the list, respectively. When the number of notes played is less than the number of voices specified, "surplus" voices will fall back to the highest or lowest note played, as specified by the fallback parameter.

Route up to three voices to different channels:
VoiceSplit([Channel(1), Channel(2), Channel(3)])

LimitPolyphony(max_polyphony, remove_oldest=True)

Limits the "MIDI polyphony" to max_polyphony.

If remove_oldest is true, the oldest notes will be stopped when the maximum polyphony is exceeded. If remove_oldest is false, no new notes are accepted while max_polyphony notes are held.
Note that the actual polyphony of a connected synthesizer can still be higher than the limit set here, e.g. due to a long release phase.

MakeMonophonic()

Makes the MIDI signal "monophonic", i.e. only one note can be played at any given time. When one note is released while another is still held (but silent), the previous one will be retriggered.

SuppressPC()

Filters out program changes if the same program had previously been selected on the same port/channel.

PedalToNoteoff(ctrl=64, sostenuto=False)

Converts sustain pedal CCs to note-off events (delaying note-offs until the pedal is released). Acts either like a regular sustain pedal (the default) or like a sostenuto pedal.

LatchNotes(polyphonic=False, reset=None)

Makes notes latching, so they will keep playing when the key is released.

If polyphonic is False, pressing a key will automatically turn off any previous notes. If polyphonic is True, each note can be stopped individually by pressing the corresponding key again. reset can be a note name/number that when pressed stops all currently playing notes.

KeyColorFilter(color)

Filters notes by key color ('black' or 'white').

Panic(bypass=True)

Generates all-notes-off messages (CC #123) on all channels. If bypass is True, these messages will be sent directly on all output ports, without going through the rest of the patch.

SendOSC(target, path, ...)

Defined in mididings.extra.osc. Requires pyliblo.

Sends an OSC message. Parameters are the same as for liblo.send(). Additionally, instead of a specific value, each data argument can also be a Python function that takes a single MidiEvent parameter, and returns the value to be sent.

SendDBUS(service, path, interface, method, ...)

Defined in mididings.extra.dbus. Requires dbus-python.

Sends a DBUS message. Instead of a specific value, each data argument can also be a Python function that takes a single MidiEvent parameter, and returns the value to be sent.

Change FFADO output volume using a MIDI controller:
CtrlFilter(42) >> SendDBUS(
    'org.ffado.Control',
    '/org/ffado/Control/DeviceManager/%s/Mixer/OUT0Gain' % DEVICEID,
    'org.ffado.Control.Element.Continuous',
    'setValue',
    lambda ev: ev.value * (2**17)
)

Restart()

Calls engine.restart().

Quit()

Calls engine.quit().

Hooks

MemorizeScene(memo_file)

Saves the currently selected scene number to a file when terminating mididings, and restores it at the next startup. memo_file is the path of the file to be used to store the scene number.

OSCInterface(port=56418, notify_ports=[56419])

Defined in mididings.extra.osc. Requires pyliblo.

Allows controlling mididings via OSC. These messages are currently understood:

  • /mididings/switch_scene ,i: switches to the given scene number.
  • /mididings/switch_subscene ,i: switches to the given subscene number.
  • /mididings/prev_scene: switches to the previous scene.
  • /mididings/next_scene: switches to the next scene.
  • /mididings/prev_subscene: switches to the previous subscene.
  • /mididings/next_subscene: switches to the next subscene.
  • /mididings/panic: sends all-notes-off on all channels and on all output ports.
  • /mididings/quit: terminates mididings.

AutoRestart(modules=True, filenames=[])

Defined in mididings.extra.inotify. Requires pyinotify ≥ 0.8.

Automatically restarts mididings when the script changes.

If modules is True, all imported local Python modules are monitored for changes as well. The filenames parameter allows specifying additional files to be monitored.
This restarts the entire mididings script, so MIDI processing is interrupted, and mididings does not take care of reestablishing any ALSA/JACK connections. If the new script contains errors that prevent it from running, mididings exits and needs to be restarted manually once the errors are fixed.

mididings-20120419~ds0/doc/basics.html0000644000175000017500000002106211745112742017316 0ustar alessioalessio mididings - Basics

mididings - Basics

Connections

Serial

A >> B
Chain([ A, B, ... ])

Connects units in series. Incoming MIDI events will be processed by unit A first, then by unit B. If the event is filtered out by unit A, it is not processed any further, and unit B will not be called.

Transpose one octave down, then set channel to 1:
Transpose(-12) >> Channel(1)

Parallel

A // B
[ A, B, ... ]
Fork([ A, B, ... ], remove_duplicates=True)

Connects units in parallel. All units will be called with identical copies of incoming MIDI events. The output will be the sum of all the units' outputs. With Fork() it's also possible to disable the automatic removal of identical MIDI events by setting remove_duplicates to False.

Send events to both channels 1 and 2:
Channel(1) // Channel(2)

or:
[ Channel(1), Channel(2) ]

+A

Applies A to a duplicate of each event and leaves the original unchanged. Equivalent to [ Pass(), A ].

Splits

{ T1: A, T2: B, ... }
Split({ T1: A, T2: B, ... })

Splits by event type. Equivalent to [ Filter(T1) >> A, Filter(T2) >> B, ... ].

Send note events to channel 1 and CC events to channel 2:
{ NOTE: Channel(1), CTRL: Channel(2) }

Filters

~F
F.invert()

Inverts the filter F. Note that for filters which only affect certain kinds of events, other events will remain unaffected when the filter is inverted. For example, an inverted KeyFilter will match a different note range, but neither the original nor the inverted filter will have any effect on controllers or program changes.

Remove CC events with controller number 42:
~CtrlFilter(42)

-F
F.negate()

Negates the filter F. Unlike ~F, this matches exactly the events that F doesn't.

Selectors

Every filter can act as a selector. In addition, it's possible to combine multiple filters into more complex selectors:

S1 & S2
AndSelector([S1, S2, ...])

Builds a selector for events that match all of the given filters or selectors.

S1 | S2
OrSelector([S1, S2, ...])

Builds a selector for events that match at least one of the given filters or selectors.

S % A
S.apply(A)

Applies A only to events which match selector S, but keeps events which don't. If S is a single filter, this is equivalent to [ S >> A, -S ].
Note that operator % has higher precedence than & and |, so the selector will usually have to be written in parentheses.

Transpose only events on channel 2:
ChannelFilter(2) % Transpose(3)
Change CC events with controller number 23 or 42 from channel 1 to channel 2:
(ChannelFilter(1) & (CtrlFilter(23) | CtrlFilter(42))) % Channel(2)

Note names and ranges

Many mididings units accept notes and/or note ranges as parameters. Notes can be specified either as a MIDI note number or by their name, consisting of one letter, optionally 'b' or '#', and an octave number. Examples of valid note names are 'c3', 'g#4', 'eb2'.

Note ranges can be specified either as a 2-tuple of note numbers, e.g. (48, 60), or as two note names separated by a colon, e.g. 'g#3:c6'. Like all ranges in Python, note ranges are semi-open (do not include their upper limit), so 'g#3:c6' matches notes from 'g#3' up to 'b5', but not 'c6'!
It's also possible to leave out either the upper or the lower limit, for example 'c4:' matches all notes above (and including) C4, while ':a2' matches all note up to (but not including) A2.

Port numbers and names

Internally, ports are always referred to by their number. When an event is received on the second input port, and is not explicitly routed to another port, it will be sent to the second output port.
If you named your input and output ports using the in_ports and out_ports parameters to config(), you can also refer to them by their names in all units that accept ports as parameters. To avoid ambiguities, port names should be unique (with the JACK backend they must be).

Miscellaneous

A couple of things you might need to know...

Overloaded functions

Many function and unit names in mididings are overloaded to have somewhat different meanings depending on the number and/or names of their parameters. For example, KeyFilter(note) filters a single note, while KeyFilter(lower, upper) filters a range of notes.

When multiple versions of a unit accept the same number of parameters, it's necessary to explicitly name the parameters of the version you'd like to use. Cases where parameter names are required are indicated in the documentation as "param=...". It's usually possible to name parameters like this regardless of whether it's actually necessary.

Add an offset of 42 to velocity values:
Velocity(42)

Set velocities to a fixed value of 42:
Velocity(fixed=42)

Everything is an object

Everything in mididings is a Python object, and can be assigned to variables, returned from functions, etc.

Add a fifth (7 semitones) above each note, route all events to channel 2 (of course there are easier ways to do this):
def add_interval(n):
    return Pass() // Transpose(n)

route = Channel(2)
mypatch = add_interval(7) >> route

run(mypatch)

Python syntax

In Python, line breaks delimit statements, and indentation delimits blocks. However, as a rule of thumb, both are irrelevant as long as at least one parenthesis or bracket is still open. Many mididings patches consist of lists and dictionaries, so line breaks and indentation are usually not an issue.

Sometimes it makes sense to put a patch in parentheses to allow it to span multiple lines:

mypatch = (
    Transpose(12) >> Velocity(curve=1.0) >>
    Filter(~PROGRAM)
)

Operator precedence

Overloading operators in Python does not change their precedence. This is a list of all operators relevant to mididings, in order of their precedence (highest to lowest):

(...)
[A, B, ...]
Binding (parentheses)
Connection in parallel (list)
~F
-F
+F
Filter inversion
Filter negation
Apply to duplicate
A // B
S % A
Connection in parallel
Selector "then"
A >> B Connection in series
F & G Selector "and"
F | G Selector "or"


In short, just remember that...

  • parallel connection binds stronger than serial.
  • selectors (of more than one filter) must be in parentheses.
  • when in doubt, you can always use additional parentheses.

Also note that operators can't be overloaded if both sides are builtin Python types like lists or dictionaries. In some cases it may be necessary to wrap those in Fork() or Split().

mididings-20120419~ds0/mididings/0000755000175000017500000000000011740266746016376 5ustar alessioalessiomididings-20120419~ds0/mididings/units/0000755000175000017500000000000011740106575017531 5ustar alessioalessiomididings-20120419~ds0/mididings/units/base.py0000644000175000017500000001517711740123443021021 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import _mididings import mididings.constants as _constants import mididings.arguments as _arguments import mididings.unitrepr as _unitrepr class _Unit(object): """ Wrapper class for all units. """ def __init__(self, unit): self.unit = unit def __rshift__(self, other): """ Connect units in series (operator >>). """ if not isinstance(other, _UNIT_TYPES): return NotImplemented return _join_units(_Chain, self, other) def __rrshift__(self, other): """ Connect units in series (operator >>). """ if not isinstance(other, _UNIT_TYPES): return NotImplemented return _join_units(_Chain, other, self) def __floordiv__(self, other): """ Connect units in parallel (operator //). """ if not isinstance(other, _UNIT_TYPES): return NotImplemented return _join_units(_Fork, self, other) def __rfloordiv__(self, other): """ Connect units in parallel (operator //). """ if not isinstance(other, _UNIT_TYPES): return NotImplemented return _join_units(_Fork, other, self) def __pos__(self): """ Apply to duplicate (unary operator +) """ return Fork([ Pass(), self ]) def __repr__(self): return _unitrepr.unit_to_string(self) def _join_units(t, a, b): """ Combine units in a single instance of type t, avoiding nesting if possible. """ if not isinstance(a, t): a = [a] if not isinstance(b, t): b = [b] return t(a + b) class _Chain(_Unit, list): def __init__(self, units): list.__init__(self, units) def __repr__(self): return _unitrepr.chain_to_string(self) class _Fork(_Unit, list): def __init__(self, units, remove_duplicates=None): list.__init__(self, units) self.remove_duplicates = remove_duplicates def __repr__(self): return _unitrepr.fork_to_string(self) class _Split(_Unit, dict): def __init__(self, d): dict.__init__(self, d) def __repr__(self): return _unitrepr.split_to_string(self) class _Selector(object): """ Base class for anything that can act as a selector. Derived classes must implement methods build() and build_negated(). """ def __and__(self, other): """ Return conjunction of multiple filters (operator &). """ if not isinstance(other, _Selector): return NotImplemented return _join_selectors(_AndSelector, self, other) def __or__(self, other): """ Return disjunction of multiple filters (operator |). """ if not isinstance(other, _Selector): return NotImplemented return _join_selectors(_OrSelector, self, other) def __mod__(self, other): """ Apply the selector (operator %). """ return self.apply(other) def apply(self, patch): return Fork([ self.build() >> patch, self.build_negated(), ]) class _AndSelector(_Selector): def __init__(self, conditions): self.conditions = conditions def build(self): return Chain(p.build() for p in self.conditions) def build_negated(self): return Fork(p.build_negated() for p in self.conditions) class _OrSelector(_Selector): def __init__(self, conditions): self.conditions = conditions def build(self): return Fork(p.build() for p in self.conditions) def build_negated(self): return Chain(p.build_negated() for p in self.conditions) def _join_selectors(t, a, b): """ Combine selectors in a single instance of type t. """ if isinstance(a, t): a = a.conditions else: a = [a] if isinstance(b, t): b = b.conditions else: b = [b] return t(a + b) class _Filter(_Unit, _Selector): """ Wrapper class for all filters. """ def __init__(self, unit): _Unit.__init__(self, unit) def __invert__(self): """ Invert the filter (still act on the same event types, unary operator ~). """ return self.invert() def __neg__(self): """ Negate the filter (ignoring event types, unary operator -) """ return self.negate() def invert(self): return _InvertedFilter(self, False) def negate(self): return _InvertedFilter(self, True) def build(self): return self def build_negated(self): return self.negate() class _InvertedFilter(_Filter): """ Inverted filter. keeps a reference to the original filter unit. """ def __init__(self, filt, negate): self.filt = filt self.negate = negate _Filter.__init__(self, _mididings.InvertedFilter(filt.unit, negate)) def __repr__(self): return _unitrepr.inverted_filter_to_string(self) # the types we accept as part of a patch _UNIT_TYPES = (_Unit, list, dict) _SELECTOR_TYPES = (_Filter, _Selector) @_arguments.accept([_UNIT_TYPES]) def Chain(units): """ Units connected in series. """ return _Chain(units) @_arguments.accept([_UNIT_TYPES], (True, False, None)) def Fork(units, remove_duplicates=None): """ Units connected in parallel. """ return _Fork(units, remove_duplicates) @_arguments.accept({_arguments.nullable(_arguments.reduce_bitmask([_constants._EventType])): _UNIT_TYPES}) def Split(d): """ Split events by type. """ return _Split(d) @_arguments.accept([_SELECTOR_TYPES]) def AndSelector(conditions): """ Conjunction of multiple filters. """ return _AndSelector(conditions) @_arguments.accept([_SELECTOR_TYPES]) def OrSelector(conditions): """ Disjunction of multiple filters. """ return _OrSelector(conditions) @_arguments.accept(_arguments.reduce_bitmask([_constants._EventType]), with_rest=True) @_unitrepr.store def Filter(types, *rest): """ Filter by event type. """ return _Filter(_mididings.TypeFilter(types)) @_unitrepr.store def Pass(): """ Pass all events. """ return _Unit(_mididings.Pass(True)) @_unitrepr.store def Discard(): """ Discard all events. """ return _Unit(_mididings.Pass(False)) mididings-20120419~ds0/mididings/units/init.py0000644000175000017500000000443711737362272021062 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings.units.base import _Unit, Fork, Chain, _UNIT_TYPES from mididings.units.generators import Program, Ctrl from mididings.units.modifiers import Port, Channel import mididings.unitrepr as _unitrepr import functools as _functools import copy as _copy class _Init(_Unit): def __init__(self, patch): self.init_patch = patch @_unitrepr.accept(_UNIT_TYPES) def Init(patch): return _Init(patch) def Output(port, channel, program=None, volume=None, pan=None, expression=None, ctrls={}): if isinstance(program, tuple): bank, program = program else: bank = None init = [] if bank is not None: init.append(Ctrl(port, channel, 0, bank // 128)) init.append(Ctrl(port, channel, 32, bank % 128)) if program is not None: init.append(Program(port, channel, program)) if volume is not None: init.append(Ctrl(port, channel, 7, volume)) if pan is not None: init.append(Ctrl(port, channel, 10, volume)) if expression is not None: init.append(Ctrl(port, channel, 11, volume)) for k, v in ctrls.items(): init.append(Ctrl(port, channel, k, v)) return Fork([ Init(init), Port(port) >> Channel(channel) ]) class OutputTemplate(object): def __init__(self, *args, **kwargs): self.partial = _functools.partial(Output, *args, **kwargs) self.before = [] self.after = [] def __call__(self, *args, **kwargs): return Chain(self.before) >> self.partial(*args, **kwargs) >> Chain(self.after) def __rshift__(self, other): if not isinstance(other, _UNIT_TYPES): return NotImplemented r = _copy.copy(self) r.after = self.after + [other] return r def __rrshift__(self, other): if not isinstance(other, _UNIT_TYPES): return NotImplemented r = _copy.copy(self) r.before = [other] + self.before return r mididings-20120419~ds0/mididings/units/engine.py0000644000175000017500000000240211740252200021331 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import _mididings from mididings.units.base import _Unit import mididings.constants as _constants import mididings.util as _util import mididings.overload as _overload import mididings.unitrepr as _unitrepr @_unitrepr.store def Sanitize(): return _Unit(_mididings.Sanitize()) @_overload.mark @_unitrepr.accept(_util.scene_number_ref) def SceneSwitch(number=_constants.EVENT_PROGRAM): return _Unit(_mididings.SceneSwitch(_util.actual_ref(number), 0)) @_overload.mark @_unitrepr.accept(int) def SceneSwitch(offset): return _Unit(_mididings.SceneSwitch(0, offset)) @_overload.mark @_unitrepr.accept(_util.subscene_number_ref) def SubSceneSwitch(number=_constants.EVENT_PROGRAM): return _Unit(_mididings.SubSceneSwitch(_util.actual_ref(number), 0, False)) @_overload.mark @_unitrepr.accept(int, bool) def SubSceneSwitch(offset, wrap=True): return _Unit(_mididings.SubSceneSwitch(0, offset, wrap)) mididings-20120419~ds0/mididings/units/call.py0000644000175000017500000000566311737711704021032 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import _mididings from mididings.units.base import _Unit import mididings.event as _event import mididings.overload as _overload import mididings.unitrepr as _unitrepr import mididings.misc as _misc from mididings.setup import get_config as _get_config import sys as _sys if _sys.version_info >= (3,): import _thread else: import thread as _thread import subprocess as _subprocess import types as _types import copy as _copy import collections as _collections if _sys.version_info >= (2, 6): _callable_constraint = _collections.Callable else: import mididings.arguments as _arguments _callable_constraint = _arguments.condition(lambda c: callable(c)) class _CallBase(_Unit): def __init__(self, function, async, cont): def do_call(ev): # add additional properties that don't exist on the C++ side ev.__class__ = _event.MidiEvent # call the function ret = function(ev) if ret is None or async: return None elif isinstance(ret, _types.GeneratorType): # function is a generator, build list ret = list(ret) elif not _misc.issequence(ret): ret = [ret] for ev in ret: ev._finalize() return ret _Unit.__init__(self, _mididings.Call(do_call, async, cont)) class _CallThread(_CallBase): def __init__(self, function): def do_thread(ev): # need to make a copy of the event. # the underlying C++ object will become invalid when this function returns ev_copy = _copy.copy(ev) _thread.start_new_thread(function, (ev_copy,)) _CallBase.__init__(self, do_thread, True, True) class _System(_CallBase): def __init__(self, command): def do_system(ev): args = command(ev) if hasattr(command, '__call__') else command _subprocess.Popen(args, shell=True) _CallBase.__init__(self, do_system, True, True) @_unitrepr.accept(_callable_constraint) def Process(function): if _get_config('backend') == 'jack-rt' and not _get_config('silent'): print("WARNING: using Process() with the 'jack-rt' backend is probably a bad idea") return _CallBase(function, False, False) @_overload.mark @_unitrepr.accept(_callable_constraint) def Call(function): return _CallBase(function, True, True) @_overload.mark @_unitrepr.accept(_callable_constraint) def Call(thread): return _CallThread(thread) @_unitrepr.accept((str, _callable_constraint)) def System(command): return _System(command) mididings-20120419~ds0/mididings/units/filters.py0000644000175000017500000000761311736103744021562 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import _mididings from mididings.units.base import _Filter import mididings.util as _util import mididings.overload as _overload import mididings.arguments as _arguments import mididings.unitrepr as _unitrepr import functools as _functools @_unitrepr.accept(_arguments.flatten(_util.port_number), with_rest=True) def PortFilter(ports, *rest): """ Filter by port. """ return _Filter(_mididings.PortFilter(map(_util.actual, ports))) @_unitrepr.accept(_arguments.flatten(_util.channel_number), with_rest=True) def ChannelFilter(channels, *rest): """ Filter by channel. """ return _Filter(_mididings.ChannelFilter(map(_util.actual, channels))) @_overload.mark( """ Filter by key. """ ) @_unitrepr.accept(_util.note_range) def KeyFilter(note_range): return _Filter(_mididings.KeyFilter(note_range[0], note_range[1], [])) @_overload.mark @_unitrepr.accept(_util.note_limit, _util.note_limit) def KeyFilter(lower, upper): return _Filter(_mididings.KeyFilter(lower, upper, [])) @_overload.mark @_unitrepr.accept(_util.note_limit) def KeyFilter(lower): return _Filter(_mididings.KeyFilter(lower, 0, [])) @_overload.mark @_unitrepr.accept(_util.note_limit) def KeyFilter(upper): return _Filter(_mididings.KeyFilter(0, upper, [])) @_overload.mark @_unitrepr.accept([_util.note_number]) def KeyFilter(notes): return _Filter(_mididings.KeyFilter(0, 0, notes)) @_overload.mark( """ Filter by note-on velocity. """ ) @_unitrepr.accept(_util.velocity_value) def VelocityFilter(value): return _Filter(_mididings.VelocityFilter(value, value + 1)) @_overload.mark @_unitrepr.accept(_util.velocity_limit) def VelocityFilter(lower): return _Filter(_mididings.VelocityFilter(lower, 0)) @_overload.mark @_unitrepr.accept(_util.velocity_limit) def VelocityFilter(upper): return _Filter(_mididings.VelocityFilter(0, upper)) @_overload.mark @_unitrepr.accept(_util.velocity_limit, _util.velocity_limit) def VelocityFilter(lower, upper): return _Filter(_mididings.VelocityFilter(lower, upper)) @_unitrepr.accept(_arguments.flatten(_util.ctrl_number), with_rest=True) def CtrlFilter(ctrls, *rest): """ Filter by controller number. """ return _Filter(_mididings.CtrlFilter(ctrls)) @_overload.mark( """ Filter by controller value. """ ) @_unitrepr.accept(_util.ctrl_value) def CtrlValueFilter(value): return _Filter(_mididings.CtrlValueFilter(value, value + 1)) @_overload.mark @_unitrepr.accept(_util.ctrl_limit) def CtrlValueFilter(lower): return _Filter(_mididings.CtrlValueFilter(lower, 0)) @_overload.mark @_unitrepr.accept(_util.ctrl_limit) def CtrlValueFilter(upper): return _Filter(_mididings.CtrlValueFilter(0, upper)) @_overload.mark @_unitrepr.accept(_util.ctrl_limit, _util.ctrl_limit) def CtrlValueFilter(lower, upper): return _Filter(_mididings.CtrlValueFilter(lower, upper)) @_unitrepr.accept(_arguments.flatten(_util.program_number), with_rest=True) def ProgramFilter(programs, *rest): """ Filter by program number. """ return _Filter(_mididings.ProgramFilter(map(_util.actual, programs))) @_overload.mark( """ Filter by sysex data. """ ) @_unitrepr.accept(_functools.partial(_util.sysex_data, allow_partial=True)) def SysExFilter(sysex): partial = (sysex[-1] != '\xf7') return _Filter(_mididings.SysExFilter(sysex, partial)) @_overload.mark @_unitrepr.accept(_util.sysex_manufacturer) def SysExFilter(manufacturer): sysex = _util.sysex_to_sequence([0xf0]) + manufacturer return _Filter(_mididings.SysExFilter(sysex, True)) mididings-20120419~ds0/mididings/units/printing.py0000644000175000017500000000672111737711704021745 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings.units.call import _CallBase import mididings.overload as _overload import mididings.unitrepr as _unitrepr import mididings.constants as _constants import mididings.misc as _misc import sys as _sys import collections as _collections if _sys.version_info >= (2, 6): _callable_constraint = _collections.Callable else: import mididings.arguments as _arguments _callable_constraint = _arguments.condition(lambda c: callable(c)) class _Print(_CallBase): max_name_length = -1 max_portname_length = -1 portnames_used = False def __init__(self, name, portnames): if portnames is not None: _Print.portnames_used = True # find maximum name length if name: _Print.max_name_length = max(_Print.max_name_length, len(name)) # using a separare object to do the actual printing avoids keeping # references back to this unit, thus preventing a cycle printer = _Printer(name, portnames) _CallBase.__init__(self, printer, True, True) class _Printer(object): def __init__(self, name, portnames): self.name = name self.portnames = portnames # to be calculated later self.ports = None def __call__(self, ev): # lazy import to avoid problems with circular imports from mididings import engine # get list of port names to be used # (delayed 'til first use, because the engine doesn't yet exist during __init__) if self.ports is None: if self.portnames == 'in': self.ports = engine.in_ports() elif self.portnames == 'out': self.ports = engine.out_ports() else: self.ports = [] # find maximum port name length (delayed for the same reason as above) if _Print.portnames_used and _Print.max_portname_length == -1: all_ports = engine.in_ports() + engine.out_ports() _Print.max_portname_length = max(len(p) for p in all_ports) if self.name: namestr = '%-*s ' % (_Print.max_name_length + 1, self.name + ':') elif _Print.max_name_length != -1: # no name, but names used elsewhere, so indent appropriately namestr = ' ' * (_Print.max_name_length + 2) else: namestr = '' if ev.type == _constants.SYSEX: eventmax = _misc.get_terminal_size()[1] - len(namestr) else: eventmax = 0 eventstr = ev.to_string(self.ports, _Print.max_portname_length, eventmax) print('%s%s' % (namestr, eventstr)) class _PrintString(_CallBase): def __init__(self, string): self.string = string _CallBase.__init__(self, self.do_print, True, True) def do_print(self, ev): string = self.string(ev) if hasattr(self.string, '__call__') else self.string print(string) @_overload.mark @_unitrepr.accept((str, type(None)), ('in', 'out', None)) def Print(name=None, portnames=None): return _Print(name, portnames) @_overload.mark @_unitrepr.accept((str, _callable_constraint)) def Print(string): return _PrintString(string) mididings-20120419~ds0/mididings/units/__init__.py0000644000175000017500000000140211735711423021635 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings.units.base import * from mididings.units.engine import * from mididings.units.filters import * from mididings.units.splits import * from mididings.units.modifiers import * from mididings.units.generators import * from mididings.units.call import * from mididings.units.printing import * from mididings.units.init import * import mididings.misc as _misc __all__ = _misc.prune_globals(globals()) mididings-20120419~ds0/mididings/units/generators.py0000644000175000017500000000733011740254726022261 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import _mididings from mididings.units.base import _Unit import mididings.constants as _constants import mididings.util as _util import mididings.overload as _overload import mididings.unitrepr as _unitrepr @_unitrepr.accept(_util.event_type, _util.port_number_ref, _util.channel_number_ref, int, int) def Generator(type, port=_constants.EVENT_PORT, channel=_constants.EVENT_CHANNEL, data1=0, data2=0): """ Generic generator. """ return _Unit(_mididings.Generator( type, _util.actual_ref(port), _util.actual_ref(channel), data1, data2 )) @_overload.partial((_constants.EVENT_PORT, _constants.EVENT_CHANNEL)) @_unitrepr.accept(_util.port_number_ref, _util.channel_number_ref, _util.note_number_ref, _util.velocity_value_ref) def NoteOn(port, channel, note, velocity): """ Generate note-on event. """ return _Unit(_mididings.Generator( _constants.NOTEON, _util.actual_ref(port), _util.actual_ref(channel), note, velocity )) @_overload.partial((_constants.EVENT_PORT, _constants.EVENT_CHANNEL)) @_unitrepr.accept(_util.port_number_ref, _util.channel_number_ref, _util.note_number_ref, _util.velocity_value_ref) def NoteOff(port, channel, note, velocity=0): """ Generate note-off event. """ return _Unit(_mididings.Generator( _constants.NOTEOFF, _util.actual_ref(port), _util.actual_ref(channel), note, velocity )) @_overload.partial((_constants.EVENT_PORT, _constants.EVENT_CHANNEL)) @_unitrepr.accept(_util.port_number_ref, _util.channel_number_ref, _util.ctrl_number_ref, _util.ctrl_value_ref) def Ctrl(port, channel, ctrl, value): """ Generate control change event. """ return _Unit(_mididings.Generator( _constants.CTRL, _util.actual_ref(port), _util.actual_ref(channel), ctrl, value )) @_overload.partial((_constants.EVENT_PORT, _constants.EVENT_CHANNEL)) @_unitrepr.accept(_util.port_number_ref, _util.channel_number_ref, int) def Pitchbend(port, channel, value): """ Generate pitch-bend event. """ return _Unit(_mididings.Generator( _constants.PITCHBEND, _util.actual_ref(port), _util.actual_ref(channel), 0, value )) @_overload.partial((_constants.EVENT_PORT, _constants.EVENT_CHANNEL)) @_unitrepr.accept(_util.port_number_ref, _util.channel_number_ref, int) def Aftertouch(port, channel, value): """ Generate aftertouch event. """ return _Unit(_mididings.Generator( _constants.AFTERTOUCH, _util.actual_ref(port), _util.actual_ref(channel), 0, value )) @_overload.partial((_constants.EVENT_PORT, _constants.EVENT_CHANNEL)) @_unitrepr.accept(_util.port_number_ref, _util.channel_number_ref, _util.program_number_ref) def Program(port, channel, program): """ Generate program change event. """ return _Unit(_mididings.Generator( _constants.PROGRAM, _util.actual_ref(port), _util.actual_ref(channel), 0, _util.actual_ref(program) )) @_overload.partial((_constants.EVENT_PORT,)) @_unitrepr.accept(_util.port_number_ref, _util.sysex_data) def SysEx(port, sysex): """ Generate sysex event. """ return _Unit(_mididings.SysExGenerator( _util.actual_ref(port), sysex, )) mididings-20120419~ds0/mididings/units/splits.py0000644000175000017500000000654611740106575021434 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings.units.base import Chain, Fork, _UNIT_TYPES from mididings.units.filters import PortFilter, ChannelFilter, KeyFilter, VelocityFilter from mididings.units.filters import CtrlFilter, CtrlValueFilter, ProgramFilter, SysExFilter import mididings.overload as _overload import mididings.arguments as _arguments import mididings.util as _util def _make_split(t, d, unpack=False): if unpack: # if dictionary key is a tuple, unpack and pass as individual parameters to ctor t = lambda p, t=t: t(*(p if isinstance(p, tuple) else (p,))) # build dict with all items from d, except d[None] dd = dict((k, v) for k, v in d.items() if k is not None) # build fork from all normal items r = Fork((t(k) >> w) for k, w in dd.items()) # add else-rule, if any if None in d: f = Chain(~t(k) for k in dd.keys()) r.append(f >> d[None]) return r def _make_threshold(f, patch_lower, patch_upper): return Fork([ f >> patch_lower, ~f >> patch_upper, ]) @_arguments.accept({_arguments.nullable(_arguments.flatten(_util.port_number, tuple)): _UNIT_TYPES}) def PortSplit(d): return _make_split(PortFilter, d) @_arguments.accept({_arguments.nullable(_arguments.flatten(_util.channel_number, tuple)): _UNIT_TYPES}) def ChannelSplit(d): return _make_split(ChannelFilter, d) @_overload.mark @_arguments.accept({_arguments.nullable(_util.note_range): _UNIT_TYPES}) def KeySplit(d): return _make_split(KeyFilter, d) @_overload.mark @_arguments.accept(_util.note_limit, _UNIT_TYPES, _UNIT_TYPES) def KeySplit(note, patch_lower, patch_upper): return _make_threshold(KeyFilter(0, note), patch_lower, patch_upper) @_overload.mark @_arguments.accept({_arguments.nullable(_util.velocity_range): _UNIT_TYPES}) def VelocitySplit(d): return _make_split(VelocityFilter, d, unpack=True) @_overload.mark @_arguments.accept(_util.velocity_limit, _UNIT_TYPES, _UNIT_TYPES) def VelocitySplit(threshold, patch_lower, patch_upper): return _make_threshold(VelocityFilter(0, threshold), patch_lower, patch_upper) @_arguments.accept({_arguments.nullable(_arguments.flatten(_util.ctrl_number, tuple)): _UNIT_TYPES}) def CtrlSplit(d): return _make_split(CtrlFilter, d) @_overload.mark @_arguments.accept({_arguments.nullable(_util.ctrl_range): _UNIT_TYPES}) def CtrlValueSplit(d): return _make_split(CtrlValueFilter, d, unpack=True) @_overload.mark @_arguments.accept(_util.ctrl_limit, _UNIT_TYPES, _UNIT_TYPES) def CtrlValueSplit(threshold, patch_lower, patch_upper): return _make_threshold(CtrlValueFilter(0, threshold), patch_lower, patch_upper) @_arguments.accept({_arguments.nullable(_arguments.flatten(_util.program_number, tuple)): _UNIT_TYPES}) def ProgramSplit(d): return _make_split(ProgramFilter, d) @_overload.mark @_arguments.accept(dict) def SysExSplit(d): return _make_split(SysExFilter, d) @_overload.mark @_arguments.accept(dict) def SysExSplit(manufacturers): return _make_split(lambda m: SysExFilter(manufacturer=m), manufacturers) mididings-20120419~ds0/mididings/units/modifiers.py0000644000175000017500000001574411740153203022065 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import _mididings from mididings.units.base import _Unit, Filter, Split, Pass from mididings.units.splits import VelocitySplit from mididings.units.generators import NoteOn, NoteOff import mididings.util as _util import mididings.misc as _misc import mididings.overload as _overload import mididings.constants as _constants import mididings.arguments as _arguments import mididings.unitrepr as _unitrepr @_unitrepr.accept(_util.port_number) def Port(port): """ Change port number. """ return _Unit(_mididings.Port(_util.actual(port))) @_unitrepr.accept(_util.channel_number) def Channel(channel): """ Change channel number.a """ return _Unit(_mididings.Channel(_util.actual(channel))) @_unitrepr.accept(int) def Transpose(offset): """ Transpose note events. """ return _Unit(_mididings.Transpose(offset)) @_arguments.accept(_util.note_number) def Key(note): """ Change note number. """ return Filter(_constants.NOTE) % Split({ _constants.NOTEON: NoteOn(note, _constants.EVENT_VELOCITY), _constants.NOTEOFF: NoteOff(note, _constants.EVENT_VELOCITY), }) @_overload.mark( """ Change note-on velocity. """ ) @_unitrepr.accept(int) def Velocity(offset): return _Unit(_mididings.Velocity(offset, _mididings.TransformMode.OFFSET)) @_overload.mark @_unitrepr.accept((float, int)) def Velocity(multiply): return _Unit(_mididings.Velocity(multiply, _mididings.TransformMode.MULTIPLY)) @_overload.mark @_unitrepr.accept(_util.velocity_value) def Velocity(fixed): return _Unit(_mididings.Velocity(fixed, _mididings.TransformMode.FIXED)) @_overload.mark @_unitrepr.accept((float, int)) def Velocity(gamma): return _Unit(_mididings.Velocity(gamma, _mididings.TransformMode.GAMMA)) @_overload.mark @_unitrepr.accept((float, int)) def Velocity(curve): return _Unit(_mididings.Velocity(curve, _mididings.TransformMode.CURVE)) @_overload.mark @_unitrepr.accept((float, int), int) def Velocity(multiply, offset): return Velocity(multiply=multiply) >> Velocity(offset=offset) @_overload.mark( """ Apply a linear slope to note-on velocities. """ ) @_unitrepr.accept([_util.note_limit], [int]) def VelocitySlope(notes, offset): _check_velocity_slope(notes, offset) return _Unit(_mididings.VelocitySlope(notes, offset, _mididings.TransformMode.OFFSET)) @_overload.mark @_unitrepr.accept([_util.note_limit], [(float, int)]) def VelocitySlope(notes, multiply): _check_velocity_slope(notes, multiply) return _Unit(_mididings.VelocitySlope(notes, multiply, _mididings.TransformMode.MULTIPLY)) @_overload.mark @_unitrepr.accept([_util.note_limit], [_util.velocity_value]) def VelocitySlope(notes, fixed): _check_velocity_slope(notes, fixed) return _Unit(_mididings.VelocitySlope(notes, fixed, _mididings.TransformMode.FIXED)) @_overload.mark @_unitrepr.accept([_util.note_limit], [(float, int)]) def VelocitySlope(notes, gamma): _check_velocity_slope(notes, gamma) return _Unit(_mididings.VelocitySlope(notes, gamma, _mididings.TransformMode.GAMMA)) @_overload.mark @_unitrepr.accept([_util.note_limit], [(float, int)]) def VelocitySlope(notes, curve): _check_velocity_slope(notes, curve) return _Unit(_mididings.VelocitySlope(notes, curve, _mididings.TransformMode.CURVE)) @_overload.mark @_unitrepr.accept([_util.note_limit], [(float, int)], [int]) def VelocitySlope(notes, multiply, offset): return VelocitySlope(notes, multiply=multiply) >> VelocitySlope(notes, offset=offset) def _check_velocity_slope(notes, params): message = None if len(notes) != len(params): message = "invalid parameters to VelocitySlope(): notes and velocity values must be sequences of the same length" elif len(notes) < 2: message = "invalid parameters to VelocitySlope(): need at least two notes" elif sorted(notes) != list(notes): message = "invalid parameters to VelocitySlope(): notes must be in ascending order" if message is not None: raise ValueError(message) @_overload.mark( """ Limit velocities to a given range. """ ) @_arguments.accept(_util.velocity_limit, _util.velocity_limit) def VelocityLimit(min, max): return Filter(_constants.NOTE) % VelocitySplit({ (0, min): Velocity(fixed=min), (min, max): Pass(), (max, 0): Velocity(fixed=max), }) @_overload.mark @_arguments.accept(_util.velocity_limit) def VelocityLimit(max): return Filter(_constants.NOTE) % VelocitySplit({ (0, max): Pass(), (max, 0): Velocity(fixed=max), }) @_overload.mark @_arguments.accept(_util.velocity_limit) def VelocityLimit(min): return Filter(_constants.NOTE) % VelocitySplit({ (0, min): Velocity(fixed=min), (min, 0): Pass(), }) @_unitrepr.accept(_util.ctrl_number, _util.ctrl_number) def CtrlMap(ctrl_in, ctrl_out): """ Convert one controller to another. """ return _Unit(_mididings.CtrlMap(ctrl_in, ctrl_out)) @_unitrepr.accept(_util.ctrl_number, int, int, int, int) def CtrlRange(ctrl, min, max, in_min=0, in_max=127): """ Convert controller range. """ if in_min > in_max: # swap ranges so that in_min is less than in_max in_min, in_max = in_max, in_min min, max = max, min return _Unit(_mididings.CtrlRange(ctrl, min, max, in_min, in_max)) @_overload.mark( """ Transform controller values. """ ) @_unitrepr.accept(_util.ctrl_number, (float, int)) def CtrlCurve(ctrl, gamma): return _Unit(_mididings.CtrlCurve(ctrl, gamma, _mididings.TransformMode.GAMMA)) @_overload.mark @_unitrepr.accept(_util.ctrl_number, (float, int)) def CtrlCurve(ctrl, curve): return _Unit(_mididings.CtrlCurve(ctrl, curve, _mididings.TransformMode.CURVE)) @_overload.mark @_unitrepr.accept(_util.ctrl_number, int) def CtrlCurve(ctrl, offset): return _Unit(_mididings.CtrlCurve(ctrl, offset, _mididings.TransformMode.OFFSET)) @_overload.mark @_unitrepr.accept(_util.ctrl_number, (float, int)) def CtrlCurve(ctrl, multiply): return _Unit(_mididings.CtrlCurve(ctrl, multiply, _mididings.TransformMode.MULTIPLY)) @_overload.mark @_unitrepr.accept(_util.ctrl_number, (float, int), int) def CtrlCurve(ctrl, multiply, offset): return CtrlCurve(ctrl, multiply=multiply) >> CtrlCurve(ctrl, offset=offset) @_overload.mark( """ Modify pitchbend range. """ ) @_unitrepr.accept(int, int, int, int) def PitchbendRange(min, max, in_min=-8192, in_max=8191): return _Unit(_mididings.PitchbendRange(min, max, in_min, in_max)) @_overload.mark @_unitrepr.accept(int, int, int) def PitchbendRange(down, up, range): return PitchbendRange(int(float(down)/range*8192), int(float(up)/range*8191)) mididings-20120419~ds0/mididings/util.py0000644000175000017500000002305511740266746017732 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import mididings.misc as _misc import mididings.constants as _constants import mididings.setup as _setup import sys as _sys _NOTE_NUMBERS = { 'c': 0, 'c#': 1, 'db': 1, 'd': 2, 'd#': 3, 'eb': 3, 'e': 4, 'f': 5, 'f#': 6, 'gb': 6, 'g': 7, 'g#': 8, 'ab': 8, 'a': 9, 'a#': 10, 'bb': 10, 'b': 11, } _NOTE_NAMES = { 0: 'c', 1: 'c#', 2: 'd', 3: 'd#', 4: 'e', 5: 'f', 6: 'f#', 7: 'g', 8: 'g#', 9: 'a', 10: 'a#', 11: 'b', } _CONTROLLER_NAMES = { 0: 'Bank select (MSB)', 1: 'Modulation', 6: 'Data entry (MSB)', 7: 'Volume', 10: 'Pan', 11: 'Expression', 32: 'Bank select (LSB)', 38: 'Data entry (LSB)', 64: 'Sustain', 65: 'Portamento', 66: 'Sostenuto', 67: 'Soft pedal', 68: 'Legato pedal', 98: 'NRPN (LSB)', 99: 'NRPN (MSB)', 100: 'RPN (LSB)', 101: 'RPN (MSB)', 121: 'Reset all controllers', 123: 'All notes off', } def note_number(note, allow_end=False): """ Convert note name/number to MIDI note number. """ if isinstance(note, int): r = note elif isinstance(note, str): note = note.lower() # find first digit for i in range(len(note)): if note[i].isdigit() or note[i] == '-': break try: name = note[:i] octave = int(note[i:]) r = _NOTE_NUMBERS[name] + (octave + _setup.get_config('octave_offset')) * 12 except Exception: raise ValueError("invalid note name '%s'" % note) else: raise TypeError("note must be an integer or string") end = 128 if not allow_end else 129 if not (0 <= r < end): raise ValueError("note number %d is out of range" % r) return r def note_limit(note): return note_number(note, allow_end=True) def note_range(notes): """ Convert note range to tuple of MIDI note numbers. """ try: # single note n = note_number(notes) return (n, n + 1) except Exception: try: if isinstance(notes, tuple): # tuple of note numbers return note_limit(notes[0]), note_limit(notes[1]) elif isinstance(notes, str): # note range string nn = notes.split(':', 1) lower = note_limit(nn[0]) if nn[0] else 0 upper = note_limit(nn[1]) if nn[1] else 0 return lower, upper else: raise TypeError("note range must be a tuple of integers or a string") except (ValueError, IndexError): raise ValueError("invalid note range %r" % notes) def note_name(note): """ Get note name from MIDI note number. """ if not isinstance(note, int): raise TypeError("note must be an integer") return _NOTE_NAMES[note % 12] + str((note // 12) - _setup.get_config('octave_offset')) def tonic_note_number(key): return _NOTE_NUMBERS[key] def controller_name(ctrl): """ Get controller description. """ if ctrl in _CONTROLLER_NAMES: return _CONTROLLER_NAMES[ctrl] else: return None def event_type(type): """ Check and return event type. """ # if not isinstance(type, _constants._EventType) or type not in (1 << x for x in range(_constants._NUM_EVENT_TYPES)): if type not in _constants._EVENT_TYPES: raise ValueError("invalid event type %r" % type) return type def port_number(port): """ Convert port number/name to actual port number. """ if isinstance(port, int): if actual(port) < 0: raise ValueError("invalid port number %d" % port) return port elif isinstance(port, str): in_ports = _setup._in_portnames out_ports = _setup._out_portnames if port in in_ports and port in out_ports and in_ports.index(port) != out_ports.index(port): raise ValueError("port name '%s' is ambiguous" % port) elif port in in_ports: return offset(in_ports.index(port)) elif port in out_ports: return offset(out_ports.index(port)) else: raise ValueError("invalid port name '%s'" % port) else: raise TypeError("port must be an integer or string") def channel_number(channel): if not isinstance(channel, int): raise TypeError("channel must be an integer") if not (0 <= actual(channel) < 16): raise ValueError("channel number %d is out of range" % channel) return channel def program_number(program): if not isinstance(program, int): raise TypeError("program must be an integer") if not (0 <= actual(program) < 128): raise ValueError("program number %d is out of range" % program) return program def ctrl_number(ctrl): if not isinstance(ctrl, int): raise TypeError("controller must be an integer") if not (0 <= ctrl < 128): raise ValueError("controller number %d is out of range" % ctrl) return ctrl def ctrl_value(value, allow_end=False): if not isinstance(value, int): raise TypeError("controller value must be an integer") end = 128 if not allow_end else 129 if not (0 <= value < end): raise ValueError("controller value %d is out of range" % value) return value def ctrl_limit(value): return ctrl_value(value, allow_end=True) def ctrl_range(value): try: n = ctrl_value(value) return (n, n + 1) except Exception: if isinstance(value, tuple) and len(value) == 2: return (ctrl_limit(value[0]), ctrl_limit(value[1])) raise ValueError("invalid controller value range %r" % value) def velocity_value(velocity, allow_end=False): if not isinstance(velocity, int): raise TypeError("velocity must be an integer") end = 128 if not allow_end else 129 if not (0 <= velocity < end): raise ValueError("velocity %d is out of range" % velocity) return velocity def velocity_limit(velocity): return velocity_value(velocity, allow_end=True) def velocity_range(velocity): try: n = velocity_value(velocity) return (n, n + 1) except Exception: if isinstance(velocity, tuple) and len(velocity) == 2: return (velocity_limit(velocity[0]), velocity_limit(velocity[1])) raise ValueError("invalid velocity range %r" % velocity) def scene_number(scene): return scene def subscene_number(subscene): return subscene SYSEX_TYPE = bytearray if _sys.version_info >= (2, 6) else list def sysex_to_sequence(sysex): if isinstance(sysex, str): sysex = map(ord, sysex) if isinstance(sysex, SYSEX_TYPE): return sysex else: return SYSEX_TYPE(sysex) def sysex_data(sysex, allow_partial=False): sysex = sysex_to_sequence(sysex) if len(sysex) < 2: raise ValueError("sysex too short") elif sysex[0] != 0xf0: raise ValueError("sysex doesn't start with F0") elif sysex[-1] != 0xf7 and not allow_partial: raise ValueError("sysex doesn't end with F7") elif any(c > 0x7f for c in sysex[1:-1]): raise ValueError("sysex data byte out of range") return sysex def sysex_manufacturer(manufacturer): if not _misc.issequence(manufacturer, True): manufacturer = [manufacturer] manid = sysex_to_sequence(manufacturer) if len(manid) not in (1, 3): raise ValueError("manufacturer id must be either one or three bytes") elif len(manid) == 3 and manid[0] != 0x00: raise ValueError("three-byte manufacturer id must start with null byte") elif any(c > 0x7f for c in manid): raise ValueError("manufacturer id out of range") return manid class NoDataOffset(int): """ An integer type that's unaffected by data offset conversions. """ def __new__(cls, value): return int.__new__(cls, value) def __repr__(self): return 'NoDataOffset(%d)' % int(self) def __str__(self): return 'NoDataOffset(%d)' % int(self) def offset(n): """ Add current data offset. """ return n + _setup.get_config('data_offset') def actual(n): """ Subtract current data offset to get the "real" value used on the C++ side. """ if isinstance(n, NoDataOffset): return int(n) else: return n - _setup.get_config('data_offset') # define wrappers around parameter check functions that will also accept # event attribute references def _allow_event_attribute(f): def func(first, *args, **kwargs): if isinstance(first, _constants._EventAttribute): return first else: return f(first, *args, **kwargs) return func port_number_ref = _allow_event_attribute(port_number) channel_number_ref = _allow_event_attribute(channel_number) note_number_ref = _allow_event_attribute(note_number) velocity_value_ref = _allow_event_attribute(velocity_value) ctrl_number_ref = _allow_event_attribute(ctrl_number) ctrl_value_ref = _allow_event_attribute(ctrl_value) program_number_ref = _allow_event_attribute(program_number) scene_number_ref = _allow_event_attribute(scene_number) subscene_number_ref = _allow_event_attribute(subscene_number) actual_ref = _allow_event_attribute(actual) mididings-20120419~ds0/mididings/setup.py0000644000175000017500000000743311744112077020106 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import _mididings import mididings.arguments as _arguments import mididings.misc as _misc _VALID_BACKENDS = _mididings.available_backends() _DEFAULT_CONFIG = { 'backend': 'alsa' if 'alsa' in _VALID_BACKENDS else 'jack', 'client_name': 'mididings', 'in_ports': 1, 'out_ports': 1, 'data_offset': 1, 'octave_offset': 2, 'initial_scene': None, 'start_delay': None, 'silent': False, } def _default_portname(n, out): prefix = 'out_' if out else 'in_' return prefix + str(n + get_config('data_offset')) def _parse_portnames(ports, out): if not _misc.issequence(ports): return [_default_portname(n, out) for n in range(ports)] portnames = [] for n, port in enumerate(ports): if _misc.issequence(port): if port[0] is None: portnames.append(_default_portname(n, out)) else: portnames.append(port[0]) else: portnames.append(port) return portnames def _parse_port_connections(ports, out): if not _misc.issequence(ports): return {} connections = {} for n, port in enumerate(ports): if not _misc.issequence(port) or len(port) <= 1 or port[1] is None: continue portname = port[0] if port[0] is None: portname = _default_portname(n, out) connections[portname] = port[1:] return connections def reset(): global _config, _config_overridden, _hooks _config = _DEFAULT_CONFIG.copy() _config_overridden = [] _hooks = [] _config_updated() @_arguments.accept(kwargs = { 'backend': tuple(_VALID_BACKENDS), 'client_name': str, 'in_ports': _arguments.either( _arguments.each(int, _arguments.condition(lambda x: x > 0)), _arguments.sequenceof(_arguments.either(str, [str])), ), 'out_ports': _arguments.either( _arguments.each(int, _arguments.condition(lambda x: x > 0)), _arguments.sequenceof(_arguments.either(str, [str])), ), 'data_offset': (0, 1), 'octave_offset': int, 'initial_scene': _arguments.either( int, _arguments.each(tuple, [int]), _arguments.each(tuple, [int, int]) ), 'start_delay': (int, float, type(None)), 'silent': bool, }) def config(**kwargs): _config_impl(**kwargs) def _config_impl(override=False, **kwargs): for k, v in kwargs.items(): # everything seems ok, go ahead and change the config if override or k not in _config_overridden: _config[k] = v if override: _config_overridden.append(k) _config_updated() def _config_updated(): global _in_portnames, _out_portnames global _in_port_connections, _out_port_connections _in_portnames = _parse_portnames(get_config('in_ports'), False) _out_portnames = _parse_portnames(get_config('out_ports'), True) _in_port_connections = _parse_port_connections(get_config('in_ports'), False) _out_port_connections = _parse_port_connections(get_config('out_ports'), True) def get_config(var): return _config[var] def hook(*args): _hooks.extend(args) def get_hooks(): return _hooks reset() mididings-20120419~ds0/mididings/live/0000755000175000017500000000000000007345256017321 5ustar alessioalessiomididings-20120419~ds0/mididings/live/widgets.py0000644000175000017500000000627211745112565021356 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import sys if sys.version_info < (3,): import Tkinter else: import tkinter as Tkinter class AutoScrollbar(Tkinter.Scrollbar): def set_show_hide(self, show, hide): self._show = show self._hide = hide def set(self, lo, hi): if float(lo) <= 0.0 and float(hi) >= 1.0: self._hide() else: self._show() Tkinter.Scrollbar.set(self, lo, hi) class LiveThemedFactory(object): def __init__(self, color, color_highlight, color_background): self.color = color self.color_highlight = color_highlight self.color_background = color_background def Tk(self, **options): w = Tkinter.Tk() w.config(background=self.color_background) w.config(**options) return w def Frame(self, master, **options): w = Tkinter.Frame(master) w.config(background=self.color) w.config(**options) return w def AutoScrollbar(self, master, **options): w = AutoScrollbar(master) w.config( background=self.color, activebackground=self.color_highlight, troughcolor=self.color_background, borderwidth=1, relief='flat', width=16, ) w.config(**options) return w def Listbox(self, master, **options): w = Tkinter.Listbox(master) w.config( background=self.color_background, foreground=self.color, selectbackground=self.color_background, selectforeground=self.color_highlight, selectborderwidth=0, borderwidth=0, ) w.config(**options) return w def Button(self, master, **options): w = Tkinter.Button(master) w.config( background=self.color_background, foreground=self.color, activebackground=self.color_background, activeforeground=self.color_highlight, borderwidth=0, highlightthickness=0, relief='flat', ) w.config(**options) return w def Canvas(self, master, **options): w = Tkinter.Canvas(master) w.config(background=self.color_background) w.config(**options) return w class UnthemedFactory(object): def Tk(self, **options): w = Tkinter.Tk() w.config(**options) return w def Frame(self, master, **options): return Tkinter.Frame(master, **options) def AutoScrollbar(self, master, **options): return AutoScrollbar(master, **options) def Listbox(self, master, **options): return Tkinter.Listbox(master, **options) def Button(self, master, **options): return Tkinter.Button(master, **options) def Canvas(self, master, **options): return Tkinter.Canvas(master, **options) mididings-20120419~ds0/mididings/live/osc_control.py0000644000175000017500000000410011745112565022220 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import liblo class LiveOSC(liblo.ServerThread): def __init__(self, dings, control_port, listen_port): liblo.ServerThread.__init__(self, listen_port) self.dings = dings self.control_port = control_port self._scenes = {} def query(self): self.send(self.control_port, '/mididings/query') def switch_scene(self, n): self.send(self.control_port, '/mididings/switch_scene', n) def switch_subscene(self, n): self.send(self.control_port, '/mididings/switch_subscene', n) def prev_scene(self): self.send(self.control_port, '/mididings/prev_scene') def next_scene(self): self.send(self.control_port, '/mididings/next_scene') def prev_subscene(self): self.send(self.control_port, '/mididings/prev_subscene') def next_subscene(self): self.send(self.control_port, '/mididings/next_subscene') def panic(self): self.send(self.control_port, '/mididings/panic') @liblo.make_method('/mididings/data_offset', 'i') def data_offset_cb(self, path, args): self.dings.set_data_offset(args[0]) @liblo.make_method('/mididings/begin_scenes', '') def begin_scenes_cb(self, path, args): self._scenes = {} @liblo.make_method('/mididings/add_scene', None) def add_scene_cb(self, path, args): number, name = args[:2] subscenes = args[2:] self._scenes[number] = (name, subscenes) @liblo.make_method('/mididings/end_scenes', '') def end_scenes_cb(self, path, args): self.dings.set_scenes(self._scenes) @liblo.make_method('/mididings/current_scene', 'ii') def current_scene_cb(self, path, args): self.dings.set_current_scene(args[0], args[1]) mididings-20120419~ds0/mididings/live/livedings.py0000644000175000017500000002227411737362165021700 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings.live.widgets import LiveThemedFactory, UnthemedFactory from mididings.live.osc_control import LiveOSC import sys if sys.version_info >= (3,): unichr = chr class LiveDings(object): def __init__(self, options): self.options = options # start OSC server self.osc = LiveOSC(self, self.options.control_port, self.options.listen_port) self.osc.start() if self.options.themed: widget_factory = LiveThemedFactory(self.options.color, self.options.color_highlight, self.options.color_background) else: widget_factory = UnthemedFactory() # create the main window self.win = widget_factory.Tk(padx=8, pady=8) self.win.minsize(480, 120) self.win.geometry('%dx%d' % (self.options.width, self.options.height)) if self.options.name: self.win.title('livedings - %s' % self.options.name) else: self.win.title('livedings') # track window resizing self.win.bind('', lambda event: self.win.after_idle(self.update, True)) # configure the grid self.win.grid_rowconfigure(0, weight=1) self.win.grid_columnconfigure(1, minsize=self.options.list_width, weight=0) for n in range(3, 8): self.win.grid_columnconfigure(n, weight=1, minsize=64) # create listbox self.listbox = widget_factory.Listbox(self.win, font=self.options.list_font, selectmode='single', activestyle='none', highlightthickness=0) self.listbox.grid(column=1, row=0, rowspan=2, sticky='nsew', padx=8) self.listbox.bind('', lambda event: self.on_select_scene()) # create scrollbar for listbox. will be attached to the grid only when necessary self.scrollbar = widget_factory.AutoScrollbar(self.win, orient='vertical') self.scrollbar.set_show_hide( lambda: self.scrollbar.grid(column=0, row=0, rowspan=2, sticky='ns'), lambda: self.scrollbar.grid_forget() ) self.scrollbar.config(command=self.listbox.yview) self.listbox.config(yscrollcommand=self.scrollbar.set) # create separator separator = widget_factory.Frame(self.win, width=2) separator.grid(column=2, row=0, rowspan=2, sticky='ns') # create canvas self.canvas = widget_factory.Canvas(self.win, highlightthickness=0) if self.options.color_background is not None: self.canvas.config(background=self.options.color_background) self.canvas.grid(column=3, columnspan=5, row=0, sticky='nsew') self.canvas.bind('', self.on_button_press) self.canvas.bind('', self.on_button_release) # create buttons try: button_size = int(int(self.options.font.split(' ')[1]) / 1.5) except IndexError: button_size = 20 button_font = 'Sans %d bold' % button_size self.btn_prev_scene = widget_factory.Button(self.win, text=unichr(0x25c0)*2, width=64, font=button_font, command=self.osc.prev_scene) self.btn_prev_scene.grid(column=3, row=1, padx=8) self.btn_next_scene = widget_factory.Button(self.win, text=unichr(0x25b6)*2, width=64, font=button_font, command=self.osc.next_scene) self.btn_next_scene.grid(column=4, row=1, padx=8) self.btn_prev_subscene = widget_factory.Button(self.win, text=unichr(0x25c0), width=64, font=button_font, command=self.osc.prev_subscene) self.btn_prev_subscene.grid(column=5, row=1, padx=8) self.btn_next_subscene = widget_factory.Button(self.win, text=unichr(0x25b6), width=64, font=button_font, command=self.osc.next_subscene) self.btn_next_subscene.grid(column=6, row=1, padx=8) self.btn_panic = widget_factory.Button(self.win, text="!", width=64, font=button_font, command=self.osc.panic) self.btn_panic.grid(column=7, row=1, padx=8) # attempt to calculate the height of one line in the current font. this is crazy... try: self.line_height = int(self.options.font.split(' ')[1]) * 2 except Exception: # whatever... self.line_height = 72 # some keybindings self.win.bind('', lambda event: self.osc.prev_subscene()) self.win.bind('', lambda event: self.osc.next_subscene()) self.win.bind('', lambda event: self.osc.prev_scene()) self.win.bind('', lambda event: self.osc.next_scene()) self.win.bind('', lambda event: self.osc.panic()) # prevent left/right keys from scrolling the listbox self.win.bind_class('Listbox', "", lambda event: None) self.win.bind_class('Listbox', "", lambda event: None) # get config from mididings self.osc.query() self._ready = False # window dimensions self._width = 0 self._height = 0 # mouse click position self._click_x = 0 self._click_y = 0 def on_select_scene(self): cursel = self.listbox.curselection() if cursel: self.osc.switch_scene(sorted(self.scenes.keys())[int(cursel[0])]) def on_button_press(self, event): self._click_x = event.x self._click_y = event.y def on_button_release(self, event): if (self._ready and self._click_y > 8 + 3 * self.line_height and self._click_y < 8 + (len(self.scenes[self.current_scene][1])+3) * self.line_height): n = (self._click_y - (8 + 3 * self.line_height)) / self.line_height self.osc.switch_subscene(n + self.data_offset) def update(self, resize=False): width = self.canvas.winfo_width() height = self.canvas.winfo_height() # check if the window size really changed if resize and width == self._width and height == self._height: return self.draw_canvas(width, height) self._width = width self._height = height def set_data_offset(self, data_offset): self.data_offset = data_offset def set_scenes(self, scenes): self.scenes = scenes self.update_scenes() self._ready = True def set_current_scene(self, scene, subscene): self.current_scene = scene self.current_subscene = subscene self.listbox.selection_clear(0, 'end') self.listbox.selection_set(sorted(self.scenes.keys()).index(scene)) self.update() def update_scenes(self): self.listbox.delete(0, 'end') for n in sorted(self.scenes.keys()): name = self.scenes[n][0] if name: self.listbox.insert('end', "%d: %s" % (n, name)) else: self.listbox.insert('end', "%d" % n) def draw_canvas(self, width, height): if not self._ready or not len(self.scenes): return scene = self.current_scene subscene = self.current_subscene scene_name, subscene_names = self.scenes[scene] has_subscenes = bool(len(subscene_names)) if not scene_name: scene_name = "(unnamed)" self.canvas.delete('all') # draw scene number self.canvas.create_text( 24, 8, text=("%d.%d" % (scene, subscene)) if has_subscenes else str(scene), fill=self.options.color, font=self.options.font, anchor='nw' ) # draw scene name self.canvas.create_text( width / 2 + 24, 8 + 1.5 * self.line_height, text=scene_name, fill=self.options.color_highlight, font=self.options.font, anchor='n' ) # draw subscenes for n, s in enumerate(subscene_names): self.canvas.create_text( width / 2 + 24, 8 + (n+3) * self.line_height, text=s if s else "(unnamed)", fill=self.options.color_highlight if n + self.data_offset == subscene else self.options.color, font=self.options.font, anchor='n' ) # draw indicator self.canvas.create_text( 16, 8 + 1.5 * self.line_height if not has_subscenes else 8 + (subscene - self.data_offset + 3) * self.line_height, text=unichr(0x25b6), fill=self.options.color_highlight, font=self.options.font, anchor='nw' ) def run(self): self.win.mainloop() mididings-20120419~ds0/mididings/live/__init__.py0000644000175000017500000000000011340606435021421 0ustar alessioalessiomididings-20120419~ds0/mididings/engine.py0000644000175000017500000002443611742037311020210 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import _mididings import mididings.patch as _patch import mididings.scene as _scene import mididings.util as _util import mididings.misc as _misc import mididings.setup as _setup import mididings.constants as _constants import mididings.overload as _overload import mididings.arguments as _arguments from mididings.units.base import _UNIT_TYPES import time as _time import weakref as _weakref import threading as _threading import gc as _gc import atexit as _atexit import os as _os import sys as _sys if _sys.version_info >= (3,): raw_input = input _TheEngine = None class Engine(_mididings.Engine): def __init__(self): # initialize C++ base class engine_args = ( _setup.get_config('backend'), _setup.get_config('client_name'), _setup._in_portnames, _setup._out_portnames, not _setup.get_config('silent') ) _mididings.Engine.__init__(self, *engine_args) self.connect_ports(_setup._in_port_connections, _setup._out_port_connections) self._scenes = {} def setup(self, scenes, control, pre, post): # build and setup all scenes and scene groups for number, scene in scenes.items(): if isinstance(scene, _scene.SceneGroup): self._scenes[number] = (scene.name, []) for subscene in scene.subscenes: sceneobj = _scene._parse_scene(subscene) self._scenes[number][1].append(sceneobj.name) # build patches patch = _patch.Patch(sceneobj.patch) init_patch = _patch.Patch(sceneobj.init_patch) # add scene to base class object self.add_scene(_util.actual(number), patch, init_patch) else: sceneobj = _scene._parse_scene(scene) self._scenes[number] = (sceneobj.name, []) # build patches patch = _patch.Patch(sceneobj.patch) init_patch = _patch.Patch(sceneobj.init_patch) # add scene to base class object self.add_scene(_util.actual(number), patch, init_patch) # build and setup control, pre, and post patches control_patch = _patch.Patch(control) if control else None pre_patch = _patch.Patch(pre) if pre else None post_patch = _patch.Patch(post) if post else None # tell base class object about these patches self.set_processing(control_patch, pre_patch, post_patch) global _TheEngine _TheEngine = _weakref.ref(self) _gc.collect() _gc.disable() def run(self): self._quit = _threading.Event() # delay before actually sending any midi data (give qjackctl patchbay time to react...) self._start_delay() self._call_hooks('on_start') initial_scene, initial_subscene = self._parse_scene_number(_setup.get_config('initial_scene')) # start the actual event processing self.start(initial_scene, initial_subscene) try: # wait() with no timeout also blocks KeyboardInterrupt, but a very long timeout doesn't. weird... while not self._quit.isSet(): self._quit.wait(86400) except KeyboardInterrupt: pass finally: self._call_hooks('on_exit') global _TheEngine _TheEngine = None def _start_delay(self): delay = _setup.get_config('start_delay') if delay is not None: if delay > 0: _time.sleep(delay) else: raw_input("press enter to start midi processing...") def _parse_scene_number(self, number): if number in self._scenes: # single scene number, no subscene return (_util.actual(number), -1) elif _misc.issequence(number) and len(number) > 1 and number[0] in self._scenes: # scene/subscene numbers as tuple... if _util.actual(number[1]) < len(self._scenes[number[0]][1]): # both scene and subscene numbers are valid return (_util.actual(number[0]), _util.actual(number[1])) # subscene number is invalid return (_util.actual(number[0]), -1) # no such scene return (-1, -1) def process_file(self): self.start(0, -1) def scene_switch_callback(self, scene, subscene): # the scene and subscene parameters are the actual numbers without offset! if scene == -1: # no scene specified, use current scene = _mididings.Engine.current_scene(self) if subscene == -1: # no subscene specified, use first subscene = 0 # save actual subscene index subscene_index = subscene # add data offset to scene/subscene numbers scene = _util.offset(scene) subscene = _util.offset(subscene) found = (scene in self._scenes and (not subscene_index or subscene_index < len(self._scenes[scene][1]))) # get string representation of scene/subscene number if subscene_index or (scene in self._scenes and len(self._scenes[scene][1])): number = "%d.%d" % (scene, subscene) else: number = str(scene) if not _setup.get_config('silent'): if found: # get scene/subscene name scene_data = self._scenes[scene] if scene_data[1]: name = "%s - %s" % (scene_data[0], scene_data[1][subscene_index]) else: name = scene_data[0] scene_desc = ("%s: %s" % (number, name)) if name else str(number) print("switching to scene %s" % scene_desc) else: print("no such scene: %s" % number) if found: self._call_hooks('on_switch_scene', scene, subscene) def _call_hooks(self, name, *args): for hook in _setup.get_hooks(): if hasattr(hook, name): f = getattr(hook, name) f(*args) def switch_scene(self, scene, subscene=None): _mididings.Engine.switch_scene(self, _util.actual(scene), _util.actual(subscene) if subscene is not None else -1 ) def switch_subscene(self, subscene): _mididings.Engine.switch_scene(self, -1, _util.actual(subscene)) def current_scene(self): return _util.offset(_mididings.Engine.current_scene(self)) def current_subscene(self): return _util.offset(_mididings.Engine.current_subscene(self)) def scenes(self): return self._scenes def process_event(self, ev): ev._finalize() return _mididings.Engine.process_event(self, ev) def output_event(self, ev): ev._finalize() _mididings.Engine.output_event(self, ev) def process(self, ev): ev._finalize() return _mididings.Engine.process(self, ev) def restart(self): _atexit.register(self._restart) self.quit() @staticmethod def _restart(): # run the same interpreter with the same arguments again _os.execl(_sys.executable, _sys.executable, *_sys.argv) def quit(self): self._quit.set() @_overload.mark @_arguments.accept(_UNIT_TYPES) def run(patch): """ Create the engine and start event processing. This function does not usually return until mididings exits. """ if isinstance(patch, dict) and all(not isinstance(k, _constants._EventType) for k in patch.keys()): # bypass the overload mechanism (just this once...) if there's no way # the given dict could be accepted as a split run(scenes=patch) else: e = Engine() e.setup({_util.offset(0): patch}, None, None, None) e.run() @_overload.mark @_arguments.accept(_UNIT_TYPES, _arguments.nullable(_UNIT_TYPES), _arguments.nullable(_UNIT_TYPES), _arguments.nullable(_UNIT_TYPES)) def run(scenes, control=None, pre=None, post=None): """ Create the engine and start event processing. This function does not usually return until mididings exits. """ e = Engine() e.setup(scenes, control, pre, post) e.run() def process_file(infile, outfile, patch): """ Process a MIDI file using the smf backend. """ _setup._config_impl( backend='smf', ) _setup.config( in_ports=[infile], out_ports=[outfile], ) e = Engine() e.setup({_util.offset(0): patch}, None, None, None) e.process_file() def switch_scene(scene, subscene=None): """ Switch to the given scene number. """ _TheEngine().switch_scene(scene, subscene) def switch_subscene(subscene): """ Switch to the given subscene number. """ _TheEngine().switch_subscene(subscene) def current_scene(): """ Return the current scene number. """ return _TheEngine().current_scene() def current_subscene(): """ Return the current subscene number. """ return _TheEngine().current_subscene() def scenes(): """ Return a mapping from scene numbers to a tuple of the form (scene_name, [subscene_name, ...]). """ return _TheEngine().scenes() def output_event(ev): """ Send an event directly to an output port. """ _TheEngine().output_event(ev) def in_ports(): """ Return the list of input port names. """ return _setup._in_portnames def out_ports(): """ Return the list of output port names. """ return _setup._out_portnames def time(): """ Return a floating point number of seconds since some unspecified starting point. """ return _TheEngine().time() def active(): """ Return True if the engine is running, otherwise False. """ return _TheEngine is not None and _TheEngine() is not None def restart(): """ Restart mididings. """ _TheEngine().restart() def quit(): """ Quit mididings. """ _TheEngine().quit() mididings-20120419~ds0/mididings/event.py0000644000175000017500000002437311737657435020107 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import _mididings import mididings.constants as _constants import mididings.arguments as _arguments import mididings.util as _util import mididings.misc as _misc def _make_property(type, data, name=None, offset=False): if isinstance(data, str) and not offset: def getter(self): self._check_type_attribute(type, name) return getattr(self, data) def setter(self, value): self._check_type_attribute(type, name) setattr(self, data, value) elif isinstance(data, str) and offset: def getter(self): self._check_type_attribute(type, name) return _util.offset(getattr(self, data)) def setter(self, value): self._check_type_attribute(type, name) setattr(self, data, _util.actual(value)) return property(getter, setter) class MidiEvent(_mididings.MidiEvent): """ The main MIDI event class. All event data is part of the C++ base class. """ @_arguments.accept(None, _constants._EventType, _util.port_number, _util.channel_number, int, int, _arguments.nullable(_util.sysex_data)) def __init__(self, type, port=_util.NoDataOffset(0), channel=_util.NoDataOffset(0), data1=0, data2=0, sysex=None): _mididings.MidiEvent.__init__(self) self.type = type self.port = port self.channel = channel self.data1 = data1 self.data2 = data2 if sysex is not None: self.sysex_ = sysex def __getinitargs__(self): self._finalize() return (self.type, self.port, self.channel, self.data1, self.data2, self.sysex if self.type == _constants.SYSEX else None) def _check_type_attribute(self, type, name): if not self.type & type: message = "MidiEvent type '%s' has no attribute '%s'" % (self._type_to_string(), name) raise AttributeError(message) def _type_to_string(self): try: return _constants._EVENT_TYPES[self.type].name except KeyError: return 'NONE' def _sysex_to_hex(self, max_length): data = self.sysex if max_length: m = max(0, max_length // 3) if len(data) > m: return '%s ...' % _misc.sequence_to_hex(data[:m]) return _misc.sequence_to_hex(data) _to_string_mapping = { _constants.NOTEON: lambda self: 'Note On: %3d %3d (%s)' % (self.note, self.velocity, _util.note_name(self.note)), _constants.NOTEOFF: lambda self: 'Note Off: %3d %3d (%s)' % (self.note, self.velocity, _util.note_name(self.note)), _constants.CTRL: lambda self: 'Ctrl: %3d %3d' % (self.ctrl, self.value) + (' (%s)' % _util.controller_name(self.ctrl) if _util.controller_name(self.ctrl) else ''), _constants.PITCHBEND: lambda self: 'Pitchbend: %5d' % self.value, _constants.AFTERTOUCH: lambda self: 'Aftertouch: %3d' % self.value, _constants.POLY_AFTERTOUCH: lambda self: 'Poly Aftertouch: %3d %3d (%s)' % (self.note, self.value, _util.note_name(self.note)), _constants.PROGRAM: lambda self: 'Program: %3d' % self.program, _constants.SYSCM_QFRAME: lambda self: 'SysCm QFrame: %3d' % self.data1, _constants.SYSCM_SONGPOS: lambda self: 'SysCm SongPos:%3d %3d' % (self.data1, self.data2), _constants.SYSCM_SONGSEL: lambda self: 'SysCm SongSel:%3d' % self.data1, _constants.SYSCM_TUNEREQ: lambda self: 'SysCm TuneReq', _constants.SYSRT_CLOCK: lambda self: 'SysRt Clock', _constants.SYSRT_START: lambda self: 'SysRt Start', _constants.SYSRT_CONTINUE: lambda self: 'SysRt Continue', _constants.SYSRT_STOP: lambda self: 'SysRt Stop', _constants.SYSRT_SENSING: lambda self: 'SysRt Sensing', _constants.SYSRT_RESET: lambda self: 'SysRt Reset', _constants.DUMMY: lambda self: 'Dummy', } _repr_mapping = { _constants.NOTEON: lambda self: 'NoteOnEvent(port=%d, channel=%d, note=%d, velocity=%d)' % (self.port, self.channel, self.note, self.velocity), _constants.NOTEOFF: lambda self: 'NoteOffEvent(port=%d, channel=%d, note=%d, velocity=%d)' % (self.port, self.channel, self.note, self.velocity), _constants.CTRL: lambda self: 'CtrlEvent(port=%d, channel=%d, ctrl=%d, value=%d)' % (self.port, self.channel, self.ctrl, self.value), _constants.PITCHBEND: lambda self: 'PitchbendEvent(port=%d, channel=%d, value=%d)' % (self.port, self.channel, self.value), _constants.AFTERTOUCH: lambda self: 'AftertouchEvent(port=%d, channel=%d, value=%d)' % (self.port, self.channel, self.value), _constants.PROGRAM: lambda self: 'ProgramEvent(port=%d, channel=%d, program=%d)' % (self.port, self.channel, self.program), _constants.SYSEX: lambda self: 'SysExEvent(port=%d, sysex=%r)' % (self.port, _misc.bytestring(self._get_sysex())), } def to_string(self, portnames=[], portname_length=0, max_length=0): if len(portnames) > self.port_: port = portnames[self.port_] else: port = str(self.port) header = '[%*s, %2d]' % (max(portname_length, 2), port, self.channel) if self.type_ == _constants.SYSEX: maxsysex = (max_length - len(header) - 25) if max_length else 0 sysexstr = self._sysex_to_hex(maxsysex) desc = 'SysEx: %8d [%s]' % (len(self.sysex), sysexstr) else: desc = MidiEvent._to_string_mapping.get( self.type_, lambda self: 'None' )(self) return '%s %s' % (header, desc) def __repr__(self): return MidiEvent._repr_mapping.get( self.type_, lambda self: 'MidiEvent(%s, %d, %d, %d, %d)' % (self._type_to_string(), self.port, self.channel, self.data1, self.data2) )(self) def __eq__(self, other): if not isinstance(other, MidiEvent): return NotImplemented self._finalize() other._finalize() return _mididings.MidiEvent.__eq__(self, other) def __ne__(self, other): if not isinstance(other, MidiEvent): return NotImplemented self._finalize() other._finalize() return _mididings.MidiEvent.__ne__(self, other) def __hash__(self): self._finalize() return _mididings.MidiEvent.__hash__(self) def _finalize(self): if hasattr(self, '_sysex_tmp'): self.sysex_ = _util.sysex_data(self._sysex_tmp) delattr(self, '_sysex_tmp') def _type_getter(self): # return an event type constant, rather than the plain int stored in # self.type_ return _constants._EVENT_TYPES[self.type_] def _type_setter(self, type): self.type_ = type def _get_sysex(self): if hasattr(self, '_sysex_tmp'): return self._sysex_tmp else: return _util.sysex_to_sequence(self.sysex_) def _sysex_getter(self): self._check_type_attribute(_constants.SYSEX, 'sysex') self._sysex_tmp = self._get_sysex() return self._sysex_tmp def _sysex_setter(self, sysex): self._check_type_attribute(_constants.SYSEX, 'sysex') self._sysex_tmp = _util.sysex_to_sequence(sysex) type = property(_type_getter, _type_setter) # port/channel attributes with data offset port = _make_property(_constants.ANY, 'port_', offset=True) channel = _make_property(_constants.ANY, 'channel_', offset=True) # event-type specific attributes note = _make_property(_constants.NOTE | _constants.POLY_AFTERTOUCH, 'data1', 'note') velocity = _make_property(_constants.NOTE, 'data2', 'velocity') ctrl = _make_property(_constants.CTRL, 'data1', 'ctrl') value = _make_property(_constants.CTRL | _constants.PITCHBEND | _constants.AFTERTOUCH | _constants.POLY_AFTERTOUCH, 'data2', 'value') program = _make_property(_constants.PROGRAM, 'data2', 'program', offset=True) sysex = property(_sysex_getter, _sysex_setter) @_arguments.accept(_util.port_number, _util.channel_number, _util.note_number, _util.velocity_value) def NoteOnEvent(port, channel, note, velocity): """ Create a new note-on event object. """ return MidiEvent(_constants.NOTEON, port, channel, note, velocity) @_arguments.accept(_util.port_number, _util.channel_number, _util.note_number, _util.velocity_value) def NoteOffEvent(port, channel, note, velocity=0): """ Create a new note-off event object. """ return MidiEvent(_constants.NOTEOFF, port, channel, note, velocity) @_arguments.accept(_util.port_number, _util.channel_number, _util.ctrl_number, _util.ctrl_value) def CtrlEvent(port, channel, ctrl, value): """ Create a new control change event object. """ return MidiEvent(_constants.CTRL, port, channel, ctrl, value) @_arguments.accept(_util.port_number, _util.channel_number, int) def PitchbendEvent(port, channel, value): """ Create a new pitch bend event object. """ return MidiEvent(_constants.PITCHBEND, port, channel, 0, value) @_arguments.accept(_util.port_number, _util.channel_number, int) def AftertouchEvent(port, channel, value): """ Create a new aftertouch event object. """ return MidiEvent(_constants.AFTERTOUCH, port, channel, 0, value) @_arguments.accept(_util.port_number, _util.channel_number, _util.program_number) def ProgramEvent(port, channel, program): """ Create a new program change event object. """ return MidiEvent(_constants.PROGRAM, port, channel, 0, _util.actual(program)) @_arguments.accept(_util.port_number, _util.sysex_data) def SysExEvent(port, sysex): """ Create a new sysex event object. """ return MidiEvent(_constants.SYSEX, port, sysex=sysex) mididings-20120419~ds0/mididings/constants.py0000644000175000017500000000256611737365160020771 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import _mididings from mididings.misc import NamedFlag as _NamedFlag from mididings.misc import NamedBitMask as _NamedBitMask class _EventType(_NamedBitMask): pass class _EventAttribute(_NamedFlag): pass _EVENT_TYPES = {} # populate this module with midi event type constants for _name, _value in _mididings.MidiEventType.names.items(): _type_object = _EventType(int(_value), _name) # add event type object to this module's global namespace globals()[_name] = _type_object # only masks matching a single event type (exactly one bit set) are added # to the event types dict if len([_x for _x in range(32) if _value & (1 << _x)]) == 1: _EVENT_TYPES[int(_value)] = _type_object # populate this module with midi event attribute constants for _name, _value in _mididings.EventAttribute.names.items(): _attribute_object = _EventAttribute(int(_value), "EVENT_" + _name) # add event attribute object to this module's global namespace globals()["EVENT_" + _name] = _attribute_object mididings-20120419~ds0/mididings/extra/0000755000175000017500000000000011737362255017517 5ustar alessioalessiomididings-20120419~ds0/mididings/extra/memorize_scene.py0000644000175000017500000000267611732204750023076 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import mididings.setup as _setup import mididings.engine as _engine import sys as _sys class MemorizeScene(object): def __init__(self, memo_file): self.memo_file = memo_file def on_start(self): try: f = open(self.memo_file) s = f.read() try: # single scene number _setup.config(initial_scene=int(s)) except ValueError: try: # scene and subscene number _setup.config(initial_scene=tuple(map(int, s.split()))) except ValueError: pass except IOError: # couldn't open memo file, might not be an error pass def on_exit(self): try: f = open(self.memo_file, 'w') f.write("%d %d\n" % (_engine.current_scene(), _engine.current_subscene())) except IOError: # yuck. not willing to break compatibility with python 2.5 just yet... _, ex, _ = _sys.exc_info() print("couldn't store current scene:\n%s" % str(ex)) mididings-20120419~ds0/mididings/extra/gm.py0000644000175000017500000000660311745112636020474 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import mididings.util as _util ( # 1 ACOUSTIC_GRAND_PIANO, BRIGHT_ACOUSTIC_PIANO, ELECTRIC_GRAND_PIANO, HONKY_TONK_PIANO, ELECTRIC_PIANO_1, ELECTRIC_PIANO_2, HARPSICHORD, CLAVINET, # 9 CELESTA, GLOCKENSPIEL, MUSIC_BOX, VIBRAPHONE, MARIMBA, XYLOPHONE, TUBULAR_BELLS, DULCIMER, # 17 DRAWBAR_ORGAN, PERCUSSIVE_ORGAN, ROCK_ORGAN, CHURCH_ORGAN, REED_ORGAN, ACCORDION, HARMONICA, TANGO_ACCORDION, # 25 ACOUSTIC_GUITAR_NYLON, ACOUSTIC_GUITAR_STEEL, ELECTRIC_GUITAR_JAZZ, ELECTRIC_GUITAR_CLEAN, ELECTRIC_GUITAR_MUTED, OVERDRIVEN_GUITAR, DISTORTION_GUITAR, GUITAR_HARMONICS, # 33 ACOUSTIC_BASS, ELECTRIC_BASS_FINGER, ELECTRIC_BASS_PICK, FRETLESS_BASS, SLAP_BASS_1, SLAP_BASS_2, SYNTH_BASS_1, SYNTH_BASS_2, # 41 VIOLIN, VIOLA, CELLO, CONTRABASS, TREMOLO_STRINGS, PIZZICATO_STRINGS, ORCHESTRAL_HARP, TIMPANI, # 49 STRING_ENSEMBLE_1, STRING_ENSEMBLE_2, SYNTH_STRINGS_1, SYNTH_STRINGS_2, CHOIR_AAHS, VOICE_OOHS, SYNTH_VOICE, ORCHESTRA_HIT, # 57 TRUMPET, TROMBONE, TUBA, MUTED_TRUMPET, FRENCH_HORN, BRASS_SECTION, SYNTHBRASS_1, SYNTHBRASS_2, # 65 SOPRANO_SAX, ALTO_SAX, TENOR_SAX, BARITONE_SAX, OBOE, ENGLISH_HORN, BASSOON, CLARINET, # 73 PICCOLO, FLUTE, RECORDER, PAN_FLUTE, BLOWN_BOTTLE, SHAKUHACHI, WHISTLE, OCARINA, # 81 LEAD_1_SQUARE, LEAD_2_SAWTOOTH, LEAD_3_CALLIOPE, LEAD_4_CHIFF, LEAD_5_CHARANG, LEAD_6_VOICE, LEAD_7_FIFTHS, LEAD_8_BASS_LEAD, # 89 PAD_1_NEW_AGE, PAD_2_WARM, PAD_3_POLYSYNTH, PAD_4_CHOIR, PAD_5_BOWED, PAD_6_METALLIC, PAD_7_HALO, PAD_8_SWEEP, # 97 FX_1_RAIN, FX_2_SOUNDTRACK, FX_3_CRYSTAL, FX_4_ATMOSPHERE, FX_5_BRIGHTNESS, FX_6_GOBLINS, FX_7_ECHOES, FX_8_SCI_FI, # 105 SITAR, BANJO, SHAMISEN, KOTO, KALIMBA, BAGPIPE, FIDDLE, SHANAI, # 113 TINKLE_BELL, AGOGO, STEEL_DRUMS, WOODBLOCK, TAIKO_DRUM, MELODIC_TOM, SYNTH_DRUM, REVERSE_CYMBAL, # 121 GUITAR_FRET_NOISE, BREATH_NOISE, SEASHORE, BIRD_TWEET, TELEPHONE_RING, HELICOPTER, APPLAUSE, GUNSHOT ) = map(_util.NoDataOffset, range(128)) CTRL_BANK_SELECT_MSB = 0 CTRL_MODULATION = 1 CTRL_DATA_ENTRY_MSB = 6 CTRL_VOLUME = 7 CTRL_PAN = 10 CTRL_EXPRESSION = 11 CTRL_BANK_SELECT_LSB = 32 CTRL_DATA_ENTRY_LSB = 38 CTRL_SUSTAIN = 64 CTRL_PORTAMENTO = 65 CTRL_SOSTENUTO = 66 CTRL_SOFT_PEDAL = 67 CTRL_LEGATO_PEDAL = 68 CTRL_NRPN_LSB = 98 CTRL_NRPN_MSB = 99 CTRL_RPN_LSB = 100 CTRL_RPN_MSB = 101 CTRL_RESET_ALL_CONTROLLERS = 121 CTRL_ALL_NOTES_OFF = 123 mididings-20120419~ds0/mididings/extra/suppress_pc.py0000644000175000017500000000135611745112636022437 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings import * from mididings.extra import PerChannel class _SuppressPC(object): def __init__(self): self.current = None def __call__(self, ev): if ev.program == self.current: return None else: self.current = ev.program return ev def SuppressPC(): return Filter(PROGRAM) % Process(PerChannel(_SuppressPC)) mididings-20120419~ds0/mididings/extra/key_color.py0000644000175000017500000000127611745112636022060 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings import * _BLACK = [1, 3, 6, 8, 10] _BLACK_KEYS = [_n for _n in range(128) if _n%12 in _BLACK] _WHITE_KEYS = [_n for _n in range(128) if _n%12 not in _BLACK] def KeyColorFilter(color): if color == 'black': return KeyFilter(notes=_BLACK_KEYS) elif color == 'white': return KeyFilter(notes=_WHITE_KEYS) mididings-20120419~ds0/mididings/extra/polyphony.py0000644000175000017500000000602111745112636022124 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings import * from mididings.extra import PerChannel import mididings.event as _event class _LimitPolyphony(object): def __init__(self, max_polyphony, remove_oldest): self.max_polyphony = max_polyphony self.remove_oldest = remove_oldest self.notes = [] def __call__(self, ev): if ev.type == NOTEON: if len(self.notes) < self.max_polyphony: # polyphony not exceeded, allow note self.notes.append(ev.note) return ev else: if self.remove_oldest: # allow note, but send note-off for oldest first noteoff = _event.NoteOffEvent(ev.port, ev.channel, self.notes[0], 0) self.notes = self.notes[1:] self.notes.append(ev.note) return [noteoff, ev] else: # discard note return None elif ev.type == NOTEOFF: if ev.note in self.notes: self.notes.remove(ev.note) return ev else: return None class _MakeMonophonic(object): def __init__(self): self.notes = [] def __call__(self, ev): if ev.type == NOTEON: if len(self.notes): # send note off for previous note, and note on for current note noteoff = _event.NoteOffEvent(ev.port, ev.channel, self.notes[-1][0], 0) self.notes.append((ev.note, ev.velocity)) return [noteoff, ev] else: # send note on for current note self.notes.append((ev.note, ev.velocity)) return ev elif ev.type == NOTEOFF: if len(self.notes) and ev.note == self.notes[-1][0]: # note off for currently sounding note if len(self.notes) == 1: # only one note left, send note off r = ev else: # send note off, and retrigger previous note noteon = _event.NoteOnEvent(ev.port, ev.channel, self.notes[-2][0], self.notes[-2][1]) r = [ev, noteon] else: # note isn't sounding, discard note off r = None # remove released note from list self.notes = [x for x in self.notes if x[0] != ev.note] return r def LimitPolyphony(max_polyphony, remove_oldest=True): return Filter(NOTE) % Process(PerChannel(lambda: _LimitPolyphony(max_polyphony, remove_oldest))) def MakeMonophonic(): return Filter(NOTE) % Process(PerChannel(_MakeMonophonic)) mididings-20120419~ds0/mididings/extra/engine.py0000644000175000017500000000103411745112636021327 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings import Call import mididings.engine as _engine def Restart(): return Call(lambda ev: _engine.restart()) def Quit(): return Call(lambda ev: _engine.quit()) mididings-20120419~ds0/mididings/extra/panic.py0000644000175000017500000000220311745112636021153 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings import * import mididings.engine as _engine import mididings.event as _event import mididings.util as _util def _panic_bypass(): # send all notes off (CC #123) and sustain off (CC #64) to all output ports and on all channels for p in _engine.out_ports(): for c in range(16): _engine.output_event(_event.CtrlEvent(p, _util.NoDataOffset(c), 123, 0)) _engine.output_event(_event.CtrlEvent(p, _util.NoDataOffset(c), 64, 0)) def Panic(bypass=True): if bypass: return Call(lambda ev: _panic_bypass()) >> Discard() else: return Fork([ (Ctrl(p, _util.NoDataOffset(c), 123, 0) // Ctrl(p, _util.NoDataOffset(c), 64, 0)) for p in _engine.out_ports() for c in range(16) ]) mididings-20120419~ds0/mididings/extra/floating_split.py0000644000175000017500000000623011735716475023115 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings import * import mididings.util as _util import mididings.engine as _engine class _FloatingKeySplitAnalyze(object): def __init__(self, threshold_lower, threshold_upper, hold_time, margin_lower, margin_upper): self.threshold_lower = _util.note_number(threshold_lower) self.threshold_upper = _util.note_number(threshold_upper) self.margin_lower = margin_lower self.margin_upper = margin_upper self.hold_time = hold_time self.notes_lower = {} self.notes_upper = {} def __call__(self, ev): now = _engine.time() # remove old notes from the lists if hold_time has elapsed for k, t in list(self.notes_lower.items()): if t and t < now - self.hold_time: del self.notes_lower[k] for k, t in list(self.notes_upper.items()): if t and t < now - self.hold_time: del self.notes_upper[k] # calculate new threshold lower = max(self.notes_lower) if len(self.notes_lower) else self.threshold_lower - self.margin_lower upper = min(self.notes_upper) if len(self.notes_upper) else self.threshold_upper + self.margin_upper self.threshold = min(max((lower + upper + 1) / 2, self.threshold_lower), self.threshold_upper) if ev.type == NOTEON: # add notes to the appropriate list if ev.note < self.threshold: self.notes_lower[ev.note] = 0 else: self.notes_upper[ev.note] = 0 elif ev.type == NOTEOFF: # mark notes for removal if ev.note in self.notes_lower: self.notes_lower[ev.note] = now if ev.note in self.notes_upper: self.notes_upper[ev.note] = now return ev class _FloatingKeySplitFilter(object): def __init__(self, analyze, index): self.analyze = analyze self.index = index def __call__(self, ev): # the split point can never move past a note that's still being held, so this is # valid for both note-on and note-off events if (self.index == 0 and ev.note < self.analyze.threshold or self.index == 1 and ev.note >= self.analyze.threshold): return ev else: return None def FloatingKeySplit(threshold_lower, threshold_upper, patch_lower, patch_upper, hold_time=1.0, margin_lower=12, margin_upper=12): analyze = _FloatingKeySplitAnalyze(threshold_lower, threshold_upper, hold_time, margin_lower, margin_upper) return Split({ NOTE: Process(analyze) >> [ Process(_FloatingKeySplitFilter(analyze, 0)) >> patch_lower, Process(_FloatingKeySplitFilter(analyze, 1)) >> patch_upper, ], None: [ patch_lower, patch_upper ], }) mididings-20120419~ds0/mididings/extra/dbus.py0000644000175000017500000000213311745112636021020 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from __future__ import absolute_import as _absolute_import from mididings import Call import dbus as _dbus class _SendDBUS(object): def __init__(self, service, path, interface, method, args): self.bus = _dbus.SessionBus() self.service = service self.path = path self.interface = interface self.method = method self.args = args def __call__(self, ev): obj = self.bus.get_object(self.service, self.path) func = obj.get_dbus_method(self.method, self.interface) args = ((x(ev) if hasattr(x, '__call__') else x) for x in self.args) func(*args) def SendDBUS(service, path, interface, method, *args): return Call(_SendDBUS(service, path, interface, method, args)) mididings-20120419~ds0/mididings/extra/osc.py0000644000175000017500000001056211737362224020655 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings import Call import mididings.engine as _engine import mididings.setup as _setup import mididings.util as _util import mididings.misc as _misc import mididings.extra.panic as _panic import liblo as _liblo class OSCInterface(object): def __init__(self, port=56418, notify_ports=[56419]): self.port = port if _misc.issequence(notify_ports): self.notify_ports = notify_ports else: self.notify_ports = [notify_ports] def on_start(self): if self.port is not None: self.server = _liblo.ServerThread(self.port) self.server.register_methods(self) self.server.start() self.send_config() def on_exit(self): if self.port is not None: self.server.stop() del self.server def on_switch_scene(self, scene, subscene): for p in self.notify_ports: _liblo.send(p, '/mididings/current_scene', scene, subscene) def send_config(self): for p in self.notify_ports: # send data offset _liblo.send(p, '/mididings/data_offset', _setup.get_config('data_offset')) # send list of scenes _liblo.send(p, '/mididings/begin_scenes') s = _engine.scenes() for n in sorted(s.keys()): _liblo.send(p, '/mididings/add_scene', n, s[n][0], *s[n][1]) _liblo.send(p, '/mididings/end_scenes') @_liblo.make_method('/mididings/query', '') def query_cb(self, path, args): self.send_config() for p in self.notify_ports: _liblo.send(p, '/mididings/current_scene', _engine.current_scene(), _engine.current_subscene()) @_liblo.make_method('/mididings/switch_scene', 'i') @_liblo.make_method('/mididings/switch_scene', 'ii') def switch_scene_cb(self, path, args): _engine.switch_scene(*args) @_liblo.make_method('/mididings/switch_subscene', 'i') def switch_subscene_cb(self, path, args): _engine.switch_subscene(*args) @_liblo.make_method('/mididings/prev_scene', '') def prev_scene_cb(self, path, args): s = sorted(_engine.scenes().keys()) n = s.index(_engine.current_scene()) - 1 if n >= 0: _engine.switch_scene(s[n]) @_liblo.make_method('/mididings/next_scene', '') def next_scene_cb(self, path, args): s = sorted(_engine.scenes().keys()) n = s.index(_engine.current_scene()) + 1 if n < len(s): _engine.switch_scene(s[n]) @_liblo.make_method('/mididings/prev_subscene', '') @_liblo.make_method('/mididings/prev_subscene', 'i') def prev_subscene_cb(self, path, args): s = _engine.scenes()[_engine.current_scene()] n = _util.actual(_engine.current_subscene()) - 1 if len(s[1]) and len(args) and args[0]: n %= len(s[1]) if n >= 0: _engine.switch_subscene(_util.offset(n)) @_liblo.make_method('/mididings/next_subscene', '') @_liblo.make_method('/mididings/next_subscene', 'i') def next_subscene_cb(self, path, args): s = _engine.scenes()[_engine.current_scene()] n = _util.actual(_engine.current_subscene()) + 1 if len(s[1]) and len(args) and args[0]: n %= len(s[1]) if n < len(s[1]): _engine.switch_subscene(_util.offset(n)) @_liblo.make_method('/mididings/panic', '') def panic_cb(self, path, args): _panic._panic_bypass() @_liblo.make_method('/mididings/restart', '') def restart_cb(self, path, args): _engine.restart() @_liblo.make_method('/mididings/quit', '') def quit_cb(self, path, args): _engine.quit() class _SendOSC(object): def __init__(self, target, path, args): self.target = target self.path = path self.args = args def __call__(self, ev): args = ((x(ev) if hasattr(x, '__call__') else x) for x in self.args) _liblo.send(self.target, self.path, *args) def SendOSC(target, path, *args): return Call(_SendOSC(target, path, args)) mididings-20120419~ds0/mididings/extra/per_channel.py0000644000175000017500000000123511745112636022343 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # class PerChannel(object): def __init__(self, factory): self.per_channel = {} self.factory = factory def __call__(self, ev): k = (ev.port, ev.channel) if k not in self.per_channel: self.per_channel[k] = self.factory() return self.per_channel[k](ev) mididings-20120419~ds0/mididings/extra/voices.py0000644000175000017500000000752611737362255021373 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings import * from mididings.extra import PerChannel import mididings.event as _event import mididings.engine as _engine class _VoiceFilter(object): def __init__(self, voice, time, retrigger): self.voice = voice self.time = time self.retrigger = retrigger self.notes = {} # all notes currently being played self.current_note = None # note number this voice is playing self.diverted = False # whether we had to fall back to a different voice def __call__(self, ev): t = _engine.time() if ev.type == NOTEON: # store new note, its velocity, and its time self.notes[ev.note] = (ev.velocity, t) elif ev.type == NOTEOFF: # delete released note del self.notes[ev.note] sorted_notes = sorted(self.notes.keys()) if len(sorted_notes): try: n = sorted_notes[self.voice] d = False except IndexError: # use the next best note: lowest for negative index, otherwise highest n = sorted_notes[0 if self.voice < 0 else -1] d = True else: n = None d = False dt = (t - self.notes[self.current_note][1]) if self.current_note in self.notes else 0.0 # change current note if... if (n != self.current_note # note number changed and... and (self.retrigger # we're always retriggering notes... or self.voice in (0, -1) # lowest/heighest voice are a bit of a special case... or self.current_note not in self.notes # current note is no longer held... or (ev.type == NOTEON and dt < self.time) # our previous note is very recent... or self.diverted and not d)): # or the new note is "better" than previous one # yield note-off for previous note (if any) if self.current_note: yield _event.NoteOffEvent(ev.port, ev.channel, self.current_note, 0) self.current_note = None dt = (t - self.notes[n][1]) if n in self.notes else 0.0 # yield note-on for new note (if any) if n is not None and (ev.note == n # if this is the note being played right now... or self.retrigger # we're retriggering notes whenever a key is pressed or released... or dt < self.time): # or our previous note is very recent yield _event.NoteOnEvent(ev.port, ev.channel, n, self.notes[n][0]) self.current_note = n self.diverted = d def VoiceFilter(voice='highest', time=0.1, retrigger=False): if voice == 'highest': voice = -1 elif voice == 'lowest': voice = 0 return Filter(NOTE) % Process(PerChannel( lambda: _VoiceFilter(voice, time, retrigger)) ) def VoiceSplit(patches, fallback='highest', time=0.1, retrigger=False): vf = lambda n: VoiceFilter(n, time, retrigger) if fallback == 'lowest': return Fork( [ vf( 0) >> patches[ 0] ] + [ vf( n) >> patches[ n] for n in range(-len(patches) + 1, 0) ] ) else: # highest return Fork( [ vf( n) >> patches[ n] for n in range(len(patches) - 1) ] + [ vf(-1) >> patches[-1] ] ) mididings-20120419~ds0/mididings/extra/harmonizer.py0000644000175000017500000000721711737362230022247 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings import * import mididings.util as _util import mididings.misc as _misc import itertools as _itertools from operator import itemgetter as _itemgetter _MAJOR_SCALE = [0, 2, 4, 5, 7, 9, 11] _HARMONIC_MINOR_SCALE = [0, 2, 3, 5, 7, 8, 11] _MODES = ['ionian', 'dorian', 'phrygian', 'lydian', 'mixolydian', 'aeolian', 'locrian'] _INTERVALS = ['unison', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'octave', 'ninth', 'tenth', 'eleventh', 'twelfth', 'thirteenth'] class _Harmonizer(object): def __init__(self, tonic, scale, interval, non_harmonic): self.tonic = tonic self.lookup = {} # for each note in the scale, calculate the transpose interval for x in scale: l = len(scale) i = scale.index(x) + interval hx = scale[i % l] + ((i / l) * 12) self.lookup[x] = hx - x r = list(range(12)) if non_harmonic == 'above': # reverse order to make sure higher notes are already calculated r.reverse() # fill the gaps (notes not in the scale) for x in r: if x not in scale: if non_harmonic == 'below': self.lookup[x] = self.lookup[x-1] elif non_harmonic == 'above': self.lookup[x] = self.lookup[(x+1) % 12] elif non_harmonic == 'same': self.lookup[x] = 0 else: # skip self.lookup[x] = None def note_offset(self, note): n = (note - self.tonic) % 12 return self.lookup[n] def __call__(self, ev): off = self.note_offset(ev.note) if off is not None: ev.note += off return True else: return False def Harmonize(tonic, scale, interval, non_harmonic='below'): t = _util.tonic_note_number(tonic) if _misc.issequence(scale): shift = 0 elif isinstance(scale, str): if scale == 'major': scale = _MAJOR_SCALE shift = 0 elif scale == 'minor': scale = _MAJOR_SCALE shift = 5 elif scale == 'minor_harmonic': scale = _HARMONIC_MINOR_SCALE shift = 0 elif scale in _MODES: shift = _MODES.index(scale) scale = _MAJOR_SCALE # shift scale to the correct mode s = [x - scale[shift] for x in scale[shift:]] + \ [x + 12 - scale[shift] for x in scale[:shift]] if not _misc.issequence(interval): interval = [interval] # convert all interval names to numbers iv = [(_INTERVALS.index(x) if x in _INTERVALS else x) for x in interval] # python version: # f = [ Process(_Harmonizer(t, s, i, non_harmonic)) for i in iv ] # pure mididings version: f = [] for i in iv: h = _Harmonizer(t, s, i, non_harmonic) # get offset for each key offsets = [(x, h.note_offset(x)) for x in range(128)] # group by offset groups = _itertools.groupby(sorted(offsets, key=_itemgetter(1)), key=_itemgetter(1)) # create one KeyFilter()/Transpose() pair for each offset for off, keys in groups: if off is not None: f.append(KeyFilter(notes=[k[0] for k in keys]) >> Transpose(off)) return Filter(NOTE) % f mididings-20120419~ds0/mididings/extra/pedal_noteoff.py0000644000175000017500000000552011745112636022673 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings import * from mididings.extra import PerChannel import mididings.event as _event class _SustainToNoteoff(object): def __init__(self, ctrl): self.ctrl = ctrl self.pedal = False self.notes = set() def __call__(self, ev): if ev.type == CTRL and ev.ctrl == self.ctrl: self.pedal = (ev.value >= 64) if self.pedal: # pedal pressed return None else: # pedal released, send note offs for all sustained notes r = [_event.NoteOffEvent(ev.port, ev.channel, x, 0) for x in self.notes] self.notes.clear() return r elif ev.type == NOTEON and self.pedal: # note on while pedal is held return ev elif ev.type == NOTEOFF and self.pedal: # delay note off until pedal released self.notes.add(ev.note) return None else: # everything else: return as is return ev class _SostenutoToNoteoff(object): def __init__(self, ctrl): self.ctrl = ctrl self.pedal = False self.held_notes = set() self.sustained_notes = set() def __call__(self, ev): if ev.type == CTRL and ev.ctrl == self.ctrl: self.pedal = (ev.value >= 64) if self.pedal: # pedal pressed, remember currently held notes self.sustained_notes |= self.held_notes return None else: # pedal released, send note offs for all sustained notes r = [_event.NoteOffEvent(ev.port, ev.channel, x, 0) for x in self.sustained_notes if x not in self.held_notes] self.sustained_notes.clear() return r elif ev.type == NOTEON: self.held_notes.add(ev.note) return ev elif ev.type == NOTEOFF: self.held_notes.discard(ev.note) # send note off only if the note is not currently being sustained if ev.note not in self.sustained_notes: return ev else: return None else: # everything else: return as is return ev def PedalToNoteoff(ctrl=64, sostenuto=False): if sostenuto: proc = Process(PerChannel(lambda: _SostenutoToNoteoff(ctrl))) else: proc = Process(PerChannel(lambda: _SustainToNoteoff(ctrl))) return (Filter(NOTE) | CtrlFilter(ctrl)) % proc mididings-20120419~ds0/mididings/extra/latch.py0000644000175000017500000000345311737362217021167 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings import * from mididings.extra import PerChannel import mididings.event as _event import mididings.util as _util class _LatchNotes(object): def __init__(self, polyphonic, reset): self.polyphonic = polyphonic self.reset = _util.note_number(reset) if reset is not None else None self.notes = [] def __call__(self, ev): if ev.type == NOTEON: if ev.note == self.reset: # reset all notes r = [_event.NoteOffEvent(ev.port, ev.channel, x, 0) for x in self.notes] self.notes = [] return r if self.polyphonic: if ev.note not in self.notes: # turn note on self.notes.append(ev.note) return ev else: # turn note off self.notes.remove(ev.note) return _event.NoteOffEvent(ev.port, ev.channel, ev.note, 0) else: # turn off previous note, play new note r = [_event.NoteOffEvent(ev.port, ev.channel, self.notes[0], 0)] if len(self.notes) else [] self.notes = [ev.note] return r + [ev] elif ev.type == NOTEOFF: # ignore all note-off events return None def LatchNotes(polyphonic=False, reset=None): return Filter(NOTE) % Process(PerChannel(lambda: _LatchNotes(polyphonic, reset))) mididings-20120419~ds0/mididings/extra/__init__.py0000644000175000017500000000162611745112636021630 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings.extra.harmonizer import * from mididings.extra.per_channel import * from mididings.extra.suppress_pc import * from mididings.extra.pedal_noteoff import * from mididings.extra.polyphony import * from mididings.extra.key_color import * from mididings.extra.memorize_scene import * from mididings.extra.latch import * from mididings.extra.panic import * from mididings.extra.floating_split import * from mididings.extra.voices import * from mididings.extra.engine import * import mididings.misc as _misc __all__ = _misc.prune_globals(globals()) mididings-20120419~ds0/mididings/extra/inotify.py0000644000175000017500000000414011740062407021537 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import mididings.engine as _engine import sys as _sys import os as _os import pyinotify as _pyinotify class AutoRestart(object): def __init__(self, modules=True, filenames=[]): self.modules = modules self.filenames = filenames def on_start(self): self.wm = _pyinotify.WatchManager() self.notifier = _pyinotify.ThreadedNotifier(self.wm) if self.modules: # find the name of the main script being executed if '__mididings_main__' in _sys.modules: main_file = _sys.modules['__mididings_main__'].__file__ elif hasattr(_sys.modules['__main__'], '__file__'): main_file = _sys.modules['__main__'].__file__ else: main_file = None if main_file: base_dir = _os.path.dirname(_os.path.abspath(main_file)) # add watches for imported modules for m in _sys.modules.values(): # builtin modules don't have a __file__ attribute if hasattr(m, '__file__'): f = _os.path.abspath(m.__file__) # only watch file if it's in the same directory as the # main script if f.startswith(base_dir): self.wm.add_watch(f, _pyinotify.IN_MODIFY, self._process_IN_MODIFY) # add watches for additional files for f in self.filenames: self.wm.add_watch(f, _pyinotify.IN_MODIFY, self._process_IN_MODIFY) self.notifier.start() def on_exit(self): self.notifier.stop() def _process_IN_MODIFY(self, event): print("file '%s' changed, restarting..." % event.pathname) _engine.restart() mididings-20120419~ds0/mididings/unitrepr.py0000644000175000017500000000440511740143151020603 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings import overload from mididings import arguments from mididings import misc import inspect import sys import decorator @decorator.decorator def store(f, *args, **kwargs): """ Decorator that modifies the function f to store its own arguments in the unit it returns. """ unit = f(*args, **kwargs) # store the unit's name, the function object, and the value of each argument unit._name = f.name if isinstance(f, overload._Overload) else f.__name__ unit._function = f unit._args = args return unit def accept(*constraints, **kwargs): """ @arguments.accept() and @unitrepr.store composed into a single decorator for convenience. """ def composed(f): return arguments.accept(*constraints, **kwargs) (store(f)) return composed def unit_to_string(unit): # can't do anything for units that didn't go through @store (or @accept) assert hasattr(unit, '_name') argnames = misc.getargspec(unit._function)[0] if sys.version_info >= (2, 6): args = [ misc.bytestring(a) if isinstance(a, bytearray) else a for a in unit._args ] else: args = unit._args # (ab)use inspect module to format the arguments used formatted = inspect.formatargspec(args=argnames, defaults=args) return unit._name + formatted def chain_to_string(chain): return ' >> '.join(repr(u) for u in chain) def fork_to_string(fork): if fork.remove_duplicates is not None: args = ('units', 'remove_duplicates') defaults = (list(fork), fork.remove_duplicates) formatted = inspect.formatargspec(args, defaults=defaults) return 'Fork' + formatted else: return list.__repr__(fork) def split_to_string(split): return dict.__repr__(split) def inverted_filter_to_string(invfilt): prefix = '-' if invfilt.negate else '~' return '%s%r' % (prefix, invfilt.filt) mididings-20120419~ds0/mididings/arguments.py0000644000175000017500000003202211740261303020734 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings import misc import inspect import sys import collections import types import functools if sys.version_info < (2, 6): functools.reduce = reduce import decorator class accept(object): """ A decorator that applies type checks and other constraints to the arguments of the decorated function. Constraints must be given in the order of the function's positional arguments, one constraint per argument. If the function accepts variable arguments, one additional constraint must be specified, and will be applied to each of those arguments. If the optional keyword argument with_rest is True, all variable arguments are instead combined with the last regular positional argument into a single list. This list is then passed to the original function as a single argument. Keyword arguments are accepted if their name is in the kwargs_constraints dictionary, and the value from that dictionnary is used as the constraint to be applied. """ def __init__(self, *constraints, **kwargs): self.with_rest = kwargs['with_rest'] if 'with_rest' in kwargs else False kwargs_constraints = kwargs['kwargs'] if 'kwargs' in kwargs else {} # build all constraints self.constraints = [_make_constraint(c) for c in constraints] self.kwargs_constraints = dict((k, _make_constraint(v)) for k, v in kwargs_constraints.items()) def __call__(self, f): argspec = misc.getargspec(f) self.arg_names = argspec[0] self.have_varargs = (argspec[1] is not None) and not self.with_rest assert ((len(self.constraints) == len(self.arg_names) and not self.have_varargs) or (len(self.constraints) == len(self.arg_names) + 1 and self.have_varargs)) return decorator.decorator(self.wrapper, f) def wrapper(self, f, *args, **kwargs): mod_args = [] mod_kwargs = {} for constraint, arg_name, arg in zip(self.constraints, self.arg_names, args): if self.with_rest and arg_name == self.arg_names[-1]: # with_rest is True and this is the last argument: combine with varargs index = len(self.arg_names) arg = (arg,) + args[index:] a = _try_apply_constraint(constraint, arg, f.__name__, arg_name) mod_args.append(a) if self.have_varargs: index = len(self.arg_names) constraint = self.constraints[index] for arg in args[index:]: a = _try_apply_constraint(constraint, arg, f.__name__, None) mod_args.append(a) for k, v in kwargs.items(): if k not in self.kwargs_constraints: message = "%s() got an unexpected keyword argument '%s'" % (f.__name__, k) raise TypeError(message) a = _try_apply_constraint(self.kwargs_constraints[k], v, f.__name__, k) mod_kwargs[k] = a return f(*mod_args, **mod_kwargs) def _try_apply_constraint(constraint, arg, func_name, arg_name): try: return constraint(arg) except (TypeError, ValueError): ex = sys.exc_info()[1] typestr = "type" if isinstance(ex, TypeError) else "value" argstr = ("for parameter '%s'" % arg_name) if arg_name else "in varargs" message = "invalid %s %s of %s():\n%s" % (typestr, argstr, func_name, str(ex)) raise type(ex)(message) def _make_constraint(c): if c is None: return _any() elif isinstance(c, list): if len(c) == 1: # single-item list: sequenceof() constraint return sequenceof(c[0]) else: # list: tupleof() constraint return tupleof(*c) elif isinstance(c, dict): assert len(c) == 1 # single-item dict: mappingof() constraint return mappingof(list(c.keys())[0], list(c.values())[0]) elif isinstance(c, tuple): if all(inspect.isclass(cc) for cc in c): # multiple types: type constraint return _type_constraint(c, True) else: # any other tuple: value constraint return _value_constraint(c) elif inspect.isclass(c): # single type: type constraint return _type_constraint(c) elif isinstance(c, _constraint): # constraint object return c elif ((sys.version_info >= (2, 6) and isinstance(c, collections.Callable)) or (sys.version_info < (2, 6) and callable(c))): # function or other callable object return transform(c) else: assert False class _constraint(object): pass class _any(_constraint): def __call__(self, arg): return arg class _type_constraint(_constraint): def __init__(self, types, multiple=False): self.types = types self.multiple = multiple def __call__(self, arg): if not self.multiple: # single type, check if instance if not isinstance(arg, self.types): message = "expected %s, got %s" % (self.types.__name__, type(arg).__name__) raise TypeError(message) return arg else: # multiple types, check if instance if not isinstance(arg, self.types): argtypes = ", ".join(c.__name__ for c in self.types) message = "expected one of (%s), got %s" % (argtypes, type(arg).__name__) raise TypeError(message) return arg def __repr__(self): if not self.multiple: return self.types.__name__ else: return repr(tuple(map(_type_constraint, self.types))) class _value_constraint(_constraint): def __init__(self, values): self.values = values def __call__(self, arg): if arg not in self.values: args = ", ".join(repr(c) for c in self.values) message = "expected one of (%s), got %r" % (args, arg) raise ValueError(message) return arg def __repr__(self): return repr(tuple(self.values)) class nullable(_constraint): """ Allows the argument to be None, instead of matching any other constraint. """ def __init__(self, what): self.what = _make_constraint(what) def __call__(self, arg): if arg is None: return None return self.what(arg) def __repr__(self): return 'nullable(%r)' % self.what class sequenceof(_constraint): """ Checks that the argument is a sequence, and applies the same constraint to each element in that sequence. """ def __init__(self, what): self.what = _make_constraint(what) def __call__(self, arg): if not misc.issequence(arg): raise TypeError("not a sequence") try: t = type(arg) if not isinstance(arg, types.GeneratorType) else list return t(self.what(value) for value in arg) except (TypeError, ValueError): ex = sys.exc_info()[1] message = "illegal item in sequence: %s" % str(ex) raise type(ex)(message) def __repr__(self): return repr([self.what]) class tupleof(_constraint): """ Checks that the argument is a sequence of a fixed length, and applies different constraints to each element in that sequence. """ def __init__(self, *what): self.what = [_make_constraint(c) for c in what] def __call__(self, arg): if not misc.issequence(arg): raise TypeError("not a sequence") if len(arg) != len(self.what): message = "expected sequence of %d items, got %d" % (len(self.what), len(arg)) raise ValueError(message) try: t = type(arg) if not isinstance(arg, types.GeneratorType) else list return t(what(value) for what, value in zip(self.what, arg)) except (TypeError, ValueError): ex = sys.exc_info()[1] message = "illegal item in sequence: %s" % str(ex) raise type(ex)(message) def __repr__(self): return repr(self.what) class mappingof(_constraint): """ Checks that the argument is a dictionary, and applies constraints to each key and each value. """ def __init__(self, fromwhat, towhat): self.fromwhat = _make_constraint(fromwhat) self.towhat = _make_constraint(towhat) def __call__(self, arg): if not isinstance(arg, dict): raise TypeError("not a dictionary") try: keys = (self.fromwhat(key) for key in arg.keys()) except (TypeError, ValueError): ex = sys.exc_info()[1] message = "illegal key in dictionary: %s" % str(ex) raise type(ex)(message) try: values = (self.towhat(value) for value in arg.values()) except (TypeError, ValueError): ex = sys.exc_info()[1] message = "illegal value in dictionary: %s" % str(ex) raise type(ex)(message) return dict(zip(keys, values)) def __repr__(self): return repr({self.fromwhat: self.towhat}) class flatten(_constraint): """ Flattens all arguments into a single list, and applies a constraint to each element in that list. """ def __init__(self, what, return_type=None): self.what = _make_constraint(what) self.return_type = return_type def __call__(self, arg): try: r = [self.what(value) for value in misc.flatten(arg)] return r if self.return_type is None else self.return_type(r) except (TypeError, ValueError): ex = sys.exc_info()[1] message = "illegal item in sequence: %s" % str(ex) raise type(ex)(message) def __repr__(self): return 'flatten(%r)' % self.what class each(_constraint): """ Applies each of the given constraints. """ def __init__(self, *requirements): self.requirements = [_make_constraint(c) for c in requirements] def __call__(self, arg): for what in self.requirements: arg = what(arg) return arg def __repr__(self): return 'each(%s)' % (', '.join(repr(c) for c in self.requirements)) class either(_constraint): """ Accepts the argument if any of the given constraints can be applied. """ def __init__(self, *alternatives): self.alternatives = [_make_constraint(c) for c in alternatives] def __call__(self, arg): errors = [] for n, what in enumerate(self.alternatives): try: return what(arg) except (TypeError, ValueError): ex = sys.exc_info()[1] exstr = str(ex).replace('\n', '\n ') errors.append(" #%d %s: %s: %s" % (n + 1, what, type(ex).__name__, exstr)) message = "none of the alternatives matched:\n" + '\n'.join(errors) raise TypeError(message) def __repr__(self): return 'either(%s)' % (', '.join(repr(c) for c in self.alternatives)) class transform(_constraint): """ Applies a function to its argument. """ def __init__(self, function): self.function = function def __call__(self, arg): return self.function(arg) def __repr__(self): return _function_repr(self.function) class condition(_constraint): """ Accepts the argument if the given function returns True. """ def __init__(self, function): self.function = function def __call__(self, arg): if not self.function(arg): message = "condition not met: %s" % _function_repr(self.function) raise ValueError(message) return arg def __repr__(self): return 'condition(%s)' % _function_repr(self.function) class reduce_bitmask(_constraint): """ Flattens all arguments and reduces them to a single bitmask. """ def __init__(self, what): self.what = _make_constraint(what) def __call__(self, arg): seq = self.what(misc.flatten(arg)) return functools.reduce(lambda x, y: x | y, seq) def _function_repr(f): if misc.islambda(f): s = inspect.getsource(f).strip() # inspect.getsource() returns the entire line of code. # try to extract only the actual definition of the lambda function. # this will fail if more than one lambda function is defined on # the same line (and possibly for several other reasons...) start = s.index('lambda') end = len(s) parens = 0 for n, c in enumerate(s[start:]): if parens == 0 and c in ',)]}': end = start + n break elif c in '([{': parens += 1 elif c in ')]}': parens -= 1 return s[start:end] else: return f.__name__ mididings-20120419~ds0/mididings/__init__.py0000644000175000017500000000120211743666745020510 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings.setup import config, hook from mididings.engine import run, process_file from mididings.constants import * from mididings.scene import * from mididings.units import * __version__ = '20120419' import mididings.misc as _misc __all__ = _misc.prune_globals(globals()) mididings-20120419~ds0/mididings/misc.py0000644000175000017500000001037411740143151017670 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import inspect import functools import itertools import termios import fcntl import struct import sys import decorator def flatten(arg): """ Flatten nested sequences into a single list. """ if issequence(arg): return list(itertools.chain(*(flatten(i) for i in arg))) else: return [arg] def issequence(seq, accept_string=False): """ Return whether seq is of a sequence type. By default, strings are not considered sequences. """ if not accept_string and isinstance(seq, str): return False try: iter(seq) return True except TypeError: return False def issequenceof(seq, t): """ Return whether seq is a sequence with elements of type t. """ return issequence(seq) and all(isinstance(v, t) for v in seq) def islambda(f): lam = lambda: None return isinstance(f, type(lam)) and f.__name__ == lam.__name__ _argspec_cache = {} def getargspec(f): """ Wrapper around inspect.getargspec() that returns sensible results for functools.partial objects. All results are cached since inspect.getargspec() is a little slow. """ if f in _argspec_cache: return _argspec_cache[f] else: if isinstance(f, functools.partial): argspec = list(inspect.getargspec(f.func)) argspec[0] = argspec[0][len(f.args):] r = tuple(argspec) else: r = inspect.getargspec(f) _argspec_cache[f] = r return r class deprecated(object): def __init__(self, replacement=None): self.replacement = None def wrapper(self, f, *args, **kwargs): # XXX: avoid circular import from mididings.setup import get_config if not (hasattr(f, '_already_used') and f._already_used) and not get_config('silent'): if self.replacement: print("%s() is deprecated, please use %s() instead" % (f.__name__, self.replacement)) else: print("%s() is deprecated" % f.__name__) f._already_used = True return f(*args, **kwargs) def __call__(self, f): f._deprecated = True return decorator.decorator(self.wrapper, f) class NamedFlag(int): """ An integer type where each value has a name attached to it. """ def __new__(cls, value, name): return int.__new__(cls, value) def __init__(self, value, name): self.name = name def __getnewargs__(self): return (int(self), self.name) def __repr__(self): return self.name def __str__(self): return self.name class NamedBitMask(NamedFlag): """ Like NamedFlag, but bit operations | and ~ are also reflected in the resulting value's string representation. """ def __or__(self, other): if type(other) is not type(self): return NotImplemented return type(self)( int(self) | int(other), '%s|%s' % (self.name, other.name) ) def __invert__(self): return type(self)( ~int(self) & ((1 << 30) -1), ('~%s' if '|' not in self.name else '~(%s)') % self.name ) def prune_globals(g): return [n for (n, m) in g.items() if not inspect.ismodule(m) and not n.startswith('_') #and not (hasattr(m, '_deprecated')) ] def sequence_to_hex(data): return ' '.join(hex(x)[2:].zfill(2) for x in data) class bytestring(object): def __init__(self, data): self.data = data def __repr__(self): return '\'%s\'' % ''.join('\\x' + hex(x)[2:].zfill(2) for x in self.data) def get_terminal_size(): """ Return the height and width of the terminal. """ try: s = struct.pack("HHHH", 0, 0, 0, 0) fd = sys.stdout.fileno() x = fcntl.ioctl(fd, termios.TIOCGWINSZ, s) t = struct.unpack("HHHH", x) return t[0], t[1] except Exception: return 25, 80 mididings-20120419~ds0/mididings/scene.py0000644000175000017500000000253011737440431020034 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings.units.base import _UNIT_TYPES import mididings.patch as _patch import mididings.arguments as _arguments class Scene(object): @_arguments.accept(None, _arguments.nullable(str), _UNIT_TYPES, _arguments.nullable(_UNIT_TYPES)) def __init__(self, name, patch, init_patch=None): self.name = name if name else '' self.patch = patch self.init_patch = init_patch if init_patch else [] class SceneGroup(object): @_arguments.accept(None, str, [(Scene,) + _UNIT_TYPES]) def __init__(self, name, subscenes): self.name = name self.subscenes = subscenes def _parse_scene(scene): if isinstance(scene, Scene): pass elif isinstance(scene, tuple): scene = Scene(None, scene[1], [scene[0]]) else: scene = Scene(None, scene, None) # add any initializations defined in the processing patch (via Init() etc.) # to the init patch scene.init_patch += _patch.get_init_patches(scene.patch) return scene mididings-20120419~ds0/mididings/overload.py0000644000175000017500000001027011740253162020547 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from mididings import misc import inspect import functools def call(args, kwargs, funcs, name=None): """ Search funcs for a function with parameters such that args and kwargs can be applied, and call the first suitable function that is found. """ for f in funcs: n = len(args) # get argument names and the number of default arguments of f argspec = misc.getargspec(f) names = argspec[0] varargs = argspec[1] ndef = len(argspec[3]) if argspec[3] else 0 # names of the default arguments not overridden by positional arguments npos = max(len(names) - ndef, n) defargs = names[npos:] # check if the number of positional arguments fits, and if the remaining # parameters can be filled with keyword and default arguments. # alternatively, a suitable function with varargs is also accepted. if ((n <= len(names) and set(kwargs) | set(defargs) == set(names[n:])) or (n >= len(names) and varargs is not None)): # call f with all original arguments return f(*args, **kwargs) # no overload found, generate a comprehensible error message if name is None: name = inspect.stack()[1][3] # format arg spec for each candidate formatargspec = lambda f: inspect.formatargspec(*misc.getargspec(f)) candidates = (' %s%s' % (name, formatargspec(f)) for f in funcs) # formatargspec() doesn't seem to care that the first argument mixes # asterisks and argument names argx = ['*'] * len(args) kwargx = ['*'] * len(kwargs) formatvalue = lambda v: '=%s' % v args_used = inspect.formatargspec(argx + list(kwargs.keys()), defaults=kwargx, formatvalue=formatvalue) message = "no suitable overload found for %s%s, candidates are:\n%s" % (name, args_used, '\n'.join(candidates)) raise TypeError(message) class _Overload(object): """ Wrapper class for an arbitrary number of overloads. """ def __init__(self, name, docstring): self.name = name self.docstring = docstring self.funcs = [] def add(self, f): self.funcs.append(f) def __call__(self, *args, **kwargs): return call(args, kwargs, self.funcs, self.name) # mapping of all overloaded function names to the corresponding _Overload object _registry = {} def _register_overload(f, docstring): k = (f.__module__, f.__name__) # create a new _Overload object if necessary, add function f to it if k not in _registry: _registry[k] = _Overload(f.__name__, docstring) _registry[k].add(f) return k def mark(f, docstring=None): """ Decorator that marks a function as being overloaded. """ if isinstance(f, str): # called with docstring argument. return this function to be used as # the actual decorator return functools.partial(mark, docstring=f) k = _register_overload(f, docstring) @functools.wraps(f) def call_overload(*args, **kwargs): return _registry[k](*args, **kwargs) # use the overload's docstring if there is one if _registry[k].docstring is not None: call_overload.__doc__ = _registry[k].docstring # return a function that, instead of calling f, calls the _Overload object return call_overload class partial(object): """ Decorator that adds functools.partial objects as overloads for the given function. """ def __init__(self, *partial_args): self.partial_args = partial_args def __call__(self, f): k = _register_overload(f, None) # register partial objects as overloads for part in self.partial_args: _registry[k].add(functools.partial(f, *part)) @functools.wraps(f) def call_overload(*args, **kwargs): return _registry[k](*args, **kwargs) return call_overload mididings-20120419~ds0/mididings/patch.py0000644000175000017500000000427411736167451020054 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import _mididings import mididings.units as _units class Patch(_mididings.Patch): def __init__(self, p): _mididings.Patch.__init__(self, self.build(p)) def build(self, p): if isinstance(p, _units.base._Chain): return Patch.Chain(self.build(i) for i in p) elif isinstance(p, list): gen = (self.build(i) for i in p) remove_duplicates = True if hasattr(p, 'remove_duplicates'): remove_duplicates = (p.remove_duplicates != False) return Patch.Fork(gen, remove_duplicates) elif isinstance(p, dict): return self.build( _units.splits._make_split(_units.base.Filter, p, unpack=True) ) elif isinstance(p, _units.init._Init): return Patch.Single(_mididings.Pass(False)) elif isinstance(p, _units.base._Unit): if isinstance(p.unit, _mididings.Unit): return Patch.Single(p.unit) elif isinstance(p.unit, _mididings.UnitEx): return Patch.Extended(p.unit) message = "type '%s' not allowed in patch. offending object is: %r" % (type(p).__name__, p) raise TypeError(message) def get_init_patches(patch): if isinstance(patch, _units.base._Chain): return flatten([get_init_patches(p) for p in patch]) elif isinstance(patch, list): return flatten([get_init_patches(p) for p in patch]) elif isinstance(patch, dict): return flatten([get_init_patches(p) for p in patch.values()]) elif isinstance(patch, _units.init._Init): return [patch.init_patch] else: return [] def flatten(patch): r = [] for i in patch: if isinstance(i, list) and not isinstance(i, _units.base._Chain): r.extend(i) else: r.append(i) return r mididings-20120419~ds0/README0000644000175000017500000000245411744114111015272 0ustar alessioalessiomididings - a MIDI router/processor based on Python Copyright (C) 2008-2012 Dominic Sacré http://das.nasophon.de/mididings/ License: ======== mididings is released under the terms of the GNU General Public License, version 2 or later. The example scripts in doc/examples are in the public domain. Requirements: ============= * Python >= 2.5 [http://www.python.org/] * ALSA [http://www.alsa-project.org/] * JACK (>= 0.116.0) [http://jackaudio.org/] * Boost >= 1.34.1 (Boost.Python, Boost.Thread) [http://www.boost.org/] * Glib [http://library.gnome.org/devel/glib/] * decorator [http://pypi.python.org/pypi/decorator] Optional: ========= * pyliblo [http://das.nasophon.de/pyliblo/] (to send or receive OSC messages) * dbus-python [http://dbus.freedesktop.org/releases/dbus-python/] (to send DBUS messages) * pyinotify >= 0.8 [http://trac.dbzteam.org/pyinotify">pyinotify] (to automatically restart when a script changes) * Tkinter (for the livedings GUI) * libsmf [http://sourceforge.net/projects/libsmf] (to read/write standard MIDI files using the process_file() function) Installation: ============= ./setup.py build [--disable-jack-midi] [--disable-alsa-seq] [--enable-smf] ./setup.py install Documentation: ============== See doc/index.html for the mididings manual. mididings-20120419~ds0/setup.py0000755000175000017500000001006711743666734016154 0ustar alessioalessio#!/usr/bin/env python from distutils.core import setup, Extension from distutils import sysconfig import sys import platform import os.path if sys.version_info >= (3,): from subprocess import getstatusoutput else: from commands import getstatusoutput config = { 'alsa-seq': (platform.system() == 'Linux'), 'jack-midi': True, 'smf': False, } def check_option(name, argv): for arg in argv: if arg == '--enable-%s' % name: sys.argv.remove(arg) config[name] = True elif arg == '--disable-%s' % name: sys.argv.remove(arg) config[name] = False check_option('alsa-seq', sys.argv[1:]) check_option('jack-midi', sys.argv[1:]) check_option('smf', sys.argv[1:]) def pkgconfig(pkg): status, output = getstatusoutput('pkg-config --libs --cflags %s' % pkg) if status: sys.exit("couldn't find package '%s'" % pkg) for token in output.split(): opt, val = token[:2], token[2:] if opt == '-I': include_dirs.append(val) elif opt == '-l': libraries.append(val) elif opt == '-L': library_dirs.append(val) def boost_lib_name(lib): for libdir in ('/usr/lib', '/usr/local/lib', '/usr/lib64', '/usr/local/lib64'): for suffix in ('', '-mt'): libname = 'lib%s%s.so' % (lib, suffix) if os.path.isfile(os.path.join(libdir, libname)): return lib + suffix return lib + '-mt' sources = [ 'src/engine.cc', 'src/patch.cc', 'src/python_caller.cc', 'src/python_module.cc', 'src/backend/base.cc', ] include_dirs = [] define_macros = [] libraries = [] library_dirs = [] include_dirs.append('src') pkgconfig('glib-2.0') libraries.append(boost_lib_name('boost_python')) libraries.append(boost_lib_name('boost_thread')) # uncomment and adapt these to build using a custom install of boost. # you may also need to comment out the two lines above #include_dirs.append('/opt/boost1.43/include') #library_dirs.append('/opt/boost1.43/lib') #libraries.append('boost_python') #libraries.append('boost_thread') if config['alsa-seq']: define_macros.append(('ENABLE_ALSA_SEQ', 1)) sources.append('src/backend/alsa.cc') pkgconfig('alsa') if config['jack-midi']: define_macros.append(('ENABLE_JACK_MIDI', 1)) sources.extend(['src/backend/jack.cc', 'src/backend/jack_buffered.cc', 'src/backend/jack_realtime.cc']) pkgconfig('jack') if config['smf']: define_macros.append(('ENABLE_SMF', 1)) sources.append('src/backend/smf.cc') pkgconfig('smf') # hack to modify the compiler flags from the distutils default distutils_customize_compiler = sysconfig.customize_compiler def my_customize_compiler(compiler): retval = distutils_customize_compiler(compiler) try: # -Wstrict-prototypes is not valid for C++ compiler.compiler_so.remove('-Wstrict-prototypes') # immediately stop on error compiler.compiler_so.append('-Wfatal-errors') # some options to reduce the size of the binary compiler.compiler_so.remove('-g') compiler.compiler_so.append('-finline-functions') compiler.compiler_so.append('-fvisibility=hidden') except (AttributeError, ValueError): pass return retval sysconfig.customize_compiler = my_customize_compiler setup( name = 'mididings', version = '20120419', author = 'Dominic Sacre', author_email = 'dominic.sacre@gmx.de', url = 'http://das.nasophon.de/mididings/', description = 'a MIDI router/processor', license = 'GPL', ext_modules = [ Extension( name = '_mididings', sources = sources, include_dirs = include_dirs, library_dirs = library_dirs, libraries = libraries, define_macros = define_macros, ), ], packages = [ 'mididings', 'mididings.units', 'mididings.extra', 'mididings.live', ], scripts = [ 'scripts/mididings', 'scripts/livedings', ], ) mididings-20120419~ds0/COPYING0000644000175000017500000004310311055641404015446 0ustar alessioalessio GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. mididings-20120419~ds0/tests/0000755000175000017500000000000011737711704015564 5ustar alessioalessiomididings-20120419~ds0/tests/units/0000755000175000017500000000000011736725034016726 5ustar alessioalessiomididings-20120419~ds0/tests/units/test_modifiers.py0000644000175000017500000002405711737711704022330 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from __future__ import with_statement from tests.helpers import * from mididings import * import random class ModifiersTestCase(MididingsTestCase): @data_offsets def test_Port(self, off): ev = self.make_event() self.check_patch(Port(off(7)), { ev: [self.modify_event(ev, port=off(7))], }) self.check_patch(Port(port=off(8)), { ev: [self.modify_event(ev, port=off(8))], }) with self.assertRaises(TypeError): Port(123.456) @data_offsets def test_Channel(self, off): ev = self.make_event() self.check_patch(Channel(off(7)), { ev: [self.modify_event(ev, channel=off(7))], }) self.check_patch(Channel(channel=off(8)), { ev: [self.modify_event(ev, channel=off(8))], }) with self.assertRaises(TypeError): Channel(123.456) with self.assertRaises(TypeError): Channel('blah') with self.assertRaises(ValueError): Channel(17) @data_offsets def test_Transpose(self, off): ev1 = self.make_event(NOTEON, note=random.randrange(0, 115)) ev2 = self.make_event(NOTEON, note=random.randrange(0, 115)) ev3 = self.make_event(CTRL) self.check_patch(Transpose(12), { ev1: [self.modify_event(ev1, note=ev1.note + 12)], ev2: [self.modify_event(ev2, note=ev2.note + 12)], ev3: [ev3], }) with self.assertRaises(TypeError): Transpose(123.456) with self.assertRaises(TypeError): Transpose('blah') @data_offsets def test_Key(self, off): ev = self.make_event(NOTE) ev2 = self.make_event(CTRL) self.check_patch(Key(42), { ev: [self.modify_event(ev, note=42)], ev2: [ev2], }) with self.assertRaises(TypeError): Key(123.456) with self.assertRaises(ValueError): Key('blah') with self.assertRaises(ValueError): Key(-1) with self.assertRaises(ValueError): Key(128) @data_offsets def test_Velocity(self, off): ev = self.make_event(NOTEON, velocity=42) ev2 = self.make_event(PROGRAM) self.check_patch(Velocity(23), { ev: [self.modify_event(ev, velocity=65)], ev2: [ev2], }) self.check_patch(Velocity(multiply=1.5), { ev: [self.modify_event(ev, velocity=63)], ev2: [ev2], }) self.check_patch(Velocity(fixed=108), { ev: [self.modify_event(ev, velocity=108)], ev2: [ev2], }) self.check_patch(Velocity(gamma=2.0), { ev: [self.modify_event(ev, velocity=73)], ev2: [ev2], }) self.check_patch(Velocity(curve=-2.0), { ev: [self.modify_event(ev, velocity=18)], ev2: [ev2], }) self.check_patch(Velocity(multiply=1.5, offset=23), { ev: [self.modify_event(ev, velocity=86)], ev2: [ev2], }) with self.assertRaises(TypeError): Velocity('blah') with self.assertRaises(TypeError): Velocity(something=123) @data_offsets def test_VelocitySlope(self, off): ev0 = self.make_event(NOTEON, note=0, velocity=42) ev23 = self.make_event(NOTEON, note=23, velocity=42) ev32 = self.make_event(NOTEON, note=32, velocity=42) ev42 = self.make_event(NOTEON, note=42, velocity=42) ev127 = self.make_event(NOTEON, note=127, velocity=42) self.check_patch(VelocitySlope((23, 42), (-13, +13)), { ev0: [self.modify_event(ev0, velocity=29)], ev23: [self.modify_event(ev23, velocity=29)], ev32: [self.modify_event(ev32, velocity=42)], ev42: [self.modify_event(ev42, velocity=55)], ev127: [self.modify_event(ev127, velocity=55)], }) self.check_patch(VelocitySlope(('b-1', 'f#1'), multiply=(0.5, 2.0)), { ev0: [self.modify_event(ev0, velocity=21)], ev23: [self.modify_event(ev23, velocity=21)], ev32: [self.modify_event(ev32, velocity=50)], ev42: [self.modify_event(ev42, velocity=84)], ev127: [self.modify_event(ev127, velocity=84)], }) self.check_patch(VelocitySlope((23, 42), fixed=(13, 113)), { ev0: [self.modify_event(ev0, velocity=13)], ev23: [self.modify_event(ev23, velocity=13)], ev32: [self.modify_event(ev32, velocity=60)], ev42: [self.modify_event(ev42, velocity=113)], ev127: [self.modify_event(ev127, velocity=113)], }) self.check_patch(VelocitySlope(('b-1', 'f#1'), gamma=(1.0, 2.0)), { ev0: [self.modify_event(ev0, velocity=42)], ev23: [self.modify_event(ev23, velocity=42)], ev32: [self.modify_event(ev32, velocity=60)], ev42: [self.modify_event(ev42, velocity=73)], ev127: [self.modify_event(ev127, velocity=73)], }) self.check_patch(VelocitySlope((23, 42), curve=(0, -2.0)), { ev0: [self.modify_event(ev0, velocity=42)], ev23: [self.modify_event(ev23, velocity=42)], ev32: [self.modify_event(ev32, velocity=29)], ev42: [self.modify_event(ev42, velocity=18)], ev127: [self.modify_event(ev127, velocity=18)], }) self.check_patch(VelocitySlope((23, 42), (0.5, 2.0), (-13, +13)), { ev0: [self.modify_event(ev0, velocity=8)], ev23: [self.modify_event(ev23, velocity=8)], ev32: [self.modify_event(ev32, velocity=50)], ev42: [self.modify_event(ev42, velocity=97)], ev127: [self.modify_event(ev127, velocity=97)], }) with self.assertRaises(TypeError): VelocitySlope((23, 42, 66.6), (4, 8, 15)) with self.assertRaises(ValueError): VelocitySlope((23, 42, 66), (4, 8)) with self.assertRaises(ValueError): VelocitySlope((23, 66, 42), (4, 8, 15)) @data_offsets def test_VelocityLimit(self, off): ev23 = self.make_event(NOTEON, velocity=23) ev42 = self.make_event(NOTEON, velocity=42) ev108 = self.make_event(NOTEON, velocity=108) ev_noteoff = self.make_event(NOTEOFF) ev_ctrl = self.make_event(CTRL) self.check_patch(VelocityLimit(32, 69), { ev23: [self.modify_event(ev23, velocity=32)], ev42: [self.modify_event(ev42, velocity=42)], ev108: [self.modify_event(ev108, velocity=69)], ev_noteoff: [ev_noteoff], ev_ctrl: [ev_ctrl], }) self.check_patch(VelocityLimit(max=69), { ev23: [self.modify_event(ev23, velocity=23)], ev42: [self.modify_event(ev42, velocity=42)], ev108: [self.modify_event(ev108, velocity=69)], ev_noteoff: [ev_noteoff], ev_ctrl: [ev_ctrl], }) self.check_patch(VelocityLimit(min=32), { ev23: [self.modify_event(ev23, velocity=32)], ev42: [self.modify_event(ev42, velocity=42)], ev108: [self.modify_event(ev108, velocity=108)], ev_noteoff: [ev_noteoff], ev_ctrl: [ev_ctrl], }) @data_offsets def test_CtrlMap(self, off): ev1 = self.make_event(CTRL, ctrl=23) ev2 = self.make_event(CTRL, ctrl=42) ev3 = self.make_event(PROGRAM) self.check_patch(CtrlMap(23, 69), { ev1: [self.modify_event(ev1, ctrl=69)], ev2: [ev2], ev3: [ev3], }) @data_offsets def test_CtrlRange(self, off): ev1 = self.make_event(CTRL, ctrl=23, value=0) ev2 = self.make_event(CTRL, ctrl=23, value=64) ev3 = self.make_event(CTRL, ctrl=23, value=127) self.check_patch(CtrlRange(23, 42, 69), { ev1: [self.modify_event(ev1, value=42)], ev2: [self.modify_event(ev2, value=55)], ev3: [self.modify_event(ev3, value=69)], }) self.check_patch(CtrlRange(23, 42, 69, 64, 127), { ev1: [self.modify_event(ev1, value=42)], ev2: [self.modify_event(ev2, value=42)], ev3: [self.modify_event(ev3, value=69)], }) @data_offsets def test_CtrlCurve(self, off): ev = self.make_event(CTRL, ctrl=23, value=42) ev2 = self.make_event(AFTERTOUCH) self.check_patch(CtrlCurve(23, offset=23), { ev: [self.modify_event(ev, value=65)], ev2: [ev2], }) self.check_patch(CtrlCurve(23, multiply=1.5), { ev: [self.modify_event(ev, value=63)], ev2: [ev2], }) self.check_patch(CtrlCurve(23, gamma=2.0), { ev: [self.modify_event(ev, value=73)], ev2: [ev2], }) self.check_patch(CtrlCurve(23, curve=-2.0), { ev: [self.modify_event(ev, value=18)], ev2: [ev2], }) self.check_patch(CtrlCurve(23, 1.5, 23), { ev: [self.modify_event(ev, value=86)], ev2: [ev2], }) @data_offsets def test_Pitchbend(self, off): ev1 = self.make_event(PITCHBEND, value=-8192) ev2 = self.make_event(PITCHBEND, value=0) ev3 = self.make_event(PITCHBEND, value=8191) self.check_patch(PitchbendRange(-1234, 5678), { ev1: [self.modify_event(ev1, value=-1234)], ev2: [self.modify_event(ev2, value=0)], ev3: [self.modify_event(ev3, value=5678)], }) self.check_patch(PitchbendRange(-12, 2, range=12), { ev1: [self.modify_event(ev1, value=-8192)], ev2: [self.modify_event(ev2, value=0)], ev3: [self.modify_event(ev3, value=1365)], }) mididings-20120419~ds0/tests/units/test_engine.py0000644000175000017500000000336311733510374021605 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from tests.helpers import * from mididings import * class EngineTestCase(MididingsTestCase): @data_offsets def testSanitize(self, off): def foo(ev): ev.port = off(42) def bar(ev): self.fail() p = Process(foo) >> Sanitize() >> Process(bar) self.run_patch(p, self.make_event(port=off(42))) p = Velocity(+666) >> Sanitize() r = self.run_patch(p, self.make_event(NOTEON, velocity=42)) self.assertEqual(len(r), 1) self.assertEqual(r[0].data2, 127) @data_offsets def testSceneSwitch(self, off): config(silent=True) p = { off(0): Split({ PROGRAM: SceneSwitch(), ~PROGRAM: Channel(off(7)), }), off(1): Channel(off(13)), } events = ( self.make_event(NOTEON, off(0), off(0), 69, 123), self.make_event(PROGRAM, off(0), off(0), 0, 1), # no data offset! self.make_event(NOTEON, off(0), off(0), 23, 42), self.make_event(NOTEOFF, off(0), off(0), 69, 0), ) results = [ self.make_event(NOTEON, off(0), off(7), 69, 123), self.make_event(NOTEON, off(0), off(13), 23, 42), self.make_event(NOTEOFF, off(0), off(7), 69, 0), ] self.check_scenes(p, { events: results, }) mididings-20120419~ds0/tests/units/test_splits.py0000644000175000017500000000445711736725034021667 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from tests.helpers import * from mididings import * class SplitsTestCase(MididingsTestCase): @data_offsets def test_PortSplit(self, off): p = PortSplit({ off(0): Discard(), (off(1), off(2)): Pass() }) self.check_patch(p, { self.make_event(port=off(0)): False, self.make_event(port=off(1)): True, }) p = PortSplit({ off(0): Discard(), (off(2), off(3)): Discard(), None: Pass() }) self.check_patch(p, { self.make_event(port=off(0)): False, self.make_event(port=off(1)): True, }) @data_offsets def test_ChannelSplit(self, off): p = ChannelSplit({ off(0): Discard(), (off(1), off(2)): Pass() }) self.check_patch(p, { self.make_event(channel=off(0)): False, self.make_event(channel=off(1)): True, }) p = ChannelSplit({ off(0): Discard(), (off(2), off(3)): Discard(), None: Pass() }) self.check_patch(p, { self.make_event(channel=off(0)): False, self.make_event(channel=off(1)): True, }) @data_offsets def test_KeySplit(self, off): ev1 = self.make_event(NOTEON, note=66) ev2 = self.make_event(NOTEON, note=42) ev3 = self.make_event(PROGRAM) p = KeySplit(55, Channel(off(3)), Channel(off(7))) self.check_patch(p, { ev1: [self.modify_event(ev1, channel=off(7))], ev2: [self.modify_event(ev2, channel=off(3))], ev3: [self.modify_event(ev3, channel=off(3)), self.modify_event(ev3, channel=off(7))], }) p = KeySplit({ (23, 52): Pass(), (52, 69): Discard(), (69, 88): Pass(), }) self.check_patch(p, { ev1: False, ev2: True, ev3: True, }) mididings-20120419~ds0/tests/units/test_base.py0000644000175000017500000001064311737161772021261 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from tests.helpers import * from mididings import * class BaseTestCase(MididingsTestCase): def test_Pass(self): self.check_patch(Pass(), { self.make_event(): True, }) self.check_patch(Discard(), { self.make_event(): False, }) def test_Fork(self): ev = self.make_event(port=2) p = Fork([Pass(), Pass()]) self.check_patch(p, {ev: True}) p = Pass() // Pass() self.check_patch(p, {ev: True}) p = +Port(3) self.check_patch(p, {ev: [ev, self.modify_event(ev, port=3)]}) p = Fork([Pass(), Discard(), Pass()]) self.check_patch(p, {ev: [ev]}) p = Fork([Pass(), Pass()], remove_duplicates=False) self.check_patch(p, {ev: [ev, ev]}) p = Fork([Pass(), Discard(), Pass()], remove_duplicates=False) self.check_patch(p, {ev: [ev, ev]}) def test_Filter(self): self.check_filter(Filter([PROGRAM]), { self.make_event(NOTEON): (False, True), self.make_event(NOTEOFF): (False, True), self.make_event(CTRL): (False, True), self.make_event(PROGRAM): (True, False), }) self.check_filter(Filter(types=NOTE), { self.make_event(NOTEON): (True, False), self.make_event(NOTEOFF): (True, False), self.make_event(CTRL): (False, True), self.make_event(PROGRAM): (False, True), }) self.check_filter(Filter(NOTE, CTRL, AFTERTOUCH), { self.make_event(NOTEON): (True, False), self.make_event(NOTEOFF): (True, False), self.make_event(CTRL): (True, False), self.make_event(PROGRAM): (False, True), }) self.check_filter(Filter(types=[CTRL, [PROGRAM], AFTERTOUCH|PITCHBEND]), { self.make_event(NOTEON): (False, True), self.make_event(CTRL): (True, False), self.make_event(PROGRAM): (True, False), self.make_event(AFTERTOUCH): (True, False), self.make_event(PITCHBEND): (True, False), }) def test_Split(self): ev1 = self.make_event(NOTEON, channel=1) ev2 = self.make_event(PROGRAM, channel=2) ev3 = self.make_event(CTRL) p = Split({ NOTE: Channel(4), PROGRAM: Channel(8) }) self.check_patch(p, { ev1: [self.modify_event(ev1, channel=4)], ev2: [self.modify_event(ev2, channel=8)], ev3: [], }) def test_Selector(self): p = CtrlFilter(23) % CtrlValueFilter(123) self.check_patch(p, { self.make_event(NOTEON): True, self.make_event(CTRL, ctrl=23, value=42): False, self.make_event(CTRL, ctrl=42, value=123): True, }) p = CtrlFilter(42) % CtrlValueFilter(123) self.check_patch(p, { self.make_event(NOTEON): True, self.make_event(CTRL, ctrl=23, value=42): True, self.make_event(CTRL, ctrl=42, value=123): True, }) p = (Filter(CTRL) & CtrlFilter(42) & CtrlValueFilter(123)) % Discard() self.check_patch(p, { self.make_event(NOTEON): True, self.make_event(CTRL, ctrl=23, value=42): True, self.make_event(CTRL, ctrl=42, value=123): False, }) p = (CtrlFilter(42) | CtrlValueFilter(123)) % Discard() self.check_patch(p, { self.make_event(NOTEON): True, self.make_event(CTRL, ctrl=23, value=42): True, self.make_event(CTRL, ctrl=42, value=123): False, }) p = (Filter(NOTE) | (CtrlFilter(42) & CtrlValueFilter(123))) % Discard() self.check_patch(p, { self.make_event(NOTEON): False, self.make_event(CTRL, ctrl=23, value=42): True, self.make_event(CTRL, ctrl=42, value=123): False, }) p = CtrlFilter(42) % (CtrlValueFilter(123) % Discard()) self.check_patch(p, { self.make_event(NOTEON): True, self.make_event(CTRL, ctrl=23, value=42): True, self.make_event(CTRL, ctrl=42, value=123): False, }) mididings-20120419~ds0/tests/units/__init__.py0000644000175000017500000000000011525543030021013 0ustar alessioalessiomididings-20120419~ds0/tests/units/test_generators.py0000644000175000017500000000652611735657770022533 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from tests.helpers import * from mididings import * class GeneratorsTestCase(MididingsTestCase): @data_offsets def test_NoteOn(self, off): ev = self.make_event(CTRL) p = NoteOn(60, 127) self.check_patch(p, { ev: [self.make_event(NOTEON, ev.port, ev.channel, 60, 127)], }) p = NoteOn(port=off(2), channel=off(3), note=EVENT_CTRL, velocity=42) self.check_patch(p, { ev: [self.make_event(NOTEON, off(2), off(3), ev.ctrl, 42)], }) @data_offsets def test_NoteOff(self, off): ev = self.make_event(CTRL) p = NoteOff(60, 127) self.check_patch(p, { ev: [self.make_event(NOTEOFF, ev.port, ev.channel, 60, 127)], }) p = NoteOff(port=off(2), channel=off(3), note=EVENT_CTRL, velocity=42) self.check_patch(p, { ev: [self.make_event(NOTEOFF, off(2), off(3), ev.ctrl, 42)], }) @data_offsets def test_Ctrl(self, off): ev = self.make_event(NOTEON) p = Ctrl(23, 42) self.check_patch(p, { ev: [self.make_event(CTRL, ev.port, ev.channel, 23, 42)], }) p = Ctrl(port=off(2), channel=off(3), ctrl=23, value=EVENT_NOTE) self.check_patch(p, { ev: [self.make_event(CTRL, off(2), off(3), 23, ev.note)], }) @data_offsets def test_Pitchbend(self, off): ev = self.make_event(CTRL) p = Pitchbend(8191) self.check_patch(p, { ev: [self.make_event(PITCHBEND, ev.port, ev.channel, 0, 8191)], }) p = Pitchbend(port=off(2), channel=off(3), value=EVENT_VALUE) self.check_patch(p, { ev: [self.make_event(PITCHBEND, off(2), off(3), 0, ev.value)], }) @data_offsets def test_Aftertouch(self, off): ev = self.make_event(CTRL) p = Aftertouch(42) self.check_patch(p, { ev: [self.make_event(AFTERTOUCH, ev.port, ev.channel, 0, 42)], }) p = Aftertouch(port=off(2), channel=off(3), value=EVENT_VALUE) self.check_patch(p, { ev: [self.make_event(AFTERTOUCH, off(2), off(3), 0, ev.value)], }) @data_offsets def test_Program(self, off): ev = self.make_event(NOTEON) p = Program(off(42)) self.check_patch(p, { ev: [self.make_event(PROGRAM, ev.port, ev.channel, 0, 42)], }) p = Program(port=off(2), channel=off(3), program=EVENT_NOTE) self.check_patch(p, { ev: [self.make_event(PROGRAM, off(2), off(3), 0, ev.note)], }) @data_offsets def test_SysEx(self, off): ev = self.make_event(NOTEON) p = SysEx([0xf0, 4, 8, 15, 16, 23, 42, 0xf7]) self.check_patch(p, { ev: [SysExEvent(ev.port, [0xf0, 4, 8, 15, 16, 23, 42, 0xf7])], }) p = SysEx(port=off(2), sysex=[0xf0, 4, 8, 15, 16, 23, 42, 0xf7]) self.check_patch(p, { ev: [SysExEvent(off(2), [0xf0, 4, 8, 15, 16, 23, 42, 0xf7])], }) mididings-20120419~ds0/tests/units/test_call.py0000644000175000017500000000310711736203345021247 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from tests.helpers import * from mididings import * class CallTestCase(MididingsTestCase): @data_offsets def test_Process(self, off): def foo(ev): self.assertEqual(ev.type, NOTEON) self.assertEqual(ev.port, off(0)) self.assertEqual(ev.channel, off(0)) self.assertEqual(ev.note, 66) self.assertEqual(ev.velocity, 23) ev.type = CTRL ev.port = off(4) ev.channel = off(5) ev.ctrl = 23 ev.value = 42 return ev self.check_patch(Process(foo), { self.make_event(NOTEON, off(0), off(0), 66, 23): [self.make_event(CTRL, off(4), off(5), 23, 42)], }) def test_Process_return(self): ev = self.make_event() self.check_patch(Process(lambda ev: ev), { ev: [ev] }) self.check_patch(Process(lambda ev: None), { ev: [] }) self.check_patch(Process(lambda ev: []), { ev: [] }) self.check_patch(Process(lambda ev: [ev, ev, ev]), { ev: [ev, ev, ev] }) self.check_patch(Process(lambda ev: (ev for ev in [ev, ev])), { ev: [ev, ev] }) mididings-20120419~ds0/tests/units/test_filters.py0000644000175000017500000002553111737711704022015 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from __future__ import with_statement from tests.helpers import * from mididings import * class FiltersTestCase(MididingsTestCase): @data_offsets def test_PortFilter(self, off): config(in_ports = ['foo', 'bar', 'baz']) self.check_filter(PortFilter(off(0)), { self.make_event(port=off(0)): (True, False), self.make_event(port=off(1)): (False, True), }) self.check_filter(PortFilter(ports=off(1)), { self.make_event(port=off(0)): (False, True), self.make_event(port=off(1)): (True, False), }) self.check_filter(PortFilter(off(0), off(1), off(2)), { self.make_event(port=off(0)): (True, False), self.make_event(port=off(1)): (True, False), self.make_event(port=off(2)): (True, False), self.make_event(port=off(3)): (False, True), }) self.check_filter(PortFilter(ports=['foo', 'baz']), { self.make_event(port=off(0)): (True, False), self.make_event(port=off(1)): (False, True), self.make_event(port=off(2)): (True, False), self.make_event(port=off(3)): (False, True), }) with self.assertRaises(TypeError): PortFilter() with self.assertRaises(TypeError): PortFilter(123.456) with self.assertRaises(ValueError): PortFilter('nonexist') with self.assertRaises(ValueError): PortFilter(off(-1)) @data_offsets def test_ChannelFilter(self, off): self.check_filter(ChannelFilter(off(2)), { self.make_event(channel=off(2)): (True, False), self.make_event(channel=off(3)): (False, True), }) self.check_filter(ChannelFilter(channels=[off(3)]), { self.make_event(channel=off(2)): (False, True), self.make_event(channel=off(3)): (True, False), }) self.check_filter(ChannelFilter(off(3), off(4), off(5)), { self.make_event(channel=off(2)): (False, True), self.make_event(channel=off(3)): (True, False), self.make_event(channel=off(4)): (True, False), self.make_event(channel=off(5)): (True, False), }) self.check_filter(ChannelFilter([off(3), off(4), off(5)]), { self.make_event(channel=off(2)): (False, True), self.make_event(channel=off(3)): (True, False), self.make_event(channel=off(4)): (True, False), self.make_event(channel=off(5)): (True, False), }) with self.assertRaises(TypeError): ChannelFilter() with self.assertRaises(TypeError): ChannelFilter('blah') with self.assertRaises(ValueError): ChannelFilter(off(-1)) with self.assertRaises(ValueError): ChannelFilter(off(16)) @data_offsets def test_KeyFilter(self, off): self.check_filter(KeyFilter('e2:a3'), { self.make_event(NOTE, note=51): (False, True), self.make_event(NOTE, note=52): (True, False), self.make_event(NOTE, note=68): (True, False), self.make_event(NOTE, note=69): (False, True), self.make_event(PROGRAM): (True, True), self.make_event(CTRL): (True, True), }) self.check_filter(KeyFilter('c3'), { self.make_event(NOTE, note=59): (False, True), self.make_event(NOTE, note=60): (True, False), self.make_event(NOTE, note=61): (False, True), }) self.check_filter(KeyFilter('e2', 'a3'), { self.make_event(NOTE, note=23): (False, True), self.make_event(NOTE, note=60): (True, False), }) self.check_filter(KeyFilter(lower='c3'), { self.make_event(NOTE, note=23): (False, True), self.make_event(NOTE, note=59): (False, True), self.make_event(NOTE, note=60): (True, False), self.make_event(NOTE, note=108): (True, False), }) self.check_filter(KeyFilter(upper=60), { self.make_event(NOTE, note=23): (True, False), self.make_event(NOTE, note=59): (True, False), self.make_event(NOTE, note=60): (False, True), self.make_event(NOTE, note=108): (False, True), }) self.check_filter(KeyFilter(notes=[23, 42]), { self.make_event(NOTE, note=16): (False, True), self.make_event(NOTE, note=23): (True, False), self.make_event(NOTE, note=32): (False, True), self.make_event(NOTE, note=42): (True, False), self.make_event(NOTE, note=69): (False, True), }) with self.assertRaises(TypeError): KeyFilter() with self.assertRaises(TypeError): KeyFilter(123.456) @data_offsets def test_VelocityFilter(self, off): self.check_filter(VelocityFilter(42), { self.make_event(NOTEON, velocity=23): (False, True), self.make_event(NOTEON, velocity=42): (True, False), self.make_event(NOTEOFF, velocity=23): (True, True), self.make_event(NOTEOFF, velocity=42): (True, True), self.make_event(PROGRAM): (True, True), self.make_event(CTRL): (True, True), }) self.check_filter(VelocityFilter(lower=42), { self.make_event(NOTEON, velocity=41): (False, True), self.make_event(NOTEON, velocity=42): (True, False), self.make_event(NOTEON, velocity=69): (True, False), }) self.check_filter(VelocityFilter(upper=108), { self.make_event(NOTEON, velocity=23): (True, False), self.make_event(NOTEON, velocity=107): (True, False), self.make_event(NOTEON, velocity=108): (False, True), }) self.check_filter(VelocityFilter(lower=64, upper=128), { self.make_event(NOTEON, velocity=23): (False, True), self.make_event(NOTEON, velocity=127): (True, False), self.make_event(NOTEOFF, velocity=23): (True, True), self.make_event(NOTEOFF, velocity=127): (True, True), self.make_event(PROGRAM): (True, True), self.make_event(CTRL): (True, True), }) with self.assertRaises(TypeError): VelocityFilter() with self.assertRaises(TypeError): VelocityFilter(123.456) with self.assertRaises(TypeError): VelocityFilter('blah') with self.assertRaises(ValueError): VelocityFilter(-1) with self.assertRaises(ValueError): VelocityFilter(128) @data_offsets def test_CtrlFilter(self, off): self.check_filter(CtrlFilter(23), { self.make_event(CTRL, ctrl=23): (True, False), self.make_event(CTRL, ctrl=42): (False, True), self.make_event(NOTEON): (False, False), self.make_event(PROGRAM): (False, False), }) self.check_filter(CtrlFilter(23, 42), { self.make_event(CTRL, ctrl=23): (True, False), self.make_event(CTRL, ctrl=42): (True, False), self.make_event(CTRL, ctrl=69): (False, True), }) self.check_filter(CtrlFilter(ctrls=[23, 42]), { self.make_event(CTRL, ctrl=23): (True, False), self.make_event(CTRL, ctrl=42): (True, False), self.make_event(CTRL, ctrl=69): (False, True), }) with self.assertRaises(TypeError): CtrlFilter(123.456) with self.assertRaises(TypeError): CtrlFilter('blah') with self.assertRaises(ValueError): CtrlFilter(-1) with self.assertRaises(ValueError): CtrlFilter(128) @data_offsets def test_CtrlValueFilter(self, off): self.check_filter(CtrlValueFilter(value=23), { self.make_event(CTRL, value=16): (False, True), self.make_event(CTRL, value=23): (True, False), self.make_event(CTRL, value=66): (False, True), self.make_event(NOTE): (False, False), self.make_event(PROGRAM): (False, False), }) self.check_filter(CtrlValueFilter(23, 42), { self.make_event(CTRL, value=32): (True, False), self.make_event(CTRL, value=66): (False, True), }) @data_offsets def test_ProgramFilter(self, off): self.check_filter(ProgramFilter(off(1)), { self.make_event(PROGRAM, program=off(1)): (True, False), self.make_event(PROGRAM, program=off(2)): (False, True), self.make_event(NOTE): (False, False), self.make_event(CTRL): (False, False), }) self.check_filter(ProgramFilter(off(4), off(8), off(15), off(16)), { self.make_event(PROGRAM, program=off(8)): (True, False), self.make_event(PROGRAM, program=off(13)): (False, True), }) self.check_filter(ProgramFilter(programs=[off(4), off(8), off(15), off(16)]), { self.make_event(PROGRAM, program=off(8)): (True, False), self.make_event(PROGRAM, program=off(13)): (False, True), }) with self.assertRaises(TypeError): ProgramFilter() with self.assertRaises(TypeError): ProgramFilter(123.456) with self.assertRaises(TypeError): ProgramFilter('blah') with self.assertRaises(ValueError): ProgramFilter(off(-1)) with self.assertRaises(ValueError): ProgramFilter(off(128)) @data_offsets def test_SysExFilter(self, off): self.check_filter(SysExFilter([0xf0, 4, 8, 15, 16, 23, 42, 0xf7]), { SysExEvent(off(0), [0xf0, 4, 8, 15, 16, 23, 42, 0xf7]): (True, False), SysExEvent(off(0), [0xf0, 4, 8, 15, 16, 23, 43, 0xf7]): (False, True), self.make_event(NOTE): (False, False), self.make_event(CTRL): (False, False), }) self.check_filter(SysExFilter('\xf0\x04\x08'), { SysExEvent(off(0), [0xf0, 4, 8, 15, 16, 23, 42, 0xf7]): (True, False), SysExEvent(off(0), [0xf0, 4, 9, 15, 16, 23, 42, 0xf7]): (False, True), }) self.check_filter(SysExFilter(manufacturer=0x42), { SysExEvent(off(0), [0xf0, 0x42, 8, 15, 16, 23, 42, 0xf7]): (True, False), SysExEvent(off(0), [0xf0, 0x43, 8, 15, 16, 23, 42, 0xf7]): (False, True), }) self.check_filter(SysExFilter(manufacturer='\x00\x23\x42'), { SysExEvent(off(0), [0xf0, 0x00, 0x23, 0x42, 16, 23, 42, 0xf7]): (True, False), SysExEvent(off(0), [0xf0, 4, 8, 15, 16, 23, 42, 0xf7]): (False, True), }) mididings-20120419~ds0/tests/test_engine.py0000644000175000017500000000224611733510374020442 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from tests.helpers import * from mididings import * from mididings import engine class EngineTestCase(MididingsTestCase): def test_in_ports(self): ports = ['foo', 'bar', 'baz'] config(in_ports = ports) self.assertEqual(engine.in_ports(), ports) config(in_ports = 3) self.assertEqual(engine.in_ports(), ['in_0', 'in_1', 'in_2']) def test_out_ports(self): ports = ['foo', 'bar', 'baz'] config(out_ports = ports) self.assertEqual(engine.out_ports(), ports) config(out_ports = 3) self.assertEqual(engine.out_ports(), ['out_0', 'out_1', 'out_2']) def test_active(self): self.assertFalse(engine.active()) def foo(ev): self.assertTrue(engine.active()) self.run_patch(Process(foo), self.make_event()) mididings-20120419~ds0/tests/test_event.py0000644000175000017500000002104011737711704020313 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from __future__ import with_statement from tests.helpers import * from mididings import * from mididings.event import * import mididings.constants import copy import pickle class EventTestCase(MididingsTestCase): @data_offsets def test_NoteOnEvent(self, off): ev = NoteOnEvent(off(1), off(7), 60, 127) def foo(ev): self.assertEqual(ev.type, NOTEON) self.assertEqual(ev.port, off(1)) self.assertEqual(ev.port_, 1) self.assertEqual(ev.channel, off(7)) self.assertEqual(ev.channel_, 7) self.assertEqual(ev.note, 60) self.assertEqual(ev.data1, 60) self.assertEqual(ev.velocity, 127) self.assertEqual(ev.data2, 127) ev.port = off(3) ev.channel = off(5) ev.note = 69 ev.velocity = 42 return ev (r,) = self.run_patch(Process(foo), ev) self.assertEqual(r.type, NOTEON) self.assertEqual(r.port, off(3)) self.assertEqual(r.port_, 3) self.assertEqual(r.channel, off(5)) self.assertEqual(r.channel_, 5) self.assertEqual(r.note, 69) self.assertEqual(r.data1, 69) self.assertEqual(r.velocity, 42) self.assertEqual(r.data2, 42) with self.assertRaises(AttributeError): r.ctrl with self.assertRaises(AttributeError): r.value with self.assertRaises(AttributeError): r.program with self.assertRaises(AttributeError): r.sysex @data_offsets def test_NoteOffEvent(self, off): ev = NoteOffEvent(off(1), off(7), 60, 127) def foo(ev): self.assertEqual(ev.type, NOTEOFF) self.assertEqual(ev.port, off(1)) self.assertEqual(ev.port_, 1) self.assertEqual(ev.channel, off(7)) self.assertEqual(ev.channel_, 7) self.assertEqual(ev.note, 60) self.assertEqual(ev.data1, 60) self.assertEqual(ev.velocity, 127) self.assertEqual(ev.data2, 127) ev.port = off(3) ev.channel = off(5) ev.note = 69 ev.velocity = 42 return ev (r,) = self.run_patch(Process(foo), ev) self.assertEqual(r.type, NOTEOFF) self.assertEqual(r.port, off(3)) self.assertEqual(r.port_, 3) self.assertEqual(r.channel, off(5)) self.assertEqual(r.channel_, 5) self.assertEqual(r.note, 69) self.assertEqual(r.data1, 69) self.assertEqual(r.velocity, 42) self.assertEqual(r.data2, 42) with self.assertRaises(AttributeError): r.ctrl with self.assertRaises(AttributeError): r.value with self.assertRaises(AttributeError): r.program with self.assertRaises(AttributeError): r.sysex @data_offsets def test_CtrlEvent(self, off): ev = CtrlEvent(off(1), off(7), 60, 127) def foo(ev): self.assertEqual(ev.type, CTRL) self.assertEqual(ev.port, off(1)) self.assertEqual(ev.port_, 1) self.assertEqual(ev.channel, off(7)) self.assertEqual(ev.channel_, 7) self.assertEqual(ev.ctrl, 60) self.assertEqual(ev.data1, 60) self.assertEqual(ev.value, 127) self.assertEqual(ev.data2, 127) ev.port = off(3) ev.channel = off(5) ev.ctrl = 69 ev.value = 42 return ev (r,) = self.run_patch(Process(foo), ev) self.assertEqual(r.type, CTRL) self.assertEqual(r.port, off(3)) self.assertEqual(r.port_, 3) self.assertEqual(r.channel, off(5)) self.assertEqual(r.channel_, 5) self.assertEqual(r.ctrl, 69) self.assertEqual(r.data1, 69) self.assertEqual(r.value, 42) self.assertEqual(r.data2, 42) with self.assertRaises(AttributeError): r.note with self.assertRaises(AttributeError): r.velocity with self.assertRaises(AttributeError): r.program with self.assertRaises(AttributeError): r.sysex @data_offsets def test_ProgramEvent(self, off): ev = ProgramEvent(off(1), off(7), off(42)) def foo(ev): self.assertEqual(ev.type, PROGRAM) self.assertEqual(ev.port, off(1)) self.assertEqual(ev.port_, 1) self.assertEqual(ev.channel, off(7)) self.assertEqual(ev.channel_, 7) self.assertEqual(ev.program, off(42)) self.assertEqual(ev.data2, 42) ev.port = off(3) ev.channel = off(5) ev.program = off(66) return ev (r,) = self.run_patch(Process(foo), ev) self.assertEqual(r.type, PROGRAM) self.assertEqual(r.port, off(3)) self.assertEqual(r.port_, 3) self.assertEqual(r.channel, off(5)) self.assertEqual(r.channel_, 5) self.assertEqual(r.program, off(66)) self.assertEqual(r.data2, 66) with self.assertRaises(AttributeError): r.note with self.assertRaises(AttributeError): r.velocity with self.assertRaises(AttributeError): r.ctrl with self.assertRaises(AttributeError): r.value with self.assertRaises(AttributeError): r.sysex @data_offsets def test_SysExEvent(self, off): sysex1 = '\xf0\x04\x08\x15\x16\x23\x42\xf7' sysex2 = '\xf0\x09\x11\x02\x74\x5b\x41\x56\x63\x56\xf7' ev = SysExEvent(off(1), sysex1) def foo(ev): self.assertEqual(ev.type, SYSEX) self.assertEqual(ev.port, off(1)) self.assertEqual(ev.sysex, self.native_sysex(sysex1)) ev.port = off(3) ev.sysex = sysex2 return ev (r,) = self.run_patch(Process(foo), ev) self.assertEqual(r.type, SYSEX) self.assertEqual(r.port, off(3)) self.assertEqual(r.port_, 3) self.assertEqual(r.sysex, self.native_sysex(sysex2)) with self.assertRaises(AttributeError): ev.note with self.assertRaises(AttributeError): ev.velocity with self.assertRaises(AttributeError): ev.ctrl with self.assertRaises(AttributeError): ev.value with self.assertRaises(AttributeError): ev.program @data_offsets def test_SysExEvent_modify(self, off): sysex1 = '\xf0\x04\x08\x15\x16\x23\x42\xf7' sysex2 = '\xf0\x05\x08\x15\x16\x23\x42\xf7' ev = SysExEvent(off(1), sysex1) def foo(ev): ev.sysex[1] = 0x05 return ev (r,) = self.run_patch(Process(foo), ev) self.assertEqual(r.sysex, self.native_sysex(sysex2)) @data_offsets def test_operator_equals(self, off): for t in constants._EVENT_TYPES.values(): a = self.make_event(type=t, port=off(0)) b = self.make_event(type=t, port=off(1)) c = self.make_event(type=a.type, port=a.port, channel=a.channel, data1=a.data1, data2=a.data2, sysex=a.sysex_) self.assertNotEqual(a, b) self.assertFalse(a == b) self.assertTrue(a != b) self.assertEqual(a, c) self.assertTrue(a == c) self.assertFalse(a != c) @data_offsets def test_to_string(self, off): for t in constants._EVENT_TYPES.values(): ev = self.make_event(t) self.assertTrue(isinstance(ev.to_string(), str)) self.assertTrue(isinstance(ev.to_string(['foo', 'bar'], 3, 80), str)) @data_offsets def test_rebuild_repr(self, off): for t in constants._EVENT_TYPES.values(): ev = self.make_event(t) rebuilt = eval(repr(ev), self.mididings_dict) self.assertEqual(rebuilt, ev) self.assertEqual(repr(ev), repr(rebuilt)) @data_offsets def test_copy(self, off): a = self.make_event() b = copy.copy(a) self.assertEqual(b, a) c = SysExEvent(off(23), '\xf0\x04\x08\x15\x16\x23\x42\xf7') d = copy.copy(c) self.assertEqual(d, c) @data_offsets def test_pickle(self, off): a = self.make_event() b = pickle.loads(pickle.dumps(a)) self.assertEqual(b, a) c = SysExEvent(off(23), '\xf0\x04\x08\x15\x16\x23\x42\xf7') d = pickle.loads(pickle.dumps(c)) self.assertEqual(d, c) mididings-20120419~ds0/tests/test_util.py0000644000175000017500000001115211737711704020152 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from __future__ import with_statement from tests.helpers import * from mididings import * from mididings.util import * class UtilTestCase(MididingsTestCase): def test_note_number(self): config(octave_offset=1) self.assertEqual(note_number(42), 42) self.assertEqual(note_number('c4'), 60) self.assertEqual(note_number('c#4'), 61) self.assertEqual(note_number('db4'), 61) self.assertEqual(note_number('a0'), 21) with self.assertRaises(ValueError): note_number(-23) with self.assertRaises(TypeError): note_number(123.456) with self.assertRaises(ValueError): note_number('a23') with self.assertRaises(ValueError): note_number('h3') config(octave_offset=2) self.assertEqual(note_number(42), 42) self.assertEqual(note_number('c3'), 60) self.assertEqual(note_number('c#3'), 61) self.assertEqual(note_number('db3'), 61) self.assertEqual(note_number('a-1'), 21) def test_note_range(self): self.assertEqual(note_range(23), (23, 24)) self.assertEqual(note_range((23, 42)), (23, 42)) # self.assertEqual(note_range('c3:c5'), (48, 72)) # self.assertEqual(note_range(':c4'), (0, 60)) # self.assertEqual(note_range('c2:'), (36, 0)) self.assertEqual(note_range('c3:c5'), (60, 84)) self.assertEqual(note_range(':c4'), (0, 72)) self.assertEqual(note_range('c2:'), (48, 0)) with self.assertRaises(ValueError): note_range('blah') with self.assertRaises(ValueError): note_range('x3:y5') with self.assertRaises(ValueError): note_range((23,)) with self.assertRaises(ValueError): note_range(('c4',)) def test_note_name(self): config(octave_offset=1) self.assertEqual(note_name(60), 'c4') self.assertEqual(note_name(61), 'c#4') self.assertEqual(note_name(0), 'c-1') self.assertEqual(note_name(-23), 'c#-3') self.assertEqual(note_name(127), 'g9') with self.assertRaises(TypeError): note_name(123.456) def test_event_type(self): self.assertEqual(event_type(NOTEON), NOTEON) self.assertEqual(event_type(PROGRAM), PROGRAM) with self.assertRaises(ValueError): event_type(NOTE) with self.assertRaises(ValueError): event_type(123) @data_offsets def test_port_number(self, off): config( in_ports=['foo', 'bar'], out_ports=['foo', 'blah', 'bar'] ) self.assertEqual(port_number(1), 1) self.assertEqual(port_number('foo'), off(0)) self.assertEqual(port_number('blah'), off(1)) with self.assertRaises(ValueError): port_number('bar') with self.assertRaises(TypeError): port_number(123.456) def test_sysex_data(self): self.assertEqual(sysex_data('\xf0\x04\x08\x15\x16\x23\x42\xf7'), self.native_sysex('\xf0\x04\x08\x15\x16\x23\x42\xf7')) self.assertEqual(sysex_data([0xf0, 0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0xf7]), self.native_sysex('\xf0\x04\x08\x15\x16\x23\x42\xf7')) self.assertEqual(sysex_data('\xf0\x23\x42\x66', allow_partial=True), self.native_sysex('\xf0\x23\x42\x66')) with self.assertRaises(ValueError): sysex_data('\xf0') with self.assertRaises(ValueError): sysex_data('\xfa\x23\x42\xf7') with self.assertRaises(ValueError): sysex_data('\xf0\x23\x42\xfa') with self.assertRaises(ValueError): sysex_data('\xf0\xff\xff\xfa') def test_sysex_manufacturer(self): self.assertEqual(sysex_manufacturer('\x42'), self.native_sysex('\x42')) self.assertEqual(sysex_manufacturer(0x42), self.native_sysex('\x42')) self.assertEqual(sysex_manufacturer('\x00\x42\x23'), self.native_sysex('\x00\x42\x23')) self.assertEqual(sysex_manufacturer([0x00, 0x42, 0x23]), self.native_sysex('\x00\x42\x23')) with self.assertRaises(ValueError): sysex_manufacturer('\x42\x23') with self.assertRaises(ValueError): sysex_manufacturer('\x42\x00\x23') with self.assertRaises(ValueError): sysex_manufacturer('\x00\x23\xff') mididings-20120419~ds0/tests/test_setup.py0000644000175000017500000000554311744005327020337 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from __future__ import with_statement from tests.helpers import * from mididings import * class SetupTestCase(MididingsTestCase): def test_config_backend(self): config(backend='alsa') config(backend='jack') config(backend='jack-rt') with self.assertRaises(ValueError): config(backend='unknown') with self.assertRaises(ValueError): config(backend=666) def test_config_client_name(self): config(client_name='foo') with self.assertRaises(TypeError): config(client_name=666) def test_config_in_ports(self): config(in_ports=23) config(in_ports=['foo', 'bar']) config(in_ports=['foo', ['bar', 'blah']]) config(in_ports=['foo', ('bar', 'blah', 'blubb')]) with self.assertRaises(TypeError): config(in_ports='foo') with self.assertRaises(TypeError): config(in_ports=[23, 42]) # with self.assertRaises(ValueError): with self.assertRaises(TypeError): config(in_ports=-1) def test_config_out_ports(self): config(out_ports=23) config(out_ports=['foo', 'bar']) config(out_ports=['foo', ['bar', 'blah']]) config(out_ports=['foo', ('bar', 'blah', 'blubb')]) with self.assertRaises(TypeError): config(out_ports='foo') with self.assertRaises(TypeError): config(out_ports=[23, 42]) # with self.assertRaises(ValueError): with self.assertRaises(TypeError): config(out_ports=-1) def test_config_data_offset(self): config(data_offset=0) config(data_offset=1) with self.assertRaises(ValueError): config(data_offset=-1) with self.assertRaises(ValueError): config(data_offset=2) def test_config_octave_offset(self): for x in range(-3, 5): config(octave_offset=x) with self.assertRaises(TypeError): config(octave_offset=1.5) def test_config_initial_scene(self): config(initial_scene=4) config(initial_scene=(8,)) config(initial_scene=(15,16)) def test_config_start_delay(self): config(start_delay=None) config(start_delay=0) config(start_delay=1.23) @data_offsets def test_named_ports(self, off): config(out_ports = ['foo', 'bar', 'baz']) ev = self.make_event(port=off(0)) self.check_patch(Port('bar'), { ev : [self.modify_event(ev, port=off(1))], }) mididings-20120419~ds0/tests/helpers.py0000644000175000017500000001605111742030415017570 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from __future__ import with_statement try: import unittest2 as unittest except: import unittest import random import itertools import sys import copy from mididings import * from mididings import setup, engine, misc, constants from mididings.event import * import mididings import mididings.event def data_offsets(f): """ Runs the test f twice, with data offsets 0 and 1 """ def data_offset_wrapper(self): for offset in (0, 1): def off(n): return n + offset config(data_offset = offset) f(self, off) return data_offset_wrapper class MididingsTestCase(unittest.TestCase): def setUp(self): setup.reset() setup.config(data_offset = 0) self.mididings_dict = mididings.__dict__.copy() self.mididings_dict.update(mididings.event.__dict__) def check_patch(self, patch, d): """ Test the given patch. d must be a mapping from events to the expected list of resulting events. """ self.check_scenes({ setup.get_config('data_offset'): patch }, d) def check_scenes(self, scenes, d): """ Test the given scenes. d must be a mapping from events to the expected list of resulting events. """ for ev, expected in d.items(): r = self.run_scenes(scenes, ev) if isinstance(expected, bool): # boolean value: ensure that at most one event was returned self.assertLessEqual(len(r), 1) # ensure that event is unchanged if len(r): self.assertEqual(r[0], ev) # check if the result is as expected self.assertEqual(bool(len(r)), expected, "\nscenes=%s\nev=%s\nexpected=%s" % (repr(scenes), repr(ev), repr(expected))) else: # list: check if the result is exactly as expected self.assertEqual(r, expected, "\nscenes=%s\nev=%s\nr=%s\nexpected=%s" % (repr(scenes), repr(ev), repr(r), repr(expected))) def check_filter(self, filt, d): """ Test if the filter filt works as expected. d must be a mapping from events to a tuple of two booleans, where the first value specifies if the event should match the filter as is, and the second value specifies if the event should match the inverted filter. """ for ev, expected in d.items(): for f, p in zip((filt, ~filt, -filt), [expected[0], expected[1], not expected[0]]): self.check_patch(f, {ev: p}) def run_patch(self, patch, events): """ Run the given events through the given patch, return the list of resulting events. """ return self.run_scenes({ setup.get_config('data_offset'): patch }, events) def run_scenes(self, scenes, events): """ Run the given events through the given scenes, return the list of resulting events. """ # run scenes r1 = self._run_scenes(scenes, events) # check if events can be rebuilt from their repr() for ev in r1: rebuilt = eval(repr(ev), self.mididings_dict) self.assertEqual(rebuilt, ev) rebuilt = self._rebuild_repr(scenes) if rebuilt is not None: # run scenes rebuilt from their repr(), result should be identical r2 = self._run_scenes(rebuilt, events) self.assertEqual(r2, r1) return r1 def _rebuild_repr(self, scenes): r = {} for k, v in scenes.items(): rep = repr(v) if 'Process' in rep: # patches with Process() units are too tricky for now return None w = eval(rep, self.mididings_dict) # the repr() of the rebuilt patch should be identical to the repr() # string it was built from self.assertEqual(repr(w), rep) r[k] = w return r def _run_scenes(self, scenes, events): setup._config_impl( backend='dummy' ) e = engine.Engine() e.setup(scenes, None, None, None) r = [] if not misc.issequence(events): events = [events] for ev in events: r += e.process_event(ev)[:] for ev in r: ev.__class__ = MidiEvent return r def make_event(self, *args, **kwargs): """ Create a new MIDI event. Attributes can be specified in args or kwargs, unspecified attributes are filled with random values. """ type, port, channel, data1, data2, sysex = itertools.islice(itertools.chain(args, itertools.repeat(None)), 6) for k, v in kwargs.items(): if k == 'type': type = v if k == 'port': port = v elif k == 'channel': channel = v elif k in ('data1', 'note', 'ctrl'): data1 = v elif k in ('data2', 'velocity', 'value'): data2 = v elif k == 'program': data2 = v - setup.get_config('data_offset') elif k == 'sysex': sysex = v if type is None: if channel is None: type = random.choice(list(set(constants._EVENT_TYPES.values()) - set([DUMMY]))) else: type = random.choice([NOTEON, NOTEOFF, CTRL, PITCHBEND, AFTERTOUCH, POLY_AFTERTOUCH, PROGRAM]) elif type == NOTE: type = random.choice([NOTEON, NOTEOFF]) elif misc.issequence(type): type = random.choice(type) if port is None: port = random.randrange(0, 42) + setup.get_config('data_offset') if channel is None: channel = random.randrange(0, 16) + setup.get_config('data_offset') if data1 is None: data1 = random.randrange(0, 128) if data2 is None: data2 = (random.randrange(1, 128) if type == NOTEON else 0 if type == NOTEOFF else random.randrange(0, 128)) if type == SYSEX and sysex is None: sysex = [0xf0] + [random.randrange(0, 128) for n in range(random.randrange(1024))] + [0xf7] return MidiEvent(type, port, channel, data1, data2, sysex) def modify_event(self, ev, **kwargs): """ Make a copy of the event ev, replacing arbitrary attributes with the values given in kwargs. """ r = copy.copy(ev) for k, v in kwargs.items(): setattr(r, k, v) return r def native_sysex(self, sysex): if isinstance(sysex, str): sysex = map(ord, sysex) if sys.version_info >= (2, 6): return bytearray(sysex) else: return list(sysex) mididings-20120419~ds0/tests/test_overload.py0000644000175000017500000001126611737711704021016 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from __future__ import with_statement try: import unittest2 as unittest except: import unittest from mididings import overload class OverloadTestCase(unittest.TestCase): def setUp(self): # yuck! self.registry_bak = overload._registry.copy() def tearDown(self): # yuck! overload._registry = self.registry_bak def test_call(self): funcs = [ lambda a: 1, lambda a, b: 2, ] self.assertEqual(overload.call([23], {}, funcs), 1) self.assertEqual(overload.call([], {'a': 23}, funcs), 1) self.assertEqual(overload.call([23, 42], {}, funcs), 2) self.assertEqual(overload.call([23], {'b': 42}, funcs), 2) self.assertEqual(overload.call([], {'a': 23, 'b': 42}, funcs), 2) with self.assertRaises(TypeError): overload.call([], {}, funcs) with self.assertRaises(TypeError): overload.call([23, 42, 69], {}, funcs) with self.assertRaises(TypeError): overload.call([23, 42], {'c': 69}, funcs) def test_mark(self): @overload.mark def foo(a): return 1 @overload.mark def foo(a, b): return 2 self.assertEqual(foo(23), 1) self.assertEqual(foo(a=23), 1) self.assertEqual(foo(23, 42), 2) self.assertEqual(foo(a=23, b=42), 2) with self.assertRaises(TypeError): foo() with self.assertRaises(TypeError): foo(23, 42, 69) with self.assertRaises(TypeError): foo(23, 42, c=69) def test_argnames(self): @overload.mark def foo(a, b): return 1 @overload.mark def foo(c, d): return 2 self.assertEqual(foo(23, 42), 1) self.assertEqual(foo(23, b=42), 1) self.assertEqual(foo(a=23, b=42), 1) self.assertEqual(foo(23, d=42), 2) self.assertEqual(foo(c=23, d=42), 2) with self.assertRaises(TypeError): foo(23, e=42) def test_defargs(self): @overload.mark def foo(a=23): return 1 @overload.mark def foo(a, b=42): return 2 @overload.mark def foo(a, b, c=69): return 3 @overload.mark def foo(a, b=42, d=123): return 4 self.assertEqual(foo(), 1) self.assertEqual(foo(23), 1) self.assertEqual(foo(a=23), 1) self.assertEqual(foo(23, 42), 2) self.assertEqual(foo(a=23, b=42), 2) self.assertEqual(foo(23, 42, 69), 3) self.assertEqual(foo(23, 42, c=69), 3) self.assertEqual(foo(a=23, b=42, c=69), 3) self.assertEqual(foo(23, 42, d=123), 4) self.assertEqual(foo(a=23, b=42, d=123), 4) self.assertEqual(foo(a=23, d=123), 4) with self.assertRaises(TypeError): foo(23, c=69) def test_varargs(self): @overload.mark def foo(a): return 1 @overload.mark def foo(b, c, d=69): return 2 @overload.mark def foo(d, e=42, *args): return 3 self.assertEqual(foo(23), 1) self.assertEqual(foo(23, 42), 2) self.assertEqual(foo(23, 42, 69), 2) self.assertEqual(foo(23, 42, d=69), 2) self.assertEqual(foo(23, e=42), 3) self.assertEqual(foo(d=23, e=42), 3) self.assertEqual(foo(23, 42, 69, 123), 3) with self.assertRaises(TypeError): foo(e=23) def test_partial(self): @overload.partial((23,)) def foo(a, b): return a, b self.assertEqual(foo(42), (23, 42)) self.assertEqual(foo(23, 42), (23, 42)) self.assertEqual(foo(b=42), (23, 42)) self.assertEqual(foo(a=23, b=42), (23, 42)) with self.assertRaises(TypeError): foo() with self.assertRaises(TypeError): foo(23, 42, 69) @overload.partial((23,)) def bar(a, b, c=123): return a, b, c self.assertEqual(bar(42), (23, 42, 123)) self.assertEqual(bar(23, 42), (23, 42, 123)) self.assertEqual(bar(42, c=123), (23, 42, 123)) self.assertEqual(bar(23, 42, 123), (23, 42, 123)) with self.assertRaises(TypeError): bar() with self.assertRaises(TypeError): bar(23, 42, 69, 123) mididings-20120419~ds0/tests/test_patch.py0000644000175000017500000000224711733510374020275 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from tests.helpers import * from mididings import * class PatchTestCase(MididingsTestCase): def test_unit_order(self): def me_impl(ev, n): order.append(n) return ev def me(n): return Process(lambda ev: me_impl(ev, n)) order = [] p = ([ me(1), me(2) ] >> me(3) >> [ me(4), me(5) >> [ me(6), me(7) ] >> me(8) ] >> me(9)) self.run_patch(p, self.make_event()) self.assertEqual(order, [1, 2, 3, 4, 5, 6, 7, 8, 9]) order = [] p = (Fork([ me(1), me(2) ], False) >> me(3) >> Fork([ me(4), me(5) >> Fork([ me(6), me(7) ], False) >> me(8) ], False) >> me(9)) self.run_patch(p, self.make_event()) self.assertEqual(order, [1, 2, 3, 3, 4, 5, 6, 7, 8, 8, 4, 5, 6, 7, 8, 8, 9, 9, 9, 9, 9, 9]) mididings-20120419~ds0/tests/__init__.py0000644000175000017500000000000011525543030017651 0ustar alessioalessiomididings-20120419~ds0/tests/test_arguments.py0000644000175000017500000002311011740260070021164 0ustar alessioalessio# -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # from __future__ import with_statement try: import unittest2 as unittest except: import unittest from mididings import arguments from mididings import misc class ArgumentsTestCase(unittest.TestCase): def test_simple(self): @arguments.accept(int) def foo(a): pass foo(123) with self.assertRaises(TypeError): foo() with self.assertRaises(TypeError): foo(123.456) @arguments.accept(str, float) def bar(a, b): pass bar('blah', 123.456) with self.assertRaises(TypeError): bar() with self.assertRaises(TypeError): bar('blah') with self.assertRaises(TypeError): bar('blah', 123) def test_isinstance(self): @arguments.accept((int, str)) def foo(a): pass foo(123) foo('blah') with self.assertRaises(TypeError): foo() with self.assertRaises(TypeError): foo(123.456) def test_value(self): @arguments.accept((True, 42)) def foo(a): pass foo(True) foo(42) with self.assertRaises(TypeError): foo() with self.assertRaises(ValueError): foo(False) with self.assertRaises(ValueError): foo(23) with self.assertRaises(ValueError): foo('blah') def test_varargs(self): @arguments.accept(int, int) def foo(a, *args): pass foo(123) foo(123, 456, 789) with self.assertRaises(TypeError): foo() with self.assertRaises(TypeError): foo(123, 456, 'blah') def test_kwargs(self): @arguments.accept(kwargs={'a': int, 'b': str}) def foo(**kwargs): pass foo() foo(a=123) foo(b='blah') foo(a=123, b='blah') with self.assertRaises(TypeError): foo(a='blah') with self.assertRaises(TypeError): foo(b=123) with self.assertRaises(TypeError): foo(c=123) @arguments.accept(int, kwargs={'b': str, 'c': float}) def bar(a, **kwargs): self.assertEqual(a, 123) self.assertDictEqual(kwargs, {'b': 'blah', 'c': 123.456}) bar(123, b='blah', c=123.456) bar(a=123, b='blah', c=123.456) with self.assertRaises(TypeError): bar(123, b=456, c=789) with self.assertRaises(TypeError): bar(123, b=456, c=789, d=123) def test_nullable(self): @arguments.accept(arguments.nullable(int)) def foo(a): pass foo(None) foo(42) with self.assertRaises(TypeError): foo() with self.assertRaises(TypeError): foo(123.456) def test_sequenceof(self): @arguments.accept(arguments.sequenceof(int)) def foo(a): self.assertTrue(misc.issequenceof(a, int)) foo([]) foo([123, 456, 789]) with self.assertRaises(TypeError): foo() with self.assertRaises(TypeError): foo(123, 456, 789) with self.assertRaises(TypeError): foo([123, 456, 'blah']) @arguments.accept([str]) def bar(a): self.assertTrue(misc.issequenceof(a, str)) bar([]) bar(['doo', 'bee', 'doo']) with self.assertRaises(TypeError): bar() with self.assertRaises(TypeError): bar('doo', 'bee', 'doo') with self.assertRaises(TypeError): bar('doo', 'bee', 123) def test_tupleof(self): @arguments.accept(arguments.tupleof(int, str)) def foo(a): pass foo((123, 'blah')) foo([123, 'blah']) with self.assertRaises(TypeError): foo() with self.assertRaises(TypeError): foo(123, 'blah') with self.assertRaises(TypeError): foo((123, 456)) with self.assertRaises(ValueError): foo([123, 'blah', 789]) @arguments.accept([int, float]) def bar(a): pass bar((123, 456.789)) bar([123, 456.789]) with self.assertRaises(TypeError): bar() with self.assertRaises(TypeError): bar(123, 456.789) with self.assertRaises(TypeError): bar((123, 'blah')) with self.assertRaises(ValueError): bar([123, 456.789, 'blah']) def test_mappingof(self): @arguments.accept(arguments.mappingof(int, float)) def foo(a): for k, v in a.items(): self.assertIsInstance(k, int) self.assertIsInstance(v, float) foo({}) foo({23: 12.34, 42: 56.78}) with self.assertRaises(TypeError): foo() with self.assertRaises(TypeError): foo({23: 12.34, 42: 666}) with self.assertRaises(TypeError): foo({23: 12.34, 123.456: 56.78}) @arguments.accept({str: int}) def bar(a): for k, v in a.items(): self.assertIsInstance(k, str) self.assertIsInstance(v, int) bar({}) bar({'foo': 23, 'bar': 42}) with self.assertRaises(TypeError): bar() with self.assertRaises(TypeError): bar({'foo': 23, 'bar': 123.456}) with self.assertRaises(TypeError): bar({'foo': 23, 666: 42}) def test_with_rest(self): @arguments.accept(arguments.sequenceof(int), with_rest=True) def foo(a, *rest): self.assertTrue(misc.issequenceof(a, int)) self.assertTupleEqual(rest, ()) foo(123) foo(123, 456, 789) with self.assertRaises(TypeError): foo() with self.assertRaises(TypeError): foo(123, 456, 'blah') with self.assertRaises(TypeError): foo(123, [456, 789]) @arguments.accept(int, [int], with_rest=True) def bar(a, b, *rest): self.assertEqual(a, 123) self.assertTupleEqual(b, (456, 789)) bar(123, 456, 789) def test_flatten(self): @arguments.accept(arguments.flatten(int)) def foo(a): self.assertListEqual(a, [123, 456, 789]) foo([123, 456, 789]) foo([123, [456, 789]]) foo([123, [[456], 789]]) foo([[[123, [[[[456]]], 789]]]]) with self.assertRaises(TypeError): foo([123, ['blah', 789]]) def test_each(self): @arguments.accept(arguments.each(int, arguments.condition(lambda x: x > 0))) def foo(a): pass foo(1) foo(123) with self.assertRaises(ValueError): foo(0) with self.assertRaises(ValueError): foo(-456) def test_either(self): @arguments.accept(arguments.either(int, str)) def foo(a): pass foo(123) foo('blah') with self.assertRaises(TypeError): foo() with self.assertRaises(TypeError): foo(123.456) @arguments.accept(arguments.either(int, arguments.sequenceof(str))) def bar(a): pass bar(123) bar(['doo', 'bee', 'doo']) with self.assertRaises(TypeError): bar('blah') with self.assertRaises(TypeError): bar([123, 456, 789]) def test_condition(self): @arguments.accept(arguments.condition(lambda x: x%2 == 0)) def foo(a): pass foo(2) foo(42) with self.assertRaises(ValueError): foo(1) with self.assertRaises(ValueError): foo(23) @arguments.accept(arguments.condition(lambda x: x > 0)) def bar(a): pass bar(1) bar(42) with self.assertRaises(ValueError): bar(0) with self.assertRaises(ValueError): bar(-23) def test_repr(self): self.assertEqual(repr(arguments._make_constraint(int)), 'int') self.assertEqual(repr(arguments._make_constraint(arguments.nullable(int))), 'nullable(int)') self.assertEqual(repr(arguments._make_constraint([int])), '[int]') self.assertEqual(repr(arguments._make_constraint((23, 42))), '(23, 42)') self.assertEqual(repr(arguments._make_constraint((int, float, str))), '(int, float, str)') self.assertEqual(repr(arguments._make_constraint([int, float, str])), '[int, float, str]') self.assertEqual(repr(arguments._make_constraint({int: str})), '{int: str}') self.assertEqual(repr(arguments._make_constraint(arguments.flatten(int))), 'flatten(int)') self.assertEqual(repr(arguments._make_constraint(arguments.each(int, float))), 'each(int, float)') self.assertEqual(repr(arguments._make_constraint(arguments.either(int, str))), 'either(int, str)') self.assertEqual(repr(arguments._make_constraint(lambda x: x / 2)), 'lambda x: x / 2') self.assertEqual(repr(arguments._make_constraint(arguments.condition(lambda x: x < 3))), 'condition(lambda x: x < 3)') def foo(x): x constraint = arguments.either( arguments.each(int, arguments.condition(lambda x: x%2 == 0)), arguments.sequenceof(foo), str, ) reprs = 'either(each(int, condition(lambda x: x%2 == 0)), [foo], str)' self.assertEqual(repr(arguments._make_constraint(constraint)), reprs) mididings-20120419~ds0/scripts/0000755000175000017500000000000011743652020016101 5ustar alessioalessiomididings-20120419~ds0/scripts/mididings0000755000175000017500000001006711743652020020002 0ustar alessioalessio#!/usr/bin/env python # -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import sys import os import optparse import mididings import mididings.extra import mididings.setup class Dings(object): def __init__(self, options): if options.backend: self.config(backend=options.backend) if options.client_name: self.config(client_name=options.client_name) if options.start_delay: self.config(start_delay=options.start_delay) if options.in_ports or options.in_connections: in_ports = self.make_port_definitions(options.in_connections, options.in_ports) self.config(in_ports=in_ports) if options.out_ports or options.out_connections: out_ports = self.make_port_definitions(options.out_connections, options.out_ports) self.config(out_ports=out_ports) self.dings_dict = mididings.__dict__.copy() self.dings_dict.update(mididings.extra.__dict__) def config(self, **kwargs): mididings.setup._config_impl(override=True, **kwargs) def make_port_definitions(self, connections, nports): if connections is None: return nports elif nports < len(connections): connections = connections[:nports] elif nports > len(connections): connections += [None] * (nports - len(connections)) return [(None, c) for c in connections] def run_file(self, filename): # add filename's directory to sys.path to allow import from the same directory d = os.path.dirname(filename) if not d: d = '.' sys.path.insert(0, d) # just a kludge to make AutoRestart() work sys.modules['__mididings_main__'] = type('MididingsMain', (), {'__file__': os.path.abspath(filename)}) if sys.version_info >= (3,): exec(compile(open(filename).read(), filename, 'exec'), self.dings_dict) else: execfile(filename, self.dings_dict) # avoid memory leaks self.dings_dict.clear() def run_patch(self, patch): mididings.run(eval(patch, self.dings_dict)) if __name__ == '__main__': usage = "Usage: mididings [options] \"patch\"\n" \ " mididings [options] -f filename.py" epilog = "The -I and -O options may be specified multiple times, once for each port.\n\n" \ "The -f option executes a regular mididings Python script. Command line arguments " \ "override config settings in that file." version = "mididings %s" % mididings.__version__ parser = optparse.OptionParser(usage=usage, epilog=epilog, version=version) parser.add_option('-b', dest='backend', help="name of backend to use") parser.add_option('-c', dest='client_name', help="ALSA or JACK client name") parser.add_option('-i', dest='in_ports', type=int, help="number of input ports") parser.add_option('-o', dest='out_ports', type=int, help="number of output ports") parser.add_option('-I', dest='in_connections', type=str, action='append', help='input port connection (regular expression)') parser.add_option('-O', dest='out_connections', type=str, action='append', help='output port connection (regular expression)') parser.add_option('-d', dest='start_delay', type=float, help="delay (in seconds) before starting MIDI processing") parser.add_option('-f', dest='filename', help="file name of script to run") options, args = parser.parse_args(sys.argv[1:]) if len(args) == 0 and not options.filename: parser.error("no patch and no filename specified") elif len(args) > 1: parser.error("more than one patch specified") app = Dings(options) if options.filename: app.run_file(options.filename) else: app.run_patch(args[0]) mididings-20120419~ds0/scripts/livedings0000755000175000017500000000552400007345256020022 0ustar alessioalessio#!/usr/bin/env python # -*- coding: utf-8 -*- # # mididings # # Copyright (C) 2008-2012 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import optparse import sys from mididings.live.livedings import LiveDings def fill_options(options, new_options): for k, v in new_options.items(): if getattr(options, k) == None: setattr(options, k, v) if __name__ == '__main__': usage = "Usage: livedings [options]" parser = optparse.OptionParser(usage=usage) parser.add_option('-p', dest='control_port', default=56418, help="OSC port mididings is listening on (56418)") parser.add_option('-l', dest='listen_port', default=56419, help="OSC port for notifications from mididings (56419)") parser.add_option('-T', dest='themed', default=False, action='store_true', help="enable custom theme and larger fonts") parser.add_option('-x', dest='width', type=int, default=None, help="width of window in pixels (640)") parser.add_option('-y', dest='height', type=int, default=None, help="height of window in pixels (400)") parser.add_option('-w', dest='list_width', type=int, default=None, help="width of scene list in pixels (240)") parser.add_option('-F', dest='font', default=None, help="display font (Sans 14 bold)") parser.add_option('-f', dest='list_font', default=None, help="scene list font (Sans 10)") parser.add_option('-c', dest='color', default=None, help="text color (gray50)") parser.add_option('-C', dest='color_highlight', default=None, help="highlight text color (black)") parser.add_option('-b', dest='color_background', default=None, help="background color") parser.add_option('-n', dest='name', default=None, help="name to be shown in window title") options, args = parser.parse_args(sys.argv[1:]) if not options.listen_port: parser.error("no OSC listen port specified") if not options.control_port: parser.error("no OSC control port specified") if options.themed: fill_options(options, { 'width': 1024, 'height': 640, 'list_width': 320, 'font': 'Sans 32 bold', 'list_font': 'Sans 16', 'color': 'green', 'color_highlight': 'white', 'color_background': 'black', }) else: fill_options(options, { 'width': 640, 'height': 400, 'list_width': 240, 'font': 'Sans 14 bold', 'list_font': 'Sans 10', 'color': 'gray50', 'color_highlight': 'black', 'color_background': None, }) app = LiveDings(options) app.run() mididings-20120419~ds0/NEWS0000644000175000017500000002731211744102536015121 0ustar alessioalessio2012-04-19 * Added the ability to automatically connect to other JACK or ALSA clients, using regex-based port name matching. * Most functions and units now check the types and values of their arguments, allowing for much more comprehensible error messages. * New dependency: The Python decorator module is now required to run mididings. * Much cleaner and more efficient conversion between Python and C++ sequence and mapping types, implemented entirely in C++. * SysEx data is now passed to Python functions as a mutable bytearray object (or as list in Python 2.5). * Many SysEx-related bugfixes: SysEx() and SysExEvent() now work correctly when data_offset is 1, SysExFilter(manufacturer) works again, and Print() no longer raises an exception when it encounters a SysEx event. * AutoRestart() is now a little smarter when deciding which files to watch for changes. * Fixed the process_file() function (which apparently no one used in years...) * Replaced a gcc-ism: mididings can now be built with clang++ (version 3.0 or later). 2012-03-12 * Added methods apply(), invert() and negate() to filter objects, and made AndSelector and OrSelector public classes. Anything that can be done with funny overloaded operators is now also possible with regular functions and objects. * Added option -n to livedings, based on a patch by Aurélien. * Added SysExEvent() function in mididings.event module. * Added Restart() and Quit() in mididings.extra module. * Fixed PitchbendEvent(), thanks to Димон. * Fixed selector operators & and | for more than two filters. * Print correct note names when using Python 3. * Fixed operator != for comparing MidiEvent objects. * Fixed compilation on OS X, automatically disable ALSA backend on non-Linux platforms. Thanks to Sílvio Almeida for lots of valuable feedback. * Extended and reorganized unit tests. * Some documentation cleanup. 2010-11-19 * Improved VoiceFilter() to make it much more usable for filtering voices other than the highest and lowest one, and added VoiceSplit() as a shortcut to route multiple voices through different patches. * Added engine.time() as a clock source that, unlike Python's time.time(), is guaranteed to be monotonic. * OSCInterface() and livedings no longer require port numbers to be specified, and simply default to 56418 and 56419. * Changed variable/parameter names referring to CC numbers from "param" to "ctrl", and removed the "get_" prefix from some function names in the engine module. In most places the old names still work to remain backward compatible. * Fixed build issues with certain Python and/or Boost versions. * Lots of cleanup and some minor fixes. 2010-06-02 * Fixed the utterly broken CtrlRange() unit. * Added new PitchbendRange() unit. * Fixed PedalToNoteoff() when using sostenuto (releasing the pedal caused note-offs to be sent for keys which were still pressed). * Fixed a small error in the livedings/OSCInterface documentation (thanks to Christopher Arndt). 2010-05-16 * Added CtrlCurve() unit and a shortcut version of Velocity() to specify multiply and offset at the same time. * Added generators for pitch bend and aftertouch events. * Fixed MidiEvent constructor to take data_offset setting into account. 2010-05-08 * Include 64 bit library directories in the search for the correct boost library names (bug reported by Niels Mayer). * Added new examples skeleton.py and router.py. * Documented the mididings.event and mididings.engine submodules. 2010-04-13 * Added support for Python 3.x (requires a recent Boost.Python built for Python 3, and most likely some manual fiddling with the setup.py script). * Fixed some compatibility issues with Python 2.5 (thanks to Moritz Firsching for the bug report). * Added support for SysEx messages larger than ALSA's buffer size. * Fixed a race condition that could lead to a crash when terminating mididings while an event is being processed. * Fixed the JACK backend so multiple input ports can be used. 2010-03-18 * Added VoiceFilter() to filter individual voices from a chord. * The Output() unit can now send arbitrary control changes, and a new class OutputTemplate was added to simplify creating partially parametrized outputs. * Added lots of new examples to the documentation. * Several minor fixes and cleanup. 2010-03-07 * Added livedings as a graphical frontend to mididings that allows monitoring and triggering scene changes. * Added SceneGroup() to make multiple subscenes accessible under a single program number. * Support floating split points using the new FloatingKeySplit() unit. * Added Panic() to send all-notes-off messages on all channels. * Several new OSC messages supported by mididings.extra.OSCInterface(). * New module mididings.extra.gm containing constants for program and controller numbers defined in the General MIDI standard. * Enable realtime scheduling for MIDI processing with the 'jack' backend. 2010-02-02 * Major documentation update, now including a tutorial and explaining some of the relevant Python basics. * Renamed a few units and parameters to keep names somewhat consistent. * Removed the types parameters from Fork() and Print(), the same result can be achieved using a selector. * The upper or lower limit for note ranges can now be omitted. * All splits now support an "else" rule. A patch with a key of None is used when none of the other filters match. * Added operator | (OR) for selectors. * Added LatchNotes() to hold notes until the key is pressed again. * Significant performance improvement when calling overloaded functions. 2010-01-12 * Included a command line program "mididings" that allows running simple patches without writing full-fledged Python scripts. For example: $ mididings "Transpose(12) >> Velocity(fixed=64)" * A first attempt to support system exclusive, system realtime and system common messages. New units include SysEx(), SysExFilter() and SysExSplit(). Thanks to Christopher Arndt for some very helpful suggestions. * Added support for polyphonic aftertouch, untested for obvious reasons :( * Implemented a hook system to easily extend some of mididings' functionality. * Added hook objects MemorizeScene() (scene persistence between restarts), OSCInterface() (scene switching via OSC), and AutoRestart() (automatic restart when the script changes. Best. Feature. Ever.) * Unified run() and run_scenes(), as well as Print() and PrintString(). The different functionality now depends only on keyword arguments. * Renamed Call() to Process(). Call() now offers the functionality of both CallAsync() and CallThread(). * Renamed GenerateEvent() to Generator() and InitAction() to Init(). * Changed semantics of CtrlFilter(), CtrlValueFilter(), ProgFilter() to block events of other types (again...). * Added parameter "curve" to Velocity(), applying an exponential curve. * New unit VelocityLimit(). * New operator & to use multiple filters as a selector with operator %. 2009-11-10 * New and clearer nomenclature to avoid different meanings of "patch": A "patch" is now always just a group of units, whereas you switch between "scenes" (which can in turn consist of a patch and optionally an init-patch). Function names changed accordingly, but for now the previous names still exist for backward compatibility. * Favor keyword arguments over different units with similar functionality, e.g. VelocityFixed(x) -> Velocity(fixed=x), etc. * Replaced VelocityGradient() with the new VelocitySlope(), which supports defining the velocity for an arbitrary number of notes. * PedalToNoteoff() can now act as either a sustain or a sostenuto pedal. 2009-10-03 * Made all key or value ranges half-open (excluding the upper limit) for consistency with Python. This affects KeyFilter(), VelocityFilter(), CtrlValueFilter() and the related *Split units. * New compiler options to significantly reduce the size of the resulting _mididings.so binary. * Several bugfixes. 2009-01-14 * Output() now supports sending volume changes (CC 7). 2009-01-13 * New units in mididings.extra: MakeMonophonic() and LimitPolyphony(). * Added event-type agnostic inversion operator - for filters. * VelocityFilter() can now be used with only an upper or lower limit when the other parameter is zero. 2008-11-23 * Added support for reading and writing standard MIDI files, based on libsmf (disabled by default). * Allow patch numbers greater than 128. * Output() now supports sending bank select messages. 2008-11-22 * Terminate threads properly on exit. * Aftertouch now actually works. * Added 3 argument version of CtrlValueSplit(). 2008-11-09 * Lots of changes and cleanup of the JACK backend, should now work properly with JACK 2. * Added support for channel aftertouch. * Implemented new operator % for filters. * Added parameters to run_patches() to specify the first patch to be selected, and a callback function to be called on every patch switch. * New function: mididings.extra.run_patches_memorize(). * New units: NoteOn(), NoteOff(), CtrlSplit(), CtrlValueSplit(), ProgSplit(). 2008-09-13 * Added System() to run shell commands, as well as SendOSC() and SendDBUS() to send OSC or DBUS messages. * Do a lot more validity checking of function arguments. 2008-09-09 * Fixed a bug that under certain circumstances caused events to not be processed properly. 2008-09-05 * Implemented experimental support for JACK MIDI. * Added mididings.extra module for units with more advanced/specific functionality than what's included in the main mididings module. This includes, among other things, a diatonic harmonizer. * Updated documentation, completely rewritten example scripts. 2008-08-28 * Allow Call() to return more than one MIDI event at the same time. 2008-08-17 * Complete rewrite of some crucial parts of the code. Lots of cleanup and restructuring. This should not affect existing scripts, except for the changes noted below. * Filters no longer remove events of different types. For example, what used to be CtrlFilter(num) should now be written as Filter(CTRL) >> CtrlFilter(num). * When units connected in parallel result in identical copies of the same event, duplicates will be removed. For example, [ Pass(), Transpose(12) ] no longer outputs two events when receiving program or control changes. * Require Boost ≥ 1.34.1. * Many minor fixes. 2008-07-21 * Added CallAsync() and CallThread(), to start Python functions without delaying event processing. * Made Print() prettier, more informative, and faster. * Added CtrlMap() and PrintString(). * ALSA event queue is now cleared before starting MIDI processing, to ignore events received during start_delay. 2008-07-13 * Added start_delay parameter to config(). * Added switch_patch() function. * Init events for the first patch loaded at startup are now sent properly. * Don't allow note on events with velocity < 1. This has the (side-) effect that Velocity() and VelocityGradient() will never completely remove any events. Use an additional VelocityFilter() if that's what you want. 2008-04-16 * Moved port parameters from run() to config(), allow ports to be specified by name rather than number. * Changed parameter order of CtrlRange(), making input range [0;127] the default. 2008-04-06 * Added new Sanitize() unit to make sure MIDI events are valid. The same is done automatically before sending events to the output ports. * Added some more documentation. mididings-20120419~ds0/src/0000755000175000017500000000000011742030415015176 5ustar alessioalessiomididings-20120419~ds0/src/units/0000755000175000017500000000000011737365124016354 5ustar alessioalessiomididings-20120419~ds0/src/units/util.hh0000644000175000017500000000565111737365124017661 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_UNITS_UTIL_HH #define MIDIDINGS_UNITS_UTIL_HH #include #include namespace Mididings { namespace Units { enum TransformMode { TRANSFORM_MODE_OFFSET = 1, TRANSFORM_MODE_MULTIPLY = 2, TRANSFORM_MODE_FIXED = 3, TRANSFORM_MODE_GAMMA = 4, TRANSFORM_MODE_CURVE = 5, }; enum EventAttribute { EVENT_ATTRIBUTE_PORT = -1, EVENT_ATTRIBUTE_CHANNEL = -2, EVENT_ATTRIBUTE_DATA1 = -3, EVENT_ATTRIBUTE_DATA2 = -4, EVENT_ATTRIBUTE_NOTE = -3, EVENT_ATTRIBUTE_VELOCITY = -4, EVENT_ATTRIBUTE_CTRL = -3, EVENT_ATTRIBUTE_VALUE = -4, EVENT_ATTRIBUTE_PROGRAM = -4, }; inline int apply_transform(int value, float param, TransformMode mode) { switch (mode) { case TRANSFORM_MODE_OFFSET: return value + (int)param; case TRANSFORM_MODE_MULTIPLY: return (int)(value * param); case TRANSFORM_MODE_FIXED: return (int)param; case TRANSFORM_MODE_GAMMA: if (value > 0) { float a = (float)value / 127.f; float b = ::powf(a, 1.f / param); return std::max(1, (int)::rintf(b * 127.f)); } else { return value; } case TRANSFORM_MODE_CURVE: if (value > 0) { if (param != 0) { float p = -param; float a = ::expf(p * value / 127.f) - 1; float b = ::expf(p) - 1; return std::max(1, (int)(127.f * a / b)); } else { return value; } } else { return 0; } default: return 0; } } /* * maps the input range [arg_lower ... arg_upper] to the * output range [val_lower ... val_upper] */ template V map_range(A arg, A arg_lower, A arg_upper, V val_lower, V val_upper) { if (arg <= arg_lower) { return val_lower; } else if (arg >= arg_upper) { return val_upper; } else { float dx = arg_upper - arg_lower; float dy = val_upper - val_lower; return static_cast((dy / dx) * (arg - arg_lower) + val_lower); } } inline int get_parameter(int value, MidiEvent const & ev) { if (value >= 0) { return value; } switch (value) { case EVENT_ATTRIBUTE_PORT: return ev.port; case EVENT_ATTRIBUTE_CHANNEL: return ev.channel; case EVENT_ATTRIBUTE_DATA1: return ev.data1; case EVENT_ATTRIBUTE_DATA2: return ev.data2; default: FAIL(); return 0; } } } // Units } // Mididings #endif // MIDIDINGS_UNITS_UTIL_HH mididings-20120419~ds0/src/units/call.hh0000644000175000017500000000224511736211527017607 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_UNITS_CALL_HH #define MIDIDINGS_UNITS_CALL_HH #include "units/base.hh" #include namespace Mididings { namespace Units { class Call : public UnitExImpl { public: Call(boost::python::object fun, bool async, bool cont) : _fun(fun) , _async(async) , _cont(cont) { } template typename B::Range process(B & buffer, typename B::Iterator it) const { PythonCaller & c = buffer.engine().python_caller(); if (_async) { return c.call_deferred(buffer, it, _fun, _cont); } else { return c.call_now(buffer, it, _fun); } } private: boost::python::object const _fun; bool const _async; bool const _cont; }; } // Units } // Mididings #endif // MIDIDINGS_UNITS_CALL_HH mididings-20120419~ds0/src/units/filters.hh0000644000175000017500000001022111737173447020346 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_UNITS_FILTERS_HH #define MIDIDINGS_UNITS_FILTERS_HH #include "units/base.hh" #include #include #include namespace Mididings { namespace Units { class PortFilter : public Filter { public: PortFilter(std::vector const & ports) : Filter() , _ports(ports) { } virtual bool process_filter(MidiEvent & ev) const { return (std::find(_ports.begin(), _ports.end(), ev.port) != _ports.end()); } private: std::vector const _ports; }; class ChannelFilter : public Filter { public: ChannelFilter(std::vector const & channels) : Filter(~(MIDI_EVENT_SYSTEM | MIDI_EVENT_DUMMY), false) , _channels(channels) { } virtual bool process_filter(MidiEvent & ev) const { return (std::find(_channels.begin(), _channels.end(), ev.channel) != _channels.end()); } private: std::vector const _channels; }; class KeyFilter : public Filter { public: KeyFilter(int lower, int upper, std::vector const & notes) : Filter(MIDI_EVENT_NOTE, true) , _lower(lower) , _upper(upper) , _notes(notes) { } virtual bool process_filter(MidiEvent & ev) const { if (_lower || _upper) { return ((ev.note.note >= _lower || _lower == 0) && (ev.note.note < _upper || _upper == 0)); } else { return (std::find(_notes.begin(), _notes.end(), ev.note.note) != _notes.end()); } } private: int const _lower; int const _upper; std::vector const _notes; }; class VelocityFilter : public Filter { public: VelocityFilter(int lower, int upper) : Filter(MIDI_EVENT_NOTEON, true) , _lower(lower) , _upper(upper) { } virtual bool process_filter(MidiEvent & ev) const { return ((ev.note.velocity >= _lower || _lower == 0) && (ev.note.velocity < _upper || _upper == 0)); } private: int const _lower; int const _upper; }; class CtrlFilter : public Filter { public: CtrlFilter(std::vector const & ctrls) : Filter(MIDI_EVENT_CTRL, false) , _ctrls(ctrls) { } virtual bool process_filter(MidiEvent & ev) const { return (std::find(_ctrls.begin(), _ctrls.end(), ev.ctrl.param) != _ctrls.end()); } private: std::vector const _ctrls; }; class CtrlValueFilter : public Filter { public: CtrlValueFilter(int lower, int upper) : Filter(MIDI_EVENT_CTRL, false) , _lower(lower) , _upper(upper) { } virtual bool process_filter(MidiEvent & ev) const { return ((ev.ctrl.value >= _lower || _lower == 0) && (ev.ctrl.value < _upper || _upper == 0)); } private: int const _lower; int const _upper; }; class ProgramFilter : public Filter { public: ProgramFilter(std::vector const & progs) : Filter(MIDI_EVENT_PROGRAM, false) , _progs(progs) { } virtual bool process_filter(MidiEvent & ev) const { return (std::find(_progs.begin(), _progs.end(), ev.ctrl.value) != _progs.end()); } private: std::vector const _progs; }; class SysExFilter : public Filter { public: SysExFilter(SysExDataConstPtr const & sysex, bool partial) : Filter(MIDI_EVENT_SYSEX, false) , _sysex(sysex) , _partial(partial) { } virtual bool process_filter(MidiEvent & ev) const { if (_partial) { return std::search(ev.sysex->begin(), ev.sysex->end(), _sysex->begin(), _sysex->end()) == ev.sysex->begin(); } else { return *ev.sysex == *_sysex; } } SysExDataConstPtr const _sysex; bool const _partial; }; } // Units } // Mididings #endif // MIDIDINGS_UNITS_FILTERS_HH mididings-20120419~ds0/src/units/generators.hh0000644000175000017500000000343511737173455021057 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_UNITS_GENERATORS_HH #define MIDIDINGS_UNITS_GENERATORS_HH #include "units/base.hh" #include "units/util.hh" namespace Mididings { namespace Units { class Generator : public Unit { public: Generator(MidiEventType type, int port, int channel, int data1, int data2) : _type(type) , _port(port) , _channel(channel) , _data1(data1) , _data2(data2) { } virtual bool process(MidiEvent & ev) const { MidiEvent ev_new(ev); ev_new.type = _type; ev_new.port = get_parameter(_port, ev); ev_new.channel = get_parameter(_channel, ev); ev_new.data1 = get_parameter(_data1, ev); ev_new.data2 = get_parameter(_data2, ev); ev = ev_new; return true; } private: MidiEventType const _type; int const _port; int const _channel; int const _data1; int const _data2; }; class SysExGenerator : public Unit { public: SysExGenerator(int port, SysExDataConstPtr const & sysex) : _port(port) , _sysex(sysex) { } virtual bool process(MidiEvent & ev) const { ev.type = MIDI_EVENT_SYSEX; ev.port = get_parameter(_port, ev); ev.channel = 0; ev.data1 = 0; ev.data2 = 0; ev.sysex = _sysex; return true; } private: int const _port; SysExDataConstPtr const _sysex; }; } // Units } // Mididings #endif // MIDIDINGS_UNITS_GENERATORS_HH mididings-20120419~ds0/src/units/base.hh0000644000175000017500000000704411736460664017620 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_UNITS_BASE_HH #define MIDIDINGS_UNITS_BASE_HH #include "midi_event.hh" #include "patch.hh" #include "units/util.hh" #include #include "util/counted_objects.hh" #include "util/debug.hh" namespace Mididings { namespace Units { class Unit : das::counted_objects { public: Unit() { } virtual ~Unit() { } virtual bool process(MidiEvent & ev) const = 0; }; class UnitEx : das::counted_objects { public: UnitEx() { } virtual ~UnitEx() { } virtual Patch::EventBufferRT::Range process(Patch::EventBufferRT & buffer, Patch::EventBufferRT::Iterator it) const = 0; virtual Patch::EventBuffer::Range process(Patch::EventBuffer & buffer, Patch::EventBuffer::Iterator it) const = 0; }; template class UnitExImpl : public UnitEx { public: virtual Patch::EventBufferRT::Range process(Patch::EventBufferRT & buffer, Patch::EventBufferRT::Iterator it) const { Derived const & d = *static_cast(this); return d.template process(buffer, it); } virtual Patch::EventBuffer::Range process(Patch::EventBuffer & buffer, Patch::EventBuffer::Iterator it) const { Derived const & d = *static_cast(this); return d.template process(buffer, it); } }; class Filter : public Unit { friend class InvertedFilter; public: Filter() : _types(MIDI_EVENT_ANY) , _pass_other(false) { } Filter(MidiEventType types, bool pass_other) : _types(types) , _pass_other(pass_other) { } protected: virtual bool process(MidiEvent & ev) const { if (ev.type & types()) { return process_filter(ev); } else { return pass_other(); } } virtual bool process_filter(MidiEvent & ev) const = 0; MidiEventType types() const { return _types; } bool pass_other() const { return _pass_other; } private: MidiEventType const _types; bool const _pass_other; }; class InvertedFilter : public Filter { public: InvertedFilter(boost::shared_ptr filter, bool negate) : Filter() , _filter(filter) , _negate(negate) { } virtual bool process_filter(MidiEvent & ev) const { if (_negate) { return !_filter->process(ev); } else { if (ev.type & _filter->types()) { return !_filter->process_filter(ev); } else { return _filter->pass_other(); } } } private: boost::shared_ptr const _filter; bool const _negate; }; class TypeFilter : public Filter { public: TypeFilter(MidiEventType types) : Filter() , _types(types) { } virtual bool process_filter(MidiEvent & ev) const { return (ev.type & _types); } MidiEventType const _types; }; class Pass : public Unit { public: Pass(bool pass) : _pass(pass) { } virtual bool process(MidiEvent & /*ev*/) const { return _pass; } private: bool const _pass; }; } // Units } // Mididings #endif // MIDIDINGS_UNITS_BASE_HH mididings-20120419~ds0/src/units/modifiers.hh0000644000175000017500000001204511737663222020660 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_UNITS_MODIFIERS_HH #define MIDIDINGS_UNITS_MODIFIERS_HH #include "units/base.hh" #include "units/util.hh" #include namespace Mididings { namespace Units { class Port : public Unit { public: Port(int port) : _port(port) { } virtual bool process(MidiEvent & ev) const { ev.port = _port; return true; } private: int const _port; }; class Channel : public Unit { public: Channel(int channel) : _channel(channel) { } virtual bool process(MidiEvent & ev) const { if (!(ev.type & (MIDI_EVENT_SYSTEM | MIDI_EVENT_DUMMY))) { ev.channel = _channel; } return true; } private: int const _channel; }; class Transpose : public Unit { public: Transpose(int offset) : _offset(offset) { } virtual bool process(MidiEvent & ev) const { if (ev.type & MIDI_EVENT_NOTE) { ev.note.note += _offset; } return true; } private: int const _offset; }; class Velocity : public Unit { public: Velocity(float param, TransformMode mode) : _param(param) , _mode(mode) { } virtual bool process(MidiEvent & ev) const { if (ev.type == MIDI_EVENT_NOTEON && ev.note.velocity > 0) { ev.note.velocity = apply_transform(ev.note.velocity, _param, _mode); } return true; } private: float const _param; TransformMode const _mode; }; class VelocitySlope : public Unit { public: VelocitySlope(std::vector notes, std::vector params, TransformMode mode) : _notes(notes) , _params(params) , _mode(mode) { ASSERT(notes.size() == params.size()); ASSERT(notes.size() > 1); for (unsigned int n = 0; n < notes.size() - 1; ++n) { ASSERT(notes[n] <= notes[n + 1]); } } virtual bool process(MidiEvent & ev) const { if (ev.type == MIDI_EVENT_NOTEON && ev.note.velocity > 0) { unsigned int n = 0; while (n < _notes.size() - 2 && _notes[n + 1] < ev.note.note) ++n; ev.note.velocity = apply_transform( ev.note.velocity, map_range(ev.note.note, _notes[n], _notes[n + 1], _params[n], _params[n + 1]), _mode ); } return true; } private: std::vector const _notes; std::vector const _params; TransformMode const _mode; }; class CtrlMap : public Unit { public: CtrlMap(int ctrl_in, int ctrl_out) : _ctrl_in(ctrl_in) , _ctrl_out(ctrl_out) { } virtual bool process(MidiEvent & ev) const { if (ev.type == MIDI_EVENT_CTRL && ev.ctrl.param == _ctrl_in) { ev.ctrl.param = _ctrl_out; } return true; } private: int const _ctrl_in; int const _ctrl_out; }; class CtrlRange : public Unit { public: CtrlRange(int ctrl, int min, int max, int in_min, int in_max) : _ctrl(ctrl) , _min(min) , _max(max) , _in_min(in_min) , _in_max(in_max) { ASSERT(in_min < in_max); } virtual bool process(MidiEvent & ev) const { if (ev.type == MIDI_EVENT_CTRL && ev.ctrl.param == _ctrl) { ev.ctrl.value = map_range(ev.ctrl.value, _in_min, _in_max, _min, _max); } return true; } private: int const _ctrl; int const _min; int const _max; int const _in_min; int const _in_max; }; class CtrlCurve : public Unit { public: CtrlCurve(int ctrl, float param, TransformMode mode) : _ctrl(ctrl) , _param(param) , _mode(mode) { } virtual bool process(MidiEvent & ev) const { if (ev.type == MIDI_EVENT_CTRL && ev.ctrl.param == _ctrl) { ev.ctrl.value = apply_transform(ev.ctrl.value, _param, _mode); } return true; } private: int const _ctrl; float const _param; TransformMode const _mode; }; class PitchbendRange : public Unit { public: PitchbendRange(int min, int max, int in_min, int in_max) : _min(min) , _max(max) , _in_min(in_min) , _in_max(in_max) { } virtual bool process(MidiEvent & ev) const { if (ev.type == MIDI_EVENT_PITCHBEND) { if (ev.ctrl.value >= 0) { ev.ctrl.value = map_range(ev.ctrl.value, 0, _in_max, 0, _max); } else { ev.ctrl.value = map_range(ev.ctrl.value, _in_min, 0, _min, 0); } } return true; } private: int const _min; int const _max; int const _in_min; int const _in_max; }; } // Units } // Mididings #endif // MIDIDINGS_UNITS_MODIFIERS_HH mididings-20120419~ds0/src/units/engine.hh0000644000175000017500000000503611736211472020141 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_UNITS_ENGINE_HH #define MIDIDINGS_UNITS_ENGINE_HH #include "units/base.hh" #include "units/util.hh" #include "engine.hh" #include "patch.hh" namespace Mididings { namespace Units { class Sanitize : public UnitExImpl { public: Sanitize() { } template typename B::Range process(B & buffer, typename B::Iterator it) const { Engine & engine = buffer.engine(); if (engine.sanitize_event(*it)) { return Patch::keep_event(buffer, it); } else { return Patch::delete_event(buffer, it); } } }; class SceneSwitch : public UnitExImpl { public: SceneSwitch(int num, int offset) : _num(num) , _offset(offset) { } template typename B::Range process(B & buffer, typename B::Iterator it) const { Engine & engine = buffer.engine(); if (_offset == 0) { engine.switch_scene(get_parameter(_num, *it)); } else { // FIXME: handle gaps in scene numbers int n = engine.current_scene() + _offset; if (engine.has_scene(n)) { engine.switch_scene(n); } } return Patch::delete_event(buffer, it); } private: int const _num; int const _offset; }; class SubSceneSwitch : public UnitExImpl { public: SubSceneSwitch(int num, int offset, bool wrap) : _num(num) , _offset(offset) , _wrap(wrap) { } template typename B::Range process(B & buffer, typename B::Iterator it) const { Engine & engine = buffer.engine(); if (_offset == 0) { engine.switch_scene(-1, get_parameter(_num, *it)); } else { int n = engine.current_subscene() + _offset; if (_wrap) { n %= engine.num_subscenes(); } if (engine.has_subscene(n)) { engine.switch_scene(-1, n); } } return Patch::delete_event(buffer, it); } private: int const _num; int const _offset; bool const _wrap; }; } // Units } // Mididings #endif // MIDIDINGS_UNITS_ENGINE_HH mididings-20120419~ds0/src/curious_alloc.hh0000644000175000017500000001055511734202664020400 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_CURIOUS_ALLOC_HH #define MIDIDINGS_CURIOUS_ALLOC_HH #include #include #include #include "util/debug.hh" namespace Mididings { template class curious_alloc_base { public: static std::size_t max_utilization() { return max_utilization_; } static std::size_t fallback_count() { return fallback_count_; } protected: static std::size_t max_utilization_; static std::size_t fallback_count_; }; /* * Constant-time allocation/deallocation from a fixed-size pool of N elements. * deleted elements are not reclaimed until all (!) elements are deallocated. * * \tparam T the type to be allocated * \tparam N the size of the data pool * \tparam R the original data type before rebind */ template class curious_alloc : public curious_alloc_base { public: typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; typedef T * pointer; typedef T const * const_pointer; typedef T & reference; typedef T const & const_reference; typedef T value_type; template struct rebind { typedef curious_alloc other; }; curious_alloc() { } curious_alloc(curious_alloc const &) { } template curious_alloc(curious_alloc const &) { } ~curious_alloc() { } bool operator==(curious_alloc const &) { return true; } bool operator!=(curious_alloc const &) { return false; } pointer address(reference x) const { return &x; } const_pointer address(const_reference x) const { return &x; } pointer allocate(size_type n, void const * hint = 0) { (void)n; ASSERT(n == 1); if (index_ >= N) { // can't allocate from pool, use fallback allocator ++this->fallback_count_; return fallback_.allocate(n, hint); } ++count_; if (index_ >= this->max_utilization_) { this->max_utilization_ = index_ + 1; } return pool_ + (index_++); } void deallocate(pointer p, size_type n) { (void)n; ASSERT(n == 1); if (std::less()(p, pool_) || std::greater_equal()(p, pool_ + N)) { // address is not within pool address range, must have been // allocated using fallback allocator fallback_.deallocate(p, n); return; } if (p == pool_ + index_ - 1) { // removing last element, can be reused --index_; } if (!(--count_)) { // no allocations left, start over index_ = 0; } } size_type max_size() const throw() { return 1; } void construct(pointer p, T const & val) { new (static_cast(p)) T(val); } void construct(pointer p) { new (static_cast(p)) T(); } void destroy(pointer p) { p->~T(); } private: static unsigned char pool_array_[N * sizeof(T)]; static T* pool_; static std::size_t count_; static std::size_t index_; // fallback allocator in case this one runs out of space static std::allocator fallback_; }; template std::size_t curious_alloc_base::max_utilization_ = 0; template std::size_t curious_alloc_base::fallback_count_ = 0; template unsigned char curious_alloc::pool_array_[N * sizeof(T)]; template T* curious_alloc::pool_ = reinterpret_cast(&curious_alloc::pool_array_); template std::size_t curious_alloc::count_ = 0; template std::size_t curious_alloc::index_ = 0; template std::allocator curious_alloc::fallback_ = std::allocator(); } // Mididings #endif // MIDIDINGS_CURIOUS_ALLOC_HH mididings-20120419~ds0/src/config.hh0000644000175000017500000000340300010137140016751 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_CONFIG_HH #define MIDIDINGS_CONFIG_HH // XXX Python.h must be included before any system header, so let's get this over with. // this allows us to keep the include order sane everywhere else. #include #include namespace Mididings { namespace Config { // total number of events that can be stored simultaneously in the event list during each process cycle static int const MAX_EVENTS = 1024; // maximum number of notes that can be remembered in case of a scene switch (so note-offs can be routed accordingly). // this is more of a soft limit, additional memory will be allocated if necessary (not RT-safe!) static int const MAX_SIMULTANEOUS_NOTES = 64; // maximum number of sustain pedal states that can be remembered in case of a scene switch static int const MAX_SUSTAIN_PEDALS = 4; static int const MAX_ASYNC_CALLS = 256; static int const ASYNC_JOIN_TIMEOUT = 3000; static int const ASYNC_CALLBACK_INTERVAL = 50; static std::size_t const ALSA_SYSEX_CHUNK_SIZE = 256; static int const MAX_JACK_EVENTS = 128; // maximum size of JACK MIDI events. in reality this depends on the JACK period size... static int const MAX_JACK_EVENT_SIZE = 4096; // realtime priority offset for buffered JACK backend, subtracted from JACK's own priority static int const JACK_BUFFERED_RTPRIO_OFFSET = 10; } } // Mididings #endif // MIDIDINGS_CONFIG_HH mididings-20120419~ds0/src/util/0000755000175000017500000000000011736404115016160 5ustar alessioalessiomididings-20120419~ds0/src/util/iterator_range.hh0000644000175000017500000000343511731520045021507 0ustar alessioalessio/* * Copyright (C) 2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef DAS_UTIL_ITERATOR_RANGE_HH #define DAS_UTIL_ITERATOR_RANGE_HH #include namespace das { template class iterator_range { private: typedef typename std::iterator_traits::difference_type difference_type; public: /** * Construction from two iterators. */ iterator_range(T const & begin, T const & end) : _begin(begin) , _end(end) { } /** * Construction from begin iterator and number of elements. */ iterator_range(T const & iter, difference_type n = 0) : _begin(iter) , _end(iter) { advance_end(n); } T begin() const { return _begin; } T end() const { return _end; } bool empty() const { return _begin == _end; } difference_type size() const { return std::distance(_begin, _end); } bool operator==(iterator_range const & other) { return _begin == other.begin() && _end == other.end(); } bool operator!=(iterator_range const & other) { return !(*this == other); } void set_begin(T const & iter) { _begin = iter; } void set_end(T const & iter) { _end = iter; } void advance_begin(difference_type n = 1) { std::advance(_begin, n); } void advance_end(difference_type n = 1) { std::advance(_end, n); } private: T _begin; T _end; }; } // namespace das #endif // DAS_UTIL_ITERATOR_RANGE_HH mididings-20120419~ds0/src/util/counted_objects.hh0000644000175000017500000000206711737171372021667 0ustar alessioalessio/* * Copyright (C) 2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef DAS_UTIL_COUNTED_OBJECTS_HH #define DAS_UTIL_COUNTED_OBJECTS_HH #include namespace das { template class counted_objects { protected: counted_objects() { ++alloc_; } ~counted_objects() { ++dealloc_; } public: static std::size_t allocated() { return alloc_; } static std::size_t deallocated() { return dealloc_; } private: static boost::detail::atomic_count alloc_; static boost::detail::atomic_count dealloc_; }; template boost::detail::atomic_count counted_objects::alloc_(0); template boost::detail::atomic_count counted_objects::dealloc_(0); } // namespace das #endif // DAS_UTIL_COUNTED_OBJECTS_HH mididings-20120419~ds0/src/util/ringbuffer.hh0000644000175000017500000000514111734203263020632 0ustar alessioalessio/* * Copyright (C) 2009 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef DAS_UTIL_RINGBUFFER_HH #define DAS_UTIL_RINGBUFFER_HH #include #include namespace das { /* * lock-free ring buffer, supports storing C++ objects. * inspired by and partly copied from Raul::RingBuffer by Dave Robillard. */ template class ringbuffer : boost::noncopyable { public: ringbuffer(std::size_t size) : _size(size) , _buf_array(new unsigned char[size * sizeof(T)]) , _buf(reinterpret_cast(_buf_array)) { reset(); } ~ringbuffer() { delete[] _buf_array; } void reset() { g_atomic_int_set(&_write_idx, 0); g_atomic_int_set(&_read_idx, 0); } std::size_t write_space() const { std::size_t const w = g_atomic_int_get(&_write_idx); std::size_t const r = g_atomic_int_get(&_read_idx); if (w > r) { return ((r - w + _size) % _size) - 1; } else if (w < r) { return (r - w) - 1; } else { return _size - 1; } } std::size_t read_space() const { std::size_t const w = g_atomic_int_get(&_write_idx); std::size_t const r = g_atomic_int_get(&_read_idx); if (w > r) { return w - r; } else { return (w - r + _size) % _size; } } std::size_t capacity() const { return _size; } bool write(T const & src) { if (write_space()) { std::size_t const priv_write_idx = g_atomic_int_get(&_write_idx); void *p = static_cast(_buf + priv_write_idx); new (p) T(src); g_atomic_int_set(&_write_idx, (priv_write_idx + 1) % _size); return true; } else { return false; } } bool read(T & dst) { if (read_space()) { std::size_t const priv_read_idx = g_atomic_int_get(&_read_idx); T *p = _buf + priv_read_idx; dst = *p; p->~T(); g_atomic_int_set(&_read_idx, (priv_read_idx + 1) % _size); return true; } else { return false; } } protected: int _write_idx; int _read_idx; std::size_t _size; unsigned char *_buf_array; T *_buf; }; } // namespace das #endif // DAS_UTIL_RINGBUFFER_HH mididings-20120419~ds0/src/util/python.hh0000644000175000017500000000134500007345256020024 0ustar alessioalessio/* * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef DAS_UTIL_PYTHON_HH #define DAS_UTIL_PYTHON_HH #include #include namespace das { class scoped_gil_lock : boost::noncopyable { public: scoped_gil_lock() { _gil = PyGILState_Ensure(); } ~scoped_gil_lock() { PyGILState_Release(_gil); } private: PyGILState_STATE _gil; }; } // namespace das #endif // DAS_UTIL_PYTHON_HH mididings-20120419~ds0/src/util/python_converters.hh0000644000175000017500000001456711737312247022316 0ustar alessioalessio/* * Copyright (C) 2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef DAS_UTIL_PYTHON_CONVERTERS_HH #define DAS_UTIL_PYTHON_CONVERTERS_HH #include "util/from_python_converter.hh" #include #include #include #include #include namespace das { namespace python_converters { template struct vector_from_sequence_converter : from_python_converter > { static bool convertible(PyObject *obj) { return PySequence_Check(obj); } static void construct(T & vec, PyObject *obj) { Py_ssize_t size = PySequence_Size(obj); vec.reserve(size); for (Py_ssize_t i = 0; i != size; ++i) { PyObject *item = PySequence_GetItem(obj, i); vec.push_back(boost::python::extract(item)); boost::python::decref(item); } } }; template struct vector_from_iterator_converter : from_python_converter > { static bool convertible(PyObject *obj) { return PyIter_Check(obj); } static void construct(T & vec, PyObject *obj) { PyObject *item; while ((item = PyIter_Next(obj))) { vec.push_back(boost::python::extract(item)); boost::python::decref(item); } // propagate exceptions that occured inside a generator back to Python if (PyErr_Occurred()) { throw boost::python::error_already_set(); } } }; template struct vector_to_list_converter : boost::python::to_python_converter #ifdef BOOST_PYTHON_SUPPORTS_PY_SIGNATURES , true #endif > { static PyObject *convert(T const & vec) { boost::python::list ret; for (typename T::const_iterator it = vec.begin(); it != vec.end(); ++it) { ret.append(*it); } return boost::python::incref(ret.ptr()); } static PyTypeObject const *get_pytype() { return &PyList_Type; } }; template struct shared_ptr_vector_from_sequence_converter : from_python_converter, shared_ptr_vector_from_sequence_converter > { static bool convertible(PyObject *obj) { return PySequence_Check(obj); } static void construct(boost::shared_ptr & pvec, PyObject *obj) { Py_ssize_t size = PySequence_Size(obj); T *vec = new T(size); for (Py_ssize_t i = 0; i != size; ++i) { PyObject *item = PySequence_GetItem(obj, i); (*vec)[i] = boost::python::extract(item); boost::python::decref(item); } pvec.reset(vec); } }; template struct shared_ptr_vector_to_list_converter : boost::python::to_python_converter, shared_ptr_vector_to_list_converter #ifdef BOOST_PYTHON_SUPPORTS_PY_SIGNATURES , true #endif > { static PyObject *convert(boost::shared_ptr const & pvec) { boost::python::list ret; for (typename T::const_iterator it = pvec->begin(); it != pvec->end(); ++it) { ret.append(*it); } return boost::python::incref(ret.ptr()); } static PyTypeObject const *get_pytype() { return &PyList_Type; } }; #if PY_VERSION_HEX >= 0x02060000 template struct shared_ptr_vector_from_bytearray_converter : from_python_converter, shared_ptr_vector_from_bytearray_converter > { static bool convertible(PyObject *obj) { return PyByteArray_Check(obj); } static void construct(boost::shared_ptr & pvec, PyObject *obj) { char const *buffer = PyByteArray_AsString(obj); Py_ssize_t size = PyByteArray_Size(obj); T *vec = new T(size); std::copy(buffer, buffer + size, &vec->front()); pvec.reset(vec); } }; template struct shared_ptr_vector_to_bytearray_converter : boost::python::to_python_converter, shared_ptr_vector_to_bytearray_converter #ifdef BOOST_PYTHON_SUPPORTS_PY_SIGNATURES , true #endif > { static PyObject *convert(boost::shared_ptr const & pvec) { return PyByteArray_FromStringAndSize(reinterpret_cast(&pvec->front()), pvec->size()); } static PyTypeObject const *get_pytype() { return &PyByteArray_Type; } }; #endif // PY_VERSION_HEX >= 0x02060000 template struct map_from_dict_converter : from_python_converter > { static bool convertible(PyObject *obj) { return PyDict_Check(obj); } static void construct(T & map, PyObject *obj) { PyObject *key_ptr, *value_ptr; Py_ssize_t pos = 0; while (PyDict_Next(obj, &pos, &key_ptr, &value_ptr)) { typename T::key_type key = boost::python::extract(key_ptr); typename T::mapped_type value = boost::python::extract(value_ptr); map[key] = value; } } }; } // python_converters template void register_vector_converters() { python_converters::vector_from_sequence_converter(); python_converters::vector_from_iterator_converter(); python_converters::vector_to_list_converter(); } template void register_shared_ptr_vector_converters() { python_converters::shared_ptr_vector_from_sequence_converter(); python_converters::shared_ptr_vector_to_list_converter(); } #if PY_VERSION_HEX >= 0x02060000 template void register_shared_ptr_vector_bytearray_converters() { python_converters::shared_ptr_vector_from_bytearray_converter(); python_converters::shared_ptr_vector_to_bytearray_converter(); } #endif // PY_VERSION_HEX >= 0x02060000 template void register_map_converters() { python_converters::map_from_dict_converter(); } } // namespace das #endif // DAS_UTIL_PYTHON_CONVERTERS_HH mididings-20120419~ds0/src/util/string.hh0000644000175000017500000000360111736404115020007 0ustar alessioalessio/* * Copyright (C) 2007-2008 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef DAS_UTIL_STRING_HH #define DAS_UTIL_STRING_HH #include #include #include #include #include #include #include namespace das { class make_string { public: template make_string & operator<< (T const& t) { _stream << t; return *this; } make_string & operator<< (std::ostream & (*pf)(std::ostream &)) { pf(_stream); return *this; } operator std::string() { return _stream.str(); } private: std::ostringstream _stream; }; class regex : boost::noncopyable { public: struct compile_error : public std::runtime_error { compile_error(std::string const & w) : std::runtime_error(w) { } }; regex(std::string const & pattern, bool complete=false) { std::string p = complete ? ("^" + pattern + "$") : pattern; int error = ::regcomp(&_preg, p.c_str(), REG_EXTENDED | REG_NOSUB); _freer.reset(&_preg, ::regfree); if (error) { std::size_t bufsize = ::regerror(error, &_preg, NULL, 0); std::vector buf(bufsize); ::regerror(error, &_preg, &(*buf.begin()), bufsize); throw compile_error(&*buf.begin()); } } bool match(std::string const & str) { return ::regexec(&_preg, str.c_str(), 0, NULL, 0) == 0; } private: ::regex_t _preg; boost::shared_ptr _freer; }; } // namespace das #endif // DAS_UTIL_STRING_HH mididings-20120419~ds0/src/util/from_python_converter.hh0000644000175000017500000000241111737205000023123 0ustar alessioalessio/* * Copyright (C) 2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef DAS_UTIL_FROM_PYTHON_CONVERTER_HH #define DAS_UTIL_FROM_PYTHON_CONVERTER_HH #include #include namespace das { template struct from_python_converter { from_python_converter() { boost::python::converter::registry::push_back(&convertible, &construct, boost::python::type_id()); } static void *convertible(PyObject *obj) { if (!Conversion::convertible(obj)) return 0; return obj; } static void construct(PyObject *obj, boost::python::converter::rvalue_from_python_stage1_data *data) { void *storage = (reinterpret_cast*>(data))->storage.bytes; new (storage) T(); T & t = *(T *) storage; Conversion::construct(t, obj); data->convertible = storage; } }; } // namespace das #endif // DAS_UTIL_FROM_PYTHON_CONVERTER_HH mididings-20120419~ds0/src/util/debug.hh0000644000175000017500000000200411465622577017600 0ustar alessioalessio/* * Copyright (C) 2007-2008 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef DAS_UTIL_DEBUG_HH #define DAS_UTIL_DEBUG_HH #ifndef NDEBUG #include #define ASSERT(f) assert(f) #define VERIFY(f) assert(f) #define FAIL() assert(false) #else #define ASSERT(f) ((void)0) #define VERIFY(f) ((void)f) #define FAIL() ((void)0) #endif #if defined(ENABLE_DEBUG_FN) || defined(ENABLE_DEBUG_PRINT) #include #endif #ifdef ENABLE_DEBUG_FN #define DEBUG_FN() std::cout << __PRETTY_FUNCTION__ << std::endl #else #define DEBUG_FN() ((void)0) #endif #ifdef ENABLE_DEBUG_PRINT #define DEBUG_PRINT(m) std::cout << m << std::endl #else #define DEBUG_PRINT(m) ((void)0) #endif #endif // DAS_UTIL_DEBUG_HH mididings-20120419~ds0/src/engine.cc0000644000175000017500000002651211744110740016762 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include "config.hh" #include "engine.hh" #include "util/python.hh" #include "units/base.hh" #include "units/engine.hh" #include #include #include #include #include #include #include "util/debug.hh" namespace Mididings { Engine::Engine(std::string const & backend_name, std::string const & client_name, Backend::PortNameVector const & in_ports, Backend::PortNameVector const & out_ports, bool verbose) : _verbose(verbose) , _current_patch(NULL) , _current_scene(-1) , _current_subscene(-1) , _new_scene(-1) , _new_subscene(-1) , _noteon_patches(Config::MAX_SIMULTANEOUS_NOTES) , _sustain_patches(Config::MAX_SUSTAIN_PEDALS) , _buffer(*this) , _python_caller(new PythonCaller(boost::bind(&Engine::run_async, this))) { _backend = Backend::create(backend_name, client_name, in_ports, out_ports); // construct a patch with a single sanitize unit Patch::UnitExPtr sani(new Units::Sanitize); Patch::ModulePtr mod(new Patch::Extended(sani)); _sanitize_patch.reset(new Patch(mod)); } Engine::~Engine() { // this needs to be gone before the engine can safely be destroyed _python_caller.reset(); if (_backend) { _backend->stop(); } } void Engine::connect_ports(Backend::PortConnectionMap const & in_port_connections, Backend::PortConnectionMap const & out_port_connections) { if (_backend) { _backend->connect_ports(in_port_connections, out_port_connections); } } void Engine::add_scene(int i, PatchPtr patch, PatchPtr init_patch) { if (!has_scene(i)) { _scenes[i] = std::vector(); } _scenes[i].push_back(ScenePtr(new Scene(patch, init_patch))); } void Engine::set_processing(PatchPtr ctrl_patch, PatchPtr pre_patch, PatchPtr post_patch) { ASSERT(!_ctrl_patch); ASSERT(!_pre_patch); ASSERT(!_post_patch); _ctrl_patch = ctrl_patch; _pre_patch = pre_patch; _post_patch = post_patch; } void Engine::start(int initial_scene, int initial_subscene) { _backend->start( boost::bind(&Engine::run_init, this, initial_scene, initial_subscene), boost::bind(&Engine::run_cycle, this) ); } void Engine::run_init(int initial_scene, int initial_subscene) { boost::mutex::scoped_lock lock(_process_mutex); // if no initial scene is specified, use the first one if (initial_scene == -1) { initial_scene = _scenes.begin()->first; } ASSERT(has_scene(initial_scene)); _buffer.clear(); _new_scene = initial_scene; _new_subscene = initial_subscene; process_scene_switch(_buffer); _backend->output_events(_buffer.begin(), _buffer.end()); } void Engine::run_cycle() { MidiEvent ev; while (_backend->input_event(ev)) { #ifdef ENABLE_BENCHMARK timeval tv1, tv2; gettimeofday(&tv1, NULL); #endif boost::mutex::scoped_lock lock(_process_mutex); _buffer.clear(); // process the event process(_buffer, ev); // handle scene switches process_scene_switch(_buffer); #ifdef ENABLE_BENCHMARK gettimeofday(&tv2, NULL); std::cout << (tv2.tv_sec - tv1.tv_sec) * 1000000 + (tv2.tv_usec - tv1.tv_usec) << std::endl; #endif _backend->output_events(_buffer.begin(), _buffer.end()); } } void Engine::run_async() { if (!_backend) { // backend already destroyed return; } if (_new_scene != -1 || _new_subscene != -1) { boost::mutex::scoped_lock lock(_process_mutex); _buffer.clear(); process_scene_switch(_buffer); _backend->output_events(_buffer.begin(), _buffer.end()); } } std::vector Engine::process_event(MidiEvent const & ev) { boost::mutex::scoped_lock lock(_process_mutex); std::vector v; Patch::EventBuffer buffer(*this); if (!_current_patch) { _current_patch = &*_scenes.find(0)->second[0]->patch; } process(buffer, ev); process_scene_switch(buffer); v.insert(v.end(), buffer.begin(), buffer.end()); return v; } template void Engine::process(B & buffer, MidiEvent const & ev) { ASSERT(buffer.empty()); Patch * patch = get_matching_patch(ev); if (_ctrl_patch) { buffer.insert(buffer.end(), ev); _ctrl_patch->process(buffer); } typename B::Iterator it = buffer.insert(buffer.end(), ev); typename B::Range r(it, buffer.end()); if (_pre_patch) { _pre_patch->process(buffer, r); } patch->process(buffer, r); if (_post_patch) { _post_patch->process(buffer, r); } _sanitize_patch->process(_buffer, r); } Patch * Engine::get_matching_patch(MidiEvent const & ev) { // note on: store current patch if (ev.type == MIDI_EVENT_NOTEON) { _noteon_patches.insert(std::make_pair(make_notekey(ev), _current_patch)); return _current_patch; } // note off: retrieve and remove stored patch else if (ev.type == MIDI_EVENT_NOTEOFF) { NotePatchMap::const_iterator i = _noteon_patches.find(make_notekey(ev)); if (i != _noteon_patches.end()) { Patch *p = i->second; _noteon_patches.erase(i); return p; } } // sustain pressed // TODO: handle half-pedal correctly else if (ev.type == MIDI_EVENT_CTRL && ev.ctrl.param == 64 && ev.ctrl.value == 127) { _sustain_patches.insert(std::make_pair(make_sustainkey(ev), _current_patch)); return _current_patch; } // sustain released else if (ev.type == MIDI_EVENT_CTRL && ev.ctrl.param == 64 && ev.ctrl.value == 0) { SustainPatchMap::const_iterator i = _sustain_patches.find(make_sustainkey(ev)); if (i != _sustain_patches.end()) { Patch *p = i->second; _sustain_patches.erase(i); return p; } } // anything else: just use current patch return _current_patch; } void Engine::switch_scene(int scene, int subscene) { if (scene != -1) { _new_scene = scene; } if (subscene != -1) { _new_subscene = subscene; } } template void Engine::process_scene_switch(B & buffer) { if (_new_scene == -1 && _new_subscene == -1) { // nothing to do return; } // call python scene switch handler if we have more than one scene if (_scenes.size() > 1) { scene_switch_callback(_new_scene, _new_subscene); } // determine the actual scene and subscene number we're switching to int scene = _new_scene != -1 ? _new_scene : _current_scene; int subscene = _new_subscene != -1 ? _new_subscene : 0; SceneMap::const_iterator i = _scenes.find(scene); // check if scene and subscene exist if (i != _scenes.end() && static_cast(i->second.size()) > subscene) { // found something... ScenePtr s = i->second[subscene]; // check if the scene has an init patch if (s->init_patch) { // create dummy event to trigger init patch MidiEvent ev; ev.type = MIDI_EVENT_DUMMY; typename B::Iterator it = buffer.insert(buffer.end(), ev); typename B::Range r(typename B::Range(it, buffer.end())); // run event through init patch s->init_patch->process(buffer, r); if (_post_patch) { _post_patch->process(buffer, r); } _sanitize_patch->process(buffer, r); } // store pointer to patch _current_patch = &*s->patch; // store scene and subscene numbers _current_scene = scene; _current_subscene = subscene; } // mark as done _new_scene = -1; _new_subscene = -1; } bool Engine::sanitize_event(MidiEvent & ev) const { // FIXME: std::cout is not RT-safe! if (ev.port < 0 || (_backend && ev.port >= static_cast(_backend->num_out_ports()))) { if (_verbose) std::cout << "invalid output port, event discarded" << std::endl; return false; } if (ev.channel < 0 || ev.channel > 15) { if (_verbose) std::cout << "invalid channel, event discarded" << std::endl; return false; } switch (ev.type) { case MIDI_EVENT_NOTEON: case MIDI_EVENT_NOTEOFF: if (ev.note.note < 0 || ev.note.note > 127) { if (_verbose) std::cout << "invalid note number, event discarded" << std::endl; return false; } if (ev.note.velocity < 0) ev.note.velocity = 0; if (ev.note.velocity > 127) ev.note.velocity = 127; if (ev.type == MIDI_EVENT_NOTEON && ev.note.velocity < 1) return false; return true; case MIDI_EVENT_CTRL: if (ev.ctrl.param < 0 || ev.ctrl.param > 127) { if (_verbose) std::cout << "invalid controller number, event discarded" << std::endl; return false; } if (ev.ctrl.value < 0) ev.ctrl.value = 0; if (ev.ctrl.value > 127) ev.ctrl.value = 127; return true; case MIDI_EVENT_PITCHBEND: if (ev.ctrl.value < -8192) ev.ctrl.value = -8192; if (ev.ctrl.value > 8191) ev.ctrl.value = 8191; return true; case MIDI_EVENT_AFTERTOUCH: if (ev.ctrl.value < 0) ev.ctrl.value = 0; if (ev.ctrl.value > 127) ev.ctrl.value = 127; return true; case MIDI_EVENT_PROGRAM: if (ev.ctrl.value < 0 || ev.ctrl.value > 127) { if (_verbose) std::cout << "invalid program number, event discarded" << std::endl; return false; } return true; case MIDI_EVENT_SYSEX: if (ev.sysex->size() < 2 || (*ev.sysex)[0] != 0xf0 || (*ev.sysex)[ev.sysex->size()-1] != 0xf7) { if (_verbose) std::cout << "invalid sysex, event discarded" << std::endl; return false; } return true; case MIDI_EVENT_POLY_AFTERTOUCH: case MIDI_EVENT_SYSCM_QFRAME: case MIDI_EVENT_SYSCM_SONGPOS: case MIDI_EVENT_SYSCM_SONGSEL: case MIDI_EVENT_SYSCM_TUNEREQ: case MIDI_EVENT_SYSRT_CLOCK: case MIDI_EVENT_SYSRT_START: case MIDI_EVENT_SYSRT_CONTINUE: case MIDI_EVENT_SYSRT_STOP: case MIDI_EVENT_SYSRT_SENSING: case MIDI_EVENT_SYSRT_RESET: return true; case MIDI_EVENT_DUMMY: return false; default: if (_verbose) std::cout << "unknown event type, event discarded" << std::endl; return false; } } void Engine::output_event(MidiEvent const & ev) { boost::mutex::scoped_lock lock(_process_mutex); _backend->output_event(ev); } double Engine::time() { #if _POSIX_TIMERS > 0 ::timespec t; ::clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec + 1e-9 * t.tv_nsec; #else ::timeval t; ::gettimeofday(&t, NULL); return t.tv_sec + 1e-6 * t.tv_usec; #endif } } // Mididings mididings-20120419~ds0/src/patch.hh0000644000175000017500000001514211736610204016624 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_PATCH_HH #define MIDIDINGS_PATCH_HH #include "config.hh" #include "midi_event.hh" #include "curious_alloc.hh" #include #include #include #include #include #include "util/iterator_range.hh" #include "util/counted_objects.hh" #include "util/debug.hh" namespace Mididings { class Engine; namespace Units { class Unit; class UnitEx; } class Patch : boost::noncopyable , das::counted_objects { private: typedef std::list > EventListRT; typedef std::list EventList; // deriving from a standard container. get over it. template class EventBufferType : public T , boost::noncopyable { public: typedef typename T::iterator Iterator; typedef das::iterator_range Range; EventBufferType(Engine & engine) : _engine(engine) { } Engine & engine() const { return _engine; } private: Engine & _engine; }; public: /** * The buffer type for RT-safe event processing, using a std::list with * custom allocator. */ typedef EventBufferType EventBufferRT; /** * Basically a regular std::list with some handy typedefs. */ typedef EventBufferType EventBuffer; typedef boost::shared_ptr UnitPtr; typedef boost::shared_ptr UnitExPtr; /** * The module base class. */ class Module : boost::noncopyable , das::counted_objects { public: Module() { } virtual ~Module() { } virtual void process(EventBufferRT & buffer, EventBufferRT::Range & range) const = 0; virtual void process(EventBuffer & buffer, EventBuffer::Range & range) const = 0; }; typedef boost::shared_ptr ModulePtr; typedef std::vector ModuleVector; private: /** * The actual base class for each module type, using the CRTP to circumvent * C++'s lack of virtual template member functions. */ template class ModuleImpl : public Module { public: virtual void process(EventBufferRT & buffer, EventBufferRT::Range & range) const { Derived const & d = *static_cast(this); d.template process(buffer, range); } virtual void process(EventBuffer & buffer, EventBuffer::Range & range) const { Derived const & d = *static_cast(this); d.template process(buffer, range); } }; public: /** * A chain of units connected in series. */ class Chain : public ModuleImpl { public: Chain(ModuleVector const & modules) : _modules(modules) { } template void process(B & buffer, typename B::Range & range) const; private: ModuleVector const _modules; }; /** * A fork, units connected in parallel. */ class Fork : public ModuleImpl { public: Fork(ModuleVector const & modules, bool remove_duplicates) : _modules(modules) , _remove_duplicates(remove_duplicates) { } template void process(B & buffer, typename B::Range & range) const; private: ModuleVector const _modules; bool const _remove_duplicates; }; /** * A single unit. */ class Single : public ModuleImpl { public: Single(UnitPtr const & unit) : _unit(unit) { } template void process(B & buffer, typename B::Range & range) const; private: UnitPtr const _unit; }; /** * A single extended unit. */ class Extended : public ModuleImpl { public: Extended(UnitExPtr const & unit) : _unit(unit) { } template void process(B & buffer, typename B::Range & range) const; private: UnitExPtr const _unit; }; public: /** * Creates a new patch. * * \param module the root module of the patch */ Patch(ModulePtr const & module) : _module(module) { } /** * Processes events. * * \tparam B the type of event buffer * \param[in,out] buffer the event buffer * \param[in,out] range the iterator range of events to be processed */ template void process(B & buffer, typename B::Range & range) const; /** * Processes all events in the given buffer. * * \tparam B the type of event buffer * \param[in,out] buffer the event buffer */ template void process(B & buffer) const { typename B::Range range(buffer.begin(), buffer.end()); process(buffer, range); } /** * Replaces event with one or more events, returns the range containing the new events. */ template static inline typename B::Range replace_event(B & buffer, typename B::Iterator it, IterT begin, IterT end) { it = buffer.erase(it); typename B::Iterator first = buffer.insert(it, *begin); buffer.insert(it, ++begin, end); return typename B::Range(first, it); } /** * Leaves event unchanged, returns a range containing the single event. */ template static inline typename B::Range keep_event(B & /*buffer*/, typename B::Iterator it) { typename B::Range ret(it, 1); return ret; } /** * Removes event, returns empty range. */ template static inline typename B::Range delete_event(B & buffer, typename B::Iterator it) { it = buffer.erase(it); return typename B::Range(it); } private: template static std::string debug_range(std::string const & str, B const & buffer, typename B::Range const & range); ModulePtr const _module; }; } // Mididings #endif // MIDIDINGS_PATCH_HH mididings-20120419~ds0/src/SConstruct0000644000175000017500000000470211742030415017233 0ustar alessioalessio# -*- python -*- import os from distutils import sysconfig def boost_lib_name(lib): for libdir in ('/usr/lib', '/usr/local/lib', '/usr/lib64', '/usr/local/lib64'): for suffix in ('', '-mt'): libname = 'lib%s%s.so' % (lib, suffix) if os.path.isfile(os.path.join(libdir, libname)): return lib + suffix return lib + '-mt' env = Environment( # CXX = 'clang++', # CCFLAGS = ['-O2', '-W', '-Wall', '-finline-functions', '-fvisibility=hidden'], CCFLAGS = ['-g', '-W', '-Wall'], CPPDEFINES = [ # 'NDEBUG', # 'ENABLE_DEBUG_FN', # 'ENABLE_DEBUG_PRINT', # 'ENABLE_DEBUG_STATS', # 'ENABLE_BENCHMARK', 'ENABLE_ALSA_SEQ', 'ENABLE_JACK_MIDI', 'ENABLE_SMF', ], CPPPATH = ['.'], ENV = os.environ, LIBS = [ boost_lib_name('boost_python'), boost_lib_name('boost_thread'), # 'boost_python3', # 'boost_thread', ], ) # hack to remove compiler flags from the distutils default. # -Wstrict-prototypes is not valid for C++ cv_opt = sysconfig.get_config_var('CFLAGS') cflags = [ x for x in cv_opt.split() if x not in ['-g', '-O2', '-Wstrict-prototypes', '-DNDEBUG'] ] env.Append(CCFLAGS = cflags) # avoid some annoying warnings caused by recent boost versions and an overreacting gcc env.Append(CCFLAGS = '-Wno-missing-field-initializers') env.Append( CPPPATH = [sysconfig.get_python_inc(plat_specific=1)], LIBPATH = [sysconfig.get_python_lib(plat_specific=1)], # CPPPATH = ['/opt/boost1.43/include', '/opt/python3.2/include/python3.2m'], # LIBPATH = ['/opt/boost1.43/lib', '/opt/python3.2/lib'], # CPPPATH = ['/opt/boost1.46.1/include', '/usr/include/python3.1'], # LIBPATH = ['/opt/boost1.46.1/lib'], ) sources = [ 'engine.cc', 'patch.cc', 'python_caller.cc', 'python_module.cc', 'backend/base.cc', ] env.ParseConfig('pkg-config --cflags --libs glib-2.0') if 'ENABLE_ALSA_SEQ' in env['CPPDEFINES']: env.ParseConfig('pkg-config --cflags --libs alsa') sources.append('backend/alsa.cc') if 'ENABLE_JACK_MIDI' in env['CPPDEFINES']: env.ParseConfig('pkg-config --cflags --libs jack') sources.append(['backend/jack.cc', 'backend/jack_buffered.cc', 'backend/jack_realtime.cc']) if 'ENABLE_SMF' in env['CPPDEFINES']: env.ParseConfig('pkg-config --cflags --libs smf') sources.append('backend/smf.cc') env.SharedLibrary('_mididings', sources, SHLIBPREFIX='', SHOBJSUFFIX='.o') mididings-20120419~ds0/src/patch.cc0000644000175000017500000001637511736211217016624 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include "patch.hh" #include "units/base.hh" #include "engine.hh" #include #include #include #include "util/debug.hh" namespace Mididings { template void Patch::Chain::process(B & buf, typename B::Range & range) const { DEBUG_PRINT(Patch::debug_range("Chain in", buf, range)); // iterate over all modules in this chain for (ModuleVector::const_iterator module = _modules.begin(); module != _modules.end(); ++module) { // run all events through the next module in the chain (*module)->process(buf, range); if (range.empty()) { // the event range became empty, there's nothing left for us to do break; } } DEBUG_PRINT(Patch::debug_range("Chain out", buf, range)); } template void Patch::Fork::process(B & buffer, typename B::Range & range) const { DEBUG_PRINT(Patch::debug_range("Fork in", buffer, range)); // make a copy of all incoming events, allocated on the stack std::size_t num_events = range.size(); MidiEvent *in_events = static_cast(::alloca(num_events * sizeof(MidiEvent))); MidiEvent *p = in_events; for (typename B::iterator it = range.begin(); it != range.end(); ++it, ++p) { new (p) MidiEvent(*it); } // remove all incoming events from the buffer buffer.erase(range.begin(), range.end()); // clear range, no events to return so far range.set_begin(range.end()); // iterate over all input events for (MidiEvent *ev = in_events; ev != in_events + num_events; ++ev) { // the range of events returned for the current input event, // empty so far typename B::Range ev_range(range.end()); // iterate over all modules in this fork for (ModuleVector::const_iterator module = _modules.begin(); module != _modules.end(); ++module) { // insert one event typename B::Iterator it = buffer.insert(ev_range.end(), *ev); // the single-event range to be processed in this iteration typename B::Range proc_range(it, 1); // process event (*module)->process(buffer, proc_range); if (!proc_range.empty() && ev_range.empty()) { // at least one event was returned, we can now set the // beginning of range and ev_range if they were empty so far if (range.empty()) { range.set_begin(proc_range.begin()); } ev_range.set_begin(proc_range.begin()); } if (_remove_duplicates) { // for all events returned in this iteration... for (typename B::Iterator it = proc_range.begin(); it != proc_range.end(); ) { // look for previous occurrences that were returned for the // same input event, but from a different module if (std::find(ev_range.begin(), proc_range.begin(), *it) != proc_range.begin()) { // found previous identical event, remove the latest one it = buffer.erase(it); } else { ++it; } } } } // destroy the event that was previously placement-constructed // on the stack ev->~MidiEvent(); } DEBUG_PRINT(Patch::debug_range("Fork out", buffer, range)); } template void Patch::Single::process(B & buffer, typename B::Range & range) const { DEBUG_PRINT(Patch::debug_range("Single in", buffer, range)); // iterate over all events in the input range for (typename B::Iterator it = range.begin(); it != range.end(); ) { // process event if (_unit->process(*it)) { // keep this event, continue with next one ++it; } else { if (it == range.begin()) { // we're going to erase at the beginning of the event range, // so we need to adjust the range to keep it valid range.advance_begin(1); } // remove this event it = buffer.erase(it); } } DEBUG_PRINT(Patch::debug_range("Single out", buffer, range)); } template void Patch::Extended::process(B & buffer, typename B::Range & range) const { DEBUG_PRINT(Patch::debug_range("Extended in", buffer, range)); // make a copy of the input range typename B::Range in_range(range); // clear range, no events to return so far range.set_begin(range.end()); // iterate over all events in the input range for (typename B::Iterator it = in_range.begin(); it != in_range.end(); ) { // process event typename B::Range ret_range = _unit->process(buffer, it); if (range.empty() && !ret_range.empty()) { // the first event returned marks the beginning of our output range range.set_begin(ret_range.begin()); } // the next event to be processed is adjacent to those we just got back it = ret_range.end(); } DEBUG_PRINT(Patch::debug_range("Extended out", buffer, range)); } template void Patch::process(B & buffer, typename B::Range & range) const { DEBUG_PRINT(debug_range("Patch in", buffer, range)); _module->process(buffer, range); DEBUG_PRINT(debug_range("Patch out", buffer, range)); } template std::string Patch::debug_range(std::string const & str, B const & buffer, typename B::Range const & range) { std::ostringstream os; os << str << ": " << std::distance(buffer.begin(), range.begin()) << " " << std::distance(range.begin(), range.end()); for (typename B::Iterator it = range.begin(); it != range.end(); ++it) { os << " [" << it->type << " " << it->port << " " << it->channel << " " << it->data1 << " " << it->data2 << "]"; } return os.str(); } // force template instantiations template void Patch::Chain::process(EventBufferRT &, EventBufferRT::Range &) const; template void Patch::Chain::process(EventBuffer &, EventBuffer::Range &) const; template void Patch::Fork::process(EventBufferRT &, EventBufferRT::Range &) const; template void Patch::Fork::process(EventBuffer &, EventBuffer::Range &) const; template void Patch::Single::process(EventBufferRT &, EventBufferRT::Range &) const; template void Patch::Single::process(EventBuffer &, EventBuffer::Range &) const; template void Patch::Extended::process(EventBufferRT &, EventBufferRT::Range &) const; template void Patch::Extended::process(EventBuffer &, EventBuffer::Range &) const; template void Patch::process(EventBufferRT &, EventBufferRT::Range &) const; template void Patch::process(EventBuffer &, EventBuffer::Range &) const; } // Mididings mididings-20120419~ds0/src/python_caller.cc0000644000175000017500000001077611737340434020374 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include "config.hh" #include "python_caller.hh" #include "patch.hh" #include #include #if BOOST_VERSION < 103500 # include #endif #include #include #include #include #include #include "util/python.hh" #include "util/debug.hh" namespace bp = boost::python; namespace Mididings { PythonCaller::PythonCaller(EngineCallback engine_callback) : _rb(new das::ringbuffer(Config::MAX_ASYNC_CALLS)) , _engine_callback(engine_callback) , _quit(false) { // start async thread _thread.reset(new boost::thread(boost::bind(&PythonCaller::async_thread, this))); } PythonCaller::~PythonCaller() { _quit = true; _cond.notify_one(); #if BOOST_VERSION >= 103500 _thread->timed_join(boost::posix_time::milliseconds(Config::ASYNC_JOIN_TIMEOUT)); #else // what if the thread doesn't terminate, due to a long-running python function? _thread->join(); #endif } template typename B::Range PythonCaller::call_now(B & buffer, typename B::Iterator it, bp::object const & fun) { das::scoped_gil_lock gil; try { // call the python function bp::object ret = fun(*it); if (ret.ptr() == Py_None) { // returned None return Patch::delete_event(buffer, it); } bp::list ret_list = bp::extract(ret); bp::ssize_t len = bp::len(ret_list); if (len == 0) { return Patch::delete_event(buffer, it); } else if (len == 1) { *it = bp::extract(ret_list[0]); return Patch::keep_event(buffer, it); } else { bp::stl_input_iterator begin(ret_list), end; return Patch::replace_event(buffer, it, begin, end); } } catch (bp::error_already_set const &) { PyErr_Print(); return Patch::delete_event(buffer, it); } } template typename B::Range PythonCaller::call_deferred(B & buffer, typename B::Iterator it, bp::object const & fun, bool keep) { AsyncCallInfo c = { &fun, *it }; // queue function/event, notify async thread VERIFY(_rb->write(c)); _cond.notify_one(); if (keep) { return Patch::keep_event(buffer, it); } else { return Patch::delete_event(buffer, it); } } void PythonCaller::async_thread() { boost::mutex mutex; for (;;) { // check for program termination if (_quit) { return; } if (_rb->read_space()) { das::scoped_gil_lock gil; // read event from ringbuffer AsyncCallInfo c; _rb->read(c); try { // call python function (*c.fun)(bp::ptr(&c.ev)); } catch (bp::error_already_set &) { PyErr_Print(); } } else { // wait until woken up again boost::mutex::scoped_lock lock(mutex); #if BOOST_VERSION >= 103500 _cond.timed_wait(lock, boost::posix_time::milliseconds(Config::ASYNC_CALLBACK_INTERVAL)); #else boost::xtime xt; boost::xtime_get(&xt, boost::TIME_UTC); xt.nsec += Config::ASYNC_CALLBACK_INTERVAL * 1000000; _cond.timed_wait(lock, xt); #endif } _engine_callback(); } } // force template instantiations template Patch::EventBufferRT::Range PythonCaller::call_now (Patch::EventBufferRT &, Patch::EventBufferRT::Iterator, boost::python::object const &); template Patch::EventBuffer::Range PythonCaller::call_now (Patch::EventBuffer &, Patch::EventBuffer::Iterator, boost::python::object const &); template Patch::EventBufferRT::Range PythonCaller::call_deferred (Patch::EventBufferRT &, Patch::EventBufferRT::Iterator, boost::python::object const &, bool); template Patch::EventBuffer::Range PythonCaller::call_deferred (Patch::EventBuffer &, Patch::EventBuffer::Iterator, boost::python::object const &, bool); } // Mididings mididings-20120419~ds0/src/python_module.cc0000644000175000017500000002562211742030415020402 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include "config.hh" #include "engine.hh" #include "patch.hh" #include "midi_event.hh" #include "units/base.hh" #include "units/engine.hh" #include "units/filters.hh" #include "units/modifiers.hh" #include "units/generators.hh" #include "units/call.hh" #include "curious_alloc.hh" #include "util/python.hh" #include "util/python_converters.hh" #include "util/counted_objects.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_DEBUG_STATS #include #endif namespace Mididings { #ifdef ENABLE_DEBUG_STATS void unload() { std::cout << "MidiEvent alloc: " << curious_alloc_base::max_utilization() << " " << curious_alloc_base::fallback_count() << std::endl; std::cout << "Engine: " << das::counted_objects::allocated() << " " << das::counted_objects::deallocated() << std::endl; std::cout << "Patch: " << das::counted_objects::allocated() << " " << das::counted_objects::deallocated() << std::endl; std::cout << "Patch::Module: " << das::counted_objects::allocated() << " " << das::counted_objects::deallocated() << std::endl; std::cout << "Units::Unit: " << das::counted_objects::allocated() << " " << das::counted_objects::deallocated() << std::endl; std::cout << "Units::UnitEx: " << das::counted_objects::allocated() << " " << das::counted_objects::deallocated() << std::endl; std::cout << "MidiEvent: " << das::counted_objects::allocated() << " " << das::counted_objects::deallocated() << std::endl; std::cout << "SysExData: " << das::counted_objects::allocated() << " " << das::counted_objects::deallocated() << std::endl; } #endif // ENABLE_DEBUG_STATS class EngineWrap : public Engine { public: EngineWrap(PyObject *self, std::string const & backend_name, std::string const & client_name, Backend::PortNameVector const & in_ports, Backend::PortNameVector const & out_ports, bool verbose) : Engine(backend_name, client_name, in_ports, out_ports, verbose) , _self(self) { } void scene_switch_callback(int scene, int subscene) { das::scoped_gil_lock gil; try { boost::python::call_method(_self, "scene_switch_callback", scene, subscene); } catch (boost::python::error_already_set &) { PyErr_Print(); } } private: PyObject *_self; }; BOOST_PYTHON_MODULE(_mididings) { namespace bp = boost::python; using bp::class_; using bp::bases; using bp::init; using bp::def; using bp::enum_; using boost::noncopyable; using namespace Units; PyEval_InitThreads(); // list of supported backends def("available_backends", &Backend::available, bp::return_value_policy()); // main engine class, derived from in python class_("Engine", init const &, std::vector const &, bool>()) .def("connect_ports", &Engine::connect_ports) .def("add_scene", &Engine::add_scene) .def("set_processing", &Engine::set_processing) .def("start", &Engine::start) .def("switch_scene", &Engine::switch_scene) .def("current_scene", &Engine::current_scene) .def("current_subscene", &Engine::current_subscene) .def("process_event", &Engine::process_event) .def("output_event", &Engine::output_event) .def("time", &Engine::time) ; // patch class, derived from in python { bp::scope patch_scope = class_("Patch", init()); class_("Module", bp::no_init); class_, noncopyable>("Chain", init()); class_, noncopyable>("Fork", init()); class_, noncopyable>("Single", init >()); class_, noncopyable>("Extended", init >()); } // midi event type enum enum_("MidiEventType") .value("NONE", MIDI_EVENT_NONE) .value("NOTEON", MIDI_EVENT_NOTEON) .value("NOTEOFF", MIDI_EVENT_NOTEOFF) .value("NOTE", MIDI_EVENT_NOTE) .value("CTRL", MIDI_EVENT_CTRL) .value("PITCHBEND", MIDI_EVENT_PITCHBEND) .value("AFTERTOUCH", MIDI_EVENT_AFTERTOUCH) .value("POLY_AFTERTOUCH", MIDI_EVENT_POLY_AFTERTOUCH) .value("PROGRAM", MIDI_EVENT_PROGRAM) .value("SYSEX", MIDI_EVENT_SYSEX) .value("SYSCM_QFRAME", MIDI_EVENT_SYSCM_QFRAME) .value("SYSCM_SONGPOS", MIDI_EVENT_SYSCM_SONGPOS) .value("SYSCM_SONGSEL", MIDI_EVENT_SYSCM_SONGSEL) .value("SYSCM_TUNEREQ", MIDI_EVENT_SYSCM_TUNEREQ) .value("SYSCM", MIDI_EVENT_SYSCM) .value("SYSRT_CLOCK", MIDI_EVENT_SYSRT_CLOCK) .value("SYSRT_START", MIDI_EVENT_SYSRT_START) .value("SYSRT_CONTINUE", MIDI_EVENT_SYSRT_CONTINUE) .value("SYSRT_STOP", MIDI_EVENT_SYSRT_STOP) .value("SYSRT_SENSING", MIDI_EVENT_SYSRT_SENSING) .value("SYSRT_RESET", MIDI_EVENT_SYSRT_RESET) .value("SYSRT", MIDI_EVENT_SYSRT) .value("SYSTEM", MIDI_EVENT_SYSTEM) .value("DUMMY", MIDI_EVENT_DUMMY) .value("ANY", MIDI_EVENT_ANY) ; // midi event class, derived from in python class_("MidiEvent") .def_readwrite("type_", &MidiEvent::type) .def_readwrite("port_", &MidiEvent::port) .def_readwrite("channel_", &MidiEvent::channel) .def_readwrite("data1", &MidiEvent::data1) .def_readwrite("data2", &MidiEvent::data2) .def_readwrite("sysex_", &MidiEvent::sysex) .def(bp::self == bp::self) .def(bp::self != bp::self) .enable_pickling() ; // unit base classes class_("Unit", bp::no_init); class_("UnitEx", bp::no_init); class_, noncopyable>("Filter", bp::no_init); // base class_, noncopyable>("Pass", init()); class_, noncopyable>("TypeFilter", init()); class_, noncopyable>("InvertedFilter", init, bool>()); // filters class_, noncopyable>("PortFilter", init const &>()); class_, noncopyable>("ChannelFilter", init const &>()); class_, noncopyable>("KeyFilter", init const &>()); class_, noncopyable>("VelocityFilter", init()); class_, noncopyable>("CtrlFilter", init const &>()); class_, noncopyable>("CtrlValueFilter", init()); class_, noncopyable>("ProgramFilter", init const &>()); class_, noncopyable>("SysExFilter", init()); // modifiers class_, noncopyable>("Port", init()); class_, noncopyable>("Channel", init()); class_, noncopyable>("Transpose", init()); class_, noncopyable>("Velocity", init()); class_, noncopyable>("VelocitySlope", init const &, std::vector const &, TransformMode>()); class_, noncopyable>("CtrlMap", init()); class_, noncopyable>("CtrlRange", init()); class_, noncopyable>("CtrlCurve", init()); class_, noncopyable>("PitchbendRange", init()); // generators class_, noncopyable>("Generator", init()); class_, noncopyable>("SysExGenerator", init()); // engine class_, noncopyable>("Sanitize", init<>()); class_, noncopyable>("SceneSwitch", init()); class_, noncopyable>("SubSceneSwitch", init()); // call class_, noncopyable>("Call", init()); enum_("TransformMode") .value("OFFSET", TRANSFORM_MODE_OFFSET) .value("MULTIPLY", TRANSFORM_MODE_MULTIPLY) .value("FIXED", TRANSFORM_MODE_FIXED) .value("GAMMA", TRANSFORM_MODE_GAMMA) .value("CURVE", TRANSFORM_MODE_CURVE) ; enum_("EventAttribute") .value("PORT", EVENT_ATTRIBUTE_PORT) .value("CHANNEL", EVENT_ATTRIBUTE_CHANNEL) .value("DATA1", EVENT_ATTRIBUTE_DATA1) .value("DATA2", EVENT_ATTRIBUTE_DATA2) .value("NOTE", EVENT_ATTRIBUTE_NOTE) .value("VELOCITY", EVENT_ATTRIBUTE_VELOCITY) .value("CTRL", EVENT_ATTRIBUTE_CTRL) .value("VALUE", EVENT_ATTRIBUTE_VALUE) .value("PROGRAM", EVENT_ATTRIBUTE_PROGRAM) ; // register to/from-python converters for various types das::register_vector_converters >(); das::register_vector_converters >(); das::register_vector_converters >(); das::register_vector_converters >(); das::register_vector_converters >(); #if PY_VERSION_HEX >= 0x02060000 das::register_shared_ptr_vector_bytearray_converters(); #else das::register_shared_ptr_vector_converters(); #endif das::register_map_converters(); #ifdef ENABLE_DEBUG_STATS std::atexit(unload); #endif } } // Mididings mididings-20120419~ds0/src/midi_event.hh0000644000175000017500000001240011737653460017656 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_MIDI_EVENT_HH #define MIDIDINGS_MIDI_EVENT_HH #include #include #include #include #include "util/counted_objects.hh" namespace Mididings { enum MidiEventTypeEnum { MIDI_EVENT_NONE = 0, MIDI_EVENT_NOTEON = 1 << 0, MIDI_EVENT_NOTEOFF = 1 << 1, MIDI_EVENT_NOTE = MIDI_EVENT_NOTEON | MIDI_EVENT_NOTEOFF, MIDI_EVENT_CTRL = 1 << 2, MIDI_EVENT_PITCHBEND = 1 << 3, MIDI_EVENT_AFTERTOUCH = 1 << 4, MIDI_EVENT_POLY_AFTERTOUCH = 1 << 5, MIDI_EVENT_PROGRAM = 1 << 6, MIDI_EVENT_SYSEX = 1 << 7, MIDI_EVENT_SYSCM_QFRAME = 1 << 8, MIDI_EVENT_SYSCM_SONGPOS = 1 << 9, MIDI_EVENT_SYSCM_SONGSEL = 1 << 10, MIDI_EVENT_SYSCM_TUNEREQ = 1 << 11, MIDI_EVENT_SYSCM = MIDI_EVENT_SYSCM_QFRAME | MIDI_EVENT_SYSCM_SONGPOS | MIDI_EVENT_SYSCM_SONGSEL | MIDI_EVENT_SYSCM_TUNEREQ, MIDI_EVENT_SYSRT_CLOCK = 1 << 12, MIDI_EVENT_SYSRT_START = 1 << 13, MIDI_EVENT_SYSRT_CONTINUE = 1 << 14, MIDI_EVENT_SYSRT_STOP = 1 << 15, MIDI_EVENT_SYSRT_SENSING = 1 << 16, MIDI_EVENT_SYSRT_RESET = 1 << 17, MIDI_EVENT_SYSRT = MIDI_EVENT_SYSRT_CLOCK | MIDI_EVENT_SYSRT_START | MIDI_EVENT_SYSRT_CONTINUE | MIDI_EVENT_SYSRT_STOP | MIDI_EVENT_SYSRT_SENSING | MIDI_EVENT_SYSRT_RESET, MIDI_EVENT_SYSTEM = MIDI_EVENT_SYSEX | MIDI_EVENT_SYSCM | MIDI_EVENT_SYSRT, MIDI_EVENT_DUMMY = 1 << 29, MIDI_EVENT_ANY = (1 << 30) - 1, }; typedef unsigned int MidiEventType; class SysExData : public std::vector , das::counted_objects { public: SysExData() : std::vector() { } SysExData(std::size_t n) : std::vector(n) { } template SysExData(InputIterator first, InputIterator last) : std::vector(first, last) { } SysExData(SysExData const & other) : std::vector(other) { } ~SysExData() { } SysExData & operator=(SysExData const & other) { std::vector::operator=(other); return *this; } }; typedef boost::shared_ptr SysExDataPtr; typedef boost::shared_ptr SysExDataConstPtr; struct MidiEvent : das::counted_objects { MidiEvent() : type(MIDI_EVENT_NONE) , port(0) , channel(0) , data1(0) , data2(0) , sysex() , frame(0) { } MidiEvent(MidiEvent const & other) : type(other.type) , port(other.port) , channel(other.channel) , data1(other.data1) , data2(other.data2) , sysex(other.sysex) , frame(other.frame) { } ~MidiEvent() { } MidiEvent & operator=(MidiEvent const & other) { type = other.type; port = other.port; channel = other.channel; data1 = other.data1; data2 = other.data2; sysex = other.sysex; frame = other.frame; return *this; } MidiEventType type; int port; int channel; union { struct { int note; int velocity; } note; struct { int param; int value; } ctrl; struct { int data1; int data2; }; }; SysExDataConstPtr sysex; uint64_t frame; }; inline bool operator==(MidiEvent const & lhs, MidiEvent const & rhs) { // the obvious case: events of different types are never equal if (lhs.type != rhs.type) { return false; } // check which fields are relevant for the given event type bool channel = !(lhs.type & (MIDI_EVENT_SYSTEM | MIDI_EVENT_DUMMY)); bool data1 = (lhs.type & (MIDI_EVENT_NOTE | MIDI_EVENT_CTRL | MIDI_EVENT_POLY_AFTERTOUCH | MIDI_EVENT_SYSCM_QFRAME | MIDI_EVENT_SYSCM_SONGPOS | MIDI_EVENT_SYSCM_SONGSEL)); bool data2 = (lhs.type & (MIDI_EVENT_NOTE | MIDI_EVENT_CTRL | MIDI_EVENT_PITCHBEND | MIDI_EVENT_AFTERTOUCH | MIDI_EVENT_POLY_AFTERTOUCH | MIDI_EVENT_PROGRAM | MIDI_EVENT_SYSCM_SONGPOS)); bool sysex = (lhs.type & MIDI_EVENT_SYSEX); // return true if each field is either irrelevant or identical return ( lhs.port == rhs.port && (!channel || lhs.channel == rhs.channel) && (!data1 || lhs.data1 == rhs.data1) && (!data2 || lhs.data2 == rhs.data2) && (!sysex || (lhs.sysex && rhs.sysex && *lhs.sysex == *rhs.sysex)) && lhs.frame == rhs.frame ); } inline bool operator!=(MidiEvent const & lhs, MidiEvent const & rhs) { return !(lhs == rhs); } } // Mididings #endif // MIDIDINGS_MIDI_EVENT_HH mididings-20120419~ds0/src/engine.hh0000644000175000017500000000763111742030415016773 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_ENGINE_HH #define MIDIDINGS_ENGINE_HH #include "patch.hh" #include "backend/base.hh" #include "python_caller.hh" #include #include #include #include #include #include #include #include #include "util/counted_objects.hh" namespace Mididings { class Engine : boost::noncopyable , das::counted_objects { public: typedef boost::shared_ptr PatchPtr; struct Scene { Scene(PatchPtr patch_, PatchPtr init_patch_) : patch(patch_), init_patch(init_patch_) { } PatchPtr patch; PatchPtr init_patch; }; typedef boost::shared_ptr ScenePtr; typedef std::map > SceneMap; typedef unsigned int EventKey; typedef std::tr1::unordered_map NotePatchMap; typedef std::tr1::unordered_map SustainPatchMap; Engine(std::string const & backend_name, std::string const & client_name, Backend::PortNameVector const & in_ports, Backend::PortNameVector const & out_ports, bool verbose); virtual ~Engine(); void connect_ports(Backend::PortConnectionMap const & in_port_connections, Backend::PortConnectionMap const & out_port_connections); void add_scene(int i, PatchPtr patch, PatchPtr init_patch); void set_processing(PatchPtr ctrl_patch, PatchPtr pre_patch, PatchPtr post_patch); void start(int initial_scene, int initial_subscene); void switch_scene(int scene, int subscene = -1); bool sanitize_event(MidiEvent & ev) const; int current_scene() const { return _current_scene; } int current_subscene() const { return _current_subscene; } bool has_scene(int n) const { return _scenes.find(n) != _scenes.end(); } bool has_subscene(int n) const { return num_subscenes() > n; } int num_subscenes() const { SceneMap::const_iterator i = _scenes.find(_current_scene); return i != _scenes.end() ? i->second.size() : 0; } std::vector process_event(MidiEvent const & ev); void output_event(MidiEvent const & ev); double time(); PythonCaller & python_caller() const { return *_python_caller; } protected: virtual void scene_switch_callback(int scene, int subscene) = 0; private: void run_init(int initial_scene, int initial_subscene); void run_cycle(); void run_async(); template void process(B & buffer, MidiEvent const & ev); template void process_scene_switch(B & buffer); Patch * get_matching_patch(MidiEvent const & ev); EventKey make_notekey(MidiEvent const & ev) const { return ev.port | ev.channel << 16 | ev.note.note << 24; } EventKey make_sustainkey(MidiEvent const & ev) const { return ev.port | ev.channel << 16; } bool _verbose; boost::shared_ptr _backend; SceneMap _scenes; PatchPtr _ctrl_patch; PatchPtr _pre_patch; PatchPtr _post_patch; PatchPtr _sanitize_patch; Patch * _current_patch; int _current_scene; int _current_subscene; int _new_scene; int _new_subscene; NotePatchMap _noteon_patches; SustainPatchMap _sustain_patches; Patch::EventBufferRT _buffer; boost::mutex _process_mutex; boost::scoped_ptr _python_caller; }; } // Mididings #endif // MIDIDINGS_ENGINE_HH mididings-20120419~ds0/src/python_caller.hh0000644000175000017500000000321411736210125020363 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_PYTHON_CALLER_HH #define MIDIDINGS_PYTHON_CALLER_HH #include "midi_event.hh" #include "patch.hh" #include #include #include #include #include #include #include "util/ringbuffer.hh" namespace Mididings { class PythonCaller : boost::noncopyable { public: typedef boost::function EngineCallback; PythonCaller(EngineCallback engine_callback); ~PythonCaller(); // calls python function immediately template typename B::Range call_now(B & buf, typename B::Iterator it, boost::python::object const & fun); // queues python function to be called asynchronously template typename B::Range call_deferred(B & buf, typename B::Iterator it, boost::python::object const & fun, bool keep); private: void async_thread(); struct AsyncCallInfo { boost::python::object const * fun; MidiEvent ev; }; boost::scoped_ptr > _rb; boost::scoped_ptr _thread; EngineCallback _engine_callback; boost::condition _cond; volatile bool _quit; }; } // Mididings #endif // MIDIDINGS_PYTHON_CALLER_HH mididings-20120419~ds0/src/backend/0000755000175000017500000000000000007345256016571 5ustar alessioalessiomididings-20120419~ds0/src/backend/jack_realtime.cc0000644000175000017500000000415100011227266021666 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include "config.hh" #include "backend/jack_realtime.hh" #include #include #include "util/debug.hh" namespace Mididings { namespace Backend { JACKRealtimeBackend::JACKRealtimeBackend(std::string const & client_name, PortNameVector const & in_port_names, PortNameVector const & out_port_names) : JACKBackend(client_name, in_port_names, out_port_names) , _out_rb(Config::MAX_JACK_EVENTS) { } void JACKRealtimeBackend::start(InitFunction init, CycleFunction cycle) { _run_init = init; _run_cycle = cycle; } int JACKRealtimeBackend::process(jack_nframes_t nframes) { _nframes = nframes; clear_buffers(nframes); if (_run_init) { _run_init(); _run_init.clear(); // RT-safe? } // write events from ringbuffer to JACK output buffers while (_out_rb.read_space()) { MidiEvent ev; _out_rb.read(ev); if (!write_event(ev, nframes)) { DEBUG_PRINT("couldn't write event from ringbuffer to output buffer"); } } if (_run_cycle) { _run_cycle(); } return 0; } bool JACKRealtimeBackend::input_event(MidiEvent & ev) { return read_event(ev, _nframes); } void JACKRealtimeBackend::output_event(MidiEvent const & ev) { if (pthread_self() == jack_client_thread_id(_client)) { // called within process(), write directly to output buffer if (!write_event(ev, _nframes)) { DEBUG_PRINT("couldn't write event to output buffer"); } } else { // called elsewhere, write to ringbuffer if (!_out_rb.write(ev)) { DEBUG_PRINT("couldn't write event to output ringbuffer"); } } } } // Backend } // Mididings mididings-20120419~ds0/src/backend/base.cc0000644000175000017500000001726111737173407020032 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include "config.hh" #include "backend/base.hh" #ifdef ENABLE_ALSA_SEQ #include "backend/alsa.hh" #endif #ifdef ENABLE_JACK_MIDI #include "backend/jack_buffered.hh" #include "backend/jack_realtime.hh" #endif #ifdef ENABLE_SMF #include "backend/smf.hh" #endif #include namespace Mididings { namespace Backend { namespace { std::vector AVAILABLE; bool init_available() { #ifdef ENABLE_ALSA_SEQ AVAILABLE.push_back("alsa"); #endif #ifdef ENABLE_JACK_MIDI AVAILABLE.push_back("jack"); AVAILABLE.push_back("jack-rt"); #endif return false; } bool throwaway = init_available(); } std::vector const & available() { return AVAILABLE; } boost::shared_ptr create(std::string const & backend_name, std::string const & client_name, PortNameVector const & in_ports, PortNameVector const & out_ports) { if (backend_name == "dummy") { // return empty shared pointer return boost::shared_ptr(); } #ifdef ENABLE_ALSA_SEQ else if (backend_name == "alsa") { return boost::shared_ptr(new ALSABackend(client_name, in_ports, out_ports)); } #endif #ifdef ENABLE_JACK_MIDI else if (backend_name == "jack") { return boost::shared_ptr(new JACKBufferedBackend(client_name, in_ports, out_ports)); } else if (backend_name == "jack-rt") { return boost::shared_ptr(new JACKRealtimeBackend(client_name, in_ports, out_ports)); } #endif #ifdef ENABLE_SMF else if (backend_name == "smf") { return boost::shared_ptr(new SMFBackend(in_ports[0], out_ports[0])); } #endif else { throw Error("invalid backend selected: " + backend_name); } } MidiEvent BackendBase::buffer_to_midi_event(unsigned char *data, std::size_t len, int port, uint64_t frame) { MidiEvent ev; ev.frame = frame; ev.port = port; if ((data[0] & 0xf0) != 0xf0) { ev.channel = data[0] & 0x0f; switch (data[0] & 0xf0) { case 0x90: ev.type = data[2] ? MIDI_EVENT_NOTEON : MIDI_EVENT_NOTEOFF; ev.note.note = data[1]; ev.note.velocity = data[2]; break; case 0x80: ev.type = MIDI_EVENT_NOTEOFF; ev.note.note = data[1]; ev.note.velocity = data[2]; break; case 0xb0: ev.type = MIDI_EVENT_CTRL; ev.ctrl.param = data[1]; ev.ctrl.value = data[2]; break; case 0xe0: ev.type = MIDI_EVENT_PITCHBEND; ev.ctrl.param = 0; ev.ctrl.value = (data[2] << 7 | data[1]) - 8192; break; case 0xd0: ev.type = MIDI_EVENT_AFTERTOUCH; ev.ctrl.param = 0; ev.ctrl.value = data[1]; break; case 0xa0: ev.type = MIDI_EVENT_POLY_AFTERTOUCH; ev.ctrl.param = data[1]; ev.ctrl.value = data[2]; break; case 0xc0: ev.type = MIDI_EVENT_PROGRAM; ev.ctrl.param = 0; ev.ctrl.value = data[1]; break; default: ev.type = MIDI_EVENT_NONE; break; } } else { ev.channel = 0; switch (data[0]) { case 0xf0: ev.type = MIDI_EVENT_SYSEX; // FIXME: come up with a realtime-safe way to do this ev.sysex.reset(new SysExData(data, data + len)); break; case 0xf1: ev.type = MIDI_EVENT_SYSCM_QFRAME; ev.data1 = data[1]; break; case 0xf2: ev.type = MIDI_EVENT_SYSCM_SONGPOS; ev.data1 = data[1]; ev.data2 = data[2]; break; case 0xf3: ev.type = MIDI_EVENT_SYSCM_SONGSEL; ev.data1 = data[1]; break; case 0xf6: ev.type = MIDI_EVENT_SYSCM_TUNEREQ; break; case 0xf8: ev.type = MIDI_EVENT_SYSRT_CLOCK; break; case 0xfa: ev.type = MIDI_EVENT_SYSRT_START; break; case 0xfb: ev.type = MIDI_EVENT_SYSRT_CONTINUE; break; case 0xfc: ev.type = MIDI_EVENT_SYSRT_STOP; break; case 0xfe: ev.type = MIDI_EVENT_SYSRT_SENSING; break; case 0xff: ev.type = MIDI_EVENT_SYSRT_RESET; break; default: ev.type = MIDI_EVENT_NONE; break; } } return ev; } std::size_t BackendBase::midi_event_to_buffer(MidiEvent const & ev, unsigned char *data, std::size_t & len, int & port, uint64_t & frame) { frame = ev.frame; port = ev.port; data[0] = ev.channel; switch (ev.type) { case MIDI_EVENT_NOTEON: len = 3; data[0] |= 0x90; data[1] = ev.note.note; data[2] = ev.note.velocity; break; case MIDI_EVENT_NOTEOFF: len = 3; data[0] |= 0x80; data[1] = ev.note.note; data[2] = ev.note.velocity; break; case MIDI_EVENT_CTRL: len = 3; data[0] |= 0xb0; data[1] = ev.ctrl.param; data[2] = ev.ctrl.value; break; case MIDI_EVENT_PITCHBEND: len = 3; data[0] |= 0xe0; data[1] = (ev.ctrl.value + 8192) % 128; data[2] = (ev.ctrl.value + 8192) / 128; break; case MIDI_EVENT_AFTERTOUCH: len = 2; data[0] |= 0xd0; data[1] = ev.ctrl.value; break; case MIDI_EVENT_POLY_AFTERTOUCH: len = 3; data[0] |= 0xa0; data[1] = ev.ctrl.param; data[2] = ev.ctrl.value; break; case MIDI_EVENT_PROGRAM: len = 2; data[0] |= 0xc0; data[1] = ev.ctrl.value; break; case MIDI_EVENT_SYSEX: if (ev.sysex->size() <= len) { len = ev.sysex->size(); std::copy(ev.sysex->begin(), ev.sysex->end(), data); } else { // sysex too long, drop it len = 0; } break; case MIDI_EVENT_SYSCM_QFRAME: len = 2; data[0] = 0xf1; data[1] = ev.data1; break; case MIDI_EVENT_SYSCM_SONGPOS: len = 3; data[0] = 0xf2; data[1] = ev.data1; data[2] = ev.data2; break; case MIDI_EVENT_SYSCM_SONGSEL: len = 2; data[0] = 0xf3; data[1] = ev.data1; break; case MIDI_EVENT_SYSCM_TUNEREQ: len = 1; data[0] = 0xf6; break; case MIDI_EVENT_SYSRT_CLOCK: len = 1; data[0] = 0xf8; break; case MIDI_EVENT_SYSRT_START: len = 1; data[0] = 0xfa; break; case MIDI_EVENT_SYSRT_CONTINUE: len = 1; data[0] = 0xfb; break; case MIDI_EVENT_SYSRT_STOP: len = 1; data[0] = 0xfc; break; case MIDI_EVENT_SYSRT_SENSING: len = 1; data[0] = 0xfe; break; case MIDI_EVENT_SYSRT_RESET: len = 1; data[0] = 0xff; break; default: len = 0; } return len; } } // Backend } // Mididings mididings-20120419~ds0/src/backend/jack_buffered.hh0000644000175000017500000000303311731520044021657 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_BACKEND_JACK_BUFFERED_HH #define MIDIDINGS_BACKEND_JACK_BUFFERED_HH #include "backend/jack.hh" #include #include #include #include #include "util/ringbuffer.hh" namespace Mididings { namespace Backend { /* * buffered JACK backend. * all events are written to a ringbuffer and processed in a separate thread. */ class JACKBufferedBackend : public JACKBackend { public: JACKBufferedBackend(std::string const & client_name, PortNameVector const & in_port_names, PortNameVector const & out_port_names); virtual void start(InitFunction init, CycleFunction cycle); virtual void stop(); virtual bool input_event(MidiEvent & ev); virtual void output_event(MidiEvent const & ev); private: virtual int process(jack_nframes_t); das::ringbuffer _in_rb; das::ringbuffer _out_rb; boost::scoped_ptr _thread; boost::condition _cond; boost::mutex _mutex; volatile bool _quit; }; } // Backend } // Mididings #endif // MIDIDINGS_BACKEND_JACK_BUFFERED_HH mididings-20120419~ds0/src/backend/jack_buffered.cc0000644000175000017500000000577511731520045021665 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include "config.hh" #include "backend/jack_buffered.hh" #include #include #include #include #include "util/debug.hh" namespace Mididings { namespace Backend { JACKBufferedBackend::JACKBufferedBackend(std::string const & client_name, PortNameVector const & in_port_names, PortNameVector const & out_port_names) : JACKBackend(client_name, in_port_names, out_port_names) , _in_rb(Config::MAX_JACK_EVENTS) , _out_rb(Config::MAX_JACK_EVENTS) , _quit(false) { } void JACKBufferedBackend::stop() { if (_thread) { _quit = true; _cond.notify_one(); _thread->join(); } } void JACKBufferedBackend::start(InitFunction init, CycleFunction cycle) { // clear input event buffer _in_rb.reset(); // start processing thread _thread.reset(new boost::thread(( boost::lambda::bind(init), boost::lambda::bind(cycle) ))); // can't get native posix thread handle before boost 1.37.0 #if BOOST_VERSION >= 103700 // try to use realtime scheduling for MIDI processing thread int jack_rtprio = jack_client_real_time_priority(_client); if (jack_rtprio != -1) { int rtprio = jack_rtprio - Config::JACK_BUFFERED_RTPRIO_OFFSET; jack_acquire_real_time_scheduling(_thread->native_handle(), rtprio); } #endif } int JACKBufferedBackend::process(jack_nframes_t nframes) { MidiEvent ev; // store all incoming events in the input ringbuffer while (read_event(ev, nframes)) { if (!_in_rb.write(ev)) { DEBUG_PRINT("couldn't write event to input ringbuffer"); } _cond.notify_one(); } // clear all JACK output buffers clear_buffers(nframes); // read all events from output ringbuffer, write them to JACK output buffers while (_out_rb.read_space()) { _out_rb.read(ev); if (!write_event(ev, nframes)) { DEBUG_PRINT("couldn't write event to output buffer"); } } return 0; } bool JACKBufferedBackend::input_event(MidiEvent & ev) { // wait until there are events to be read from the ringbuffer while (!_in_rb.read_space()) { boost::mutex::scoped_lock lock(_mutex); _cond.wait(lock); // check for program termination if (_quit) { return false; } } VERIFY(_in_rb.read(ev)); return true; } void JACKBufferedBackend::output_event(MidiEvent const & ev) { if (!_out_rb.write(ev)) { DEBUG_PRINT("couldn't write event to output ringbuffer"); } } } // Backend } // Mididings mididings-20120419~ds0/src/backend/smf.cc0000644000175000017500000000434500007345256017673 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include "config.hh" #include "backend/smf.hh" namespace Mididings { namespace Backend { SMFBackend::SMFBackend(std::string const & infile, std::string const & outfile) : _outfile(outfile) { smf_t *smf_in = smf_load(infile.c_str()); if (!smf_in) { throw Error("couldn't load input file"); } _smf_in.reset(smf_in, smf_delete); _smf_out.reset(smf_new(), smf_delete); if (smf_set_ppqn(_smf_out.get(), _smf_in->ppqn)) { // can't happen } for (int n = 1; n <= _smf_in->number_of_tracks; ++n) { smf_add_track(_smf_out.get(), smf_track_new()); } } void SMFBackend::start(InitFunction init, CycleFunction cycle) { init(); cycle(); if (smf_save(_smf_out.get(), _outfile.c_str())) { throw Error("couldn't save output file"); } } bool SMFBackend::input_event(MidiEvent & ev) { while (true) { smf_event_t *smf_ev = smf_get_next_event(_smf_in.get()); if (!smf_ev) { return false; } else if (smf_event_is_metadata(smf_ev)) { // yuck smf_event_t *smf_ev_out = smf_event_new_from_pointer(smf_ev->midi_buffer, smf_ev->midi_buffer_length); smf_track_add_event_pulses(smf_get_track_by_number(_smf_out.get(), smf_ev->track_number), smf_ev_out, smf_ev->time_pulses); } else { ev = buffer_to_midi_event(smf_ev->midi_buffer, smf_ev->midi_buffer_length, smf_ev->track_number - 1, smf_ev->time_pulses); return true; } } } void SMFBackend::output_event(MidiEvent const & ev) { unsigned char data[3]; std::size_t len; int track; uint64_t pulses; midi_event_to_buffer(ev, data, len, track, pulses); smf_event_t *smf_ev = smf_event_new_from_pointer(data, len); smf_track_add_event_pulses(smf_get_track_by_number(_smf_out.get(), track + 1), smf_ev, pulses); } } // Backend } // Mididings mididings-20120419~ds0/src/backend/base.hh0000644000175000017500000000547500011032211020012 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_BACKEND_BASE_HH #define MIDIDINGS_BACKEND_BASE_HH #include #include #include #include #include #include #include #include "midi_event.hh" namespace Mididings { namespace Backend { struct Error : public std::runtime_error { Error(std::string const & w) : std::runtime_error(w) { } }; typedef std::vector PortNameVector; typedef std::map > PortConnectionMap; std::vector const & available(); boost::shared_ptr create(std::string const & backend_name, std::string const & client_name, PortNameVector const & in_ports, PortNameVector const & out_ports); class BackendBase : boost::noncopyable { public: typedef boost::function InitFunction; typedef boost::function CycleFunction; BackendBase() { } virtual ~BackendBase() { } virtual void connect_ports(PortConnectionMap const &, PortConnectionMap const &) { } // start MIDI processing, run init function. // depending on the backend, cycle may be called once (and not return) or periodically. virtual void start(InitFunction init, CycleFunction cycle) = 0; // stop MIDI processing. virtual void stop() = 0; // get one event from input, return true if an event was read. // depending on the backend, this may block until an event is available. virtual bool input_event(MidiEvent & ev) = 0; // send one event to the output. virtual void output_event(MidiEvent const & ev) = 0; // send multiple events to the output. template void output_events(IterT begin, IterT end) { for (IterT it = begin; it != end; ++it) { output_event(*it); } } // return the number of output ports virtual std::size_t num_out_ports() const = 0; protected: // convert normalized MIDI data to one MidiEvent static MidiEvent buffer_to_midi_event(unsigned char *data, std::size_t len, int port, uint64_t frame); // convert MidiEvent object to normalized MIDI data static std::size_t midi_event_to_buffer(MidiEvent const & ev, unsigned char *data, std::size_t & len, int & port, uint64_t & frame); }; } // Backend } // Mididings #endif // MIDIDINGS_BACKEND_BASE_HH mididings-20120419~ds0/src/backend/smf.hh0000644000175000017500000000222100007345256017674 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_BACKEND_SMF_HH #define MIDIDINGS_BACKEND_SMF_HH #include "backend/base.hh" #include "midi_event.hh" #include #include #include namespace Mididings { namespace Backend { class SMFBackend : public BackendBase { public: SMFBackend(std::string const & infile, std::string const & outfile); virtual void start(InitFunction init, CycleFunction cycle); virtual void stop() { } virtual bool input_event(MidiEvent & ev); virtual void output_event(MidiEvent const & ev); virtual std::size_t num_out_ports() const { return _smf_in->number_of_tracks; } private: boost::shared_ptr _smf_in; boost::shared_ptr _smf_out; std::string _outfile; }; } // Backend } // Mididings #endif // MIDIDINGS_BACKEND_SMF_HH mididings-20120419~ds0/src/backend/jack.cc0000644000175000017500000001643200011032211017771 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include "config.hh" #include "backend/jack.hh" #include #include #include #include #include "util/string.hh" #include "util/debug.hh" namespace Mididings { namespace Backend { JACKBackend::JACKBackend(std::string const & client_name, PortNameVector const & in_port_names, PortNameVector const & out_port_names) : _current_frame(0) { ASSERT(!client_name.empty()); ASSERT(!in_port_names.empty()); ASSERT(!out_port_names.empty()); // create JACK client if ((_client = jack_client_open(client_name.c_str(), JackNullOption, NULL)) == 0) { throw Error("can't connect to jack server"); } jack_set_process_callback(_client, &process_, static_cast(this)); // create input ports BOOST_FOREACH (std::string const & port_name, in_port_names) { jack_port_t *p = jack_port_register(_client, port_name.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); if (p == NULL) { throw Error("error creating input port"); } _in_ports.push_back(p); } // create output ports BOOST_FOREACH (std::string const & port_name, out_port_names) { jack_port_t *p = jack_port_register(_client, port_name.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); if (p == NULL) { throw Error("error creating output port"); } _out_ports.push_back(p); } if (jack_activate(_client)) { throw Error("can't activate client"); } } JACKBackend::~JACKBackend() { jack_deactivate(_client); jack_client_close(_client); } void JACKBackend::connect_ports(PortConnectionMap const & in_port_connections, PortConnectionMap const & out_port_connections) { connect_ports_impl(in_port_connections, _in_ports, false); connect_ports_impl(out_port_connections, _out_ports, true); } void JACKBackend::connect_ports_impl(PortConnectionMap const & port_connections, std::vector const & ports, bool out) { if (port_connections.empty()) return; // get all JACK MIDI ports we could connect to char const **external_ports_array = jack_get_ports(_client, NULL, JACK_DEFAULT_MIDI_TYPE, out ? JackPortIsInput : JackPortIsOutput); if (!external_ports_array) return; // find end of array char const **end = external_ports_array; while (*end != NULL) ++end; // convert char* array to vector of strings PortNameVector external_ports(external_ports_array, end); jack_free(external_ports_array); // for each of our ports... BOOST_FOREACH (jack_port_t * port, ports) { std::string short_name = jack_port_short_name(port); std::string port_name = jack_port_name(port); PortConnectionMap::const_iterator element = port_connections.find(short_name); // break if no connections are defined for this port if (element == port_connections.end()) break; // for each regex pattern defined for this port... BOOST_FOREACH (std::string const & pattern, element->second) { // connect to all ports that match the pattern if (connect_matching_ports(port_name, pattern, external_ports, out) == 0) { std::cerr << "regular expression '" << pattern << "' didn't match any ports" << std::endl; } } } } int JACKBackend::connect_matching_ports(std::string const & port_name, std::string const & pattern, PortNameVector const & external_ports, bool out) { try { // compile pattern into regex object das::regex regex(pattern, true); int count = 0; // for each external JACK MIDI port we might connect to... BOOST_FOREACH (std::string const & external_port, external_ports) { // check if port name matches regex if (regex.match(external_port)) { // connect output to input port std::string const & output_port = out ? port_name : external_port; std::string const & input_port = out ? external_port : port_name; int error = jack_connect(_client, output_port.c_str(), input_port.c_str()); if (error && error != EEXIST) { std::cerr << "could not connect " << output_port << " to " << input_port << std::endl; } ++count; } } return count; } catch (das::regex::compile_error & ex) { throw std::runtime_error(das::make_string() << "failed to parse regular expression '" << pattern << "': " << ex.what()); } } int JACKBackend::process_(jack_nframes_t nframes, void *arg) { JACKBackend *this_ = static_cast(arg); this_->_input_port = 0; this_->_input_count = 0; int r = this_->process(nframes); this_->_current_frame += nframes; return r; } void JACKBackend::clear_buffers(jack_nframes_t nframes) { for (int n = 0; n < static_cast(_out_ports.size()); ++n) { void *port_buffer = jack_port_get_buffer(_out_ports[n], nframes); jack_midi_clear_buffer(port_buffer); } } bool JACKBackend::read_event(MidiEvent & ev, jack_nframes_t nframes) { while (_input_port < static_cast(_in_ports.size())) { void *port_buffer = jack_port_get_buffer(_in_ports[_input_port], nframes); int num_events = jack_midi_get_event_count(port_buffer); if (_input_count < num_events) { jack_midi_event_t jack_ev; VERIFY(!jack_midi_event_get(&jack_ev, port_buffer, _input_count)); //std::cout << "in: " << jack_ev.time << std::endl; ev = buffer_to_midi_event(jack_ev.buffer, jack_ev.size, _input_port, _current_frame + jack_ev.time); if (++_input_count >= num_events) { ++_input_port; _input_count = 0; } return true; } ++_input_port; } return false; } bool JACKBackend::write_event(MidiEvent const & ev, jack_nframes_t nframes) { unsigned char data[Config::MAX_JACK_EVENT_SIZE]; std::size_t len = sizeof(data); int port; uint64_t frame; VERIFY(midi_event_to_buffer(ev, data, len, port, frame)); if (!len) { return false; } void *port_buffer = jack_port_get_buffer(_out_ports[port], nframes); jack_nframes_t f; if (frame >= _current_frame) { // event received within current period, zero delay f = frame - _current_frame; } else if (frame >= _current_frame - nframes) { // event received during last period, exactly one period delay (minimize jitter) f = frame - _current_frame + nframes; } else { // event is older, send as soon as possible (minimize latency) f = 0; } //std::cout << "out: " << f << std::endl; if (!jack_midi_event_write(port_buffer, f, data, len)) { return true; } else { return false; } } } // Backend } // Mididings mididings-20120419~ds0/src/backend/alsa.hh0000644000175000017500000000625311737173353020051 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_BACKEND_ALSA_HH #define MIDIDINGS_BACKEND_ALSA_HH #include "backend/base.hh" #include #include #include #include #include #include namespace Mididings { namespace Backend { class ALSABackend : public BackendBase { public: ALSABackend(std::string const & client_name, PortNameVector const & in_port_names, PortNameVector const & out_port_names); virtual ~ALSABackend(); virtual void start(InitFunction init, CycleFunction cycle); virtual void stop(); virtual bool input_event(MidiEvent & ev); virtual void output_event(MidiEvent const & ev); virtual std::size_t num_out_ports() const { return _out_ports.size(); } virtual void connect_ports(PortConnectionMap const & in_port_connections, PortConnectionMap const & out_port_connections); private: struct ClientPortInfo { ClientPortInfo(int client_id_, int port_id_, std::string const & client_name_, std::string const & port_name_) : client_id(client_id_), port_id(port_id_), client_name(client_name_), port_name(port_name_) { } int client_id; int port_id; std::string client_name; std::string port_name; }; typedef std::vector ClientPortInfoVector; typedef std::vector PortIdVector; typedef std::map RevPortIdMap; void connect_ports_impl(PortConnectionMap const & port_connections, PortIdVector const & ports, bool out); int connect_matching_ports(int port, std::string const & port_name, std::string const & pattern, ClientPortInfoVector const & external_ports, bool out); ClientPortInfoVector get_external_ports(bool out); void alsa_to_midi_event(MidiEvent & ev, snd_seq_event_t const & alsa_ev); void alsa_to_midi_event_sysex(MidiEvent & ev, snd_seq_event_t const & alsa_ev); void alsa_to_midi_event_generic(MidiEvent & ev, snd_seq_event_t const & alsa_ev); void midi_event_to_alsa(snd_seq_event_t & alsa_ev, MidiEvent const & ev, std::size_t & count); void midi_event_to_alsa_sysex(snd_seq_event_t & alsa_ev, MidiEvent const & ev, std::size_t & count); void midi_event_to_alsa_generic(snd_seq_event_t & alsa_ev, MidiEvent const & ev); snd_seq_t *_seq; PortIdVector _in_ports; // alsa input port IDs RevPortIdMap _in_ports_rev; // reverse mapping (input port ID -> port #) PortIdVector _out_ports; // alsa output port IDs snd_midi_event_t *_parser; // per-port buffers of incoming sysex data std::map _sysex_buffer; boost::scoped_ptr _thread; }; } // Backend } // Mididings #endif // MIDIDINGS_BACKEND_ALSA_HH mididings-20120419~ds0/src/backend/alsa.cc0000644000175000017500000004107511737173376020045 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include "config.hh" #include "backend/alsa.hh" #include "midi_event.hh" #include #include #include #include #include #include #include #include "util/string.hh" #include "util/debug.hh" namespace Mididings { namespace Backend { ALSABackend::ALSABackend(std::string const & client_name, PortNameVector const & in_port_names, PortNameVector const & out_port_names) { ASSERT(!client_name.empty()); ASSERT(!in_port_names.empty()); ASSERT(!out_port_names.empty()); // create sequencer client if (snd_seq_open(&_seq, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) { throw Error("error opening alsa sequencer"); } snd_seq_set_client_name(_seq, client_name.c_str()); // create input ports BOOST_FOREACH (std::string const & port_name, in_port_names) { int id = snd_seq_create_simple_port(_seq, port_name.c_str(), SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_APPLICATION); if (id < 0) { throw Error("error creating sequencer input port"); } int index = &port_name - &in_port_names[0]; _in_ports.push_back(id); _in_ports_rev[id] = index; } // create output ports BOOST_FOREACH (std::string const & port_name, out_port_names) { int id = snd_seq_create_simple_port(_seq, port_name.c_str(), SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_APPLICATION); if (id < 0) { throw Error("error creating sequencer output port"); } _out_ports.push_back(id); } // initialize MIDI event parser. // we don't use the parser for sysex, so a 12 byte buffer will do if (snd_midi_event_new(12, &_parser)) { throw Error("error initializing MIDI event parser"); } snd_midi_event_init(_parser); snd_midi_event_no_status(_parser, 1); } ALSABackend::~ALSABackend() { snd_midi_event_free(_parser); BOOST_FOREACH (int i, _in_ports) { snd_seq_delete_port(_seq, i); } BOOST_FOREACH (int i, _out_ports) { snd_seq_delete_port(_seq, i); } snd_seq_close(_seq); } void ALSABackend::connect_ports(PortConnectionMap const & in_port_connections, PortConnectionMap const & out_port_connections) { connect_ports_impl(in_port_connections, _in_ports, false); connect_ports_impl(out_port_connections, _out_ports, true); } void ALSABackend::connect_ports_impl(PortConnectionMap const & port_connections, PortIdVector const & ports, bool out) { if (port_connections.empty()) return; // get all JACK MIDI ports we could connect to ClientPortInfoVector external_ports = get_external_ports(!out); // for each of our ports... BOOST_FOREACH (int port, ports) { snd_seq_port_info_t *port_info; snd_seq_port_info_alloca(&port_info); snd_seq_get_port_info(_seq, port, port_info); std::string port_name = snd_seq_port_info_get_name(port_info); PortConnectionMap::const_iterator element = port_connections.find(port_name); // break if no connections are defined for this port if (element == port_connections.end()) break; // for each regex pattern defined for this port... BOOST_FOREACH (std::string const & pattern, element->second) { // connect to all ports that match the pattern if (connect_matching_ports(port, port_name, pattern, external_ports, out) == 0) { std::cerr << "regular expression '" << pattern << "' didn't match any ports" << std::endl; } } } } int ALSABackend::connect_matching_ports(int port, std::string const & port_name, std::string const & pattern, ClientPortInfoVector const & external_ports, bool out) { snd_seq_client_info_t *client_info; snd_seq_client_info_alloca(&client_info); snd_seq_get_client_info(_seq, client_info); std::string client_name = snd_seq_client_info_get_name(client_info); int client_id = snd_seq_client_info_get_client(client_info); try { // compile pattern into regex object das::regex regex(pattern, true); int count = 0; // for each external ALSA MIDI port we might connect to... BOOST_FOREACH (ClientPortInfo const & external_port, external_ports) { std::string external_client_id = boost::lexical_cast(external_port.client_id); std::string external_port_id = boost::lexical_cast(external_port.port_id); // check if any combination of client/port and name/id matches regex if (regex.match(external_client_id + ":" + external_port_id) || regex.match(external_client_id + ":" + external_port.port_name) || regex.match(external_port.client_name + ":" + external_port_id) || regex.match(external_port.client_name + ":" + external_port.port_name)) { // connect ports snd_seq_addr_t self, other; self.client = client_id; self.port = port; other.client = external_port.client_id; other.port = external_port.port_id; snd_seq_port_subscribe_t *subscribe; snd_seq_port_subscribe_alloca(&subscribe); snd_seq_port_subscribe_set_sender(subscribe, out ? &self : &other); snd_seq_port_subscribe_set_dest(subscribe, out ? & other : &self); if (snd_seq_subscribe_port(_seq, subscribe) != 0 && snd_seq_get_port_subscription(_seq, subscribe) != 0) { std::string self_full = client_name + ":" + port_name; std::string other_full = external_port.client_name + ":" + external_port.port_name; std::cerr << "could not connect " << (out ? self_full : other_full) << " to " << (out ? other_full : self_full) << std::endl; } ++count; } } return count; } catch (das::regex::compile_error & ex) { throw std::runtime_error(das::make_string() << "failed to parse regular expression '" << pattern << "': " << ex.what()); } } ALSABackend::ClientPortInfoVector ALSABackend::get_external_ports(bool out) { unsigned int port_flags = out ? SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ : SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE; ClientPortInfoVector vec; snd_seq_client_info_t *client_info; snd_seq_client_info_alloca(&client_info); snd_seq_client_info_set_client(client_info, -1); while (snd_seq_query_next_client(_seq, client_info) == 0) { int client_id = snd_seq_client_info_get_client(client_info); std::string client_name = snd_seq_client_info_get_name(client_info); snd_seq_port_info_t *port_info; snd_seq_port_info_alloca(&port_info); snd_seq_port_info_set_client(port_info, client_id); snd_seq_port_info_set_port(port_info, -1); while (snd_seq_query_next_port(_seq, port_info) == 0) { unsigned int capability = snd_seq_port_info_get_capability(port_info); if (((capability & port_flags) == port_flags) && ((capability & SND_SEQ_PORT_CAP_NO_EXPORT) == 0)) { int port_id = snd_seq_port_info_get_port(port_info); std::string port_name = snd_seq_port_info_get_name(port_info); ClientPortInfo info(client_id, port_id, client_name, port_name); vec.push_back(info); } } } return vec; } void ALSABackend::start(InitFunction init, CycleFunction cycle) { // discard events which were received while processing wasn't ready snd_seq_drop_input(_seq); // start processing thread. // cycle doesn't return until the program is shut down _thread.reset(new boost::thread(( boost::lambda::bind(init), boost::lambda::bind(cycle) ))); } void ALSABackend::stop() { if (_thread) { // send event to ourselves to make snd_seq_event_input() return snd_seq_event_t ev; snd_seq_ev_clear(&ev); snd_seq_ev_set_direct(&ev); ev.type = SND_SEQ_EVENT_USR0; ev.source.port = _out_ports[0]; ev.dest.client = snd_seq_client_id(_seq); ev.dest.port = _in_ports[0]; snd_seq_event_output_direct(_seq, &ev); // wait for event processing thread to terminate _thread->join(); } } void ALSABackend::alsa_to_midi_event(MidiEvent & ev, snd_seq_event_t const & alsa_ev) { ev.port = _in_ports_rev[alsa_ev.dest.port]; switch (alsa_ev.type) { case SND_SEQ_EVENT_NOTEON: ev.type = alsa_ev.data.note.velocity ? MIDI_EVENT_NOTEON : MIDI_EVENT_NOTEOFF; ev.channel = alsa_ev.data.note.channel; ev.note.note = alsa_ev.data.note.note; ev.note.velocity = alsa_ev.data.note.velocity; break; case SND_SEQ_EVENT_NOTEOFF: ev.type = MIDI_EVENT_NOTEOFF; ev.channel = alsa_ev.data.note.channel; ev.note.note = alsa_ev.data.note.note; ev.note.velocity = alsa_ev.data.note.velocity; break; case SND_SEQ_EVENT_CONTROLLER: ev.type = MIDI_EVENT_CTRL; ev.channel = alsa_ev.data.control.channel; ev.ctrl.param = alsa_ev.data.control.param; ev.ctrl.value = alsa_ev.data.control.value; break; case SND_SEQ_EVENT_PITCHBEND: ev.type = MIDI_EVENT_PITCHBEND; ev.channel = alsa_ev.data.control.channel; ev.ctrl.param = 0; ev.ctrl.value = alsa_ev.data.control.value; break; case SND_SEQ_EVENT_CHANPRESS: ev.type = MIDI_EVENT_AFTERTOUCH; ev.channel = alsa_ev.data.control.channel; ev.ctrl.param = 0; ev.ctrl.value = alsa_ev.data.control.value; break; case SND_SEQ_EVENT_PGMCHANGE: ev.type = MIDI_EVENT_PROGRAM; ev.channel = alsa_ev.data.control.channel; ev.ctrl.param = 0; ev.ctrl.value = alsa_ev.data.control.value; break; case SND_SEQ_EVENT_SYSEX: alsa_to_midi_event_sysex(ev, alsa_ev); break; case SND_SEQ_EVENT_KEYPRESS: case SND_SEQ_EVENT_QFRAME: case SND_SEQ_EVENT_SONGPOS: case SND_SEQ_EVENT_SONGSEL: case SND_SEQ_EVENT_TUNE_REQUEST: case SND_SEQ_EVENT_CLOCK: case SND_SEQ_EVENT_START: case SND_SEQ_EVENT_CONTINUE: case SND_SEQ_EVENT_STOP: case SND_SEQ_EVENT_SENSING: case SND_SEQ_EVENT_RESET: // use generic decoder for other event types (because i'm lazy) alsa_to_midi_event_generic(ev, alsa_ev); break; default: // who the hell are you? ev.type = MIDI_EVENT_NONE; break; } } void ALSABackend::alsa_to_midi_event_sysex(MidiEvent & ev, snd_seq_event_t const & alsa_ev) { unsigned char *ptr = static_cast(alsa_ev.data.ext.ptr); std::size_t len = alsa_ev.data.ext.len; if (ptr[0] == 0xf0) { // new sysex started, insert into buffer _sysex_buffer.erase(ev.port); _sysex_buffer.insert(std::make_pair(ev.port, SysExDataPtr(new SysExData(ptr, ptr + len)))); } else if (_sysex_buffer.find(ev.port) != _sysex_buffer.end()) { // previous sysex continued, append to buffer _sysex_buffer[ev.port]->insert(_sysex_buffer[ev.port]->end(), ptr, ptr + len); } if (_sysex_buffer[ev.port]->back() == 0xf7) { // end of sysex, assign complete event ev.type = MIDI_EVENT_SYSEX; ev.channel = 0; ev.data1 = 0; ev.data2 = 0; ev.sysex = _sysex_buffer[ev.port]; // delete from buffer _sysex_buffer.erase(ev.port); } else { // sysex still incomplete ev.type = MIDI_EVENT_NONE; } } void ALSABackend::alsa_to_midi_event_generic(MidiEvent & ev, snd_seq_event_t const & alsa_ev) { unsigned char buf[12]; snd_midi_event_reset_decode(_parser); std::size_t len = snd_midi_event_decode(_parser, buf, sizeof(buf), &alsa_ev); ev = buffer_to_midi_event(buf, len, _in_ports_rev[alsa_ev.dest.port], 0); } void ALSABackend::midi_event_to_alsa(snd_seq_event_t & alsa_ev, MidiEvent const & ev, std::size_t & count) { ASSERT(ev.type != MIDI_EVENT_NONE); ASSERT((uint)ev.port < _out_ports.size()); if (ev.type != MIDI_EVENT_PITCHBEND) { ASSERT(ev.data1 >= 0x0 && ev.data1 <= 0x7f); ASSERT(ev.data2 >= 0x0 && ev.data2 <= 0x7f); } else { ASSERT(ev.data2 >= -8192 && ev.data2 <= 8191); } snd_seq_ev_clear(&alsa_ev); switch (ev.type) { case MIDI_EVENT_NOTEON: snd_seq_ev_set_noteon(&alsa_ev, ev.channel, ev.note.note, ev.note.velocity); break; case MIDI_EVENT_NOTEOFF: snd_seq_ev_set_noteoff(&alsa_ev, ev.channel, ev.note.note, ev.note.velocity); break; case MIDI_EVENT_CTRL: snd_seq_ev_set_controller(&alsa_ev, ev.channel, ev.ctrl.param, ev.ctrl.value); break; case MIDI_EVENT_PITCHBEND: snd_seq_ev_set_pitchbend(&alsa_ev, ev.channel, ev.ctrl.value); break; case MIDI_EVENT_AFTERTOUCH: snd_seq_ev_set_chanpress(&alsa_ev, ev.channel, ev.ctrl.value); break; case MIDI_EVENT_PROGRAM: snd_seq_ev_set_pgmchange(&alsa_ev, ev.channel, ev.ctrl.value); break; case MIDI_EVENT_SYSEX: midi_event_to_alsa_sysex(alsa_ev, ev, count); break; default: // use generic encoder for other event types midi_event_to_alsa_generic(alsa_ev, ev); break; } } void ALSABackend::midi_event_to_alsa_sysex(snd_seq_event_t & alsa_ev, MidiEvent const & ev, std::size_t & count) { unsigned char const * data = &ev.sysex->front(); std::size_t size = ev.sysex->size(); // number of bytes that will be sent in this chunk std::size_t len = std::min(size - count, Config::ALSA_SYSEX_CHUNK_SIZE); // let's hope the alsa guys just "forgot" that little const keyword... snd_seq_ev_set_sysex(&alsa_ev, len, const_cast(static_cast(data + count))); count += len; if (count >= size) { // done, this was the last chunk count = 0; } } void ALSABackend::midi_event_to_alsa_generic(snd_seq_event_t & alsa_ev, MidiEvent const & ev) { // maximum size of non-sysex sequencer events is 12 bytes unsigned char buf[12]; std::size_t len = 12; int port; uint64_t frame; midi_event_to_buffer(ev, buf, len, port, frame); snd_midi_event_reset_encode(_parser); snd_midi_event_encode(_parser, buf, len, &alsa_ev); } bool ALSABackend::input_event(MidiEvent & ev) { snd_seq_event_t *alsa_ev; // loop until we've received an event we're interested in for (;;) { if (snd_seq_event_input(_seq, &alsa_ev) < 0 || !alsa_ev) { DEBUG_PRINT("couldn't retrieve ALSA sequencer event"); continue; } // check for program termination if (alsa_ev->type == SND_SEQ_EVENT_USR0) { return false; } // convert event from alsa alsa_to_midi_event(ev, *alsa_ev); if (ev.type != MIDI_EVENT_NONE) { return true; } } } void ALSABackend::output_event(MidiEvent const & ev) { snd_seq_event_t alsa_ev; // number of bytes already sent (for sysex), or zero if the whole event was sent std::size_t count = 0; do { midi_event_to_alsa(alsa_ev, ev, count); snd_seq_ev_set_subs(&alsa_ev); snd_seq_ev_set_direct(&alsa_ev); snd_seq_ev_set_source(&alsa_ev, _out_ports[ev.port]); if (snd_seq_event_output_direct(_seq, &alsa_ev) < 0) { DEBUG_PRINT("couldn't output event to ALSA sequencer buffer"); } if (count) { // wait as long as it takes for one chunk to be transmitted at MIDI baud rate. // constant copied from Simple Sysexxer by Christoph Eckert. ::usleep(Config::ALSA_SYSEX_CHUNK_SIZE * 352); } } while (count); } } // Backend } // Mididings mididings-20120419~ds0/src/backend/jack_realtime.hh0000644000175000017500000000245000011227266021700 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_BACKEND_JACK_REALTIME_HH #define MIDIDINGS_BACKEND_JACK_REALTIME_HH #include "backend/jack.hh" #include "util/ringbuffer.hh" namespace Mididings { namespace Backend { /* * realtime JACK backend. * events are processed inside the JACK callback. */ class JACKRealtimeBackend : public JACKBackend { public: JACKRealtimeBackend(std::string const & client_name, PortNameVector const & in_port_names, PortNameVector const & out_port_names); virtual void start(InitFunction init, CycleFunction cycle); virtual void stop() { } virtual bool input_event(MidiEvent & ev); virtual void output_event(MidiEvent const & ev); private: virtual int process(jack_nframes_t); InitFunction _run_init; CycleFunction _run_cycle; jack_nframes_t _nframes; das::ringbuffer _out_rb; }; } // Backend } // Mididings #endif // MIDIDINGS_BACKEND_JACK_REALTIME_HH mididings-20120419~ds0/src/backend/jack.hh0000644000175000017500000000404600011032211020001 0ustar alessioalessio/* * mididings * * Copyright (C) 2008-2012 Dominic Sacré * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #ifndef MIDIDINGS_BACKEND_JACK_HH #define MIDIDINGS_BACKEND_JACK_HH #include "backend/base.hh" #include "midi_event.hh" #include #include #include namespace Mididings { namespace Backend { /* * JACK backend base class. */ class JACKBackend : public BackendBase { public: JACKBackend(std::string const & client_name, PortNameVector const & in_port_names, PortNameVector const & out_port_names); virtual ~JACKBackend(); virtual std::size_t num_out_ports() const { return _out_ports.size(); } virtual void connect_ports(PortConnectionMap const & in_port_connections, PortConnectionMap const & out_port_connections); protected: // XXX this should be pure virtual. // it isn't, because the process thread is started within the c'tor virtual int process(jack_nframes_t) { return 0; } //= 0; void clear_buffers(jack_nframes_t nframes); bool read_event(MidiEvent & ev, jack_nframes_t nframes); bool write_event(MidiEvent const & ev, jack_nframes_t nframes); jack_client_t *_client; std::vector _in_ports; std::vector _out_ports; jack_nframes_t _current_frame; private: static int process_(jack_nframes_t, void *); void connect_ports_impl(PortConnectionMap const & port_connections, std::vector const & ports, bool out); int connect_matching_ports(std::string const & port_name, std::string const & pattern, PortNameVector const & external_ports, bool out); // loop counters used by read_event() int _input_port; int _input_count; }; } // Backend } // Mididings #endif // MIDIDINGS_BACKEND_JACK_HH