Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorabhunkin <abhunkin@uncg.edu>2011-02-17 20:28:54 (GMT)
committer abhunkin <abhunkin@uncg.edu>2011-02-17 20:28:54 (GMT)
commitc5452df239f773300d7e93fdd9320f05dbb48ca0 (patch)
tree9b1243bb21be30ecb92c131f0579ee4f921a5c5e
Initial commit
-rwxr-xr-xFileMixAuto.csd195
-rwxr-xr-xFileMixAutoReadMe.txt160
-rwxr-xr-xMANIFEST12
-rwxr-xr-xactivity/activity-filemixauto.svg12
-rwxr-xr-xactivity/activity.info14
-rwxr-xr-xcsndsugui.py813
-rwxr-xr-xfilemixauto.py223
-rwxr-xr-xsetup.py4
-rwxr-xr-xsoundin.1bin0 -> 2586168 bytes
-rwxr-xr-xsoundin.2bin0 -> 2578752 bytes
-rwxr-xr-xsoundin.3bin0 -> 2620676 bytes
-rwxr-xr-xsoundin.4bin0 -> 2625648 bytes
12 files changed, 1433 insertions, 0 deletions
diff --git a/FileMixAuto.csd b/FileMixAuto.csd
new file mode 100755
index 0000000..4661512
--- /dev/null
+++ b/FileMixAuto.csd
@@ -0,0 +1,195 @@
+; FileMixAuto (2011) for realtime Csound5 - by Arthur B. Hunkins
+; 1-4 (user) soundfiles suitable for looping. Files may be mono or stereo;
+; can have different sample rates, may be a variety of different 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.
+; They must be named soundin.1 (through soundin.4), and in the same folder as this file,
+; or they may be loaded through the Journal (with Sugar 0.84/Strawberry or later).
+; Default soundfiles are the same as those in FileMix.
+
+<CsoundSynthesizer>
+<CsOptions>
+
+-odac -+rtaudio=alsa -m0d --expression-opt -b128 -B2048
+
+</CsOptions>
+<CsInstruments>
+
+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
+ga1 init 0
+ga2 init 0
+gktrig init 0
+
+gitemp ftgen 2, 0, 512, -5, 200, 512, sr / 13.2
+
+gifiles chnexport "Files", 1
+gimaxvol1 chnexport "MaxVol1", 1
+gimaxvol2 chnexport "MaxVol2", 1
+gimaxvol3 chnexport "MaxVol3", 1
+gimaxvol4 chnexport "MaxVol4", 1
+girandrate chnexport "RandRate", 1
+girandvol chnexport "RandVol", 1
+girandptch chnexport "RandPtch", 1
+girandpeak chnexport "RandPeak", 1
+girandfilt chnexport "RandFilt", 1
+gifiltshft chnexport "FiltShft", 1
+gifadedur chnexport "FadeDur", 1
+gitotaldur chnexport "TotalDur", 1
+
+ instr 1
+
+gimvol1 = gimaxvol1 * .1
+gimvol2 = gimaxvol2 * .1
+gimvol3 = gimaxvol3 * .1
+gimvol4 = gimaxvol4 * .1
+girrate = girandrate * .01
+girvol = girandvol * .1
+girptch = girandptch * .01
+girpeak = girandpeak * .1
+girfilt = girandfilt * .1
+gifshft = gifiltshft * .1
+gifade = gifadedur
+ if gifade > 0 igoto skip
+gifade = abs(gifade) * 60
+gifade = (gifade == 0? .01: gifade)
+skip:
+gitotal = gitotaldur * 60
+ if gitotal > 0 igoto skip2
+gitotal = abs(gitotal) * 60
+gitotal = (gitotal == 0? 30: gitotal)
+skip2:
+ event_i "i", 2, 0, gitotal
+ tabw_i gimvol1, 0, 3
+ event_i "i", 6, 0, gitotal
+ if gifiles == 1 igoto fin
+ event_i "i", 3, 0, gitotal
+ tabw_i gimvol2, 1, 3
+ if gifiles == 2 igoto fin
+ event_i "i", 4, 0, gitotal
+ tabw_i gimvol3, 2, 3
+ if gifiles == 3 igoto fin
+ event_i "i", 5, 0, gitotal
+ tabw_i gimvol4, 3, 3
+fin:
+ endin
+
+ instr 2, 3, 4, 5
+
+ if p1 != 2 goto cont
+Sname chnget "file1"
+i1 strcmp Sname, "0"
+ if i1 != 0 goto cont2
+Sname = "soundin.1"
+ goto cont2
+cont:
+ if p1 != 3 goto cont3
+Sname chnget "file2"
+i1 strcmp Sname, "0"
+ if i1 != 0 goto cont2
+Sname = "soundin.2"
+ goto cont2
+cont3:
+ if p1 != 4 goto cont4
+Sname chnget "file3"
+i1 strcmp Sname, "0"
+ if i1 != 0 goto cont2
+Sname = "soundin.3"
+ goto cont2
+cont4:
+Sname chnget "file4"
+i1 strcmp Sname, "0"
+ if i1 != 0 goto cont2
+Sname = "soundin.4"
+cont2:
+iamp tab_i p1 - 2, 3
+ if girrate > 0 goto skip
+kamp2 = iamp
+ goto skip2
+skip:
+kamp rspline iamp, 1 - girvol, girrate, girrate
+kamp2 table kamp * 512, 1
+kamp2 port kamp2, .01
+skip2:
+ichans filenchnls Sname
+ilen filelen Sname
+ipeak filepeak Sname
+imult = 32760 / gifiles / ipeak
+kamp2 = kamp2 * imult
+irand unirand ilen * .75
+ if girrate > 0 goto skip3
+kbase = 1
+ goto skip4
+skip3:
+kbase rspline girptch, -girptch, girrate, girrate
+kbase port 1 + kbase, .01
+skip4:
+ if ichans == 2 goto skip5
+a1 diskin2 Sname, kbase, irand, 1
+ goto skip6
+skip5:
+a1, a2 diskin2 Sname, kbase, irand, 1
+skip6:
+ if girrate > 0 goto skip7
+kfreq = 0
+ goto skip8
+skip7:
+kfreq rspline (-256 * girfilt) + (gifshft * 255), (255 * girfilt) + (gifshft * 254), girrate, girrate
+ if kfreq < 255 goto skip9
+kfreq = 255
+ goto skip8
+skip9:
+ if kfreq > -256 goto skip8
+kfreq = -256
+skip8:
+kfreq table 256 + kfreq, 2
+kfreq port kfreq, .01
+ if girrate > 0 goto skip10
+kres = .25
+ goto skip11
+skip10:
+kres rspline 0, .45 * girpeak, girrate, girrate
+kres port .25 + kres, .01
+skip11:
+a3,a4,a5 svfilter a1, kfreq, kres, 1
+ if ichans == 1 goto skip12
+a6,a7,a8 svfilter a2, kfreq, kres, 1
+skip12:
+kamp2 = kamp2 + (kamp2 * 3 * (kres - .25))
+ga1 = ga1 + (a5 * kamp2)
+ga2 = ga2 + ((ichans == 1? a5: a8) * kamp2)
+
+ endin
+
+ instr 6
+
+ if (gifade * 2) <= p3 goto skip
+gifade = p3 * .5
+skip:
+kamp linseg 0, gifade, 1, p3 - (gifade * 2) , 1, gifade, 0
+kamp2 table kamp * 512, 1
+kamp2 port kamp2, .01
+ outs ga1 * kamp2, ga2 * kamp2
+ga1 = 0
+ga2 = 0
+
+ endin
+
+</CsInstruments>
+
+<CsScore>
+
+f 1 0 512 16 0 512 .8 1
+f 3 0 4 -2 1 1 1 1
+i 1 0 .01
+
+e
+
+</CsScore>
+</CsoundSynthesizer>
+
diff --git a/FileMixAutoReadMe.txt b/FileMixAutoReadMe.txt
new file mode 100755
index 0000000..71a982e
--- /dev/null
+++ b/FileMixAutoReadMe.txt
@@ -0,0 +1,160 @@
+FILEMIXAUTO - Sugar Activity/Linux version - Notes
+Art Hunkins
+abhunkin@uncg.edu
+www.arthunkins.com
+
+
+Working with User Soundfiles/Objects
+
+FILEMIXAUTO is a self-playing, automatic version of the FileMix
+activity. FileMixAuto can handle 1-4 mono or stereo soundfiles. 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 <Enter>
+ yum update libsndfile <Enter>
+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 nature soundscapes. (This is the primary intent
+behind FileMixAuto. The four short "nature" files included here
+are abbreviated versions of those from the author's DUSK AT ST.
+FRANCIS SPRINGS [www.arthunkins.com].) Soundscapes can to set to play
+from 30 seconds up to 24 hours, and can be shut off whenever desired.
+They can be used as background for movement, pantomime, dramatic
+productions, meditation/relaxation, or just to create a mood.
+
+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 versions of Record seem to work well or fairly
+well: v86 with the XO-1.5 and the XO-1 upgraded to Sugar 0.84; and
+v64 with Sugar-on-a-Stick Strawberry (0.84). The XO-1.5 works *best*
+with v78 (strangely named the "Grabar" activity). No current Record
+versions work correctly with Sugar 0.86 and higher (as of 12/2010).
+Please note that Record prior to v74 (except for v61-64) produce ogg
+*speex* files; these files are *not* compatible with FileMixAuto.
+
+Soundfiles must be moved into the folder where this file resides,
+and be renamed soundin.1 through soundin.4. Alternatively, and more
+practically, however, user sound-files may be loaded from the Journal
+(Sugar 0.84 and later). In this case, only wav and ogg/vorbis formats
+are allowed.
+
+More advanced users may wish to record their soundfiles on some other
+system, and import their wav or ogg vorbis files into the Journal via
+a USB drive. (Display drive contents in Journal view, and drag your
+file onto the Journal icon.)
+
+Otherwise, advanced users can also 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 <Enter>
+ yum import audacity <Enter>
+ ...
+ audacity <Enter>
+(you are now running Audacity from the Terminal).
+
+When you are finished recording and editing (including auditioning the
+file in loop mode), "Export" the file in wav or ogg vorbis format,
+saving it to a USB drive with appropriate filename. Exit audacity. In
+the Journal, display the contents of your USB drive, and drag your
+newly-recorded file onto the Journal icon. It is now ready for
+FileMixAuto.
+
+
+The (Random) Controls
+
+A number of controls, all of which are set prior to performance, allow
+for independent random variation of the (up to) 4 soundfiles. Files of
+slightly varied duration (the default soundfiles are excellent
+examples), that begin at different random points within the file,
+create an ever-changing texture. If the loops are carefully and
+continuously made, and striking events avoided, the resultant
+soundscapes should be both seamless and modestly interesting.
+
+1) Number of files: self-explanatory (soundin.1 is file #1). If you
+don't need all 4, reduce the number, as it is less work for the
+computer. This is not crucial, however, as the volumes of unwanted
+files can be reduced to zero. If you do not select one of your own
+files, the corresponding default file will play.
+
+2) Maximum Volume, files 1-4: here is where you set basic mix levels.
+The files never get relatively louder than this.
+
+3) Random Rate: the common random speed at which random changes occur.
+A rate of zero is no change in speed at all. The fastest rate is two
+changes per second. All changes are gradual and "rounded"; as a result,
+the periodic "points" of change are not noticeable. What is heard is a
+kind of "average random rate" of change.
+
+4) Random Volume Change: changes of volume for the four soundfiles are
+independent. Particularly important: the AMOUNT of change (up to 100%)
+is DOWNWARD from (or below) maximum volume for each file.
+
+5) Random Pitch Change: random pitch variation up to 10% above AND
+BELOW the original pitch.
+
+6) Random Filter Peak (Resonance): variation in the center frequency
+strength of the filter. A higher peak concentrates the sound around the
+central filter frequency. Zero represents minimum filtering and a sound
+closest to the original.
+
+7) Random Filter Frequency: variation AROUND the center point of the
+filter (both up and down). Works in conjunction with #8.
+
+8) Filter (center point) Shift: this is NOT a random variation. The
+center point of the filter is moved up or down by a fixed amount. Works
+in conjunction with #7.
+
+9) Fade In/Out Duration: duration of initial fadein and final fadeout.
+These durations are included in Total Duration (below). Positive
+numbers are SECONDS, negative numbers are MINUTES. Range: 0 seconds to
+10 minutes.
+
+1) Total Duration: self-explanatory. All files begin and end at the
+same time (though they start at different locations within; see below).
+Though the performance will conclude at the time specified, it can
+always be halted sooner by hitting STOP (in which case, of course,
+there is no final fade). When a performance concludes naturally, the
+START button immediately reappears.
+
+Note: Every file begins from a different random position each time a
+performance is started.
+
+Additional observation: Although the controllers display only integer
+values, the buttons may be clicked and decimal - or other alternate
+numbers - inserted. (This may be particularly appropriate for Total
+Duration.) Upon hitting START, the display returns to the closest
+integer, but the chosen value will remain until changed.
+
+
+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 the
+FileMixAuto.csd file, using a text editor.
+
+
+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). Stereo headphones (an inexpensive set will work fine) or
+external amplifier/speaker system are highly recommended.
+
+
+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.)
diff --git a/MANIFEST b/MANIFEST
new file mode 100755
index 0000000..c3f6479
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,12 @@
+csndsugui.py
+filemixauto.py
+FileMixAuto.csd
+activity/activity.info
+activity/activity-filemixauto.svg
+setup.py
+soundin.1
+soundin.2
+soundin.3
+soundin.4
+FileMixAutoReadMe.txt
+
diff --git a/activity/activity-filemixauto.svg b/activity/activity-filemixauto.svg
new file mode 100755
index 0000000..a765535
--- /dev/null
+++ b/activity/activity-filemixauto.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" ?><!-- Created with Inkscape (http://www.inkscape.org/) --><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#666666">
+ <!ENTITY fill_color "#ffffff">
+]><svg height="48" id="svg2" version="1.0" width="48" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
+ <defs id="defs10"/>
+ <g id="g2177" transform="matrix(0.7841945,0,0,0.7841945,0.2187336,0.6636294)">
+ <path d="M 34.058322,51.120439 C 35.957706,53.219362 38.477558,52.472662 40.780575,51.007139 C 46.023315,47.607712 52.377326,44.171749 50.70658,42.508548 C 48.953496,39.868899 47.826928,38.157142 44.438323,34.206958 C 43.053105,32.509048 36.441408,29.467159 42.479715,24.497365 C 48.648328,19.419861 54.947536,19.133922 55.823633,22.650134 C 56.956344,27.517369 48.052633,30.41306 48.774426,32.014697 C 51.024789,36.838396 56.163323,40.521258 58.878954,45.136952 C 58.985452,45.317968 58.315124,46.07044 57.866559,46.461775 C 56.772808,47.55083 40.454089,58.326464 39.067674,58.743849 C 38.61911,59.135184 37.581032,59.351392 37.271196,59.403604 C 34.666931,59.842454 31.626978,55.896219 31.079221,54.994931 C 29.326131,52.355283 28.245043,50.627188 24.856443,46.67701 C 23.471224,44.97909 16.814052,41.95354 22.852347,36.983745 C 29.020957,31.906246 35.320175,31.620301 36.196275,35.136507 C 37.328987,40.003751 28.470752,42.883113 29.192547,44.484748 C 30.317731,46.896591 32.158942,49.021523 34.058322,51.120439 z" id="path2387" style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1.83669972"/>
+ <path d="M 54.281488,16.060986 C 55.198233,13.145652 53.380907,11.596495 51.047238,10.694778 C 45.68046,8.683577 39.763711,5.7969643 39.114589,8.2411835 C 37.665073,11.393143 36.720288,13.428817 34.90816,18.764927 C 34.095983,21.000843 34.568163,28.34917 27.48111,26.725147 C 20.240721,25.066374 17.055581,20.155909 19.667349,17.138208 C 23.319975,13.025767 29.962412,18.330676 31.001474,16.694915 C 34.095152,11.7065 34.861036,5.129612 37.55891,-0.098788352 C 37.664709,-0.30383194 38.623929,-0.25535964 39.169432,-0.14925797 C 40.615375,0.020918594 57.487264,6.1533597 58.492792,7.0014005 C 59.038293,7.1075072 59.708428,7.8057045 59.897863,8.0221892 C 61.490138,9.841792 59.518699,14.893131 59.000059,15.92822 C 57.550538,19.080183 56.570505,21.089762 54.758373,26.42587 C 53.94619,28.661785 54.453637,36.036202 47.366581,34.412178 C 40.12619,32.753406 36.941047,27.84294 39.552812,24.825237 C 43.205452,20.712801 49.81263,25.991625 50.851701,24.355864 C 52.398533,21.861654 53.364751,18.976316 54.281488,16.060986 z" id="path5" style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1.83821511"/>
+ <path d="M 5.8946454,44.279961 C 5.4438624,47.293699 7.3295291,48.723371 9.6037146,49.460441 C 14.82527,51.09236 20.672506,53.567729 20.940261,51.049925 C 21.850348,47.757407 22.446074,45.630319 23.394885,40.104087 C 23.841215,37.78458 22.411028,30.39565 29.166619,31.508176 C 36.068323,32.644374 39.670411,37.368084 37.670314,40.611581 C 34.85852,45.038706 28.014685,40.174173 27.277885,41.904263 C 25.100148,47.174729 25.284139,53.876542 23.503906,59.359961 C 23.434088,59.575007 22.542928,59.597533 22.025494,59.530995 C 20.668987,59.466839 4.2794392,54.528737 3.2373364,53.746891 C 2.7199063,53.680348 2.0073804,53.024904 1.8033706,52.820304 C 0.088654182,51.100622 1.2229281,45.850224 1.5611077,44.765802 C 2.4711863,41.473286 3.1029519,39.369924 4.0517655,33.843705 C 4.4981018,31.524195 3.0318651,24.111533 9.7874609,25.22405 C 16.689162,26.360261 20.291249,31.083972 18.291155,34.327462 C 15.479367,38.754579 8.6715695,33.91379 7.9347644,35.643872 C 6.845901,38.279103 6.3454303,41.266225 5.8946454,44.279961 z" id="path2389" style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1.78393352"/>
+ <path d="M 24.399365,8.8326613 C 22.873602,6.2858475 20.464273,6.3771311 18.176842,7.2300679 C 12.962723,9.2338606 6.7179907,10.990231 8.0800672,13.05058 C 9.411701,16.091445 10.265745,18.060776 12.965377,22.806465 C 14.059887,24.828127 19.836137,29.503025 13.715783,32.847133 C 7.4632924,36.263854 1.6127592,34.938762 1.1869049,31.260134 C 0.67134036,26.188456 9.2126071,25.613075 8.7204885,23.855174 C 7.1676612,18.54132 2.8222561,13.612158 0.81692205,8.3840814 C 0.73827839,8.1790488 1.4396126,7.6105305 1.8966919,7.340343 C 3.0259029,6.5490278 19.276761,0.12084851 20.603089,0.064186246 C 21.060166,-0.20600027 22.042745,-0.15378499 22.334675,-0.12609447 C 24.788416,0.10665278 27.166378,4.7595663 27.574107,5.7848912 C 28.905747,8.8257561 29.716002,10.799537 32.415619,15.545229 C 33.510138,17.566891 39.330183,22.237339 33.209825,25.58144 C 26.957335,28.998161 21.106798,27.673074 20.680948,23.994443 C 20.165384,18.922766 28.662856,18.35184 28.17074,16.593936 C 27.39432,13.93701 25.925126,11.379473 24.399365,8.8326613 z" id="path2391" style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1.77659035"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/activity/activity.info b/activity/activity.info
new file mode 100755
index 0000000..09cdde3
--- /dev/null
+++ b/activity/activity.info
@@ -0,0 +1,14 @@
+[Activity]
+
+name = FileMixAuto
+
+bundle_id = org.laptop.FileMixAuto
+icon = activity-filemixauto
+
+activity_version = 1
+
+host_version = 1
+
+show_launcher = yes
+exec = sugar-activity filemixauto.FileMixAuto
+license = CC-by-SA 3.0
diff --git a/csndsugui.py b/csndsugui.py
new file mode 100755
index 0000000..c8a404d
--- /dev/null
+++ b/csndsugui.py
@@ -0,0 +1,813 @@
+# 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, 2))
+ 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(0xFFFF,0,0, 1))
+ i[0].modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0xFFFF,0,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=""):
+ """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_PRELIGHT, gtk.gdk.Color(0x8000,0x8000,0x8000, 2))
+ box.pack_start(butt, False, False, 1)
+ self.buttons.append([butt,title,0])
+ butt.connect("clicked", self.buttcallback)
+ 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/filemixauto.py b/filemixauto.py
new file mode 100755
index 0000000..2ccfbe8
--- /dev/null
+++ b/filemixauto.py
@@ -0,0 +1,223 @@
+# FILEMIXAUTO - Audio File Looper/Mixer/Processor for Children (2011)
+# Art Hunkins (www.arthunkins.com)
+#
+# FileMixAuto 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
+import gobject
+from sugar.activity import activity
+from sugar.graphics.objectchooser import ObjectChooser
+from sugar import mime
+import gtk
+import os
+
+class FileMixAuto(activity.Activity):
+
+ def __init__(self, handle):
+
+ activity.Activity.__init__(self, handle)
+
+ red = (0xDDDD, 0, 0)
+ brown = (0x6600, 0, 0)
+ green = (0, 0x5500, 0)
+ self.path1 = "0"
+ self.path2 = "0"
+ self.path3 = "0"
+ self.path4 = "0"
+ self.jobject1 = None
+ self.jobject2 = None
+ self.jobject3 = None
+ self.jobject4 = None
+
+ 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("<big><b><big><u>FILEMIXAUTO</u> - Audio File Looper/Mixer/Processor \
+for Children (2011)</big></b>\n\
+\t\t\t\t Art Hunkins (www.arthunkins.com)</big>", all)
+
+ win.text("Loop and process 1 to 4 mono/stereo files; \
+wav and ogg vorbis formats only (no ogg vorbis on Sugar 0.84).\n\
+ User sound files must be placed in Journal (Record activity \
+does this). <b>No user files on Sugar 0.82</b> (original XO-1).\n\
+ The default files are abbreviated versions of those from the author's \
+<b>DUSK AT ST. FRANCIS SPRINGS</b>.\n\
+You are urged to create your own sound files suitable for looping, for example, \
+with the Record activity - \n especially nature soundscapes - to set a mood \
+or accompany movement, drama, pantomime, etc.", all, brown)
+
+ self.b2box = win.box(False, all)
+ bbox = win.box(False, all)
+ self.bb = bbox
+ self.w = win
+ self.r = red
+ self.g = green
+ self.br = brown
+ self.p = False
+
+ try:
+ from jarabe import config
+ version = [int(i) for i in config.version.split('.')][:2]
+ except ImportError:
+ version = [0, 82]
+ if version >= [0, 84]:
+ boxa = win.box(False, self.b2box)
+ boxb = win.box(False, self.b2box)
+ boxc = win.box(True, boxb)
+ boxd = win.box(False, boxc)
+ boxe = win.box(True, boxc)
+ win.text("\t\tOptionally, select your own <b>audio</b> file(s) from the Journal.\n\
+\t\tDeselect a file by choosing another, or by closing Journal.\n\
+\t\tSuggestion: create files with Record v64/v78/v86 or Audacity. ", boxa, green)
+ win.text("Select File(s):", boxd, brown)
+ but5 = win.cbbutton(boxd, self.choose1, " 1 ")
+ but5.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0x6600, 0, 0))
+ but5.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0x6600, 0, 0))
+ but6 = win.cbbutton(boxd, self.choose2, " 2 ")
+ but6.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0x6600, 0, 0))
+ but6.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0x6600, 0, 0))
+ but7 = win.cbbutton(boxd, self.choose3, " 3 ")
+ but7.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0x6600, 0, 0))
+ but7.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0x6600, 0, 0))
+ but8 = win.cbbutton(boxd, self.choose4, " 4 ")
+ but8.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0x6600, 0, 0))
+ but8.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0x6600, 0, 0))
+ but9 = win.cbbutton(boxe, self.auto, " CLICK when selections made ")
+ but9.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0, 0x7700, 0))
+ but9.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(0, 0x7700, 0))
+ else:
+ self.auto(self)
+
+ def choose1(self, widget):
+ chooser = ObjectChooser(parent=self, what_filter=mime.GENERIC_TYPE_AUDIO)
+ result = chooser.run()
+ if result == gtk.RESPONSE_ACCEPT:
+ self.jobject1 = chooser.get_selected_object()
+ self.path1 = str(self.jobject1.get_file_path())
+ else:
+ self.jobject1 = None
+ self.path1 = "0"
+
+ def choose2(self, widget):
+ chooser = ObjectChooser(parent=self, what_filter=mime.GENERIC_TYPE_AUDIO)
+ result = chooser.run()
+ if result == gtk.RESPONSE_ACCEPT:
+ self.jobject2 = chooser.get_selected_object()
+ self.path2 = str(self.jobject2.get_file_path())
+ else:
+ self.jobject2 = None
+ self.path2 = "0"
+
+ def choose3(self, widget):
+ chooser = ObjectChooser(parent=self, what_filter=mime.GENERIC_TYPE_AUDIO)
+ result = chooser.run()
+ if result == gtk.RESPONSE_ACCEPT:
+ self.jobject3 = chooser.get_selected_object()
+ self.path3 = str(self.jobject3.get_file_path())
+ else:
+ self.jobject3 = None
+ self.path3 = "0"
+
+ def choose4(self, widget):
+ chooser = ObjectChooser(parent=self, what_filter=mime.GENERIC_TYPE_AUDIO)
+ result = chooser.run()
+ if result == gtk.RESPONSE_ACCEPT:
+ self.jobject4 = chooser.get_selected_object()
+ self.path4 = str(self.jobject4.get_file_path())
+ else:
+ self.jobject4 = None
+ self.path4 = "0"
+
+ def send_data(self):
+ self.w.set_filechannel("file1", self.path1)
+ self.w.set_filechannel("file2", self.path2)
+ self.w.set_filechannel("file3", self.path3)
+ self.w.set_filechannel("file4", self.path4)
+
+ def playcsd(self, widget):
+ def start(self):
+ self.p = True
+ self.w.play()
+ gobject.timeout_add(500, checkstat)
+ 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))
+ def stop(self):
+ self.p = False
+ self.w.recompile()
+ self.w.channels_reinit()
+ self.send_data()
+ 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 checkstat():
+ if self.w.perf.GetStatus() == 2:
+ stop(self)
+ return True
+ if self.p == False:
+ start(self)
+ else:
+ stop(self)
+
+ def auto(self, widget):
+ self.b2box.destroy()
+ 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(" <b>FileMixAuto</b> - <i>Select Options First</i> ",\
+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.b7 = self.w.box(True, self.f)
+ self.w.csd("FileMixAuto.csd")
+ self.w.spin(4, 1, 4, 1, 1, self.b1, 0, "Files", "# of Files")
+ self.w.spin(0, 0, 50, 1, 5, self.b1, 0, "RandRate", "Random Rate\n\
+[0=no change]")
+ self.w.spin(10, 0, 10, 1, 1, self.b2, 0, "MaxVol1", "Max Vol/File1 ")
+ self.w.spin(10, 0, 10, 1, 1, self.b2, 0, "MaxVol2", "Max Vol/File2 \n\
+ Random Vol")
+ self.w.spin(10, 0, 10, 1, 1, self.b3, 0, "MaxVol3", "Max Vol/File3 ")
+ self.w.spin(10, 0, 10, 1, 1, self.b3, 0, "MaxVol4", " Max Vol/File4 \n\
+is < Max Vol")
+ self.w.spin(0, 0, 10, 1, 1, self.b4, 0, "RandVol", "Random Volume")
+ self.w.spin(0, 0, 10, 1, 1, self.b4, 0, "RandPtch", "Random Pitch\n\
+[+/- 10% max]")
+ self.w.spin(0, 0, 10, 1, 1, self.b5, 0, "RandPeak", "Random Peak")
+ self.w.spin(0, 0, 10, 1, 1, self.b5, 0, "RandFilt", "Random Filter")
+ self.w.spin(0, -10, 10, 1, 1, self.b6, 0, "FiltShft", "Filter Center +/-")
+ self.w.spin(5, -10, 60, 1, 10, self.b6, 0, "FadeDur", " Fade Duration\n\
+[+ = secs;- = mins]")
+ self.dur = self.w.spin(1, -24, 60, 1, 10, self.b7, 0, "TotalDur",\
+" Total Duration\n[+ = mins;- = hrs;\n 0 = 30 secs]")
+ self.send_data()
+ self.but = self.w.cbbutton(self.b7, 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))
diff --git a/setup.py b/setup.py
new file mode 100755
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.1 b/soundin.1
new file mode 100755
index 0000000..11d9695
--- /dev/null
+++ b/soundin.1
Binary files differ
diff --git a/soundin.2 b/soundin.2
new file mode 100755
index 0000000..94abfe5
--- /dev/null
+++ b/soundin.2
Binary files differ
diff --git a/soundin.3 b/soundin.3
new file mode 100755
index 0000000..35a6398
--- /dev/null
+++ b/soundin.3
Binary files differ
diff --git a/soundin.4 b/soundin.4
new file mode 100755
index 0000000..9b981c0
--- /dev/null
+++ b/soundin.4
Binary files differ