From 1c7cecc5f46222cabbe3b4f11e099c35efa501eb Mon Sep 17 00:00:00 2001 From: abhunkin Date: Mon, 05 Mar 2012 02:42:02 +0000 Subject: Version 1 --- diff --git a/ChimePlay.csd b/ChimePlay.csd new file mode 100644 index 0000000..c40fbd4 --- /dev/null +++ b/ChimePlay.csd @@ -0,0 +1,162 @@ +; CHIMEPLAY (2012) for realtime Csound5 - by Arthur B. Hunkins +; Custom version of SAMPLEPLAY for chime and bell samples (up to 8) +; Requires MIDI device with up to 8 keys, buttons or pads +; optionally, keys/button/pads velocity sensitive +; optionally, 1 additional key/button/pad and/or 1-3 MIDI knobs or sliders +; 0-1 background loop, 1-8 single-shot samples of minimum 3-second duration +; Files may be mono or stereo, must have same Sample Rate and agree with this SR. +; They may be a variety of uncompressed types including WAV and AIFF; also +; Ogg/Vorbis (with Sugar 0.86/Blueberry or later, or Sugar 0.84/Strawberry +; with updated libsndfile) but not MP3. +; Choice of two chime sets; both default sets include 6 chimes and an ambient loop. +; Background loop must be named soundin.0 (set 1) or soundin.10 (set 2), +; and samples labeled soundin.1 and up (set 1), or soundin.11 and up (set 2). +; They must be placed in the same folder as this file. + + + + +-odac -+rtaudio=alsa -+rtmidi=alsa -M hw:1,0 -m0d --expression-opt -b128 -B2048 -+raw_controller_mode=1 + + + + +sr = 44100 +; change sample rate to 48000 (or 32000 if necessary) when 44100 gives no audio. +; (Necessary for Intel Classmate PC and some other systems.) +ksmps = 100 +nchnls = 2 + + seed 0 + massign 0, 0 + +gichan chnexport "Chan", 1 +gibackgnd chnexport "Backgnd", 1 +gibgmax chnexport "Bgmax", 1 +gistctrl chnexport "Stctrl", 1 +gistnote chnexport "Stnote", 1 +gistfade chnexport "Stfade", 1 +gisamps chnexport "Samps", 1 +gisset chnexport "Sset", 1 +gisampamp chnexport "Sampamp", 1 +girndrang chnexport "Rndrange", 1 +gismpctrl chnexport "Smpctrl", 1 +gisampdur chnexport "Sampdur", 1 +gimidi1 chnexport "MIDI1", 1 +gipanpos chnexport "Panpos", 1 +gipanctrl chnexport "Panctrl", 1 + + instr 1 + +gkfreq init 0 +gkpan init .5 +kflag init 0 + if ((gibackgnd == 0) || (kflag == 1)) goto skip +kinstr = (gisset == 0? 2: 12) + event "i", kinstr, 0, -1 +kflag = 1 +skip: +gkstat,gkchan,gkd1,gkd2 midiin + if ((gkstat == 0) || (gkchan != gichan)) goto end + if ((gkstat != 144) || ((gkstat == 144) && (gkd2 == 0))) goto end + if ((gkd1 < gimidi1) || (gkd1 > (gimidi1 + (gisamps - 1)))) goto end +kbase = gkd1 - gimidi1 + 1 +kinstr = (gisset == 0? kbase + 2: kbase + 12) + if gisampamp > 0 goto skip2 +kamp = 1 + goto skip3 +skip2: + if gisampamp > 1 goto skip4 +kamp random girndrang * .01, 1 + goto skip3 +skip4: + if gisampamp > 2 goto skip5 +kamp = gkd2 / 127 + goto skip3 +skip5: +kamp ctrl7 gichan, gismpctrl, 0, 1 +skip3: + if gipanpos = 0 goto skip6 + if gipanpos > 1 goto skip7 +gkpan linrand 1 + goto skip6 +skip7: + if gipanpos > 2 goto skip8 +gkpan = gkd2 / 127 + goto skip6 +skip8: +gkpan ctrl7 gichan, gipanctrl, 0, 1 +skip6: + goto skip9 +skip10: +isamp = i(kinstr) - 2 +ilen filelen isamp + rireturn +skip9: + reinit skip10 + event "i", kinstr, 0, ilen, kamp * .5, gkpan + +end: endin + + instr 2, 12 + +isamp = p1 - 2 +kamp init 0 +ibgmax = gibgmax * .1 +ichans filenchnls isamp + if gibackgnd == 2 goto skip + if ((gkstat == 144) && (gkd1 == gistnote) && (gkd2 > 0)) goto skip2 + goto skip3 +skip2: +kamp = (kamp == 0? ibgmax: 0) +skip3: +kamp2 lineto kamp, gistfade +kamp2 = (kamp2 < .01? 0: kamp2) + goto skip4 +skip: +kamp2 ctrl7 gichan, gistctrl, 0, ibgmax +skip4: + if ichans == 2 goto skip5 +aout diskin2 isamp, 1, 0, 1 + outs aout * kamp2, aout * kamp2 + goto end +skip5: +a1, a2 diskin2 isamp, 1, 0, 1 + outs a1 * kamp2, a2 * kamp2 + +end: endin + + instr 3, 4, 5, 6, 7, 8, 9, 10, 13, 14, 15, 16, 17, 18, 19, 20 + +isamp = p1 - 2 +ichans filenchnls isamp +ilen = ((gisampdur == 2) || (gisampdur > p3)? p3: gisampdur) +kamp linsegr 0, .025, p4, ilen - 2.025, p4, 2, 0 + if ichans == 2 goto skip +aout diskin2 isamp, 1 +a1,a2,a3,a4 pan aout, p5, 1, 1, 1 + outs a1 * kamp, a2 * kamp + goto skip2 +skip: +aout,aout2 diskin2 isamp, 1 +a1,a2,a3,a4 pan aout, p5, 1, 1, 1 +a5,a6,a7,a8 pan aout2, p5, 1, 1, 1 + outs (a1 + a5) * kamp, (a2 + a6) * kamp +skip2: +ktime timeinsts + if ktime < (ilen - 2) goto end + turnoff + +end: endin + + + + + +f1 0 8193 9 .25 1 0 +i1 0 3600 + +e + + + diff --git a/ChimePlayAuto.csd b/ChimePlayAuto.csd new file mode 100644 index 0000000..2553e08 --- /dev/null +++ b/ChimePlayAuto.csd @@ -0,0 +1,271 @@ +; CHIMEPLAYAUTO (2012) for realtime Csound5 - by Arthur B. Hunkins +; A custom autoplay version of SAMPLEPLAY for chime and bell samples (up to 8) +; ChimePlayAuto.csd - no MIDI device required +; 0-1 background loop, 1-8 single-shot samples of minimum 3-second duration +; Files may be mono or stereo, must have same Sample Rate and agree with this SR. +; Choice of two chime sets; both default sets include 6 chimes and an ambient loop. +; Background loop must be named soundin.0 (set 1) or soundin.10 (set 2), +; and samples labeled soundin.1 and up (set 1), or soundin.11 and up (set 2). +; They must be placed in the same folder as this file. + + + + +-odac -+rtaudio=alsa -m0d --expression-opt -b128 -B2048 + + + + +sr = 44100 +; change sample rate to 48000 (or 32000 if necessary) when 44100 gives no audio. +; (Necessary for Intel Classmate PC and some other systems.) +ksmps = 100 +nchnls = 2 + + seed 0 +gifirst init 1 +gilen init 0 + +gibackgnd chnexport "Loop", 1 +gibgmax chnexport "Bgmax", 1 +gistfade chnexport "Stfade", 1 +gisamps chnexport "Samps", 1 +gisset chnexport "Sset", 1 +girndsamp chnexport "Rndsamp", 1 +girndrang chnexport "Rndrange", 1 +gisampdur chnexport "Sampdur", 1 +gipanpos chnexport "Panpos", 1 +gitotdur chnexport "Totaldur", 1 + +gimindur1 chnexport "Min1", 1 +gimaxdur1 chnexport "Max1", 1 +gimindur2 chnexport "Min2", 1 +gimaxdur2 chnexport "Max2", 1 +gimindur3 chnexport "Min3", 1 +gimaxdur3 chnexport "Max3", 1 +gimindur4 chnexport "Min4", 1 +gimaxdur4 chnexport "Max4", 1 +gimindur5 chnexport "Min5", 1 +gimaxdur5 chnexport "Max5", 1 +gimindur6 chnexport "Min6", 1 +gimaxdur6 chnexport "Max6", 1 +gimindur7 chnexport "Min7", 1 +gimaxdur7 chnexport "Max7", 1 +gimindur8 chnexport "Min8", 1 +gimaxdur8 chnexport "Max8", 1 + + instr 1 + +kflag init 0 +kflag1 init 0 +kflag2 init 0 +kflag3 init 0 +kflag4 init 0 +kflag5 init 0 +kflag6 init 0 +kflag7 init 0 +kflag8 init 0 +ktime1 init 0 +ktime2 init 0 +ktime3 init 0 +ktime4 init 0 +ktime5 init 0 +ktime6 init 0 +ktime7 init 0 +ktime8 init 0 +kinst3 init 3 +kinst4 init 4 +kinst5 init 5 +kinst6 init 6 +kinst7 init 7 +kinst8 init 8 +kinst9 init 9 +kinst10 init 10 +gidur = (gitotdur == 0? 30: gitotdur * 60) +gktime times +ibase = (gisset == 0? 0: 10) + if gisamps == 0 igoto skip +ilen3 filelen 1 + ibase +gilen = ilen3 + if gisamps == 1 igoto skip +ilen4 filelen 2 + ibase +gilen = (ilen4 > gilen? ilen4: gilen) + if gisamps == 2 igoto skip +ilen5 filelen 3 + ibase +gilen = (ilen5 > gilen? ilen5: gilen) + if gisamps == 3 igoto skip +ilen6 filelen 4 + ibase +gilen = (ilen6 > gilen? ilen6: gilen) + if gisamps == 4 igoto skip +ilen7 filelen 5 + ibase +gilen = (ilen7 > gilen? ilen7: gilen) + if gisamps == 5 igoto skip +ilen8 filelen 6 + ibase +gilen = (ilen8 > gilen? ilen8: gilen) + if gisamps == 6 igoto skip +ilen9 filelen 7 + ibase +gilen = (ilen9 > gilen? ilen9: gilen) + if gisamps == 7 igoto skip +ilen10 filelen 8 + ibase +gilen = (ilen10 > gilen? ilen10: gilen) +skip: + if ((gibackgnd == 0) || (kflag == 1)) goto skip2 +kinstr = (gisset == 0? 2: 12) + event "i", kinstr, 0, gidur +kflag = 1 +skip2: + if gisamps == 0 goto end +ifade = (gibackgnd == 0? 0: gistfade) + if gktime > (gidur - ifade - gilen) goto end + if gktime < ifade goto end + if kflag1 == 1 goto skip3 +k1 random gimindur1, gimaxdur1 +ktime1 = gktime + k1 +kflag1 = 1 +skip3: + if gktime < ktime1 goto skip4 +klen = (gisampdur < 3? ilen3: gisampdur) + event "i", kinst3, 0, klen +kflag1 = 0 +skip4: + if gisamps < 2 goto end + if kflag2 == 1 goto skip5 +k2 random gimindur2, gimaxdur2 +ktime2 = gktime + k2 +kflag2 = 1 +skip5: + if gktime < ktime2 goto skip6 +klen = (gisampdur < 3? ilen4: gisampdur) + event "i", kinst4, 0, klen +kflag2 = 0 +skip6: + if gisamps < 3 goto end + if kflag3 == 1 goto skip7 +k3 random gimindur3, gimaxdur3 +ktime3 = gktime + k3 +kflag3 = 1 +skip7: + if gktime < ktime3 goto skip8 +klen = (gisampdur < 3? ilen5: gisampdur) + event "i", kinst5, 0, klen +kflag3 = 0 +skip8: + if gisamps < 4 goto end + if kflag4 == 1 goto skip9 +k4 random gimindur4, gimaxdur4 +ktime4 = gktime + k4 +kflag4 = 1 +skip9: + if gktime < ktime4 goto skip10 +klen = (gisampdur < 3? ilen6: gisampdur) + event "i", kinst6, 0, klen +kflag4 = 0 +skip10: + if gisamps < 5 goto end + if kflag5 == 1 goto skip11 +k5 random gimindur5, gimaxdur5 +ktime5 = gktime + k5 +kflag5 = 1 +skip11: + if gktime < ktime5 goto skip12 +klen = (gisampdur < 3? ilen7: gisampdur) + event "i", kinst7, 0, klen +kflag5 = 0 +skip12: + if gisamps < 6 goto end + if kflag6 == 1 goto skip13 +k6 random gimindur6, gimaxdur6 +ktime6 = gktime + k6 +kflag6 = 1 +skip13: + if gktime < ktime6 goto skip14 +klen = (gisampdur < 3? ilen8: gisampdur) + event "i", kinst8, 0, klen +kflag6 = 0 +skip14: + if gisamps < 7 goto end + if kflag7 == 1 goto skip15 +k7 random gimindur7, gimaxdur7 +ktime7 = gktime + k7 +kflag7 = 1 +skip15: + if gktime < ktime7 goto skip16 +klen = (gisampdur < 3? ilen9: gisampdur) + event "i", kinst9, 0, klen +kflag7 = 0 +skip16: + if gisamps < 8 goto end + if kflag8 == 1 goto skip17 +k8 random gimindur8, gimaxdur8 +ktime8 = gktime + k8 +kflag8 = 1 +skip17: + if gktime < ktime8 goto end +klen = (gisampdur < 3? ilen10: gisampdur) + event "i", kinst10, 0, klen +kflag8 = 0 + +end: endin + + instr 2, 12 + +isamp = p1 - 2 +ichans filenchnls isamp +ibgmax = gibgmax * .1 +kamp linseg 0, gistfade, ibgmax, gidur - (gistfade * 2), ibgmax, gistfade, 0 + if ichans == 2 goto skip +aout diskin2 isamp, 1, 0, 1 + outs aout * kamp, aout * kamp + goto end +skip: +a1, a2 diskin2 isamp, 1, 0, 1 + outs a1 * kamp, a2 * kamp + +end: endin + + instr 3, 4, 5, 6, 7, 8, 9, 10 + +isamp = (gisset == 0? p1 - 2: p1 + 8) +ichans filenchnls isamp +ilen filelen isamp +ilen = ((gisampdur == 2) || (gisampdur > ilen)? ilen: gisampdur) +;gilen = (ilen > gilen? ilen: gilen) + if girndsamp > 0 goto skip +iamp = 1 + goto skip2 +skip: +iamp random girndrang * .01, 1 +iamp = (gifirst == 1? 1: iamp) +gifirst = 0 +skip2: + if gipanpos = 0 goto skip3 +ipan linrand 1 + goto skip4 +skip3: +ipan = .5 +skip4: +kamp linseg 0, .025, iamp * .5, p3 - 2.025, iamp * .5, 2, 0 + if ichans == 2 goto skip5 +aout diskin2 isamp, 1 +a1,a2,a3,a4 pan aout, ipan, 1, 1, 1 + outs a1 * kamp, a2 * kamp + goto end +skip5: +aout,aout2 diskin2 isamp, 1 +a1,a2,a3,a4 pan aout, ipan, 1, 1, 1 +a5,a6,a7,a8 pan aout2, ipan, 1, 1, 1 + outs (a1 + a5) * kamp, (a2 + a6) * kamp + +end: endin + + + + + +f1 0 8193 9 .25 1 0 +i1 0 3600 + +e + + + diff --git a/ChimePlayReadMe.txt b/ChimePlayReadMe.txt new file mode 100644 index 0000000..a42a507 --- /dev/null +++ b/ChimePlayReadMe.txt @@ -0,0 +1,150 @@ +CHIMEPLAY and CHIMEPLAYAUTO - Sugar Activity/Linux version - Notes +Art Hunkins +abhunkin@uncg.edu +www.arthunkins.com + + +Working with User Soundfiles + +The ChimePlay utility series includes ChimePlay (requiring a MIDI +controller) and ChimePlayAuto (which is self-performing and needs no +MIDI device). Both can handle mono or stereo soundfiles, up to 8 +chime or bell samples and a single optional background loop. The +files can be of any sample rate and a variety of uncompressed formats +including WAV and AIFF; also Ogg/Vorbis, but not MP3. The Ogg/Vorbis +format is only possible when the Sugar version is later than 0.84; +this excludes the original XO-1 and SoaS (Sugar-on-a-Stick) +Strawberry. + +*However*, the ogg vorbis format (which is written by later versions +of the Record activity) *can* be used by SoaS (Strawberry) 0.84 if +libsndfile is updated. This can be done while connected to the +internet by issuing the following commands in the Terminal: + su + yum update libsndfile +Neither the XO-1.5, nor XO-1 upgraded to Sugar 0.84 require this mod. + +Students are encouraged to create their own soundfiles, especially to +make their own windchime or bell collections. (This is the primary +intent behind these utilities.) The two 6-chime sets included here +are from the St. Francis Prayer Center in Stoneville, NC (USA). Set +one consists of recordings of chimes located under an interior cupola +opposite the Chapel. The second is a set found on the patio of the +Center's main building. Soundin.1 through soundin.6 and background +loop soundin.0 comprise set one (the inside set); soundin.11 through +soundin.16 and ambient loop soundin.10 are set two (the outside +chimes). Soundin.7, 8, 17 and 18 are dummy samples that complete the +possible sets of 8. The optional ambient loops can help mask and +"homogenize" unwanted noise that may be present in the samples. + +These samples were created with a high-quality handheld digital +recorder (at 44100Hz sample rate stereo, uncompressed WAV) using its +built-in mike. Sounds were then selected, edited and looped in +Audacity (see below). It was important to record all sounds in the +same environment and at the same level. An ambient background loop +of the location helped mask unwanted noise (as did the high pass +filter effect available in Audacity - cutoff frequency = 661Hz). + +The natural vehicle for soundfile creation is the Record activity. +This activity is fairly simple and straightforward; the only problem +is that many versions of it do not work with various incarnations of +Sugar. The following pairings of Record with Sugar seem to work +reliably: v86 with XO-1.5 and XO-1 upgraded to Sugar 0.84, Sugar-on- +a-Stick Strawberry (0.84) and Blueberry (0.86). Sugar 0.86 and above +(as of 3/2012) are compatible with Record v90, including XO's +updated to at least 0.90. Please note that Record prior to v74 +(except for v61-64) produce ogg *speex* files; these files are +incompatible with ChimePlay. Though the Record activity produces +mono files only, at 16000Hz, such samples are nevertheless quite +useable. + +Soundfiles must be moved into the folder where this file resides, +and be renamed soundin.0 or soundin.10 (for the background loop) and +soundin.1 or soundin.11 through soundin.8 or .18 (for the samples). +Rename them in the Journal (where Record deposits its files), then +click/drag samples to an external USB stick. + +Unfortunately, no other Sugar activity (including TimeLapse, +ShowNTell, and most importantly, Etoys) produces soundfiles useable +by ChimePlay. Either they write files other than Ogg Vorbis or wav, +or are restricted to Sugar 0.82. + +More advanced users may wish to record their soundfiles on some other +system, and copy the files to a USB drive with an appropriate +soundin.x filename. Then recopy the samples (in the Terminal, from +the ChimePlay.activity folder) to their new location. +[cp /media/USBname/soundin.x soundin.x] + +Otherwise, adventurous users may run the fine Audacity application to +record and edit. (Happily, none of the limitations of the Record +activity apply here.) In the Terminal, connected to the web, enter: + su + yum import audacity + ... + audacity +(you are now running Audacity from the Terminal). + +When you are finished recording and editing (including auditioning +the background loop in loop mode), pay particular attention to making +the loop point as inconspicuous as possible), "Export" the file in +wav or ogg vorbis format, saving it to a USB drive with appropriate +filename (soundin.x). It can then be copied to your +ChimePlay.activity folder. Since your chime/bell sets are on a +USB drive they can easily be shared with other students. + + +MIDI Controller Hints (ChimePlay only) + +Important: The controller must be attached AFTER boot, and BEFORE +the MIDI version is selected. It is assumed that the controller is a +USB device. + +ChimePlay was specifically designed for minimal (8-9 key) velocity- +sensitive MIDI keyboards, preferably those with 1 or 2 additional +sliders or modulation wheels (rotary knobs are OK, but not as easy to +work with). Suggested inexpensive USB models: Alesis Q25, Akai LPK25 +(no sliders/knobs), Korg nanoKey (no sliders/knobs and rather flimsy +construction), M-audio O2, and M-audio Oxygen8. + +The Korg nanoKontrol is an adequate, if not ideal mini-controller for +ChimePlay; unfortunately, its numerous buttons, which can issue MIDI +note data (it has no pads or keys), are not velocity-sensitive. One of +its four "Scenes" must be significantly reprogrammed by the Korg +Kontrol Editor to function with ChimePlay. Though it has a multitude +of programmable sliders and knobs, unfortunately the buttons are not +laid out well for ChimePlay performance. + + +No Sound - Sample Rate Issues + +On a few systems, e.g. the Intel Classmate PC, the specified sr +(sample rate) of 44100 may not produce audio. Substitute a rate of +48000 (or, if necessary, 32000) toward the beginning of each .csd +file, using a text editor. (The sample rate, sr, is specified on +line 24 of ChimePlay.csd, and line 19 of ChimePlayAuto.csd.) + + +Audio Glitching/Breakup + +If you get audio glitching, open Sugar's Control Panel, and turn off +Extreme power management (under Power) or Wireless radio (under +Network). A more drastic solution is to reduce textural density +(fewer chimes, fewer chimes ringing simultaneously). It is also +possible to lower the sample rate to 32000 or even 24000 (see above). + +Stereo headphones (an inexpensive set will work fine) or external +amplifier/speaker system are highly recommended. Speakers built into +computers are fairly worthless musically. + + +Resizing the Font + +The font display of this activity can be resized in csndsugui.py, +using any text editor. Further instructions are found toward the +beginning of csndsugui.py. (Simply change the value of the "resize" +variable (= 0), plus or minus.) + + +Further relevent items of interest may be found in the document +ChimePlay.txt on the author's website. (It is the text file +associated with the *all-platform* non-Sugar version of ChimePlay.) diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..bc6fd59 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,27 @@ +csndsugui.py +ChimePlay.csd +ChimePlayAuto.csd +chimeplay.py +activity/activity.info +activity/activity-chimeplay.svg +setup.py +soundin.0 +soundin.1 +soundin.2 +soundin.3 +soundin.4 +soundin.5 +soundin.6 +soundin.7 +soundin.8 +soundin.10 +soundin.11 +soundin.12 +soundin.13 +soundin.14 +soundin.15 +soundin.16 +soundin.17 +soundin.18 +ChimePlayReadMe.txt + diff --git a/activity/activity-chimeplay.svg b/activity/activity-chimeplay.svg new file mode 100644 index 0000000..986c636 --- /dev/null +++ b/activity/activity-chimeplay.svg @@ -0,0 +1,33 @@ + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/activity/activity.info b/activity/activity.info new file mode 100644 index 0000000..7373e19 --- /dev/null +++ b/activity/activity.info @@ -0,0 +1,14 @@ +[Activity] + +name = ChimePlay + +bundle_id = org.laptop.ChimePlay +icon = activity-chimeplay + +activity_version = 1 + +host_version = 1 + +show_launcher = yes +exec = sugar-activity chimeplay.ChimePlay +license = CC-by-SA 3.0 diff --git a/chimeplay.py b/chimeplay.py new file mode 100644 index 0000000..7d48750 --- /dev/null +++ b/chimeplay.py @@ -0,0 +1,214 @@ +# CHIMEPLAY - Chime and Bell Player Utilities for Children (2012) +# Custom versions of SAMPLEPLAY for windchime and bell sample sets +# Art Hunkins (www.arthunkins.com) +# +# ChimePlay is licensed under the Creative Commons Attribution-Share +# Alike 3.0 Unported License. To view a copy of this license, visit +# http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to +# Creative Commons, 171 Second Street, Suite 300, San Francisco, +# California, 94105, USA. +# +# It 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. +# + +import csndsugui +from sugar.activity import activity +import gtk +import os + +class ChimePlay(activity.Activity): + + def __init__(self, handle): + + activity.Activity.__init__(self, handle) + + red = (0xDDDD, 0, 0) + brown = (0x6600, 0, 0) + green = (0, 0x5500, 0) + + win = csndsugui.CsoundGUI(self) + width = gtk.gdk.screen_width() + height = gtk.gdk.screen_height() + if os.path.exists("/etc/olpc-release") or os.path.exists("/sys/power/olpc-pm"): + adjust = 78 + else: + adjust = 57 + screen = win.box() + screen.set_size_request(width, height - adjust) + scrolled = gtk.ScrolledWindow() + scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + screen.pack_start(scrolled) + all = gtk.VBox() + all.show() + scrolled.add_with_viewport(all) + scrolled.show() + + win.text("CHIMEPLAY - Chime and Bell Player \ +Utilities for Children (2012)\n\ +\t\t\t Art Hunkins (www.arthunkins.com)", all) + + win.text("\ +ChimePlay and ChimePlayAuto are custom versions of SAMPLEPLAY \ +for sets of up to 8 windchimes or bells.\t\t\n\ +Two sets of 6 chimes (plus ambient loop) are included. \ +You are urged to create your own (see ChimePlayReadMe.txt). ", all, brown) + win.text("ChimePlay requires MIDI controller, \ +one key/button/pad per active chime and loop (optionally velocity-sensitive);\t\nalso \ +(optionally) an additional key/button/pad and/or 1-2 MIDI knobs/sliders. \ +All samples/MIDI notes are consecutive.\t", all, green) + win.text("\ +MIDI: plug in controller after boot & before selecting version. \ +Zero controls before start; reset pan to .5 afterward.\t", all, green) + win.text("ChimePlayAuto doesn't involve MIDI. \ +You specify the random range in seconds between \ +repeats of a given chime.\t", all, brown) + + nbox = win.box(False, all) + self.b2box = win.box(False, all) + self.b3box = win.box(False, all) + but1 = win.cbbutton(nbox, self.version1, " 1 ChimePlay ") + but1.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0, 0x7700, 0)) + but1.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0, 0x7700, 0)) + but2 = win.cbbutton(nbox, self.version2, " 2 ChimePlayAuto ") + but2.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0, 0x7700, 0)) + but2.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0, 0x7700, 0)) + win.text(" MIDI DEVICE REQUIRED for ChimePlay", nbox, green) + + bbox = win.box(False, all) + self.bb = bbox + bbox2 = win.box(False, all) + self.bb2 = bbox2 + self.w = win + self.r = red + self.g = green + self.br = brown + self.ver = 0 + + def playcsd(self, widget): + if self.p == False: + self.p = True + self.w.play() + self.but.child.set_label("STOP !") + self.but.child.set_use_markup(True) + self.but.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0xFFFF, 0, 0)) + self.but.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0xFFFF, 0, 0)) + else: + self.p = False + self.w.recompile() + self.w.channels_reinit() + self.but.child.set_label("START !") + self.but.child.set_use_markup(True) + self.but.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0, 0x7700, 0)) + self.but.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0, 0x7700, 0)) + + def version1(self, widget): + if self.ver != 0: + self.box1.destroy() + self.box2.destroy() + self.box3.destroy() + self.ver = 1 + self.box1 = self.w.box(True, self.bb) + self.w.text("", self.box1) + self.box2 = self.w.box(True, self.bb) + self.f = self.w.framebox(" 1 - ChimePlay ", False, self.box2, self.r) + self.b1 = self.w.box(True, self.f) + self.b2 = self.w.box(True, self.f) + self.b3 = self.w.box(True, self.f) + self.b4 = self.w.box(True, self.f) + self.b5 = self.w.box(True, self.f) + self.b6 = self.w.box(True, self.f) + self.w.reset() + self.w.csd("ChimePlay.csd") + self.w.spin(1, 1, 16, 1, 1, self.b1, 0, "Chan", "Channel #") + self.w.spin(2, 0, 2, 1, 1, self.b1, 0, "Backgnd", "Background Loop\n\ +[0=none 1=note\n 2=controller]") + self.w.spin(10, 0, 20, 1, 1, self.b1, 0, "Bgmax", "Loop Lev [10=norm]") + self.w.spin(7, 0, 127, 1, 1, self.b2, 0, "Stctrl", "Loop Controller #") + self.w.spin(84, 0, 127, 1, 1, self.b2, 0, "Stnote", " Loop Note\n\ +(start/stop)") + self.w.spin(5, 1, 30, 1, 1, self.b2, 0, "Stfade", "Start/Stop Secs") + self.w.spin(6, 0, 8, 1, 1, self.b3, 0, "Samps", "# of Samples") + self.w.spin(1, 0, 3, 1, 1, self.b3, 0, "Sampamp", "Sample Level Ctrl\n\ +[0=none 1=rand\n2=note vel 3=ctrl]") + self.w.spin(21, 0, 127, 1, 1, self.b3, 0, "Smpctrl", "Samp Lev Ctrl #") + self.w.spin(10, 10, 100, 1, 1, self.b4, 0, "Rndrange", "Lowest Rand Amp\n\ + Level [as %]") + self.w.spin(2, 2, 20, 1, 1, self.b4, 0, "Sampdur", " Chime Duration\n\ +[2=natural >2=dur\nin secs if < natural]") + self.w.spin(60, 0, 120, 1, 1, self.b5, 0, "MIDI1", "1st MIDI Note #") + self.w.spin(0, 0, 3, 1, 1, self.b5, 0, "Panpos", " Pan Pos Control\n\ +[0=none 1=rand\n2=note vel 3=ctrl]") + self.w.spin(20, 0, 127, 1, 1, self.b5, 0, "Panctrl", "Pan Position Ctrl #") + self.w.button(self.b6, "Sset", "Chime Set 2?") + self.p = False + self.w.text("\nSelect options first ", self.b6, self.g) + self.but = self.w.cbbutton(self.b6, self.playcsd, "START !") + self.but.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0, 0x7700, 0)) + self.but.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0, 0x7700, 0)) + + def version2(self, widget): + if self.ver != 0: + self.box1.destroy() + self.box2.destroy() + self.ver = 2 + self.box1 = self.w.box(True, self.bb) + self.w.text(" ", self.box1) + self.box2 = self.w.box(True, self.bb) + self.f = self.w.framebox(" 2 - ChimePlayAuto ", False, self.box2, self.r) + self.b1 = self.w.box(True, self.f) + self.b2 = self.w.box(True, self.f) + self.b3 = self.w.box(True, self.f) + self.b4 = self.w.box(True, self.f) + self.b5 = self.w.box(True, self.f) + self.b6 = self.w.box(True, self.f) + self.w.reset() + self.w.csd("ChimePlayAuto.csd") + self.w.spin(1, 0, 60, 1, 1, self.b1, 0, "Totaldur", "Total Dur (mins)\n\ + [0=30 secs]") + self.w.button(self.b1, "Loop", "Background Loop?", 1) + self.w.spin(10, 0, 30, 1, 1, self.b2, 0, "Bgmax", "Loop Lev [10=norm]") + self.w.spin(5, 1, 30, 1, 1, self.b2, 0, "Stfade", "Start/Stop Seconds") + self.w.spin(6, 0, 8, 1, 1, self.b3, 0, "Samps", "# of Samples") + self.w.button(self.b3, "Rndsamp", "Rand Samp Lev?", 1) + self.w.spin(10, 0, 100, 1, 1, self.b4, 0, "Rndrange", "Lowest Random\n\ +Amp Lev [as %]") + self.w.button(self.b4, "Sset", "Chime Set 2?") + self.w.spin(2, 2, 20, 1, 1, self.b5, 0, "Sampdur", " Sample Dur\n [2=natural\n\ +>2=dur in secs\n if < natural]") + self.w.button(self.b6, "Panpos", "Rand Pan Pos?") + self.w.text(" Select options\n\tfirst", self.b6, self.g) + self.but = self.w.cbbutton(self.b6, self.playcsd, "START !") + self.but.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0, 0x7700, 0)) + self.but.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0, 0x7700, 0)) + self.p = False + + self.box3 = self.w.box(False, self.bb2) + self.b7 = self.w.box(True, self.box3) + self.b8 = self.w.box(True, self.box3) + self.b9 = self.w.box(True, self.box3) + self.b10 = self.w.box(True, self.box3) + self.b11 = self.w.box(True, self.box3) + self.b12 = self.w.box(True, self.box3) + self.b13 = self.w.box(True, self.box3) + self.b14 = self.w.box(True, self.box3) + self.b15 = self.w.box(True, self.box3) + self.w.text("Min/Max\nRandom\nSeconds\n\ +between\nChimes", self.b7, self.br) + self.w.spin(2, 1, 30, 1, 1, self.b8, 0, "Min1") + self.w.spin(5, 1, 30, 1, 1, self.b8, 0, "Max1") + self.w.spin(2, 1, 30, 1, 1, self.b9, 0, "Min2") + self.w.spin(5, 1, 30, 1, 1, self.b9, 0, "Max2") + self.w.spin(2, 1, 30, 1, 1, self.b10, 0, "Min3") + self.w.spin(5, 1, 30, 1, 1, self.b10, 0, "Max3") + self.w.spin(2, 1, 30, 1, 1, self.b11, 0, "Min4") + self.w.spin(5, 1, 30, 1, 1, self.b11, 0, "Max4") + self.w.spin(2, 1, 30, 1, 1, self.b12, 0, "Min5") + self.w.spin(5, 1, 30, 1, 1, self.b12, 0, "Max5") + self.w.spin(2, 1, 30, 1, 1, self.b13, 0, "Min6") + self.w.spin(5, 1, 30, 1, 1, self.b13, 0, "Max6") + self.w.spin(2, 1, 30, 1, 1, self.b14, 0, "Min7") + self.w.spin(5, 1, 30, 1, 1, self.b14, 0, "Max7") + self.w.spin(2, 1, 30, 1, 1, self.b15, 0, "Min8") + self.w.spin(5, 1, 30, 1, 1, self.b15, 0, "Max8") diff --git a/csndsugui.py b/csndsugui.py new file mode 100644 index 0000000..6a476ed --- /dev/null +++ b/csndsugui.py @@ -0,0 +1,816 @@ +# sugar-aware GUI classes +# with boxes, sliders, spinbuttons, buttons, etc +# +# (c) Victor Lazzarini, 2006-08 +# +# This library is free software; you can redistribute it +# and/or modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# csndsugui 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with csndsugui; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +# 02111-1307 USA +# +# As a special exception, if other files instantiate templates or +# use macros or inline functions from this file, this file does not +# by itself cause the resulting executable or library to be covered +# by the GNU Lesser General Public License. This exception does not +# however invalidate any other reasons why the library or executable +# file might be covered by the GNU Lesser General Public License. +# +# +# version 0.1.3 06/08/08 + +import pygtk +pygtk.require('2.0') +from sugar.activity import activity +import gtk, gobject +import sys +import csnd +import math +import locale +import os +import sugar.logger +import time + +class BasicGUI: + """Basic GUI with boxes, sliders, spins, buttons etc + using pygtk/sugar, from which GUI classes + can be derived for Csound use.""" + + def scale_font(self, widget): + font = widget.get_pango_context().get_font_description() + +# The FONT DISPLAY in this activity can be resized (smaller or larger) +# by changing the value of "resize" below. "Resize" can be positive +# or negative, and is not limited to integers. A value of 1 equals a +# point in font size. + resize = 0 + + font_size = font.get_size() + (resize * 1024) + width = gtk.gdk.screen_width() + mult = width * .00076 + if os.path.exists("/etc/olpc-release") or os.path.exists("/etc/power/olpc-pm"): + mult = width * .00082 + elif os.path.exists("/etc/fedora-release"): + release = open("/etc/fedora-release").read() + if release.find("SoaS release 1 ") != -1: + mult = width * .00132 + elif release.find("SoaS release 2 ") != -1: + mult = width * .00085 + elif release.find("Fedora release ") != -1: + mult = width * .00119 + font.set_size(int(font_size * mult)) + widget.modify_font(font) + + def set_channel(self,name, val): + """basic bus channel setting method, + should be overriden for full-functionality.""" + self.logger.debug("channel:%s, value:%.1f" % (name,val)) + + def set_filechannel(self,chan,name): + """basic filename channel setting method + should be overriden for full-functionality.""" + self.logger.debug("channel:%s, filename:%s" % (chan,name)) + + def set_message(self, mess): + """basic message setting method + should be overriden for full-functionality.""" + self.logger.debug(mess) + + def get_slider_value(self,name): + """returns the slider value + name: slider name (which should also be the attached bus channel name""" + for i in self.sliders: + if i[1] == name: + return i[2] + return 0 + + def get_button_value(self,name): + """returns the button value (0 or 1) + name: button name (which should also be the attached bus channel name)""" + for i in self.buttons: + if i[1] == name: + return i[2] + return 0 + + def get_slider(self,name): + """returns the slider widget instance + name: slider name""" + for i in self.sliders: + if i[1] == name: + return i[0] + return 0 + + def get_button(self,name): + """returns the button widget instance + name: button name""" + for i in self.sliders: + if i[1] == name: + return i[0] + return 0 + + def set_focus(self): + """ called whenever the focus changes """ + self.logger.debug(self.focus) + + def focus_out(self, widget, event): + if(self.focus): + self.focus = False + self.set_focus() + + def focus_in(self, widget, event): + if(not self.focus): + self.focus = True + self.set_focus() + + def focus_back(self, widget, event): + self.window.disconnect(self.fback) + self.focus_connect() + + def buttcallback(self, widget, data=None): + for i in self.buttons: + if i[0] == widget: + if i[2]: + i[2] = 0 + i[0].modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0x8000,0x8000,0x8000, 1)) + i[0].modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0x8000,0x8000,0x8000, 2)) + else: + i[2] = 1 + i[0].modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0,0x7700,0, 1)) + i[0].modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0,0x7700,0, 2)) + self.set_channel(i[1], i[2]) + + def button_setvalue(self, widget, value): + if not value: + widget.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0x0FFF,0,0x00FF, 2)) + widget.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0xFFFF,0,0xFFFF, 2)) + else: + widget.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0xFFFF,0,0, 1)) + widget.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0xFFFF,0,0, 2)) + + def mbuttcallback(self, widget, data=None): + for i in self.mbuttons: + if i[0] == widget: + self.set_message(i[2]) + + def slidcallback(self,adj,widget): + for i in self.sliders: + if i[0] == widget: + i[2] = adj.value + if i[4]: + self.set_channel(i[1],i[2]) + i[3].set_text("%f" % i[2]) + pass + else: + value = i[5]*pow(i[6]/i[5], i[2]/i[6]) + self.set_channel(i[1], value) + i[3].set_text("%.3f" % value) + pass + + def spincallback(self,adj,widget): + for i in self.spins: + if i[0] == widget: + i[2] = adj.value + self.set_channel(i[1],i[2]) + + def filecallback(self,widget): + name = self.curfile[0].get_filename() + self.set_filechannel(self.curfile[2], name) + for i in self.buttons: + if i[0] == self.curfile[1]: + i[2] = name + self.filenames.update({self.curfile[2] : name}) + self.curfile[0].destroy() + self.fback = self.window.connect('focus_out_event', self.focus_back) + + def destroy_chooser(self,widget): + self.curfile[0].destroy() + + def fbuttcallback(self, widget, data=None): + self.focus_disconnect() + for i in self.buttons: + if i[0] == widget: + chooser = gtk.FileSelection(i[1]) + self.curfile = (chooser, i[0], i[1]) + chooser.set_filename(self.data_path) + chooser.ok_button.connect("clicked", self.filecallback) + chooser.cancel_button.connect("clicked", self.destroy_chooser) + chooser.show() + + def cbbutton(self,box,callback,title=""): + """Creates a callbackbutton + box: parent box + callback: click callback + title: if given, the button name + returns the widget instance""" + self.cbbutts = self.cbbutts + 1 + butt = gtk.Button(title) + self.scale_font(butt.child) + box.pack_start(butt, False, False, 2) + self.cbbuttons.append([butt,title,0]) + butt.connect("clicked", callback) + butt.show() + return butt + + def button(self,box, title="",label="",value=None): + """Creates a button (on/off) + box: parent box + title: if given, the button name, + which will also be the bus channel + name. Otherwise a default name is + given, BN, where N is button number + in order of creation. + label: if given, an alternative button name, + which will be displayed instead of title + returns the widget instance""" + self.butts = self.butts + 1 + if title == "": + title = "B%d" % self.butts + if label == "": name = title + else: name = label + butt = gtk.Button(" %s " % name) + self.scale_font(butt.child) + butt.modify_bg(gtk.STATE_ACTIVE, gtk.gdk.Color(0,0x7700,0, 1)) + butt.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0,0x7700,0, 2)) + box.pack_start(butt, False, False, 1) + self.buttons.append([butt,title,0]) + butt.connect("clicked", self.buttcallback) + if value == 1: + butt.clicked() + butt.show() + return butt + + def mbutton(self,box,mess,title=""): + """Creates a mbutton (for sending a message) + box: parent box + title: if given, the button name, otherwise a default name is + given, BN, where N is button number + in order of creation. + mess: message to be sent when button is clicked + returns the widget instance""" + self.mbutts = self.mbutts + 1 + if title == "": + title = "B%d" % self.mbutts + butt = gtk.Button(title) + butt.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0x0FFF,0,0x00FF, 1)) + butt.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0xFFFF,0xFFFF,0x0000, 2)) + box.pack_start(butt, False, False, 5) + self.mbuttons.append([butt,title,mess]) + butt.connect("clicked", self.mbuttcallback) + butt.show() + return butt + + def box(self,vert=True, parent=None, padding=3): + """creates a box + vert: True, creates a vertical box; horiz. + otherwise + parent: parent box, None if this is a toplevel box + padding: box padding + returns the widget instance""" + if vert: + box = gtk.VBox() + else: + box = gtk.HBox() + if parent: + parent.pack_start(box, False, False, padding) + else: + self.outbox.pack_start(box, False, False, padding) + self.boxes.append(box) + box.show() + return box + + def filechooser(self,box,title,label=""): + """Creates a filechooser button + title: button name, also file bus channel name + box: parent box + label: if given, alternative name, for display purposes only + otherwise button will display its title.""" + if label == "": name = title + else: name = label + butt = gtk.Button(name) + box.pack_start(butt, False, False, 5) + self.buttons.append([butt,title,"0"]) + butt.connect("clicked", self.fbuttcallback) + self.set_filechannel(title,"0") + self.filenames.update({title:"0"}) + butt.show() + return butt + + def slider(self,init, start, end, x, y, box, title="",vert=True,linear=True,dwid=100,label=""): + """Creates a slider + init: initial value + start, end: start and end of slider range + x, y: x and y sizes of slider + box: parent box + title: if given, the slider name, + which will also be the bus channel + name. Otherwise a default name is + given, SN, where N is slider number + in order of creation. + vert: vertical slider (True), else horiz. + linear: linear response (True), else exponential (zero or negative + ranges are not allowed) + dwid: display width in pixels + label: if given, the alternative slider name, for display only + returns the widget instance""" + self.slids = self.slids + 1 + if title == "": + title = "S%d" % self.slids + a = end - start + if vert: + step = a/y + adj = gtk.Adjustment(init,start,end,step,step,0) + slider = gtk.VScale(adj) + slider.set_inverted(True) + else: + step = a/x + adj = gtk.Adjustment(init,start,end,step,step,0) + slider = gtk.HScale(adj) + slider.set_draw_value(False) + if step < 1.0: + slider.set_digits(3) + elif step < 10: + slider.set_digits(2) + elif step < 100: + slider.set_digits(1) + else: + slider.set_digits(0) + entry = gtk.Entry(5) + if vert: entry.set_size_request(dwid,50) + else: entry.set_size_request(dwid,50) + entry.set_editable(False) + if not linear: + if (init <= 0) or (start <= 0) or (end <= 0): + linear = True + if not linear: + pos = end*math.log(1,end/start) + slider.set_range(pos, end) + pos = end*math.log(init/start,end/start) + slider.set_value(pos) + if label == "": name = title + else: name = label + entry.set_text("%f" % init) + label = gtk.Label(name) + slider.set_size_request(x,y) + box.pack_start(slider, False, False, 5) + box.pack_start(entry, False, False, 2) + box.pack_start(label, False, False, 2) + self.sliders.append([slider,title,init,entry,linear,start,end]) + adj.connect("value_changed", self.slidcallback, slider) + slider.show() + entry.show() + label.show() + self.set_channel(title, init) + return slider + + def numdisplay(self,box,title="",init=0.0,label=""): + self.ndispwids = self.ndispwids + 1 + entry = gtk.Entry() + if label == "": name = title + else: name = label + entry.set_text("%f" % init) + label = gtk.Label(name) + box.pack_start(entry, False, False, 2) + box.pack_start(label, False, False, 2) + self.ndisps.append([entry,title,init]) + entry.show() + label.show() + self.set_channel(title,init) + return entry + + def setnumdisp(self,title,val): + for i in self.ndisps: + if i[1] == title: + i[2] = val + i[0].set_text("%f" % val) + self.set_channel(title, val) + + def spin(self,init, start, end, step, page, box, accel=0,title="",label=""): + """Creates a spin button + init: initial value + start, end: start and end of slider range + step, page: small and large step sizes + box: parent box + accel: acceleration or 'climb rate' (0.0-1.0) + title: if given, the spin button name, + which will also be the bus channel + name. Otherwise a default name is + given, SPN, where N is spin number + in order of creation. + label: if given, the alternative name for the widget, for display only. + returns the widget instance""" + self.spinbs = self.spinbs + 1 + if title == "": + title = "SP%d" % self.spinbs + adj = gtk.Adjustment(init,start,end,step,page,0) + spin = gtk.SpinButton(adj,accel) + self.scale_font(spin) + spin.set_alignment(.5) + if label == "": name = title + else: name = label + label = gtk.Label(name) + self.scale_font(label) + box.pack_start(spin, False, False, 3) + box.pack_start(label, False, False, 0) + self.spins.append([spin,title,init]) + adj.connect("value_changed", self.spincallback, spin) + spin.show() + label.show() + self.set_channel(title, init) + return spin + + def text(self, name, box=None,colour=(0,0,0)): + """Creates a static text label + name: text label + box: parent box, None if text is to be placed toplevel + colour: RGB values in a tuple (R,G,B) + returns the widget instance""" + label = gtk.Label(name) + self.scale_font(label) + label.set_use_markup(True) + label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(colour[0],colour[1],colour[2], 0)) + if box: + box.pack_start(label, False, False, 3) + else: + self.outbox.pack_start(label, False, False, 3) + label.show() + return label + + def framebox(self, name, vert=True, parent=None, colour=(0,0,0), padding=5): + """Creates a frame box + name: text label + vert: vertical (True) box, else horiz. + parent: parent box, if None, this is a toplevel box + colour: RGB values in a tuple (R,G,B) + padding: padding space + returns the box widget instance""" + frame = gtk.Frame(name) + self.scale_font(frame.get_label_widget()) + frame.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(colour[0],colour[1],colour[2], 0)) + frame.get_label_widget().modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(colour[0],colour[1],colour[2], 0)) + frame.get_label_widget().set_use_markup(True) + if parent: + parent.pack_start(frame, False, False, padding) + else: + self.outbox.pack_start(frame, False, False, padding) + if vert: + box = gtk.VBox() + else: + box = gtk.HBox() + frame.add(box) + frame.show() + box.show() + return box + + def vsliderbank(self,items,init, start, end, x, y, box): + """Creates a vertical slider bank + items: number of sliders + init: initial value + start, end: start and end of slider range + x, y: x and y sizes of slider + box: parent box""" + slid = self.slids + for i in range(slid, slid+items): + cbox = self.box(parent=box) + self.slider(init,start,end,x,y,cbox) + + def hsliderbank(self,items,init, start, end, x, y, box): + """Creates a horizontal slider bank + items: number of sliders + init: initial value + start, end: start and end of slider range + x, y: x and y sizes of slider + box: parent box""" + slid = self.slids + for i in range(slid, slid+items): + cbox = self.box(False,box) + self.slider(init,start,end,x,y,cbox,"",False) + + def buttonbank(self,items, box): + """Creates a button bank + items: number of sliders + box: parent box.""" + start = self.butts + for i in range(start, start+items): + self.button(box) + + def delete_event(self, widget, event, data=None): + return False + + def get_toolbox(self): + """Returns the Activity toolbox""" + return self.toolbox + + def channels_reinit(self): + """ resets channel to current widget values""" + for j in self.buttons: + if(j[1] != "pause"): + if(j[1] != "play"): + if(j[1] != "reset"): + self.set_channel(j[1],j[2]) + for j in self.sliders: + if j[4]: + self.set_channel(j[1],j[2]) + pass + else: + value = j[5]*pow(j[6]/j[5], j[2]/j[6]) + self.set_channel(j[1], value) + for j in self.spins: + self.set_channel(j[1],j[2]) + + def widgets_reset(self): + """ resets widget to channel values""" + for j in self.buttons: + self.button_setvalue(j[0], j[2]) + self.set_channel(j[1],j[2]) + for j in self.sliders: + j[0].set_value(j[2]) + j[0].emit("value_changed") + for j in self.spins: + j[0].set_value(j[2]) + j[0].emit("value_changed") + + def channels_save(self): + """ Saves a list with channel names and current values. + Returns a list of tuples (channel_name, channel_value)""" + chan_list = [] + for i in self.channel_widgets: + for j in i: + if(j[1] != "pause"): + if(j[1] != "play"): + if(j[1] != "reset"): + chan_list.append((j[1],j[2])); + return chan_list + + def channels_load(self, chan_list): + """ Loads a list with channel names and values into the + current channel list """ + for i in self.channnel_widgets: + for j in i: + cnt = 0 + while(j[1] == chan_list[cnt][0]): + j[1] = chann_list[cnt][0] + j[2] = chan_list[cnt][1] + cnt = cnt+1 + self.widgets_reset() + + def set_channel_metadata(self): + """ Saves channel data as metadata. Can be called in + write_file() to save channel/widget data """ + mdata = self.channels_save() + for i in mdata: + self.window.metadata['channel-'+i[0]] = str(i[1]) + + def get_channel_metadata(self): + """ Retrieves channel data from metadata. Can be called after + widgets have been created to retrieve channel data and + reset widgets """ + for i in self.channel_widgets: + for j in i: + mdata = self.window.metadata.get('channel-'+j[1],'0') + if mdata is None: continue + else: + try: j[2] = float(mdata) + except: j[2] = mdata + self.widgets_reset() + + + def nofocus(self): + pass + + def focus_connect(self): + if not self.connected: + self.focus = True + self.in_id = self.window.connect('focus_in_event', self.focus_in) + self.out_id = self.window.connect('focus_out_event', self.focus_out) + self.connected = True + + def focus_disconnect(self): + if self.connected: + self.window.disconnect(self.in_id) + self.window.disconnect(self.out_id) + self.connected = False + + def __init__(self,act,colour=(-1,-1,-1),vert=True,toolbox=None): + """Constructor + act: activity object + colour: bg colour RGB tuple (R,G, B) + vert: True for vertical topmost arrangement, horiz. otherwise + toolbox: activity toolbox object, if None (default) a + standard toolbox will be supplied""" + self.sliders = [] + self.slids = 0 + self.spins = [] + self.spinbs = 0 + self.buttons = [] + self.butts = 0 + self.cbbuttons = [] + self.cbbutts = 0 + self.mbuttons = [] + self.mbutts = 0 + self.boxes = [] + self.ndisps = [] + self.ndispwids = 0 + self.connected = False + self.channel_widgets = [self.sliders, self.spins, self.buttons] + self.filenames = dict() + self.window = act + if toolbox == None: + self.toolbox = activity.ActivityToolbox(self.window) + else: self.toolbox = toolbox + self.window.set_toolbox(self.toolbox) + self.toolbox.show() + if colour[0] >= 0: + self.window.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(colour[0],colour[1],colour[2], 0)) + if vert: self.outbox = gtk.VBox() + else: self.outbox = gtk.HBox() + self.window.set_canvas(self.outbox) + self.data_path = os.path.join(act.get_activity_root(),"data/") + self.outbox.show() + self.logger = sugar.logger.logging.getLogger('csndsugui') + + +class CsoundGUI(BasicGUI): + """A class inheriting from BasicGUI containing a Csound instance and a performance + thread instance.""" + + def set_channel(self,name,val): + """overrides the base method. + sets the bus channel value, called by the widget callbacks + channel names 'play', 'pause' and + 'reset' are reserved for these respective uses""" + if not self.ready: + if name == "play": + self.play() + elif name == "pause": + self.pause() + elif name == "reset": + self.reset() + self.csound.SetChannel(name,val) + else: + BasicGUI.set_channel(self,name,val) + + def set_filechannel(self,chan,name): + """overrides the base method, setting the channel string""" + if not self.ready: + self.csound.SetChannel(chan,name) + else: + BasicGUI.set_filechannel(self,chan,name) + + def set_message(self, mess): + """overrides the base method, sends a score message""" + self.perf.InputMessage(mess) + + def set_focus(self): + """overrides the base class method, resetting/recompiling Csound""" + if self.focus: + self.compile() + self.channels_reinit() + if self.replay and not self.on: + self.play() + self.logger.debug("focus_off and playing") + else: + self.replay = self.on + self.logger.debug("focus_out and stopping") + self.reset() + return 1 + + def play(self): + """Starts a performance. """ + if not self.on: + if self.paused: return + self.on = True + self.perf.Play() + else: + self.on = False + self.perf.Pause() + + def pause(self): + """Pauses a performance. """ + if self.on: + self.on = False + self.paused = True + self.perf.Pause() + elif self.paused: + self.on = True + self.paused = False + self.perf.Play() + + def csd(self, name): + """Sets the source CSD and compiles it. + name: CSD filename + returns zero if successful""" + path = activity.get_bundle_path() + if self.ready: + res = self.csound.Compile("%s/%s" % (path,name)) + if not res: + self.ready = False + self.focus_connect() + self.path = path + self.name = name + return res + + def recompile(self): + """Recompiles the set CSD. + returns zero if successful""" + if not self.ready and self.name != "0": + self.perf.Stop() + self.perf.Join() + self.on = False + self.paused = False + self.perf = csnd.CsoundPerformanceThread(self.csound) + if self.arglist != None: + res = self.csound.Compile(self.arglist.argc(),self.arglist.argv()) + else: + res = self.csound.Compile("%s/%s" % (self.path,self.name)) + if(res): self.ready = True + return res + + def compile(self,name=None,args=[]): + """Compiles Csound code. + name: CSD filename if given + args: list of arguments (as strings) + returns 0 if successful , non-zero if not.""" + if self.ready: + if args != []: + self.arglist = csnd.CsoundArgVList() + self.path = activity.get_bundle_path() + if name != None: self.name = name + elif self.name == "0": return -1 + if self.arglist != None: + if name != None: + self.arglist.Append("csound") + self.arglist.Append("%s/%s" % (self.path,self.name)) + for i in args: + self.arglist.Append(i) + res = self.csound.Compile(self.arglist.argc(),self.arglist.argv()) + else: res = self.csound.Compile("%s/%s" % (self.path,self.name)) + if not res: + self.ready = False + self.focus_connect() + else: + self.arglist = None + return res + + def reset(self): + """Resets Csound, ready for a new CSD""" + if not self.ready: + self.perf.Stop() + self.perf.Join() + self.on = False + self.paused = False + self.perf = csnd.CsoundPerformanceThread(self.csound) + self.ready = True + + def close(self, event): + self.reset() + sys.exit(0) + + def tcallback(self,cbdata): + if self.stopcb: return False + if self.on and self.sync: + self.tcb(cbdata) + return True + + def set_timer(self,time,cb,cbdata,sync=True): + """Sets a timer callback, called at time intervals. + Sync=True makes it start/stop with Csound performance""" + if(self.stopcb == True): + self.sync = sync + self.tcb = cb + self.stopcb = False + gobject.timeout_add(time,self.tcallback,cbdata) + + def stop_timer(self): + """Stops the timer""" + self.stopcb = True + + def score_time(self): + """Returns the current score time""" + return self.csound.GetScoreTime() + + def __init__(self,act,colour=(-1,-1,-1),vert=True): + """constructor + act: activity object + colour: bg colour RGB tuple (R,G, B) + vert: True for vertical topmost arrangement, horiz. otherwise.""" + locale.setlocale(locale.LC_NUMERIC, 'C') + self.csound = csnd.Csound() + self.perf = csnd.CsoundPerformanceThread(self.csound) + BasicGUI.__init__(self,act,colour,vert) + self.ready = True + self.on = False + self.paused = False + self.name = "0" + self.arglist = None + self.replay = False + self.stopcb = True + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..77fda74 --- /dev/null +++ b/setup.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +from sugar.activity import bundlebuilder +if __name__ == "__main__": + bundlebuilder.start() diff --git a/soundin.0 b/soundin.0 new file mode 100644 index 0000000..d77b7d9 --- /dev/null +++ b/soundin.0 Binary files differ diff --git a/soundin.1 b/soundin.1 new file mode 100644 index 0000000..193b6a3 --- /dev/null +++ b/soundin.1 Binary files differ diff --git a/soundin.10 b/soundin.10 new file mode 100644 index 0000000..7857192 --- /dev/null +++ b/soundin.10 Binary files differ diff --git a/soundin.11 b/soundin.11 new file mode 100644 index 0000000..45c1e19 --- /dev/null +++ b/soundin.11 Binary files differ diff --git a/soundin.12 b/soundin.12 new file mode 100644 index 0000000..b45281a --- /dev/null +++ b/soundin.12 Binary files differ diff --git a/soundin.13 b/soundin.13 new file mode 100644 index 0000000..a1db87e --- /dev/null +++ b/soundin.13 Binary files differ diff --git a/soundin.14 b/soundin.14 new file mode 100644 index 0000000..544834a --- /dev/null +++ b/soundin.14 Binary files differ diff --git a/soundin.15 b/soundin.15 new file mode 100644 index 0000000..899a74c --- /dev/null +++ b/soundin.15 Binary files differ diff --git a/soundin.16 b/soundin.16 new file mode 100644 index 0000000..fd86d5b --- /dev/null +++ b/soundin.16 Binary files differ diff --git a/soundin.17 b/soundin.17 new file mode 100644 index 0000000..878bf23 --- /dev/null +++ b/soundin.17 Binary files differ diff --git a/soundin.18 b/soundin.18 new file mode 100644 index 0000000..878bf23 --- /dev/null +++ b/soundin.18 Binary files differ diff --git a/soundin.2 b/soundin.2 new file mode 100644 index 0000000..30499cb --- /dev/null +++ b/soundin.2 Binary files differ diff --git a/soundin.3 b/soundin.3 new file mode 100644 index 0000000..6b74455 --- /dev/null +++ b/soundin.3 Binary files differ diff --git a/soundin.4 b/soundin.4 new file mode 100644 index 0000000..e898e73 --- /dev/null +++ b/soundin.4 Binary files differ diff --git a/soundin.5 b/soundin.5 new file mode 100644 index 0000000..c0422e8 --- /dev/null +++ b/soundin.5 Binary files differ diff --git a/soundin.6 b/soundin.6 new file mode 100644 index 0000000..f0b0d46 --- /dev/null +++ b/soundin.6 Binary files differ diff --git a/soundin.7 b/soundin.7 new file mode 100644 index 0000000..878bf23 --- /dev/null +++ b/soundin.7 Binary files differ diff --git a/soundin.8 b/soundin.8 new file mode 100644 index 0000000..878bf23 --- /dev/null +++ b/soundin.8 Binary files differ -- cgit v0.9.1